diff --git a/.editorconfig b/.editorconfig index 58d0d332bb..370c3f9dee 100644 --- a/.editorconfig +++ b/.editorconfig @@ -9,9 +9,10 @@ indent_style = space tab_width = 4 # New line preferences -end_of_line = crlf:suggestion +#end_of_line = crlf insert_final_newline = true trim_trailing_whitespace = true +max_line_length = 120 #### .NET Coding Conventions #### @@ -71,7 +72,7 @@ csharp_style_expression_bodied_constructors = false:suggestion #csharp_style_expression_bodied_indexers = true:silent #csharp_style_expression_bodied_lambdas = true:silent #csharp_style_expression_bodied_local_functions = false:silent -csharp_style_expression_bodied_methods = false:suggestion +csharp_style_expression_bodied_methods = true:suggestion #csharp_style_expression_bodied_operators = false:silent csharp_style_expression_bodied_properties = true:suggestion @@ -104,7 +105,6 @@ csharp_preferred_modifier_order = public, private, protected, internal, new, abs # 'using' directive preferences csharp_using_directive_placement = outside_namespace:silent -csharp_style_namespace_declarations = file_scoped:suggestion #### C# Formatting Rules #### @@ -337,7 +337,11 @@ dotnet_naming_symbols.type_parameters_symbols.applicable_kinds = type_parameter # ReSharper properties resharper_braces_for_ifelse = required_for_multiline +resharper_csharp_wrap_arguments_style = chop_if_long +resharper_csharp_wrap_parameters_style = chop_if_long resharper_keep_existing_attribute_arrangement = true +resharper_wrap_chained_binary_patterns = chop_if_long +resharper_wrap_chained_method_calls = chop_if_long [*.{csproj,xml,yml,yaml,dll.config,msbuildproj,targets,props}] indent_size = 2 diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index 2dc3757588..0000000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,7 +0,0 @@ -contact_links: - - name: Report a Security Vulnerability - url: https://github.com/space-wizards/space-station-14/blob/master/SECURITY.md - about: Please report security vulnerabilities to the Space Wizards privately so they can fix them before they are publicly disclosed. - - name: Toolshed Feature Request - url: https://github.com/space-wizards/space-station-14/issues/new?assignees=moonheart08&labels=Toolshed&projects=&template=toolshed-feature-request.md&title=%5BTOOLSHED+REQUEST%5D - about: Suggest a feature for Toolshed (for game admins/developers) diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index aba549332f..51f9de8baf 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,8 +1,8 @@ --- -name: Request a Feature -about: "Template for noting future planned features. Please ask for approval in the Discord if you aren't an organization Member before posting a feature request" +name: Request a feature +about: "Please outline your request in Discord first if you aren't a maintainer." title: '' -labels: '' +labels: ["Type: Feature"] assignees: '' --- diff --git a/.github/ISSUE_TEMPLATE/issue_report.md b/.github/ISSUE_TEMPLATE/issue_report.md index ab82181197..c74f24554a 100644 --- a/.github/ISSUE_TEMPLATE/issue_report.md +++ b/.github/ISSUE_TEMPLATE/issue_report.md @@ -1,8 +1,8 @@ --- -name: Report an Issue -about: "Any general issues you have during play or with the codebase" +name: Report an issue +about: "Any issues found in gameplay or the codebase" title: '' -labels: '' +labels: 'Type: Bug' assignees: '' --- diff --git a/.github/labeler.yml b/.github/labeler.yml index eb01eeecc4..4cfe775ed4 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -1,39 +1,56 @@ "Changes: Audio": - - "**/*.ogg" - + - changed-files: + - any-glob-to-any-file: "**/*.ogg" + "Changes: C#": - - "**/*.cs" + - changed-files: + - any-glob-to-any-file: "**/*.cs" "Changes: Config": - - "**/*.toml" - - "**/*.config" - - "*.json" - - ".github/*.yml" - - ".github/*.json" - - ".vscode/*.json" - - ".editorconfig" +- changed-files: + - any-glob-to-any-file: + - "**/*.toml" + - "**/*.config" + - "*.json" + - ".github/*.yml" + - ".github/*.json" + - ".vscode/*.json" + - ".editorconfig" "Changes: Documentation": - - "**/*.xml" - - "**/*.md" + - changed-files: + - any-glob-to-any-file: + - "**/*.xml" + - "**/*.md" "Changes: Localization": -- 'Resources/Locale/**/*.ftl' + - changed-files: + - any-glob-to-any-file: 'Resources/Locale/**/*.ftl' "Changes: Map": - - "Resources/Maps/**/*.yml" - - "Resources/Prototypes/Maps/**/*.yml" + - changed-files: + - any-glob-to-any-file: + - "Resources/Maps/**/*.yml" + - "Resources/Prototypes/Maps/**/*.yml" "Changes: Sprite": - - "**/*.rsi/*.png" - - "**/*.rsi/*.json" + - changed-files: + - any-glob-to-any-file: + - "**/*.rsi/*.png" + - "**/*.rsi/*.json" "Changes: UI": - - "**/*.xaml*" + - changed-files: + - any-glob-to-any-file: "**/*.xaml*" "Changes: YML": - - any: ["**/*.yml"] - all: ["!Resources/Maps/**/*.yml", "!Resources/Prototypes/Maps/**/*.yml"] + - changed-files: + - any-glob-to-any-file: + - "**/*.yml" + - all-globs-to-all-files: + - "!Resources/Maps/**/*.yml" + - "!Resources/Prototypes/Maps/**/*.yml" "Changes: Workflow": - - ".github/workflows/*.yml" + - changed-files: + - any-glob-to-any-file: ".github/workflows/*.yml" diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index 877273d764..79cfdd83cd 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -16,41 +16,40 @@ jobs: permissions: contents: write steps: - - name: Checkout Master - uses: actions/checkout@v3 - with: - token: ${{ secrets.BOT_TOKEN }} - ref: "${{ vars.CHANGELOG_BRANCH }}" + - name: Checkout Master + uses: actions/checkout@v3 + with: + token: ${{ secrets.BOT_TOKEN }} + ref: ${{ vars.CHANGELOG_BRANCH }} - - name: Setup Git - run: | - git config --global user.name "${{ vars.CHANGELOG_USER }}" - git config --global user.email "${{ vars.CHANGELOG_EMAIL }}" - shell: bash + - name: Setup Git + run: | + git config --global user.name "${{ vars.CHANGELOG_USER }}" + git config --global user.email "${{ vars.CHANGELOG_EMAIL }}" + shell: bash - - name: Setup Node - uses: actions/setup-node@v3 - with: - node-version: 18.x + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version: 18.x - - name: Install Dependencies - run: | - cd "Tools/changelogs" - npm install - shell: bash - continue-on-error: true + - name: Install Dependencies + run: | + cd "Tools/changelogs" + npm install + shell: bash - - name: Generate Changelog - run: | - cd "Tools/changelogs" - node changelog.js - shell: bash - continue-on-error: true + - name: Generate Changelog + run: | + cd "Tools/changelogs" + node changelog.js + shell: bash - - name: Commit Changelog - run: | - git add *.yml - git commit -m "${{ vars.CHANGELOG_MESSAGE }} (#${{ env.PR_NUMBER }})" - git push - shell: bash - continue-on-error: true + - name: Commit Changelog + run: | + git pull origin master + git add *.yml + git commit -m "${{ vars.CHANGELOG_MESSAGE }} (#${{ env.PR_NUMBER }})" + git push + shell: bash + continue-on-error: true diff --git a/.github/workflows/conflict-labeler.yml b/.github/workflows/conflict-labeler.yml index 152d3a9f3c..1bba677022 100644 --- a/.github/workflows/conflict-labeler.yml +++ b/.github/workflows/conflict-labeler.yml @@ -1,18 +1,20 @@ name: Check Merge Conflicts on: - push: - branches: - - master pull_request_target: + types: + - opened + - synchronize + - reopened + - ready_for_review jobs: Label: - if: github.actor != 'PJBot' && github.actor != 'DeltaV-Bot' && github.actor != 'SimpleStation14' + if: ( github.event.pull_request.draft == false ) && ( github.actor != 'PJBot' && github.actor != 'DeltaV-Bot' && github.actor != 'SimpleStation14' ) runs-on: ubuntu-latest steps: - name: Check for Merge Conflicts - uses: ike709/actions-label-merge-conflict@9eefdd17e10566023c46d2dc6dc04fcb8ec76142 + uses: eps1lon/actions-label-merge-conflict@v3.0.0 with: dirtyLabel: "Status: Merge Conflict" repoToken: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/discord-changelog.yml b/.github/workflows/discord-changelog.yml new file mode 100644 index 0000000000..74be415432 --- /dev/null +++ b/.github/workflows/discord-changelog.yml @@ -0,0 +1,24 @@ +name: Discord Changelog + +on: + workflow_dispatch: + schedule: + - cron: '0 6 * * *' + +jobs: + publish_changelog: + runs-on: ubuntu-latest + steps: + + - name: checkout + uses: actions/checkout@v3 + with: + ref: master + + - name: Publish changelog + run: Tools/actions_changelogs_since_last_run.py + env: + CHANGELOG_DIR: ${{ vars.CHANGELOG_DIR }} + CHANGELOG_WEBHOOK: ${{ secrets.CHANGELOG_WEBHOOK }} + GITHUB_TOKEN: ${{ secrets.BOT_TOKEN }} + continue-on-error: true diff --git a/.github/workflows/labeler-pr.yml b/.github/workflows/labeler-pr.yml index efb051f4cc..2fd754b15e 100644 --- a/.github/workflows/labeler-pr.yml +++ b/.github/workflows/labeler-pr.yml @@ -6,8 +6,9 @@ on: jobs: labeler: if: github.actor != 'PJBot' && github.actor != 'DeltaV-Bot' && github.actor != 'SimpleStation14' + permissions: + contents: read + pull-requests: write runs-on: ubuntu-latest steps: - - uses: actions/labeler@v3 - with: - repo-token: "${{ secrets.GITHUB_TOKEN }}" + - uses: actions/labeler@v5 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 177e6a0fe6..d9cfd3b25b 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -41,39 +41,17 @@ jobs: - name: Package client run: dotnet run --project Content.Packaging client --no-wipe-release - - name: Update Build Info - run: Tools/gen_build_info.py - - - name: Shuffle files around - run: | - mkdir "release/${{ github.sha }}" - mv release/*.zip "release/${{ github.sha }}" - - - name: Upload files to centcomm - uses: appleboy/scp-action@master - with: - host: ${{ secrets.PUBLISH_HOST }} - username: ${{ secrets.PUBLISH_USER }} - key: ${{ secrets.PUBLISH_KEY }} - port: ${{ secrets.PUBLISH_PORT }} - source: "release/${{ github.sha }}" - target: "/var/www/builds.delta-v.org/delta-v/builds/" - strip_components: 1 - - - name: Update manifest JSON - uses: appleboy/ssh-action@master - with: - host: ${{ secrets.PUBLISH_HOST }} - username: ${{ secrets.PUBLISH_USER }} - key: ${{ secrets.PUBLISH_KEY }} - port: ${{ secrets.PUBLISH_PORT }} - script: /home/deltav/publish/push.ps1 ${{ github.sha }} - - - name: Publish changelog (Discord) - run: Tools/actions_changelogs_since_last_run.py + - name: Publish version + run: Tools/publish_multi_request.py env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - DISCORD_WEBHOOK_URL: ${{ secrets.CHANGELOG_DISCORD_WEBHOOK }} + PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }} + GITHUB_REPOSITORY: ${{ vars.GITHUB_REPOSITORY }} + + # - name: Publish changelog (Discord) + # run: Tools/actions_changelogs_since_last_run.py + # env: + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # DISCORD_WEBHOOK_URL: ${{ secrets.CHANGELOG_DISCORD_WEBHOOK }} - name: Publish changelog (RSS) run: Tools/actions_changelog_rss.py diff --git a/.github/workflows/update-credits.yml b/.github/workflows/update-credits.yml index 69a8bc1988..5dc6299c6c 100644 --- a/.github/workflows/update-credits.yml +++ b/.github/workflows/update-credits.yml @@ -19,6 +19,8 @@ jobs: - name: Get this week's Contributors shell: pwsh + env: + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} run: Tools/dump_github_contributors.ps1 > Resources/Credits/GitHub.txt # TODO diff --git a/Content.Benchmarks/MapLoadBenchmark.cs b/Content.Benchmarks/MapLoadBenchmark.cs index 7caa995836..cc41d62575 100644 --- a/Content.Benchmarks/MapLoadBenchmark.cs +++ b/Content.Benchmarks/MapLoadBenchmark.cs @@ -46,7 +46,7 @@ public async Task Cleanup() PoolManager.Shutdown(); } - public static readonly string[] MapsSource = { "Empty", "Box", "Aspid", "Bagel", "Dev", "CentComm", "Atlas", "Core", "TestTeg", "Saltern", "Packed", "Omega", "Cluster", "Gemini", "Reach", "Origin", "Meta", "Marathon", "Europa", "MeteorArena", "Fland", "Barratry" }; + public static readonly string[] MapsSource = { "Empty", "Dev", "CentComm", "TestTeg", }; [ParamsSource(nameof(MapsSource))] public string Map; diff --git a/Content.Benchmarks/SpawnEquipDeleteBenchmark.cs b/Content.Benchmarks/SpawnEquipDeleteBenchmark.cs index de51b2fb19..8512107b69 100644 --- a/Content.Benchmarks/SpawnEquipDeleteBenchmark.cs +++ b/Content.Benchmarks/SpawnEquipDeleteBenchmark.cs @@ -58,7 +58,7 @@ await _pair.Server.WaitPost(() => for (var i = 0; i < N; i++) { _entity = server.EntMan.SpawnAttachedTo(Mob, _coords); - _spawnSys.EquipStartingGear(_entity, _gear, null); + _spawnSys.EquipStartingGear(_entity, _gear); server.EntMan.DeleteEntity(_entity); } }); diff --git a/Content.Client/Access/IdCardSystem.cs b/Content.Client/Access/IdCardSystem.cs index fcf2bf57de..e0c02976f7 100644 --- a/Content.Client/Access/IdCardSystem.cs +++ b/Content.Client/Access/IdCardSystem.cs @@ -2,6 +2,4 @@ namespace Content.Client.Access; -public sealed class IdCardSystem : SharedIdCardSystem -{ -} +public sealed class IdCardSystem : SharedIdCardSystem; diff --git a/Content.Client/Access/UI/AgentIDCardBoundUserInterface.cs b/Content.Client/Access/UI/AgentIDCardBoundUserInterface.cs index 73f18aec8d..c3fac8cb92 100644 --- a/Content.Client/Access/UI/AgentIDCardBoundUserInterface.cs +++ b/Content.Client/Access/UI/AgentIDCardBoundUserInterface.cs @@ -40,9 +40,9 @@ private void OnJobChanged(string newJob) SendMessage(new AgentIDCardJobChangedMessage(newJob)); } - public void OnJobIconChanged(string newJobIcon) + public void OnJobIconChanged(string newJobIconId) { - SendMessage(new AgentIDCardJobIconChangedMessage(newJobIcon)); + SendMessage(new AgentIDCardJobIconChangedMessage(newJobIconId)); } /// @@ -57,7 +57,7 @@ protected override void UpdateState(BoundUserInterfaceState state) _window.SetCurrentName(cast.CurrentName); _window.SetCurrentJob(cast.CurrentJob); - _window.SetAllowedIcons(cast.Icons); + _window.SetAllowedIcons(cast.Icons, cast.CurrentJobIconId); } protected override void Dispose(bool disposing) diff --git a/Content.Client/Access/UI/AgentIDCardWindow.xaml.cs b/Content.Client/Access/UI/AgentIDCardWindow.xaml.cs index beca0c41ba..9a38c0c485 100644 --- a/Content.Client/Access/UI/AgentIDCardWindow.xaml.cs +++ b/Content.Client/Access/UI/AgentIDCardWindow.xaml.cs @@ -38,7 +38,7 @@ public AgentIDCardWindow(AgentIDCardBoundUserInterface bui) JobLineEdit.OnFocusExit += e => OnJobChanged?.Invoke(e.Text); } - public void SetAllowedIcons(HashSet icons) + public void SetAllowedIcons(HashSet icons, string currentJobIconId) { IconGrid.DisposeAllChildren(); @@ -79,6 +79,10 @@ public void SetAllowedIcons(HashSet icons) jobIconButton.AddChild(jobIconTexture); jobIconButton.OnPressed += _ => _bui.OnJobIconChanged(jobIcon.ID); IconGrid.AddChild(jobIconButton); + + if (jobIconId.Equals(currentJobIconId)) + jobIconButton.Pressed = true; + i++; } } diff --git a/Content.Client/Access/UI/IdCardConsoleBoundUserInterface.cs b/Content.Client/Access/UI/IdCardConsoleBoundUserInterface.cs index 5b7011c195..a321b4121e 100644 --- a/Content.Client/Access/UI/IdCardConsoleBoundUserInterface.cs +++ b/Content.Client/Access/UI/IdCardConsoleBoundUserInterface.cs @@ -1,6 +1,5 @@ using Content.Shared.Access; using Content.Shared.Access.Components; -using Content.Shared.Access; using Content.Shared.Access.Systems; using Content.Shared.Containers.ItemSlots; using Content.Shared.CrewManifest; diff --git a/Content.Client/Actions/ActionsSystem.cs b/Content.Client/Actions/ActionsSystem.cs index b992e77256..0bc65eb935 100644 --- a/Content.Client/Actions/ActionsSystem.cs +++ b/Content.Client/Actions/ActionsSystem.cs @@ -65,6 +65,7 @@ private void OnEntityTargetHandleState(EntityUid uid, EntityTargetActionComponen return; component.Whitelist = state.Whitelist; + component.Blacklist = state.Blacklist; component.CanTargetSelf = state.CanTargetSelf; BaseHandleState(uid, component, state); } @@ -248,7 +249,10 @@ public void TriggerAction(EntityUid actionId, BaseActionComponent action) if (action.ClientExclusive) { if (instantAction.Event != null) + { instantAction.Event.Performer = user; + instantAction.Event.Action = actionId; + } PerformAction(user, actions, actionId, instantAction, instantAction.Event, GameTiming.CurTime); } diff --git a/Content.Client/Administration/Components/HeadstandComponent.cs b/Content.Client/Administration/Components/HeadstandComponent.cs index d95e74576b..a4e3bfc5aa 100644 --- a/Content.Client/Administration/Components/HeadstandComponent.cs +++ b/Content.Client/Administration/Components/HeadstandComponent.cs @@ -3,7 +3,7 @@ namespace Content.Client.Administration.Components; -[RegisterComponent, NetworkedComponent] +[RegisterComponent] public sealed partial class HeadstandComponent : SharedHeadstandComponent { diff --git a/Content.Client/Administration/Components/KillSignComponent.cs b/Content.Client/Administration/Components/KillSignComponent.cs index 1cf47b93ff..91c44ef3f2 100644 --- a/Content.Client/Administration/Components/KillSignComponent.cs +++ b/Content.Client/Administration/Components/KillSignComponent.cs @@ -3,6 +3,5 @@ namespace Content.Client.Administration.Components; -[NetworkedComponent, RegisterComponent] -public sealed partial class KillSignComponent : SharedKillSignComponent -{ } +[RegisterComponent] +public sealed partial class KillSignComponent : SharedKillSignComponent; diff --git a/Content.Client/Administration/Managers/ClientAdminManager.cs b/Content.Client/Administration/Managers/ClientAdminManager.cs index fdd62fb6a2..0f740c8104 100644 --- a/Content.Client/Administration/Managers/ClientAdminManager.cs +++ b/Content.Client/Administration/Managers/ClientAdminManager.cs @@ -126,12 +126,15 @@ void IPostInjectInit.PostInject() public AdminData? GetAdminData(EntityUid uid, bool includeDeAdmin = false) { - return uid == _player.LocalEntity ? _adminData : null; + if (uid == _player.LocalEntity && (_adminData?.Active ?? includeDeAdmin)) + return _adminData; + + return null; } public AdminData? GetAdminData(ICommonSession session, bool includeDeAdmin = false) { - if (_player.LocalUser == session.UserId) + if (_player.LocalUser == session.UserId && (_adminData?.Active ?? includeDeAdmin)) return _adminData; return null; diff --git a/Content.Client/Administration/Systems/AdminVerbSystem.cs b/Content.Client/Administration/Systems/AdminVerbSystem.cs index e0f84bc4f0..dced59bbf2 100644 --- a/Content.Client/Administration/Systems/AdminVerbSystem.cs +++ b/Content.Client/Administration/Systems/AdminVerbSystem.cs @@ -1,3 +1,6 @@ +using Content.Shared.Administration; +using Content.Shared.Administration.Managers; +using Content.Shared.Mind.Components; using Content.Shared.Verbs; using Robust.Client.Console; using Robust.Shared.Utility; @@ -11,10 +14,12 @@ sealed class AdminVerbSystem : EntitySystem { [Dependency] private readonly IClientConGroupController _clientConGroupController = default!; [Dependency] private readonly IClientConsoleHost _clientConsoleHost = default!; + [Dependency] private readonly ISharedAdminManager _admin = default!; public override void Initialize() { SubscribeLocalEvent>(AddAdminVerbs); + } private void AddAdminVerbs(GetVerbsEvent args) @@ -33,6 +38,24 @@ private void AddAdminVerbs(GetVerbsEvent args) }; args.Verbs.Add(verb); } + + if (!_admin.IsAdmin(args.User)) + return; + + if (_admin.HasAdminFlag(args.User, AdminFlags.Admin)) + args.ExtraCategories.Add(VerbCategory.Admin); + + if (_admin.HasAdminFlag(args.User, AdminFlags.Fun) && HasComp(args.Target)) + args.ExtraCategories.Add(VerbCategory.Antag); + + if (_admin.HasAdminFlag(args.User, AdminFlags.Debug)) + args.ExtraCategories.Add(VerbCategory.Debug); + + if (_admin.HasAdminFlag(args.User, AdminFlags.Fun)) + args.ExtraCategories.Add(VerbCategory.Smite); + + if (_admin.HasAdminFlag(args.User, AdminFlags.Admin)) + args.ExtraCategories.Add(VerbCategory.Tricks); } } } diff --git a/Content.Client/Administration/UI/AdminUIHelpers.cs b/Content.Client/Administration/UI/AdminUIHelpers.cs index 89ab33e931..7fa8172891 100644 --- a/Content.Client/Administration/UI/AdminUIHelpers.cs +++ b/Content.Client/Administration/UI/AdminUIHelpers.cs @@ -50,7 +50,7 @@ public static bool TryConfirm(Button button, Dictionary _roleCheckboxes = new(); + private readonly ISawmill _banpanelSawmill; [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly ILogManager _logManager = default!; private enum TabNumbers { @@ -65,6 +70,7 @@ public BanPanel() { RobustXamlLoader.Load(this); IoCManager.InjectDependencies(this); + _banpanelSawmill = _logManager.GetSawmill("admin.banpanel"); PlayerList.OnSelectionChanged += OnPlayerSelectionChanged; PlayerNameLine.OnFocusExit += _ => OnPlayerNameChanged(); PlayerCheckbox.OnPressed += _ => @@ -104,6 +110,11 @@ public BanPanel() }; SubmitButton.OnPressed += SubmitButtonOnOnPressed; + IpCheckbox.Pressed = _cfg.GetCVar(CCVars.ServerBanIpBanDefault); + HwidCheckbox.Pressed = _cfg.GetCVar(CCVars.ServerBanHwidBanDefault); + LastConnCheckbox.Pressed = _cfg.GetCVar(CCVars.ServerBanUseLastDetails); + EraseCheckbox.Pressed = _cfg.GetCVar(CCVars.ServerBanErasePlayer); + SeverityOption.AddItem(Loc.GetString("admin-note-editor-severity-none"), (int) NoteSeverity.None); SeverityOption.AddItem(Loc.GetString("admin-note-editor-severity-low"), (int) NoteSeverity.Minor); SeverityOption.AddItem(Loc.GetString("admin-note-editor-severity-medium"), (int) NoteSeverity.Medium); @@ -175,6 +186,39 @@ private void CreateRoleGroup(string roleName, IEnumerable roleList, Colo c.Pressed = args.Pressed; } } + + if (args.Pressed) + { + if (!Enum.TryParse(_cfg.GetCVar(CCVars.DepartmentBanDefaultSeverity), true, out NoteSeverity newSeverity)) + { + _banpanelSawmill + .Warning("Departmental role ban severity could not be parsed from config!"); + return; + } + SeverityOption.SelectId((int) newSeverity); + } + else + { + foreach (var childContainer in RolesContainer.Children) + { + if (childContainer is Container) + { + foreach (var child in childContainer.Children) + { + if (child is CheckBox { Pressed: true }) + return; + } + } + } + + if (!Enum.TryParse(_cfg.GetCVar(CCVars.RoleBanDefaultSeverity), true, out NoteSeverity newSeverity)) + { + _banpanelSawmill + .Warning("Role ban severity could not be parsed from config!"); + return; + } + SeverityOption.SelectId((int) newSeverity); + } }; outerContainer.AddChild(innerContainer); foreach (var role in roleList) @@ -353,6 +397,35 @@ private void OnTypeChanged() { TypeOption.ModulateSelfOverride = null; Tabs.SetTabVisible((int) TabNumbers.Roles, TypeOption.SelectedId == (int) Types.Role); + NoteSeverity? newSeverity = null; + switch (TypeOption.SelectedId) + { + case (int)Types.Server: + if (Enum.TryParse(_cfg.GetCVar(CCVars.ServerBanDefaultSeverity), true, out NoteSeverity serverSeverity)) + newSeverity = serverSeverity; + else + { + _banpanelSawmill + .Warning("Server ban severity could not be parsed from config!"); + } + + break; + case (int) Types.Role: + + if (Enum.TryParse(_cfg.GetCVar(CCVars.RoleBanDefaultSeverity), true, out NoteSeverity roleSeverity)) + { + newSeverity = roleSeverity; + } + else + { + _banpanelSawmill + .Warning("Role ban severity could not be parsed from config!"); + } + break; + } + + if (newSeverity != null) + SeverityOption.SelectId((int) newSeverity.Value); } private void UpdateSubmitEnabled() diff --git a/Content.Client/Administration/UI/DepartmentWhitelistPanel.xaml b/Content.Client/Administration/UI/DepartmentWhitelistPanel.xaml new file mode 100644 index 0000000000..d5f77aedd5 --- /dev/null +++ b/Content.Client/Administration/UI/DepartmentWhitelistPanel.xaml @@ -0,0 +1,11 @@ + + + + + + diff --git a/Content.Client/Administration/UI/DepartmentWhitelistPanel.xaml.cs b/Content.Client/Administration/UI/DepartmentWhitelistPanel.xaml.cs new file mode 100644 index 0000000000..275055daf6 --- /dev/null +++ b/Content.Client/Administration/UI/DepartmentWhitelistPanel.xaml.cs @@ -0,0 +1,49 @@ +using Content.Shared.Roles; +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.CustomControls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Prototypes; + +namespace Content.Client.Administration.UI; + +[GenerateTypedNameReferences] +public sealed partial class DepartmentWhitelistPanel : PanelContainer +{ + public Action, bool>? OnSetJob; + + public DepartmentWhitelistPanel(DepartmentPrototype department, IPrototypeManager proto, HashSet> whitelists) + { + RobustXamlLoader.Load(this); + + var allWhitelisted = true; + var grey = Color.FromHex("#ccc"); + foreach (var id in department.Roles) + { + var thisJob = id; // closure capturing funny + var button = new CheckBox(); + button.Text = proto.Index(id).LocalizedName; + if (!proto.Index(id).Whitelisted) + button.Modulate = grey; // Let admins know whitelisting this job is only for futureproofing. + button.Pressed = whitelists.Contains(id); + button.OnPressed += _ => OnSetJob?.Invoke(thisJob, button.Pressed); + JobsContainer.AddChild(button); + + allWhitelisted &= button.Pressed; + } + + Department.Text = Loc.GetString(department.ID); + Department.Modulate = department.Color; + Department.Pressed = allWhitelisted; + Department.OnPressed += args => + { + foreach (var id in department.Roles) + { + // only request to whitelist roles that aren't already whitelisted, and vice versa + if (whitelists.Contains(id) != Department.Pressed) + OnSetJob?.Invoke(id, Department.Pressed); + } + }; + } +} diff --git a/Content.Client/Administration/UI/JobWhitelistsEui.cs b/Content.Client/Administration/UI/JobWhitelistsEui.cs new file mode 100644 index 0000000000..b8fe974c0a --- /dev/null +++ b/Content.Client/Administration/UI/JobWhitelistsEui.cs @@ -0,0 +1,40 @@ +using Content.Client.Eui; +using Content.Shared.Administration; +using Content.Shared.Eui; + +namespace Content.Client.Administration.UI; + +public sealed class JobWhitelistsEui : BaseEui +{ + private JobWhitelistsWindow Window; + + public JobWhitelistsEui() + { + Window = new JobWhitelistsWindow(); + Window.OnClose += () => SendMessage(new CloseEuiMessage()); + Window.OnSetJob += (id, whitelisted) => SendMessage(new SetJobWhitelistedMessage(id, whitelisted)); + } + + public override void HandleState(EuiStateBase state) + { + if (state is not JobWhitelistsEuiState cast) + return; + + Window.HandleState(cast); + } + + public override void Opened() + { + base.Opened(); + + Window.OpenCentered(); + } + + public override void Closed() + { + base.Closed(); + + Window.Close(); + Window.Dispose(); + } +} diff --git a/Content.Client/Administration/UI/JobWhitelistsWindow.xaml b/Content.Client/Administration/UI/JobWhitelistsWindow.xaml new file mode 100644 index 0000000000..165f5ac3d7 --- /dev/null +++ b/Content.Client/Administration/UI/JobWhitelistsWindow.xaml @@ -0,0 +1,11 @@ + + + + diff --git a/Content.Client/Administration/UI/JobWhitelistsWindow.xaml.cs b/Content.Client/Administration/UI/JobWhitelistsWindow.xaml.cs new file mode 100644 index 0000000000..51fb5287dc --- /dev/null +++ b/Content.Client/Administration/UI/JobWhitelistsWindow.xaml.cs @@ -0,0 +1,46 @@ +using Content.Client.UserInterface.Controls; +using Content.Shared.Database; +using Content.Shared.Administration; +using Content.Shared.Roles; +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.CustomControls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Prototypes; + +namespace Content.Client.Administration.UI; + +/// +/// An admin panel to toggle whitelists for individual jobs or entire departments. +/// This should generally be preferred to a blanket whitelist (Whitelisted: True) since +/// being good with a batong doesn't mean you know engineering and vice versa. +/// +[GenerateTypedNameReferences] +public sealed partial class JobWhitelistsWindow : FancyWindow +{ + [Dependency] private readonly IPrototypeManager _proto = default!; + + public Action, bool>? OnSetJob; + + public JobWhitelistsWindow() + { + RobustXamlLoader.Load(this); + IoCManager.InjectDependencies(this); + + PlayerName.Text = "???"; + } + + public void HandleState(JobWhitelistsEuiState state) + { + PlayerName.Text = state.PlayerName; + + Departments.RemoveAllChildren(); + foreach (var proto in _proto.EnumeratePrototypes()) + { + var panel = new DepartmentWhitelistPanel(proto, _proto, state.Whitelists); + panel.OnSetJob += (id, whitelisting) => OnSetJob?.Invoke(id, whitelisting); + Departments.AddChild(panel); + } + } +} diff --git a/Content.Client/Administration/UI/SpawnExplosion/ExplosionDebugOverlay.cs b/Content.Client/Administration/UI/SpawnExplosion/ExplosionDebugOverlay.cs index eede3a6217..d60094ad89 100644 --- a/Content.Client/Administration/UI/SpawnExplosion/ExplosionDebugOverlay.cs +++ b/Content.Client/Administration/UI/SpawnExplosion/ExplosionDebugOverlay.cs @@ -25,7 +25,7 @@ public sealed class ExplosionDebugOverlay : Overlay public override OverlaySpace Space => OverlaySpace.WorldSpace | OverlaySpace.ScreenSpace; - public Matrix3 SpaceMatrix; + public Matrix3x2 SpaceMatrix; public MapId Map; private readonly Font _font; @@ -78,7 +78,8 @@ private void DrawScreen(OverlayDrawArgs args) if (SpaceTiles == null) return; - gridBounds = Matrix3.Invert(SpaceMatrix).TransformBox(args.WorldBounds); + Matrix3x2.Invert(SpaceMatrix, out var invSpace); + gridBounds = invSpace.TransformBox(args.WorldBounds); DrawText(handle, gridBounds, SpaceMatrix, SpaceTiles, SpaceTileSize); } @@ -86,7 +87,7 @@ private void DrawScreen(OverlayDrawArgs args) private void DrawText( DrawingHandleScreen handle, Box2 gridBounds, - Matrix3 transform, + Matrix3x2 transform, Dictionary> tileSets, ushort tileSize) { @@ -103,7 +104,7 @@ private void DrawText( if (!gridBounds.Contains(centre)) continue; - var worldCenter = transform.Transform(centre); + var worldCenter = Vector2.Transform(centre, transform); var screenCenter = _eyeManager.WorldToScreen(worldCenter); @@ -119,7 +120,7 @@ private void DrawText( if (tileSets.TryGetValue(0, out var set)) { var epicenter = set.First(); - var worldCenter = transform.Transform((epicenter + Vector2Helpers.Half) * tileSize); + var worldCenter = Vector2.Transform((epicenter + Vector2Helpers.Half) * tileSize, transform); var screenCenter = _eyeManager.WorldToScreen(worldCenter) + new Vector2(-24, -24); var text = $"{Intensity[0]:F2}\nΣ={TotalIntensity:F1}\nΔ={Slope:F1}"; handle.DrawString(_font, screenCenter, text); @@ -148,11 +149,12 @@ private void DrawWorld(in OverlayDrawArgs args) if (SpaceTiles == null) return; - gridBounds = Matrix3.Invert(SpaceMatrix).TransformBox(args.WorldBounds).Enlarged(2); + Matrix3x2.Invert(SpaceMatrix, out var invSpace); + gridBounds = invSpace.TransformBox(args.WorldBounds).Enlarged(2); handle.SetTransform(SpaceMatrix); DrawTiles(handle, gridBounds, SpaceTiles, SpaceTileSize); - handle.SetTransform(Matrix3.Identity); + handle.SetTransform(Matrix3x2.Identity); } private void DrawTiles( diff --git a/Content.Client/Animations/TrackUserComponent.cs b/Content.Client/Animations/TrackUserComponent.cs new file mode 100644 index 0000000000..374c187398 --- /dev/null +++ b/Content.Client/Animations/TrackUserComponent.cs @@ -0,0 +1,17 @@ +using System.Numerics; + +namespace Content.Client.Animations; + +/// +/// Entities with this component tracks the user's world position every frame. +/// +[RegisterComponent] +public sealed partial class TrackUserComponent : Component +{ + public EntityUid? User; + + /// + /// Offset in the direction of the entity's rotation. + /// + public Vector2 Offset = Vector2.Zero; +} diff --git a/Content.Client/Announcements/Systems/AnnouncerSystem.cs b/Content.Client/Announcements/Systems/AnnouncerSystem.cs index de76396f70..2ce419b788 100644 --- a/Content.Client/Announcements/Systems/AnnouncerSystem.cs +++ b/Content.Client/Announcements/Systems/AnnouncerSystem.cs @@ -1,3 +1,4 @@ +using System.Linq; using Content.Client.Audio; using Content.Shared.Announcements.Events; using Content.Shared.Announcements.Systems; @@ -18,8 +19,8 @@ public sealed class AnnouncerSystem : SharedAnnouncerSystem [Dependency] private readonly IResourceCache _cache = default!; [Dependency] private readonly IAudioManager _audioManager = default!; - private IAudioSource? AnnouncerSource { get; set; } - private float AnnouncerVolume { get; set; } + public List AnnouncerSources { get; } = new(); + public float AnnouncerVolume { get; private set; } public override void Initialize() @@ -28,8 +29,10 @@ public override void Initialize() AnnouncerVolume = _config.GetCVar(CCVars.AnnouncerVolume) * 100f / ContentAudioSystem.AnnouncerMultiplier; - SubscribeNetworkEvent(OnAnnouncementReceived); _config.OnValueChanged(CCVars.AnnouncerVolume, OnAnnouncerVolumeChanged); + _config.OnValueChanged(CCVars.AnnouncerDisableMultipleSounds, OnAnnouncerDisableMultipleSounds); + + SubscribeNetworkEvent(OnAnnouncementReceived); } public override void Shutdown() @@ -37,6 +40,7 @@ public override void Shutdown() base.Shutdown(); _config.UnsubValueChanged(CCVars.AnnouncerVolume, OnAnnouncerVolumeChanged); + _config.UnsubValueChanged(CCVars.AnnouncerDisableMultipleSounds, OnAnnouncerDisableMultipleSounds); } @@ -44,10 +48,23 @@ private void OnAnnouncerVolumeChanged(float value) { AnnouncerVolume = value; - if (AnnouncerSource != null) - AnnouncerSource.Gain = AnnouncerVolume; + foreach (var source in AnnouncerSources) + source.Gain = AnnouncerVolume; } + private void OnAnnouncerDisableMultipleSounds(bool value) + { + if (!value) + return; + + foreach (var audioSource in AnnouncerSources.ToList()) + { + audioSource.Dispose(); + AnnouncerSources.Remove(audioSource); + } + } + + private void OnAnnouncementReceived(AnnouncementSendEvent ev) { if (!ev.Recipients.Contains(_player.LocalSession!.UserId) @@ -56,14 +73,28 @@ private void OnAnnouncementReceived(AnnouncementSendEvent ev) return; var source = _audioManager.CreateAudioSource(resource); - if (source != null) + if (source == null) + return; + + source.Gain = AnnouncerVolume * SharedAudioSystem.VolumeToGain(ev.AudioParams.Volume); + source.Global = true; + + if (_config.GetCVar(CCVars.AnnouncerDisableMultipleSounds)) + { + foreach (var audioSource in AnnouncerSources.ToList()) + { + audioSource.Dispose(); + AnnouncerSources.Remove(audioSource); + } + } + + foreach (var audioSource in AnnouncerSources.ToList().Where(audioSource => !audioSource.Playing)) { - source.Gain = AnnouncerVolume * SharedAudioSystem.VolumeToGain(ev.AudioParams.Volume); - source.Global = true; + audioSource.Dispose(); + AnnouncerSources.Remove(audioSource); } - AnnouncerSource?.Dispose(); - AnnouncerSource = source; - AnnouncerSource?.StartPlaying(); + AnnouncerSources.Add(source); + source.StartPlaying(); } } diff --git a/Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml b/Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml new file mode 100644 index 0000000000..96f136abf0 --- /dev/null +++ b/Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml.cs b/Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml.cs new file mode 100644 index 0000000000..b0d0365ef6 --- /dev/null +++ b/Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml.cs @@ -0,0 +1,215 @@ +using Content.Client.Stylesheets; +using Content.Shared.Atmos; +using Content.Shared.Atmos.Components; +using Content.Shared.Atmos.Monitor; +using Content.Shared.FixedPoint; +using Content.Shared.Temperature; +using Robust.Client.AutoGenerated; +using Robust.Client.Graphics; +using Robust.Client.ResourceManagement; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Map; +using System.Linq; + +namespace Content.Client.Atmos.Consoles; + +[GenerateTypedNameReferences] +public sealed partial class AtmosAlarmEntryContainer : BoxContainer +{ + public NetEntity NetEntity; + public EntityCoordinates? Coordinates; + + private readonly IEntityManager _entManager; + private readonly IResourceCache _cache; + + private Dictionary _alarmStrings = new Dictionary() + { + [AtmosAlarmType.Invalid] = "atmos-alerts-window-invalid-state", + [AtmosAlarmType.Normal] = "atmos-alerts-window-normal-state", + [AtmosAlarmType.Warning] = "atmos-alerts-window-warning-state", + [AtmosAlarmType.Danger] = "atmos-alerts-window-danger-state", + }; + + private Dictionary _gasShorthands = new Dictionary() + { + [Gas.Ammonia] = "NH₃", + [Gas.CarbonDioxide] = "CO₂", + [Gas.Frezon] = "F", + [Gas.Nitrogen] = "N₂", + [Gas.NitrousOxide] = "N₂O", + [Gas.Oxygen] = "O₂", + [Gas.Plasma] = "P", + [Gas.Tritium] = "T", + [Gas.WaterVapor] = "H₂O", + }; + + public AtmosAlarmEntryContainer(NetEntity uid, EntityCoordinates? coordinates) + { + RobustXamlLoader.Load(this); + + _entManager = IoCManager.Resolve(); + _cache = IoCManager.Resolve(); + + NetEntity = uid; + Coordinates = coordinates; + + // Load fonts + var headerFont = new VectorFont(_cache.GetResource("/Fonts/NotoSans/NotoSans-Bold.ttf"), 11); + var normalFont = new VectorFont(_cache.GetResource("/Fonts/NotoSansDisplay/NotoSansDisplay-Regular.ttf"), 11); + var smallFont = new VectorFont(_cache.GetResource("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10); + + // Set fonts + TemperatureHeaderLabel.FontOverride = headerFont; + PressureHeaderLabel.FontOverride = headerFont; + OxygenationHeaderLabel.FontOverride = headerFont; + GasesHeaderLabel.FontOverride = headerFont; + + TemperatureLabel.FontOverride = normalFont; + PressureLabel.FontOverride = normalFont; + OxygenationLabel.FontOverride = normalFont; + + NoDataLabel.FontOverride = headerFont; + + SilenceCheckBox.Label.FontOverride = smallFont; + SilenceCheckBox.Label.FontColorOverride = Color.DarkGray; + } + + public void UpdateEntry(AtmosAlertsComputerEntry entry, bool isFocus, AtmosAlertsFocusDeviceData? focusData = null) + { + NetEntity = entry.NetEntity; + Coordinates = _entManager.GetCoordinates(entry.Coordinates); + + // Load fonts + var normalFont = new VectorFont(_cache.GetResource("/Fonts/NotoSansDisplay/NotoSansDisplay-Regular.ttf"), 11); + + // Update alarm state + if (!_alarmStrings.TryGetValue(entry.AlarmState, out var alarmString)) + alarmString = "atmos-alerts-window-invalid-state"; + + AlarmStateLabel.Text = Loc.GetString(alarmString); + AlarmStateLabel.FontColorOverride = GetAlarmStateColor(entry.AlarmState); + + // Update alarm name + AlarmNameLabel.Text = Loc.GetString("atmos-alerts-window-alarm-label", ("name", entry.EntityName), ("address", entry.Address)); + + // Focus updates + FocusContainer.Visible = isFocus; + + if (isFocus) + SetAsFocus(); + else + RemoveAsFocus(); + + if (isFocus && entry.Group == AtmosAlertsComputerGroup.AirAlarm) + { + MainDataContainer.Visible = (entry.AlarmState != AtmosAlarmType.Invalid); + NoDataLabel.Visible = (entry.AlarmState == AtmosAlarmType.Invalid); + + if (focusData != null) + { + // Update temperature + var tempK = (FixedPoint2)focusData.Value.TemperatureData.Item1; + var tempC = (FixedPoint2)TemperatureHelpers.KelvinToCelsius(tempK.Float()); + + TemperatureLabel.Text = Loc.GetString("atmos-alerts-window-temperature-value", ("valueInC", tempC), ("valueInK", tempK)); + TemperatureLabel.FontColorOverride = GetAlarmStateColor(focusData.Value.TemperatureData.Item2); + + // Update pressure + PressureLabel.Text = Loc.GetString("atmos-alerts-window-pressure-value", ("value", (FixedPoint2)focusData.Value.PressureData.Item1)); + PressureLabel.FontColorOverride = GetAlarmStateColor(focusData.Value.PressureData.Item2); + + // Update oxygenation + var oxygenPercent = (FixedPoint2)0f; + var oxygenAlert = AtmosAlarmType.Invalid; + + if (focusData.Value.GasData.TryGetValue(Gas.Oxygen, out var oxygenData)) + { + oxygenPercent = oxygenData.Item2 * 100f; + oxygenAlert = oxygenData.Item3; + } + + OxygenationLabel.Text = Loc.GetString("atmos-alerts-window-oxygenation-value", ("value", oxygenPercent)); + OxygenationLabel.FontColorOverride = GetAlarmStateColor(oxygenAlert); + + // Update other present gases + GasGridContainer.RemoveAllChildren(); + + var gasData = focusData.Value.GasData.Where(g => g.Key != Gas.Oxygen); + + if (gasData.Count() == 0) + { + // No other gases + var gasLabel = new Label() + { + Text = Loc.GetString("atmos-alerts-window-other-gases-value-nil"), + FontOverride = normalFont, + FontColorOverride = StyleNano.DisabledFore, + HorizontalAlignment = HAlignment.Center, + VerticalAlignment = VAlignment.Center, + HorizontalExpand = true, + Margin = new Thickness(0, 2, 0, 0), + SetHeight = 24f, + }; + + GasGridContainer.AddChild(gasLabel); + } + + else + { + // Add an entry for each gas + foreach ((var gas, (var mol, var percent, var alert)) in gasData) + { + var gasPercent = (FixedPoint2)0f; + gasPercent = percent * 100f; + + if (!_gasShorthands.TryGetValue(gas, out var gasShorthand)) + gasShorthand = "X"; + + var gasLabel = new Label() + { + Text = Loc.GetString("atmos-alerts-window-other-gases-value", ("shorthand", gasShorthand), ("value", gasPercent)), + FontOverride = normalFont, + FontColorOverride = GetAlarmStateColor(alert), + HorizontalAlignment = HAlignment.Center, + VerticalAlignment = VAlignment.Center, + HorizontalExpand = true, + Margin = new Thickness(0, 2, 0, 0), + SetHeight = 24f, + }; + + GasGridContainer.AddChild(gasLabel); + } + } + } + } + } + + public void SetAsFocus() + { + FocusButton.AddStyleClass(StyleNano.StyleClassButtonColorGreen); + ArrowTexture.TexturePath = "/Textures/Interface/Nano/inverted_triangle.svg.png"; + } + + public void RemoveAsFocus() + { + FocusButton.RemoveStyleClass(StyleNano.StyleClassButtonColorGreen); + ArrowTexture.TexturePath = "/Textures/Interface/Nano/triangle_right.png"; + FocusContainer.Visible = false; + } + + private Color GetAlarmStateColor(AtmosAlarmType alarmType) + { + switch (alarmType) + { + case AtmosAlarmType.Normal: + return StyleNano.GoodGreenFore; + case AtmosAlarmType.Warning: + return StyleNano.ConcerningOrangeFore; + case AtmosAlarmType.Danger: + return StyleNano.DangerousRedFore; + } + + return StyleNano.DisabledFore; + } +} \ No newline at end of file diff --git a/Content.Client/Atmos/Consoles/AtmosAlertsComputerBoundUserInterface.cs b/Content.Client/Atmos/Consoles/AtmosAlertsComputerBoundUserInterface.cs new file mode 100644 index 0000000000..08cae979b9 --- /dev/null +++ b/Content.Client/Atmos/Consoles/AtmosAlertsComputerBoundUserInterface.cs @@ -0,0 +1,52 @@ +using Content.Shared.Atmos.Components; + +namespace Content.Client.Atmos.Consoles; + +public sealed class AtmosAlertsComputerBoundUserInterface : BoundUserInterface +{ + [ViewVariables] + private AtmosAlertsComputerWindow? _menu; + + public AtmosAlertsComputerBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) { } + + protected override void Open() + { + _menu = new AtmosAlertsComputerWindow(this, Owner); + _menu.OpenCentered(); + _menu.OnClose += Close; + + EntMan.TryGetComponent(Owner, out var xform); + } + + protected override void UpdateState(BoundUserInterfaceState state) + { + base.UpdateState(state); + + var castState = (AtmosAlertsComputerBoundInterfaceState) state; + + if (castState == null) + return; + + EntMan.TryGetComponent(Owner, out var xform); + _menu?.UpdateUI(xform?.Coordinates, castState.AirAlarms, castState.FireAlarms, castState.FocusData); + } + + public void SendFocusChangeMessage(NetEntity? netEntity) + { + SendMessage(new AtmosAlertsComputerFocusChangeMessage(netEntity)); + } + + public void SendDeviceSilencedMessage(NetEntity netEntity, bool silenceDevice) + { + SendMessage(new AtmosAlertsComputerDeviceSilencedMessage(netEntity, silenceDevice)); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (!disposing) + return; + + _menu?.Dispose(); + } +} diff --git a/Content.Client/Atmos/Consoles/AtmosAlertsComputerWindow.xaml b/Content.Client/Atmos/Consoles/AtmosAlertsComputerWindow.xaml new file mode 100644 index 0000000000..8824a776ee --- /dev/null +++ b/Content.Client/Atmos/Consoles/AtmosAlertsComputerWindow.xaml @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Content.Client/Atmos/Consoles/AtmosAlertsComputerWindow.xaml.cs b/Content.Client/Atmos/Consoles/AtmosAlertsComputerWindow.xaml.cs new file mode 100644 index 0000000000..a55321833c --- /dev/null +++ b/Content.Client/Atmos/Consoles/AtmosAlertsComputerWindow.xaml.cs @@ -0,0 +1,550 @@ +using Content.Client.Message; +using Content.Client.Pinpointer.UI; +using Content.Client.Stylesheets; +using Content.Client.UserInterface.Controls; +using Content.Shared.Atmos.Components; +using Content.Shared.Atmos.Monitor; +using Content.Shared.Pinpointer; +using Robust.Client.AutoGenerated; +using Robust.Client.GameObjects; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Map; +using Robust.Shared.Timing; +using Robust.Shared.Utility; +using Robust.Shared.ContentPack; +using Robust.Shared.Prototypes; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +namespace Content.Client.Atmos.Consoles; + +[GenerateTypedNameReferences] +public sealed partial class AtmosAlertsComputerWindow : FancyWindow +{ + private readonly IEntityManager _entManager; + private readonly SpriteSystem _spriteSystem; + + private EntityUid? _owner; + private NetEntity? _trackedEntity; + + private AtmosAlertsComputerEntry[]? _airAlarms = null; + private AtmosAlertsComputerEntry[]? _fireAlarms = null; + private IEnumerable? _allAlarms = null; + + private IEnumerable? _activeAlarms = null; + private Dictionary _deviceSilencingProgress = new(); + + public event Action? SendFocusChangeMessageAction; + public event Action? SendDeviceSilencedMessageAction; + + private bool _autoScrollActive = false; + private bool _autoScrollAwaitsUpdate = false; + + private const float SilencingDuration = 2.5f; + + public AtmosAlertsComputerWindow(AtmosAlertsComputerBoundUserInterface userInterface, EntityUid? owner) + { + RobustXamlLoader.Load(this); + _entManager = IoCManager.Resolve(); + _spriteSystem = _entManager.System(); + + // Pass the owner to nav map + _owner = owner; + NavMap.Owner = _owner; + + // Set nav map colors + NavMap.WallColor = new Color(64, 64, 64); + NavMap.TileColor = Color.DimGray * NavMap.WallColor; + + // Set nav map grid uid + var stationName = Loc.GetString("atmos-alerts-window-unknown-location"); + + if (_entManager.TryGetComponent(owner, out var xform)) + { + NavMap.MapUid = xform.GridUid; + + // Assign station name + if (_entManager.TryGetComponent(xform.GridUid, out var stationMetaData)) + stationName = stationMetaData.EntityName; + + var msg = new FormattedMessage(); + msg.AddMarkup(Loc.GetString("atmos-alerts-window-station-name", ("stationName", stationName))); + + StationName.SetMessage(msg); + } + + else + { + StationName.SetMessage(stationName); + NavMap.Visible = false; + } + + // Set trackable entity selected action + NavMap.TrackedEntitySelectedAction += SetTrackedEntityFromNavMap; + + // Update nav map + NavMap.ForceNavMapUpdate(); + + // Set tab container headers + MasterTabContainer.SetTabTitle(0, Loc.GetString("atmos-alerts-window-tab-no-alerts")); + MasterTabContainer.SetTabTitle(1, Loc.GetString("atmos-alerts-window-tab-air-alarms")); + MasterTabContainer.SetTabTitle(2, Loc.GetString("atmos-alerts-window-tab-fire-alarms")); + + // Set UI toggles + ShowInactiveAlarms.OnToggled += _ => OnShowAlarmsToggled(ShowInactiveAlarms, AtmosAlarmType.Invalid); + ShowNormalAlarms.OnToggled += _ => OnShowAlarmsToggled(ShowNormalAlarms, AtmosAlarmType.Normal); + ShowWarningAlarms.OnToggled += _ => OnShowAlarmsToggled(ShowWarningAlarms, AtmosAlarmType.Warning); + ShowDangerAlarms.OnToggled += _ => OnShowAlarmsToggled(ShowDangerAlarms, AtmosAlarmType.Danger); + + // Set atmos monitoring message action + SendFocusChangeMessageAction += userInterface.SendFocusChangeMessage; + SendDeviceSilencedMessageAction += userInterface.SendDeviceSilencedMessage; + } + + #region Toggle handling + + private void OnShowAlarmsToggled(CheckBox toggle, AtmosAlarmType toggledAlarmState) + { + if (_owner == null) + return; + + if (!_entManager.TryGetComponent(_owner.Value, out var console)) + return; + + foreach (var device in console.AtmosDevices) + { + var alarmState = GetAlarmState(device.NetEntity); + + if (toggledAlarmState != alarmState) + continue; + + if (toggle.Pressed) + AddTrackedEntityToNavMap(device, alarmState); + + else + NavMap.TrackedEntities.Remove(device.NetEntity); + } + } + + private void OnSilenceAlertsToggled(NetEntity netEntity, bool toggleState) + { + if (!_entManager.TryGetComponent(_owner, out var console)) + return; + + if (toggleState) + _deviceSilencingProgress[netEntity] = SilencingDuration; + + else + _deviceSilencingProgress.Remove(netEntity); + + foreach (AtmosAlarmEntryContainer entryContainer in AlertsTable.Children) + { + if (entryContainer.NetEntity == netEntity) + entryContainer.SilenceAlarmProgressBar.Visible = toggleState; + } + + SendDeviceSilencedMessageAction?.Invoke(netEntity, toggleState); + } + + #endregion + + public void UpdateUI(EntityCoordinates? consoleCoords, AtmosAlertsComputerEntry[] airAlarms, AtmosAlertsComputerEntry[] fireAlarms, AtmosAlertsFocusDeviceData? focusData) + { + if (_owner == null) + return; + + if (!_entManager.TryGetComponent(_owner.Value, out var console)) + return; + + if (_trackedEntity != focusData?.NetEntity) + { + SendFocusChangeMessageAction?.Invoke(_trackedEntity); + focusData = null; + } + + // Retain alarm data for use inbetween updates + _airAlarms = airAlarms; + _fireAlarms = fireAlarms; + _allAlarms = airAlarms.Concat(fireAlarms); + + var silenced = console.SilencedDevices; + + _activeAlarms = _allAlarms.Where(x => x.AlarmState > AtmosAlarmType.Normal && + (!silenced.Contains(x.NetEntity) || _deviceSilencingProgress.ContainsKey(x.NetEntity))); + + // Reset nav map data + NavMap.TrackedCoordinates.Clear(); + NavMap.TrackedEntities.Clear(); + + // Add tracked entities to the nav map + foreach (var device in console.AtmosDevices) + { + if (!NavMap.Visible) + continue; + + var alarmState = GetAlarmState(device.NetEntity); + + if (_trackedEntity != device.NetEntity) + { + // Skip air alarms if the appropriate overlay is off + if (!ShowInactiveAlarms.Pressed && alarmState == AtmosAlarmType.Invalid) + continue; + + if (!ShowNormalAlarms.Pressed && alarmState == AtmosAlarmType.Normal) + continue; + + if (!ShowWarningAlarms.Pressed && alarmState == AtmosAlarmType.Warning) + continue; + + if (!ShowDangerAlarms.Pressed && alarmState == AtmosAlarmType.Danger) + continue; + } + + AddTrackedEntityToNavMap(device, alarmState); + } + + // Show the monitor location + var consoleUid = _entManager.GetNetEntity(_owner); + + if (consoleCoords != null && consoleUid != null) + { + var texture = _spriteSystem.Frame0(new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_circle.png"))); + var blip = new NavMapBlip(consoleCoords.Value, texture, Color.Cyan, true, false); + NavMap.TrackedEntities[consoleUid.Value] = blip; + } + + // Update the nav map + NavMap.ForceNavMapUpdate(); + + // Clear excess children from the tables + var activeAlarmCount = _activeAlarms.Count(); + + while (AlertsTable.ChildCount > activeAlarmCount) + AlertsTable.RemoveChild(AlertsTable.GetChild(AlertsTable.ChildCount - 1)); + + while (AirAlarmsTable.ChildCount > airAlarms.Length) + AirAlarmsTable.RemoveChild(AirAlarmsTable.GetChild(AirAlarmsTable.ChildCount - 1)); + + while (FireAlarmsTable.ChildCount > fireAlarms.Length) + FireAlarmsTable.RemoveChild(FireAlarmsTable.GetChild(FireAlarmsTable.ChildCount - 1)); + + // Update all entries in each table + for (int index = 0; index < _activeAlarms.Count(); index++) + { + var entry = _activeAlarms.ElementAt(index); + UpdateUIEntry(entry, index, AlertsTable, console, focusData); + } + + for (int index = 0; index < airAlarms.Count(); index++) + { + var entry = airAlarms.ElementAt(index); + UpdateUIEntry(entry, index, AirAlarmsTable, console, focusData); + } + + for (int index = 0; index < fireAlarms.Count(); index++) + { + var entry = fireAlarms.ElementAt(index); + UpdateUIEntry(entry, index, FireAlarmsTable, console, focusData); + } + + // If no alerts are active, display a message + if (MasterTabContainer.CurrentTab == 0 && activeAlarmCount == 0) + { + var label = new RichTextLabel() + { + HorizontalExpand = true, + VerticalExpand = true, + HorizontalAlignment = HAlignment.Center, + VerticalAlignment = VAlignment.Center, + }; + + label.SetMarkup(Loc.GetString("atmos-alerts-window-no-active-alerts", ("color", StyleNano.GoodGreenFore.ToHexNoAlpha()))); + + AlertsTable.AddChild(label); + } + + // Update the alerts tab with the number of active alerts + if (activeAlarmCount == 0) + MasterTabContainer.SetTabTitle(0, Loc.GetString("atmos-alerts-window-tab-no-alerts")); + + else + MasterTabContainer.SetTabTitle(0, Loc.GetString("atmos-alerts-window-tab-alerts", ("value", activeAlarmCount))); + + // Auto-scroll re-enable + if (_autoScrollAwaitsUpdate) + { + _autoScrollActive = true; + _autoScrollAwaitsUpdate = false; + } + } + + private void AddTrackedEntityToNavMap(AtmosAlertsDeviceNavMapData metaData, AtmosAlarmType alarmState) + { + var data = GetBlipTexture(alarmState); + + if (data == null) + return; + + var texture = data.Value.Item1; + var color = data.Value.Item2; + var coords = _entManager.GetCoordinates(metaData.NetCoordinates); + + if (_trackedEntity != null && _trackedEntity != metaData.NetEntity) + color *= Color.DimGray; + + var selectable = true; + var blip = new NavMapBlip(coords, _spriteSystem.Frame0(texture), color, _trackedEntity == metaData.NetEntity, selectable); + + NavMap.TrackedEntities[metaData.NetEntity] = blip; + } + + private void UpdateUIEntry(AtmosAlertsComputerEntry entry, int index, Control table, AtmosAlertsComputerComponent console, AtmosAlertsFocusDeviceData? focusData = null) + { + // Make new UI entry if required + if (index >= table.ChildCount) + { + var newEntryContainer = new AtmosAlarmEntryContainer(entry.NetEntity, _entManager.GetCoordinates(entry.Coordinates)); + + // On click + newEntryContainer.FocusButton.OnButtonUp += args => + { + if (_trackedEntity == newEntryContainer.NetEntity) + { + _trackedEntity = null; + } + + else + { + _trackedEntity = newEntryContainer.NetEntity; + + if (newEntryContainer.Coordinates != null) + NavMap.CenterToCoordinates(newEntryContainer.Coordinates.Value); + } + + // Send message to console that the focus has changed + SendFocusChangeMessageAction?.Invoke(_trackedEntity); + + // Update affected UI elements across all tables + UpdateConsoleTable(console, AlertsTable, _trackedEntity); + UpdateConsoleTable(console, AirAlarmsTable, _trackedEntity); + UpdateConsoleTable(console, FireAlarmsTable, _trackedEntity); + }; + + // On toggling the silence check box + newEntryContainer.SilenceCheckBox.OnToggled += _ => OnSilenceAlertsToggled(newEntryContainer.NetEntity, newEntryContainer.SilenceCheckBox.Pressed); + + // Add the entry to the current table + table.AddChild(newEntryContainer); + } + + // Update values and UI elements + var tableChild = table.GetChild(index); + + if (tableChild is not AtmosAlarmEntryContainer) + { + table.RemoveChild(tableChild); + UpdateUIEntry(entry, index, table, console, focusData); + + return; + } + + var entryContainer = (AtmosAlarmEntryContainer)tableChild; + + entryContainer.UpdateEntry(entry, entry.NetEntity == _trackedEntity, focusData); + + if (_trackedEntity != entry.NetEntity) + { + var silenced = console.SilencedDevices; + entryContainer.SilenceCheckBox.Pressed = (silenced.Contains(entry.NetEntity) || _deviceSilencingProgress.ContainsKey(entry.NetEntity)); + } + + entryContainer.SilenceAlarmProgressBar.Visible = (table == AlertsTable && _deviceSilencingProgress.ContainsKey(entry.NetEntity)); + } + + private void UpdateConsoleTable(AtmosAlertsComputerComponent console, Control table, NetEntity? currTrackedEntity) + { + foreach (var tableChild in table.Children) + { + if (tableChild is not AtmosAlarmEntryContainer) + continue; + + var entryContainer = (AtmosAlarmEntryContainer)tableChild; + + if (entryContainer.NetEntity != currTrackedEntity) + entryContainer.RemoveAsFocus(); + + else if (entryContainer.NetEntity == currTrackedEntity) + entryContainer.SetAsFocus(); + } + } + + private void SetTrackedEntityFromNavMap(NetEntity? netEntity) + { + if (netEntity == null) + return; + + if (!_entManager.TryGetComponent(_owner, out var console)) + return; + + _trackedEntity = netEntity; + + if (netEntity != null) + { + // Tab switching + if (MasterTabContainer.CurrentTab != 0 || _activeAlarms?.Any(x => x.NetEntity == netEntity) == false) + { + var device = console.AtmosDevices.FirstOrNull(x => x.NetEntity == netEntity); + + switch (device?.Group) + { + case AtmosAlertsComputerGroup.AirAlarm: + MasterTabContainer.CurrentTab = 1; break; + case AtmosAlertsComputerGroup.FireAlarm: + MasterTabContainer.CurrentTab = 2; break; + } + } + + // Get the scroll position of the selected entity on the selected button the UI + ActivateAutoScrollToFocus(); + } + + // Send message to console that the focus has changed + SendFocusChangeMessageAction?.Invoke(_trackedEntity); + } + + protected override void FrameUpdate(FrameEventArgs args) + { + AutoScrollToFocus(); + + // Device silencing update + foreach ((var device, var remainingTime) in _deviceSilencingProgress) + { + var t = remainingTime - args.DeltaSeconds; + + if (t <= 0) + { + _deviceSilencingProgress.Remove(device); + + if (device == _trackedEntity) + _trackedEntity = null; + } + + else + _deviceSilencingProgress[device] = t; + } + } + + private void ActivateAutoScrollToFocus() + { + _autoScrollActive = false; + _autoScrollAwaitsUpdate = true; + } + + private void AutoScrollToFocus() + { + if (!_autoScrollActive) + return; + + var scroll = MasterTabContainer.Children.ElementAt(MasterTabContainer.CurrentTab) as ScrollContainer; + if (scroll == null) + return; + + if (!TryGetVerticalScrollbar(scroll, out var vScrollbar)) + return; + + if (!TryGetNextScrollPosition(out float? nextScrollPosition)) + return; + + vScrollbar.ValueTarget = nextScrollPosition.Value; + + if (MathHelper.CloseToPercent(vScrollbar.Value, vScrollbar.ValueTarget)) + _autoScrollActive = false; + } + + private bool TryGetVerticalScrollbar(ScrollContainer scroll, [NotNullWhen(true)] out VScrollBar? vScrollBar) + { + vScrollBar = null; + + foreach (var child in scroll.Children) + { + if (child is not VScrollBar) + continue; + + var castChild = child as VScrollBar; + + if (castChild != null) + { + vScrollBar = castChild; + return true; + } + } + + return false; + } + + private bool TryGetNextScrollPosition([NotNullWhen(true)] out float? nextScrollPosition) + { + nextScrollPosition = null; + + var scroll = MasterTabContainer.Children.ElementAt(MasterTabContainer.CurrentTab) as ScrollContainer; + if (scroll == null) + return false; + + var container = scroll.Children.ElementAt(0) as BoxContainer; + if (container == null || container.Children.Count() == 0) + return false; + + // Exit if the heights of the children haven't been initialized yet + if (!container.Children.Any(x => x.Height > 0)) + return false; + + nextScrollPosition = 0; + + foreach (var control in container.Children) + { + if (control == null || control is not AtmosAlarmEntryContainer) + continue; + + if (((AtmosAlarmEntryContainer)control).NetEntity == _trackedEntity) + return true; + + nextScrollPosition += control.Height; + } + + // Failed to find control + nextScrollPosition = null; + + return false; + } + + private AtmosAlarmType GetAlarmState(NetEntity netEntity) + { + var alarmState = _allAlarms?.FirstOrNull(x => x.NetEntity == netEntity)?.AlarmState; + + if (alarmState == null) + return AtmosAlarmType.Invalid; + + return alarmState.Value; + } + + private (SpriteSpecifier.Texture, Color)? GetBlipTexture(AtmosAlarmType alarmState) + { + (SpriteSpecifier.Texture, Color)? output = null; + + switch (alarmState) + { + case AtmosAlarmType.Invalid: + output = (new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_circle.png")), StyleNano.DisabledFore); break; + case AtmosAlarmType.Normal: + output = (new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_circle.png")), Color.LimeGreen); break; + case AtmosAlarmType.Warning: + output = (new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_triangle.png")), new Color(255, 182, 72)); break; + case AtmosAlarmType.Danger: + output = (new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_square.png")), new Color(255, 67, 67)); break; + } + + return output; + } +} \ No newline at end of file diff --git a/Content.Client/Atmos/EntitySystems/GasTileOverlaySystem.cs b/Content.Client/Atmos/EntitySystems/GasTileOverlaySystem.cs index 78185ce6b0..86cf0a9eb8 100644 --- a/Content.Client/Atmos/EntitySystems/GasTileOverlaySystem.cs +++ b/Content.Client/Atmos/EntitySystems/GasTileOverlaySystem.cs @@ -1,4 +1,5 @@ using Content.Client.Atmos.Overlays; +using Content.Shared.Atmos; using Content.Shared.Atmos.Components; using Content.Shared.Atmos.EntitySystems; using JetBrains.Annotations; @@ -36,28 +37,38 @@ public override void Shutdown() private void OnHandleState(EntityUid gridUid, GasTileOverlayComponent comp, ref ComponentHandleState args) { - if (args.Current is not GasTileOverlayState state) - return; + Dictionary modifiedChunks; - // is this a delta or full state? - if (!state.FullState) + switch (args.Current) { - foreach (var index in comp.Chunks.Keys) + // is this a delta or full state? + case GasTileOverlayDeltaState delta: { - if (!state.AllChunks!.Contains(index)) - comp.Chunks.Remove(index); + modifiedChunks = delta.ModifiedChunks; + foreach (var index in comp.Chunks.Keys) + { + if (!delta.AllChunks.Contains(index)) + comp.Chunks.Remove(index); + } + + break; } - } - else - { - foreach (var index in comp.Chunks.Keys) + case GasTileOverlayState state: { - if (!state.Chunks.ContainsKey(index)) - comp.Chunks.Remove(index); + modifiedChunks = state.Chunks; + foreach (var index in comp.Chunks.Keys) + { + if (!state.Chunks.ContainsKey(index)) + comp.Chunks.Remove(index); + } + + break; } + default: + return; } - foreach (var (index, data) in state.Chunks) + foreach (var (index, data) in modifiedChunks) { comp.Chunks[index] = data; } diff --git a/Content.Client/Atmos/Overlays/AtmosDebugOverlay.cs b/Content.Client/Atmos/Overlays/AtmosDebugOverlay.cs index 6dfbc326ec..c85dbd2051 100644 --- a/Content.Client/Atmos/Overlays/AtmosDebugOverlay.cs +++ b/Content.Client/Atmos/Overlays/AtmosDebugOverlay.cs @@ -66,7 +66,7 @@ protected override void Draw(in OverlayDrawArgs args) DrawData(msg, handle); } - handle.SetTransform(Matrix3.Identity); + handle.SetTransform(Matrix3x2.Identity); } private void DrawData(DebugMessage msg, diff --git a/Content.Client/Atmos/Overlays/GasTileOverlay.cs b/Content.Client/Atmos/Overlays/GasTileOverlay.cs index f4dc274a4e..17027525e5 100644 --- a/Content.Client/Atmos/Overlays/GasTileOverlay.cs +++ b/Content.Client/Atmos/Overlays/GasTileOverlay.cs @@ -190,7 +190,7 @@ protected override void Draw(in OverlayDrawArgs args) var (_, _, worldMatrix, invMatrix) = gridXform.GetWorldPositionRotationMatrixWithInv(); state.drawHandle.SetTransform(worldMatrix); - var floatBounds = invMatrix.TransformBox(in state.WorldBounds).Enlarged(grid.TileSize); + var floatBounds = invMatrix.TransformBox(state.WorldBounds).Enlarged(grid.TileSize); var localBounds = new Box2i( (int) MathF.Floor(floatBounds.Left), (int) MathF.Floor(floatBounds.Bottom), @@ -249,7 +249,7 @@ protected override void Draw(in OverlayDrawArgs args) }); drawHandle.UseShader(null); - drawHandle.SetTransform(Matrix3.Identity); + drawHandle.SetTransform(Matrix3x2.Identity); } private void DrawMapOverlay( diff --git a/Content.Client/Atmos/UI/GasAnalyzerWindow.xaml.cs b/Content.Client/Atmos/UI/GasAnalyzerWindow.xaml.cs index b105e629cf..b54af3a587 100644 --- a/Content.Client/Atmos/UI/GasAnalyzerWindow.xaml.cs +++ b/Content.Client/Atmos/UI/GasAnalyzerWindow.xaml.cs @@ -163,6 +163,26 @@ private void GenerateGasDisplay(GasMixEntry gasMix, Control parent) parent.AddChild(panel); panel.AddChild(dataContainer); + // Volume label + var volBox = new BoxContainer { Orientation = BoxContainer.LayoutOrientation.Horizontal }; + + volBox.AddChild(new Label + { + Text = Loc.GetString("gas-analyzer-window-volume-text") + }); + volBox.AddChild(new Control + { + MinSize = new Vector2(10, 0), + HorizontalExpand = true + }); + volBox.AddChild(new Label + { + Text = Loc.GetString("gas-analyzer-window-volume-val-text", ("volume", $"{gasMix.Volume:0.##}")), + Align = Label.AlignMode.Right, + HorizontalExpand = true + }); + dataContainer.AddChild(volBox); + // Pressure label var presBox = new BoxContainer { Orientation = BoxContainer.LayoutOrientation.Horizontal }; diff --git a/Content.Client/Audio/Jukebox/JukeboxBoundUserInterface.cs b/Content.Client/Audio/Jukebox/JukeboxBoundUserInterface.cs new file mode 100644 index 0000000000..60fe339069 --- /dev/null +++ b/Content.Client/Audio/Jukebox/JukeboxBoundUserInterface.cs @@ -0,0 +1,118 @@ +using Content.Shared.Audio.Jukebox; +using Robust.Client.Audio; +using Robust.Client.Player; +using Robust.Shared.Audio.Components; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; + +namespace Content.Client.Audio.Jukebox; + +public sealed class JukeboxBoundUserInterface : BoundUserInterface +{ + [Dependency] private readonly IPrototypeManager _protoManager = default!; + + [ViewVariables] + private JukeboxMenu? _menu; + + public JukeboxBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) + { + IoCManager.InjectDependencies(this); + } + + protected override void Open() + { + base.Open(); + + _menu = new JukeboxMenu(); + _menu.OnClose += Close; + _menu.OpenCentered(); + + _menu.OnPlayPressed += args => + { + if (args) + { + SendMessage(new JukeboxPlayingMessage()); + } + else + { + SendMessage(new JukeboxPauseMessage()); + } + }; + + _menu.OnStopPressed += () => + { + SendMessage(new JukeboxStopMessage()); + }; + + _menu.OnSongSelected += SelectSong; + + _menu.SetTime += SetTime; + PopulateMusic(); + Reload(); + } + + /// + /// Reloads the attached menu if it exists. + /// + public void Reload() + { + if (_menu == null || !EntMan.TryGetComponent(Owner, out JukeboxComponent? jukebox)) + return; + + _menu.SetAudioStream(jukebox.AudioStream); + + if (_protoManager.TryIndex(jukebox.SelectedSongId, out var songProto)) + { + var length = EntMan.System().GetAudioLength(songProto.Path.Path.ToString()); + _menu.SetSelectedSong(songProto.Name, (float) length.TotalSeconds); + } + else + { + _menu.SetSelectedSong(string.Empty, 0f); + } + } + + public void PopulateMusic() + { + _menu?.Populate(_protoManager.EnumeratePrototypes()); + } + + public void SelectSong(ProtoId songid) + { + SendMessage(new JukeboxSelectedMessage(songid)); + } + + public void SetTime(float time) + { + var sentTime = time; + + // You may be wondering, what the fuck is this + // Well we want to be able to predict the playback slider change, of which there are many ways to do it + // We can't just use SendPredictedMessage because it will reset every tick and audio updates every frame + // so it will go BRRRRT + // Using ping gets us close enough that it SHOULD, MOST OF THE TIME, fall within the 0.1 second tolerance + // that's still on engine so our playback position never gets corrected. + if (EntMan.TryGetComponent(Owner, out JukeboxComponent? jukebox) && + EntMan.TryGetComponent(jukebox.AudioStream, out AudioComponent? audioComp)) + { + audioComp.PlaybackPosition = time; + } + + SendMessage(new JukeboxSetTimeMessage(sentTime)); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (!disposing) + return; + + if (_menu == null) + return; + + _menu.OnClose -= Close; + _menu.Dispose(); + _menu = null; + } +} + diff --git a/Content.Client/Audio/Jukebox/JukeboxMenu.xaml b/Content.Client/Audio/Jukebox/JukeboxMenu.xaml new file mode 100644 index 0000000000..e8d39a9b11 --- /dev/null +++ b/Content.Client/Audio/Jukebox/JukeboxMenu.xaml @@ -0,0 +1,18 @@ + + + + + + + + + + diff --git a/Content.Client/Chemistry/UI/ReagentCardControl.xaml.cs b/Content.Client/Chemistry/UI/ReagentCardControl.xaml.cs new file mode 100644 index 0000000000..60336143fc --- /dev/null +++ b/Content.Client/Chemistry/UI/ReagentCardControl.xaml.cs @@ -0,0 +1,30 @@ +using Content.Shared.Chemistry; +using Robust.Client.AutoGenerated; +using Robust.Client.Graphics; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.XAML; + +namespace Content.Client.Chemistry.UI; + +[GenerateTypedNameReferences] +public sealed partial class ReagentCardControl : Control +{ + public string StorageSlotId { get; } + public Action? OnPressed; + public Action? OnEjectButtonPressed; + + public ReagentCardControl(ReagentInventoryItem item) + { + RobustXamlLoader.Load(this); + + StorageSlotId = item.StorageSlotId; + ColorPanel.PanelOverride = new StyleBoxFlat { BackgroundColor = item.ReagentColor }; + ReagentNameLabel.Text = item.ReagentLabel; + ReagentNameLabel.FontColorOverride = Color.White; + FillLabel.Text = item.StoredAmount; + EjectButtonIcon.Text = Loc.GetString("reagent-dispenser-window-eject-container-button"); + + MainButton.OnPressed += args => OnPressed?.Invoke(StorageSlotId); + EjectButton.OnPressed += args => OnEjectButtonPressed?.Invoke(StorageSlotId); + } +} diff --git a/Content.Client/Chemistry/UI/ReagentDispenserBoundUserInterface.cs b/Content.Client/Chemistry/UI/ReagentDispenserBoundUserInterface.cs index 8244e3e6ed..99e5a3d395 100644 --- a/Content.Client/Chemistry/UI/ReagentDispenserBoundUserInterface.cs +++ b/Content.Client/Chemistry/UI/ReagentDispenserBoundUserInterface.cs @@ -1,3 +1,4 @@ +using Content.Client.Guidebook.Components; using Content.Shared.Chemistry; using Content.Shared.Containers.ItemSlots; using JetBrains.Annotations; @@ -34,6 +35,7 @@ protected override void Open() _window = new() { Title = EntMan.GetComponent(Owner).EntityName, + HelpGuidebookIds = EntMan.GetComponent(Owner).Guides }; _window.OpenCentered(); @@ -42,38 +44,11 @@ protected override void Open() // Setup static button actions. _window.EjectButton.OnPressed += _ => SendMessage(new ItemSlotButtonPressedEvent(SharedReagentDispenser.OutputSlotName)); _window.ClearButton.OnPressed += _ => SendMessage(new ReagentDispenserClearContainerSolutionMessage()); - _window.DispenseButton1.OnPressed += _ => SendMessage(new ReagentDispenserSetDispenseAmountMessage(ReagentDispenserDispenseAmount.U1)); - _window.DispenseButton5.OnPressed += _ => SendMessage(new ReagentDispenserSetDispenseAmountMessage(ReagentDispenserDispenseAmount.U5)); - _window.DispenseButton10.OnPressed += _ => SendMessage(new ReagentDispenserSetDispenseAmountMessage(ReagentDispenserDispenseAmount.U10)); - _window.DispenseButton15.OnPressed += _ => SendMessage(new ReagentDispenserSetDispenseAmountMessage(ReagentDispenserDispenseAmount.U15)); - _window.DispenseButton20.OnPressed += _ => SendMessage(new ReagentDispenserSetDispenseAmountMessage(ReagentDispenserDispenseAmount.U20)); - _window.DispenseButton25.OnPressed += _ => SendMessage(new ReagentDispenserSetDispenseAmountMessage(ReagentDispenserDispenseAmount.U25)); - _window.DispenseButton30.OnPressed += _ => SendMessage(new ReagentDispenserSetDispenseAmountMessage(ReagentDispenserDispenseAmount.U30)); - _window.DispenseButton50.OnPressed += _ => SendMessage(new ReagentDispenserSetDispenseAmountMessage(ReagentDispenserDispenseAmount.U50)); - _window.DispenseButton100.OnPressed += _ => SendMessage(new ReagentDispenserSetDispenseAmountMessage(ReagentDispenserDispenseAmount.U100)); - // Setup reagent button actions. - _window.OnDispenseReagentButtonPressed += (args, button) => SendMessage(new ReagentDispenserDispenseReagentMessage(button.ReagentId)); - _window.OnDispenseReagentButtonMouseEntered += (args, button) => - { - if (_lastState is not null) - _window.UpdateContainerInfo(_lastState); - }; - _window.OnDispenseReagentButtonMouseExited += (args, button) => - { - if (_lastState is not null) - _window.UpdateContainerInfo(_lastState); - }; + _window.AmountGrid.OnButtonPressed += s => SendMessage(new ReagentDispenserSetDispenseAmountMessage(s)); - _window.OnEjectJugButtonPressed += (args, button) => SendMessage(new ItemSlotButtonPressedEvent(button.ReagentId)); - _window.OnEjectJugButtonMouseEntered += (args, button) => { - if (_lastState is not null) - _window.UpdateContainerInfo(_lastState); - }; - _window.OnEjectJugButtonMouseExited += (args, button) => { - if (_lastState is not null) - _window.UpdateContainerInfo(_lastState); - }; + _window.OnDispenseReagentButtonPressed += (id) => SendMessage(new ReagentDispenserDispenseReagentMessage(id)); + _window.OnEjectJugButtonPressed += (id) => SendMessage(new ItemSlotButtonPressedEvent(id)); } /// diff --git a/Content.Client/Chemistry/UI/ReagentDispenserWindow.xaml b/Content.Client/Chemistry/UI/ReagentDispenserWindow.xaml index 3b812ba56b..c900d7ecf2 100644 --- a/Content.Client/Chemistry/UI/ReagentDispenserWindow.xaml +++ b/Content.Client/Chemistry/UI/ReagentDispenserWindow.xaml @@ -1,53 +1,77 @@ - - - - [GenerateTypedNameReferences] - public sealed partial class ReagentDispenserWindow : DefaultWindow + public sealed partial class ReagentDispenserWindow : FancyWindow { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IEntityManager _entityManager = default!; - public event Action? OnDispenseReagentButtonPressed; - public event Action? OnDispenseReagentButtonMouseEntered; - public event Action? OnDispenseReagentButtonMouseExited; - - public event Action? OnEjectJugButtonPressed; - public event Action? OnEjectJugButtonMouseEntered; - public event Action? OnEjectJugButtonMouseExited; + public event Action? OnDispenseReagentButtonPressed; + public event Action? OnEjectJugButtonPressed; /// /// Create and initialize the dispenser UI client-side. Creates the basic layout, @@ -35,44 +29,27 @@ public ReagentDispenserWindow() { RobustXamlLoader.Load(this); IoCManager.InjectDependencies(this); - - var dispenseAmountGroup = new ButtonGroup(); - DispenseButton1.Group = dispenseAmountGroup; - DispenseButton5.Group = dispenseAmountGroup; - DispenseButton10.Group = dispenseAmountGroup; - DispenseButton15.Group = dispenseAmountGroup; - DispenseButton20.Group = dispenseAmountGroup; - DispenseButton25.Group = dispenseAmountGroup; - DispenseButton30.Group = dispenseAmountGroup; - DispenseButton50.Group = dispenseAmountGroup; - DispenseButton100.Group = dispenseAmountGroup; } /// /// Update the button grid of reagents which can be dispensed. /// /// Reagents which can be dispensed by this dispenser - public void UpdateReagentsList(List>> inventory) + public void UpdateReagentsList(List inventory) { - if (ChemicalList == null) + if (ReagentList == null) return; - ChemicalList.Children.Clear(); + ReagentList.Children.Clear(); //Sort inventory by reagentLabel - inventory.Sort((x, y) => x.Value.Key.CompareTo(y.Value.Key)); + inventory.Sort((x, y) => x.ReagentLabel.CompareTo(y.ReagentLabel)); - foreach (KeyValuePair> entry in inventory) + foreach (var item in inventory) { - var button = new DispenseReagentButton(entry.Key, entry.Value.Key, entry.Value.Value); - button.OnPressed += args => OnDispenseReagentButtonPressed?.Invoke(args, button); - button.OnMouseEntered += args => OnDispenseReagentButtonMouseEntered?.Invoke(args, button); - button.OnMouseExited += args => OnDispenseReagentButtonMouseExited?.Invoke(args, button); - ChemicalList.AddChild(button); - var ejectButton = new EjectJugButton(entry.Key); - ejectButton.OnPressed += args => OnEjectJugButtonPressed?.Invoke(args, ejectButton); - ejectButton.OnMouseEntered += args => OnEjectJugButtonMouseEntered?.Invoke(args, ejectButton); - ejectButton.OnMouseExited += args => OnEjectJugButtonMouseExited?.Invoke(args, ejectButton); - ChemicalList.AddChild(ejectButton); + var card = new ReagentCardControl(item); + card.OnPressed += OnDispenseReagentButtonPressed; + card.OnEjectButtonPressed += OnEjectJugButtonPressed; + ReagentList.Children.Add(card); } } @@ -93,36 +70,7 @@ public void UpdateState(BoundUserInterfaceState state) ClearButton.Disabled = castState.OutputContainer is null; EjectButton.Disabled = castState.OutputContainer is null; - switch (castState.SelectedDispenseAmount) - { - case ReagentDispenserDispenseAmount.U1: - DispenseButton1.Pressed = true; - break; - case ReagentDispenserDispenseAmount.U5: - DispenseButton5.Pressed = true; - break; - case ReagentDispenserDispenseAmount.U10: - DispenseButton10.Pressed = true; - break; - case ReagentDispenserDispenseAmount.U15: - DispenseButton15.Pressed = true; - break; - case ReagentDispenserDispenseAmount.U20: - DispenseButton20.Pressed = true; - break; - case ReagentDispenserDispenseAmount.U25: - DispenseButton25.Pressed = true; - break; - case ReagentDispenserDispenseAmount.U30: - DispenseButton30.Pressed = true; - break; - case ReagentDispenserDispenseAmount.U50: - DispenseButton50.Pressed = true; - break; - case ReagentDispenserDispenseAmount.U100: - DispenseButton100.Pressed = true; - break; - } + AmountGrid.Selected = ((int)castState.SelectedDispenseAmount).ToString(); } /// @@ -137,23 +85,15 @@ public void UpdateContainerInfo(ReagentDispenserBoundUserInterfaceState state) if (state.OutputContainer is null) { + ContainerInfoName.Text = ""; + ContainerInfoFill.Text = ""; ContainerInfo.Children.Add(new Label { Text = Loc.GetString("reagent-dispenser-window-no-container-loaded-text") }); return; } - ContainerInfo.Children.Add(new BoxContainer // Name of the container and its fill status (Ex: 44/100u) - { - Orientation = LayoutOrientation.Horizontal, - Children = - { - new Label {Text = $"{state.OutputContainer.DisplayName}: "}, - new Label - { - Text = $"{state.OutputContainer.CurrentVolume}/{state.OutputContainer.MaxVolume}", - StyleClasses = {StyleNano.StyleClassLabelSecondaryColor} - } - } - }); + // Set Name of the container and its fill status (Ex: 44/100u) + ContainerInfoName.Text = state.OutputContainer.DisplayName; + ContainerInfoFill.Text = state.OutputContainer.CurrentVolume + "/" + state.OutputContainer.MaxVolume; foreach (var (reagent, quantity) in state.OutputContainer.Reagents!) { diff --git a/Content.Client/Chemistry/UI/SolutionStatusControl.cs b/Content.Client/Chemistry/UI/SolutionStatusControl.cs new file mode 100644 index 0000000000..1a33ffb0e1 --- /dev/null +++ b/Content.Client/Chemistry/UI/SolutionStatusControl.cs @@ -0,0 +1,59 @@ +using Content.Client.Chemistry.Components; +using Content.Client.Chemistry.EntitySystems; +using Content.Client.Items.UI; +using Content.Client.Message; +using Content.Client.Stylesheets; +using Content.Shared.Chemistry.Components; +using Content.Shared.Chemistry.EntitySystems; +using Content.Shared.FixedPoint; +using Robust.Client.UserInterface.Controls; + +namespace Content.Client.Chemistry.UI; + +/// +/// Displays basic solution information for . +/// +/// +public sealed class SolutionStatusControl : PollingItemStatusControl +{ + private readonly Entity _parent; + private readonly IEntityManager _entityManager; + private readonly SharedSolutionContainerSystem _solutionContainers; + private readonly RichTextLabel _label; + + public SolutionStatusControl( + Entity parent, + IEntityManager entityManager, + SharedSolutionContainerSystem solutionContainers) + { + _parent = parent; + _entityManager = entityManager; + _solutionContainers = solutionContainers; + _label = new RichTextLabel { StyleClasses = { StyleNano.StyleClassItemStatus } }; + AddChild(_label); + } + + protected override Data PollData() + { + if (!_solutionContainers.TryGetSolution(_parent.Owner, _parent.Comp.Solution, out _, out var solution)) + return default; + + FixedPoint2? transferAmount = null; + if (_entityManager.TryGetComponent(_parent.Owner, out SolutionTransferComponent? transfer)) + transferAmount = transfer.TransferAmount; + + return new Data(solution.Volume, solution.MaxVolume, transferAmount); + } + + protected override void Update(in Data data) + { + var markup = Loc.GetString("solution-status-volume", + ("currentVolume", data.Volume), + ("maxVolume", data.MaxVolume)); + if (data.TransferVolume is { } transferVolume) + markup += "\n" + Loc.GetString("solution-status-transfer", ("volume", transferVolume)); + _label.SetMarkup(markup); + } + + public readonly record struct Data(FixedPoint2 Volume, FixedPoint2 MaxVolume, FixedPoint2? TransferVolume); +} diff --git a/Content.Client/Clickable/ClickableComponent.cs b/Content.Client/Clickable/ClickableComponent.cs index cfbd1a99d6..6f75df4683 100644 --- a/Content.Client/Clickable/ClickableComponent.cs +++ b/Content.Client/Clickable/ClickableComponent.cs @@ -38,9 +38,9 @@ public bool CheckClick(SpriteComponent sprite, TransformComponent transform, Ent renderOrder = sprite.RenderOrder; var (spritePos, spriteRot) = transform.GetWorldPositionRotation(xformQuery); var spriteBB = sprite.CalculateRotatedBoundingBox(spritePos, spriteRot, eye.Rotation); - bottom = Matrix3.CreateRotation(eye.Rotation).TransformBox(spriteBB).Bottom; + bottom = Matrix3Helpers.CreateRotation(eye.Rotation).TransformBox(spriteBB).Bottom; - var invSpriteMatrix = Matrix3.Invert(sprite.GetLocalMatrix()); + Matrix3x2.Invert(sprite.GetLocalMatrix(), out var invSpriteMatrix); // This should have been the rotation of the sprite relative to the screen, but this is not the case with no-rot or directional sprites. var relativeRotation = (spriteRot + eye.Rotation).Reduced().FlipPositive(); @@ -48,8 +48,8 @@ public bool CheckClick(SpriteComponent sprite, TransformComponent transform, Ent Angle cardinalSnapping = sprite.SnapCardinals ? relativeRotation.GetCardinalDir().ToAngle() : Angle.Zero; // First we get `localPos`, the clicked location in the sprite-coordinate frame. - var entityXform = Matrix3.CreateInverseTransform(transform.WorldPosition, sprite.NoRotation ? -eye.Rotation : spriteRot - cardinalSnapping); - var localPos = invSpriteMatrix.Transform(entityXform.Transform(worldPos)); + var entityXform = Matrix3Helpers.CreateInverseTransform(transform.WorldPosition, sprite.NoRotation ? -eye.Rotation : spriteRot - cardinalSnapping); + var localPos = Vector2.Transform(Vector2.Transform(worldPos, entityXform), invSpriteMatrix); // Check explicitly defined click-able bounds if (CheckDirBound(sprite, relativeRotation, localPos)) @@ -79,8 +79,8 @@ public bool CheckClick(SpriteComponent sprite, TransformComponent transform, Ent // convert to layer-local coordinates layer.GetLayerDrawMatrix(dir, out var matrix); - var inverseMatrix = Matrix3.Invert(matrix); - var layerLocal = inverseMatrix.Transform(localPos); + Matrix3x2.Invert(matrix, out var inverseMatrix); + var layerLocal = Vector2.Transform(localPos, inverseMatrix); // Convert to image coordinates var layerImagePos = (Vector2i) (layerLocal * EyeManager.PixelsPerMeter * new Vector2(1, -1) + rsiState.Size / 2f); diff --git a/Content.Client/Clothing/ClientClothingSystem.cs b/Content.Client/Clothing/ClientClothingSystem.cs index 7e78ac7d70..dd69521f48 100644 --- a/Content.Client/Clothing/ClientClothingSystem.cs +++ b/Content.Client/Clothing/ClientClothingSystem.cs @@ -11,6 +11,7 @@ using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Client.ResourceManagement; +using Robust.Shared.Serialization.Manager; using Robust.Shared.Serialization.TypeSerializers.Implementations; using Robust.Shared.Utility; using static Robust.Client.GameObjects.SpriteComponent; @@ -46,6 +47,7 @@ public sealed class ClientClothingSystem : ClothingSystem }; [Dependency] private readonly IResourceCache _cache = default!; + [Dependency] private readonly ISerializationManager _serialization = default!; [Dependency] private readonly InventorySystem _inventorySystem = default!; public override void Initialize() @@ -265,6 +267,7 @@ private void RenderEquipment(EntityUid equipee, EntityUid equipment, string slot // temporary, until layer draw depths get added. Basically: a layer with the key "slot" is being used as a // bookmark to determine where in the list of layers we should insert the clothing layers. bool slotLayerExists = sprite.LayerMapTryGet(slot, out var index); + var displacementData = inventory.Displacements.GetValueOrDefault(slot); // add the new layers foreach (var (key, layerData) in ev.Layers) @@ -308,6 +311,28 @@ private void RenderEquipment(EntityUid equipee, EntityUid equipment, string slot sprite.LayerSetData(index, layerData); layer.Offset += slotDef.Offset; + + if (displacementData != null) + { + if (displacementData.ShaderOverride != null) + sprite.LayerSetShader(index, displacementData.ShaderOverride); + + var displacementKey = $"{key}-displacement"; + if (!revealedLayers.Add(displacementKey)) + { + Log.Warning($"Duplicate key for clothing visuals DISPLACEMENT: {displacementKey}."); + continue; + } + + var displacementLayer = _serialization.CreateCopy(displacementData.Layer, notNullableOverride: true); + displacementLayer.CopyToShaderParameters!.LayerKey = key; + + // Add before main layer for this item. + sprite.AddLayer(displacementLayer, index); + sprite.LayerMapSet(displacementKey, index); + + revealedLayers.Add(displacementKey); + } } RaiseLocalEvent(equipment, new EquipmentVisualsUpdatedEvent(equipee, slot, revealedLayers), true); diff --git a/Content.Client/Cocoon/CocoonSystem.cs b/Content.Client/Cocoon/CocoonSystem.cs new file mode 100644 index 0000000000..d3eb4a8205 --- /dev/null +++ b/Content.Client/Cocoon/CocoonSystem.cs @@ -0,0 +1,33 @@ +using Content.Shared.Cocoon; +using Content.Shared.Humanoid; +using Robust.Client.GameObjects; +using Robust.Shared.Containers; +using System.Numerics; + +namespace Content.Client.Cocoon +{ + public sealed class CocoonSystem : EntitySystem + { + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnCocEntInserted); + } + + private void OnCocEntInserted(EntityUid uid, CocoonComponent component, EntInsertedIntoContainerMessage args) + { + if (!TryComp(uid, out var cocoonSprite)) + return; + + if (TryComp(args.Entity, out var humanoidAppearance)) // If humanoid, use height and width + cocoonSprite.Scale = new Vector2(humanoidAppearance.Width, humanoidAppearance.Height); + else if (!TryComp(args.Entity, out var entSprite)) + return; + else if (entSprite.BaseRSI != null) // Set scale based on sprite scale + sprite dimensions. Ideally we would somehow get a bounding box from the sprite size not including transparent pixels, but FUCK figuring that out. + cocoonSprite.Scale = entSprite.Scale * (entSprite.BaseRSI.Size / 32); + else if (entSprite.Scale != cocoonSprite.Scale) // if basersi somehow not found (?) just use scale + cocoonSprite.Scale = entSprite.Scale; + } + } +} diff --git a/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml.cs b/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml.cs index 90643e45cf..4d8dd86a4d 100644 --- a/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml.cs +++ b/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml.cs @@ -1,7 +1,9 @@ using Content.Client.UserInterface.Controls; using System.Threading; +using Content.Shared.CCVar; using Robust.Client.AutoGenerated; using Robust.Client.UserInterface.XAML; +using Robust.Shared.Configuration; using Robust.Shared.Utility; using Timer = Robust.Shared.Timing.Timer; @@ -13,6 +15,8 @@ public sealed partial class CommunicationsConsoleMenu : FancyWindow private CommunicationsConsoleBoundUserInterface Owner { get; set; } private readonly CancellationTokenSource _timerCancelTokenSource = new(); + [Dependency] private readonly IConfigurationManager _cfg = default!; + public CommunicationsConsoleMenu(CommunicationsConsoleBoundUserInterface owner) { IoCManager.InjectDependencies(this); @@ -23,6 +27,22 @@ public CommunicationsConsoleMenu(CommunicationsConsoleBoundUserInterface owner) var loc = IoCManager.Resolve(); MessageInput.Placeholder = new Rope.Leaf(loc.GetString("comms-console-menu-announcement-placeholder")); + var maxAnnounceLength = _cfg.GetCVar(CCVars.ChatMaxAnnouncementLength); + MessageInput.OnTextChanged += (args) => + { + if (args.Control.TextLength > maxAnnounceLength) + { + AnnounceButton.Disabled = true; + AnnounceButton.ToolTip = Loc.GetString("comms-console-message-too-long"); + } + else + { + AnnounceButton.Disabled = !owner.CanAnnounce; + AnnounceButton.ToolTip = null; + + } + }; + AnnounceButton.OnPressed += (_) => Owner.AnnounceButtonPressed(Rope.Collapse(MessageInput.TextRope)); AnnounceButton.Disabled = !owner.CanAnnounce; diff --git a/Content.Client/CriminalRecords/Systems/CriminalRecordsHackerSystem.cs b/Content.Client/CriminalRecords/Systems/CriminalRecordsHackerSystem.cs new file mode 100644 index 0000000000..21fccc880d --- /dev/null +++ b/Content.Client/CriminalRecords/Systems/CriminalRecordsHackerSystem.cs @@ -0,0 +1,7 @@ +using Content.Shared.CriminalRecords.Systems; + +namespace Content.Client.CriminalRecords.Systems; + +public sealed class CriminalRecordsHackerSystem : SharedCriminalRecordsHackerSystem +{ +} diff --git a/Content.Client/Decals/DecalSystem.cs b/Content.Client/Decals/DecalSystem.cs index 901ab270fb..41e5f39c28 100644 --- a/Content.Client/Decals/DecalSystem.cs +++ b/Content.Client/Decals/DecalSystem.cs @@ -56,34 +56,43 @@ protected override void OnDecalRemoved(EntityUid gridId, uint decalId, DecalGrid private void OnHandleState(EntityUid gridUid, DecalGridComponent gridComp, ref ComponentHandleState args) { - if (args.Current is not DecalGridState state) - return; - // is this a delta or full state? _removedChunks.Clear(); + Dictionary modifiedChunks; - if (!state.FullState) + switch (args.Current) { - foreach (var key in gridComp.ChunkCollection.ChunkCollection.Keys) + case DecalGridDeltaState delta: { - if (!state.AllChunks!.Contains(key)) - _removedChunks.Add(key); + modifiedChunks = delta.ModifiedChunks; + foreach (var key in gridComp.ChunkCollection.ChunkCollection.Keys) + { + if (!delta.AllChunks.Contains(key)) + _removedChunks.Add(key); + } + + break; } - } - else - { - foreach (var key in gridComp.ChunkCollection.ChunkCollection.Keys) + case DecalGridState state: { - if (!state.Chunks.ContainsKey(key)) - _removedChunks.Add(key); + modifiedChunks = state.Chunks; + foreach (var key in gridComp.ChunkCollection.ChunkCollection.Keys) + { + if (!state.Chunks.ContainsKey(key)) + _removedChunks.Add(key); + } + + break; } + default: + return; } if (_removedChunks.Count > 0) RemoveChunks(gridUid, gridComp, _removedChunks); - if (state.Chunks.Count > 0) - UpdateChunks(gridUid, gridComp, state.Chunks); + if (modifiedChunks.Count > 0) + UpdateChunks(gridUid, gridComp, modifiedChunks); } private void OnChunkUpdate(DecalChunkUpdateEvent ev) diff --git a/Content.Client/Decals/Overlays/DecalOverlay.cs b/Content.Client/Decals/Overlays/DecalOverlay.cs index d9904ae80b..0de3301e58 100644 --- a/Content.Client/Decals/Overlays/DecalOverlay.cs +++ b/Content.Client/Decals/Overlays/DecalOverlay.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Content.Shared.Decals; using Robust.Client.GameObjects; using Robust.Client.Graphics; @@ -113,7 +114,7 @@ protected override void Draw(in OverlayDrawArgs args) handle.DrawTexture(cache.Texture, decal.Coordinates, angle, decal.Color); } - handle.SetTransform(Matrix3.Identity); + handle.SetTransform(Matrix3x2.Identity); } } } diff --git a/Content.Client/Decals/Overlays/DecalPlacementOverlay.cs b/Content.Client/Decals/Overlays/DecalPlacementOverlay.cs index be277448ed..845bd7c03d 100644 --- a/Content.Client/Decals/Overlays/DecalPlacementOverlay.cs +++ b/Content.Client/Decals/Overlays/DecalPlacementOverlay.cs @@ -51,7 +51,7 @@ protected override void Draw(in OverlayDrawArgs args) var handle = args.WorldHandle; handle.SetTransform(worldMatrix); - var localPos = invMatrix.Transform(mousePos.Position); + var localPos = Vector2.Transform(mousePos.Position, invMatrix); if (snap) { @@ -63,6 +63,6 @@ protected override void Draw(in OverlayDrawArgs args) var box = new Box2Rotated(aabb, rotation, localPos); handle.DrawTextureRect(_sprite.Frame0(decal.Sprite), box, color); - handle.SetTransform(Matrix3.Identity); + handle.SetTransform(Matrix3x2.Identity); } } diff --git a/Content.Client/DeltaV/Options/UI/Tabs/DeltaTab.xaml b/Content.Client/DeltaV/Options/UI/Tabs/DeltaTab.xaml deleted file mode 100644 index f1dae68077..0000000000 --- a/Content.Client/DeltaV/Options/UI/Tabs/DeltaTab.xaml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - diff --git a/Content.Client/Lathe/UI/RecipeControl.xaml.cs b/Content.Client/Lathe/UI/RecipeControl.xaml.cs index bf85ff7d93..47b6b5932c 100644 --- a/Content.Client/Lathe/UI/RecipeControl.xaml.cs +++ b/Content.Client/Lathe/UI/RecipeControl.xaml.cs @@ -2,8 +2,8 @@ using Robust.Client.AutoGenerated; using Robust.Client.Graphics; using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.XAML; -using Robust.Shared.Graphics; namespace Content.Client.Lathe.UI; @@ -13,12 +13,12 @@ public sealed partial class RecipeControl : Control public Action? OnButtonPressed; public Func TooltipTextSupplier; - public RecipeControl(LatheRecipePrototype recipe, Func tooltipTextSupplier, bool canProduce, Texture? texture = null) + public RecipeControl(LatheRecipePrototype recipe, Func tooltipTextSupplier, bool canProduce, List textures) { RobustXamlLoader.Load(this); RecipeName.Text = recipe.Name; - RecipeTexture.Texture = texture; + RecipeTextures.Textures = textures; Button.Disabled = !canProduce; TooltipTextSupplier = tooltipTextSupplier; Button.TooltipSupplier = SupplyTooltip; diff --git a/Content.Client/Light/EntitySystems/ExpendableLightSystem.cs b/Content.Client/Light/EntitySystems/ExpendableLightSystem.cs index a2a7fb2531..8077406730 100644 --- a/Content.Client/Light/EntitySystems/ExpendableLightSystem.cs +++ b/Content.Client/Light/EntitySystems/ExpendableLightSystem.cs @@ -52,7 +52,7 @@ protected override void OnAppearanceChange(EntityUid uid, ExpendableLightCompone case ExpendableLightState.Lit: _audioSystem.Stop(comp.PlayingStream); comp.PlayingStream = _audioSystem.PlayPvs( - comp.LoopedSound, uid, SharedExpendableLightComponent.LoopedSoundParams)?.Entity; + comp.LoopedSound, uid)?.Entity; if (args.Sprite.LayerMapTryGet(ExpendableLightVisualLayers.Overlay, out var layerIdx, true)) { diff --git a/Content.Client/Preferences/ClientPreferencesManager.cs b/Content.Client/Lobby/ClientPreferencesManager.cs similarity index 99% rename from Content.Client/Preferences/ClientPreferencesManager.cs rename to Content.Client/Lobby/ClientPreferencesManager.cs index aca7159504..2926968657 100644 --- a/Content.Client/Preferences/ClientPreferencesManager.cs +++ b/Content.Client/Lobby/ClientPreferencesManager.cs @@ -10,7 +10,7 @@ using Robust.Shared.Prototypes; using Robust.Shared.Utility; -namespace Content.Client.Preferences +namespace Content.Client.Lobby { /// /// Receives and from the server during the initial diff --git a/Content.Client/Preferences/IClientPreferencesManager.cs b/Content.Client/Lobby/IClientPreferencesManager.cs similarity index 94% rename from Content.Client/Preferences/IClientPreferencesManager.cs rename to Content.Client/Lobby/IClientPreferencesManager.cs index e55d6b600c..1b72593ad0 100644 --- a/Content.Client/Preferences/IClientPreferencesManager.cs +++ b/Content.Client/Lobby/IClientPreferencesManager.cs @@ -1,7 +1,7 @@ using System; using Content.Shared.Preferences; -namespace Content.Client.Preferences +namespace Content.Client.Lobby { public interface IClientPreferencesManager { diff --git a/Content.Client/Lobby/LobbyState.cs b/Content.Client/Lobby/LobbyState.cs index bed52217a9..2e728f552a 100644 --- a/Content.Client/Lobby/LobbyState.cs +++ b/Content.Client/Lobby/LobbyState.cs @@ -3,8 +3,6 @@ using Content.Client.LateJoin; using Content.Client.Lobby.UI; using Content.Client.Message; -using Content.Client.Preferences; -using Content.Client.Preferences.UI; using Content.Client.UserInterface.Systems.Chat; using Content.Client.Voting; using Robust.Client; @@ -12,8 +10,6 @@ using Robust.Client.ResourceManagement; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; -using Robust.Shared.Configuration; -using Robust.Shared.Prototypes; using Robust.Shared.Timing; @@ -25,59 +21,39 @@ public sealed class LobbyState : Robust.Client.State.State [Dependency] private readonly IClientConsoleHost _consoleHost = default!; [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IResourceCache _resourceCache = default!; - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!; - [Dependency] private readonly IClientPreferencesManager _preferencesManager = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IVoteManager _voteManager = default!; - [Dependency] private readonly IConfigurationManager _configurationManager = default!; - - [ViewVariables] private CharacterSetupGui? _characterSetup; private ClientGameTicker _gameTicker = default!; private ContentAudioSystem _contentAudioSystem = default!; protected override Type? LinkedScreenType { get; } = typeof(LobbyGui); - private LobbyGui? _lobby; + public LobbyGui? Lobby; protected override void Startup() { if (_userInterfaceManager.ActiveScreen == null) return; - _lobby = (LobbyGui) _userInterfaceManager.ActiveScreen; + Lobby = (LobbyGui) _userInterfaceManager.ActiveScreen; var chatController = _userInterfaceManager.GetUIController(); _gameTicker = _entityManager.System(); _contentAudioSystem = _entityManager.System(); _contentAudioSystem.LobbySoundtrackChanged += UpdateLobbySoundtrackInfo; - _characterSetup = new CharacterSetupGui(_entityManager, _resourceCache, _preferencesManager, - _prototypeManager, _configurationManager); - LayoutContainer.SetAnchorPreset(_characterSetup, LayoutContainer.LayoutPreset.Wide); - _lobby.CharacterSetupState.AddChild(_characterSetup); chatController.SetMainChat(true); - _voteManager.SetPopupContainer(_lobby.VoteContainer); - - _characterSetup.CloseButton.OnPressed += _ => - { - _lobby.SwitchState(LobbyGui.LobbyGuiState.Default); - }; + _voteManager.SetPopupContainer(Lobby.VoteContainer); + LayoutContainer.SetAnchorPreset(Lobby, LayoutContainer.LayoutPreset.Wide); + Lobby.ServerName.Text = _baseClient.GameInfo?.ServerName; // The eye of refactor gazes upon you... - _characterSetup.SaveButton.OnPressed += _ => - { - _characterSetup.Save(); - _userInterfaceManager.GetUIController().UpdateCharacterUI(); - }; - - LayoutContainer.SetAnchorPreset(_lobby, LayoutContainer.LayoutPreset.Wide); - _lobby.ServerName.Text = _baseClient.GameInfo?.ServerName; //The eye of refactor gazes upon you... UpdateLobbyUi(); - _lobby.CharacterPreview.CharacterSetupButton.OnPressed += OnSetupPressed; - _lobby.ReadyButton.OnPressed += OnReadyPressed; - _lobby.ReadyButton.OnToggled += OnReadyToggled; + Lobby.CharacterPreview.CharacterSetupButton.OnPressed += OnSetupPressed; + Lobby.ReadyButton.OnPressed += OnReadyPressed; + Lobby.ReadyButton.OnToggled += OnReadyToggled; _gameTicker.InfoBlobUpdated += UpdateLobbyUi; _gameTicker.LobbyStatusUpdated += LobbyStatusUpdated; @@ -95,20 +71,23 @@ protected override void Shutdown() _voteManager.ClearPopupContainer(); - _lobby!.CharacterPreview.CharacterSetupButton.OnPressed -= OnSetupPressed; - _lobby!.ReadyButton.OnPressed -= OnReadyPressed; - _lobby!.ReadyButton.OnToggled -= OnReadyToggled; + Lobby!.CharacterPreview.CharacterSetupButton.OnPressed -= OnSetupPressed; + Lobby!.ReadyButton.OnPressed -= OnReadyPressed; + Lobby!.ReadyButton.OnToggled -= OnReadyToggled; - _lobby = null; + Lobby = null; + } - _characterSetup?.Dispose(); - _characterSetup = null; + public void SwitchState(LobbyGui.LobbyGuiState state) + { + // Yeah I hate this but LobbyState contains all the badness for now + Lobby?.SwitchState(state); } private void OnSetupPressed(BaseButton.ButtonEventArgs args) { SetReady(false); - _lobby!.SwitchState(LobbyGui.LobbyGuiState.CharacterSetup); + Lobby?.SwitchState(LobbyGui.LobbyGuiState.CharacterSetup); } private void OnReadyPressed(BaseButton.ButtonEventArgs args) @@ -128,20 +107,20 @@ public override void FrameUpdate(FrameEventArgs e) { if (_gameTicker.IsGameStarted) { - _lobby!.StartTime.Text = string.Empty; + Lobby!.StartTime.Text = string.Empty; var roundTime = _gameTiming.CurTime.Subtract(_gameTicker.RoundStartTimeSpan); - _lobby!.StationTime.Text = Loc.GetString("lobby-state-player-status-round-time", ("hours", roundTime.Hours), ("minutes", roundTime.Minutes)); + Lobby!.StationTime.Text = Loc.GetString("lobby-state-player-status-round-time", ("hours", roundTime.Hours), ("minutes", roundTime.Minutes)); return; } - _lobby!.StationTime.Text = Loc.GetString("lobby-state-player-status-round-not-started"); + Lobby!.StationTime.Text = Loc.GetString("lobby-state-player-status-round-not-started"); string text; if (_gameTicker.Paused) text = Loc.GetString("lobby-state-paused"); else if (_gameTicker.StartTime < _gameTiming.CurTime) { - _lobby!.StartTime.Text = Loc.GetString("lobby-state-soon"); + Lobby!.StartTime.Text = Loc.GetString("lobby-state-soon"); return; } else @@ -156,7 +135,7 @@ public override void FrameUpdate(FrameEventArgs e) text = $"{difference.Minutes}:{difference.Seconds:D2}"; } - _lobby!.StartTime.Text = Loc.GetString("lobby-state-round-start-countdown-text", ("timeLeft", text)); + Lobby!.StartTime.Text = Loc.GetString("lobby-state-round-start-countdown-text", ("timeLeft", text)); } private void LobbyStatusUpdated() @@ -167,36 +146,36 @@ private void LobbyStatusUpdated() private void LobbyLateJoinStatusUpdated() { - _lobby!.ReadyButton.Disabled = _gameTicker.DisallowedLateJoin; + Lobby!.ReadyButton.Disabled = _gameTicker.DisallowedLateJoin; } private void UpdateLobbyUi() { if (_gameTicker.IsGameStarted) { - _lobby!.ReadyButton.Text = Loc.GetString("lobby-state-ready-button-join-state"); - _lobby!.ReadyButton.ToggleMode = false; - _lobby!.ReadyButton.Pressed = false; - _lobby!.ObserveButton.Disabled = false; + Lobby!.ReadyButton.Text = Loc.GetString("lobby-state-ready-button-join-state"); + Lobby!.ReadyButton.ToggleMode = false; + Lobby!.ReadyButton.Pressed = false; + Lobby!.ObserveButton.Disabled = false; } else { - _lobby!.StartTime.Text = string.Empty; - _lobby!.ReadyButton.Text = Loc.GetString(_lobby!.ReadyButton.Pressed ? "lobby-state-player-status-ready": "lobby-state-player-status-not-ready"); - _lobby!.ReadyButton.ToggleMode = true; - _lobby!.ReadyButton.Disabled = false; - _lobby!.ReadyButton.Pressed = _gameTicker.AreWeReady; - _lobby!.ObserveButton.Disabled = true; + Lobby!.StartTime.Text = string.Empty; + Lobby!.ReadyButton.Text = Loc.GetString(Lobby!.ReadyButton.Pressed ? "lobby-state-player-status-ready": "lobby-state-player-status-not-ready"); + Lobby!.ReadyButton.ToggleMode = true; + Lobby!.ReadyButton.Disabled = false; + Lobby!.ReadyButton.Pressed = _gameTicker.AreWeReady; + Lobby!.ObserveButton.Disabled = true; } if (_gameTicker.ServerInfoBlob != null) - _lobby!.ServerInfo.SetInfoBlob(_gameTicker.ServerInfoBlob); + Lobby!.ServerInfo.SetInfoBlob(_gameTicker.ServerInfoBlob); } private void UpdateLobbySoundtrackInfo(LobbySoundtrackChangedEvent ev) { if (ev.SoundtrackFilename == null) - _lobby!.LobbySong.SetMarkup(Loc.GetString("lobby-state-song-no-song-text")); + Lobby!.LobbySong.SetMarkup(Loc.GetString("lobby-state-song-no-song-text")); else if (ev.SoundtrackFilename != null && _resourceCache.TryGetResource(ev.SoundtrackFilename, out var lobbySongResource)) { @@ -214,16 +193,16 @@ private void UpdateLobbySoundtrackInfo(LobbySoundtrackChangedEvent ev) ("songTitle", title), ("songArtist", artist)); - _lobby!.LobbySong.SetMarkup(markup); + Lobby!.LobbySong.SetMarkup(markup); } } private void UpdateLobbyBackground() { if (_gameTicker.LobbyBackground != null) - _lobby!.Background.Texture = _resourceCache.GetResource(_gameTicker.LobbyBackground); + Lobby!.Background.Texture = _resourceCache.GetResource(_gameTicker.LobbyBackground); else - _lobby!.Background.Texture = null; + Lobby!.Background.Texture = null; } diff --git a/Content.Client/Lobby/LobbyUIController.cs b/Content.Client/Lobby/LobbyUIController.cs index 47ab651c10..26643cb603 100644 --- a/Content.Client/Lobby/LobbyUIController.cs +++ b/Content.Client/Lobby/LobbyUIController.cs @@ -3,156 +3,252 @@ using Content.Client.Inventory; using Content.Client.Lobby.UI; using Content.Client.Players.PlayTimeTracking; -using Content.Client.Preferences; -using Content.Client.Preferences.UI; +using Content.Shared.CCVar; +using Content.Shared.Clothing.Loadouts.Prototypes; using Content.Shared.Clothing.Loadouts.Systems; using Content.Shared.GameTicking; -using Content.Shared.Humanoid; +using Content.Shared.Humanoid.Markings; using Content.Shared.Humanoid.Prototypes; using Content.Shared.Preferences; using Content.Shared.Roles; +using Content.Shared.Traits; +using Robust.Client.Player; +using Robust.Client.ResourceManagement; using Robust.Client.State; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controllers; +using Robust.Shared.Configuration; using Robust.Shared.Map; using Robust.Shared.Prototypes; +using Robust.Shared.Utility; +using static Content.Shared.Humanoid.SharedHumanoidAppearanceSystem; +using CharacterSetupGui = Content.Client.Lobby.UI.CharacterSetupGui; +using HumanoidProfileEditor = Content.Client.Lobby.UI.HumanoidProfileEditor; namespace Content.Client.Lobby; public sealed class LobbyUIController : UIController, IOnStateEntered, IOnStateExited { [Dependency] private readonly IClientPreferencesManager _preferencesManager = default!; - [Dependency] private readonly IStateManager _stateManager = default!; + [Dependency] private readonly IConfigurationManager _configurationManager = default!; + [Dependency] private readonly IFileDialogManager _dialogManager = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IResourceCache _resourceCache = default!; + [Dependency] private readonly IStateManager _stateManager = default!; + [Dependency] private readonly JobRequirementsManager _requirements = default!; + [Dependency] private readonly MarkingManager _markings = default!; [Dependency] private readonly JobRequirementsManager _jobRequirements = default!; [UISystemDependency] private readonly HumanoidAppearanceSystem _humanoid = default!; [UISystemDependency] private readonly ClientInventorySystem _inventory = default!; [UISystemDependency] private readonly LoadoutSystem _loadouts = default!; - private LobbyCharacterPanel? _previewPanel; + private CharacterSetupGui? _characterSetup; private HumanoidProfileEditor? _profileEditor; - /* - * Each character profile has its own dummy. There is also a dummy for the lobby screen + character editor - * that is shared too. - */ + /// This is the character preview panel in the chat. This should only update if their character updates + private LobbyCharacterPreviewPanel? PreviewPanel => GetLobbyPreview(); - /// - /// Preview dummy for role gear. - /// - private EntityUid? _previewDummy; + /// This is the modified profile currently being edited + private HumanoidCharacterProfile? EditedProfile => _profileEditor?.Profile; + private int? EditedSlot => _profileEditor?.CharacterSlot; - [Access(typeof(HumanoidProfileEditor))] - public bool UpdateClothes = true; - [Access(typeof(HumanoidProfileEditor))] - public bool ShowClothes = true; - [Access(typeof(HumanoidProfileEditor))] - public bool ShowLoadouts = true; - - // TODO: Load the species directly and don't update entity ever. - public event Action? PreviewDummyUpdated; public override void Initialize() { base.Initialize(); + _prototypeManager.PrototypesReloaded += OnPrototypesReloaded; _preferencesManager.OnServerDataLoaded += PreferencesDataLoaded; + _requirements.Updated += OnRequirementsUpdated; + + _configurationManager.OnValueChanged(CCVars.FlavorText, _ => _profileEditor?.RefreshFlavorText()); + _configurationManager.OnValueChanged(CCVars.GameRoleTimers, _ => RefreshProfileEditor()); + _configurationManager.OnValueChanged(CCVars.GameRoleWhitelist, _ => RefreshProfileEditor()); + + _preferencesManager.OnServerDataLoaded += PreferencesDataLoaded; + } + + public void OnStateEntered(LobbyState state) + { + PreviewPanel?.SetLoaded(_preferencesManager.ServerDataLoaded); + ReloadCharacterSetup(); + } + + public void OnStateExited(LobbyState state) + { + PreviewPanel?.SetLoaded(false); + _characterSetup?.Dispose(); + _profileEditor?.Dispose(); + _characterSetup = null; + _profileEditor = null; } + private void PreferencesDataLoaded() { - if (_previewDummy != null) - EntityManager.DeleteEntity(_previewDummy); + PreviewPanel?.SetLoaded(true); + + if (_stateManager.CurrentState is not LobbyState) + return; - UpdateCharacterUI(); + ReloadCharacterSetup(); } - public void OnStateEntered(LobbyState state) + private LobbyCharacterPreviewPanel? GetLobbyPreview() { + return _stateManager.CurrentState is LobbyState lobby ? lobby.Lobby?.CharacterPreview : null; } - public void OnStateExited(LobbyState state) + private void OnRequirementsUpdated() { - EntityManager.DeleteEntity(_previewDummy); - _previewDummy = null; + if (_profileEditor == null) + return; + + _profileEditor.RefreshAntags(); + _profileEditor.RefreshJobs(); } - public void SetPreviewPanel(LobbyCharacterPanel? panel) + private void OnPrototypesReloaded(PrototypesReloadedEventArgs obj) { - _previewPanel = panel; - UpdateCharacterUI(); + if (_profileEditor == null) + return; + + if (obj.WasModified()) + _profileEditor.RefreshSpecies(); + + if (obj.WasModified()) + _profileEditor.RefreshAntags(); + + if (obj.WasModified() + || obj.WasModified()) + _profileEditor.RefreshJobs(); + + if (obj.WasModified()) + _profileEditor.UpdateTraits(null, true); + + if (obj.WasModified()) + _profileEditor.UpdateLoadouts(null, true); } - public void SetProfileEditor(HumanoidProfileEditor? editor) + + /// Reloads every single character setup control + public void ReloadCharacterSetup() { - _profileEditor = editor; - UpdateCharacterUI(); + RefreshLobbyPreview(); + var (characterGui, profileEditor) = EnsureGui(); + characterGui.ReloadCharacterPickers(); + profileEditor.SetProfile( + (HumanoidCharacterProfile?) _preferencesManager.Preferences?.SelectedCharacter, + _preferencesManager.Preferences?.SelectedCharacterIndex); } - public void UpdateCharacterUI() + /// Refreshes the character preview in the lobby chat + private void RefreshLobbyPreview() { - // Test moment - if (_stateManager.CurrentState is not LobbyState) + if (PreviewPanel == null) return; - if (!_preferencesManager.ServerDataLoaded) + // Get selected character, load it, then set it + var character = _preferencesManager.Preferences?.SelectedCharacter; + + if (character is not HumanoidCharacterProfile humanoid) { - _previewPanel?.SetLoaded(false); + PreviewPanel.SetSprite(EntityUid.Invalid); + PreviewPanel.SetSummaryText(string.Empty); return; } - var maybeProfile = _profileEditor?.Profile ?? (HumanoidCharacterProfile) _preferencesManager.Preferences!.SelectedCharacter; + var dummy = LoadProfileEntity(humanoid, true); + PreviewPanel.SetSprite(dummy); + PreviewPanel.SetSummaryText(humanoid.Summary); + } - if (_previewDummy == null - || maybeProfile.Species != EntityManager.GetComponent(_previewDummy.Value).Species) - { - RespawnDummy(maybeProfile); - _previewPanel?.SetSprite(_previewDummy!.Value); - } + private void RefreshProfileEditor() + { + _profileEditor?.RefreshAntags(); + _profileEditor?.RefreshJobs(); + } - _previewPanel?.SetLoaded(true); + private void SaveProfile() + { + DebugTools.Assert(EditedProfile != null); - if (_previewDummy == null) + if (EditedProfile == null || EditedSlot == null) return; - _previewPanel?.SetSummaryText(maybeProfile.Summary); - _humanoid.LoadProfile(_previewDummy.Value, maybeProfile); + var selected = _preferencesManager.Preferences?.SelectedCharacterIndex; + if (selected == null) + return; + _preferencesManager.UpdateCharacter(EditedProfile, EditedSlot.Value); + ReloadCharacterSetup(); + } - if (UpdateClothes) + private (CharacterSetupGui, HumanoidProfileEditor) EnsureGui() + { + if (_characterSetup != null && _profileEditor != null) { - RemoveDummyClothes(_previewDummy.Value); - if (ShowClothes) - GiveDummyJobClothes(_previewDummy.Value, GetPreferredJob(maybeProfile), maybeProfile); - if (ShowLoadouts) - _loadouts.ApplyCharacterLoadout(_previewDummy.Value, GetPreferredJob(maybeProfile), maybeProfile, - _jobRequirements.GetRawPlayTimeTrackers(), _jobRequirements.IsWhitelisted()); - UpdateClothes = false; + _characterSetup.Visible = true; + _profileEditor.Visible = true; + return (_characterSetup, _profileEditor); } - PreviewDummyUpdated?.Invoke(_previewDummy.Value); - } + _profileEditor = new HumanoidProfileEditor( + _preferencesManager, + _configurationManager, + EntityManager, + _dialogManager, + _playerManager, + _prototypeManager, + _requirements, + _markings); + _characterSetup = new CharacterSetupGui(EntityManager, _prototypeManager, _resourceCache, _preferencesManager, _profileEditor); - public void RespawnDummy(HumanoidCharacterProfile profile) - { - if (_previewDummy != null) - RemoveDummyClothes(_previewDummy.Value); + _characterSetup.CloseButton.OnPressed += _ => + { + // Reset sliders etc. + _profileEditor.SetProfile(null, null); + _profileEditor.Visible = false; + if (_stateManager.CurrentState is LobbyState lobbyGui) + { + lobbyGui.SwitchState(LobbyGui.LobbyGuiState.Default); + } + }; + + _profileEditor.Save += SaveProfile; + + _characterSetup.SelectCharacter += args => + { + _preferencesManager.SelectCharacter(args); + ReloadCharacterSetup(); + }; + + _characterSetup.DeleteCharacter += args => + { + _preferencesManager.DeleteCharacter(args); - EntityManager.DeleteEntity(_previewDummy); - _previewDummy = EntityManager.SpawnEntity( - _prototypeManager.Index(profile.Species).DollPrototype, MapCoordinates.Nullspace); + if (EditedSlot == args) + // Reload everything + ReloadCharacterSetup(); + else + // Only need to reload character pickers + _characterSetup?.ReloadCharacterPickers(); + }; - UpdateClothes = true; + if (_stateManager.CurrentState is LobbyState lobby) + lobby.Lobby?.CharacterSetupState.AddChild(_characterSetup); + + return (_characterSetup, _profileEditor); } - /// - /// Gets the highest priority job for the profile. - /// + #region Helpers + + /// Gets the highest priority job for the profile. public JobPrototype GetPreferredJob(HumanoidCharacterProfile profile) { var highPriorityJob = profile.JobPriorities.FirstOrDefault(p => p.Value == JobPriority.High).Key; - // ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract (what is ReSharper smoking?) return _prototypeManager.Index(highPriorityJob ?? SharedGameTicker.FallbackOverflowJob); } @@ -166,9 +262,7 @@ public void RemoveDummyClothes(EntityUid dummy) EntityManager.DeleteEntity(unequippedItem.Value); } - /// - /// Applies the highest priority job's clothes and loadouts to the dummy. - /// + /// Applies the highest priority job's clothes and loadouts to the dummy. public void GiveDummyJobClothesLoadout(EntityUid dummy, HumanoidCharacterProfile profile) { var job = GetPreferredJob(profile); @@ -176,9 +270,7 @@ public void GiveDummyJobClothesLoadout(EntityUid dummy, HumanoidCharacterProfile _loadouts.ApplyCharacterLoadout(dummy, job, profile, _jobRequirements.GetRawPlayTimeTrackers(), _jobRequirements.IsWhitelisted()); } - /// - /// Applies the specified job's clothes to the dummy. - /// + /// Applies the specified job's clothes to the dummy. public void GiveDummyJobClothes(EntityUid dummy, JobPrototype job, HumanoidCharacterProfile profile) { if (!_inventory.TryGetSlots(dummy, out var slots) @@ -202,8 +294,28 @@ public void GiveDummyJobClothes(EntityUid dummy, JobPrototype job, HumanoidChara } } - public EntityUid? GetPreviewDummy() + /// Loads the profile onto a dummy entity + public EntityUid LoadProfileEntity(HumanoidCharacterProfile? humanoid, bool jobClothes) { - return _previewDummy; + EntityUid dummyEnt; + + if (humanoid is not null) + { + var dummy = _prototypeManager.Index(humanoid.Species).DollPrototype; + dummyEnt = EntityManager.SpawnEntity(dummy, MapCoordinates.Nullspace); + } + else + dummyEnt = EntityManager.SpawnEntity( + _prototypeManager.Index(DefaultSpecies).DollPrototype, + MapCoordinates.Nullspace); + + _humanoid.LoadProfile(dummyEnt, humanoid); + + if (humanoid != null && jobClothes) + GiveDummyJobClothesLoadout(dummyEnt, humanoid); + + return dummyEnt; } + + #endregion } diff --git a/Content.Client/Lobby/UI/CharacterPickerButton.xaml b/Content.Client/Lobby/UI/CharacterPickerButton.xaml new file mode 100644 index 0000000000..fa428b9b61 --- /dev/null +++ b/Content.Client/Lobby/UI/CharacterPickerButton.xaml @@ -0,0 +1,11 @@ + + + + + [GenerateTypedNameReferences] + public sealed partial class CharacterSetupGui : Control + { + private readonly IClientPreferencesManager _preferencesManager; + private readonly IEntityManager _entManager; + private readonly IPrototypeManager _protomanager; + + private readonly Button _createNewCharacterButton; + + public event Action? SelectCharacter; + public event Action? DeleteCharacter; + + public CharacterSetupGui( + IEntityManager entManager, + IPrototypeManager protoManager, + IResourceCache resourceCache, + IClientPreferencesManager preferencesManager, + HumanoidProfileEditor profileEditor) + { + RobustXamlLoader.Load(this); + _preferencesManager = preferencesManager; + _entManager = entManager; + _protomanager = protoManager; + + var panelTex = resourceCache.GetTexture("/Textures/Interface/Nano/button.svg.96dpi.png"); + var back = new StyleBoxTexture + { + Texture = panelTex, + Modulate = new Color(37, 37, 42) + }; + back.SetPatchMargin(StyleBox.Margin.All, 10); + + BackgroundPanel.PanelOverride = back; + + _createNewCharacterButton = new Button + { + Text = Loc.GetString("character-setup-gui-create-new-character-button"), + }; + + _createNewCharacterButton.OnPressed += args => + { + preferencesManager.CreateCharacter(HumanoidCharacterProfile.Random()); + ReloadCharacterPickers(); + args.Event.Handle(); + }; + + CharEditor.AddChild(profileEditor); + RulesButton.OnPressed += _ => new RulesAndInfoWindow().Open(); + + StatsButton.OnPressed += _ => new PlaytimeStatsWindow().OpenCentered(); + } + + /// + /// Disposes and reloads all character picker buttons from the preferences data. + /// + public void ReloadCharacterPickers() + { + _createNewCharacterButton.Orphan(); + Characters.DisposeAllChildren(); + + var numberOfFullSlots = 0; + var characterButtonsGroup = new ButtonGroup(); + + if (!_preferencesManager.ServerDataLoaded) + { + return; + } + + _createNewCharacterButton.ToolTip = + Loc.GetString("character-setup-gui-create-new-character-button-tooltip", + ("maxCharacters", _preferencesManager.Settings!.MaxCharacterSlots)); + + var selectedSlot = _preferencesManager.Preferences?.SelectedCharacterIndex; + + foreach (var (slot, character) in _preferencesManager.Preferences!.Characters) + { + numberOfFullSlots++; + var characterPickerButton = new CharacterPickerButton(_entManager, + _protomanager, + characterButtonsGroup, + character, + slot == selectedSlot); + + Characters.AddChild(characterPickerButton); + + characterPickerButton.OnPressed += args => + { + SelectCharacter?.Invoke(slot); + }; + + characterPickerButton.OnDeletePressed += () => + { + DeleteCharacter?.Invoke(slot); + }; + } + + _createNewCharacterButton.Disabled = numberOfFullSlots >= _preferencesManager.Settings.MaxCharacterSlots; + Characters.AddChild(_createNewCharacterButton); + } + } +} diff --git a/Content.Client/Preferences/UI/HighlightedContainer.xaml b/Content.Client/Lobby/UI/HighlightedContainer.xaml similarity index 100% rename from Content.Client/Preferences/UI/HighlightedContainer.xaml rename to Content.Client/Lobby/UI/HighlightedContainer.xaml diff --git a/Content.Client/Preferences/UI/HighlightedContainer.xaml.cs b/Content.Client/Lobby/UI/HighlightedContainer.xaml.cs similarity index 88% rename from Content.Client/Preferences/UI/HighlightedContainer.xaml.cs rename to Content.Client/Lobby/UI/HighlightedContainer.xaml.cs index 68294d0f05..084c1c3709 100644 --- a/Content.Client/Preferences/UI/HighlightedContainer.xaml.cs +++ b/Content.Client/Lobby/UI/HighlightedContainer.xaml.cs @@ -2,7 +2,7 @@ using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.XAML; -namespace Content.Client.Preferences.UI; +namespace Content.Client.Lobby.UI; [GenerateTypedNameReferences] public sealed partial class HighlightedContainer : PanelContainer diff --git a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml new file mode 100644 index 0000000000..df364546ba --- /dev/null +++ b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml @@ -0,0 +1,220 @@ + + + + + + + + + + + + + +public sealed class ColorTintOverlay : Overlay +{ + [Dependency] private readonly IPrototypeManager _prototype = default!; + [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] IEntityManager _entityManager = default!; + + public override bool RequestScreenTexture => true; + public override OverlaySpace Space => OverlaySpace.WorldSpace; + private readonly ShaderInstance _shader; + + /// + /// The color to tint the screen to as RGB on a scale of 0-1. + /// + public Robust.Shared.Maths.Vector3? TintColor = null; + /// + /// The percent to tint the screen by on a scale of 0-1. + /// + public float? TintAmount = null; + + public ColorTintOverlay() + { + IoCManager.InjectDependencies(this); + _shader = _prototype.Index("ColorTint").InstanceUnique(); + } + + protected override bool BeforeDraw(in OverlayDrawArgs args) + { + if (_player.LocalEntity is not { Valid: true } player + || !_entityManager.HasComponent(player)) + return false; + + return base.BeforeDraw(in args); + } + protected override void Draw(in OverlayDrawArgs args) + { + if (ScreenTexture is null) + return; + + _shader.SetParameter("SCREEN_TEXTURE", ScreenTexture); + if (TintColor != null) + _shader.SetParameter("tint_color", (Robust.Shared.Maths.Vector3) TintColor); + if (TintAmount != null) + _shader.SetParameter("tint_amount", (float) TintAmount); + + var worldHandle = args.WorldHandle; + var viewport = args.WorldBounds; + worldHandle.SetTransform(Matrix3x2.Identity); + worldHandle.UseShader(_shader); + worldHandle.DrawRect(viewport, Color.White); + worldHandle.UseShader(null); + } +} diff --git a/Content.Client/Overlays/DogVisionOverlay.cs b/Content.Client/Overlays/DogVisionOverlay.cs new file mode 100644 index 0000000000..022a31d696 --- /dev/null +++ b/Content.Client/Overlays/DogVisionOverlay.cs @@ -0,0 +1,50 @@ +using System.Numerics; +using Robust.Client.Graphics; +using Robust.Client.Player; +using Robust.Shared.Enums; +using Robust.Shared.Prototypes; +using Content.Shared.Traits.Assorted.Components; + +namespace Content.Client.Overlays; + +public sealed partial class DogVisionOverlay : Overlay +{ + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] IEntityManager _entityManager = default!; + + + public override bool RequestScreenTexture => true; + public override OverlaySpace Space => OverlaySpace.WorldSpace; + private readonly ShaderInstance _dogVisionShader; + + public DogVisionOverlay() + { + IoCManager.InjectDependencies(this); + _dogVisionShader = _prototypeManager.Index("DogVision").Instance().Duplicate(); + } + + protected override bool BeforeDraw(in OverlayDrawArgs args) + { + if (_playerManager.LocalEntity is not { Valid: true } player + || !_entityManager.HasComponent(player)) + return false; + + return base.BeforeDraw(in args); + } + + protected override void Draw(in OverlayDrawArgs args) + { + if (ScreenTexture is null) + return; + + _dogVisionShader.SetParameter("SCREEN_TEXTURE", ScreenTexture); + + var worldHandle = args.WorldHandle; + var viewport = args.WorldBounds; + worldHandle.SetTransform(Matrix3x2.Identity); + worldHandle.UseShader(_dogVisionShader); + worldHandle.DrawRect(viewport, Color.White); + worldHandle.UseShader(null); + } +} diff --git a/Content.Client/Overlays/DogVisionSystem.cs b/Content.Client/Overlays/DogVisionSystem.cs new file mode 100644 index 0000000000..5c80c307cb --- /dev/null +++ b/Content.Client/Overlays/DogVisionSystem.cs @@ -0,0 +1,66 @@ +using Content.Shared.Traits.Assorted.Components; +using Content.Shared.CCVar; +using Robust.Client.Graphics; +using Robust.Shared.Configuration; +using Robust.Shared.Player; + +namespace Content.Client.Overlays; + +public sealed partial class DogVisionSystem : EntitySystem +{ + [Dependency] private readonly IOverlayManager _overlayMan = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly ISharedPlayerManager _playerMan = default!; + + private DogVisionOverlay _overlay = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnDogVisionInit); + SubscribeLocalEvent(OnDogVisionShutdown); + SubscribeLocalEvent(OnPlayerAttached); + SubscribeLocalEvent(OnPlayerDetached); + + Subs.CVar(_cfg, CCVars.NoVisionFilters, OnNoVisionFiltersChanged); + + _overlay = new(); + } + + private void OnDogVisionInit(EntityUid uid, DogVisionComponent component, ComponentInit args) + { + if (uid != _playerMan.LocalEntity) + return; + + if (!_cfg.GetCVar(CCVars.NoVisionFilters)) + _overlayMan.AddOverlay(_overlay); + } + + private void OnDogVisionShutdown(EntityUid uid, DogVisionComponent component, ComponentShutdown args) + { + if (uid != _playerMan.LocalEntity) + return; + + _overlayMan.RemoveOverlay(_overlay); + } + + private void OnPlayerAttached(EntityUid uid, DogVisionComponent component, LocalPlayerAttachedEvent args) + { + if (!_cfg.GetCVar(CCVars.NoVisionFilters)) + _overlayMan.AddOverlay(_overlay); + } + + private void OnPlayerDetached(EntityUid uid, DogVisionComponent component, LocalPlayerDetachedEvent args) + { + _overlayMan.RemoveOverlay(_overlay); + } + + private void OnNoVisionFiltersChanged(bool enabled) + { + if (enabled) + _overlayMan.RemoveOverlay(_overlay); + else + _overlayMan.AddOverlay(_overlay); + } +} diff --git a/Content.Client/Overlays/EntityHealthBarOverlay.cs b/Content.Client/Overlays/EntityHealthBarOverlay.cs index c1c0ae93ec..c96225c0c6 100644 --- a/Content.Client/Overlays/EntityHealthBarOverlay.cs +++ b/Content.Client/Overlays/EntityHealthBarOverlay.cs @@ -43,8 +43,8 @@ protected override void Draw(in OverlayDrawArgs args) var xformQuery = _entManager.GetEntityQuery(); const float scale = 1f; - var scaleMatrix = Matrix3.CreateScale(new Vector2(scale, scale)); - var rotationMatrix = Matrix3.CreateRotation(-rotation); + var scaleMatrix = Matrix3Helpers.CreateScale(new Vector2(scale, scale)); + var rotationMatrix = Matrix3Helpers.CreateRotation(-rotation); var query = _entManager.AllEntityQueryEnumerator(); while (query.MoveNext(out var uid, @@ -80,10 +80,10 @@ protected override void Draw(in OverlayDrawArgs args) } var worldPosition = _transform.GetWorldPosition(xform); - var worldMatrix = Matrix3.CreateTranslation(worldPosition); + var worldMatrix = Matrix3Helpers.CreateTranslation(worldPosition); - Matrix3.Multiply(scaleMatrix, worldMatrix, out var scaledWorld); - Matrix3.Multiply(rotationMatrix, scaledWorld, out var matty); + var scaledWorld = Matrix3x2.Multiply(scaleMatrix, worldMatrix); + var matty = Matrix3x2.Multiply(rotationMatrix, scaledWorld); handle.SetTransform(matty); @@ -116,7 +116,7 @@ protected override void Draw(in OverlayDrawArgs args) handle.DrawRect(pixelDarken, Black.WithAlpha(128)); } - handle.SetTransform(Matrix3.Identity); + handle.SetTransform(Matrix3x2.Identity); } /// diff --git a/Content.Client/Overlays/EtherealOverlay.cs b/Content.Client/Overlays/EtherealOverlay.cs new file mode 100644 index 0000000000..594a3656c8 --- /dev/null +++ b/Content.Client/Overlays/EtherealOverlay.cs @@ -0,0 +1,49 @@ +using System.Numerics; +using Robust.Client.Graphics; +using Robust.Client.Player; +using Robust.Shared.Enums; +using Robust.Shared.Prototypes; +using Content.Shared.Shadowkin; + +namespace Content.Client.Overlays; + +public sealed class EtherealOverlay : Overlay +{ + [Dependency] private readonly IPrototypeManager _prototype = default!; + [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] IEntityManager _entityManager = default!; + + public override bool RequestScreenTexture => true; + public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV; + private readonly ShaderInstance _shader; + + public EtherealOverlay() + { + IoCManager.InjectDependencies(this); + _shader = _prototype.Index("Ethereal").InstanceUnique(); + } + + protected override bool BeforeDraw(in OverlayDrawArgs args) + { + if (_player.LocalEntity is not { Valid: true } player + || !_entityManager.HasComponent(player)) + return false; + + return base.BeforeDraw(in args); + } + + protected override void Draw(in OverlayDrawArgs args) + { + if (ScreenTexture is null) + return; + + _shader.SetParameter("SCREEN_TEXTURE", ScreenTexture); + + var worldHandle = args.WorldHandle; + var viewport = args.WorldBounds; + worldHandle.SetTransform(Matrix3x2.Identity); + worldHandle.UseShader(_shader); + worldHandle.DrawRect(viewport, Color.White); + worldHandle.UseShader(null); + } +} diff --git a/Content.Client/Overlays/SaturationScaleOverlay.cs b/Content.Client/Overlays/SaturationScaleOverlay.cs new file mode 100644 index 0000000000..d3a27a9724 --- /dev/null +++ b/Content.Client/Overlays/SaturationScaleOverlay.cs @@ -0,0 +1,54 @@ +using System.Numerics; +using Robust.Client.Graphics; +using Robust.Client.Player; +using Robust.Shared.Enums; +using Robust.Shared.Prototypes; +using Content.Shared.Mood; +using Content.Shared.Overlays; + +namespace Content.Client.Overlays; + +public sealed class SaturationScaleOverlay : Overlay +{ + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] IEntityManager _entityManager = default!; + + public override bool RequestScreenTexture => true; + public override OverlaySpace Space => OverlaySpace.WorldSpace; + private readonly ShaderInstance _shader; + private const float Saturation = 0.5f; + + + public SaturationScaleOverlay() + { + IoCManager.InjectDependencies(this); + + _shader = _prototypeManager.Index("SaturationScale").Instance().Duplicate(); + } + + protected override bool BeforeDraw(in OverlayDrawArgs args) + { + if (_playerManager.LocalEntity is not { Valid: true } player + || !_entityManager.HasComponent(player)) + return false; + + return base.BeforeDraw(in args); + } + + + protected override void Draw(in OverlayDrawArgs args) + { + if (ScreenTexture is null) + return; + + _shader.SetParameter("SCREEN_TEXTURE", ScreenTexture); + _shader.SetParameter("saturation", Saturation); + + var handle = args.WorldHandle; + handle.SetTransform(Matrix3x2.Identity); + handle.UseShader(_shader); + handle.DrawRect(args.WorldBounds, Color.White); + handle.UseShader(null); + } +} diff --git a/Content.Client/Overlays/SaturationScaleSystem.cs b/Content.Client/Overlays/SaturationScaleSystem.cs new file mode 100644 index 0000000000..b5932e3a49 --- /dev/null +++ b/Content.Client/Overlays/SaturationScaleSystem.cs @@ -0,0 +1,63 @@ +using Content.Shared.GameTicking; +using Content.Shared.Mood; +using Content.Shared.Overlays; +using Robust.Client.Graphics; +using Robust.Shared.Player; + +namespace Content.Client.Overlays; + +public sealed class SaturationScaleSystem : EntitySystem +{ + [Dependency] private readonly IOverlayManager _overlayMan = default!; + [Dependency] private readonly ISharedPlayerManager _playerMan = default!; + + private SaturationScaleOverlay _overlay = default!; + + + public override void Initialize() + { + base.Initialize(); + + _overlay = new(); + + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnShutdown); + + SubscribeLocalEvent(OnPlayerAttached); + SubscribeLocalEvent(OnPlayerDetached); + + SubscribeNetworkEvent(RoundRestartCleanup); + } + + + private void RoundRestartCleanup(RoundRestartCleanupEvent ev) + { + _overlayMan.RemoveOverlay(_overlay); + } + + private void OnPlayerDetached(EntityUid uid, SaturationScaleOverlayComponent component, PlayerDetachedEvent args) + { + _overlayMan.RemoveOverlay(_overlay); + } + + private void OnPlayerAttached(EntityUid uid, SaturationScaleOverlayComponent component, PlayerAttachedEvent args) + { + _overlayMan.AddOverlay(_overlay); + } + + private void OnShutdown(EntityUid uid, SaturationScaleOverlayComponent component, ComponentShutdown args) + { + if (uid != _playerMan.LocalEntity) + return; + + _overlayMan.RemoveOverlay(_overlay); + } + + private void OnInit(EntityUid uid, SaturationScaleOverlayComponent component, ComponentInit args) + { + if (uid != _playerMan.LocalEntity) + return; + + _overlayMan.AddOverlay(_overlay); + } +} diff --git a/Content.Client/Overlays/ShowCriminalRecordIconsSystem.cs b/Content.Client/Overlays/ShowCriminalRecordIconsSystem.cs new file mode 100644 index 0000000000..8f23cd510c --- /dev/null +++ b/Content.Client/Overlays/ShowCriminalRecordIconsSystem.cs @@ -0,0 +1,28 @@ +using Content.Shared.Overlays; +using Content.Shared.Security.Components; +using Content.Shared.StatusIcon; +using Content.Shared.StatusIcon.Components; +using Robust.Shared.Prototypes; + +namespace Content.Client.Overlays; + +public sealed class ShowCriminalRecordIconsSystem : EquipmentHudSystem +{ + [Dependency] private readonly IPrototypeManager _prototype = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnGetStatusIconsEvent); + } + + private void OnGetStatusIconsEvent(EntityUid uid, CriminalRecordComponent component, ref GetStatusIconsEvent ev) + { + if (!IsActive || ev.InContainer) + return; + + if (_prototype.TryIndex(component.StatusIcon.Id, out var iconPrototype)) + ev.StatusIcons.Add(iconPrototype); + } +} diff --git a/Content.Client/Overlays/ShowHungerIconsSystem.cs b/Content.Client/Overlays/ShowHungerIconsSystem.cs index 58551b30c2..b1c0f3a1a0 100644 --- a/Content.Client/Overlays/ShowHungerIconsSystem.cs +++ b/Content.Client/Overlays/ShowHungerIconsSystem.cs @@ -1,14 +1,13 @@ +using Content.Shared.Nutrition.EntitySystems; using Content.Shared.Nutrition.Components; using Content.Shared.Overlays; -using Content.Shared.StatusIcon; using Content.Shared.StatusIcon.Components; -using Robust.Shared.Prototypes; namespace Content.Client.Overlays; public sealed class ShowHungerIconsSystem : EquipmentHudSystem { - [Dependency] private readonly IPrototypeManager _prototypeMan = default!; + [Dependency] private readonly HungerSystem _hunger = default!; public override void Initialize() { @@ -17,42 +16,12 @@ public override void Initialize() SubscribeLocalEvent(OnGetStatusIconsEvent); } - private void OnGetStatusIconsEvent(EntityUid uid, HungerComponent hungerComponent, ref GetStatusIconsEvent args) + private void OnGetStatusIconsEvent(EntityUid uid, HungerComponent component, ref GetStatusIconsEvent ev) { - if (!IsActive || args.InContainer) + if (!IsActive || ev.InContainer) return; - var hungerIcons = DecideHungerIcon(uid, hungerComponent); - - args.StatusIcons.AddRange(hungerIcons); - } - - private IReadOnlyList DecideHungerIcon(EntityUid uid, HungerComponent hungerComponent) - { - var result = new List(); - - switch (hungerComponent.CurrentThreshold) - { - case HungerThreshold.Overfed: - if (_prototypeMan.TryIndex("HungerIconOverfed", out var overfed)) - { - result.Add(overfed); - } - break; - case HungerThreshold.Peckish: - if (_prototypeMan.TryIndex("HungerIconPeckish", out var peckish)) - { - result.Add(peckish); - } - break; - case HungerThreshold.Starving: - if (_prototypeMan.TryIndex("HungerIconStarving", out var starving)) - { - result.Add(starving); - } - break; - } - - return result; + if (_hunger.TryGetStatusIconPrototype(component, out var iconPrototype)) + ev.StatusIcons.Add(iconPrototype); } } diff --git a/Content.Client/Overlays/ShowJobIconsSystem.cs b/Content.Client/Overlays/ShowJobIconsSystem.cs new file mode 100644 index 0000000000..e24b99f3e8 --- /dev/null +++ b/Content.Client/Overlays/ShowJobIconsSystem.cs @@ -0,0 +1,60 @@ +using Content.Shared.Access.Components; +using Content.Shared.Access.Systems; +using Content.Shared.Overlays; +using Content.Shared.PDA; +using Content.Shared.StatusIcon; +using Content.Shared.StatusIcon.Components; +using Robust.Shared.Prototypes; + +namespace Content.Client.Overlays; + +public sealed class ShowJobIconsSystem : EquipmentHudSystem +{ + [Dependency] private readonly IPrototypeManager _prototype = default!; + [Dependency] private readonly AccessReaderSystem _accessReader = default!; + + [ValidatePrototypeId] + private const string JobIconForNoId = "JobIconNoId"; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnGetStatusIconsEvent); + } + + private void OnGetStatusIconsEvent(EntityUid uid, StatusIconComponent _, ref GetStatusIconsEvent ev) + { + if (!IsActive || ev.InContainer) + return; + + var iconId = JobIconForNoId; + + if (_accessReader.FindAccessItemsInventory(uid, out var items)) + { + foreach (var item in items) + { + // ID Card + if (TryComp(item, out var id)) + { + iconId = id.JobIcon; + break; + } + + // PDA + if (TryComp(item, out var pda) + && pda.ContainedId != null + && TryComp(pda.ContainedId, out id)) + { + iconId = id.JobIcon; + break; + } + } + } + + if (_prototype.TryIndex(iconId, out var iconPrototype)) + ev.StatusIcons.Add(iconPrototype); + else + Log.Error($"Invalid job icon prototype: {iconPrototype}"); + } +} diff --git a/Content.Client/Overlays/ShowMindShieldIconsSystem.cs b/Content.Client/Overlays/ShowMindShieldIconsSystem.cs new file mode 100644 index 0000000000..8bf39b875f --- /dev/null +++ b/Content.Client/Overlays/ShowMindShieldIconsSystem.cs @@ -0,0 +1,28 @@ +using Content.Shared.Mindshield.Components; +using Content.Shared.Overlays; +using Content.Shared.StatusIcon; +using Content.Shared.StatusIcon.Components; +using Robust.Shared.Prototypes; + +namespace Content.Client.Overlays; + +public sealed class ShowMindShieldIconsSystem : EquipmentHudSystem +{ + [Dependency] private readonly IPrototypeManager _prototype = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnGetStatusIconsEvent); + } + + private void OnGetStatusIconsEvent(EntityUid uid, MindShieldComponent component, ref GetStatusIconsEvent ev) + { + if (!IsActive || ev.InContainer) + return; + + if (_prototype.TryIndex(component.MindShieldStatusIcon.Id, out var iconPrototype)) + ev.StatusIcons.Add(iconPrototype); + } +} diff --git a/Content.Client/Overlays/ShowSecurityIconsSystem.cs b/Content.Client/Overlays/ShowSecurityIconsSystem.cs deleted file mode 100644 index 7a4abd05e0..0000000000 --- a/Content.Client/Overlays/ShowSecurityIconsSystem.cs +++ /dev/null @@ -1,86 +0,0 @@ -using Content.Shared.Access.Components; -using Content.Shared.Access.Systems; -using Content.Shared.Mindshield.Components; -using Content.Shared.Overlays; -using Content.Shared.PDA; -using Content.Shared.Security.Components; -using Content.Shared.StatusIcon; -using Content.Shared.StatusIcon.Components; -using Robust.Shared.Prototypes; - -namespace Content.Client.Overlays; - -public sealed class ShowSecurityIconsSystem : EquipmentHudSystem -{ - [Dependency] private readonly IPrototypeManager _prototypeMan = default!; - [Dependency] private readonly AccessReaderSystem _accessReader = default!; - - [ValidatePrototypeId] - private const string JobIconForNoId = "JobIconNoId"; - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnGetStatusIconsEvent); - } - - private void OnGetStatusIconsEvent(EntityUid uid, StatusIconComponent _, ref GetStatusIconsEvent @event) - { - if (!IsActive || @event.InContainer) - { - return; - } - - var securityIcons = DecideSecurityIcon(uid); - - @event.StatusIcons.AddRange(securityIcons); - } - - private IReadOnlyList DecideSecurityIcon(EntityUid uid) - { - var result = new List(); - - var jobIconToGet = JobIconForNoId; - if (_accessReader.FindAccessItemsInventory(uid, out var items)) - { - foreach (var item in items) - { - // ID Card - if (TryComp(item, out IdCardComponent? id)) - { - jobIconToGet = id.JobIcon; - break; - } - - // PDA - if (TryComp(item, out PdaComponent? pda) - && pda.ContainedId != null - && TryComp(pda.ContainedId, out id)) - { - jobIconToGet = id.JobIcon; - break; - } - } - } - - if (_prototypeMan.TryIndex(jobIconToGet, out var jobIcon)) - result.Add(jobIcon); - else - Log.Error($"Invalid job icon prototype: {jobIcon}"); - - if (TryComp(uid, out var comp)) - { - if (_prototypeMan.TryIndex(comp.MindShieldStatusIcon.Id, out var icon)) - result.Add(icon); - } - - if (TryComp(uid, out var record)) - { - if(_prototypeMan.TryIndex(record.StatusIcon.Id, out var criminalIcon)) - result.Add(criminalIcon); - } - - return result; - } -} diff --git a/Content.Client/Overlays/ShowSyndicateIconsSystem.cs b/Content.Client/Overlays/ShowSyndicateIconsSystem.cs index a640726685..660ef198e1 100644 --- a/Content.Client/Overlays/ShowSyndicateIconsSystem.cs +++ b/Content.Client/Overlays/ShowSyndicateIconsSystem.cs @@ -1,10 +1,11 @@ using Content.Shared.Overlays; -using Content.Shared.StatusIcon.Components; using Content.Shared.NukeOps; using Content.Shared.StatusIcon; +using Content.Shared.StatusIcon.Components; using Robust.Shared.Prototypes; namespace Content.Client.Overlays; + public sealed class ShowSyndicateIconsSystem : EquipmentHudSystem { [Dependency] private readonly IPrototypeManager _prototype = default!; @@ -16,28 +17,13 @@ public override void Initialize() SubscribeLocalEvent(OnGetStatusIconsEvent); } - private void OnGetStatusIconsEvent(EntityUid uid, NukeOperativeComponent nukeOperativeComponent, ref GetStatusIconsEvent args) + private void OnGetStatusIconsEvent(EntityUid uid, NukeOperativeComponent component, ref GetStatusIconsEvent ev) { - if (!IsActive || args.InContainer) - { + if (!IsActive || ev.InContainer) return; - } - - var syndicateIcons = SyndicateIcon(uid, nukeOperativeComponent); - - args.StatusIcons.AddRange(syndicateIcons); - } - - private IReadOnlyList SyndicateIcon(EntityUid uid, NukeOperativeComponent nukeOperativeComponent) - { - var result = new List(); - - if (_prototype.TryIndex(nukeOperativeComponent.SyndStatusIcon, out var syndicateicon)) - { - result.Add(syndicateicon); - } - return result; + if (_prototype.TryIndex(component.SyndStatusIcon, out var iconPrototype)) + ev.StatusIcons.Add(iconPrototype); } } diff --git a/Content.Client/Overlays/ShowThirstIconsSystem.cs b/Content.Client/Overlays/ShowThirstIconsSystem.cs index f9d6d0ab25..b08aa4340b 100644 --- a/Content.Client/Overlays/ShowThirstIconsSystem.cs +++ b/Content.Client/Overlays/ShowThirstIconsSystem.cs @@ -1,14 +1,13 @@ +using Content.Shared.Nutrition.EntitySystems; using Content.Shared.Nutrition.Components; using Content.Shared.Overlays; -using Content.Shared.StatusIcon; using Content.Shared.StatusIcon.Components; -using Robust.Shared.Prototypes; namespace Content.Client.Overlays; public sealed class ShowThirstIconsSystem : EquipmentHudSystem { - [Dependency] private readonly IPrototypeManager _prototypeMan = default!; + [Dependency] private readonly ThirstSystem _thirst = default!; public override void Initialize() { @@ -17,42 +16,12 @@ public override void Initialize() SubscribeLocalEvent(OnGetStatusIconsEvent); } - private void OnGetStatusIconsEvent(EntityUid uid, ThirstComponent thirstComponent, ref GetStatusIconsEvent args) + private void OnGetStatusIconsEvent(EntityUid uid, ThirstComponent component, ref GetStatusIconsEvent ev) { - if (!IsActive || args.InContainer) + if (!IsActive || ev.InContainer) return; - var thirstIcons = DecideThirstIcon(uid, thirstComponent); - - args.StatusIcons.AddRange(thirstIcons); - } - - private IReadOnlyList DecideThirstIcon(EntityUid uid, ThirstComponent thirstComponent) - { - var result = new List(); - - switch (thirstComponent.CurrentThirstThreshold) - { - case ThirstThreshold.OverHydrated: - if (_prototypeMan.TryIndex("ThirstIconOverhydrated", out var overhydrated)) - { - result.Add(overhydrated); - } - break; - case ThirstThreshold.Thirsty: - if (_prototypeMan.TryIndex("ThirstIconThirsty", out var thirsty)) - { - result.Add(thirsty); - } - break; - case ThirstThreshold.Parched: - if (_prototypeMan.TryIndex("ThirstIconParched", out var parched)) - { - result.Add(parched); - } - break; - } - - return result; + if (_thirst.TryGetStatusIconPrototype(component, out var iconPrototype)) + ev.StatusIcons.Add(iconPrototype!); } } diff --git a/Content.Client/Overlays/StencilOverlay.RestrictedRange.cs b/Content.Client/Overlays/StencilOverlay.RestrictedRange.cs index 9581fec37b..d29564caa9 100644 --- a/Content.Client/Overlays/StencilOverlay.RestrictedRange.cs +++ b/Content.Client/Overlays/StencilOverlay.RestrictedRange.cs @@ -7,7 +7,7 @@ namespace Content.Client.Overlays; public sealed partial class StencilOverlay { - private void DrawRestrictedRange(in OverlayDrawArgs args, RestrictedRangeComponent rangeComp, Matrix3 invMatrix) + private void DrawRestrictedRange(in OverlayDrawArgs args, RestrictedRangeComponent rangeComp, Matrix3x2 invMatrix) { var worldHandle = args.WorldHandle; var renderScale = args.Viewport.RenderScale.X; @@ -16,7 +16,7 @@ private void DrawRestrictedRange(in OverlayDrawArgs args, RestrictedRangeCompone var length = zoom.X; var bufferRange = MathF.Min(10f, rangeComp.Range); - var pixelCenter = invMatrix.Transform(rangeComp.Origin); + var pixelCenter = Vector2.Transform(rangeComp.Origin, invMatrix); // Something something offset? var vertical = args.Viewport.Size.Y; @@ -44,7 +44,7 @@ private void DrawRestrictedRange(in OverlayDrawArgs args, RestrictedRangeCompone worldHandle.DrawRect(localAABB, Color.White); }, Color.Transparent); - worldHandle.SetTransform(Matrix3.Identity); + worldHandle.SetTransform(Matrix3x2.Identity); worldHandle.UseShader(_protoManager.Index("StencilMask").Instance()); worldHandle.DrawTextureRect(_blep!.Texture, worldBounds); var curTime = _timing.RealTime; diff --git a/Content.Client/Overlays/StencilOverlay.Weather.cs b/Content.Client/Overlays/StencilOverlay.Weather.cs index 31bc88af45..29ed157a79 100644 --- a/Content.Client/Overlays/StencilOverlay.Weather.cs +++ b/Content.Client/Overlays/StencilOverlay.Weather.cs @@ -10,7 +10,7 @@ public sealed partial class StencilOverlay { private List> _grids = new(); - private void DrawWeather(in OverlayDrawArgs args, WeatherPrototype weatherProto, float alpha, Matrix3 invMatrix) + private void DrawWeather(in OverlayDrawArgs args, WeatherPrototype weatherProto, float alpha, Matrix3x2 invMatrix) { var worldHandle = args.WorldHandle; var mapId = args.MapId; @@ -32,7 +32,7 @@ private void DrawWeather(in OverlayDrawArgs args, WeatherPrototype weatherProto, foreach (var grid in _grids) { var matrix = _transform.GetWorldMatrix(grid, xformQuery); - Matrix3.Multiply(in matrix, in invMatrix, out var matty); + var matty = Matrix3x2.Multiply(matrix, invMatrix); worldHandle.SetTransform(matty); foreach (var tile in grid.Comp.GetTilesIntersecting(worldAABB)) @@ -52,7 +52,7 @@ private void DrawWeather(in OverlayDrawArgs args, WeatherPrototype weatherProto, }, Color.Transparent); - worldHandle.SetTransform(Matrix3.Identity); + worldHandle.SetTransform(Matrix3x2.Identity); worldHandle.UseShader(_protoManager.Index("StencilMask").Instance()); worldHandle.DrawTextureRect(_blep!.Texture, worldBounds); var curTime = _timing.RealTime; @@ -62,7 +62,7 @@ private void DrawWeather(in OverlayDrawArgs args, WeatherPrototype weatherProto, worldHandle.UseShader(_protoManager.Index("StencilDraw").Instance()); _parallax.DrawParallax(worldHandle, worldAABB, sprite, curTime, position, Vector2.Zero, modulate: (weatherProto.Color ?? Color.White).WithAlpha(alpha)); - worldHandle.SetTransform(Matrix3.Identity); + worldHandle.SetTransform(Matrix3x2.Identity); worldHandle.UseShader(null); } } diff --git a/Content.Client/Overlays/StencilOverlay.cs b/Content.Client/Overlays/StencilOverlay.cs index e475dca759..78b1c4d2b1 100644 --- a/Content.Client/Overlays/StencilOverlay.cs +++ b/Content.Client/Overlays/StencilOverlay.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Content.Client.Parallax; using Content.Client.Weather; using Content.Shared.Salvage; @@ -72,6 +73,6 @@ protected override void Draw(in OverlayDrawArgs args) } args.WorldHandle.UseShader(null); - args.WorldHandle.SetTransform(Matrix3.Identity); + args.WorldHandle.SetTransform(Matrix3x2.Identity); } } diff --git a/Content.Client/Overlays/UltraVisionOverlay.cs b/Content.Client/Overlays/UltraVisionOverlay.cs new file mode 100644 index 0000000000..a12aa94ea8 --- /dev/null +++ b/Content.Client/Overlays/UltraVisionOverlay.cs @@ -0,0 +1,50 @@ +using System.Numerics; +using Robust.Client.Graphics; +using Robust.Client.Player; +using Robust.Shared.Enums; +using Robust.Shared.Prototypes; +using Content.Shared.Traits.Assorted.Components; + +namespace Content.Client.Overlays; + +public sealed partial class UltraVisionOverlay : Overlay +{ + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] IEntityManager _entityManager = default!; + + + public override bool RequestScreenTexture => true; + public override OverlaySpace Space => OverlaySpace.WorldSpace; + private readonly ShaderInstance _ultraVisionShader; + + public UltraVisionOverlay() + { + IoCManager.InjectDependencies(this); + _ultraVisionShader = _prototypeManager.Index("UltraVision").Instance().Duplicate(); + } + + protected override bool BeforeDraw(in OverlayDrawArgs args) + { + if (_playerManager.LocalEntity is not { Valid: true } player + || !_entityManager.HasComponent(player)) + return false; + + return base.BeforeDraw(in args); + } + + protected override void Draw(in OverlayDrawArgs args) + { + if (ScreenTexture is null) + return; + + _ultraVisionShader.SetParameter("SCREEN_TEXTURE", ScreenTexture); + + var worldHandle = args.WorldHandle; + var viewport = args.WorldBounds; + worldHandle.SetTransform(Matrix3x2.Identity); + worldHandle.UseShader(_ultraVisionShader); + worldHandle.DrawRect(viewport, Color.White); + worldHandle.UseShader(null); + } +} diff --git a/Content.Client/Overlays/UltraVisionSystem.cs b/Content.Client/Overlays/UltraVisionSystem.cs new file mode 100644 index 0000000000..e8e6cdfa72 --- /dev/null +++ b/Content.Client/Overlays/UltraVisionSystem.cs @@ -0,0 +1,66 @@ +using Content.Shared.Traits.Assorted.Components; +using Content.Shared.CCVar; +using Robust.Client.Graphics; +using Robust.Shared.Configuration; +using Robust.Shared.Player; + +namespace Content.Client.Overlays; + +public sealed partial class UltraVisionSystem : EntitySystem +{ + [Dependency] private readonly IOverlayManager _overlayMan = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly ISharedPlayerManager _playerMan = default!; + + private UltraVisionOverlay _overlay = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnUltraVisionInit); + SubscribeLocalEvent(OnUltraVisionShutdown); + SubscribeLocalEvent(OnPlayerAttached); + SubscribeLocalEvent(OnPlayerDetached); + + Subs.CVar(_cfg, CCVars.NoVisionFilters, OnNoVisionFiltersChanged); + + _overlay = new(); + } + + private void OnUltraVisionInit(EntityUid uid, UltraVisionComponent component, ComponentInit args) + { + if (uid != _playerMan.LocalEntity) + return; + + if (!_cfg.GetCVar(CCVars.NoVisionFilters)) + _overlayMan.AddOverlay(_overlay); + } + + private void OnUltraVisionShutdown(EntityUid uid, UltraVisionComponent component, ComponentShutdown args) + { + if (uid != _playerMan.LocalEntity) + return; + + _overlayMan.RemoveOverlay(_overlay); + } + + private void OnPlayerAttached(EntityUid uid, UltraVisionComponent component, LocalPlayerAttachedEvent args) + { + if (!_cfg.GetCVar(CCVars.NoVisionFilters)) + _overlayMan.AddOverlay(_overlay); + } + + private void OnPlayerDetached(EntityUid uid, UltraVisionComponent component, LocalPlayerDetachedEvent args) + { + _overlayMan.RemoveOverlay(_overlay); + } + + private void OnNoVisionFiltersChanged(bool enabled) + { + if (enabled) + _overlayMan.RemoveOverlay(_overlay); + else + _overlayMan.AddOverlay(_overlay); + } +} diff --git a/Content.Client/PDA/PdaBoundUserInterface.cs b/Content.Client/PDA/PdaBoundUserInterface.cs index ef9d6e8b9b..07352b512b 100644 --- a/Content.Client/PDA/PdaBoundUserInterface.cs +++ b/Content.Client/PDA/PdaBoundUserInterface.cs @@ -21,7 +21,6 @@ public PdaBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) protected override void Open() { base.Open(); - SendMessage(new PdaRequestUpdateInterfaceMessage()); _menu = new PdaMenu(); _menu.OpenCenteredLeft(); _menu.OnClose += Close; @@ -32,17 +31,17 @@ protected override void Open() _menu.EjectIdButton.OnPressed += _ => { - SendMessage(new ItemSlotButtonPressedEvent(PdaComponent.PdaIdSlotId)); + SendPredictedMessage(new ItemSlotButtonPressedEvent(PdaComponent.PdaIdSlotId)); }; _menu.EjectPenButton.OnPressed += _ => { - SendMessage(new ItemSlotButtonPressedEvent(PdaComponent.PdaPenSlotId)); + SendPredictedMessage(new ItemSlotButtonPressedEvent(PdaComponent.PdaPenSlotId)); }; _menu.EjectPaiButton.OnPressed += _ => { - SendMessage(new ItemSlotButtonPressedEvent(PdaComponent.PdaPaiSlotId)); + SendPredictedMessage(new ItemSlotButtonPressedEvent(PdaComponent.PdaPaiSlotId)); }; _menu.ActivateMusicButton.OnPressed += _ => diff --git a/Content.Client/Paint/PaintVisualizerSystem.cs b/Content.Client/Paint/PaintVisualizerSystem.cs new file mode 100644 index 0000000000..a00314cd68 --- /dev/null +++ b/Content.Client/Paint/PaintVisualizerSystem.cs @@ -0,0 +1,101 @@ +using Robust.Client.GameObjects; +using static Robust.Client.GameObjects.SpriteComponent; +using Content.Shared.Clothing; +using Content.Shared.Hands; +using Content.Shared.Paint; +using Robust.Client.Graphics; +using Robust.Shared.Prototypes; + +namespace Content.Client.Paint; + +public sealed class PaintedVisualizerSystem : VisualizerSystem +{ + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly IPrototypeManager _protoMan = default!; + + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnHeldVisualsUpdated); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnEquipmentVisualsUpdated); + } + + + protected override void OnAppearanceChange(EntityUid uid, PaintedComponent component, ref AppearanceChangeEvent args) + { + if (args.Sprite == null + || !_appearance.TryGetData(uid, PaintVisuals.Painted, out bool isPainted)) + return; + + var shader = _protoMan.Index(component.ShaderName).Instance(); + foreach (var spriteLayer in args.Sprite.AllLayers) + { + if (spriteLayer is not Layer layer) + continue; + + if (layer.Shader == null || layer.Shader == shader) + { + layer.Shader = shader; + layer.Color = component.Color; + } + } + } + + private void OnShutdown(EntityUid uid, PaintedComponent component, ref ComponentShutdown args) + { + if (!TryComp(uid, out SpriteComponent? sprite)) + return; + component.BeforeColor = sprite.Color; + + if (Terminating(uid)) + return; + + foreach (var spriteLayer in sprite.AllLayers) + { + if (spriteLayer is not Layer layer + || layer.Shader != _protoMan.Index(component.ShaderName).Instance()) + continue; + + layer.Shader = null; + if (layer.Color == component.Color) + layer.Color = component.BeforeColor; + } + } + + private void OnHeldVisualsUpdated(EntityUid uid, PaintedComponent component, HeldVisualsUpdatedEvent args) => + UpdateVisuals(component, args); + private void OnEquipmentVisualsUpdated(EntityUid uid, PaintedComponent component, EquipmentVisualsUpdatedEvent args) => + UpdateVisuals(component, args); + private void UpdateVisuals(PaintedComponent component, EntityEventArgs args) + { + var layers = new HashSet(); + var entity = EntityUid.Invalid; + + switch (args) + { + case HeldVisualsUpdatedEvent hgs: + layers = hgs.RevealedLayers; + entity = hgs.User; + break; + case EquipmentVisualsUpdatedEvent eqs: + layers = eqs.RevealedLayers; + entity = eqs.Equipee; + break; + } + + if (layers.Count == 0 || !TryComp(entity, out SpriteComponent? sprite)) + return; + + foreach (var revealed in layers) + { + if (!sprite.LayerMapTryGet(revealed, out var layer)) + continue; + + sprite.LayerSetShader(layer, component.ShaderName); + sprite.LayerSetColor(layer, component.Color); + } + } +} diff --git a/Content.Client/Paper/PaperComponent.cs b/Content.Client/Paper/PaperComponent.cs index d197cd3721..1dc827bf7e 100644 --- a/Content.Client/Paper/PaperComponent.cs +++ b/Content.Client/Paper/PaperComponent.cs @@ -1,9 +1,6 @@ using Content.Shared.Paper; -using Robust.Shared.GameStates; namespace Content.Client.Paper; -[NetworkedComponent, RegisterComponent] -public sealed partial class PaperComponent : SharedPaperComponent -{ -} +[RegisterComponent] +public sealed partial class PaperComponent : SharedPaperComponent; diff --git a/Content.Client/Paper/UI/StampLabel.xaml.cs b/Content.Client/Paper/UI/StampLabel.xaml.cs index 6a8eb5f98f..be6d52baea 100644 --- a/Content.Client/Paper/UI/StampLabel.xaml.cs +++ b/Content.Client/Paper/UI/StampLabel.xaml.cs @@ -50,7 +50,7 @@ protected override void Draw(DrawingHandleScreen handle) base.Draw(handle); // Restore a sane transform+shader - handle.SetTransform(Matrix3.Identity); + handle.SetTransform(Matrix3x2.Identity); handle.UseShader(null); } } diff --git a/Content.Client/Paper/UI/StampWidget.xaml.cs b/Content.Client/Paper/UI/StampWidget.xaml.cs index a04508aeba..487e0732b4 100644 --- a/Content.Client/Paper/UI/StampWidget.xaml.cs +++ b/Content.Client/Paper/UI/StampWidget.xaml.cs @@ -53,7 +53,7 @@ protected override void Draw(DrawingHandleScreen handle) base.Draw(handle); // Restore a sane transform+shader - handle.SetTransform(Matrix3.Identity); + handle.SetTransform(Matrix3x2.Identity); handle.UseShader(null); } } diff --git a/Content.Client/Parkstation/Overlays/Shaders/NearsightedOverlays.cs b/Content.Client/Parkstation/Overlays/Shaders/NearsightedOverlays.cs deleted file mode 100644 index 8235d86792..0000000000 --- a/Content.Client/Parkstation/Overlays/Shaders/NearsightedOverlays.cs +++ /dev/null @@ -1,130 +0,0 @@ -using Content.Shared.Parkstation.Traits.Components; -using Robust.Client.GameObjects; -using Robust.Client.Graphics; -using Robust.Client.Player; -using Robust.Shared.Enums; -using Robust.Shared.Prototypes; -using Robust.Shared.Timing; - -namespace Content.Client.Parkstation.Overlays.Shaders; - -public sealed class NearsightedOverlay : Overlay -{ - [Dependency] private readonly IGameTiming _timing = default!; - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly IEntityManager _entityManager = default!; - [Dependency] private readonly IPlayerManager _playerManager = default!; - - public override OverlaySpace Space => OverlaySpace.WorldSpace; - private readonly ShaderInstance _nearsightShader; - - public float Radius; - private float _oldRadius; - public float Darkness; - private float _oldDarkness; - - private float _lerpTime; - public float LerpDuration; - - - public NearsightedOverlay() - { - IoCManager.InjectDependencies(this); - _nearsightShader = _prototypeManager.Index("GradientCircleMask").InstanceUnique(); - } - - protected override bool BeforeDraw(in OverlayDrawArgs args) - { - // Check if the player has a NearsightedComponent and is controlling it - if (!_entityManager.TryGetComponent(_playerManager.LocalPlayer?.ControlledEntity, out NearsightedComponent? nearComp) || - _playerManager.LocalPlayer?.ControlledEntity != nearComp.Owner) - return false; - - // Check if the player has an EyeComponent and if the overlay should be drawn for this eye - if (!_entityManager.TryGetComponent(_playerManager.LocalPlayer?.ControlledEntity, out EyeComponent? eyeComp) || - args.Viewport.Eye != eyeComp.Eye) - return false; - - return true; - } - - protected override void Draw(in OverlayDrawArgs args) - { - // We already checked if they have a NearsightedComponent and are controlling it in BeforeDraw, so we assume this hasn't changed - var nearComp = _entityManager.GetComponent(_playerManager.LocalPlayer!.ControlledEntity!.Value); - - // Set LerpDuration based on nearComp.LerpDuration - LerpDuration = nearComp.LerpDuration; - - // Set the radius and darkness values based on whether the player is wearing glasses or not - if (nearComp.Active) - { - Radius = nearComp.EquippedRadius; - Darkness = nearComp.EquippedAlpha; - } - else - { - Radius = nearComp.Radius; - Darkness = nearComp.Alpha; - } - - - var viewport = args.WorldAABB; - var handle = args.WorldHandle; - var distance = args.ViewportBounds.Width; - - var lastFrameTime = (float) _timing.FrameTime.TotalSeconds; - - - // If the current radius value is different from the previous one, lerp between them - if (!MathHelper.CloseTo(_oldRadius, Radius, 0.001f)) - { - _lerpTime += lastFrameTime; - var t = MathHelper.Clamp(_lerpTime / LerpDuration, 0f, 1f); // Calculate lerp time - _oldRadius = MathHelper.Lerp(_oldRadius, Radius, t); // Lerp between old and new radius values - } - // If the current radius value is the same as the previous one, reset the lerp time and old radius value - else - { - _lerpTime = 0f; - _oldRadius = Radius; - } - - // If the current darkness value is different from the previous one, lerp between them - if (!MathHelper.CloseTo(_oldDarkness, Darkness, 0.001f)) - { - _lerpTime += lastFrameTime; - var t = MathHelper.Clamp(_lerpTime / LerpDuration, 0f, 1f); // Calculate lerp time - _oldDarkness = MathHelper.Lerp(_oldDarkness, Darkness, t); // Lerp between old and new darkness values - } - // If the current darkness value is the same as the previous one, reset the lerp time and old darkness value - else - { - _lerpTime = 0f; - _oldDarkness = Darkness; - } - - - // Calculate the outer and inner radii based on the current radius value - var outerMaxLevel = 0.6f * distance; - var outerMinLevel = 0.06f * distance; - var innerMaxLevel = 0.02f * distance; - var innerMinLevel = 0.02f * distance; - - var outerRadius = outerMaxLevel - _oldRadius * (outerMaxLevel - outerMinLevel); - var innerRadius = innerMaxLevel - _oldRadius * (innerMaxLevel - innerMinLevel); - - // Set the shader parameters and draw the overlay - _nearsightShader.SetParameter("time", 0.0f); - _nearsightShader.SetParameter("color", new Vector3(1f, 1f, 1f)); - _nearsightShader.SetParameter("darknessAlphaOuter", _oldDarkness); - _nearsightShader.SetParameter("innerCircleRadius", innerRadius); - _nearsightShader.SetParameter("innerCircleMaxRadius", innerRadius); - _nearsightShader.SetParameter("outerCircleRadius", outerRadius); - _nearsightShader.SetParameter("outerCircleMaxRadius", outerRadius + 0.2f * distance); - handle.UseShader(_nearsightShader); - handle.DrawRect(viewport, Color.Black); - - handle.UseShader(null); - } -} diff --git a/Content.Client/Parkstation/Overlays/Systems/NearsightedSystems.cs b/Content.Client/Parkstation/Overlays/Systems/NearsightedSystems.cs deleted file mode 100644 index c42d7866c4..0000000000 --- a/Content.Client/Parkstation/Overlays/Systems/NearsightedSystems.cs +++ /dev/null @@ -1,64 +0,0 @@ -using Content.Client.Parkstation.Overlays.Shaders; -using Content.Shared.Inventory.Events; -using Content.Shared.Parkstation.Traits; -using Content.Shared.Parkstation.Traits.Components; -using Content.Shared.Tag; -using Robust.Client.Graphics; - -namespace Content.Client.Parkstation.Overlays.Systems; - -public sealed class NearsightedSystem : EntitySystem -{ - [Dependency] private readonly IOverlayManager _overlayMan = default!; - - private NearsightedOverlay _overlay = default!; - - public override void Initialize() - { - base.Initialize(); - - _overlay = new NearsightedOverlay(); - - SubscribeLocalEvent(OnStartup); - SubscribeLocalEvent(OnEquip); - SubscribeLocalEvent(OnUnEquip); - } - - - private void OnStartup(EntityUid uid, NearsightedComponent component, ComponentStartup args) - { - UpdateShader(component, false); - } - - private void OnEquip(GotEquippedEvent args) - { - // Note: it would be cleaner to check if the glasses are being equipped - // to the eyes rather than the pockets using `args.SlotFlags.HasFlag(SlotFlags.EYES)`, - // but this field is not present on GotUnequippedEvent. This method is - // used for both equip and unequip to make it consistent between checks. - if (TryComp(args.Equipee, out var nearsighted) && - EnsureComp(args.Equipment).Tags.Contains("GlassesNearsight") && - args.Slot == "eyes") - UpdateShader(nearsighted, true); - } - - private void OnUnEquip(GotUnequippedEvent args) - { - if (TryComp(args.Equipee, out var nearsighted) && - EnsureComp(args.Equipment).Tags.Contains("GlassesNearsight") && - args.Slot == "eyes") - UpdateShader(nearsighted, false); - } - - - private void UpdateShader(NearsightedComponent component, bool booLean) - { - while (_overlayMan.HasOverlay()) - { - _overlayMan.RemoveOverlay(_overlay); - } - - component.Active = booLean; - _overlayMan.AddOverlay(_overlay); - } -} diff --git a/Content.Client/Physics/Controllers/MoverController.cs b/Content.Client/Physics/Controllers/MoverController.cs index 31042854d4..9d453e5518 100644 --- a/Content.Client/Physics/Controllers/MoverController.cs +++ b/Content.Client/Physics/Controllers/MoverController.cs @@ -1,9 +1,12 @@ +using Content.Shared.CCVar; using Content.Shared.Movement.Components; +using Content.Shared.Movement.Events; using Content.Shared.Movement.Pulling.Components; using Content.Shared.Movement.Systems; using Robust.Client.GameObjects; using Robust.Client.Physics; using Robust.Client.Player; +using Robust.Shared.Configuration; using Robust.Shared.Physics.Components; using Robust.Shared.Player; using Robust.Shared.Timing; @@ -12,6 +15,7 @@ namespace Content.Client.Physics.Controllers { public sealed class MoverController : SharedMoverController { + [Dependency] private readonly IConfigurationManager _config = default!; [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; @@ -26,6 +30,8 @@ public override void Initialize() SubscribeLocalEvent(OnUpdatePredicted); SubscribeLocalEvent(OnUpdateRelayTargetPredicted); SubscribeLocalEvent(OnUpdatePullablePredicted); + + Subs.CVar(_config, CCVars.DefaultWalk, _ => RaiseNetworkEvent(new UpdateInputCVarsMessage())); } private void OnUpdatePredicted(EntityUid uid, InputMoverComponent component, ref UpdateIsPredictedEvent args) diff --git a/Content.Client/Pinpointer/NavMapSystem.cs b/Content.Client/Pinpointer/NavMapSystem.cs index bd7dfc1117..9aeb792a42 100644 --- a/Content.Client/Pinpointer/NavMapSystem.cs +++ b/Content.Client/Pinpointer/NavMapSystem.cs @@ -1,109 +1,63 @@ -using System.Numerics; using Content.Shared.Pinpointer; -using Robust.Client.Graphics; -using Robust.Shared.Enums; using Robust.Shared.GameStates; -using Robust.Shared.Map; -using Robust.Shared.Map.Components; namespace Content.Client.Pinpointer; -public sealed class NavMapSystem : SharedNavMapSystem +public sealed partial class NavMapSystem : SharedNavMapSystem { public override void Initialize() { base.Initialize(); + SubscribeLocalEvent(OnHandleState); } private void OnHandleState(EntityUid uid, NavMapComponent component, ref ComponentHandleState args) { - if (args.Current is not NavMapComponentState state) - return; - - component.Chunks.Clear(); + Dictionary modifiedChunks; + Dictionary beacons; - foreach (var (origin, data) in state.TileData) + switch (args.Current) { - component.Chunks.Add(origin, new NavMapChunk(origin) + case NavMapDeltaState delta: { - TileData = data, - }); - } - - component.Beacons.Clear(); - component.Beacons.AddRange(state.Beacons); - - component.Airlocks.Clear(); - component.Airlocks.AddRange(state.Airlocks); - } -} - -public sealed class NavMapOverlay : Overlay -{ - private readonly IEntityManager _entManager; - private readonly IMapManager _mapManager; - - public override OverlaySpace Space => OverlaySpace.WorldSpace; - - private List> _grids = new(); - - public NavMapOverlay(IEntityManager entManager, IMapManager mapManager) - { - _entManager = entManager; - _mapManager = mapManager; - } - - protected override void Draw(in OverlayDrawArgs args) - { - var query = _entManager.GetEntityQuery(); - var xformQuery = _entManager.GetEntityQuery(); - var scale = Matrix3.CreateScale(new Vector2(1f, 1f)); - - _grids.Clear(); - _mapManager.FindGridsIntersecting(args.MapId, args.WorldBounds, ref _grids); - - foreach (var grid in _grids) - { - if (!query.TryGetComponent(grid, out var navMap) || !xformQuery.TryGetComponent(grid.Owner, out var xform)) - continue; - - // TODO: Faster helper method - var (_, _, matrix, invMatrix) = xform.GetWorldPositionRotationMatrixWithInv(); - - var localAABB = invMatrix.TransformBox(args.WorldBounds); - Matrix3.Multiply(in scale, in matrix, out var matty); - - args.WorldHandle.SetTransform(matty); + modifiedChunks = delta.ModifiedChunks; + beacons = delta.Beacons; + foreach (var index in component.Chunks.Keys) + { + if (!delta.AllChunks!.Contains(index)) + component.Chunks.Remove(index); + } - for (var x = Math.Floor(localAABB.Left); x <= Math.Ceiling(localAABB.Right); x += SharedNavMapSystem.ChunkSize * grid.Comp.TileSize) + break; + } + case NavMapState state: { - for (var y = Math.Floor(localAABB.Bottom); y <= Math.Ceiling(localAABB.Top); y += SharedNavMapSystem.ChunkSize * grid.Comp.TileSize) + modifiedChunks = state.Chunks; + beacons = state.Beacons; + foreach (var index in component.Chunks.Keys) { - var floored = new Vector2i((int) x, (int) y); - - var chunkOrigin = SharedMapSystem.GetChunkIndices(floored, SharedNavMapSystem.ChunkSize); - - if (!navMap.Chunks.TryGetValue(chunkOrigin, out var chunk)) - continue; - - // TODO: Okay maybe I should just use ushorts lmao... - for (var i = 0; i < SharedNavMapSystem.ChunkSize * SharedNavMapSystem.ChunkSize; i++) - { - var value = (int) Math.Pow(2, i); - - var mask = chunk.TileData & value; - - if (mask == 0x0) - continue; - - var tile = chunk.Origin * SharedNavMapSystem.ChunkSize + SharedNavMapSystem.GetTile(mask); - args.WorldHandle.DrawRect(new Box2(tile * grid.Comp.TileSize, (tile + 1) * grid.Comp.TileSize), Color.Aqua, false); - } + if (!state.Chunks.ContainsKey(index)) + component.Chunks.Remove(index); } + + break; } + default: + return; } - args.WorldHandle.SetTransform(Matrix3.Identity); + foreach (var (origin, chunk) in modifiedChunks) + { + var newChunk = new NavMapChunk(origin); + Array.Copy(chunk, newChunk.TileData, chunk.Length); + component.Chunks[origin] = newChunk; + } + + component.Beacons.Clear(); + foreach (var (nuid, beacon) in beacons) + { + component.Beacons[nuid] = beacon; + } } } diff --git a/Content.Client/Pinpointer/UI/NavMapControl.cs b/Content.Client/Pinpointer/UI/NavMapControl.cs index 677092e191..3c99a18818 100644 --- a/Content.Client/Pinpointer/UI/NavMapControl.cs +++ b/Content.Client/Pinpointer/UI/NavMapControl.cs @@ -16,6 +16,9 @@ using Robust.Shared.Timing; using System.Numerics; using JetBrains.Annotations; +using Content.Shared.Atmos; +using System.Linq; +using Robust.Shared.Utility; namespace Content.Client.Pinpointer.UI; @@ -27,6 +30,7 @@ public partial class NavMapControl : MapGridControl { [Dependency] private IResourceCache _cache = default!; private readonly SharedTransformSystem _transformSystem; + private readonly SharedNavMapSystem _navMapSystem; public EntityUid? Owner; public EntityUid? MapUid; @@ -40,7 +44,10 @@ public partial class NavMapControl : MapGridControl // Tracked data public Dictionary TrackedCoordinates = new(); public Dictionary TrackedEntities = new(); - public Dictionary>? TileGrid = default!; + + public List<(Vector2, Vector2)> TileLines = new(); + public List<(Vector2, Vector2)> TileRects = new(); + public List<(Vector2[], Color)> TilePolygons = new(); // Default colors public Color WallColor = new(102, 217, 102); @@ -53,14 +60,23 @@ public partial class NavMapControl : MapGridControl protected static float MinDisplayedRange = 8f; protected static float MaxDisplayedRange = 128f; protected static float DefaultDisplayedRange = 48f; + protected float MinmapScaleModifier = 0.075f; + protected float FullWallInstep = 0.165f; + protected float ThinWallThickness = 0.165f; + protected float ThinDoorThickness = 0.30f; // Local variables - private float _updateTimer = 0.25f; + private float _updateTimer = 1.0f; private Dictionary _sRGBLookUp = new(); protected Color BackgroundColor; protected float BackgroundOpacity = 0.9f; private int _targetFontsize = 8; + private Dictionary _horizLines = new(); + private Dictionary _horizLinesReversed = new(); + private Dictionary _vertLines = new(); + private Dictionary _vertLinesReversed = new(); + // Components private NavMapComponent? _navMap; private MapGridComponent? _grid; @@ -72,6 +88,7 @@ public partial class NavMapControl : MapGridControl private readonly Label _zoom = new() { VerticalAlignment = VAlignment.Top, + HorizontalExpand = true, Margin = new Thickness(8f, 8f), }; @@ -80,6 +97,7 @@ public partial class NavMapControl : MapGridControl Text = Loc.GetString("navmap-recenter"), VerticalAlignment = VAlignment.Top, HorizontalAlignment = HAlignment.Right, + HorizontalExpand = true, Margin = new Thickness(8f, 4f), Disabled = true, }; @@ -87,9 +105,10 @@ public partial class NavMapControl : MapGridControl private readonly CheckBox _beacons = new() { Text = Loc.GetString("navmap-toggle-beacons"), - Margin = new Thickness(4f, 0f), VerticalAlignment = VAlignment.Center, HorizontalAlignment = HAlignment.Center, + HorizontalExpand = true, + Margin = new Thickness(4f, 0f), Pressed = true, }; @@ -98,6 +117,8 @@ public NavMapControl() : base(MinDisplayedRange, MaxDisplayedRange, DefaultDispl IoCManager.InjectDependencies(this); _transformSystem = EntManager.System(); + _navMapSystem = EntManager.System(); + BackgroundColor = Color.FromSrgb(TileColor.WithAlpha(BackgroundOpacity)); RectClipContent = true; @@ -112,6 +133,8 @@ public NavMapControl() : base(MinDisplayedRange, MaxDisplayedRange, DefaultDispl BorderColor = StyleNano.PanelDark }, VerticalExpand = false, + HorizontalExpand = true, + SetWidth = 650f, Children = { new BoxContainer() @@ -130,6 +153,7 @@ public NavMapControl() : base(MinDisplayedRange, MaxDisplayedRange, DefaultDispl var topContainer = new BoxContainer() { Orientation = BoxContainer.LayoutOrientation.Vertical, + HorizontalExpand = true, Children = { topPanel, @@ -157,6 +181,9 @@ public void ForceNavMapUpdate() { EntManager.TryGetComponent(MapUid, out _navMap); EntManager.TryGetComponent(MapUid, out _grid); + EntManager.TryGetComponent(MapUid, out _xform); + EntManager.TryGetComponent(MapUid, out _physics); + EntManager.TryGetComponent(MapUid, out _fixtures); UpdateNavMap(); } @@ -191,7 +218,7 @@ protected override void KeyBindUp(GUIBoundKeyEventArgs args) // Convert to a world position var unscaledPosition = (localPosition - MidPointVector) / MinimapScale; - var worldPosition = _transformSystem.GetWorldMatrix(_xform).Transform(new Vector2(unscaledPosition.X, -unscaledPosition.Y) + offset); + var worldPosition = Vector2.Transform(new Vector2(unscaledPosition.X, -unscaledPosition.Y) + offset, _transformSystem.GetWorldMatrix(_xform)); // Find closest tracked entity in range var closestEntity = NetEntity.Invalid; @@ -251,119 +278,93 @@ protected override void Draw(DrawingHandleScreen handle) EntManager.TryGetComponent(MapUid, out _physics); EntManager.TryGetComponent(MapUid, out _fixtures); + if (_navMap == null || _grid == null || _xform == null) + return; + // Map re-centering _recenter.Disabled = DrawRecenter(); - _zoom.Text = Loc.GetString("navmap-zoom", ("value", $"{(DefaultDisplayedRange / WorldRange ):0.0}")); - - if (_navMap == null || _xform == null) - return; + // Update zoom text + _zoom.Text = Loc.GetString("navmap-zoom", ("value", $"{(DefaultDisplayedRange / WorldRange):0.0}")); + // Update offset with physics local center var offset = Offset; if (_physics != null) offset += _physics.LocalCenter; - // Draw tiles - if (_fixtures != null) + var offsetVec = new Vector2(offset.X, -offset.Y); + + // Wall sRGB + if (!_sRGBLookUp.TryGetValue(WallColor, out var wallsRGB)) + { + wallsRGB = Color.ToSrgb(WallColor); + _sRGBLookUp[WallColor] = wallsRGB; + } + + // Draw floor tiles + if (TilePolygons.Any()) { Span verts = new Vector2[8]; - foreach (var fixture in _fixtures.Fixtures.Values) + foreach (var (polygonVerts, polygonColor) in TilePolygons) { - if (fixture.Shape is not PolygonShape poly) - continue; - - for (var i = 0; i < poly.VertexCount; i++) + for (var i = 0; i < polygonVerts.Length; i++) { - var vert = poly.Vertices[i] - offset; - + var vert = polygonVerts[i] - offset; verts[i] = ScalePosition(new Vector2(vert.X, -vert.Y)); } - handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, verts[..poly.VertexCount], TileColor); + handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, verts[..polygonVerts.Length], polygonColor); } } - var area = new Box2(-WorldRange, -WorldRange, WorldRange + 1f, WorldRange + 1f).Translated(offset); - - // Drawing lines can be rather expensive due to the number of neighbors that need to be checked in order - // to figure out where they should be drawn. However, we don't *need* to do check these every frame. - // Instead, lets periodically update where to draw each line and then store these points in a list. - // Then we can just run through the list each frame and draw the lines without any extra computation. - - // Draw walls - if (TileGrid != null && TileGrid.Count > 0) + // Draw map lines + if (TileLines.Any()) { - var walls = new ValueList(); + var lines = new ValueList(TileLines.Count * 2); - foreach ((var chunk, var chunkedLines) in TileGrid) + foreach (var (o, t) in TileLines) { - var offsetChunk = new Vector2(chunk.X, chunk.Y) * SharedNavMapSystem.ChunkSize; + var origin = ScalePosition(o - offsetVec); + var terminus = ScalePosition(t - offsetVec); - if (offsetChunk.X < area.Left - SharedNavMapSystem.ChunkSize || offsetChunk.X > area.Right) - continue; - - if (offsetChunk.Y < area.Bottom - SharedNavMapSystem.ChunkSize || offsetChunk.Y > area.Top) - continue; - - foreach (var chunkedLine in chunkedLines) - { - var start = ScalePosition(chunkedLine.Origin - new Vector2(offset.X, -offset.Y)); - var end = ScalePosition(chunkedLine.Terminus - new Vector2(offset.X, -offset.Y)); - - walls.Add(start); - walls.Add(end); - } + lines.Add(origin); + lines.Add(terminus); } - if (walls.Count > 0) - { - if (!_sRGBLookUp.TryGetValue(WallColor, out var sRGB)) - { - sRGB = Color.ToSrgb(WallColor); - _sRGBLookUp[WallColor] = sRGB; - } - - handle.DrawPrimitives(DrawPrimitiveTopology.LineList, walls.Span, sRGB); - } + if (lines.Count > 0) + handle.DrawPrimitives(DrawPrimitiveTopology.LineList, lines.Span, wallsRGB); } - var airlockBuffer = Vector2.One * (MinimapScale / 2.25f) * 0.75f; - var airlockLines = new ValueList(); - var foobarVec = new Vector2(1, -1); - - foreach (var airlock in _navMap.Airlocks) + // Draw map rects + if (TileRects.Any()) { - var position = airlock.Position - offset; - position = ScalePosition(position with { Y = -position.Y }); - airlockLines.Add(position + airlockBuffer); - airlockLines.Add(position - airlockBuffer * foobarVec); - - airlockLines.Add(position + airlockBuffer); - airlockLines.Add(position + airlockBuffer * foobarVec); - - airlockLines.Add(position - airlockBuffer); - airlockLines.Add(position + airlockBuffer * foobarVec); - - airlockLines.Add(position - airlockBuffer); - airlockLines.Add(position - airlockBuffer * foobarVec); + var rects = new ValueList(TileRects.Count * 8); - airlockLines.Add(position + airlockBuffer * -Vector2.UnitY); - airlockLines.Add(position - airlockBuffer * -Vector2.UnitY); - } - - if (airlockLines.Count > 0) - { - if (!_sRGBLookUp.TryGetValue(WallColor, out var sRGB)) + foreach (var (lt, rb) in TileRects) { - sRGB = Color.ToSrgb(WallColor); - _sRGBLookUp[WallColor] = sRGB; + var leftTop = ScalePosition(lt - offsetVec); + var rightBottom = ScalePosition(rb - offsetVec); + + var rightTop = new Vector2(rightBottom.X, leftTop.Y); + var leftBottom = new Vector2(leftTop.X, rightBottom.Y); + + rects.Add(leftTop); + rects.Add(rightTop); + rects.Add(rightTop); + rects.Add(rightBottom); + rects.Add(rightBottom); + rects.Add(leftBottom); + rects.Add(leftBottom); + rects.Add(leftTop); } - handle.DrawPrimitives(DrawPrimitiveTopology.LineList, airlockLines.Span, sRGB); + if (rects.Count > 0) + handle.DrawPrimitives(DrawPrimitiveTopology.LineList, rects.Span, wallsRGB); } + // Invoke post wall drawing action if (PostWallDrawingAction != null) PostWallDrawingAction.Invoke(handle); @@ -373,10 +374,10 @@ protected override void Draw(DrawingHandleScreen handle) var rectBuffer = new Vector2(5f, 3f); // Calculate font size for current zoom level - var fontSize = (int) Math.Round(1 / WorldRange * DefaultDisplayedRange * UIScale * _targetFontsize , 0); + var fontSize = (int) Math.Round(1 / WorldRange * DefaultDisplayedRange * UIScale * _targetFontsize, 0); var font = new VectorFont(_cache.GetResource("/Fonts/NotoSans/NotoSans-Bold.ttf"), fontSize); - foreach (var beacon in _navMap.Beacons) + foreach (var beacon in _navMap.Beacons.Values) { var position = beacon.Position - offset; position = ScalePosition(position with { Y = -position.Y }); @@ -400,7 +401,7 @@ protected override void Draw(DrawingHandleScreen handle) if (mapPos.MapId != MapId.Nullspace) { - var position = _transformSystem.GetInvWorldMatrix(_xform).Transform(mapPos.Position) - offset; + var position = Vector2.Transform(mapPos.Position, _transformSystem.GetInvWorldMatrix(_xform)) - offset; position = ScalePosition(new Vector2(position.X, -position.Y)); handle.DrawCircle(position, float.Sqrt(MinimapScale) * 2f, value.Color); @@ -409,8 +410,6 @@ protected override void Draw(DrawingHandleScreen handle) } // Tracked entities (can use a supplied sprite as a marker instead; should probably just replace TrackedCoordinates with this eventually) - var iconVertexUVs = new Dictionary<(Texture, Color), ValueList>(); - foreach (var blip in TrackedEntities.Values) { if (blip.Blinks && !lit) @@ -419,39 +418,18 @@ protected override void Draw(DrawingHandleScreen handle) if (blip.Texture == null) continue; - if (!iconVertexUVs.TryGetValue((blip.Texture, blip.Color), out var vertexUVs)) - vertexUVs = new(); - var mapPos = blip.Coordinates.ToMap(EntManager, _transformSystem); if (mapPos.MapId != MapId.Nullspace) { - var position = _transformSystem.GetInvWorldMatrix(_xform).Transform(mapPos.Position) - offset; + var position = Vector2.Transform(mapPos.Position, _transformSystem.GetInvWorldMatrix(_xform)) - offset; position = ScalePosition(new Vector2(position.X, -position.Y)); - var scalingCoefficient = 2.5f; - var positionOffset = scalingCoefficient * float.Sqrt(MinimapScale); - - vertexUVs.Add(new DrawVertexUV2D(new Vector2(position.X - positionOffset, position.Y - positionOffset), new Vector2(1f, 1f))); - vertexUVs.Add(new DrawVertexUV2D(new Vector2(position.X - positionOffset, position.Y + positionOffset), new Vector2(1f, 0f))); - vertexUVs.Add(new DrawVertexUV2D(new Vector2(position.X + positionOffset, position.Y - positionOffset), new Vector2(0f, 1f))); - vertexUVs.Add(new DrawVertexUV2D(new Vector2(position.X - positionOffset, position.Y + positionOffset), new Vector2(1f, 0f))); - vertexUVs.Add(new DrawVertexUV2D(new Vector2(position.X + positionOffset, position.Y - positionOffset), new Vector2(0f, 1f))); - vertexUVs.Add(new DrawVertexUV2D(new Vector2(position.X + positionOffset, position.Y + positionOffset), new Vector2(0f, 0f))); - } - - iconVertexUVs[(blip.Texture, blip.Color)] = vertexUVs; - } + var scalingCoefficient = MinmapScaleModifier * float.Sqrt(MinimapScale); + var positionOffset = new Vector2(scalingCoefficient * blip.Texture.Width, scalingCoefficient * blip.Texture.Height); - foreach ((var (texture, color), var vertexUVs) in iconVertexUVs) - { - if (!_sRGBLookUp.TryGetValue(color, out var sRGB)) - { - sRGB = Color.ToSrgb(color); - _sRGBLookUp[color] = sRGB; + handle.DrawTextureRect(blip.Texture, new UIBox2(position - positionOffset, position + positionOffset), blip.Color); } - - handle.DrawPrimitives(DrawPrimitiveTopology.TriangleList, texture, vertexUVs.Span, sRGB); } } @@ -470,123 +448,265 @@ protected override void FrameUpdate(FrameEventArgs args) protected virtual void UpdateNavMap() { - if (_navMap == null || _grid == null) + // Clear stale values + TilePolygons.Clear(); + TileLines.Clear(); + TileRects.Clear(); + + UpdateNavMapFloorTiles(); + UpdateNavMapWallLines(); + UpdateNavMapAirlocks(); + } + + private void UpdateNavMapFloorTiles() + { + if (_fixtures == null) return; - TileGrid = GetDecodedWallChunks(_navMap.Chunks, _grid); + var verts = new Vector2[8]; + + foreach (var fixture in _fixtures.Fixtures.Values) + { + if (fixture.Shape is not PolygonShape poly) + continue; + + for (var i = 0; i < poly.VertexCount; i++) + { + var vert = poly.Vertices[i]; + verts[i] = new Vector2(MathF.Round(vert.X), MathF.Round(vert.Y)); + } + + TilePolygons.Add((verts[..poly.VertexCount], TileColor)); + } } - public Dictionary> GetDecodedWallChunks - (Dictionary chunks, - MapGridComponent grid) + private void UpdateNavMapWallLines() { - var decodedOutput = new Dictionary>(); + if (_navMap == null || _grid == null) + return; - foreach ((var chunkOrigin, var chunk) in chunks) - { - var list = new List(); + // We'll use the following dictionaries to combine collinear wall lines + _horizLines.Clear(); + _horizLinesReversed.Clear(); + _vertLines.Clear(); + _vertLinesReversed.Clear(); - // TODO: Okay maybe I should just use ushorts lmao... - for (var i = 0; i < SharedNavMapSystem.ChunkSize * SharedNavMapSystem.ChunkSize; i++) + const int southMask = (int) AtmosDirection.South << (int) NavMapChunkType.Wall; + const int eastMask = (int) AtmosDirection.East << (int) NavMapChunkType.Wall; + const int westMask = (int) AtmosDirection.West << (int) NavMapChunkType.Wall; + const int northMask = (int) AtmosDirection.North << (int) NavMapChunkType.Wall; + + foreach (var (chunkOrigin, chunk) in _navMap.Chunks) + { + for (var i = 0; i < SharedNavMapSystem.ArraySize; i++) { - var value = (int) Math.Pow(2, i); + var tileData = chunk.TileData[i] & SharedNavMapSystem.WallMask; + if (tileData == 0) + continue; - var mask = chunk.TileData & value; + tileData >>= (int) NavMapChunkType.Wall; - if (mask == 0x0) + var relativeTile = SharedNavMapSystem.GetTileFromIndex(i); + var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relativeTile) * _grid.TileSize; + + if (tileData != SharedNavMapSystem.AllDirMask) + { + AddRectForThinWall(tileData, tile); continue; + } - // Alright now we'll work out our edges - var relativeTile = SharedNavMapSystem.GetTile(mask); - var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relativeTile) * grid.TileSize; - var position = new Vector2(tile.X, -tile.Y); + tile = tile with { Y = -tile.Y }; NavMapChunk? neighborChunk; - bool neighbor; // North edge - if (relativeTile.Y == SharedNavMapSystem.ChunkSize - 1) - { - neighbor = chunks.TryGetValue(chunkOrigin + new Vector2i(0, 1), out neighborChunk) && - (neighborChunk.TileData & - SharedNavMapSystem.GetFlag(new Vector2i(relativeTile.X, 0))) != 0x0; - } - else - { - var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(0, 1)); - neighbor = (chunk.TileData & flag) != 0x0; - } + var neighborData = 0; + if (relativeTile.Y != SharedNavMapSystem.ChunkSize - 1) + neighborData = chunk.TileData[i+1]; + else if (_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Up, out neighborChunk)) + neighborData = neighborChunk.TileData[i + 1 - SharedNavMapSystem.ChunkSize]; - if (!neighbor) + if ((neighborData & southMask) == 0) { - // Add points - list.Add(new NavMapLine(position + new Vector2(0f, -grid.TileSize), position + new Vector2(grid.TileSize, -grid.TileSize))); + AddOrUpdateNavMapLine(tile + new Vector2i(0, -_grid.TileSize), + tile + new Vector2i(_grid.TileSize, -_grid.TileSize), _horizLines, + _horizLinesReversed); } // East edge - if (relativeTile.X == SharedNavMapSystem.ChunkSize - 1) - { - neighbor = chunks.TryGetValue(chunkOrigin + new Vector2i(1, 0), out neighborChunk) && - (neighborChunk.TileData & - SharedNavMapSystem.GetFlag(new Vector2i(0, relativeTile.Y))) != 0x0; - } - else - { - var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(1, 0)); - neighbor = (chunk.TileData & flag) != 0x0; - } + neighborData = 0; + if (relativeTile.X != SharedNavMapSystem.ChunkSize - 1) + neighborData = chunk.TileData[i+SharedNavMapSystem.ChunkSize]; + else if (_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Right, out neighborChunk)) + neighborData = neighborChunk.TileData[i + SharedNavMapSystem.ChunkSize - SharedNavMapSystem.ArraySize]; - if (!neighbor) + if ((neighborData & westMask) == 0) { - // Add points - list.Add(new NavMapLine(position + new Vector2(grid.TileSize, -grid.TileSize), position + new Vector2(grid.TileSize, 0f))); + AddOrUpdateNavMapLine(tile + new Vector2i(_grid.TileSize, -_grid.TileSize), + tile + new Vector2i(_grid.TileSize, 0), _vertLines, _vertLinesReversed); } // South edge - if (relativeTile.Y == 0) - { - neighbor = chunks.TryGetValue(chunkOrigin + new Vector2i(0, -1), out neighborChunk) && - (neighborChunk.TileData & - SharedNavMapSystem.GetFlag(new Vector2i(relativeTile.X, SharedNavMapSystem.ChunkSize - 1))) != 0x0; - } - else - { - var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(0, -1)); - neighbor = (chunk.TileData & flag) != 0x0; - } + neighborData = 0; + if (relativeTile.Y != 0) + neighborData = chunk.TileData[i-1]; + else if (_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Down, out neighborChunk)) + neighborData = neighborChunk.TileData[i - 1 + SharedNavMapSystem.ChunkSize]; - if (!neighbor) + if ((neighborData & northMask) == 0) { - // Add points - list.Add(new NavMapLine(position + new Vector2(grid.TileSize, 0f), position)); + AddOrUpdateNavMapLine(tile, tile + new Vector2i(_grid.TileSize, 0), _horizLines, + _horizLinesReversed); } // West edge - if (relativeTile.X == 0) - { - neighbor = chunks.TryGetValue(chunkOrigin + new Vector2i(-1, 0), out neighborChunk) && - (neighborChunk.TileData & - SharedNavMapSystem.GetFlag(new Vector2i(SharedNavMapSystem.ChunkSize - 1, relativeTile.Y))) != 0x0; - } - else + neighborData = 0; + if (relativeTile.X != 0) + neighborData = chunk.TileData[i-SharedNavMapSystem.ChunkSize]; + else if (_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Left, out neighborChunk)) + neighborData = neighborChunk.TileData[i - SharedNavMapSystem.ChunkSize + SharedNavMapSystem.ArraySize]; + + if ((neighborData & eastMask) == 0) { - var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(-1, 0)); - neighbor = (chunk.TileData & flag) != 0x0; + AddOrUpdateNavMapLine(tile + new Vector2i(0, -_grid.TileSize), tile, _vertLines, + _vertLinesReversed); } - if (!neighbor) + // Add a diagonal line for interiors. Unless there are a lot of double walls, there is no point combining these + TileLines.Add((tile + new Vector2(0, -_grid.TileSize), tile + new Vector2(_grid.TileSize, 0))); + } + } + + // Record the combined lines + foreach (var (origin, terminal) in _horizLines) + { + TileLines.Add((origin, terminal)); + } + + foreach (var (origin, terminal) in _vertLines) + { + TileLines.Add((origin, terminal)); + } + } + + private void UpdateNavMapAirlocks() + { + if (_navMap == null || _grid == null) + return; + + foreach (var chunk in _navMap.Chunks.Values) + { + for (var i = 0; i < SharedNavMapSystem.ArraySize; i++) + { + var tileData = chunk.TileData[i] & SharedNavMapSystem.AirlockMask; + if (tileData == 0) + continue; + + tileData >>= (int) NavMapChunkType.Airlock; + + var relative = SharedNavMapSystem.GetTileFromIndex(i); + var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relative) * _grid.TileSize; + + // If the edges of an airlock tile are not all occupied, draw a thin airlock for each edge + if (tileData != SharedNavMapSystem.AllDirMask) { - // Add point - list.Add(new NavMapLine(position, position + new Vector2(0f, -grid.TileSize))); + AddRectForThinAirlock(tileData, tile); + continue; } - // Draw a diagonal line for interiors. - list.Add(new NavMapLine(position + new Vector2(0f, -grid.TileSize), position + new Vector2(grid.TileSize, 0f))); + // Otherwise add a single full tile airlock + TileRects.Add((new Vector2(tile.X + FullWallInstep, -tile.Y - FullWallInstep), + new Vector2(tile.X - FullWallInstep + 1f, -tile.Y + FullWallInstep - 1))); + + TileLines.Add((new Vector2(tile.X + 0.5f, -tile.Y - FullWallInstep), + new Vector2(tile.X + 0.5f, -tile.Y + FullWallInstep - 1))); + } + } + } + + private void AddRectForThinWall(int tileData, Vector2i tile) + { + var leftTop = new Vector2(-0.5f, 0.5f - ThinWallThickness); + var rightBottom = new Vector2(0.5f, 0.5f); + + for (var i = 0; i < SharedNavMapSystem.Directions; i++) + { + var dirMask = 1 << i; + if ((tileData & dirMask) == 0) + continue; + + var tilePosition = new Vector2(tile.X + 0.5f, -tile.Y - 0.5f); + + // TODO NAVMAP + // Consider using faster rotation operations, given that these are always 90 degree increments + var angle = -((AtmosDirection) dirMask).ToAngle(); + TileRects.Add((angle.RotateVec(leftTop) + tilePosition, angle.RotateVec(rightBottom) + tilePosition)); + } + } + + private void AddRectForThinAirlock(int tileData, Vector2i tile) + { + var leftTop = new Vector2(-0.5f + FullWallInstep, 0.5f - FullWallInstep - ThinDoorThickness); + var rightBottom = new Vector2(0.5f - FullWallInstep, 0.5f - FullWallInstep); + var centreTop = new Vector2(0f, 0.5f - FullWallInstep - ThinDoorThickness); + var centreBottom = new Vector2(0f, 0.5f - FullWallInstep); + + for (var i = 0; i < SharedNavMapSystem.Directions; i++) + { + var dirMask = 1 << i; + if ((tileData & dirMask) == 0) + continue; + + var tilePosition = new Vector2(tile.X + 0.5f, -tile.Y - 0.5f); + var angle = -((AtmosDirection) dirMask).ToAngle(); + TileRects.Add((angle.RotateVec(leftTop) + tilePosition, angle.RotateVec(rightBottom) + tilePosition)); + TileLines.Add((angle.RotateVec(centreTop) + tilePosition, angle.RotateVec(centreBottom) + tilePosition)); + } + } + + protected void AddOrUpdateNavMapLine( + Vector2i origin, + Vector2i terminus, + Dictionary lookup, + Dictionary lookupReversed) + { + Vector2i foundTermius; + Vector2i foundOrigin; + + // Does our new line end at the beginning of an existing line? + if (lookup.Remove(terminus, out foundTermius)) + { + DebugTools.Assert(lookupReversed[foundTermius] == terminus); + + // Does our new line start at the end of an existing line? + if (lookupReversed.Remove(origin, out foundOrigin)) + { + // Our new line just connects two existing lines + DebugTools.Assert(lookup[foundOrigin] == origin); + lookup[foundOrigin] = foundTermius; + lookupReversed[foundTermius] = foundOrigin; + } + else + { + // Our new line precedes an existing line, extending it further to the left + lookup[origin] = foundTermius; + lookupReversed[foundTermius] = origin; } + return; + } - decodedOutput.Add(chunkOrigin, list); + // Does our new line start at the end of an existing line? + if (lookupReversed.Remove(origin, out foundOrigin)) + { + // Our new line just extends an existing line further to the right + DebugTools.Assert(lookup[foundOrigin] == origin); + lookup[foundOrigin] = terminus; + lookupReversed[terminus] = foundOrigin; + return; } - return decodedOutput; + // Completely disconnected line segment. + lookup.Add(origin, terminus); + lookupReversed.Add(terminus, origin); } protected Vector2 GetOffset() @@ -612,15 +732,3 @@ public NavMapBlip(EntityCoordinates coordinates, Texture texture, Color color, b Selectable = selectable; } } - -public struct NavMapLine -{ - public readonly Vector2 Origin; - public readonly Vector2 Terminus; - - public NavMapLine(Vector2 origin, Vector2 terminus) - { - Origin = origin; - Terminus = terminus; - } -} diff --git a/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs b/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs index a2f8061d05..286358b85e 100644 --- a/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs +++ b/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs @@ -1,6 +1,7 @@ using System.Diagnostics.CodeAnalysis; using Content.Shared.CCVar; using Content.Shared.Customization.Systems; +using Content.Shared.Players.JobWhitelist; using Content.Shared.Players; using Content.Shared.Players.PlayTimeTracking; using Content.Shared.Roles; @@ -18,13 +19,13 @@ public sealed partial class JobRequirementsManager : ISharedPlaytimeManager { [Dependency] private readonly IBaseClient _client = default!; [Dependency] private readonly IClientNetManager _net = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; [Dependency] private readonly IPrototypeManager _prototypes = default!; private readonly Dictionary _roles = new(); private readonly List _roleBans = new(); - private ISawmill _sawmill = default!; - + private readonly List _jobWhitelists = new(); public event Action? Updated; public void Initialize() @@ -35,6 +36,7 @@ public void Initialize() _net.RegisterNetMessage(RxRoleBans); _net.RegisterNetMessage(RxPlayTime); _net.RegisterNetMessage(RxWhitelist); + _net.RegisterNetMessage(RxJobWhitelist); _client.RunLevelChanged += ClientOnRunLevelChanged; } @@ -78,6 +80,28 @@ private void RxPlayTime(MsgPlayTime message) Updated?.Invoke(); } + private void RxJobWhitelist(MsgJobWhitelist message) + { + _jobWhitelists.Clear(); + _jobWhitelists.AddRange(message.Whitelist); + Updated?.Invoke(); + } + + public bool CheckJobWhitelist(JobPrototype job, [NotNullWhen(false)] out FormattedMessage? reason) + { + reason = default; + if (!_cfg.GetCVar(CCVars.GameRoleWhitelist)) + return true; + + if (job.Whitelisted && !_jobWhitelists.Contains(job.ID)) + { + reason = FormattedMessage.FromUnformatted(Loc.GetString("role-not-whitelisted")); + return false; + } + + return true; + } + public TimeSpan FetchOverallPlaytime() { return _roles.TryGetValue("Overall", out var overallPlaytime) ? overallPlaytime : TimeSpan.Zero; diff --git a/Content.Client/Polymorph/Systems/ChameleonProjectorSystem.cs b/Content.Client/Polymorph/Systems/ChameleonProjectorSystem.cs new file mode 100644 index 0000000000..5ba4878c6d --- /dev/null +++ b/Content.Client/Polymorph/Systems/ChameleonProjectorSystem.cs @@ -0,0 +1,33 @@ +using Content.Shared.Chemistry.Components; +using Content.Shared.Polymorph.Components; +using Content.Shared.Polymorph.Systems; +using Robust.Client.GameObjects; + +namespace Content.Client.Polymorph.Systems; + +public sealed class ChameleonProjectorSystem : SharedChameleonProjectorSystem +{ + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + + private EntityQuery _appearanceQuery; + + public override void Initialize() + { + base.Initialize(); + + _appearanceQuery = GetEntityQuery(); + + SubscribeLocalEvent(OnHandleState); + } + + private void OnHandleState(Entity ent, ref AfterAutoHandleStateEvent args) + { + CopyComp(ent); + CopyComp(ent); + CopyComp(ent); + + // reload appearance to hopefully prevent any invisible layers + if (_appearanceQuery.TryComp(ent, out var appearance)) + _appearance.QueueUpdate(ent, appearance); + } +} diff --git a/Content.Client/Popups/PopupOverlay.cs b/Content.Client/Popups/PopupOverlay.cs index fb6bb3bf56..77eeb611f5 100644 --- a/Content.Client/Popups/PopupOverlay.cs +++ b/Content.Client/Popups/PopupOverlay.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Content.Shared.Examine; using Robust.Client.Graphics; using Robust.Client.Player; @@ -55,7 +56,7 @@ protected override void Draw(in OverlayDrawArgs args) if (args.ViewportControl == null) return; - args.DrawingHandle.SetTransform(Matrix3.Identity); + args.DrawingHandle.SetTransform(Matrix3x2.Identity); args.DrawingHandle.UseShader(_shader); var scale = _configManager.GetCVar(CVars.DisplayUIScale); @@ -90,7 +91,7 @@ private void DrawWorld(DrawingHandleScreen worldHandle, OverlayDrawArgs args, fl e => e == popup.InitialPos.EntityId || e == ourEntity, entMan: _entManager)) continue; - var pos = matrix.Transform(mapPos.Position); + var pos = Vector2.Transform(mapPos.Position, matrix); _controller.DrawPopup(popup, worldHandle, pos, scale); } } diff --git a/Content.Client/Popups/PopupSystem.cs b/Content.Client/Popups/PopupSystem.cs index 3faa392e58..1ef8dfba2d 100644 --- a/Content.Client/Popups/PopupSystem.cs +++ b/Content.Client/Popups/PopupSystem.cs @@ -5,7 +5,6 @@ using Robust.Client.Graphics; using Robust.Client.Input; using Robust.Client.Player; -using Robust.Client.ResourceManagement; using Robust.Client.UserInterface; using Robust.Shared.Configuration; using Robust.Shared.Map; @@ -163,6 +162,15 @@ public override void PopupEntity(string? message, EntityUid uid, Filter filter, PopupEntity(message, uid, type); } + public override void PopupClient(string? message, EntityUid? recipient, PopupType type = PopupType.Small) + { + if (recipient == null) + return; + + if (_timing.IsFirstTimePredicted) + PopupCursor(message, recipient.Value, type); + } + public override void PopupClient(string? message, EntityUid uid, EntityUid? recipient, PopupType type = PopupType.Small) { if (recipient == null) @@ -172,6 +180,15 @@ public override void PopupClient(string? message, EntityUid uid, EntityUid? reci PopupEntity(message, uid, recipient.Value, type); } + public override void PopupClient(string? message, EntityCoordinates coordinates, EntityUid? recipient, PopupType type = PopupType.Small) + { + if (recipient == null) + return; + + if (_timing.IsFirstTimePredicted) + PopupCoordinates(message, coordinates, recipient.Value, type); + } + public override void PopupEntity(string? message, EntityUid uid, PopupType type = PopupType.Small) { if (TryComp(uid, out TransformComponent? transform)) diff --git a/Content.Client/Power/ActivatableUIRequiresPowerSystem.cs b/Content.Client/Power/ActivatableUIRequiresPowerSystem.cs new file mode 100644 index 0000000000..60ed8d87b9 --- /dev/null +++ b/Content.Client/Power/ActivatableUIRequiresPowerSystem.cs @@ -0,0 +1,21 @@ +using Content.Shared.Power.Components; +using Content.Shared.UserInterface; +using Content.Shared.Wires; + +namespace Content.Client.Power; + +public sealed class ActivatableUIRequiresPowerSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnActivate); + } + + private void OnActivate(EntityUid uid, ActivatableUIRequiresPowerComponent component, ActivatableUIOpenAttemptEvent args) + { + // Client can't predict the power properly at the moment so rely upon the server to do it. + args.Cancel(); + } +} diff --git a/Content.Client/Power/PowerMonitoringConsoleNavMapControl.cs b/Content.Client/Power/PowerMonitoringConsoleNavMapControl.cs index 902d6bb7e6..d5057416cf 100644 --- a/Content.Client/Power/PowerMonitoringConsoleNavMapControl.cs +++ b/Content.Client/Power/PowerMonitoringConsoleNavMapControl.cs @@ -5,6 +5,7 @@ using Robust.Shared.Collections; using Robust.Shared.Map.Components; using System.Numerics; +using static Content.Shared.Power.SharedPowerMonitoringConsoleSystem; namespace Content.Client.Power; @@ -23,8 +24,13 @@ public sealed partial class PowerMonitoringConsoleNavMapControl : NavMapControl public PowerMonitoringCableNetworksComponent? PowerMonitoringCableNetworks; public List HiddenLineGroups = new(); - public Dictionary>? PowerCableNetwork; - public Dictionary>? FocusCableNetwork; + public List PowerCableNetwork = new(); + public List FocusCableNetwork = new(); + + private Dictionary[] _horizLines = [new(), new(), new()]; + private Dictionary[] _horizLinesReversed = [new(), new(), new()]; + private Dictionary[] _vertLines = [new(), new(), new()]; + private Dictionary[] _vertLinesReversed = [new(), new(), new()]; private MapGridComponent? _grid; @@ -48,15 +54,15 @@ protected override void UpdateNavMap() if (!_entManager.TryGetComponent(Owner, out var cableNetworks)) return; - if (!_entManager.TryGetComponent(MapUid, out _grid)) - return; - - PowerCableNetwork = GetDecodedPowerCableChunks(cableNetworks.AllChunks, _grid); - FocusCableNetwork = GetDecodedPowerCableChunks(cableNetworks.FocusChunks, _grid); + PowerCableNetwork = GetDecodedPowerCableChunks(cableNetworks.AllChunks); + FocusCableNetwork = GetDecodedPowerCableChunks(cableNetworks.FocusChunks); } public void DrawAllCableNetworks(DrawingHandleScreen handle) { + if (!_entManager.TryGetComponent(MapUid, out _grid)) + return; + // Draw full cable network if (PowerCableNetwork != null && PowerCableNetwork.Count > 0) { @@ -69,36 +75,29 @@ public void DrawAllCableNetworks(DrawingHandleScreen handle) DrawCableNetwork(handle, FocusCableNetwork, Color.White); } - public void DrawCableNetwork(DrawingHandleScreen handle, Dictionary> fullCableNetwork, Color modulator) + public void DrawCableNetwork(DrawingHandleScreen handle, List fullCableNetwork, Color modulator) { + if (!_entManager.TryGetComponent(MapUid, out _grid)) + return; + var offset = GetOffset(); - var area = new Box2(-WorldRange, -WorldRange, WorldRange + 1f, WorldRange + 1f).Translated(offset); + offset = offset with { Y = -offset.Y }; if (WorldRange / WorldMaxRange > 0.5f) { var cableNetworks = new ValueList[3]; - foreach ((var chunk, var chunkedLines) in fullCableNetwork) + foreach (var line in fullCableNetwork) { - var offsetChunk = new Vector2(chunk.X, chunk.Y) * SharedNavMapSystem.ChunkSize; - - if (offsetChunk.X < area.Left - SharedNavMapSystem.ChunkSize || offsetChunk.X > area.Right) + if (HiddenLineGroups.Contains(line.Group)) continue; - if (offsetChunk.Y < area.Bottom - SharedNavMapSystem.ChunkSize || offsetChunk.Y > area.Top) - continue; - - foreach (var chunkedLine in chunkedLines) - { - if (HiddenLineGroups.Contains(chunkedLine.Group)) - continue; - - var start = ScalePosition(chunkedLine.Origin - new Vector2(offset.X, -offset.Y)); - var end = ScalePosition(chunkedLine.Terminus - new Vector2(offset.X, -offset.Y)); + var cableOffset = _powerCableOffsets[(int) line.Group]; + var start = ScalePosition(line.Origin + cableOffset - offset); + var end = ScalePosition(line.Terminus + cableOffset - offset); - cableNetworks[(int) chunkedLine.Group].Add(start); - cableNetworks[(int) chunkedLine.Group].Add(end); - } + cableNetworks[(int) line.Group].Add(start); + cableNetworks[(int) line.Group].Add(end); } for (int cableNetworkIdx = 0; cableNetworkIdx < cableNetworks.Length; cableNetworkIdx++) @@ -124,48 +123,39 @@ public void DrawCableNetwork(DrawingHandleScreen handle, Dictionary[3]; - foreach ((var chunk, var chunkedLines) in fullCableNetwork) + foreach (var line in fullCableNetwork) { - var offsetChunk = new Vector2(chunk.X, chunk.Y) * SharedNavMapSystem.ChunkSize; - - if (offsetChunk.X < area.Left - SharedNavMapSystem.ChunkSize || offsetChunk.X > area.Right) + if (HiddenLineGroups.Contains(line.Group)) continue; - if (offsetChunk.Y < area.Bottom - SharedNavMapSystem.ChunkSize || offsetChunk.Y > area.Top) - continue; - - foreach (var chunkedLine in chunkedLines) - { - if (HiddenLineGroups.Contains(chunkedLine.Group)) - continue; - - var leftTop = ScalePosition(new Vector2 - (Math.Min(chunkedLine.Origin.X, chunkedLine.Terminus.X) - 0.1f, - Math.Min(chunkedLine.Origin.Y, chunkedLine.Terminus.Y) - 0.1f) - - new Vector2(offset.X, -offset.Y)); - - var rightTop = ScalePosition(new Vector2 - (Math.Max(chunkedLine.Origin.X, chunkedLine.Terminus.X) + 0.1f, - Math.Min(chunkedLine.Origin.Y, chunkedLine.Terminus.Y) - 0.1f) - - new Vector2(offset.X, -offset.Y)); - - var leftBottom = ScalePosition(new Vector2 - (Math.Min(chunkedLine.Origin.X, chunkedLine.Terminus.X) - 0.1f, - Math.Max(chunkedLine.Origin.Y, chunkedLine.Terminus.Y) + 0.1f) - - new Vector2(offset.X, -offset.Y)); - - var rightBottom = ScalePosition(new Vector2 - (Math.Max(chunkedLine.Origin.X, chunkedLine.Terminus.X) + 0.1f, - Math.Max(chunkedLine.Origin.Y, chunkedLine.Terminus.Y) + 0.1f) - - new Vector2(offset.X, -offset.Y)); - - cableVertexUVs[(int) chunkedLine.Group].Add(leftBottom); - cableVertexUVs[(int) chunkedLine.Group].Add(leftTop); - cableVertexUVs[(int) chunkedLine.Group].Add(rightBottom); - cableVertexUVs[(int) chunkedLine.Group].Add(leftTop); - cableVertexUVs[(int) chunkedLine.Group].Add(rightBottom); - cableVertexUVs[(int) chunkedLine.Group].Add(rightTop); - } + var cableOffset = _powerCableOffsets[(int) line.Group]; + + var leftTop = ScalePosition(new Vector2 + (Math.Min(line.Origin.X, line.Terminus.X) - 0.1f, + Math.Min(line.Origin.Y, line.Terminus.Y) - 0.1f) + + cableOffset - offset); + + var rightTop = ScalePosition(new Vector2 + (Math.Max(line.Origin.X, line.Terminus.X) + 0.1f, + Math.Min(line.Origin.Y, line.Terminus.Y) - 0.1f) + + cableOffset - offset); + + var leftBottom = ScalePosition(new Vector2 + (Math.Min(line.Origin.X, line.Terminus.X) - 0.1f, + Math.Max(line.Origin.Y, line.Terminus.Y) + 0.1f) + + cableOffset - offset); + + var rightBottom = ScalePosition(new Vector2 + (Math.Max(line.Origin.X, line.Terminus.X) + 0.1f, + Math.Max(line.Origin.Y, line.Terminus.Y) + 0.1f) + + cableOffset - offset); + + cableVertexUVs[(int) line.Group].Add(leftBottom); + cableVertexUVs[(int) line.Group].Add(leftTop); + cableVertexUVs[(int) line.Group].Add(rightBottom); + cableVertexUVs[(int) line.Group].Add(leftTop); + cableVertexUVs[(int) line.Group].Add(rightBottom); + cableVertexUVs[(int) line.Group].Add(rightTop); } for (int cableNetworkIdx = 0; cableNetworkIdx < cableVertexUVs.Length; cableNetworkIdx++) @@ -188,34 +178,43 @@ public void DrawCableNetwork(DrawingHandleScreen handle, Dictionary>? GetDecodedPowerCableChunks(Dictionary? chunks, MapGridComponent? grid) + public List GetDecodedPowerCableChunks(Dictionary? chunks) { - if (chunks == null || grid == null) - return null; + var decodedOutput = new List(); - var decodedOutput = new Dictionary>(); + if (!_entManager.TryGetComponent(MapUid, out _grid)) + return decodedOutput; - foreach ((var chunkOrigin, var chunk) in chunks) - { - var list = new List(); + if (chunks == null) + return decodedOutput; + + Array.ForEach(_horizLines, x=> x.Clear()); + Array.ForEach(_horizLinesReversed, x=> x.Clear()); + Array.ForEach(_vertLines, x=> x.Clear()); + Array.ForEach(_vertLinesReversed, x=> x.Clear()); - for (int cableIdx = 0; cableIdx < chunk.PowerCableData.Length; cableIdx++) + foreach (var (chunkOrigin, chunk) in chunks) + { + for (var cableIdx = 0; cableIdx < 3; cableIdx++) { - var chunkMask = chunk.PowerCableData[cableIdx]; + var horizLines = _horizLines[cableIdx]; + var horizLinesReversed = _horizLinesReversed[cableIdx]; + var vertLines = _vertLines[cableIdx]; + var vertLinesReversed = _vertLinesReversed[cableIdx]; - Vector2 offset = _powerCableOffsets[cableIdx]; + var chunkMask = chunk.PowerCableData[cableIdx]; - for (var chunkIdx = 0; chunkIdx < SharedNavMapSystem.ChunkSize * SharedNavMapSystem.ChunkSize; chunkIdx++) + for (var chunkIdx = 0; chunkIdx < ChunkSize * ChunkSize; chunkIdx++) { - var value = (int) Math.Pow(2, chunkIdx); + var value = 1 << chunkIdx; var mask = chunkMask & value; if (mask == 0x0) continue; - var relativeTile = SharedNavMapSystem.GetTile(mask); - var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relativeTile) * grid.TileSize; - var position = new Vector2(tile.X, -tile.Y); + var relativeTile = GetTileFromIndex(chunkIdx); + var tile = (chunk.Origin * ChunkSize + relativeTile) * _grid.TileSize; + tile = tile with { Y = -tile.Y }; PowerCableChunk neighborChunk; bool neighbor; @@ -223,56 +222,65 @@ public void DrawCableNetwork(DrawingHandleScreen handle, Dictionary 0) - decodedOutput.Add(chunkOrigin, list); + for (var index = 0; index < _vertLines.Length; index++) + { + var vertLines = _vertLines[index]; + foreach (var (origin, terminal) in vertLines) + { + decodedOutput.Add(new PowerMonitoringConsoleLine(origin + gridOffset, terminal + gridOffset, + (PowerMonitoringConsoleLineGroup) index)); + } } return decodedOutput; diff --git a/Content.Client/Power/PowerMonitoringWindow.xaml.cs b/Content.Client/Power/PowerMonitoringWindow.xaml.cs index edc0eaa18a..81fe1f4d04 100644 --- a/Content.Client/Power/PowerMonitoringWindow.xaml.cs +++ b/Content.Client/Power/PowerMonitoringWindow.xaml.cs @@ -170,9 +170,6 @@ public void ShowEntites NavMap.TrackedEntities[mon.Value] = blip; } - // Update nav map - NavMap.ForceNavMapUpdate(); - // If the entry group doesn't match the current tab, the data is out dated, do not use it if (allEntries.Length > 0 && allEntries[0].Group != GetCurrentPowerMonitoringConsoleGroup()) return; diff --git a/Content.Client/Preferences/UI/AntagPreferenceSelector.cs b/Content.Client/Preferences/UI/AntagPreferenceSelector.cs deleted file mode 100644 index 4a339d3f65..0000000000 --- a/Content.Client/Preferences/UI/AntagPreferenceSelector.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Content.Client.Players.PlayTimeTracking; -using Content.Shared.Customization.Systems; -using Content.Shared.Preferences; -using Content.Shared.Roles; -using Robust.Shared.Configuration; -using Robust.Shared.Prototypes; - -namespace Content.Client.Preferences.UI; - -public sealed class AntagPreferenceSelector : RequirementsSelector -{ - // 0 is yes and 1 is no - public bool Preference - { - get => Options.SelectedValue == 0; - set => Options.Select(value && !Disabled ? 0 : 1); - } - - public event Action? PreferenceChanged; - - public AntagPreferenceSelector(AntagPrototype proto, JobPrototype highJob) : base(proto, highJob) - { - Options.OnItemSelected += _ => PreferenceChanged?.Invoke(Preference); - - var items = new[] - { - ("humanoid-profile-editor-antag-preference-yes-button", 0), - ("humanoid-profile-editor-antag-preference-no-button", 1), - }; - var title = Loc.GetString(proto.Name); - var description = Loc.GetString(proto.Objective); - Setup(items, title, 250, description); - - // Immediately lock requirements if they aren't met. - // Another function checks Disabled after creating the selector so this has to be done now - var requirements = IoCManager.Resolve(); - var prefs = IoCManager.Resolve(); - var entMan = IoCManager.Resolve(); - var characterReqs = entMan.System(); - var protoMan = IoCManager.Resolve(); - var configMan = IoCManager.Resolve(); - - if (proto.Requirements != null - && !characterReqs.CheckRequirementsValid( - proto.Requirements, - highJob, - (HumanoidCharacterProfile) (prefs.Preferences?.SelectedCharacter ?? HumanoidCharacterProfile.DefaultWithSpecies()), - requirements.GetRawPlayTimeTrackers(), - requirements.IsWhitelisted(), - entMan, - protoMan, - configMan, - out var reasons)) - LockRequirements(characterReqs.GetRequirementsText(reasons)); - } -} diff --git a/Content.Client/Preferences/UI/CharacterSetupGui.xaml.cs b/Content.Client/Preferences/UI/CharacterSetupGui.xaml.cs deleted file mode 100644 index 5165db5479..0000000000 --- a/Content.Client/Preferences/UI/CharacterSetupGui.xaml.cs +++ /dev/null @@ -1,257 +0,0 @@ -using System.Linq; -using System.Numerics; -using Content.Client.Humanoid; -using Content.Client.Info; -using Content.Client.Info.PlaytimeStats; -using Content.Client.Lobby; -using Content.Client.Lobby.UI; -using Content.Client.Resources; -using Content.Client.Stylesheets; -using Content.Shared.Clothing.Loadouts.Systems; -using Content.Shared.Humanoid; -using Content.Shared.Humanoid.Prototypes; -using Content.Shared.Preferences; -using Content.Shared.Roles; -using Robust.Client.AutoGenerated; -using Robust.Client.Graphics; -using Robust.Client.ResourceManagement; -using Robust.Client.UserInterface; -using Robust.Client.UserInterface.Controls; -using Robust.Client.UserInterface.XAML; -using Robust.Shared.Configuration; -using Robust.Shared.Map; -using Robust.Shared.Prototypes; -using static Robust.Client.UserInterface.Controls.BoxContainer; -using Direction = Robust.Shared.Maths.Direction; - -namespace Content.Client.Preferences.UI -{ - [GenerateTypedNameReferences] - public sealed partial class CharacterSetupGui : Control - { - private readonly IClientPreferencesManager _preferencesManager; - private readonly IEntityManager _entityManager; - private readonly IPrototypeManager _prototypeManager; - private readonly Button _createNewCharacterButton; - private readonly HumanoidProfileEditor _humanoidProfileEditor; - - public CharacterSetupGui( - IEntityManager entityManager, - IResourceCache resourceCache, - IClientPreferencesManager preferencesManager, - IPrototypeManager prototypeManager, - IConfigurationManager configurationManager) - { - RobustXamlLoader.Load(this); - _entityManager = entityManager; - _prototypeManager = prototypeManager; - _preferencesManager = preferencesManager; - - var panelTex = resourceCache.GetTexture("/Textures/Interface/Nano/button.svg.96dpi.png"); - var back = new StyleBoxTexture - { - Texture = panelTex, - Modulate = new Color(37, 37, 42) - }; - back.SetPatchMargin(StyleBox.Margin.All, 10); - - BackgroundPanel.PanelOverride = back; - - _createNewCharacterButton = new Button - { - Text = Loc.GetString("character-setup-gui-create-new-character-button"), - }; - _createNewCharacterButton.OnPressed += args => - { - preferencesManager.CreateCharacter(HumanoidCharacterProfile.Random()); - UpdateUI(); - args.Event.Handle(); - }; - - _humanoidProfileEditor = new HumanoidProfileEditor(preferencesManager, prototypeManager, configurationManager); - _humanoidProfileEditor.OnProfileChanged += ProfileChanged; - CharEditor.AddChild(_humanoidProfileEditor); - - UpdateUI(); - - RulesButton.OnPressed += _ => new RulesAndInfoWindow().Open(); - - StatsButton.OnPressed += _ => new PlaytimeStatsWindow().OpenCentered(); - preferencesManager.OnServerDataLoaded += UpdateUI; - } - - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - if (!disposing) - return; - - _preferencesManager.OnServerDataLoaded -= UpdateUI; - } - - public void Save() => _humanoidProfileEditor.Save(); - - private void ProfileChanged(ICharacterProfile profile, int profileSlot) - { - _humanoidProfileEditor.UpdateControls(); - UpdateUI(); - } - - private void UpdateUI() - { - UserInterfaceManager.GetUIController().UpdateCharacterUI(); - var numberOfFullSlots = 0; - var characterButtonsGroup = new ButtonGroup(); - Characters.RemoveAllChildren(); - - if (!_preferencesManager.ServerDataLoaded) - return; - - _createNewCharacterButton.ToolTip = - Loc.GetString("character-setup-gui-create-new-character-button-tooltip", - ("maxCharacters", _preferencesManager.Settings!.MaxCharacterSlots)); - - foreach (var (slot, character) in _preferencesManager.Preferences!.Characters) - { - numberOfFullSlots++; - var characterPickerButton = new CharacterPickerButton(_entityManager, - _preferencesManager, - _prototypeManager, - characterButtonsGroup, - character); - Characters.AddChild(characterPickerButton); - - var characterIndexCopy = slot; - characterPickerButton.OnPressed += args => - { - _humanoidProfileEditor.Profile = (HumanoidCharacterProfile)character; - _humanoidProfileEditor.CharacterSlot = characterIndexCopy; - _humanoidProfileEditor.UpdateControls(); - _preferencesManager.SelectCharacter(character); - UpdateUI(); - args.Event.Handle(); - }; - } - - _createNewCharacterButton.Disabled = - numberOfFullSlots >= _preferencesManager.Settings.MaxCharacterSlots; - Characters.AddChild(_createNewCharacterButton); - // TODO: Move this shit to the Lobby UI controller - } - - /// - /// Shows individual characters on the side of the character GUI. - /// - private sealed class CharacterPickerButton : ContainerButton - { - private EntityUid _previewDummy; - - public CharacterPickerButton( - IEntityManager entityManager, - IClientPreferencesManager preferencesManager, - IPrototypeManager prototypeManager, - ButtonGroup group, - ICharacterProfile profile) - { - AddStyleClass(StyleClassButton); - ToggleMode = true; - Group = group; - - var humanoid = profile as HumanoidCharacterProfile; - if (humanoid is not null) - { - var dummy = prototypeManager.Index(humanoid.Species).DollPrototype; - _previewDummy = entityManager.SpawnEntity(dummy, MapCoordinates.Nullspace); - } - else - { - _previewDummy = entityManager.SpawnEntity(prototypeManager.Index(SharedHumanoidAppearanceSystem.DefaultSpecies).DollPrototype, MapCoordinates.Nullspace); - } - - EntitySystem.Get().LoadProfile(_previewDummy, (HumanoidCharacterProfile)profile); - - if (humanoid != null) - { - var controller = UserInterfaceManager.GetUIController(); - controller.GiveDummyJobClothesLoadout(_previewDummy, humanoid); - } - - var isSelectedCharacter = profile == preferencesManager.Preferences?.SelectedCharacter; - - if (isSelectedCharacter) - Pressed = true; - - var view = new SpriteView - { - Scale = new Vector2(2, 2), - OverrideDirection = Direction.South - }; - view.SetEntity(_previewDummy); - - var description = profile.Name; - - var highPriorityJob = humanoid?.JobPriorities.SingleOrDefault(p => p.Value == JobPriority.High).Key; - if (highPriorityJob != null) - { - var jobName = IoCManager.Resolve().Index(highPriorityJob).LocalizedName; - description = $"{description}\n{jobName}"; - } - - var descriptionLabel = new Label - { - Text = description, - ClipText = true, - HorizontalExpand = true - }; - var deleteButton = new Button - { - Text = Loc.GetString("character-setup-gui-character-picker-button-delete-button"), - Visible = !isSelectedCharacter, - }; - var confirmDeleteButton = new Button - { - Text = Loc.GetString("character-setup-gui-character-picker-button-confirm-delete-button"), - Visible = false, - }; - confirmDeleteButton.ModulateSelfOverride = StyleNano.ButtonColorCautionDefault; - confirmDeleteButton.OnPressed += _ => - { - Parent?.RemoveChild(this); - Parent?.RemoveChild(confirmDeleteButton); - preferencesManager.DeleteCharacter(profile); - }; - deleteButton.OnPressed += _ => - { - deleteButton.Visible = false; - confirmDeleteButton.Visible = true; - }; - - var internalHBox = new BoxContainer - { - Orientation = LayoutOrientation.Horizontal, - HorizontalExpand = true, - SeparationOverride = 0, - Children = - { - view, - descriptionLabel, - deleteButton, - confirmDeleteButton - } - }; - - AddChild(internalHBox); - } - - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - if (!disposing) - return; - - IoCManager.Resolve().DeleteEntity(_previewDummy); - _previewDummy = default; - } - } - } -} diff --git a/Content.Client/Preferences/UI/HumanoidProfileEditor.Random.cs b/Content.Client/Preferences/UI/HumanoidProfileEditor.Random.cs deleted file mode 100644 index e12da12d0a..0000000000 --- a/Content.Client/Preferences/UI/HumanoidProfileEditor.Random.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Content.Shared.Preferences; - -namespace Content.Client.Preferences.UI; - -public sealed partial class HumanoidProfileEditor -{ - private void RandomizeEverything() - { - Profile = HumanoidCharacterProfile.Random(); - UpdateControls(); - IsDirty = true; - } - - private void RandomizeName() - { - if (Profile == null) - return; - var name = HumanoidCharacterProfile.GetName(Profile.Species, Profile.Gender); - SetName(name); - UpdateNameEdit(); - } -} diff --git a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml b/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml deleted file mode 100644 index ebf794954e..0000000000 --- a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml +++ /dev/null @@ -1,203 +0,0 @@ - - - - - - - - - - - - - public AlignRCDConstruction(PlacementManager pMan) : base(pMan) { - var dependencies = IoCManager.Instance!; - _entityManager = dependencies.Resolve(); - _mapManager = dependencies.Resolve(); - _playerManager = dependencies.Resolve(); - _stateManager = dependencies.Resolve(); - + IoCManager.InjectDependencies(this); _mapSystem = _entityManager.System(); _rcdSystem = _entityManager.System(); _transformSystem = _entityManager.System(); diff --git a/Content.Client/RCD/RCDMenu.xaml.cs b/Content.Client/RCD/RCDMenu.xaml.cs index 51ec66ea44..3eb0397a69 100644 --- a/Content.Client/RCD/RCDMenu.xaml.cs +++ b/Content.Client/RCD/RCDMenu.xaml.cs @@ -68,7 +68,7 @@ public RCDMenu(EntityUid owner, RCDMenuBoundUserInterface bui) tooltip = Loc.GetString(entProto.Name); } - tooltip = char.ToUpper(tooltip[0]) + tooltip.Remove(0, 1); + tooltip = OopsConcat(char.ToUpper(tooltip[0]).ToString(), tooltip.Remove(0, 1)); var button = new RCDMenuButton() { @@ -119,6 +119,12 @@ public RCDMenu(EntityUid owner, RCDMenuBoundUserInterface bui) SendRCDSystemMessageAction += bui.SendRCDSystemMessage; } + private static string OopsConcat(string a, string b) + { + // This exists to prevent Roslyn being clever and compiling something that fails sandbox checks. + return a + b; + } + private void AddRCDMenuButtonOnClickActions(Control control) { var radialContainer = control as RadialContainer; diff --git a/Content.Client/RadialSelector/RadialSelectorMenuBUI.cs b/Content.Client/RadialSelector/RadialSelectorMenuBUI.cs new file mode 100644 index 0000000000..6b2a89f7a9 --- /dev/null +++ b/Content.Client/RadialSelector/RadialSelectorMenuBUI.cs @@ -0,0 +1,202 @@ +using System.Linq; +using System.Numerics; +using Content.Client.UserInterface.Controls; +using Content.Shared.Construction.Prototypes; +using Content.Shared.RadialSelector; +using JetBrains.Annotations; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Client.Input; +using Robust.Client.ResourceManagement; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Shared.Prototypes; + +// ReSharper disable InconsistentNaming + +namespace Content.Client.RadialSelector; + +[UsedImplicitly] +public sealed class RadialSelectorMenuBUI : BoundUserInterface +{ + [Dependency] private readonly IClyde _displayManager = default!; + [Dependency] private readonly IInputManager _inputManager = default!; + [Dependency] private readonly IResourceCache _resources = default!; + [Dependency] private readonly IPrototypeManager _protoManager = default!; + [Dependency] private readonly IEntityManager _entManager = default!; + + private readonly SpriteSystem _spriteSystem; + + private readonly RadialMenu _menu; + + // Used to clearing on state changing + private readonly HashSet _cachedContainers = new(); + + private bool _openCentered; + private readonly Vector2 ItemSize = Vector2.One * 64; + + public RadialSelectorMenuBUI(EntityUid owner, Enum uiKey) : base(owner, uiKey) + { + _spriteSystem = _entManager.System(); + _menu = new RadialMenu + { + HorizontalExpand = true, + VerticalExpand = true, + BackButtonStyleClass = "RadialMenuBackButton", + CloseButtonStyleClass = "RadialMenuCloseButton" + }; + } + + protected override void Open() + { + _menu.OnClose += Close; + + if (_openCentered) + _menu.OpenCentered(); + else + _menu.OpenCenteredAt(_inputManager.MouseScreenPosition.Position / _displayManager.ScreenSize); + } + + protected override void UpdateState(BoundUserInterfaceState state) + { + base.UpdateState(state); + + if (state is not RadialSelectorState radialSelectorState) + return; + + ClearExistingContainers(); + CreateMenu(radialSelectorState.Entries); + _openCentered = radialSelectorState.OpenCentered; + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (disposing) + _menu.Dispose(); + } + + private void CreateMenu(List entries, string parentCategory = "") + { + var container = new RadialContainer + { + Name = !string.IsNullOrEmpty(parentCategory) ? parentCategory : "Main", + Radius = 48f + 24f * MathF.Log(entries.Count), + }; + + _menu.AddChild(container); + _cachedContainers.Add(container); + + foreach (var entry in entries) + { + if (entry.Category != null) + { + var button = CreateButton(entry.Category.Name, _spriteSystem.Frame0(entry.Category.Icon)); + button.TargetLayer = entry.Category.Name; + CreateMenu(entry.Category.Entries, entry.Category.Name); + container.AddChild(button); + } + else if (entry.Prototype != null) + { + var name = GetName(entry.Prototype); + var icon = GetTextures(entry); + var button = CreateButton(name, icon); + button.OnButtonUp += _ => + { + var msg = new RadialSelectorSelectedMessage(entry.Prototype); + SendPredictedMessage(msg); + }; + + container.AddChild(button); + } + } + } + + private string GetName(string proto) + { + if (_protoManager.TryIndex(proto, out var prototype)) + return prototype.Name; + + if (_protoManager.TryIndex(proto, out ConstructionPrototype? constructionPrototype)) + return constructionPrototype.Name; + + return proto; + } + + private List GetTextures(RadialSelectorEntry entry) + { + var result = new List(); + if (entry.Icon is not null) + { + result.Add(_spriteSystem.Frame0(entry.Icon)); + return result; + } + + if (_protoManager.TryIndex(entry.Prototype!, out var prototype)) + { + result.AddRange(SpriteComponent.GetPrototypeTextures(prototype, _resources).Select(o => o.Default)); + return result; + } + + if (_protoManager.TryIndex(entry.Prototype!, out ConstructionPrototype? constructionProto)) + { + result.Add(_spriteSystem.Frame0(constructionProto.Icon)); + return result; + } + + // No icons provided and no icons found in prototypes. There's nothing we can do. + return result; + } + + private RadialMenuTextureButton CreateButton(string name, Texture icon) + { + var button = new RadialMenuTextureButton + { + ToolTip = Loc.GetString(name), + StyleClasses = { "RadialMenuButton" }, + SetSize = ItemSize + }; + + var iconScale = ItemSize / icon.Size; + var texture = new TextureRect + { + VerticalAlignment = Control.VAlignment.Center, + HorizontalAlignment = Control.HAlignment.Center, + Texture = icon, + TextureScale = iconScale + }; + + button.AddChild(texture); + return button; + } + + private RadialMenuTextureButton CreateButton(string name, List icons) + { + var button = new RadialMenuTextureButton + { + ToolTip = Loc.GetString(name), + StyleClasses = { "RadialMenuButton" }, + SetSize = ItemSize + }; + + var iconScale = ItemSize / icons[0].Size; + var texture = new LayeredTextureRect + { + VerticalAlignment = Control.VAlignment.Center, + HorizontalAlignment = Control.HAlignment.Center, + Textures = icons, + TextureScale = iconScale + }; + + button.AddChild(texture); + return button; + } + + private void ClearExistingContainers() + { + foreach (var container in _cachedContainers) + _menu.RemoveChild(container); + + _cachedContainers.Clear(); + } +} diff --git a/Content.Client/Replay/Spectator/ReplaySpectatorSystem.Position.cs b/Content.Client/Replay/Spectator/ReplaySpectatorSystem.Position.cs index 2ee7e30ec9..d00e319eed 100644 --- a/Content.Client/Replay/Spectator/ReplaySpectatorSystem.Position.cs +++ b/Content.Client/Replay/Spectator/ReplaySpectatorSystem.Position.cs @@ -195,9 +195,16 @@ private void OnParentChanged(EntityUid uid, ReplaySpectatorComponent component, if (uid != _player.LocalEntity) return; - if (args.Transform.MapUid != null || args.OldMapId == MapId.Nullspace) + if (args.Transform.MapUid != null || args.OldMapId == null) return; + if (_spectatorData != null) + { + // Currently scrubbing/setting the replay tick + // the observer will get respawned once the state was applied + return; + } + // The entity being spectated from was moved to null-space. // This was probably because they were spectating some entity in a client-side replay that left PVS range. // Simple respawn the ghost. diff --git a/Content.Client/Robotics/Systems/RoboticsConsoleSystem.cs b/Content.Client/Robotics/Systems/RoboticsConsoleSystem.cs new file mode 100644 index 0000000000..0219c965cd --- /dev/null +++ b/Content.Client/Robotics/Systems/RoboticsConsoleSystem.cs @@ -0,0 +1,7 @@ +using Content.Shared.Robotics.Systems; + +namespace Content.Client.Robotics.Systems; + +public sealed class RoboticsConsoleSystem : SharedRoboticsConsoleSystem +{ +} diff --git a/Content.Client/Robotics/UI/RoboticsConsoleBoundUserInterface.cs b/Content.Client/Robotics/UI/RoboticsConsoleBoundUserInterface.cs new file mode 100644 index 0000000000..6185979eee --- /dev/null +++ b/Content.Client/Robotics/UI/RoboticsConsoleBoundUserInterface.cs @@ -0,0 +1,50 @@ +using Content.Shared.Robotics; +using Robust.Client.GameObjects; + +namespace Content.Client.Robotics.UI; + +public sealed class RoboticsConsoleBoundUserInterface : BoundUserInterface +{ + [ViewVariables] + public RoboticsConsoleWindow _window = default!; + + public RoboticsConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) + { + } + + protected override void Open() + { + base.Open(); + + _window = new RoboticsConsoleWindow(Owner); + _window.OnDisablePressed += address => + { + SendMessage(new RoboticsConsoleDisableMessage(address)); + }; + _window.OnDestroyPressed += address => + { + SendMessage(new RoboticsConsoleDestroyMessage(address)); + }; + _window.OnClose += Close; + + _window.OpenCentered(); + } + + protected override void UpdateState(BoundUserInterfaceState state) + { + base.UpdateState(state); + + if (state is not RoboticsConsoleState cast) + return; + + _window?.UpdateState(cast); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + _window?.Dispose(); + } +} diff --git a/Content.Client/Robotics/UI/RoboticsConsoleWindow.xaml b/Content.Client/Robotics/UI/RoboticsConsoleWindow.xaml new file mode 100644 index 0000000000..a3b3978790 --- /dev/null +++ b/Content.Client/Robotics/UI/RoboticsConsoleWindow.xaml @@ -0,0 +1,40 @@ + + + + + + + + + + /// /// - private List GetViewportMapObjects(Matrix3 matty, List mapObjects) + private List GetViewportMapObjects(Matrix3x2 matty, List mapObjects) { var results = new List(); var enlargement = new Vector2i((int) (16 * UIScale), (int) (16 * UIScale)); @@ -217,7 +217,7 @@ private List GetViewportMapObjects(Matrix3 matty, List m var mapCoords = _shuttles.GetMapCoordinates(mapObj); - var relativePos = matty.Transform(mapCoords.Position); + var relativePos = Vector2.Transform(mapCoords.Position, matty); relativePos = relativePos with { Y = -relativePos.Y }; var uiPosition = ScalePosition(relativePos); @@ -250,7 +250,7 @@ protected override void Draw(DrawingHandleScreen handle) DrawParallax(handle); var viewedMapUid = _mapManager.GetMapEntityId(ViewingMap); - var matty = Matrix3.CreateInverseTransform(Offset, Angle.Zero); + var matty = Matrix3Helpers.CreateInverseTransform(Offset, Angle.Zero); var realTime = _timing.RealTime; var viewBox = new Box2(Offset - WorldRangeVector, Offset + WorldRangeVector); var viewportObjects = GetViewportMapObjects(matty, mapObjects); @@ -267,7 +267,7 @@ protected override void Draw(DrawingHandleScreen handle) var (gridPos, gridRot) = _xformSystem.GetWorldPositionRotation(shuttleXform); gridPos = Maps.GetGridPosition((gridUid, gridPhysics), gridPos, gridRot); - var gridRelativePos = matty.Transform(gridPos); + var gridRelativePos = Vector2.Transform(gridPos, matty); gridRelativePos = gridRelativePos with { Y = -gridRelativePos.Y }; var gridUiPos = ScalePosition(gridRelativePos); @@ -296,7 +296,7 @@ protected override void Draw(DrawingHandleScreen handle) continue; } - var adjustedPos = matty.Transform(mapCoords.Position); + var adjustedPos = Vector2.Transform(mapCoords.Position, matty); var localPos = ScalePosition(adjustedPos with { Y = -adjustedPos.Y}); handle.DrawCircle(localPos, exclusion.Range * MinimapScale, exclusionColor.WithAlpha(0.05f)); handle.DrawCircle(localPos, exclusion.Range * MinimapScale, exclusionColor, filled: false); @@ -319,7 +319,7 @@ protected override void Draw(DrawingHandleScreen handle) foreach (var (beaconName, coords, mapO) in GetBeacons(viewportObjects, matty, controlLocalBounds)) { - var localPos = matty.Transform(coords.Position); + var localPos = Vector2.Transform(coords.Position, matty); localPos = localPos with { Y = -localPos.Y }; var beaconUiPos = ScalePosition(localPos); var mapObject = GetMapObject(localPos, Angle.Zero, scale: 0.75f, scalePosition: true); @@ -360,7 +360,7 @@ protected override void Draw(DrawingHandleScreen handle) var (gridPos, gridRot) = _xformSystem.GetWorldPositionRotation(grid.Owner); gridPos = Maps.GetGridPosition((grid, gridPhysics), gridPos, gridRot); - var gridRelativePos = matty.Transform(gridPos); + var gridRelativePos = Vector2.Transform(gridPos, matty); gridRelativePos = gridRelativePos with { Y = -gridRelativePos.Y }; var gridUiPos = ScalePosition(gridRelativePos); @@ -439,7 +439,7 @@ protected override void Draw(DrawingHandleScreen handle) var color = ftlFree ? Color.LimeGreen : Color.Magenta; - var gridRelativePos = matty.Transform(gridPos); + var gridRelativePos = Vector2.Transform(gridPos, matty); gridRelativePos = gridRelativePos with { Y = -gridRelativePos.Y }; var gridUiPos = ScalePosition(gridRelativePos); @@ -512,7 +512,7 @@ private void AddMapObject(List edges, List verts, ValueList /// Returns the beacons that intersect the viewport. /// - private IEnumerable<(string Beacon, MapCoordinates Coordinates, IMapObject MapObject)> GetBeacons(List mapObjs, Matrix3 mapTransform, UIBox2i area) + private IEnumerable<(string Beacon, MapCoordinates Coordinates, IMapObject MapObject)> GetBeacons(List mapObjs, Matrix3x2 mapTransform, UIBox2i area) { foreach (var mapO in mapObjs) { @@ -520,7 +520,7 @@ private void AddMapObject(List edges, List verts, ValueList GetMapObject(Vector2 localPos, Angle angle, float sca return mapObj; } - private bool TryGetBeacon(IEnumerable mapObjects, Matrix3 mapTransform, Vector2 mousePos, UIBox2i area, out ShuttleBeaconObject foundBeacon, out Vector2 foundLocalPos) + private bool TryGetBeacon(IEnumerable mapObjects, Matrix3x2 mapTransform, Vector2 mousePos, UIBox2i area, out ShuttleBeaconObject foundBeacon, out Vector2 foundLocalPos) { // In pixels const float BeaconSnapRange = 32f; @@ -579,7 +579,7 @@ private bool TryGetBeacon(IEnumerable mapObjects, Matrix3 mapTransfo if (!_shuttles.CanFTLBeacon(beaconObj.Coordinates)) continue; - var position = mapTransform.Transform(beaconCoords.Position); + var position = Vector2.Transform(beaconCoords.Position, mapTransform); var localPos = ScalePosition(position with {Y = -position.Y}); // If beacon not on screen then ignore it. diff --git a/Content.Client/Shuttles/UI/ShuttleNavControl.xaml.cs b/Content.Client/Shuttles/UI/ShuttleNavControl.xaml.cs index 00ee6890b2..0b8720add2 100644 --- a/Content.Client/Shuttles/UI/ShuttleNavControl.xaml.cs +++ b/Content.Client/Shuttles/UI/ShuttleNavControl.xaml.cs @@ -137,10 +137,10 @@ protected override void Draw(DrawingHandleScreen handle) var mapPos = _transform.ToMapCoordinates(_coordinates.Value); var offset = _coordinates.Value.Position; - var posMatrix = Matrix3.CreateTransform(offset, _rotation.Value); + var posMatrix = Matrix3Helpers.CreateTransform(offset, _rotation.Value); var (_, ourEntRot, ourEntMatrix) = _transform.GetWorldPositionRotationMatrix(_coordinates.Value.EntityId); - Matrix3.Multiply(posMatrix, ourEntMatrix, out var ourWorldMatrix); - var ourWorldMatrixInvert = ourWorldMatrix.Invert(); + var ourWorldMatrix = Matrix3x2.Multiply(posMatrix, ourEntMatrix); + Matrix3x2.Invert(ourWorldMatrix, out var ourWorldMatrixInvert); // Draw our grid in detail var ourGridId = xform.GridUid; @@ -148,7 +148,7 @@ protected override void Draw(DrawingHandleScreen handle) fixturesQuery.HasComponent(ourGridId.Value)) { var ourGridMatrix = _transform.GetWorldMatrix(ourGridId.Value); - Matrix3.Multiply(in ourGridMatrix, in ourWorldMatrixInvert, out var matrix); + var matrix = Matrix3x2.Multiply(ourGridMatrix, ourWorldMatrixInvert); var color = _shuttles.GetIFFColor(ourGridId.Value, self: true); DrawGrid(handle, matrix, (ourGridId.Value, ourGrid), color); @@ -194,7 +194,7 @@ protected override void Draw(DrawingHandleScreen handle) continue; var gridMatrix = _transform.GetWorldMatrix(gUid); - Matrix3.Multiply(in gridMatrix, in ourWorldMatrixInvert, out var matty); + var matty = Matrix3x2.Multiply(gridMatrix, ourWorldMatrixInvert); var color = _shuttles.GetIFFColor(grid, self: false, iff); // Others default: @@ -207,7 +207,7 @@ protected override void Draw(DrawingHandleScreen handle) { var gridBounds = grid.Comp.LocalAABB; - var gridCentre = matty.Transform(gridBody.LocalCenter); + var gridCentre = Vector2.Transform(gridBody.LocalCenter, matty); gridCentre.Y = -gridCentre.Y; var distance = gridCentre.Length(); var labelText = Loc.GetString("shuttle-console-iff-label", ("name", labelName), @@ -242,7 +242,7 @@ protected override void Draw(DrawingHandleScreen handle) } } - private void DrawDocks(DrawingHandleScreen handle, EntityUid uid, Matrix3 matrix) + private void DrawDocks(DrawingHandleScreen handle, EntityUid uid, Matrix3x2 matrix) { if (!ShowDocks) return; @@ -255,7 +255,7 @@ private void DrawDocks(DrawingHandleScreen handle, EntityUid uid, Matrix3 matrix foreach (var state in docks) { var position = state.Coordinates.Position; - var uiPosition = matrix.Transform(position); + var uiPosition = Vector2.Transform(position, matrix); if (uiPosition.Length() > (WorldRange * 2f) - DockScale) continue; @@ -264,10 +264,10 @@ private void DrawDocks(DrawingHandleScreen handle, EntityUid uid, Matrix3 matrix var verts = new[] { - matrix.Transform(position + new Vector2(-DockScale, -DockScale)), - matrix.Transform(position + new Vector2(DockScale, -DockScale)), - matrix.Transform(position + new Vector2(DockScale, DockScale)), - matrix.Transform(position + new Vector2(-DockScale, DockScale)), + Vector2.Transform(position + new Vector2(-DockScale, -DockScale), matrix), + Vector2.Transform(position + new Vector2(DockScale, -DockScale), matrix), + Vector2.Transform(position + new Vector2(DockScale, DockScale), matrix), + Vector2.Transform(position + new Vector2(-DockScale, DockScale), matrix), }; for (var i = 0; i < verts.Length; i++) diff --git a/Content.Client/Standing/LayingDownSystem.cs b/Content.Client/Standing/LayingDownSystem.cs new file mode 100644 index 0000000000..d45d481134 --- /dev/null +++ b/Content.Client/Standing/LayingDownSystem.cs @@ -0,0 +1,89 @@ +using Content.Shared.Buckle; +using Content.Shared.Rotation; +using Content.Shared.Standing; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Shared.Configuration; +using Robust.Shared.Timing; + +namespace Content.Client.Standing; + +public sealed class LayingDownSystem : SharedLayingDownSystem +{ + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly IEyeManager _eyeManager = default!; + [Dependency] private readonly StandingStateSystem _standing = default!; + [Dependency] private readonly AnimationPlayerSystem _animation = default!; + [Dependency] private readonly SharedBuckleSystem _buckle = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnMovementInput); + SubscribeNetworkEvent(OnCheckAutoGetUp); + } + + public override void Update(float frameTime) + { + // Update draw depth of laying down entities as necessary + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var layingDown, out var standing, out var sprite)) + { + // Do not modify the entities draw depth if it's modified externally + if (sprite.DrawDepth != layingDown.NormalDrawDepth && sprite.DrawDepth != layingDown.CrawlingUnderDrawDepth) + continue; + + sprite.DrawDepth = standing.CurrentState is StandingState.Lying && layingDown.IsCrawlingUnder + ? layingDown.CrawlingUnderDrawDepth + : layingDown.NormalDrawDepth; + } + + query.Dispose(); + } + + private void OnMovementInput(EntityUid uid, LayingDownComponent component, MoveEvent args) + { + if (!_timing.IsFirstTimePredicted + || !_standing.IsDown(uid) + || _buckle.IsBuckled(uid) + || _animation.HasRunningAnimation(uid, "rotate") + || !TryComp(uid, out var transform) + || !TryComp(uid, out var sprite) + || !TryComp(uid, out var rotationVisuals)) + return; + + var rotation = transform.LocalRotation + (_eyeManager.CurrentEye.Rotation - (transform.LocalRotation - transform.WorldRotation)); + + if (rotation.GetDir() is Direction.SouthEast or Direction.East or Direction.NorthEast or Direction.North) + { + rotationVisuals.HorizontalRotation = Angle.FromDegrees(270); + sprite.Rotation = Angle.FromDegrees(270); + return; + } + + rotationVisuals.HorizontalRotation = Angle.FromDegrees(90); + sprite.Rotation = Angle.FromDegrees(90); + } + + private void OnCheckAutoGetUp(CheckAutoGetUpEvent ev, EntitySessionEventArgs args) + { + if (!_timing.IsFirstTimePredicted) + return; + + var uid = GetEntity(ev.User); + + if (!TryComp(uid, out var transform) || !TryComp(uid, out var rotationVisuals)) + return; + + var rotation = transform.LocalRotation + (_eyeManager.CurrentEye.Rotation - (transform.LocalRotation - transform.WorldRotation)); + + if (rotation.GetDir() is Direction.SouthEast or Direction.East or Direction.NorthEast or Direction.North) + { + rotationVisuals.HorizontalRotation = Angle.FromDegrees(270); + return; + } + + rotationVisuals.HorizontalRotation = Angle.FromDegrees(90); + } +} diff --git a/Content.Client/Station/StationSpawningSystem.cs b/Content.Client/Station/StationSpawningSystem.cs index 65da518d22..71dce5a78f 100644 --- a/Content.Client/Station/StationSpawningSystem.cs +++ b/Content.Client/Station/StationSpawningSystem.cs @@ -2,7 +2,4 @@ namespace Content.Client.Station; -public sealed class StationSpawningSystem : SharedStationSpawningSystem -{ - -} +public sealed class StationSpawningSystem : SharedStationSpawningSystem; diff --git a/Content.Client/StatusIcon/StatusIconOverlay.cs b/Content.Client/StatusIcon/StatusIconOverlay.cs index f8381afdbe..372bd04f57 100644 --- a/Content.Client/StatusIcon/StatusIconOverlay.cs +++ b/Content.Client/StatusIcon/StatusIconOverlay.cs @@ -39,8 +39,8 @@ protected override void Draw(in OverlayDrawArgs args) var eyeRot = args.Viewport.Eye?.Rotation ?? default; var xformQuery = _entity.GetEntityQuery(); - var scaleMatrix = Matrix3.CreateScale(new Vector2(1, 1)); - var rotationMatrix = Matrix3.CreateRotation(-eyeRot); + var scaleMatrix = Matrix3Helpers.CreateScale(new Vector2(1, 1)); + var rotationMatrix = Matrix3Helpers.CreateRotation(-eyeRot); var query = _entity.AllEntityQueryEnumerator(); while (query.MoveNext(out var uid, out var comp, out var sprite, out var xform, out var meta)) @@ -59,9 +59,9 @@ protected override void Draw(in OverlayDrawArgs args) if (icons.Count == 0) continue; - var worldMatrix = Matrix3.CreateTranslation(worldPos); - Matrix3.Multiply(scaleMatrix, worldMatrix, out var scaledWorld); - Matrix3.Multiply(rotationMatrix, scaledWorld, out var matty); + var worldMatrix = Matrix3Helpers.CreateTranslation(worldPos); + var scaledWorld = Matrix3x2.Multiply(scaleMatrix, worldMatrix); + var matty = Matrix3x2.Multiply(rotationMatrix, scaledWorld); handle.SetTransform(matty); var countL = 0; diff --git a/Content.Client/Storage/Components/StorageContainerVisualsComponent.cs b/Content.Client/Storage/Components/StorageContainerVisualsComponent.cs index 9f07867da8..9ef6c65e89 100644 --- a/Content.Client/Storage/Components/StorageContainerVisualsComponent.cs +++ b/Content.Client/Storage/Components/StorageContainerVisualsComponent.cs @@ -1,4 +1,5 @@ using Content.Client.Chemistry.Visualizers; +using Content.Shared.Chemistry.Components; namespace Content.Client.Storage.Components; diff --git a/Content.Client/Storage/StorageBoundUserInterface.cs b/Content.Client/Storage/StorageBoundUserInterface.cs index f7fdbb8367..899df30f7f 100644 --- a/Content.Client/Storage/StorageBoundUserInterface.cs +++ b/Content.Client/Storage/StorageBoundUserInterface.cs @@ -17,6 +17,14 @@ public StorageBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKe _storage = _entManager.System(); } + protected override void Open() + { + base.Open(); + + if (_entManager.TryGetComponent(Owner, out var comp)) + _storage.OpenStorageWindow((Owner, comp)); + } + protected override void Dispose(bool disposing) { base.Dispose(disposing); @@ -25,16 +33,5 @@ protected override void Dispose(bool disposing) _storage.CloseStorageWindow(Owner); } - - protected override void ReceiveMessage(BoundUserInterfaceMessage message) - { - base.ReceiveMessage(message); - - if (message is StorageModifyWindowMessage) - { - if (_entManager.TryGetComponent(Owner, out var comp)) - _storage.OpenStorageWindow((Owner, comp)); - } - } } diff --git a/Content.Client/Storage/Systems/StorageSystem.cs b/Content.Client/Storage/Systems/StorageSystem.cs index ce0a6bf1ca..b80a855f98 100644 --- a/Content.Client/Storage/Systems/StorageSystem.cs +++ b/Content.Client/Storage/Systems/StorageSystem.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System.Linq; +using System.Numerics; using Content.Client.Animations; using Content.Shared.Hands; using Content.Shared.Storage; @@ -26,7 +27,7 @@ public override void Initialize() SubscribeLocalEvent(OnShutdown); SubscribeNetworkEvent(HandlePickupAnimation); - SubscribeNetworkEvent(HandleAnimatingInsertingEntities); + SubscribeAllEvent(HandleAnimatingInsertingEntities); } public override void UpdateUI(Entity entity) @@ -111,7 +112,7 @@ private void CloseStorageBoundUserInterface(Entity enti if (!Resolve(entity, ref entity.Comp, false)) return; - if (entity.Comp.OpenInterfaces.GetValueOrDefault(StorageComponent.StorageUiKey.Key) is not { } bui) + if (entity.Comp.ClientOpenInterfaces.GetValueOrDefault(StorageComponent.StorageUiKey.Key) is not { } bui) return; bui.Close(); @@ -149,7 +150,7 @@ public void PickupAnimation(EntityUid item, EntityCoordinates initialCoords, Ent } var finalMapPos = finalCoords.ToMapPos(EntityManager, TransformSystem); - var finalPos = TransformSystem.GetInvWorldMatrix(initialCoords.EntityId).Transform(finalMapPos); + var finalPos = Vector2.Transform(finalMapPos, TransformSystem.GetInvWorldMatrix(initialCoords.EntityId)); _entityPickupAnimation.AnimateEntityPickup(item, initialCoords, finalPos, initialAngle); } diff --git a/Content.Client/Store/Ui/StoreMenu.xaml.cs b/Content.Client/Store/Ui/StoreMenu.xaml.cs index b7a2c285fe..7eb597f2f3 100644 --- a/Content.Client/Store/Ui/StoreMenu.xaml.cs +++ b/Content.Client/Store/Ui/StoreMenu.xaml.cs @@ -3,6 +3,7 @@ using Content.Client.Message; using Content.Shared.FixedPoint; using Content.Shared.Store; +using Content.Client.Stylesheets; using Robust.Client.AutoGenerated; using Robust.Client.GameObjects; using Robust.Client.Graphics; @@ -147,6 +148,10 @@ private void AddListingGui(ListingData listing) } var newListing = new StoreListingControl(listing, GetListingPriceString(listing), hasBalance, texture); + + if (listing.DiscountValue > 0) + newListing.StoreItemBuyButton.AddStyleClass(StyleNano.ButtonColorDangerDefault.ToString()); + newListing.StoreItemBuyButton.OnButtonDown += args => OnListingButtonPressed?.Invoke(args, listing); diff --git a/Content.Client/Strip/StrippableSystem.cs b/Content.Client/Strip/StrippableSystem.cs index c5083d2204..23f38e9d51 100644 --- a/Content.Client/Strip/StrippableSystem.cs +++ b/Content.Client/Strip/StrippableSystem.cs @@ -35,7 +35,7 @@ public void UpdateUi(EntityUid uid, StrippableComponent? component = null, Entit if (!TryComp(uid, out UserInterfaceComponent? uiComp)) return; - foreach (var ui in uiComp.OpenInterfaces.Values) + foreach (var ui in uiComp.ClientOpenInterfaces.Values) { if (ui is StrippableBoundUserInterface stripUi) stripUi.DirtyMenu(); diff --git a/Content.Client/Stylesheets/StyleBase.cs b/Content.Client/Stylesheets/StyleBase.cs index 048d5602a2..638ef84c8d 100644 --- a/Content.Client/Stylesheets/StyleBase.cs +++ b/Content.Client/Stylesheets/StyleBase.cs @@ -1,5 +1,6 @@ using System.Numerics; using Content.Client.Resources; +using Content.Client.UserInterface.Controls; using Robust.Client.Graphics; using Robust.Client.ResourceManagement; using Robust.Client.UserInterface; @@ -25,6 +26,7 @@ public abstract class StyleBase public const string ButtonSquare = "ButtonSquare"; public const string ButtonCaution = "Caution"; + public const string ButtonDanger = "Danger"; public const int DefaultGrabberSize = 10; diff --git a/Content.Client/Stylesheets/StyleNano.cs b/Content.Client/Stylesheets/StyleNano.cs index a10e3eb592..b2d2bf3b48 100644 --- a/Content.Client/Stylesheets/StyleNano.cs +++ b/Content.Client/Stylesheets/StyleNano.cs @@ -98,12 +98,17 @@ public sealed class StyleNano : StyleBase public static readonly Color ButtonColorHovered = Color.FromHex("#575b61"); public static readonly Color ButtonColorHoveredRed = Color.FromHex("#DF6B6B"); public static readonly Color ButtonColorPressed = Color.FromHex("#3e6c45"); - public static readonly Color ButtonColorDisabled = Color.FromHex("#303133"); + public static readonly Color ButtonColorDisabled = Color.FromHex("#292929"); - public static readonly Color ButtonColorCautionDefault = Color.FromHex("#ab3232"); - public static readonly Color ButtonColorCautionHovered = Color.FromHex("#cf2f2f"); - public static readonly Color ButtonColorCautionPressed = Color.FromHex("#3e6c45"); - public static readonly Color ButtonColorCautionDisabled = Color.FromHex("#602a2a"); + public static readonly Color ButtonColorCautionDefault = Color.FromHex("#8F6A33"); + public static readonly Color ButtonColorCautionHovered = Color.FromHex("#C0934E"); + public static readonly Color ButtonColorCautionPressed = Color.FromHex("#E49F35"); + public static readonly Color ButtonColorCautionDisabled = Color.FromHex("#28251F"); + + public static readonly Color ButtonColorDangerDefault = Color.FromHex("#7B2D2D"); + public static readonly Color ButtonColorDangerHovered = Color.FromHex("#BD524B"); + public static readonly Color ButtonColorDangerPressed = Color.FromHex("#C12525"); + public static readonly Color ButtonColorDangerDisabled = Color.FromHex("#2F2020"); public static readonly Color ButtonColorGoodDefault = Color.FromHex("#3E6C45"); public static readonly Color ButtonColorGoodHovered = Color.FromHex("#31843E"); @@ -136,6 +141,8 @@ public sealed class StyleNano : StyleBase public const string StyleClassPowerStateGood = "PowerStateGood"; public const string StyleClassItemStatus = "ItemStatus"; + public const string StyleClassItemStatusNotHeld = "ItemStatusNotHeld"; + public static readonly Color ItemStatusNotHeldColor = Color.Gray; //Background public const string StyleClassBackgroundBaseDark = "PanelBackgroundBaseDark"; @@ -660,22 +667,39 @@ public StyleNano(IResourceCache resCache) : base(resCache) .Pseudo(ContainerButton.StylePseudoClassDisabled) .Prop(Control.StylePropertyModulateSelf, ButtonColorCautionDisabled), + // Colors for the danger buttons. + Element().Class(ContainerButton.StyleClassButton).Class(ButtonDanger) + .Pseudo(ContainerButton.StylePseudoClassNormal) + .Prop(Control.StylePropertyModulateSelf, ButtonColorDangerDefault), + + Element().Class(ContainerButton.StyleClassButton).Class(ButtonDanger) + .Pseudo(ContainerButton.StylePseudoClassHover) + .Prop(Control.StylePropertyModulateSelf, ButtonColorDangerHovered), + + Element().Class(ContainerButton.StyleClassButton).Class(ButtonDanger) + .Pseudo(ContainerButton.StylePseudoClassPressed) + .Prop(Control.StylePropertyModulateSelf, ButtonColorDangerPressed), + + Element().Class(ContainerButton.StyleClassButton).Class(ButtonDanger) + .Pseudo(ContainerButton.StylePseudoClassDisabled) + .Prop(Control.StylePropertyModulateSelf, ButtonColorDangerDisabled), + // Colors for confirm buttons confirm states. Element() .Pseudo(ConfirmButton.ConfirmPrefix + ContainerButton.StylePseudoClassNormal) - .Prop(Control.StylePropertyModulateSelf, ButtonColorCautionDefault), + .Prop(Control.StylePropertyModulateSelf, ButtonColorDangerDefault), Element() .Pseudo(ConfirmButton.ConfirmPrefix + ContainerButton.StylePseudoClassHover) - .Prop(Control.StylePropertyModulateSelf, ButtonColorCautionHovered), + .Prop(Control.StylePropertyModulateSelf, ButtonColorDangerHovered), Element() .Pseudo(ConfirmButton.ConfirmPrefix + ContainerButton.StylePseudoClassPressed) - .Prop(Control.StylePropertyModulateSelf, ButtonColorCautionPressed), + .Prop(Control.StylePropertyModulateSelf, ButtonColorDangerPressed), Element() .Pseudo(ConfirmButton.ConfirmPrefix + ContainerButton.StylePseudoClassDisabled) - .Prop(Control.StylePropertyModulateSelf, ButtonColorCautionDisabled), + .Prop(Control.StylePropertyModulateSelf, ButtonColorDangerDisabled), new StyleRule(new SelectorChild( new SelectorElement(typeof(Button), null, null, new[] {ContainerButton.StylePseudoClassDisabled}), @@ -734,19 +758,19 @@ public StyleNano(IResourceCache resCache) : base(resCache) Element().Class(ConfirmationMenuElement.StyleClassConfirmationContextMenuButton) .Pseudo(ContainerButton.StylePseudoClassNormal) - .Prop(Control.StylePropertyModulateSelf, ButtonColorCautionDefault), + .Prop(Control.StylePropertyModulateSelf, ButtonColorDangerDefault), Element().Class(ConfirmationMenuElement.StyleClassConfirmationContextMenuButton) .Pseudo(ContainerButton.StylePseudoClassHover) - .Prop(Control.StylePropertyModulateSelf, ButtonColorCautionHovered), + .Prop(Control.StylePropertyModulateSelf, ButtonColorDangerHovered), Element().Class(ConfirmationMenuElement.StyleClassConfirmationContextMenuButton) .Pseudo(ContainerButton.StylePseudoClassPressed) - .Prop(Control.StylePropertyModulateSelf, ButtonColorCautionPressed), + .Prop(Control.StylePropertyModulateSelf, ButtonColorDangerPressed), Element().Class(ConfirmationMenuElement.StyleClassConfirmationContextMenuButton) .Pseudo(ContainerButton.StylePseudoClassDisabled) - .Prop(Control.StylePropertyModulateSelf, ButtonColorCautionDisabled), + .Prop(Control.StylePropertyModulateSelf, ButtonColorDangerDisabled), // Examine buttons Element().Class(ExamineButton.StyleClassExamineButton) @@ -1234,6 +1258,16 @@ public StyleNano(IResourceCache resCache) : base(resCache) new StyleProperty("font", notoSans10), }), + Element() + .Class(StyleClassItemStatusNotHeld) + .Prop("font", notoSansItalic10) + .Prop("font-color", ItemStatusNotHeldColor), + + Element() + .Class(StyleClassItemStatus) + .Prop(nameof(RichTextLabel.LineHeightScale), 0.7f) + .Prop(nameof(Control.Margin), new Thickness(0, 0, 0, -6)), + // Slider new StyleRule(SelectorElement.Type(typeof(Slider)), new [] { @@ -1377,6 +1411,17 @@ public StyleNano(IResourceCache resCache) : base(resCache) Element().Class("WindowHeadingBackgroundLight") .Prop("panel", new StyleBoxTexture(BaseButtonOpenLeft) { Padding = default }), + // Window Header Help Button + Element().Class(FancyWindow.StyleClassWindowHelpButton) + .Prop(TextureButton.StylePropertyTexture, resCache.GetTexture("/Textures/Interface/Nano/help.png")) + .Prop(Control.StylePropertyModulateSelf, Color.FromHex("#4B596A")), + + Element().Class(FancyWindow.StyleClassWindowHelpButton).Pseudo(ContainerButton.StylePseudoClassHover) + .Prop(Control.StylePropertyModulateSelf, Color.FromHex("#7F3636")), + + Element().Class(FancyWindow.StyleClassWindowHelpButton).Pseudo(ContainerButton.StylePseudoClassPressed) + .Prop(Control.StylePropertyModulateSelf, Color.FromHex("#753131")), + //The lengths you have to go through to change a background color smh Element().Class("PanelBackgroundBaseDark") .Prop("panel", new StyleBoxTexture(BaseButtonOpenBoth) { Padding = default }) @@ -1576,6 +1621,60 @@ public StyleNano(IResourceCache resCache) : base(resCache) { BackgroundColor = FancyTreeSelectedRowColor, }), + // Shitmed Edit Start + Element().Class("TargetDollButtonHead") + .Pseudo(TextureButton.StylePseudoClassHover) + .Prop(TextureButton.StylePropertyTexture, resCache.GetTexture("/Textures/Interface/Targeting/Doll/head_hover.png")), + + Element().Class("TargetDollButtonChest") + .Pseudo(TextureButton.StylePseudoClassHover) + .Prop(TextureButton.StylePropertyTexture, resCache.GetTexture("/Textures/Interface/Targeting/Doll/torso_hover.png")), + + Element().Class("TargetDollButtonGroin") + .Pseudo(TextureButton.StylePseudoClassHover) + .Prop(TextureButton.StylePropertyTexture, resCache.GetTexture("/Textures/Interface/Targeting/Doll/groin_hover.png")), + + Element().Class("TargetDollButtonLeftArm") + .Pseudo(TextureButton.StylePseudoClassHover) + .Prop(TextureButton.StylePropertyTexture, resCache.GetTexture("/Textures/Interface/Targeting/Doll/leftarm_hover.png")), + + Element().Class("TargetDollButtonLeftHand") + .Pseudo(TextureButton.StylePseudoClassHover) + .Prop(TextureButton.StylePropertyTexture, resCache.GetTexture("/Textures/Interface/Targeting/Doll/lefthand_hover.png")), + + Element().Class("TargetDollButtonRightArm") + .Pseudo(TextureButton.StylePseudoClassHover) + .Prop(TextureButton.StylePropertyTexture, resCache.GetTexture("/Textures/Interface/Targeting/Doll/rightarm_hover.png")), + + Element().Class("TargetDollButtonRightHand") + .Pseudo(TextureButton.StylePseudoClassHover) + .Prop(TextureButton.StylePropertyTexture, resCache.GetTexture("/Textures/Interface/Targeting/Doll/righthand_hover.png")), + + Element().Class("TargetDollButtonLeftLeg") + .Pseudo(TextureButton.StylePseudoClassHover) + .Prop(TextureButton.StylePropertyTexture, resCache.GetTexture("/Textures/Interface/Targeting/Doll/leftleg_hover.png")), + + Element().Class("TargetDollButtonLeftFoot") + .Pseudo(TextureButton.StylePseudoClassHover) + .Prop(TextureButton.StylePropertyTexture, resCache.GetTexture("/Textures/Interface/Targeting/Doll/leftfoot_hover.png")), + + Element().Class("TargetDollButtonRightLeg") + .Pseudo(TextureButton.StylePseudoClassHover) + .Prop(TextureButton.StylePropertyTexture, resCache.GetTexture("/Textures/Interface/Targeting/Doll/rightleg_hover.png")), + + Element().Class("TargetDollButtonRightFoot") + .Pseudo(TextureButton.StylePseudoClassHover) + .Prop(TextureButton.StylePropertyTexture, resCache.GetTexture("/Textures/Interface/Targeting/Doll/rightfoot_hover.png")), + + Element().Class("TargetDollButtonEyes") + .Pseudo(TextureButton.StylePseudoClassHover) + .Prop(TextureButton.StylePropertyTexture, resCache.GetTexture("/Textures/Interface/Targeting/Doll/eyes_hover.png")), + + Element().Class("TargetDollButtonMouth") + .Pseudo(TextureButton.StylePseudoClassHover) + .Prop(TextureButton.StylePropertyTexture, resCache.GetTexture("/Textures/Interface/Targeting/Doll/mouth_hover.png")), + // Shitmed Edit End + }).ToList()); } } diff --git a/Content.Client/Stylesheets/StyleSpace.cs b/Content.Client/Stylesheets/StyleSpace.cs index 3bb4e986af..84f82ea038 100644 --- a/Content.Client/Stylesheets/StyleSpace.cs +++ b/Content.Client/Stylesheets/StyleSpace.cs @@ -131,19 +131,19 @@ public StyleSpace(IResourceCache resCache) : base(resCache) .Prop(Control.StylePropertyModulateSelf, ButtonColorDisabled), // Colors for the caution buttons. - Element().Class(ContainerButton.StyleClassButton).Class(ButtonCaution) + Element().Class(ContainerButton.StyleClassButton).Class(ButtonDanger) .Pseudo(ContainerButton.StylePseudoClassNormal) .Prop(Control.StylePropertyModulateSelf, ButtonColorCautionDefault), - Element().Class(ContainerButton.StyleClassButton).Class(ButtonCaution) + Element().Class(ContainerButton.StyleClassButton).Class(ButtonDanger) .Pseudo(ContainerButton.StylePseudoClassHover) .Prop(Control.StylePropertyModulateSelf, ButtonColorCautionHovered), - Element().Class(ContainerButton.StyleClassButton).Class(ButtonCaution) + Element().Class(ContainerButton.StyleClassButton).Class(ButtonDanger) .Pseudo(ContainerButton.StylePseudoClassPressed) .Prop(Control.StylePropertyModulateSelf, ButtonColorCautionPressed), - Element().Class(ContainerButton.StyleClassButton).Class(ButtonCaution) + Element().Class(ContainerButton.StyleClassButton).Class(ButtonDanger) .Pseudo(ContainerButton.StylePseudoClassDisabled) .Prop(Control.StylePropertyModulateSelf, ButtonColorCautionDisabled), diff --git a/Content.Client/Targeting/TargetingSystem.cs b/Content.Client/Targeting/TargetingSystem.cs new file mode 100644 index 0000000000..2c92d53ae1 --- /dev/null +++ b/Content.Client/Targeting/TargetingSystem.cs @@ -0,0 +1,102 @@ +using Content.Shared.Input; +using Content.Shared.Targeting; +using Content.Shared.Targeting.Events; +using Robust.Client.Player; +using Robust.Shared.Input.Binding; +using Robust.Shared.Player; + +namespace Content.Client.Targeting; +public sealed class TargetingSystem : SharedTargetingSystem +{ + [Dependency] private readonly IPlayerManager _playerManager = default!; + + public event Action? TargetingStartup; + public event Action? TargetingShutdown; + public event Action? TargetChange; + public event Action? PartStatusStartup; + public event Action? PartStatusUpdate; + public event Action? PartStatusShutdown; + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(HandlePlayerAttached); + SubscribeLocalEvent(HandlePlayerDetached); + SubscribeLocalEvent(OnTargetingStartup); + SubscribeLocalEvent(OnTargetingShutdown); + SubscribeNetworkEvent(OnTargetIntegrityChange); + + CommandBinds.Builder + .Bind(ContentKeyFunctions.TargetHead, + InputCmdHandler.FromDelegate((session) => HandleTargetChange(session, TargetBodyPart.Head))) + .Bind(ContentKeyFunctions.TargetTorso, + InputCmdHandler.FromDelegate((session) => HandleTargetChange(session, TargetBodyPart.Torso))) + .Bind(ContentKeyFunctions.TargetLeftArm, + InputCmdHandler.FromDelegate((session) => HandleTargetChange(session, TargetBodyPart.LeftArm))) +/* .Bind(ContentKeyFunctions.TargetLeftHand, + InputCmdHandler.FromDelegate((session) => HandleTargetChange(session, TargetBodyPart.LeftHand))) SOON :TM: */ + .Bind(ContentKeyFunctions.TargetRightArm, + InputCmdHandler.FromDelegate((session) => HandleTargetChange(session, TargetBodyPart.RightArm))) +/* .Bind(ContentKeyFunctions.TargetRightHand, + InputCmdHandler.FromDelegate((session) => HandleTargetChange(session, TargetBodyPart.RightHand)))*/ + .Bind(ContentKeyFunctions.TargetLeftLeg, + InputCmdHandler.FromDelegate((session) => HandleTargetChange(session, TargetBodyPart.LeftLeg))) +/* .Bind(ContentKeyFunctions.TargetLeftFoot, + InputCmdHandler.FromDelegate((session) => HandleTargetChange(session, TargetBodyPart.LeftFoot)))*/ + .Bind(ContentKeyFunctions.TargetRightLeg, + InputCmdHandler.FromDelegate((session) => HandleTargetChange(session, TargetBodyPart.RightLeg))) +/* .Bind(ContentKeyFunctions.TargetRightFoot, + InputCmdHandler.FromDelegate((session) => HandleTargetChange(session, TargetBodyPart.RightFoot)))*/ + .Register(); + } + + private void HandlePlayerAttached(EntityUid uid, TargetingComponent component, LocalPlayerAttachedEvent args) + { + TargetingStartup?.Invoke(component); + PartStatusStartup?.Invoke(component); + } + + private void HandlePlayerDetached(EntityUid uid, TargetingComponent component, LocalPlayerDetachedEvent args) + { + TargetingShutdown?.Invoke(); + PartStatusShutdown?.Invoke(); + } + + private void OnTargetingStartup(EntityUid uid, TargetingComponent component, ComponentStartup args) + { + if (_playerManager.LocalEntity != uid) + return; + + TargetingStartup?.Invoke(component); + PartStatusStartup?.Invoke(component); + } + + private void OnTargetingShutdown(EntityUid uid, TargetingComponent component, ComponentShutdown args) + { + if (_playerManager.LocalEntity != uid) + return; + + TargetingShutdown?.Invoke(); + PartStatusShutdown?.Invoke(); + } + + private void OnTargetIntegrityChange(TargetIntegrityChangeEvent args) + { + if (!TryGetEntity(args.Uid, out var uid) + || !_playerManager.LocalEntity.Equals(uid) + || !TryComp(uid, out TargetingComponent? component) + || !args.RefreshUi) + return; + + PartStatusUpdate?.Invoke(component); + } + + private void HandleTargetChange(ICommonSession? session, TargetBodyPart target) + { + if (session == null + || session.AttachedEntity is not { } uid + || !TryComp(uid, out var targeting)) + return; + + TargetChange?.Invoke(target); + } +} diff --git a/Content.Client/Telescope/TelescopeSystem.cs b/Content.Client/Telescope/TelescopeSystem.cs new file mode 100644 index 0000000000..ac2270aa97 --- /dev/null +++ b/Content.Client/Telescope/TelescopeSystem.cs @@ -0,0 +1,128 @@ +using System.Numerics; +using Content.Client.Viewport; +using Content.Shared.CCVar; +using Content.Shared.Telescope; +using Content.Shared.Input; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Client.Input; +using Robust.Client.Player; +using Robust.Client.UserInterface; +using Robust.Shared.Configuration; +using Robust.Shared.Input; +using Robust.Shared.Input.Binding; +using Robust.Shared.Timing; + +namespace Content.Client.Telescope; + +public sealed class TelescopeSystem : SharedTelescopeSystem +{ + [Dependency] private readonly InputSystem _inputSystem = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly IInputManager _input = default!; + [Dependency] private readonly IEyeManager _eyeManager = default!; + [Dependency] private readonly IUserInterfaceManager _uiManager = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; + + private ScalingViewport? _viewport; + private bool _holdLookUp; + private bool _toggled; + + public override void Initialize() + { + base.Initialize(); + + _cfg.OnValueChanged(CCVars.HoldLookUp, + val => + { + var input = val ? null : InputCmdHandler.FromDelegate(_ => _toggled = !_toggled); + _input.SetInputCommand(ContentKeyFunctions.LookUp, input); + _holdLookUp = val; + _toggled = false; + }, + true); + } + + public override void FrameUpdate(float frameTime) + { + base.FrameUpdate(frameTime); + + if (_timing.ApplyingState + || !_timing.IsFirstTimePredicted + || !_input.MouseScreenPosition.IsValid) + return; + + var player = _player.LocalEntity; + + var telescope = GetRightTelescope(player); + + if (telescope == null) + { + _toggled = false; + return; + } + + if (!TryComp(player, out var eye)) + return; + + var offset = Vector2.Zero; + + if (_holdLookUp) + { + if (_inputSystem.CmdStates.GetState(ContentKeyFunctions.LookUp) != BoundKeyState.Down) + { + RaiseEvent(offset); + return; + } + } + else if (!_toggled) + { + RaiseEvent(offset); + return; + } + + var mousePos = _input.MouseScreenPosition; + + if (_uiManager.MouseGetControl(mousePos) as ScalingViewport is { } viewport) + _viewport = viewport; + + if (_viewport == null) + return; + + var centerPos = _eyeManager.WorldToScreen(eye.Eye.Position.Position + eye.Offset); + + var diff = mousePos.Position - centerPos; + var len = diff.Length(); + + var size = _viewport.PixelSize; + + var maxLength = Math.Min(size.X, size.Y) * 0.4f; + var minLength = maxLength * 0.2f; + + if (len > maxLength) + { + diff *= maxLength / len; + len = maxLength; + } + + var divisor = maxLength * telescope.Divisor; + + if (len > minLength) + { + diff -= diff * minLength / len; + offset = new Vector2(diff.X / divisor, -diff.Y / divisor); + offset = new Angle(-eye.Rotation.Theta).RotateVec(offset); + } + + RaiseEvent(offset); + } + + private void RaiseEvent(Vector2 offset) + { + RaisePredictiveEvent(new EyeOffsetChangedEvent + { + Offset = offset + }); + } +} diff --git a/Content.Client/Tips/TippyUI.xaml b/Content.Client/Tips/TippyUI.xaml new file mode 100644 index 0000000000..a86e05aadd --- /dev/null +++ b/Content.Client/Tips/TippyUI.xaml @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/Content.Client/Tips/TippyUI.xaml.cs b/Content.Client/Tips/TippyUI.xaml.cs new file mode 100644 index 0000000000..de3eaf4f51 --- /dev/null +++ b/Content.Client/Tips/TippyUI.xaml.cs @@ -0,0 +1,54 @@ +using Content.Client.Paper; +using Robust.Client.AutoGenerated; +using Robust.Client.Graphics; +using Robust.Client.ResourceManagement; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; + +namespace Content.Client.Tips; + +[GenerateTypedNameReferences] +public sealed partial class TippyUI : UIWidget +{ + public TippyState State = TippyState.Hidden; + public bool ModifyLayers = true; + + public TippyUI() + { + RobustXamlLoader.Load(this); + } + + public void InitLabel(PaperVisualsComponent? visuals, IResourceCache resCache) + { + if (visuals == null) + return; + + Label.ModulateSelfOverride = visuals.FontAccentColor; + + if (visuals.BackgroundImagePath == null) + return; + + LabelPanel.ModulateSelfOverride = visuals.BackgroundModulate; + var backgroundImage = resCache.GetResource(visuals.BackgroundImagePath); + var backgroundImageMode = visuals.BackgroundImageTile ? StyleBoxTexture.StretchMode.Tile : StyleBoxTexture.StretchMode.Stretch; + var backgroundPatchMargin = visuals.BackgroundPatchMargin; + LabelPanel.PanelOverride = new StyleBoxTexture + { + Texture = backgroundImage, + TextureScale = visuals.BackgroundScale, + Mode = backgroundImageMode, + PatchMarginLeft = backgroundPatchMargin.Left, + PatchMarginBottom = backgroundPatchMargin.Bottom, + PatchMarginRight = backgroundPatchMargin.Right, + PatchMarginTop = backgroundPatchMargin.Top + }; + } + + public enum TippyState : byte + { + Hidden, + Revealing, + Speaking, + Hiding, + } +} diff --git a/Content.Client/Tips/TippyUIController.cs b/Content.Client/Tips/TippyUIController.cs new file mode 100644 index 0000000000..2cc694d97d --- /dev/null +++ b/Content.Client/Tips/TippyUIController.cs @@ -0,0 +1,241 @@ +using Content.Client.Gameplay; +using System.Numerics; +using Content.Client.Message; +using Content.Client.Paper; +using Content.Shared.CCVar; +using Content.Shared.Movement.Components; +using Content.Shared.Tips; +using Robust.Client.GameObjects; +using Robust.Client.ResourceManagement; +using Robust.Client.State; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controllers; +using Robust.Client.UserInterface.Controls; +using Robust.Client.Audio; +using Robust.Shared.Configuration; +using Robust.Shared.Console; +using Robust.Shared.Map; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; +using static Content.Client.Tips.TippyUI; + +namespace Content.Client.Tips; + +public sealed class TippyUIController : UIController +{ + [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly IResourceCache _resCache = default!; + [UISystemDependency] private readonly AudioSystem _audio = default!; + + public const float Padding = 50; + public static Angle WaddleRotation = Angle.FromDegrees(10); + + private EntityUid _entity; + private float _secondsUntilNextState; + private int _previousStep = 0; + private TippyEvent? _currentMessage; + private readonly Queue _queuedMessages = new(); + + public override void Initialize() + { + base.Initialize(); + UIManager.OnScreenChanged += OnScreenChanged; + SubscribeNetworkEvent(OnTippyEvent); + } + + private void OnTippyEvent(TippyEvent msg, EntitySessionEventArgs args) + { + _queuedMessages.Enqueue(msg); + } + + public override void FrameUpdate(FrameEventArgs args) + { + base.FrameUpdate(args); + + var screen = UIManager.ActiveScreen; + if (screen == null) + { + _queuedMessages.Clear(); + return; + } + + var tippy = screen.GetOrAddWidget(); + _secondsUntilNextState -= args.DeltaSeconds; + + if (_secondsUntilNextState <= 0) + NextState(tippy); + else + { + var pos = UpdatePosition(tippy, screen.Size, args); ; + LayoutContainer.SetPosition(tippy, pos); + } + } + + private Vector2 UpdatePosition(TippyUI tippy, Vector2 screenSize, FrameEventArgs args) + { + if (_currentMessage == null) + return default; + + var slideTime = _currentMessage.SlideTime; + + var offset = tippy.State switch + { + TippyState.Hidden => 0, + TippyState.Revealing => Math.Clamp(1 - _secondsUntilNextState / slideTime, 0, 1), + TippyState.Hiding => Math.Clamp(_secondsUntilNextState / slideTime, 0, 1), + _ => 1, + }; + + var waddle = _currentMessage.WaddleInterval; + + if (_currentMessage == null + || waddle <= 0 + || tippy.State == TippyState.Hidden + || tippy.State == TippyState.Speaking + || !EntityManager.TryGetComponent(_entity, out SpriteComponent? sprite)) + { + return new Vector2(screenSize.X - offset * (tippy.DesiredSize.X + Padding), (screenSize.Y - tippy.DesiredSize.Y) / 2); + } + + var numSteps = (int) Math.Ceiling(slideTime / waddle); + var curStep = (int) Math.Floor(numSteps * offset); + var stepSize = (tippy.DesiredSize.X + Padding) / numSteps; + + if (curStep != _previousStep) + { + _previousStep = curStep; + sprite.Rotation = sprite.Rotation > 0 + ? -WaddleRotation + : WaddleRotation; + + if (EntityManager.TryGetComponent(_entity, out FootstepModifierComponent? step)) + { + var audioParams = step.FootstepSoundCollection.Params + .AddVolume(-7f) + .WithVariation(0.1f); + _audio.PlayGlobal(step.FootstepSoundCollection, EntityUid.Invalid, audioParams); + } + } + + return new Vector2(screenSize.X - stepSize * curStep, (screenSize.Y - tippy.DesiredSize.Y) / 2); + } + + private void NextState(TippyUI tippy) + { + SpriteComponent? sprite; + switch (tippy.State) + { + case TippyState.Hidden: + if (!_queuedMessages.TryDequeue(out var next)) + return; + + if (next.Proto != null) + { + _entity = EntityManager.SpawnEntity(next.Proto, MapCoordinates.Nullspace); + tippy.ModifyLayers = false; + } + else + { + _entity = EntityManager.SpawnEntity(_cfg.GetCVar(CCVars.TippyEntity), MapCoordinates.Nullspace); + tippy.ModifyLayers = true; + } + if (!EntityManager.TryGetComponent(_entity, out sprite)) + return; + if (!EntityManager.HasComponent(_entity)) + { + var paper = EntityManager.AddComponent(_entity); + paper.BackgroundImagePath = "/Textures/Interface/Paper/paper_background_default.svg.96dpi.png"; + paper.BackgroundPatchMargin = new(16f, 16f, 16f, 16f); + paper.BackgroundModulate = new(255, 255, 204); + paper.FontAccentColor = new(0, 0, 0); + } + tippy.InitLabel(EntityManager.GetComponentOrNull(_entity), _resCache); + + var scale = sprite.Scale; + if (tippy.ModifyLayers) + { + sprite.Scale = Vector2.One; + } + else + { + sprite.Scale = new Vector2(3, 3); + } + tippy.Entity.SetEntity(_entity); + tippy.Entity.Scale = scale; + + _currentMessage = next; + _secondsUntilNextState = next.SlideTime; + tippy.State = TippyState.Revealing; + _previousStep = 0; + if (tippy.ModifyLayers) + { + sprite.LayerSetAnimationTime("revealing", 0); + sprite.LayerSetVisible("revealing", true); + sprite.LayerSetVisible("speaking", false); + sprite.LayerSetVisible("hiding", false); + } + sprite.Rotation = 0; + tippy.Label.SetMarkupPermissive(_currentMessage.Msg); + tippy.Label.Visible = false; + tippy.LabelPanel.Visible = false; + tippy.Visible = true; + sprite.Visible = true; + break; + + case TippyState.Revealing: + tippy.State = TippyState.Speaking; + if (!EntityManager.TryGetComponent(_entity, out sprite)) + return; + sprite.Rotation = 0; + _previousStep = 0; + if (tippy.ModifyLayers) + { + sprite.LayerSetAnimationTime("speaking", 0); + sprite.LayerSetVisible("revealing", false); + sprite.LayerSetVisible("speaking", true); + sprite.LayerSetVisible("hiding", false); + } + tippy.Label.Visible = true; + tippy.LabelPanel.Visible = true; + tippy.InvalidateArrange(); + tippy.InvalidateMeasure(); + if (_currentMessage != null) + _secondsUntilNextState = _currentMessage.SpeakTime; + + break; + + case TippyState.Speaking: + tippy.State = TippyState.Hiding; + if (!EntityManager.TryGetComponent(_entity, out sprite)) + return; + if (tippy.ModifyLayers) + { + sprite.LayerSetAnimationTime("hiding", 0); + sprite.LayerSetVisible("revealing", false); + sprite.LayerSetVisible("speaking", false); + sprite.LayerSetVisible("hiding", true); + } + tippy.LabelPanel.Visible = false; + if (_currentMessage != null) + _secondsUntilNextState = _currentMessage.SlideTime; + break; + + default: // finished hiding + + EntityManager.DeleteEntity(_entity); + _entity = default; + tippy.Visible = false; + _currentMessage = null; + _secondsUntilNextState = 0; + tippy.State = TippyState.Hidden; + break; + } + } + + private void OnScreenChanged((UIScreen? Old, UIScreen? New) ev) + { + ev.Old?.RemoveWidget(); + _currentMessage = null; + EntityManager.DeleteEntity(_entity); + } +} diff --git a/Content.Client/Tools/Components/WelderComponent.cs b/Content.Client/Tools/Components/WelderComponent.cs deleted file mode 100644 index a83a78a5a4..0000000000 --- a/Content.Client/Tools/Components/WelderComponent.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Content.Client.Tools.UI; -using Content.Shared.Tools.Components; - -namespace Content.Client.Tools.Components -{ - [RegisterComponent, Access(typeof(ToolSystem), typeof(WelderStatusControl))] - public sealed partial class WelderComponent : SharedWelderComponent - { - [ViewVariables(VVAccess.ReadWrite)] - public bool UiUpdateNeeded { get; set; } - - [ViewVariables] - public float FuelCapacity { get; set; } - - [ViewVariables] - public float Fuel { get; set; } - } -} diff --git a/Content.Client/Tools/ToolSystem.cs b/Content.Client/Tools/ToolSystem.cs index 6811d58460..2207242918 100644 --- a/Content.Client/Tools/ToolSystem.cs +++ b/Content.Client/Tools/ToolSystem.cs @@ -1,10 +1,8 @@ using Content.Client.Items; using Content.Client.Tools.Components; using Content.Client.Tools.UI; -using Content.Shared.Item; using Content.Shared.Tools.Components; using Robust.Client.GameObjects; -using Robust.Shared.GameStates; using SharedToolSystem = Content.Shared.Tools.Systems.SharedToolSystem; namespace Content.Client.Tools @@ -15,8 +13,7 @@ public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnWelderHandleState); - Subs.ItemStatus(ent => new WelderStatusControl(ent)); + Subs.ItemStatus(ent => new WelderStatusControl(ent, EntityManager, this)); Subs.ItemStatus(ent => new MultipleToolStatusControl(ent)); } @@ -42,20 +39,5 @@ public override void SetMultipleTool(EntityUid uid, sprite.LayerSetSprite(0, current.Sprite); } } - - private void OnWelderHandleState(EntityUid uid, WelderComponent welder, ref ComponentHandleState args) - { - if (args.Current is not WelderComponentState state) - return; - - welder.FuelCapacity = state.FuelCapacity; - welder.Fuel = state.Fuel; - welder.UiUpdateNeeded = true; - } - - protected override bool IsWelder(EntityUid uid) - { - return HasComp(uid); - } } } diff --git a/Content.Client/Tools/UI/WelderStatusControl.cs b/Content.Client/Tools/UI/WelderStatusControl.cs index af81a28f62..3d44d6fa84 100644 --- a/Content.Client/Tools/UI/WelderStatusControl.cs +++ b/Content.Client/Tools/UI/WelderStatusControl.cs @@ -1,62 +1,45 @@ +using Content.Client.Items.UI; using Content.Client.Message; using Content.Client.Stylesheets; -using Content.Client.Tools.Components; -using Content.Shared.Item; -using Robust.Client.UserInterface; +using Content.Shared.FixedPoint; +using Content.Shared.Tools.Components; +using Content.Shared.Tools.Systems; using Robust.Client.UserInterface.Controls; -using Robust.Shared.Timing; -using ItemToggleComponent = Content.Shared.Item.ItemToggle.Components.ItemToggleComponent; namespace Content.Client.Tools.UI; -public sealed class WelderStatusControl : Control +public sealed class WelderStatusControl : PollingItemStatusControl { - [Dependency] private readonly IEntityManager _entMan = default!; - - private readonly WelderComponent _parent; - private readonly ItemToggleComponent? _toggleComponent; + private readonly Entity _parent; + private readonly IEntityManager _entityManager; + private readonly SharedToolSystem _toolSystem; private readonly RichTextLabel _label; - public WelderStatusControl(Entity parent) + public WelderStatusControl(Entity parent, IEntityManager entityManager, SharedToolSystem toolSystem) { _parent = parent; - _entMan = IoCManager.Resolve(); - if (_entMan.TryGetComponent(parent, out var itemToggle)) - _toggleComponent = itemToggle; + _entityManager = entityManager; + _toolSystem = toolSystem; _label = new RichTextLabel { StyleClasses = { StyleNano.StyleClassItemStatus } }; AddChild(_label); UpdateDraw(); } - /// - protected override void FrameUpdate(FrameEventArgs args) + protected override Data PollData() { - base.FrameUpdate(args); - - if (!_parent.UiUpdateNeeded) - { - return; - } - Update(); + var (fuel, capacity) = _toolSystem.GetWelderFuelAndCapacity(_parent, _parent.Comp); + return new Data(fuel, capacity, _parent.Comp.Enabled); } - public void Update() + protected override void Update(in Data data) { - _parent.UiUpdateNeeded = false; - - var fuelCap = _parent.FuelCapacity; - var fuel = _parent.Fuel; - var lit = false; - if (_toggleComponent != null) - { - lit = _toggleComponent.Activated; - } - _label.SetMarkup(Loc.GetString("welder-component-on-examine-detailed-message", - ("colorName", fuel < fuelCap / 4f ? "darkorange" : "orange"), - ("fuelLeft", Math.Round(fuel, 1)), - ("fuelCapacity", fuelCap), - ("status", Loc.GetString(lit ? "welder-component-on-examine-welder-lit-message" : "welder-component-on-examine-welder-not-lit-message")))); + ("colorName", data.Fuel < data.FuelCapacity / 4f ? "darkorange" : "orange"), + ("fuelLeft", data.Fuel), + ("fuelCapacity", data.FuelCapacity), + ("status", Loc.GetString(data.Lit ? "welder-component-on-examine-welder-lit-message" : "welder-component-on-examine-welder-not-lit-message")))); } + + public record struct Data(FixedPoint2 Fuel, FixedPoint2 FuelCapacity, bool Lit); } diff --git a/Content.Client/UserInterface/Controls/ClipControl.cs b/Content.Client/UserInterface/Controls/ClipControl.cs new file mode 100644 index 0000000000..1fca3c0f47 --- /dev/null +++ b/Content.Client/UserInterface/Controls/ClipControl.cs @@ -0,0 +1,55 @@ +using System.Numerics; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; + +namespace Content.Client.UserInterface.Controls; + +/// +/// Pretends to child controls that there's infinite space. +/// This can be used to make something like a clip instead of wrapping. +/// +public sealed class ClipControl : Control +{ + private bool _clipHorizontal = true; + private bool _clipVertical = true; + + public bool ClipHorizontal + { + get => _clipHorizontal; + set + { + _clipHorizontal = value; + InvalidateMeasure(); + } + } + + public bool ClipVertical + { + get => _clipVertical; + set + { + _clipVertical = value; + InvalidateMeasure(); + } + } + + protected override Vector2 MeasureOverride(Vector2 availableSize) + { + if (ClipHorizontal) + availableSize = availableSize with { X = float.PositiveInfinity }; + if (ClipVertical) + availableSize = availableSize with { Y = float.PositiveInfinity }; + + return base.MeasureOverride(availableSize); + } + + protected override Vector2 ArrangeOverride(Vector2 finalSize) + { + foreach (var child in Children) + { + child.Arrange(UIBox2.FromDimensions(Vector2.Zero, child.DesiredSize)); + } + + return finalSize; + } +} diff --git a/Content.Client/UserInterface/Controls/DirectionIcon.cs b/Content.Client/UserInterface/Controls/DirectionIcon.cs index a6cc428091..c8fd63b43c 100644 --- a/Content.Client/UserInterface/Controls/DirectionIcon.cs +++ b/Content.Client/UserInterface/Controls/DirectionIcon.cs @@ -65,7 +65,7 @@ protected override void Draw(DrawingHandleScreen handle) if (_rotation != null) { var offset = (-_rotation.Value).RotateVec(Size * UIScale / 2) - Size * UIScale / 2; - handle.SetTransform(Matrix3.CreateTransform(GlobalPixelPosition - offset, -_rotation.Value)); + handle.SetTransform(Matrix3Helpers.CreateTransform(GlobalPixelPosition - offset, -_rotation.Value)); } base.Draw(handle); diff --git a/Content.Client/UserInterface/Controls/FancyWindow.xaml b/Content.Client/UserInterface/Controls/FancyWindow.xaml index d076a552bf..84d0499b3a 100644 --- a/Content.Client/UserInterface/Controls/FancyWindow.xaml +++ b/Content.Client/UserInterface/Controls/FancyWindow.xaml @@ -11,6 +11,7 @@ diff --git a/Content.Client/UserInterface/Controls/FancyWindow.xaml.cs b/Content.Client/UserInterface/Controls/FancyWindow.xaml.cs index 8cdfe57dba..5912687fc3 100644 --- a/Content.Client/UserInterface/Controls/FancyWindow.xaml.cs +++ b/Content.Client/UserInterface/Controls/FancyWindow.xaml.cs @@ -1,7 +1,10 @@ using System.Numerics; +using Content.Client.Guidebook; +using Content.Client.Guidebook.Components; using Robust.Client.AutoGenerated; using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.XAML; +using Robust.Shared.Prototypes; namespace Content.Client.UserInterface.Controls { @@ -9,13 +12,17 @@ namespace Content.Client.UserInterface.Controls [Virtual] public partial class FancyWindow : BaseWindow { + [Dependency] private readonly IEntitySystemManager _sysMan = default!; + private GuidebookSystem? _guidebookSystem; private const int DRAG_MARGIN_SIZE = 7; + public const string StyleClassWindowHelpButton = "windowHelpButton"; public FancyWindow() { RobustXamlLoader.Load(this); CloseButton.OnPressed += _ => Close(); + HelpButton.OnPressed += _ => Help(); XamlChildren = ContentsContainer.Children; } @@ -25,6 +32,26 @@ public string? Title set => WindowTitle.Text = value; } + private List? _helpGuidebookIds; + public List? HelpGuidebookIds + { + get => _helpGuidebookIds; + set + { + _helpGuidebookIds = value; + HelpButton.Disabled = _helpGuidebookIds == null; + HelpButton.Visible = !HelpButton.Disabled; + } + } + + public void Help() + { + if (HelpGuidebookIds is null) + return; + _guidebookSystem ??= _sysMan.GetEntitySystem(); + _guidebookSystem.OpenHelp(HelpGuidebookIds); + } + protected override DragMode GetDragModeFor(Vector2 relativeMousePos) { var mode = DragMode.Move; diff --git a/Content.Client/UserInterface/Controls/MainViewport.cs b/Content.Client/UserInterface/Controls/MainViewport.cs index e334f61572..721d750115 100644 --- a/Content.Client/UserInterface/Controls/MainViewport.cs +++ b/Content.Client/UserInterface/Controls/MainViewport.cs @@ -51,6 +51,7 @@ public void UpdateCfg() var stretch = _cfg.GetCVar(CCVars.ViewportStretch); var renderScaleUp = _cfg.GetCVar(CCVars.ViewportScaleRender); var fixedFactor = _cfg.GetCVar(CCVars.ViewportFixedScaleFactor); + var verticalFit = _cfg.GetCVar(CCVars.ViewportVerticalFit); if (stretch) { @@ -60,6 +61,7 @@ public void UpdateCfg() // Did not find a snap, enable stretching. Viewport.FixedStretchSize = null; Viewport.StretchMode = ScalingViewportStretchMode.Bilinear; + Viewport.IgnoreDimension = verticalFit ? ScalingViewportIgnoreDimension.Horizontal : ScalingViewportIgnoreDimension.None; if (renderScaleUp) { @@ -104,6 +106,8 @@ public void UpdateCfg() // where we are clipping the viewport to make it fit. var cfgToleranceClip = _cfg.GetCVar(CCVars.ViewportSnapToleranceClip); + var cfgVerticalFit = _cfg.GetCVar(CCVars.ViewportVerticalFit); + // Calculate if the viewport, when rendered at an integer scale, // is close enough to the control size to enable "snapping" to NN, // potentially cutting a tiny bit off/leaving a margin. @@ -123,7 +127,8 @@ public void UpdateCfg() // The rule for which snap fits is that at LEAST one axis needs to be in the tolerance size wise. // One axis MAY be larger but not smaller than tolerance. // Obviously if it's too small it's bad, and if it's too big on both axis we should stretch up. - if (Fits(dx) && Fits(dy) || Fits(dx) && Larger(dy) || Larger(dx) && Fits(dy)) + // Additionally, if the viewport's supposed to be vertically fit, then the horizontal scale should just be ignored where appropriate. + if ((Fits(dx) || cfgVerticalFit) && Fits(dy) || !cfgVerticalFit && Fits(dx) && Larger(dy) || Larger(dx) && Fits(dy)) { // Found snap that fits. return i; diff --git a/Content.Client/UserInterface/Controls/MapGridControl.xaml.cs b/Content.Client/UserInterface/Controls/MapGridControl.xaml.cs index f6b0929f3b..a10155f3e8 100644 --- a/Content.Client/UserInterface/Controls/MapGridControl.xaml.cs +++ b/Content.Client/UserInterface/Controls/MapGridControl.xaml.cs @@ -169,7 +169,7 @@ protected Vector2 InverseMapPosition(Vector2 value) var inversePos = (value - MidPointVector) / MinimapScale; inversePos = inversePos with { Y = -inversePos.Y }; - inversePos = Matrix3.CreateTransform(Offset, Angle.Zero).Transform(inversePos); + inversePos = Vector2.Transform(inversePos, Matrix3Helpers.CreateTransform(Offset, Angle.Zero)); return inversePos; } diff --git a/Content.Client/UserInterface/Controls/NeoTabContainer.Props.xaml.cs b/Content.Client/UserInterface/Controls/NeoTabContainer.Props.xaml.cs new file mode 100644 index 0000000000..318dd06014 --- /dev/null +++ b/Content.Client/UserInterface/Controls/NeoTabContainer.Props.xaml.cs @@ -0,0 +1,163 @@ +using static Robust.Client.UserInterface.Controls.BoxContainer.LayoutOrientation; +using static Robust.Shared.Maths.Direction; + +namespace Content.Client.UserInterface.Controls; + +public sealed partial class NeoTabContainer +{ + // Too many computed properties... + + /// + /// Where to place the tabs in relation to the contents + ///
+ /// If , the tabs will be above the contents + ///
+ private Direction _tabLocation = North; + public Direction TabLocation + { + get => _tabLocation; + set => LayoutChanged(value); + } + + /// If the 's horizontal scroll is enabled + private bool _hScrollEnabled; + /// + public bool HScrollEnabled + { + get => _hScrollEnabled; + set => ScrollingChanged(value, _vScrollEnabled); + } + + /// If the 's vertical scroll is enabled + private bool _vScrollEnabled; + /// + public bool VScrollEnabled + { + get => _vScrollEnabled; + set => ScrollingChanged(_hScrollEnabled, value); + } + + /// The margin around the whole UI element + private Thickness _containerMargin = new(0); + /// + public Thickness ContainerMargin + { + get => _containerMargin; + set => ContainerMarginChanged(value); + } + + /// The margin around the separator between the tabs and contents + public Thickness SeparatorMargin + { + get => Separator.Margin; + set => Separator.Margin = value; + } + + private bool _firstTabOpenBoth; + public bool FirstTabOpenBoth + { + get => _firstTabOpenBoth; + set => TabStyleChanged(value, LastTabOpenBoth); + } + + private bool _lastTabOpenBoth; + public bool LastTabOpenBoth + { + get => _lastTabOpenBoth; + set => TabStyleChanged(FirstTabOpenBoth, value); + } + + + /// Changes the layout of the tabs and contents based on the value + /// See + private void LayoutChanged(Direction direction) + { + _tabLocation = direction; + + LayoutOrientation DirectionalOrientation(Direction direction, LayoutOrientation north, LayoutOrientation south, + LayoutOrientation east, LayoutOrientation west) + { + return direction switch + { + North => north, + South => south, + East => east, + West => west, + _ => Vertical, + }; + } + TabContainer.Orientation = DirectionalOrientation(direction, Horizontal, Horizontal, Vertical, Vertical); + Container.Orientation = DirectionalOrientation(direction, Vertical, Vertical, Horizontal, Horizontal); + + var containerMargin = direction switch + { + North => new Thickness(_containerMargin.Left, 0, _containerMargin.Right, _containerMargin.Bottom), + South => new Thickness(_containerMargin.Left, _containerMargin.Top, _containerMargin.Right, 0), + East => new Thickness(_containerMargin.Left, _containerMargin.Top, 0, _containerMargin.Bottom), + West => new Thickness(0, _containerMargin.Top, _containerMargin.Right, _containerMargin.Bottom), + _ => _containerMargin, + }; + TabScrollContainer.Margin = containerMargin; + ContentScrollContainer.Margin = containerMargin; + + bool DirectionalBool(Direction direction, bool north, bool south, bool east, bool west) + { + return direction switch + { + North => north, + South => south, + East => east, + West => west, + _ => false, + }; + } + TabScrollContainer.HorizontalExpand = DirectionalBool(direction, true, true, false, false); + TabScrollContainer.VerticalExpand = DirectionalBool(direction, false, false, true, true); + TabScrollContainer.HScrollEnabled = DirectionalBool(direction, true, true, false, false); + TabScrollContainer.VScrollEnabled = DirectionalBool(direction, false, false, true, true); + + + // Move the TabScrollContainer, Separator, and ContentScrollContainer to the correct positions + TabScrollContainer.Orphan(); + Separator.Orphan(); + ContentScrollContainer.Orphan(); + + if (direction is North or West) + { + Container.AddChild(TabScrollContainer); + Container.AddChild(Separator); + Container.AddChild(ContentScrollContainer); + } + else + { + Container.AddChild(ContentScrollContainer); + Container.AddChild(Separator); + Container.AddChild(TabScrollContainer); + } + + UpdateTabMerging(); + } + + private void ScrollingChanged(bool hScroll, bool vScroll) + { + _hScrollEnabled = hScroll; + _vScrollEnabled = vScroll; + + ContentScrollContainer.HScrollEnabled = hScroll; + ContentScrollContainer.VScrollEnabled = vScroll; + } + + private void ContainerMarginChanged(Thickness value) + { + _containerMargin = value; + LayoutChanged(TabLocation); + } + + private void TabStyleChanged(bool firstTabOpenBoth, bool lastTabOpenBoth) + { + _firstTabOpenBoth = firstTabOpenBoth; + _lastTabOpenBoth = lastTabOpenBoth; + + UpdateTabMerging(); + } +} diff --git a/Content.Client/UserInterface/Controls/NeoTabContainer.xaml b/Content.Client/UserInterface/Controls/NeoTabContainer.xaml new file mode 100644 index 0000000000..21ee79a6ca --- /dev/null +++ b/Content.Client/UserInterface/Controls/NeoTabContainer.xaml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + diff --git a/Content.Client/UserInterface/Controls/NeoTabContainer.xaml.cs b/Content.Client/UserInterface/Controls/NeoTabContainer.xaml.cs new file mode 100644 index 0000000000..fc148cc634 --- /dev/null +++ b/Content.Client/UserInterface/Controls/NeoTabContainer.xaml.cs @@ -0,0 +1,299 @@ +using System.Linq; +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Timing; +using Robust.Shared.Utility; +using static Content.Client.Stylesheets.StyleBase; +using static Robust.Shared.Maths.Direction; + +namespace Content.Client.UserInterface.Controls; + +/// +/// A simple yet good-looking tab container using normal UI elements with multiple styles +///
+/// Because nobody else could do it better. +///
+[GenerateTypedNameReferences] +public sealed partial class NeoTabContainer : BoxContainer +{ + private readonly Dictionary _tabs = new(); + private readonly List _controls = new(); + private readonly ButtonGroup _tabGroup = new(false); + + /// All children within the + public OrderedChildCollection Contents => ContentContainer.Children; + /// All children within the that are visible + public List VisibleContents => Contents.Where(c => c == CurrentControl).ToList(); + + /// All children within the + public OrderedChildCollection Tabs => TabContainer.Children; + /// All children within the that are visible + public List VisibleTabs => Tabs.Where(c => c.Visible).ToList(); + + public Control? CurrentControl { get; private set; } + public int? CurrentTab => _controls.FirstOrDefault(control => control == CurrentControl) switch + { + { } control => _controls.IndexOf(control), + _ => null, + }; + + + /// + public NeoTabContainer() + { + RobustXamlLoader.Load(this); + + LayoutChanged(TabLocation); + ScrollingChanged(HScrollEnabled, VScrollEnabled); + } + + protected override void ChildRemoved(Control child) + { + if (_tabs.Remove(child, out var button)) + button.Dispose(); + + // Set the current tab to a different control + if (CurrentControl == child) + { + var previous = _controls.IndexOf(child) - 1; + + if (previous > -1) + SelectTab(_controls[previous]); + else + CurrentControl = null; + } + + _controls.Remove(child); + base.ChildRemoved(child); + UpdateTabMerging(); + } + + // A fun display of every location for the tabs if you want it + // private TimeSpan _lastLayoutChange = TimeSpan.Zero; + // private TimeSpan _nextLayoutChange = TimeSpan.Zero; + // protected override void FrameUpdate(FrameEventArgs args) + // { + // base.FrameUpdate(args); + // + // _lastLayoutChange += TimeSpan.FromSeconds(args.DeltaSeconds); + // // Change the layout every second such that the tabs go in a circle + // if (_lastLayoutChange.TotalSeconds < _nextLayoutChange.TotalSeconds) + // return; + // + // _lastLayoutChange = _nextLayoutChange; + // _nextLayoutChange = _lastLayoutChange + TimeSpan.FromSeconds(2); + // + // LayoutChanged(TabLocation switch + // { + // North => East, + // East => South, + // South => West, + // West => North, + // _ => North, + // }); + // } + + + /// + /// Adds a tab to this container + /// + /// The tab contents + /// The title of the tab + /// Whether the tabs should fix their styling automatically. Useful if you're doing tons of updates at once + /// The index of the new tab + public int AddTab(Control control, string? title, bool updateTabMerging = true) + { + var button = new Button + { + Group = _tabGroup, + MinHeight = 32, + MaxHeight = 32, + HorizontalExpand = true, + }; + button.OnPressed += _ => SelectTab(control); + if (!string.IsNullOrEmpty(title)) + button.Text = title; + + TabContainer.AddChild(button); + ContentContainer.AddChild(control); + _controls.Add(control); + _tabs.Add(control, button); + + // Show it if it has content + if (ContentContainer.ChildCount > 1) + control.Visible = false; + else + // Select it if it's the only tab + SelectTab(control); + + if (updateTabMerging) + UpdateTabMerging(); + return ChildCount - 1; + } + + /// + /// Removes/Disposes the tab associated with the given index + /// + /// The index of the tab to remove + /// Whether the tabs should fix their styling automatically. Useful if you're doing tons of updates at once + /// True if the tab was removed, false otherwise + public bool RemoveTab(int index, bool updateTabMerging = true) + { + if (index < 0 || index >= _controls.Count) + return false; + + var control = _controls[index]; + RemoveTab(control, updateTabMerging); + return true; + } + + /// + /// Removes/Disposes the tab associated with the given control + /// + /// The control to remove + /// Whether the tabs should fix their styling automatically. Useful if you're doing tons of updates at once + /// True if the tab was removed, false otherwise + public bool RemoveTab(Control control, bool updateTabMerging = true) + { + if (!_tabs.TryGetValue(control, out var button)) + return false; + + button.Dispose(); + control.Dispose(); + if (updateTabMerging) + UpdateTabMerging(); + return true; + } + + + /// Sets the title of the tab associated with the given index + public void SetTabTitle(int index, string title) + { + if (index < 0 || index >= _controls.Count) + return; + + var control = _controls[index]; + SetTabTitle(control, title); + } + + /// Sets the title of the tab associated with the given control + public void SetTabTitle(Control control, string title) + { + if (!_tabs.TryGetValue(control, out var button)) + return; + + if (button is Button b) + b.Text = title; + } + + /// Shows or hides the tab associated with the given index + public void SetTabVisible(int index, bool visible) + { + if (index < 0 || index >= _controls.Count) + return; + + var control = _controls[index]; + SetTabVisible(control, visible); + } + + /// Shows or hides the tab associated with the given control + public void SetTabVisible(Control control, bool visible) + { + if (!_tabs.TryGetValue(control, out var button)) + return; + + button.Visible = visible; + UpdateTabMerging(); + } + + /// Selects the tab associated with the control + public void SelectTab(Control control) + { + if (CurrentControl != null) + CurrentControl.Visible = false; + + var button = _tabs[control]; + button.Pressed = true; + control.Visible = true; + CurrentControl = control; + } + + /// Sets the style of every visible tab's Button to be Open to Right, Both, or Left depending on position + public void UpdateTabMerging() + { + var visibleTabs = VisibleTabs; + + if (visibleTabs.Count == 0) + return; + + if (visibleTabs.Count == 1) + { + var button = visibleTabs[0]; + button.RemoveStyleClass(ButtonOpenRight); + button.RemoveStyleClass(ButtonOpenBoth); + button.RemoveStyleClass(ButtonOpenLeft); + + if (FirstTabOpenBoth) + button.AddStyleClass(ButtonOpenBoth); + + return; + } + + string GetDirection(Direction direction, int position) + { + return position switch + { + // First + 0 => direction switch + { + North => ButtonOpenRight, + South => ButtonOpenRight, + East => ButtonOpenLeft, + West => ButtonOpenLeft, + _ => ButtonOpenRight, + }, + // Middle + 1 => ButtonOpenBoth, + // Last + 2 => direction switch + { + North => ButtonOpenLeft, + South => ButtonOpenLeft, + East => ButtonOpenRight, + West => ButtonOpenRight, + _ => ButtonOpenLeft, + }, + _ => ButtonOpenBoth, + }; + } + + for (var i = 0; i < visibleTabs.Count; i++) + { + var button = visibleTabs[i]; + button.RemoveStyleClass(ButtonOpenRight); + button.RemoveStyleClass(ButtonOpenBoth); + button.RemoveStyleClass(ButtonOpenLeft); + + if (FirstTabOpenBoth && i == 0) + { + button.AddStyleClass(ButtonOpenBoth); + continue; + } + if (LastTabOpenBoth && i == visibleTabs.Count - 1) + { + button.AddStyleClass(ButtonOpenBoth); + continue; + } + + var position = i switch + { + 0 => 0, + _ when i == visibleTabs.Count - 1 => 2, + _ => 1, + }; + button.AddStyleClass(GetDirection(TabLocation, position)); + } + } +} diff --git a/Content.Client/UserInterface/Controls/SlotButton.cs b/Content.Client/UserInterface/Controls/SlotButton.cs index 4ec6861606..c33782371f 100644 --- a/Content.Client/UserInterface/Controls/SlotButton.cs +++ b/Content.Client/UserInterface/Controls/SlotButton.cs @@ -9,6 +9,7 @@ public SlotButton() { } public SlotButton(SlotData slotData) { ButtonTexturePath = slotData.TextureName; + FullButtonTexturePath = slotData.FullTextureName; Blocked = slotData.Blocked; Highlight = slotData.Highlighted; StorageTexturePath = "Slots/back"; diff --git a/Content.Client/UserInterface/Controls/SlotControl.cs b/Content.Client/UserInterface/Controls/SlotControl.cs index 9b94f8a0c8..a684bb05ef 100644 --- a/Content.Client/UserInterface/Controls/SlotControl.cs +++ b/Content.Client/UserInterface/Controls/SlotControl.cs @@ -15,11 +15,12 @@ public abstract class SlotControl : Control, IEntityControl public TextureRect ButtonRect { get; } public TextureRect BlockedRect { get; } public TextureRect HighlightRect { get; } - public SpriteView SpriteView { get; } public SpriteView HoverSpriteView { get; } public TextureButton StorageButton { get; } public CooldownGraphic CooldownDisplay { get; } + private SpriteView SpriteView { get; } + public EntityUid? Entity => SpriteView.Entity; private bool _slotNameSet; @@ -68,7 +69,18 @@ public string? ButtonTexturePath set { _buttonTexturePath = value; - ButtonRect.Texture = Theme.ResolveTextureOrNull(_buttonTexturePath)?.Texture; + UpdateButtonTexture(); + } + } + + private string? _fullButtonTexturePath; + public string? FullButtonTexturePath + { + get => _fullButtonTexturePath; + set + { + _fullButtonTexturePath = value; + UpdateButtonTexture(); } } @@ -197,6 +209,21 @@ public void ClearHover() HoverSpriteView.SetEntity(null); } + public void SetEntity(EntityUid? ent) + { + SpriteView.SetEntity(ent); + UpdateButtonTexture(); + } + + private void UpdateButtonTexture() + { + var fullTexture = Theme.ResolveTextureOrNull(_fullButtonTexturePath); + var texture = Entity.HasValue && fullTexture != null + ? fullTexture.Texture + : Theme.ResolveTextureOrNull(_buttonTexturePath)?.Texture; + ButtonRect.Texture = texture; + } + private void OnButtonPressed(GUIBoundKeyEventArgs args) { Pressed?.Invoke(args, this); @@ -229,8 +256,8 @@ protected override void OnThemeUpdated() base.OnThemeUpdated(); StorageButton.TextureNormal = Theme.ResolveTextureOrNull(_storageTexturePath)?.Texture; - ButtonRect.Texture = Theme.ResolveTextureOrNull(_buttonTexturePath)?.Texture; HighlightRect.Texture = Theme.ResolveTextureOrNull(_highlightTexturePath)?.Texture; + UpdateButtonTexture(); } EntityUid? IEntityControl.UiEntity => Entity; diff --git a/Content.Client/UserInterface/Screens/OverlayChatGameScreen.xaml b/Content.Client/UserInterface/Screens/OverlayChatGameScreen.xaml index 4ba820b339..0596009ed1 100644 --- a/Content.Client/UserInterface/Screens/OverlayChatGameScreen.xaml +++ b/Content.Client/UserInterface/Screens/OverlayChatGameScreen.xaml @@ -9,6 +9,7 @@ xmlns:hotbar="clr-namespace:Content.Client.UserInterface.Systems.Hotbar.Widgets" xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls" xmlns:inventory="clr-namespace:Content.Client.UserInterface.Systems.Inventory.Widgets" + xmlns:targeting="clr-namespace:Content.Client.UserInterface.Systems.Targeting.Widgets" Name="DefaultHud" VerticalExpand="False" VerticalAlignment="Bottom" @@ -28,6 +29,7 @@ + diff --git a/Content.Client/UserInterface/Screens/OverlayChatGameScreen.xaml.cs b/Content.Client/UserInterface/Screens/OverlayChatGameScreen.xaml.cs index c45ec9d4a0..d6bc0f97cc 100644 --- a/Content.Client/UserInterface/Screens/OverlayChatGameScreen.xaml.cs +++ b/Content.Client/UserInterface/Screens/OverlayChatGameScreen.xaml.cs @@ -22,6 +22,7 @@ public OverlayChatGameScreen() SetAnchorAndMarginPreset(Hotbar, LayoutPreset.BottomWide, margin: 5); SetAnchorAndMarginPreset(Chat, LayoutPreset.TopRight, margin: 10); SetAnchorAndMarginPreset(Alerts, LayoutPreset.TopRight, margin: 10); + SetAnchorAndMarginPreset(Targeting, LayoutPreset.BottomRight, margin: 5); Chat.OnResized += ChatOnResized; Chat.OnChatResizeFinish += ChatOnResizeFinish; diff --git a/Content.Client/UserInterface/Screens/SeparatedChatGameScreen.xaml b/Content.Client/UserInterface/Screens/SeparatedChatGameScreen.xaml index 7f1d1bcd5b..c60b6c44dd 100644 --- a/Content.Client/UserInterface/Screens/SeparatedChatGameScreen.xaml +++ b/Content.Client/UserInterface/Screens/SeparatedChatGameScreen.xaml @@ -10,6 +10,7 @@ xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls" xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client" xmlns:inventory="clr-namespace:Content.Client.UserInterface.Systems.Inventory.Widgets" + xmlns:targeting="clr-namespace:Content.Client.UserInterface.Systems.Targeting.Widgets" Name="SeparatedChatHud" VerticalExpand="False" VerticalAlignment="Bottom" @@ -20,6 +21,7 @@ + diff --git a/Content.Client/UserInterface/Screens/SeparatedChatGameScreen.xaml.cs b/Content.Client/UserInterface/Screens/SeparatedChatGameScreen.xaml.cs index 45a29e03f1..5c612587ed 100644 --- a/Content.Client/UserInterface/Screens/SeparatedChatGameScreen.xaml.cs +++ b/Content.Client/UserInterface/Screens/SeparatedChatGameScreen.xaml.cs @@ -23,6 +23,7 @@ public SeparatedChatGameScreen() SetAnchorAndMarginPreset(Ghost, LayoutPreset.BottomWide, margin: 80); SetAnchorAndMarginPreset(Hotbar, LayoutPreset.BottomWide, margin: 5); SetAnchorAndMarginPreset(Alerts, LayoutPreset.CenterRight, margin: 10); + SetAnchorAndMarginPreset(Targeting, LayoutPreset.BottomRight, margin: 5); ScreenContainer.OnSplitResizeFinished += () => OnChatResized?.Invoke(new Vector2(ScreenContainer.SplitFraction, 0)); diff --git a/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs b/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs index 09663ba82c..69ac3ab023 100644 --- a/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs +++ b/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs @@ -15,6 +15,7 @@ using Content.Shared.Input; using Robust.Client.GameObjects; using Robust.Client.Graphics; +using Robust.Client.Input; using Robust.Client.Player; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controllers; @@ -42,6 +43,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged position && position >= 0) + _actions.RemoveAt(position); } } else if (button.TryReplaceWith(actionId.Value, _actionsSystem) && @@ -539,24 +548,23 @@ private void SetAction(ActionButton button, EntityUid? actionId, bool updateSlot private void DragAction() { - EntityUid? swapAction = null; - if (UIManager.CurrentlyHovered is ActionButton button) + if (_menuDragHelper.Dragged is not {ActionId: {} action} dragged) { - if (!_menuDragHelper.IsDragging || _menuDragHelper.Dragged?.ActionId is not { } type) - { - _menuDragHelper.EndDrag(); - return; - } - - swapAction = button.ActionId; - SetAction(button, type, false); + _menuDragHelper.EndDrag(); + return; } - if (_menuDragHelper.Dragged is {Parent: ActionButtonContainer} old) + EntityUid? swapAction = null; + var currentlyHovered = UIManager.MouseGetControl(_input.MouseScreenPosition); + if (currentlyHovered is ActionButton button) { - SetAction(old, swapAction, false); + swapAction = button.ActionId; + SetAction(button, action, false); } + if (dragged.Parent is ActionButtonContainer) + SetAction(dragged, swapAction, false); + if (_actionsSystem != null) _container?.SetActionData(_actionsSystem, _actions.ToArray()); @@ -610,27 +618,27 @@ private void OnWindowActionFocusExisted(ActionButton button) private void OnActionPressed(GUIBoundKeyEventArgs args, ActionButton button) { - if (args.Function == EngineKeyFunctions.UIClick) + if (args.Function == EngineKeyFunctions.UIRightClick) { - if (button.ActionId == null) - { - var ev = new FillActionSlotEvent(); - EntityManager.EventBus.RaiseEvent(EventSource.Local, ev); - if (ev.Action != null) - SetAction(button, ev.Action); - } - else - { - _menuDragHelper.MouseDown(button); - } - + SetAction(button, null); args.Handle(); + return; } - else if (args.Function == EngineKeyFunctions.UIRightClick) + + if (args.Function != EngineKeyFunctions.UIClick) + return; + + args.Handle(); + if (button.ActionId != null) { - SetAction(button, null); - args.Handle(); + _menuDragHelper.MouseDown(button); + return; } + + var ev = new FillActionSlotEvent(); + EntityManager.EventBus.RaiseEvent(EventSource.Local, ev); + if (ev.Action != null) + SetAction(button, ev.Action); } private void OnActionUnpressed(GUIBoundKeyEventArgs args, ActionButton button) @@ -638,33 +646,31 @@ private void OnActionUnpressed(GUIBoundKeyEventArgs args, ActionButton button) if (args.Function != EngineKeyFunctions.UIClick || _actionsSystem == null) return; - //todo: make dragging onto the same spot NOT trigger again - if (UIManager.CurrentlyHovered == button) - { - _menuDragHelper.EndDrag(); + args.Handle(); - if (_actionsSystem.TryGetActionData(button.ActionId, out var baseAction)) - { - if (baseAction is BaseTargetActionComponent action) - { - // for target actions, we go into "select target" mode, we don't - // message the server until we actually pick our target. - - // if we're clicking the same thing we're already targeting for, then we simply cancel - // targeting - ToggleTargeting(button.ActionId.Value, action); - return; - } - - _actionsSystem?.TriggerAction(button.ActionId.Value, baseAction); - } - } - else + if (_menuDragHelper.IsDragging) { DragAction(); + return; } - args.Handle(); + _menuDragHelper.EndDrag(); + + if (!_actionsSystem.TryGetActionData(button.ActionId, out var baseAction)) + return; + + if (baseAction is not BaseTargetActionComponent action) + { + _actionsSystem?.TriggerAction(button.ActionId.Value, baseAction); + return; + } + + // for target actions, we go into "select target" mode, we don't + // message the server until we actually pick our target. + + // if we're clicking the same thing we're already targeting for, then we simply cancel + // targeting + ToggleTargeting(button.ActionId.Value, action); } private bool OnMenuBeginDrag() diff --git a/Content.Client/UserInterface/Systems/Actions/Controls/ActionButton.cs b/Content.Client/UserInterface/Systems/Actions/Controls/ActionButton.cs index 2372d98f8d..6be41af0d8 100644 --- a/Content.Client/UserInterface/Systems/Actions/Controls/ActionButton.cs +++ b/Content.Client/UserInterface/Systems/Actions/Controls/ActionButton.cs @@ -152,16 +152,8 @@ public ActionButton(IEntityManager entities, SpriteSystem? spriteSys = null, Act OnThemeUpdated(); - OnKeyBindDown += args => - { - Depress(args, true); - OnPressed(args); - }; - OnKeyBindUp += args => - { - Depress(args, false); - OnUnpressed(args); - }; + OnKeyBindDown += OnPressed; + OnKeyBindUp += OnUnpressed; TooltipSupplier = SupplyTooltip; } @@ -175,11 +167,23 @@ protected override void OnThemeUpdated() private void OnPressed(GUIBoundKeyEventArgs args) { + if (args.Function != EngineKeyFunctions.UIClick && args.Function != EngineKeyFunctions.UIRightClick) + return; + + if (args.Function == EngineKeyFunctions.UIRightClick) + Depress(args, true); + ActionPressed?.Invoke(args, this); } private void OnUnpressed(GUIBoundKeyEventArgs args) { + if (args.Function != EngineKeyFunctions.UIClick && args.Function != EngineKeyFunctions.UIRightClick) + return; + + if (args.Function == EngineKeyFunctions.UIRightClick) + Depress(args, false); + ActionUnpressed?.Invoke(args, this); } @@ -281,10 +285,19 @@ public void UpdateIcons() _controller ??= UserInterfaceManager.GetUIController(); _spriteSys ??= _entities.System(); - if ((_controller.SelectingTargetFor == ActionId || _action.Toggled) && _action.IconOn != null) - SetActionIcon(_spriteSys.Frame0(_action.IconOn)); + if ((_controller.SelectingTargetFor == ActionId || _action.Toggled)) + { + if (_action.IconOn != null) + SetActionIcon(_spriteSys.Frame0(_action.IconOn)); + + if (_action.BackgroundOn != null) + _buttonBackgroundTexture = _spriteSys.Frame0(_action.BackgroundOn); + } else + { SetActionIcon(_action.Icon != null ? _spriteSys.Frame0(_action.Icon) : null); + _buttonBackgroundTexture = Theme.ResolveTexture("SlotBackground"); + } } public void UpdateBackground() @@ -378,12 +391,6 @@ public void Depress(GUIBoundKeyEventArgs args, bool depress) if (_action is not {Enabled: true}) return; - if (_depressed && !depress) - { - // fire the action - OnUnpressed(args); - } - _depressed = depress; DrawModeChanged(); } diff --git a/Content.Client/UserInterface/Systems/Alerts/Controls/AlertControl.cs b/Content.Client/UserInterface/Systems/Alerts/Controls/AlertControl.cs index 9423f7288d..6327757dec 100644 --- a/Content.Client/UserInterface/Systems/Alerts/Controls/AlertControl.cs +++ b/Content.Client/UserInterface/Systems/Alerts/Controls/AlertControl.cs @@ -3,7 +3,6 @@ using Content.Client.Cooldown; using Content.Shared.Alert; using Robust.Client.GameObjects; -using Robust.Client.Graphics; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Shared.Timing; @@ -118,7 +117,8 @@ protected override void Dispose(bool disposing) { base.Dispose(disposing); - _entityManager.DeleteEntity(_spriteViewEntity); + if (!_entityManager.Deleted(_spriteViewEntity)) + _entityManager.QueueDeleteEntity(_spriteViewEntity); } } diff --git a/Content.Client/UserInterface/Systems/Alerts/Widgets/AlertsUI.xaml b/Content.Client/UserInterface/Systems/Alerts/Widgets/AlertsUI.xaml index 9a273d2ed1..e52e2175b2 100644 --- a/Content.Client/UserInterface/Systems/Alerts/Widgets/AlertsUI.xaml +++ b/Content.Client/UserInterface/Systems/Alerts/Widgets/AlertsUI.xaml @@ -1,7 +1,11 @@  - - - + + + + + + diff --git a/Content.Client/UserInterface/Systems/Alerts/Widgets/AlertsUI.xaml.cs b/Content.Client/UserInterface/Systems/Alerts/Widgets/AlertsUI.xaml.cs index 189de50407..a1a494c47b 100644 --- a/Content.Client/UserInterface/Systems/Alerts/Widgets/AlertsUI.xaml.cs +++ b/Content.Client/UserInterface/Systems/Alerts/Widgets/AlertsUI.xaml.cs @@ -97,7 +97,8 @@ private void SyncUpdateControls(AlertsSystem alertsSystem, AlertOrderPrototype? } else { - if (existingAlertControl != null) AlertContainer.Children.Remove(existingAlertControl); + if (existingAlertControl != null) + AlertContainer.Children.Remove(existingAlertControl); // this is a new alert + alert key or just a different alert with the same // key, create the control and add it in the appropriate order diff --git a/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs b/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs index bc79283d76..b1d8b01050 100644 --- a/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs +++ b/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs @@ -550,7 +550,7 @@ private void UpdateChannelPermissions() } // only admins can see / filter asay - if (_admin.HasFlag(AdminFlags.Admin)) + if (_admin.HasFlag(AdminFlags.Adminchat)) { FilterableChannels |= ChatChannel.Admin; FilterableChannels |= ChatChannel.AdminAlert; diff --git a/Content.Client/UserInterface/Systems/Emotes/EmotesUIController.cs b/Content.Client/UserInterface/Systems/Emotes/EmotesUIController.cs new file mode 100644 index 0000000000..7b86859a1a --- /dev/null +++ b/Content.Client/UserInterface/Systems/Emotes/EmotesUIController.cs @@ -0,0 +1,125 @@ +using Content.Client.Chat.UI; +using Content.Client.Gameplay; +using Content.Client.UserInterface.Controls; +using Content.Shared.Chat; +using Content.Shared.Chat.Prototypes; +using Content.Shared.Input; +using JetBrains.Annotations; +using Robust.Client.Graphics; +using Robust.Client.Input; +using Robust.Client.UserInterface.Controllers; +using Robust.Client.UserInterface.Controls; +using Robust.Shared.Input.Binding; +using Robust.Shared.Prototypes; + +namespace Content.Client.UserInterface.Systems.Emotes; + +[UsedImplicitly] +public sealed class EmotesUIController : UIController, IOnStateChanged +{ + [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly IClyde _displayManager = default!; + [Dependency] private readonly IInputManager _inputManager = default!; + + private MenuButton? EmotesButton => UIManager.GetActiveUIWidgetOrNull()?.EmotesButton; + private EmotesMenu? _menu; + + public void OnStateEntered(GameplayState state) + { + CommandBinds.Builder + .Bind(ContentKeyFunctions.OpenEmotesMenu, + InputCmdHandler.FromDelegate(_ => ToggleEmotesMenu(false))) + .Register(); + } + + public void OnStateExited(GameplayState state) + { + CommandBinds.Unregister(); + } + + private void ToggleEmotesMenu(bool centered) + { + if (_menu == null) + { + // setup window + _menu = UIManager.CreateWindow(); + _menu.OnClose += OnWindowClosed; + _menu.OnOpen += OnWindowOpen; + _menu.OnPlayEmote += OnPlayEmote; + + if (EmotesButton != null) + EmotesButton.SetClickPressed(true); + + if (centered) + { + _menu.OpenCentered(); + } + else + { + // Open the menu, centered on the mouse + var vpSize = _displayManager.ScreenSize; + _menu.OpenCenteredAt(_inputManager.MouseScreenPosition.Position / vpSize); + } + } + else + { + _menu.OnClose -= OnWindowClosed; + _menu.OnOpen -= OnWindowOpen; + _menu.OnPlayEmote -= OnPlayEmote; + + if (EmotesButton != null) + EmotesButton.SetClickPressed(false); + + CloseMenu(); + } + } + + public void UnloadButton() + { + if (EmotesButton == null) + return; + + EmotesButton.OnPressed -= ActionButtonPressed; + } + + public void LoadButton() + { + if (EmotesButton == null) + return; + + EmotesButton.OnPressed += ActionButtonPressed; + } + + private void ActionButtonPressed(BaseButton.ButtonEventArgs args) + { + ToggleEmotesMenu(true); + } + + private void OnWindowClosed() + { + if (EmotesButton != null) + EmotesButton.Pressed = false; + + CloseMenu(); + } + + private void OnWindowOpen() + { + if (EmotesButton != null) + EmotesButton.Pressed = true; + } + + private void CloseMenu() + { + if (_menu == null) + return; + + _menu.Dispose(); + _menu = null; + } + + private void OnPlayEmote(ProtoId protoId) + { + _entityManager.RaisePredictiveEvent(new PlayEmoteMessage(protoId)); + } +} diff --git a/Content.Client/UserInterface/Systems/Ghost/Controls/GhostTargetWindow.xaml b/Content.Client/UserInterface/Systems/Ghost/Controls/GhostTargetWindow.xaml index 86fd09b2c8..32cab732d1 100644 --- a/Content.Client/UserInterface/Systems/Ghost/Controls/GhostTargetWindow.xaml +++ b/Content.Client/UserInterface/Systems/Ghost/Controls/GhostTargetWindow.xaml @@ -1,5 +1,6 @@ + + Text="{Loc 'analysis-console-server-list-button'}"> + + + + + @@ -36,13 +52,13 @@ - + - + diff --git a/Content.Client/Xenoarchaeology/Ui/AnalysisConsoleMenu.xaml.cs b/Content.Client/Xenoarchaeology/Ui/AnalysisConsoleMenu.xaml.cs index 90732f814f..2890bb3dbf 100644 --- a/Content.Client/Xenoarchaeology/Ui/AnalysisConsoleMenu.xaml.cs +++ b/Content.Client/Xenoarchaeology/Ui/AnalysisConsoleMenu.xaml.cs @@ -3,6 +3,7 @@ using Content.Shared.Xenoarchaeology.Equipment; using Robust.Client.AutoGenerated; using Robust.Client.GameObjects; +using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.XAML; using Robust.Shared.Timing; using Robust.Shared.Utility; @@ -19,6 +20,8 @@ public sealed partial class AnalysisConsoleMenu : FancyWindow public event Action? OnScanButtonPressed; public event Action? OnPrintButtonPressed; public event Action? OnExtractButtonPressed; + public event Action? OnUpBiasButtonPressed; + public event Action? OnDownBiasButtonPressed; // For rendering the progress bar, updated from BUI state private TimeSpan? _startTime; @@ -36,6 +39,12 @@ public AnalysisConsoleMenu() ScanButton.OnPressed += _ => OnScanButtonPressed?.Invoke(); PrintButton.OnPressed += _ => OnPrintButtonPressed?.Invoke(); ExtractButton.OnPressed += _ => OnExtractButtonPressed?.Invoke(); + UpBiasButton.OnPressed += _ => OnUpBiasButtonPressed?.Invoke(); + DownBiasButton.OnPressed += _ => OnDownBiasButtonPressed?.Invoke(); + + var buttonGroup = new ButtonGroup(false); + UpBiasButton.Group = buttonGroup; + DownBiasButton.Group = buttonGroup; } protected override void FrameUpdate(FrameEventArgs args) @@ -60,25 +69,55 @@ protected override void FrameUpdate(FrameEventArgs args) ProgressBar.Value = Math.Clamp(1.0f - (float) remaining.Divide(total), 0.0f, 1.0f); } - public void SetButtonsDisabled(AnalysisConsoleScanUpdateState state) + public void SetButtonsDisabled(AnalysisConsoleUpdateState state) { ScanButton.Disabled = !state.CanScan; PrintButton.Disabled = !state.CanPrint; + if (state.IsTraversalDown) + DownBiasButton.Pressed = true; + else + UpBiasButton.Pressed = true; - var disabled = !state.ServerConnected || !state.CanScan || state.PointAmount <= 0; - - ExtractButton.Disabled = disabled; + ExtractButton.Disabled = false; + if (!state.ServerConnected) + { + ExtractButton.Disabled = true; + ExtractButton.ToolTip = Loc.GetString("analysis-console-no-server-connected"); + } + else if (!state.CanScan) + { + ExtractButton.Disabled = true; + + // CanScan can be false if either there's no analyzer connected or if there's + // no entity on the scanner. The `Information` text will always tell the user + // of the former case, but in the latter, it'll only show a message if a scan + // has never been performed, so add a tooltip to indicate that the artifact + // is gone. + if (state.AnalyzerConnected) + { + ExtractButton.ToolTip = Loc.GetString("analysis-console-no-artifact-placed"); + } + else + { + ExtractButton.ToolTip = null; + } + } + else if (state.PointAmount <= 0) + { + ExtractButton.Disabled = true; + ExtractButton.ToolTip = Loc.GetString("analysis-console-no-points-to-extract"); + } - if (disabled) + if (ExtractButton.Disabled) { ExtractButton.RemoveStyleClass("ButtonColorGreen"); } else { ExtractButton.AddStyleClass("ButtonColorGreen"); + ExtractButton.ToolTip = null; } } - private void UpdateArtifactIcon(EntityUid? uid) { if (uid == null) @@ -91,7 +130,7 @@ private void UpdateArtifactIcon(EntityUid? uid) ArtifactDisplay.SetEntity(uid); } - public void UpdateInformationDisplay(AnalysisConsoleScanUpdateState state) + public void UpdateInformationDisplay(AnalysisConsoleUpdateState state) { var message = new FormattedMessage(); @@ -129,7 +168,7 @@ public void UpdateInformationDisplay(AnalysisConsoleScanUpdateState state) Information.SetMessage(message); } - public void UpdateProgressBar(AnalysisConsoleScanUpdateState state) + public void UpdateProgressBar(AnalysisConsoleUpdateState state) { ProgressBar.Visible = state.Scanning; ProgressLabel.Visible = state.Scanning; diff --git a/Content.Client/Xenonids/UI/XenoChoiceControl.xaml b/Content.Client/Xenonids/UI/XenoChoiceControl.xaml new file mode 100644 index 0000000000..1257fbc54b --- /dev/null +++ b/Content.Client/Xenonids/UI/XenoChoiceControl.xaml @@ -0,0 +1,17 @@ + + + + + \ No newline at end of file diff --git a/Content.Client/Xenonids/UI/XenoChoiceControl.xaml.cs b/Content.Client/Xenonids/UI/XenoChoiceControl.xaml.cs new file mode 100644 index 0000000000..ae451fffe0 --- /dev/null +++ b/Content.Client/Xenonids/UI/XenoChoiceControl.xaml.cs @@ -0,0 +1,26 @@ +using Robust.Client.AutoGenerated; +using Robust.Client.Graphics; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Utility; + +namespace Content.Client.Xenonids.UI; + +[GenerateTypedNameReferences] +[Virtual] +public partial class XenoChoiceControl : Control +{ + public XenoChoiceControl() => RobustXamlLoader.Load(this); + + public void Set(string name, Texture? texture) + { + NameLabel.SetMessage(name); + Texture.Texture = texture; + } + + public void Set(FormattedMessage msg, Texture? texture) + { + NameLabel.SetMessage(msg); + Texture.Texture = texture; + } +} diff --git a/Content.IntegrationTests/Pair/TestPair.Helpers.cs b/Content.IntegrationTests/Pair/TestPair.Helpers.cs index 0ea6d3e2dc..f46b83165f 100644 --- a/Content.IntegrationTests/Pair/TestPair.Helpers.cs +++ b/Content.IntegrationTests/Pair/TestPair.Helpers.cs @@ -2,6 +2,9 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using Content.Server.Preferences.Managers; +using Content.Shared.Preferences; +using Content.Shared.Roles; using Robust.Shared.GameObjects; using Robust.Shared.Map; using Robust.Shared.Prototypes; @@ -128,4 +131,29 @@ public List GetPrototypesWithComponent( return list; } + + /// + /// Helper method for enabling or disabling a antag role + /// + public async Task SetAntagPref(ProtoId id, bool value) + { + var prefMan = Server.ResolveDependency(); + + var prefs = prefMan.GetPreferences(Client.User!.Value); + // what even is the point of ICharacterProfile if we always cast it to HumanoidCharacterProfile to make it usable? + var profile = (HumanoidCharacterProfile) prefs.SelectedCharacter; + + Assert.That(profile.AntagPreferences.Any(preference => preference == id), Is.EqualTo(!value)); + var newProfile = profile.WithAntagPreference(id, value); + + await Server.WaitPost(() => + { + prefMan.SetProfile(Client.User.Value, prefs.SelectedCharacterIndex, newProfile).Wait(); + }); + + // And why the fuck does it always create a new preference and profile object instead of just reusing them? + var newPrefs = prefMan.GetPreferences(Client.User.Value); + var newProf = (HumanoidCharacterProfile) newPrefs.SelectedCharacter; + Assert.That(newProf.AntagPreferences.Any(preference => preference == id), Is.EqualTo(value)); + } } diff --git a/Content.IntegrationTests/Pair/TestPair.Recycle.cs b/Content.IntegrationTests/Pair/TestPair.Recycle.cs index 52fdf600bb..c0f4b3b745 100644 --- a/Content.IntegrationTests/Pair/TestPair.Recycle.cs +++ b/Content.IntegrationTests/Pair/TestPair.Recycle.cs @@ -131,7 +131,7 @@ public async Task CleanPooledPair(PoolSettings settings, TextWriter testOut) // Move to pre-round lobby. Required to toggle dummy ticker on and off if (gameTicker.RunLevel != GameRunLevel.PreRoundLobby) { - await testOut.WriteLineAsync($"Recycling: {Watch.Elapsed.TotalMilliseconds} ms: Restarting server."); + await testOut.WriteLineAsync($"Recycling: {Watch.Elapsed.TotalMilliseconds} ms: Restarting round."); Assert.That(gameTicker.DummyTicker, Is.False); Server.CfgMan.SetCVar(CCVars.GameLobbyEnabled, true); await Server.WaitPost(() => gameTicker.RestartRound()); @@ -146,6 +146,7 @@ public async Task CleanPooledPair(PoolSettings settings, TextWriter testOut) // Restart server. await testOut.WriteLineAsync($"Recycling: {Watch.Elapsed.TotalMilliseconds} ms: Restarting server again"); + await Server.WaitPost(() => Server.EntMan.FlushEntities()); await Server.WaitPost(() => gameTicker.RestartRound()); await RunTicksSync(1); diff --git a/Content.IntegrationTests/Pair/TestPair.Timing.cs b/Content.IntegrationTests/Pair/TestPair.Timing.cs index 3487ea6801..e0859660d4 100644 --- a/Content.IntegrationTests/Pair/TestPair.Timing.cs +++ b/Content.IntegrationTests/Pair/TestPair.Timing.cs @@ -1,5 +1,4 @@ #nullable enable -using Robust.Shared.Timing; namespace Content.IntegrationTests.Pair; @@ -19,6 +18,22 @@ public async Task RunTicksSync(int ticks) } } + /// + /// Convert a time interval to some number of ticks. + /// + public int SecondsToTicks(float seconds) + { + return (int) Math.Ceiling(seconds / Server.Timing.TickPeriod.TotalSeconds); + } + + /// + /// Run the server & client in sync for some amount of time + /// + public async Task RunSeconds(float seconds) + { + await RunTicksSync(SecondsToTicks(seconds)); + } + /// /// Runs the server-client pair in sync, but also ensures they are both idle each tick. /// @@ -59,4 +74,4 @@ public async Task SyncTicks(int targetDelta = 1) delta = cTick - sTick; Assert.That(delta, Is.EqualTo(targetDelta)); } -} \ No newline at end of file +} diff --git a/Content.IntegrationTests/PoolManager.Cvars.cs b/Content.IntegrationTests/PoolManager.Cvars.cs index 327ec627f5..5acd9d502c 100644 --- a/Content.IntegrationTests/PoolManager.Cvars.cs +++ b/Content.IntegrationTests/PoolManager.Cvars.cs @@ -21,7 +21,9 @@ private static readonly (string cvar, string value)[] TestCvars = (CCVars.NPCMaxUpdates.Name, "999999"), (CVars.ThreadParallelCount.Name, "1"), (CCVars.GameRoleTimers.Name, "false"), + (CCVars.GameRoleWhitelist.Name, "false"), (CCVars.GridFill.Name, "false"), + (CCVars.PreloadGrids.Name, "false"), (CCVars.ArrivalsShuttles.Name, "false"), (CCVars.EmergencyShuttleEnabled.Name, "false"), (CCVars.ProcgenPreload.Name, "false"), @@ -32,6 +34,7 @@ private static readonly (string cvar, string value)[] TestCvars = (CCVars.GameLobbyEnabled.Name, "false"), (CCVars.ConfigPresetDevelopment.Name, "false"), (CCVars.AdminLogsEnabled.Name, "false"), + (CCVars.AutosaveEnabled.Name, "false"), (CVars.NetBufferSize.Name, "0") }; diff --git a/Content.IntegrationTests/PoolManager.cs b/Content.IntegrationTests/PoolManager.cs index b544fe2854..3f489de649 100644 --- a/Content.IntegrationTests/PoolManager.cs +++ b/Content.IntegrationTests/PoolManager.cs @@ -68,11 +68,11 @@ public static partial class PoolManager options.BeforeStart += () => { + // Server-only systems (i.e., systems that subscribe to events with server-only components) var entSysMan = IoCManager.Resolve(); - entSysMan.LoadExtraSystemType(); - entSysMan.LoadExtraSystemType(); entSysMan.LoadExtraSystemType(); entSysMan.LoadExtraSystemType(); + IoCManager.Resolve().GetSawmill("loc").Level = LogLevel.Error; IoCManager.Resolve() .OnValueChanged(RTCVars.FailureLogLevel, value => logHandler.FailureLevel = value, true); diff --git a/Content.IntegrationTests/Tests/Construction/Interaction/MachineConstruction.cs b/Content.IntegrationTests/Tests/Construction/Interaction/MachineConstruction.cs index f52f820a4c..cd95a85f20 100644 --- a/Content.IntegrationTests/Tests/Construction/Interaction/MachineConstruction.cs +++ b/Content.IntegrationTests/Tests/Construction/Interaction/MachineConstruction.cs @@ -57,4 +57,3 @@ public async Task ChangeMachine() AssertPrototype("Autolathe"); } } - diff --git a/Content.IntegrationTests/Tests/Damageable/DamageableTest.cs b/Content.IntegrationTests/Tests/Damageable/DamageableTest.cs index 16744d83dc..c40b8ed286 100644 --- a/Content.IntegrationTests/Tests/Damageable/DamageableTest.cs +++ b/Content.IntegrationTests/Tests/Damageable/DamageableTest.cs @@ -19,36 +19,45 @@ public sealed class DamageableTest # Define some damage groups - type: damageType id: TestDamage1 + name: damage-type-blunt - type: damageType id: TestDamage2a + name: damage-type-blunt - type: damageType id: TestDamage2b + name: damage-type-blunt - type: damageType id: TestDamage3a + name: damage-type-blunt - type: damageType id: TestDamage3b + name: damage-type-blunt - type: damageType id: TestDamage3c + name: damage-type-blunt # Define damage Groups with 1,2,3 damage types - type: damageGroup id: TestGroup1 + name: damage-group-brute damageTypes: - TestDamage1 - type: damageGroup id: TestGroup2 + name: damage-group-brute damageTypes: - TestDamage2a - TestDamage2b - type: damageGroup id: TestGroup3 + name: damage-group-brute damageTypes: - TestDamage3a - TestDamage3b diff --git a/Content.IntegrationTests/Tests/Destructible/DestructibleTestPrototypes.cs b/Content.IntegrationTests/Tests/Destructible/DestructibleTestPrototypes.cs index 12292f4652..7ff9242398 100644 --- a/Content.IntegrationTests/Tests/Destructible/DestructibleTestPrototypes.cs +++ b/Content.IntegrationTests/Tests/Destructible/DestructibleTestPrototypes.cs @@ -12,24 +12,31 @@ public static class DestructibleTestPrototypes public const string DamagePrototypes = $@" - type: damageType id: TestBlunt + name: damage-type-blunt - type: damageType id: TestSlash + name: damage-type-slash - type: damageType id: TestPiercing + name: damage-type-piercing - type: damageType id: TestHeat + name: damage-type-heat - type: damageType id: TestShock + name: damage-type-shock - type: damageType id: TestCold + name: damage-type-cold - type: damageGroup id: TestBrute + name: damage-group-brute damageTypes: - TestBlunt - TestSlash @@ -37,6 +44,7 @@ public static class DestructibleTestPrototypes - type: damageGroup id: TestBurn + name: damage-group-burn damageTypes: - TestHeat - TestShock diff --git a/Content.IntegrationTests/Tests/DoAfter/DoAfterCancellationTests.cs b/Content.IntegrationTests/Tests/DoAfter/DoAfterCancellationTests.cs index d47eb13273..0ebd17d887 100644 --- a/Content.IntegrationTests/Tests/DoAfter/DoAfterCancellationTests.cs +++ b/Content.IntegrationTests/Tests/DoAfter/DoAfterCancellationTests.cs @@ -3,8 +3,6 @@ using Content.IntegrationTests.Tests.Interaction; using Content.IntegrationTests.Tests.Weldable; using Content.Shared.Tools.Components; -using Content.Server.Tools.Components; -using Content.Shared.DoAfter; namespace Content.IntegrationTests.Tests.DoAfter; diff --git a/Content.IntegrationTests/Tests/EntityTest.cs b/Content.IntegrationTests/Tests/EntityTest.cs index d3b1fb4722..926374cf05 100644 --- a/Content.IntegrationTests/Tests/EntityTest.cs +++ b/Content.IntegrationTests/Tests/EntityTest.cs @@ -19,6 +19,8 @@ namespace Content.IntegrationTests.Tests [TestOf(typeof(EntityUid))] public sealed class EntityTest { + private static readonly ProtoId SpawnerCategory = "Spawner"; + [Test] public async Task SpawnAndDeleteAllEntitiesOnDifferentMaps() { @@ -234,14 +236,6 @@ public async Task SpawnAndDeleteEntityCountTest() "StationEvent", "TimedDespawn", - // Spawner entities - "DragonRift", - "RandomHumanoidSpawner", - "RandomSpawner", - "ConditionalSpawner", - "GhostRoleMobSpawner", - "NukeOperativeSpawner", - "TimedSpawner", // makes an announcement on mapInit. "AnnounceOnSpawn", }; @@ -253,6 +247,7 @@ public async Task SpawnAndDeleteEntityCountTest() .Where(p => !p.Abstract) .Where(p => !pair.IsTestPrototype(p)) .Where(p => !excluded.Any(p.Components.ContainsKey)) + .Where(p => p.Categories.All(x => x.ID != SpawnerCategory)) .Select(p => p.ID) .ToList(); @@ -350,8 +345,12 @@ public async Task AllComponentsOneToOneDeleteTest() "DebrisFeaturePlacerController", // Above. "LoadedChunk", // Worldgen chunk loading malding. "BiomeSelection", // Whaddya know, requires config. + "ActivatableUI", // Requires enum key }; + // TODO TESTS + // auto ignore any components that have a "required" data field. + await using var pair = await PoolManager.GetServerClient(); var server = pair.Server; var entityManager = server.ResolveDependency(); diff --git a/Content.IntegrationTests/Tests/GameObjects/Components/Mobs/AlertsComponentTests.cs b/Content.IntegrationTests/Tests/GameObjects/Components/Mobs/AlertsComponentTests.cs index 1da77ac558..b0aceacc03 100644 --- a/Content.IntegrationTests/Tests/GameObjects/Components/Mobs/AlertsComponentTests.cs +++ b/Content.IntegrationTests/Tests/GameObjects/Components/Mobs/AlertsComponentTests.cs @@ -84,11 +84,13 @@ static AlertsUI FindAlertsUI(Control control) return null; } - // we should be seeing 3 alerts - our health, and the 2 debug alerts, in a specific order. - Assert.That(clientAlertsUI.AlertContainer.ChildCount, Is.GreaterThanOrEqualTo(3)); + // This originally was hardcoded to expect a player character to have a Human Healthbar. + // It is no longer hardcoded to demand that. + // We should be seeing 2 alerts - the 2 debug alerts, in a specific order. + Assert.That(clientAlertsUI.AlertContainer.ChildCount, Is.GreaterThanOrEqualTo(2)); var alertControls = clientAlertsUI.AlertContainer.Children.Select(c => (AlertControl) c); var alertIDs = alertControls.Select(ac => ac.Alert.AlertType).ToArray(); - var expectedIDs = new[] { AlertType.HumanHealth, AlertType.Debug1, AlertType.Debug2 }; + var expectedIDs = new[] { AlertType.Debug1, AlertType.Debug2 }; Assert.That(alertIDs, Is.SupersetOf(expectedIDs)); }); @@ -101,11 +103,11 @@ await server.WaitAssertion(() => await client.WaitAssertion(() => { - // we should be seeing 2 alerts now because one was cleared - Assert.That(clientAlertsUI.AlertContainer.ChildCount, Is.GreaterThanOrEqualTo(2)); + // We should be seeing 1 alert now because one was cleared + Assert.That(clientAlertsUI.AlertContainer.ChildCount, Is.GreaterThanOrEqualTo(1)); var alertControls = clientAlertsUI.AlertContainer.Children.Select(c => (AlertControl) c); var alertIDs = alertControls.Select(ac => ac.Alert.AlertType).ToArray(); - var expectedIDs = new[] { AlertType.HumanHealth, AlertType.Debug2 }; + var expectedIDs = new[] { AlertType.Debug2 }; Assert.That(alertIDs, Is.SupersetOf(expectedIDs)); }); diff --git a/Content.IntegrationTests/Tests/GameRules/AntagPreferenceTest.cs b/Content.IntegrationTests/Tests/GameRules/AntagPreferenceTest.cs new file mode 100644 index 0000000000..662ea3b974 --- /dev/null +++ b/Content.IntegrationTests/Tests/GameRules/AntagPreferenceTest.cs @@ -0,0 +1,76 @@ +#nullable enable +using System.Collections.Generic; +using System.Linq; +using Content.Server.Antag; +using Content.Server.Antag.Components; +using Content.Server.GameTicking; +using Content.Shared.GameTicking; +using Robust.Shared.GameObjects; +using Robust.Shared.Player; +using Robust.Shared.Random; + +namespace Content.IntegrationTests.Tests.GameRules; + +// Once upon a time, players in the lobby weren't ever considered eligible for antag roles. +// Lets not let that happen again. +[TestFixture] +public sealed class AntagPreferenceTest +{ + [Test] + public async Task TestLobbyPlayersValid() + { + await using var pair = await PoolManager.GetServerClient(new PoolSettings + { + DummyTicker = false, + Connected = true, + InLobby = true + }); + + var server = pair.Server; + var client = pair.Client; + var ticker = server.System(); + var sys = server.System(); + + // Initially in the lobby + Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby)); + Assert.That(client.AttachedEntity, Is.Null); + Assert.That(ticker.PlayerGameStatuses[client.User!.Value], Is.EqualTo(PlayerGameStatus.NotReadyToPlay)); + + EntityUid uid = default; + await server.WaitPost(() => uid = server.EntMan.Spawn("Traitor")); + var rule = new Entity(uid, server.EntMan.GetComponent(uid)); + var def = rule.Comp.Definitions.Single(); + + // IsSessionValid & IsEntityValid are preference agnostic and should always be true for players in the lobby. + // Though maybe that will change in the future, but then GetPlayerPool() needs to be updated to reflect that. + Assert.That(sys.IsSessionValid(rule, pair.Player, def), Is.True); + Assert.That(sys.IsEntityValid(client.AttachedEntity, def), Is.True); + + // By default, traitor/antag preferences are disabled, so the pool should be empty. + var sessions = new List{pair.Player!}; + var pool = sys.GetPlayerPool(rule, sessions, def); + Assert.That(pool.Count, Is.EqualTo(0)); + + // Opt into the traitor role. + await pair.SetAntagPref("Traitor", true); + + Assert.That(sys.IsSessionValid(rule, pair.Player, def), Is.True); + Assert.That(sys.IsEntityValid(client.AttachedEntity, def), Is.True); + pool = sys.GetPlayerPool(rule, sessions, def); + Assert.That(pool.Count, Is.EqualTo(1)); + pool.TryPickAndTake(pair.Server.ResolveDependency(), out var picked); + Assert.That(picked, Is.EqualTo(pair.Player)); + Assert.That(sessions.Count, Is.EqualTo(1)); + + // opt back out + await pair.SetAntagPref("Traitor", false); + + Assert.That(sys.IsSessionValid(rule, pair.Player, def), Is.True); + Assert.That(sys.IsEntityValid(client.AttachedEntity, def), Is.True); + pool = sys.GetPlayerPool(rule, sessions, def); + Assert.That(pool.Count, Is.EqualTo(0)); + + await server.WaitPost(() => server.EntMan.DeleteEntity(uid)); + await pair.CleanReturnAsync(); + } +} diff --git a/Content.IntegrationTests/Tests/GameRules/FailAndStartPresetTest.cs b/Content.IntegrationTests/Tests/GameRules/FailAndStartPresetTest.cs new file mode 100644 index 0000000000..d0e0255ae7 --- /dev/null +++ b/Content.IntegrationTests/Tests/GameRules/FailAndStartPresetTest.cs @@ -0,0 +1,152 @@ +// #nullable enable +// using Content.Server.GameTicking; +// using Content.Server.GameTicking.Components; +// using Content.Server.GameTicking.Presets; +// using Content.Shared.CCVar; +// using Content.Shared.GameTicking; +// using Robust.Shared.GameObjects; + +// namespace Content.IntegrationTests.Tests.GameRules; + +// [TestFixture] +// public sealed class FailAndStartPresetTest +// { +// [TestPrototypes] +// private const string Prototypes = @" +// - type: gamePreset +// id: TestPreset +// alias: +// - nukeops +// name: Test Preset +// description: """" +// showInVote: false +// rules: +// - TestRule + +// - type: gamePreset +// id: TestPresetTenPlayers +// alias: +// - nukeops +// name: Test Preset 10 players +// description: """" +// showInVote: false +// rules: +// - TestRuleTenPlayers + +// - type: entity +// id: TestRule +// parent: BaseGameRule +// noSpawn: true +// components: +// - type: GameRule +// minPlayers: 0 +// - type: TestRule + +// - type: entity +// id: TestRuleTenPlayers +// parent: BaseGameRule +// noSpawn: true +// components: +// - type: GameRule +// minPlayers: 10 +// - type: TestRule +// "; + +// /// +// /// Test that a nuke ops gamemode can start after failing to start once. +// /// +// [Test] +// public async Task FailAndStartTest() +// { +// await using var pair = await PoolManager.GetServerClient(new PoolSettings +// { +// Dirty = true, +// DummyTicker = false, +// Connected = true, +// InLobby = true +// }); + +// var server = pair.Server; +// var client = pair.Client; +// var entMan = server.EntMan; +// var ticker = server.System(); +// server.System().Run = true; + +// Assert.That(server.CfgMan.GetCVar(CCVars.GridFill), Is.False); +// Assert.That(server.CfgMan.GetCVar(CCVars.GameLobbyFallbackEnabled), Is.True); +// Assert.That(server.CfgMan.GetCVar(CCVars.GameLobbyDefaultPreset), Is.EqualTo("secret")); +// server.CfgMan.SetCVar(CCVars.GridFill, true); +// server.CfgMan.SetCVar(CCVars.GameLobbyFallbackEnabled, false); +// server.CfgMan.SetCVar(CCVars.GameLobbyDefaultPreset, "TestPreset"); + +// // Initially in the lobby +// Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby)); +// Assert.That(client.AttachedEntity, Is.Null); +// Assert.That(ticker.PlayerGameStatuses[client.User!.Value], Is.EqualTo(PlayerGameStatus.NotReadyToPlay)); + +// // Try to start nukeops without readying up +// await pair.WaitCommand("setgamepreset TestPresetTenPlayers"); +// await pair.WaitCommand("startround"); +// await pair.RunTicksSync(10); + +// // Game should not have started +// Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby)); +// Assert.That(ticker.PlayerGameStatuses[client.User!.Value], Is.EqualTo(PlayerGameStatus.NotReadyToPlay)); +// Assert.That(!client.EntMan.EntityExists(client.AttachedEntity)); +// var player = pair.Player!.AttachedEntity; +// Assert.That(!entMan.EntityExists(player)); + +// // Ready up and start nukeops +// await pair.WaitClientCommand("toggleready True"); +// Assert.That(ticker.PlayerGameStatuses[client.User!.Value], Is.EqualTo(PlayerGameStatus.ReadyToPlay)); +// await pair.WaitCommand("setgamepreset TestPreset"); +// await pair.WaitCommand("startround"); +// await pair.RunTicksSync(10); + +// // Game should have started +// Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.InRound)); +// Assert.That(ticker.PlayerGameStatuses[client.User!.Value], Is.EqualTo(PlayerGameStatus.JoinedGame)); +// Assert.That(client.EntMan.EntityExists(client.AttachedEntity)); +// player = pair.Player!.AttachedEntity!.Value; +// Assert.That(entMan.EntityExists(player)); + +// ticker.SetGamePreset((GamePresetPrototype?)null); +// server.CfgMan.SetCVar(CCVars.GridFill, false); +// server.CfgMan.SetCVar(CCVars.GameLobbyFallbackEnabled, true); +// server.CfgMan.SetCVar(CCVars.GameLobbyDefaultPreset, "secret"); +// server.System().Run = false; +// await pair.CleanReturnAsync(); +// } +// } + +// public sealed class TestRuleSystem : EntitySystem +// { +// public bool Run; + +// public override void Initialize() +// { +// SubscribeLocalEvent(OnRoundStartAttempt); +// } + +// private void OnRoundStartAttempt(RoundStartAttemptEvent args) +// { +// if (!Run) +// return; + +// if (args.Forced || args.Cancelled) +// return; + +// var query = EntityQueryEnumerator(); +// while (query.MoveNext(out _, out _, out var gameRule)) +// { +// var minPlayers = gameRule.MinPlayers; +// if (args.Players.Length >= minPlayers) +// continue; + +// args.Cancel(); +// } +// } +// } + +// [RegisterComponent] +// public sealed partial class TestRuleComponent : Component; diff --git a/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs new file mode 100644 index 0000000000..822f7967cf --- /dev/null +++ b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs @@ -0,0 +1,226 @@ +/* WD edit + +#nullable enable +using System.Linq; +using Content.Server.Body.Components; +using Content.Server.GameTicking; +using Content.Server.GameTicking.Presets; +using Content.Server.GameTicking.Rules.Components; +using Content.Server.Mind; +using Content.Server.NPC.Systems; +using Content.Server.Pinpointer; +using Content.Server.Roles; +using Content.Server.Shuttles.Components; +using Content.Server.Station.Components; +using Content.Shared.CCVar; +using Content.Shared.Damage; +using Content.Shared.FixedPoint; +using Content.Shared.GameTicking; +using Content.Shared.Hands.Components; +using Content.Shared.Inventory; +using Content.Shared.NPC.Systems; +using Content.Shared.NukeOps; +using Robust.Server.GameObjects; +using Robust.Shared.GameObjects; +using Robust.Shared.Map.Components; + +namespace Content.IntegrationTests.Tests.GameRules; + +[TestFixture] +public sealed class NukeOpsTest +{ + /// + /// Check that a nuke ops game mode can start without issue. I.e., that the nuke station and such all get loaded. + /// + [Test] + public async Task TryStopNukeOpsFromConstantlyFailing() + { + await using var pair = await PoolManager.GetServerClient(new PoolSettings + { + Dirty = true, + DummyTicker = false, + Connected = true, + InLobby = true + }); + + var server = pair.Server; + var client = pair.Client; + var entMan = server.EntMan; + var mapSys = server.System(); + var ticker = server.System(); + var mindSys = server.System(); + var roleSys = server.System(); + var invSys = server.System(); + var factionSys = server.System(); + + Assert.That(server.CfgMan.GetCVar(CCVars.GridFill), Is.False); + server.CfgMan.SetCVar(CCVars.GridFill, true); + + // Initially in the lobby + Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby)); + Assert.That(client.AttachedEntity, Is.Null); + Assert.That(ticker.PlayerGameStatuses[client.User!.Value], Is.EqualTo(PlayerGameStatus.NotReadyToPlay)); + + // Opt into the nukies role. + await pair.SetAntagPref("NukeopsCommander", true); + + // There are no grids or maps + Assert.That(entMan.Count(), Is.Zero); + Assert.That(entMan.Count(), Is.Zero); + Assert.That(entMan.Count(), Is.Zero); + Assert.That(entMan.Count(), Is.Zero); + Assert.That(entMan.Count(), Is.Zero); + + // And no nukie related components + Assert.That(entMan.Count(), Is.Zero); + Assert.That(entMan.Count(), Is.Zero); + Assert.That(entMan.Count(), Is.Zero); + Assert.That(entMan.Count(), Is.Zero); + Assert.That(entMan.Count(), Is.Zero); + + // Ready up and start nukeops + await pair.WaitClientCommand("toggleready True"); + Assert.That(ticker.PlayerGameStatuses[client.User!.Value], Is.EqualTo(PlayerGameStatus.ReadyToPlay)); + await pair.WaitCommand("forcepreset Nukeops"); + await pair.RunTicksSync(10); + + // Game should have started + Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.InRound)); + Assert.That(ticker.PlayerGameStatuses[client.User!.Value], Is.EqualTo(PlayerGameStatus.JoinedGame)); + Assert.That(client.EntMan.EntityExists(client.AttachedEntity)); + var player = pair.Player!.AttachedEntity!.Value; + Assert.That(entMan.EntityExists(player)); + + // Maps now exist + Assert.That(entMan.Count(), Is.GreaterThan(0)); + Assert.That(entMan.Count(), Is.GreaterThan(0)); + Assert.That(entMan.Count(), Is.EqualTo(2)); // The main station & nukie station + Assert.That(entMan.Count(), Is.GreaterThan(3)); // Each station has at least 1 grid, plus some shuttles + Assert.That(entMan.Count(), Is.EqualTo(1)); + + // And we now have nukie related components + Assert.That(entMan.Count(), Is.EqualTo(1)); + Assert.That(entMan.Count(), Is.EqualTo(1)); + Assert.That(entMan.Count(), Is.EqualTo(1)); + Assert.That(entMan.Count(), Is.EqualTo(1)); + + // The player entity should be the nukie commander + var mind = mindSys.GetMind(player)!.Value; + Assert.That(entMan.HasComponent(player)); + Assert.That(roleSys.MindIsAntagonist(mind)); + Assert.That(roleSys.MindHasRole(mind)); + + Assert.That(factionSys.IsMember(player, "Syndicate"), Is.True); + Assert.That(factionSys.IsMember(player, "NanoTrasen"), Is.False); + + var roles = roleSys.MindGetAllRoles(mind); + var cmdRoles = roles.Where(x => x.Prototype == "NukeopsCommander" && x.Component is NukeopsRoleComponent); + Assert.That(cmdRoles.Count(), Is.EqualTo(1)); + + // The game rule exists, and all the stations/shuttles/maps are properly initialized + var rule = entMan.AllComponents().Single().Component; + var mapRule = entMan.AllComponents().Single().Component; + foreach (var grid in mapRule.MapGrids) + { + Assert.That(entMan.EntityExists(grid)); + Assert.That(entMan.HasComponent(grid)); + Assert.That(entMan.HasComponent(grid)); + } + Assert.That(entMan.EntityExists(rule.NukieOutpost)); + Assert.That(entMan.EntityExists(rule.NukieShuttle)); + Assert.That(entMan.EntityExists(rule.TargetStation)); + + Assert.That(entMan.HasComponent(rule.NukieOutpost)); + Assert.That(entMan.HasComponent(rule.NukieShuttle)); + + Assert.That(entMan.HasComponent(rule.NukieOutpost)); + Assert.That(entMan.HasComponent(rule.TargetStation)); + + var nukieShuttlEnt = entMan.AllComponents().FirstOrDefault().Uid; + Assert.That(entMan.EntityExists(nukieShuttlEnt)); + + EntityUid? nukieStationEnt = null; + foreach (var grid in mapRule.MapGrids) + { + if (entMan.HasComponent(grid)) + { + nukieStationEnt = grid; + break; + } + } + + Assert.That(entMan.EntityExists(nukieStationEnt)); + var nukieStation = entMan.GetComponent(nukieStationEnt!.Value); + + var nukieStation = entMan.GetComponent(rule.NukieOutpost!.Value); + Assert.That(entMan.EntityExists(nukieStation.Station)); + Assert.That(nukieStation.Station, Is.Not.EqualTo(rule.TargetStation)); + + Assert.That(server.MapMan.MapExists(mapRule.Map)); + var nukieMap = mapSys.GetMap(mapRule.Map!.Value); + Assert.That(server.MapMan.MapExists(rule.NukiePlanet)); + var nukieMap = mapSys.GetMap(rule.NukiePlanet!.Value); + + var targetStation = entMan.GetComponent(rule.TargetStation!.Value); + var targetGrid = targetStation.Grids.First(); + var targetMap = entMan.GetComponent(targetGrid).MapUid!.Value; + Assert.That(targetMap, Is.Not.EqualTo(nukieMap)); + + Assert.That(entMan.GetComponent(player).MapUid, Is.EqualTo(nukieMap)); + Assert.That(entMan.GetComponent(nukieStationEnt.Value).MapUid, Is.EqualTo(nukieMap)); + Assert.That(entMan.GetComponent(nukieShuttlEnt).MapUid, Is.EqualTo(nukieMap)); + Assert.That(entMan.GetComponent(rule.NukieOutpost!.Value).MapUid, Is.EqualTo(nukieMap)); + Assert.That(entMan.GetComponent(rule.NukieShuttle!.Value).MapUid, Is.EqualTo(nukieMap)); + + // The maps are all map-initialized, including the player + // Yes, this is necessary as this has repeatedly been broken somehow. + Assert.That(mapSys.IsInitialized(nukieMap)); + Assert.That(mapSys.IsInitialized(targetMap)); + Assert.That(mapSys.IsPaused(nukieMap), Is.False); + Assert.That(mapSys.IsPaused(targetMap), Is.False); + + EntityLifeStage LifeStage(EntityUid? uid) => entMan.GetComponent(uid!.Value).EntityLifeStage; + Assert.That(LifeStage(player), Is.GreaterThan(EntityLifeStage.Initialized)); + Assert.That(LifeStage(nukieMap), Is.GreaterThan(EntityLifeStage.Initialized)); + Assert.That(LifeStage(targetMap), Is.GreaterThan(EntityLifeStage.Initialized)); + Assert.That(LifeStage(nukieStationEnt.Value), Is.GreaterThan(EntityLifeStage.Initialized)); + Assert.That(LifeStage(nukieShuttlEnt), Is.GreaterThan(EntityLifeStage.Initialized)); + Assert.That(LifeStage(rule.NukieOutpost), Is.GreaterThan(EntityLifeStage.Initialized)); + Assert.That(LifeStage(rule.NukieShuttle), Is.GreaterThan(EntityLifeStage.Initialized)); + Assert.That(LifeStage(rule.TargetStation), Is.GreaterThan(EntityLifeStage.Initialized)); + + // Make sure the player has hands. We've had fucking disarmed nukies before. + Assert.That(entMan.HasComponent(player)); + Assert.That(entMan.GetComponent(player).Hands.Count, Is.GreaterThan(0)); + + // While we're at it, lets make sure they aren't naked. I don't know how many inventory slots all mobs will be + // likely to have in the future. But nukies should probably have at least 3 slots with something in them. + var enumerator = invSys.GetSlotEnumerator(player); + int total = 0; + while (enumerator.NextItem(out _)) + { + total++; + } + Assert.That(total, Is.GreaterThan(3)); + + // Finally lets check the nukie commander passed basic training and figured out how to breathe. + var totalSeconds = 30; + var totalTicks = (int) Math.Ceiling(totalSeconds / server.Timing.TickPeriod.TotalSeconds); + int increment = 5; + var resp = entMan.GetComponent(player); + var damage = entMan.GetComponent(player); + for (var tick = 0; tick < totalTicks; tick += increment) + { + await pair.RunTicksSync(increment); + Assert.That(resp.SuffocationCycles, Is.LessThanOrEqualTo(resp.SuffocationCycleThreshold)); + Assert.That(damage.TotalDamage, Is.EqualTo(FixedPoint2.Zero)); + } + + //ticker.SetGamePreset((GamePresetPrototype?)null); WD edit + server.CfgMan.SetCVar(CCVars.GridFill, false); + await pair.SetAntagPref("NukeopsCommander", false); + await pair.CleanReturnAsync(); + } +} + +*/ diff --git a/Content.IntegrationTests/Tests/GameRules/RuleMaxTimeRestartTest.cs b/Content.IntegrationTests/Tests/GameRules/RuleMaxTimeRestartTest.cs index 1e3f9c9854..20a157e33e 100644 --- a/Content.IntegrationTests/Tests/GameRules/RuleMaxTimeRestartTest.cs +++ b/Content.IntegrationTests/Tests/GameRules/RuleMaxTimeRestartTest.cs @@ -1,5 +1,6 @@ using Content.Server.GameTicking; using Content.Server.GameTicking.Commands; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules; using Content.Server.GameTicking.Rules.Components; using Content.Shared.CCVar; @@ -19,6 +20,9 @@ public async Task RestartTest() await using var pair = await PoolManager.GetServerClient(new PoolSettings { InLobby = true }); var server = pair.Server; + Assert.That(server.EntMan.Count(), Is.Zero); + Assert.That(server.EntMan.Count(), Is.Zero); + var entityManager = server.ResolveDependency(); var sGameTicker = server.ResolveDependency().GetEntitySystem(); var sGameTiming = server.ResolveDependency(); @@ -26,6 +30,9 @@ public async Task RestartTest() sGameTicker.StartGameRule("MaxTimeRestart", out var ruleEntity); Assert.That(entityManager.TryGetComponent(ruleEntity, out var maxTime)); + Assert.That(server.EntMan.Count(), Is.EqualTo(1)); + Assert.That(server.EntMan.Count(), Is.EqualTo(1)); + await server.WaitAssertion(() => { Assert.That(sGameTicker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby)); @@ -33,6 +40,9 @@ await server.WaitAssertion(() => sGameTicker.StartRound(); }); + Assert.That(server.EntMan.Count(), Is.EqualTo(1)); + Assert.That(server.EntMan.Count(), Is.EqualTo(1)); + await server.WaitAssertion(() => { Assert.That(sGameTicker.RunLevel, Is.EqualTo(GameRunLevel.InRound)); diff --git a/Content.IntegrationTests/Tests/GameRules/SecretStartsTest.cs b/Content.IntegrationTests/Tests/GameRules/SecretStartsTest.cs index 0f665a63de..5d7ae8efbf 100644 --- a/Content.IntegrationTests/Tests/GameRules/SecretStartsTest.cs +++ b/Content.IntegrationTests/Tests/GameRules/SecretStartsTest.cs @@ -17,6 +17,7 @@ public async Task TestSecretStarts() var server = pair.Server; await server.WaitIdleAsync(); + var entMan = server.ResolveDependency(); var gameTicker = server.ResolveDependency().GetEntitySystem(); await server.WaitAssertion(() => @@ -32,10 +33,7 @@ await server.WaitAssertion(() => await server.WaitAssertion(() => { - foreach (var rule in gameTicker.GetAddedGameRules()) - { - Assert.That(gameTicker.GetActiveGameRules(), Does.Contain(rule)); - } + Assert.That(gameTicker.GetAddedGameRules().Count(), Is.GreaterThan(1), $"No additional rules started by secret rule."); // End all rules gameTicker.ClearGameRules(); diff --git a/Content.IntegrationTests/Tests/Interaction/Click/InteractionSystemTests.cs b/Content.IntegrationTests/Tests/Interaction/Click/InteractionSystemTests.cs index 317aa10400..4415eddf37 100644 --- a/Content.IntegrationTests/Tests/Interaction/Click/InteractionSystemTests.cs +++ b/Content.IntegrationTests/Tests/Interaction/Click/InteractionSystemTests.cs @@ -407,7 +407,6 @@ await server.WaitAssertion(() => await pair.CleanReturnAsync(); } - [Reflect(false)] public sealed class TestInteractionSystem : EntitySystem { public EntityEventHandler? InteractUsingEvent; diff --git a/Content.IntegrationTests/Tests/Interaction/InteractionTest.Constants.cs b/Content.IntegrationTests/Tests/Interaction/InteractionTest.Constants.cs index 11381fb8cc..a915e5d47d 100644 --- a/Content.IntegrationTests/Tests/Interaction/InteractionTest.Constants.cs +++ b/Content.IntegrationTests/Tests/Interaction/InteractionTest.Constants.cs @@ -1,3 +1,4 @@ + namespace Content.IntegrationTests.Tests.Interaction; // This partial class contains various constant prototype IDs common to interaction tests. @@ -27,8 +28,6 @@ public abstract partial class InteractionTest // Parts protected const string Bin1 = "MatterBinStockPart"; - protected const string Cap1 = "CapacitorStockPart"; protected const string Manipulator1 = "MicroManipulatorStockPart"; - protected const string Battery1 = "PowerCellSmall"; - protected const string Battery4 = "PowerCellHyper"; } + diff --git a/Content.IntegrationTests/Tests/Interaction/InteractionTest.EntitySpecifier.cs b/Content.IntegrationTests/Tests/Interaction/InteractionTest.EntitySpecifier.cs index 414cf4bb56..37dca72137 100644 --- a/Content.IntegrationTests/Tests/Interaction/InteractionTest.EntitySpecifier.cs +++ b/Content.IntegrationTests/Tests/Interaction/InteractionTest.EntitySpecifier.cs @@ -3,6 +3,7 @@ using Robust.Shared.GameObjects; using Robust.Shared.Map; using Robust.Shared.Prototypes; +using static Robust.UnitTesting.RobustIntegrationTest; namespace Content.IntegrationTests.Tests.Interaction; @@ -54,7 +55,7 @@ public static implicit operator EntitySpecifier((string, int) tuple) /// /// Convert applicable entity prototypes into stack prototypes. /// - public void ConvertToStack(IPrototypeManager protoMan, IComponentFactory factory) + public async Task ConvertToStack(IPrototypeManager protoMan, IComponentFactory factory, ServerIntegrationInstance server) { if (Converted) return; @@ -73,11 +74,14 @@ public void ConvertToStack(IPrototypeManager protoMan, IComponentFactory factory return; } - if (entProto.TryGetComponent(factory.GetComponentName(typeof(StackComponent)), - out var stackComp)) + StackComponent? stack = null; + await server.WaitPost(() => { - Prototype = stackComp.StackTypeId; - } + entProto.TryGetComponent(factory.GetComponentName(typeof(StackComponent)), out stack); + }); + + if (stack != null) + Prototype = stack.StackTypeId; } } @@ -100,11 +104,14 @@ await Server.WaitPost(() => return default; } - if (entProto.TryGetComponent(Factory.GetComponentName(typeof(StackComponent)), - out var stackComp)) + StackComponent? stack = null; + await Server.WaitPost(() => { - return await SpawnEntity((stackComp.StackTypeId, spec.Quantity), coords); - } + entProto.TryGetComponent(Factory.GetComponentName(typeof(StackComponent)), out stack); + }); + + if (stack != null) + return await SpawnEntity((stack.StackTypeId, spec.Quantity), coords); Assert.That(spec.Quantity, Is.EqualTo(1), "SpawnEntity only supports returning a singular entity"); await Server.WaitPost(() => uid = SEntMan.SpawnEntity(spec.Prototype, coords)); diff --git a/Content.IntegrationTests/Tests/Interaction/InteractionTest.EntitySpecifierCollection.cs b/Content.IntegrationTests/Tests/Interaction/InteractionTest.EntitySpecifierCollection.cs index 520d2699c1..7f7de3318b 100644 --- a/Content.IntegrationTests/Tests/Interaction/InteractionTest.EntitySpecifierCollection.cs +++ b/Content.IntegrationTests/Tests/Interaction/InteractionTest.EntitySpecifierCollection.cs @@ -5,6 +5,7 @@ using Robust.Shared.GameObjects; using Robust.Shared.Prototypes; using Robust.Shared.Utility; +using static Robust.UnitTesting.RobustIntegrationTest; namespace Content.IntegrationTests.Tests.Interaction; @@ -111,7 +112,7 @@ public EntitySpecifierCollection Clone() /// /// Convert applicable entity prototypes into stack prototypes. /// - public void ConvertToStacks(IPrototypeManager protoMan, IComponentFactory factory) + public async Task ConvertToStacks(IPrototypeManager protoMan, IComponentFactory factory, ServerIntegrationInstance server) { if (Converted) return; @@ -130,14 +131,17 @@ public void ConvertToStacks(IPrototypeManager protoMan, IComponentFactory factor continue; } - if (!entProto.TryGetComponent(factory.GetComponentName(typeof(StackComponent)), - out var stackComp)) + StackComponent? stack = null; + await server.WaitPost(() => { + entProto.TryGetComponent(factory.GetComponentName(typeof(StackComponent)), out stack); + }); + + if (stack == null) continue; - } toRemove.Add(id); - toAdd.Add((stackComp.StackTypeId, quantity)); + toAdd.Add((stack.StackTypeId, quantity)); } foreach (var id in toRemove) diff --git a/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs b/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs index 480fd9cde6..19ca83a971 100644 --- a/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs +++ b/Content.IntegrationTests/Tests/Interaction/InteractionTest.Helpers.cs @@ -5,12 +5,9 @@ using System.Numerics; using System.Reflection; using Content.Client.Construction; -using Content.Server.Atmos; -using Content.Server.Atmos.Components; using Content.Server.Atmos.EntitySystems; using Content.Server.Construction.Components; using Content.Server.Gravity; -using Content.Server.Item; using Content.Server.Power.Components; using Content.Shared.Atmos; using Content.Shared.Construction.Prototypes; @@ -634,7 +631,7 @@ protected async Task AssertEntityLookup( var entities = await DoEntityLookup(flags); var found = ToEntityCollection(entities); expected.Remove(found); - expected.ConvertToStacks(ProtoMan, Factory); + await expected.ConvertToStacks(ProtoMan, Factory, Server); if (expected.Entities.Count == 0) return; @@ -670,7 +667,7 @@ protected async Task FindEntity( LookupFlags flags = LookupFlags.Uncontained | LookupFlags.Contained, bool shouldSucceed = true) { - spec.ConvertToStack(ProtoMan, Factory); + await spec.ConvertToStack(ProtoMan, Factory, Server); var entities = await DoEntityLookup(flags); foreach (var uid in entities) @@ -767,14 +764,9 @@ protected async Task RunTicks(int ticks) await Pair.RunTicksSync(ticks); } - protected int SecondsToTicks(float seconds) - { - return (int) Math.Ceiling(seconds / TickPeriod); - } - protected async Task RunSeconds(float seconds) { - await RunTicks(SecondsToTicks(seconds)); + await Pair.RunSeconds(seconds); } #endregion @@ -825,7 +817,7 @@ protected bool TryGetBui(Enum key, [NotNullWhen(true)] out BoundUserInterface? b return false; } - if (!ui.OpenInterfaces.TryGetValue(key, out bui)) + if (!ui.ClientOpenInterfaces.TryGetValue(key, out bui)) { if (shouldSucceed) Assert.Fail($"Entity {SEntMan.ToPrettyString(SEntMan.GetEntity(target.Value))} does not have an open bui with key {key.GetType()}.{key}."); diff --git a/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs b/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs index a4ed31e998..42f64b344c 100644 --- a/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs +++ b/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs @@ -12,7 +12,6 @@ using Content.Shared.DoAfter; using Content.Shared.Hands.Components; using Content.Shared.Interaction; -using Content.Server.Item; using Content.Shared.Mind; using Content.Shared.Players; using Robust.Client.Input; diff --git a/Content.IntegrationTests/Tests/InteractionVerbs/InteractionPrototypesTest.cs b/Content.IntegrationTests/Tests/InteractionVerbs/InteractionPrototypesTest.cs new file mode 100644 index 0000000000..fd64c4ff1a --- /dev/null +++ b/Content.IntegrationTests/Tests/InteractionVerbs/InteractionPrototypesTest.cs @@ -0,0 +1,37 @@ +using System.Linq; +using Content.Client.Guidebook; +using Content.Server.Verbs; +using Content.Shared.InteractionVerbs; +using Content.Shared.Verbs; +using Robust.Shared.GameObjects; +using Robust.Shared.Prototypes; + +namespace Content.IntegrationTests.Tests.InteractionVerbs; + +[TestFixture] +[FixtureLifeCycle(LifeCycle.SingleInstance)] +[TestOf(typeof(InteractionVerbPrototype))] +public sealed class InteractionPrototypesTest +{ + public const string TestMobProto = "MobHuman"; + + [Test] + public async Task ValidatePrototypeContents() + { + await using var pair = await PoolManager.GetServerClient(new PoolSettings { Connected = true }); + var server = pair.Server; + await server.WaitIdleAsync(); + + var entMan = server.ResolveDependency(); + var protoMan = server.ResolveDependency(); + + // TODO probably should test if an entity receives an abstract verb, but Iunno how + foreach (var proto in protoMan.EnumeratePrototypes()) + { + Assert.That(proto.Abstract || proto.Action is not null, $"Non-abstract prototype {proto.ID} lacks an action!"); + } + + + await pair.CleanReturnAsync(); + } +} diff --git a/Content.IntegrationTests/Tests/Lobby/CharacterCreationTest.cs b/Content.IntegrationTests/Tests/Lobby/CharacterCreationTest.cs index 4abe2839fb..60501a781f 100644 --- a/Content.IntegrationTests/Tests/Lobby/CharacterCreationTest.cs +++ b/Content.IntegrationTests/Tests/Lobby/CharacterCreationTest.cs @@ -1,5 +1,4 @@ using Content.Client.Lobby; -using Content.Client.Preferences; using Content.Server.Preferences.Managers; using Content.Shared.Preferences; using Robust.Client.State; diff --git a/Content.IntegrationTests/Tests/Mapping/MappingTests.cs b/Content.IntegrationTests/Tests/Mapping/MappingTests.cs new file mode 100644 index 0000000000..287e30eb8b --- /dev/null +++ b/Content.IntegrationTests/Tests/Mapping/MappingTests.cs @@ -0,0 +1,102 @@ +using Robust.Server.GameObjects; +using Robust.Shared.GameObjects; +using Robust.Shared.Map; + +namespace Content.IntegrationTests.Tests.Mapping; + +[TestFixture] +public sealed class MappingTests +{ + /// + /// Checks that the mapping command creates paused & uninitialized maps. + /// + [Test] + public async Task MappingTest() + { + await using var pair = await PoolManager.GetServerClient(new PoolSettings {Dirty = true, Connected = true, DummyTicker = false}); + + var server = pair.Server; + var entMan = server.EntMan; + var mapSys = server.System(); + + await pair.RunTicksSync(5); + var mapId = 1; + while (mapSys.MapExists(new(mapId))) + { + mapId++; + } + + await pair.WaitClientCommand($"mapping {mapId}"); + var map = mapSys.GetMap(new MapId(mapId)); + + var mapXform = server.Transform(map); + Assert.That(mapXform.MapUid, Is.EqualTo(map)); + Assert.That(mapXform.MapID, Is.EqualTo(new MapId(mapId))); + + var xform = server.Transform(pair.Player!.AttachedEntity!.Value); + + Assert.That(xform.MapUid, Is.EqualTo(map)); + Assert.That(mapSys.IsInitialized(map), Is.False); + Assert.That(mapSys.IsPaused(map), Is.True); + Assert.That(server.MetaData(map).EntityLifeStage, Is.EqualTo(EntityLifeStage.Initialized)); + Assert.That(server.MetaData(map).EntityPaused, Is.True); + + // Spawn a new entity + EntityUid ent = default; + await server.WaitPost(() => + { + ent = entMan.Spawn(null, new MapCoordinates(default, new(mapId))); + }); + await pair.RunTicksSync(5); + Assert.That(server.MetaData(ent).EntityLifeStage, Is.EqualTo(EntityLifeStage.Initialized)); + Assert.That(server.MetaData(ent).EntityPaused, Is.True); + + // Save the map + var file = $"{nameof(MappingTest)}.yml"; + await pair.WaitClientCommand($"savemap {mapId} {file}"); + + // Mapinitialize it + await pair.WaitClientCommand($"mapinit {mapId}"); + Assert.That(mapSys.IsInitialized(map), Is.True); + Assert.That(mapSys.IsPaused(map), Is.False); + Assert.That(server.MetaData(map).EntityLifeStage, Is.EqualTo(EntityLifeStage.MapInitialized)); + Assert.That(server.MetaData(map).EntityPaused, Is.False); + Assert.That(server.MetaData(ent).EntityLifeStage, Is.EqualTo(EntityLifeStage.MapInitialized)); + Assert.That(server.MetaData(ent).EntityPaused, Is.False); + + await server.WaitPost(() => entMan.DeleteEntity(map)); + + // Load the saved map + mapId++; + while (mapSys.MapExists(new(mapId))) + { + mapId++; + } + + await pair.WaitClientCommand($"mapping {mapId} {file}"); + map = mapSys.GetMap(new MapId(mapId)); + + // And it should all be paused and un-initialized + xform = server.Transform(pair.Player!.AttachedEntity!.Value); + Assert.That(xform.MapUid, Is.EqualTo(map)); + Assert.That(mapSys.IsInitialized(map), Is.False); + Assert.That(mapSys.IsPaused(map), Is.True); + Assert.That(server.MetaData(map).EntityLifeStage, Is.EqualTo(EntityLifeStage.Initialized)); + Assert.That(server.MetaData(map).EntityPaused, Is.True); + + mapXform = server.Transform(map); + Assert.That(mapXform.MapUid, Is.EqualTo(map)); + Assert.That(mapXform.MapID, Is.EqualTo(new MapId(mapId))); + Assert.That(mapXform.ChildCount, Is.EqualTo(2)); + + mapXform.ChildEnumerator.MoveNext(out ent); + if (ent == pair.Player.AttachedEntity) + mapXform.ChildEnumerator.MoveNext(out ent); + + Assert.That(server.MetaData(ent).EntityLifeStage, Is.EqualTo(EntityLifeStage.Initialized)); + Assert.That(server.MetaData(ent).EntityPaused, Is.True); + + await server.WaitPost(() => entMan.DeleteEntity(map)); + await pair.CleanReturnAsync(); + } +} diff --git a/Content.IntegrationTests/Tests/MaterialArbitrageTest.cs b/Content.IntegrationTests/Tests/MaterialArbitrageTest.cs index a2faef0dd4..7f9c02fc13 100644 --- a/Content.IntegrationTests/Tests/MaterialArbitrageTest.cs +++ b/Content.IntegrationTests/Tests/MaterialArbitrageTest.cs @@ -1,22 +1,23 @@ +using System.Collections.Generic; using Content.Server.Cargo.Systems; using Content.Server.Construction.Completions; using Content.Server.Construction.Components; using Content.Server.Destructible; using Content.Server.Destructible.Thresholds.Behaviors; using Content.Server.Stack; +using Content.Shared.Chemistry.Reagent; +using Content.Shared.Construction.Components; using Content.Shared.Construction.Prototypes; using Content.Shared.Construction.Steps; +using Content.Shared.FixedPoint; using Content.Shared.Lathe; +using Content.Shared.Materials; using Content.Shared.Research.Prototypes; using Content.Shared.Stacks; using Robust.Shared.GameObjects; using Robust.Shared.Map; using Robust.Shared.Prototypes; -using System.Collections.Generic; -using Content.Shared.Chemistry.Reagent; -using Content.Shared.Construction.Components; -using Content.Shared.FixedPoint; -using Content.Shared.Materials; +using Robust.Shared.Utility; namespace Content.IntegrationTests.Tests; @@ -52,10 +53,10 @@ public async Task NoMaterialArbitrage() var destructibleName = compFact.GetComponentName(typeof(DestructibleComponent)); // construct inverted lathe recipe dictionary - Dictionary latheRecipes = new(); + Dictionary> latheRecipes = new(); foreach (var proto in protoManager.EnumeratePrototypes()) { - latheRecipes.Add(proto.Result, proto); + latheRecipes.GetOrNew(proto.Result).Add(proto); } // Lets assume the possible lathe for resource multipliers: @@ -183,16 +184,19 @@ public async Task NoMaterialArbitrage() var spawnedPrice = await GetSpawnedPrice(spawnedEnts); var price = await GetPrice(id); if (spawnedPrice > 0 && price > 0) - Assert.That(spawnedPrice, Is.LessThanOrEqualTo(price), $"{id} increases in price after being destroyed"); + Assert.That(spawnedPrice, Is.LessThanOrEqualTo(price), $"{id} increases in price after being destroyed\nEntities spawned on destruction: {string.Join(',', spawnedEnts)}"); // Check lathe production - if (latheRecipes.TryGetValue(id, out var recipe)) + if (latheRecipes.TryGetValue(id, out var recipes)) { - foreach (var (matId, amount) in recipe.RequiredMaterials) + foreach (var recipe in recipes) { - var actualAmount = SharedLatheSystem.AdjustMaterial(amount, recipe.ApplyMaterialDiscount, multiplier); - if (spawnedMats.TryGetValue(matId, out var numSpawned)) - Assert.That(numSpawned, Is.LessThanOrEqualTo(actualAmount), $"destroying a {id} spawns more {matId} than required to produce via an (upgraded) lathe."); + foreach (var (matId, amount) in recipe.RequiredMaterials) + { + var actualAmount = SharedLatheSystem.AdjustMaterial(amount, recipe.ApplyMaterialDiscount, multiplier); + if (spawnedMats.TryGetValue(matId, out var numSpawned)) + Assert.That(numSpawned, Is.LessThanOrEqualTo(actualAmount), $"destroying a {id} spawns more {matId} than required to produce via an (upgraded) lathe."); + } } } @@ -263,13 +267,16 @@ public async Task NoMaterialArbitrage() Assert.That(deconstructedPrice, Is.LessThanOrEqualTo(price), $"{id} increases in price after being deconstructed"); // Check lathe production - if (latheRecipes.TryGetValue(id, out var recipe)) + if (latheRecipes.TryGetValue(id, out var recipes)) { - foreach (var (matId, amount) in recipe.RequiredMaterials) + foreach (var recipe in recipes) { - var actualAmount = SharedLatheSystem.AdjustMaterial(amount, recipe.ApplyMaterialDiscount, multiplier); - if (deconstructedMats.TryGetValue(matId, out var numSpawned)) - Assert.That(numSpawned, Is.LessThanOrEqualTo(actualAmount), $"deconstructing {id} spawns more {matId} than required to produce via an (upgraded) lathe."); + foreach (var (matId, amount) in recipe.RequiredMaterials) + { + var actualAmount = SharedLatheSystem.AdjustMaterial(amount, recipe.ApplyMaterialDiscount, multiplier); + if (deconstructedMats.TryGetValue(matId, out var numSpawned)) + Assert.That(numSpawned, Is.LessThanOrEqualTo(actualAmount), $"deconstructing {id} spawns more {matId} than required to produce via an (upgraded) lathe."); + } } } @@ -315,13 +322,16 @@ public async Task NoMaterialArbitrage() Assert.That(sumPrice, Is.LessThanOrEqualTo(price), $"{id} increases in price after decomposed into raw materials"); // Check lathe production - if (latheRecipes.TryGetValue(id, out var recipe)) + if (latheRecipes.TryGetValue(id, out var recipes)) { - foreach (var (matId, amount) in recipe.RequiredMaterials) + foreach (var recipe in recipes) { - var actualAmount = SharedLatheSystem.AdjustMaterial(amount, recipe.ApplyMaterialDiscount, multiplier); - if (compositionComponent.MaterialComposition.TryGetValue(matId, out var numSpawned)) - Assert.That(numSpawned, Is.LessThanOrEqualTo(actualAmount), $"The physical composition of {id} has more {matId} than required to produce via an (upgraded) lathe."); + foreach (var (matId, amount) in recipe.RequiredMaterials) + { + var actualAmount = SharedLatheSystem.AdjustMaterial(amount, recipe.ApplyMaterialDiscount, multiplier); + if (compositionComponent.MaterialComposition.TryGetValue(matId, out var numSpawned)) + Assert.That(numSpawned, Is.LessThanOrEqualTo(actualAmount), $"The physical composition of {id} has more {matId} than required to produce via an (upgraded) lathe."); + } } } @@ -359,7 +369,7 @@ await server.WaitPost(() => { var ent = entManager.SpawnEntity(id, testMap.GridCoords); stackSys.SetCount(ent, 1); - priceCache[id] = price = pricing.GetPrice(ent); + priceCache[id] = price = pricing.GetPrice(ent, false); entManager.DeleteEntity(ent); }); } diff --git a/Content.IntegrationTests/Tests/Minds/GhostRoleTests.cs b/Content.IntegrationTests/Tests/Minds/GhostRoleTests.cs index ca97e435a7..150bc951f8 100644 --- a/Content.IntegrationTests/Tests/Minds/GhostRoleTests.cs +++ b/Content.IntegrationTests/Tests/Minds/GhostRoleTests.cs @@ -1,5 +1,6 @@ #nullable enable using System.Linq; +using Content.IntegrationTests.Pair; using Content.Server.Ghost.Roles; using Content.Server.Ghost.Roles.Components; using Content.Server.Players; @@ -26,7 +27,7 @@ public sealed class GhostRoleTests "; /// - /// This is a simple test that just checks if a player can take a ghost roll and then regain control of their + /// This is a simple test that just checks if a player can take a ghost role and then regain control of their /// original entity without encountering errors. /// [Test] @@ -34,12 +35,15 @@ public async Task TakeRoleAndReturn() { await using var pair = await PoolManager.GetServerClient(new PoolSettings { + Dirty = true, DummyTicker = false, Connected = true }); var server = pair.Server; var client = pair.Client; + var mapData = await pair.CreateTestMap(); + var entMan = server.ResolveDependency(); var sPlayerMan = server.ResolveDependency(); var conHost = client.ResolveDependency(); @@ -51,7 +55,7 @@ public async Task TakeRoleAndReturn() EntityUid originalMob = default; await server.WaitPost(() => { - originalMob = entMan.SpawnEntity(null, MapCoordinates.Nullspace); + originalMob = entMan.SpawnEntity(null, mapData.GridCoords); mindSystem.TransferTo(originalMindId, originalMob, true); }); @@ -69,12 +73,12 @@ await server.WaitPost(() => Assert.That(entMan.HasComponent(ghost)); Assert.That(ghost, Is.Not.EqualTo(originalMob)); Assert.That(session.ContentData()?.Mind, Is.EqualTo(originalMindId)); - Assert.That(originalMind.OwnedEntity, Is.EqualTo(originalMob)); + Assert.That(originalMind.OwnedEntity, Is.EqualTo(originalMob), $"Original mob: {originalMob}, Ghost: {ghost}"); Assert.That(originalMind.VisitingEntity, Is.EqualTo(ghost)); // Spawn ghost takeover entity. EntityUid ghostRole = default; - await server.WaitPost(() => ghostRole = entMan.SpawnEntity("GhostRoleTestEntity", MapCoordinates.Nullspace)); + await server.WaitPost(() => ghostRole = entMan.SpawnEntity("GhostRoleTestEntity", mapData.GridCoords)); // Take the ghost role await server.WaitPost(() => diff --git a/Content.IntegrationTests/Tests/Minds/GhostTests.cs b/Content.IntegrationTests/Tests/Minds/GhostTests.cs new file mode 100644 index 0000000000..ad9d53a70d --- /dev/null +++ b/Content.IntegrationTests/Tests/Minds/GhostTests.cs @@ -0,0 +1,159 @@ +using System.Numerics; +using Content.IntegrationTests.Pair; +using Content.Shared.Ghost; +using Content.Shared.Mind; +using Content.Shared.Players; +using Robust.Server.GameObjects; +using Robust.Shared.GameObjects; +using Robust.Shared.Map; +using Robust.Shared.Player; +using Robust.UnitTesting; + +namespace Content.IntegrationTests.Tests.Minds; + +[TestFixture] +public sealed class GhostTests +{ + struct GhostTestData + { + public IEntityManager SEntMan; + public Robust.Server.Player.IPlayerManager SPlayerMan; + public Server.Mind.MindSystem SMindSys; + public SharedTransformSystem STransformSys = default!; + + public TestPair Pair = default!; + + public TestMapData MapData => Pair.TestMap!; + + public RobustIntegrationTest.ServerIntegrationInstance Server => Pair.Server; + public RobustIntegrationTest.ClientIntegrationInstance Client => Pair.Client; + + /// + /// Initial player coordinates. Note that this does not necessarily correspond to the position of the + /// entity. + /// + public NetCoordinates PlayerCoords = default!; + + public NetEntity Player = default!; + public EntityUid SPlayerEnt = default!; + + public ICommonSession ClientSession = default!; + public ICommonSession ServerSession = default!; + + public GhostTestData() + { + } + } + + private async Task SetupData() + { + var data = new GhostTestData(); + + // Client is needed to create a session for the ghost system. Creating a dummy session was too difficult. + data.Pair = await PoolManager.GetServerClient(new PoolSettings + { + DummyTicker = false, + Connected = true, + Dirty = true + }); + + data.SEntMan = data.Pair.Server.ResolveDependency(); + data.SPlayerMan = data.Pair.Server.ResolveDependency(); + data.SMindSys = data.SEntMan.System(); + data.STransformSys = data.SEntMan.System(); + + // Setup map. + await data.Pair.CreateTestMap(); + data.PlayerCoords = data.SEntMan.GetNetCoordinates(data.MapData.GridCoords.Offset(new Vector2(0.5f, 0.5f)).WithEntityId(data.MapData.MapUid, data.STransformSys, data.SEntMan)); + + if (data.Client.Session == null) + Assert.Fail("No player"); + data.ClientSession = data.Client.Session!; + data.ServerSession = data.SPlayerMan.GetSessionById(data.ClientSession.UserId); + + Entity mind = default!; + await data.Pair.Server.WaitPost(() => + { + data.Player = data.SEntMan.GetNetEntity(data.SEntMan.SpawnEntity(null, data.SEntMan.GetCoordinates(data.PlayerCoords))); + mind = data.SMindSys.CreateMind(data.ServerSession.UserId, "DummyPlayerEntity"); + data.SPlayerEnt = data.SEntMan.GetEntity(data.Player); + data.SMindSys.TransferTo(mind, data.SPlayerEnt, mind: mind.Comp); + data.Server.PlayerMan.SetAttachedEntity(data.ServerSession, data.SPlayerEnt); + }); + + await data.Pair.RunTicksSync(5); + + Assert.Multiple(() => + { + Assert.That(data.ServerSession.ContentData()?.Mind, Is.EqualTo(mind.Owner)); + Assert.That(data.ServerSession.AttachedEntity, Is.EqualTo(data.SPlayerEnt)); + Assert.That(data.ServerSession.AttachedEntity, Is.EqualTo(mind.Comp.CurrentEntity), + "Player is not attached to the mind's current entity."); + Assert.That(data.SEntMan.EntityExists(mind.Comp.OwnedEntity), + "The mind's current entity does not exist"); + Assert.That(mind.Comp.VisitingEntity == null || data.SEntMan.EntityExists(mind.Comp.VisitingEntity), + "The minds visited entity does not exist."); + }); + + Assert.That(data.SPlayerEnt, Is.Not.EqualTo(null)); + + return data; + } + + /// + /// Test that a ghost gets created when the player entity is deleted. + /// 1. Delete mob + /// 2. Assert is ghost + /// + [Test] + public async Task TestGridGhostOnDelete() + { + var data = await SetupData(); + + var oldPosition = data.SEntMan.GetComponent(data.SPlayerEnt).Coordinates; + + Assert.That(!data.SEntMan.HasComponent(data.SPlayerEnt), "Player was initially a ghost?"); + + // Delete entity + await data.Server.WaitPost(() => data.SEntMan.DeleteEntity(data.SPlayerEnt)); + await data.Pair.RunTicksSync(5); + + var ghost = data.ServerSession.AttachedEntity!.Value; + Assert.That(data.SEntMan.HasComponent(ghost), "Player did not become a ghost"); + + // Ensure the position is the same + var ghostPosition = data.SEntMan.GetComponent(ghost).Coordinates; + Assert.That(ghostPosition, Is.EqualTo(oldPosition)); + + await data.Pair.CleanReturnAsync(); + } + + /// + /// Test that a ghost gets created when the player entity is queue deleted. + /// 1. Delete mob + /// 2. Assert is ghost + /// + [Test] + public async Task TestGridGhostOnQueueDelete() + { + var data = await SetupData(); + + var oldPosition = data.SEntMan.GetComponent(data.SPlayerEnt).Coordinates; + + Assert.That(!data.SEntMan.HasComponent(data.SPlayerEnt), "Player was initially a ghost?"); + + // Delete entity + await data.Server.WaitPost(() => data.SEntMan.QueueDeleteEntity(data.SPlayerEnt)); + await data.Pair.RunTicksSync(5); + + var ghost = data.ServerSession.AttachedEntity!.Value; + Assert.That(data.SEntMan.HasComponent(ghost), "Player did not become a ghost"); + + // Ensure the position is the same + var ghostPosition = data.SEntMan.GetComponent(ghost).Coordinates; + Assert.That(ghostPosition, Is.EqualTo(oldPosition)); + + await data.Pair.CleanReturnAsync(); + } + +} diff --git a/Content.IntegrationTests/Tests/Minds/MindTest.DeleteAllThenGhost.cs b/Content.IntegrationTests/Tests/Minds/MindTest.DeleteAllThenGhost.cs index 7bc62dfe2b..ab9e96ab91 100644 --- a/Content.IntegrationTests/Tests/Minds/MindTest.DeleteAllThenGhost.cs +++ b/Content.IntegrationTests/Tests/Minds/MindTest.DeleteAllThenGhost.cs @@ -34,7 +34,7 @@ public async Task DeleteAllThenGhost() Console.WriteLine(pair.Client.EntMan.ToPrettyString(ent)); } - Assert.That(pair.Client.EntMan.EntityCount, Is.EqualTo(0)); + Assert.That(pair.Client.EntMan.EntityCount, Is.AtMost(1)); // Tolerate at most one client entity // Create a new map. int mapId = 1; diff --git a/Content.IntegrationTests/Tests/Minds/MindTests.EntityDeletion.cs b/Content.IntegrationTests/Tests/Minds/MindTests.EntityDeletion.cs index 2ebe750f98..de7739b2ad 100644 --- a/Content.IntegrationTests/Tests/Minds/MindTests.EntityDeletion.cs +++ b/Content.IntegrationTests/Tests/Minds/MindTests.EntityDeletion.cs @@ -1,3 +1,4 @@ +#nullable enable using System.Linq; using Content.Server.GameTicking; using Content.Shared.Ghost; @@ -77,7 +78,7 @@ public async Task TestGhostOnDeleteMap() await using var pair = await SetupPair(dirty: true); var server = pair.Server; var testMap = await pair.CreateTestMap(); - var coordinates = testMap.GridCoords; + var testMap2 = await pair.CreateTestMap(); var entMan = server.ResolveDependency(); var mapManager = server.ResolveDependency(); @@ -91,7 +92,7 @@ public async Task TestGhostOnDeleteMap() MindComponent mind = default!; await server.WaitAssertion(() => { - playerEnt = entMan.SpawnEntity(null, coordinates); + playerEnt = entMan.SpawnEntity(null, testMap.GridCoords); mindId = player.ContentData()!.Mind!.Value; mind = entMan.GetComponent(mindId); mindSystem.TransferTo(mindId, playerEnt); @@ -100,14 +101,20 @@ await server.WaitAssertion(() => }); await pair.RunTicksSync(5); - await server.WaitPost(() => mapManager.DeleteMap(testMap.MapId)); + await server.WaitAssertion(() => mapManager.DeleteMap(testMap.MapId)); await pair.RunTicksSync(5); await server.WaitAssertion(() => { #pragma warning disable NUnit2045 // Interdependent assertions. - Assert.That(entMan.EntityExists(mind.CurrentEntity), Is.True); - Assert.That(mind.CurrentEntity, Is.Not.EqualTo(playerEnt)); + // Spawn ghost on the second map + var attachedEntity = player.AttachedEntity; + Assert.That(entMan.EntityExists(attachedEntity), Is.True); + Assert.That(attachedEntity, Is.Not.EqualTo(playerEnt)); + Assert.That(entMan.HasComponent(attachedEntity)); + var transform = entMan.GetComponent(attachedEntity.Value); + Assert.That(transform.MapID, Is.Not.EqualTo(MapId.Nullspace)); + Assert.That(transform.MapID, Is.Not.EqualTo(testMap.MapId)); #pragma warning restore NUnit2045 }); diff --git a/Content.IntegrationTests/Tests/Nyanotrasen/Metempsychosis/MetempsychosisTest.cs b/Content.IntegrationTests/Tests/Nyanotrasen/Metempsychosis/MetempsychosisTest.cs deleted file mode 100644 index cd6a4b4c2b..0000000000 --- a/Content.IntegrationTests/Tests/Nyanotrasen/Metempsychosis/MetempsychosisTest.cs +++ /dev/null @@ -1,53 +0,0 @@ -using Content.Server.Nyanotrasen.Cloning; -using Content.Shared.Humanoid.Prototypes; -using Content.Shared.Random; -using Robust.Shared.Prototypes; - -namespace Content.IntegrationTests.Tests.DeltaV; - -[TestFixture] -[TestOf(typeof(MetempsychoticMachineSystem))] -public sealed class MetempsychosisTest -{ - [Test] - public async Task AllHumanoidPoolSpeciesExist() - { - await using var pair = await PoolManager.GetServerClient(); - var server = pair.Server; - // Per RobustIntegrationTest.cs, wait until state is settled to access it. - await server.WaitIdleAsync(); - - var prototypeManager = server.ResolveDependency(); - - var metemComponent = new MetempsychoticMachineComponent(); - - await server.WaitAssertion(() => - { - prototypeManager.TryIndex(metemComponent.MetempsychoticHumanoidPool, - out var humanoidPool); - prototypeManager.TryIndex(metemComponent.MetempsychoticNonHumanoidPool, - out var nonHumanoidPool); - - Assert.That(humanoidPool, Is.Not.Null, "MetempsychoticHumanoidPool is null!"); - Assert.That(nonHumanoidPool, Is.Not.Null, "MetempsychoticNonHumanoidPool is null!"); - - Assert.That(humanoidPool.Weights, Is.Not.Empty, - "MetempsychoticHumanoidPool has no valid prototypes!"); - Assert.That(nonHumanoidPool.Weights, Is.Not.Empty, - "MetempsychoticNonHumanoidPool has no valid prototypes!"); - - foreach (var key in humanoidPool.Weights.Keys) - { - Assert.That(prototypeManager.TryIndex(key, out _), - $"MetempsychoticHumanoidPool has invalid prototype {key}!"); - } - - foreach (var key in nonHumanoidPool.Weights.Keys) - { - Assert.That(prototypeManager.TryIndex(key, out _), - $"MetempsychoticNonHumanoidPool has invalid prototype {key}!"); - } - }); - await pair.CleanReturnAsync(); - } -} diff --git a/Content.IntegrationTests/Tests/PostMapInitTest.cs b/Content.IntegrationTests/Tests/PostMapInitTest.cs index a4097728d7..f275313ab2 100644 --- a/Content.IntegrationTests/Tests/PostMapInitTest.cs +++ b/Content.IntegrationTests/Tests/PostMapInitTest.cs @@ -140,7 +140,10 @@ public async Task NoSavedPostMapInitTest() [Test, TestCaseSource(nameof(GameMaps))] public async Task GameMapsLoadableTest(string mapProto) { - await using var pair = await PoolManager.GetServerClient(); + await using var pair = await PoolManager.GetServerClient(new PoolSettings + { + Dirty = true // Stations spawn a bunch of nullspace entities and maps like centcomm. + }); var server = pair.Server; var mapManager = server.ResolveDependency(); diff --git a/Content.IntegrationTests/Tests/Power/PowerTest.cs b/Content.IntegrationTests/Tests/Power/PowerTest.cs index a6af3e6a65..a94e94489c 100644 --- a/Content.IntegrationTests/Tests/Power/PowerTest.cs +++ b/Content.IntegrationTests/Tests/Power/PowerTest.cs @@ -143,8 +143,8 @@ public sealed class PowerTest anchored: true - type: UserInterface interfaces: - - key: enum.ApcUiKey.Key - type: ApcBoundUserInterface + enum.ApcUiKey.Key: + type: ApcBoundUserInterface - type: AccessReader access: [['Engineering']] diff --git a/Content.IntegrationTests/Tests/Preferences/ServerDbSqliteTests.cs b/Content.IntegrationTests/Tests/Preferences/ServerDbSqliteTests.cs index b441720024..631bb4ef46 100644 --- a/Content.IntegrationTests/Tests/Preferences/ServerDbSqliteTests.cs +++ b/Content.IntegrationTests/Tests/Preferences/ServerDbSqliteTests.cs @@ -3,6 +3,7 @@ using Content.Server.Database; using Content.Shared.GameTicking; using Content.Shared.Humanoid; +using Content.Shared.Humanoid.Markings; using Content.Shared.Preferences; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; @@ -37,36 +38,22 @@ public sealed class ServerDbSqliteTests private static HumanoidCharacterProfile CharlieCharlieson() { - return new( - "Charlie Charlieson", - "The biggest boy around.", - "Human", - 1, - 1, - 21, - Sex.Male, - Gender.Epicene, - new HumanoidCharacterAppearance( + return new HumanoidCharacterProfile + { + Name = "Charlie Charlieson", + FlavorText = "The biggest boy around.", + Species = "Human", + Customspeciename = "", + Age = 21, + Appearance = new( "Afro", Color.Aqua, "Shaved", Color.Aquamarine, Color.Azure, Color.Beige, - new () - ), - ClothingPreference.Jumpskirt, - BackpackPreference.Backpack, - SpawnPriorityPreference.None, - new Dictionary - { - {SharedGameTicker.FallbackOverflowJob, JobPriority.High} - }, - PreferenceUnavailableMode.StayInLobby, - antagPreferences: new List(), - traitPreferences: new List(), - loadoutPreferences: new List() - ); + new List()), + }; } private static ServerDbSqlite GetDb(RobustIntegrationTest.ServerIntegrationInstance server) diff --git a/Content.IntegrationTests/Tests/ResettingEntitySystemTests.cs b/Content.IntegrationTests/Tests/ResettingEntitySystemTests.cs index d5c2a9124d..40457f5488 100644 --- a/Content.IntegrationTests/Tests/ResettingEntitySystemTests.cs +++ b/Content.IntegrationTests/Tests/ResettingEntitySystemTests.cs @@ -9,7 +9,6 @@ namespace Content.IntegrationTests.Tests [TestOf(typeof(RoundRestartCleanupEvent))] public sealed class ResettingEntitySystemTests { - [Reflect(false)] public sealed class TestRoundRestartCleanupEvent : EntitySystem { public bool HasBeenReset { get; set; } @@ -49,8 +48,6 @@ await server.WaitAssertion(() => system.HasBeenReset = false; - Assert.That(system.HasBeenReset, Is.False); - gameTicker.RestartRound(); Assert.That(system.HasBeenReset); diff --git a/Content.IntegrationTests/Tests/RoundEndTest.cs b/Content.IntegrationTests/Tests/RoundEndTest.cs index 1ddddf66bc..6978085640 100644 --- a/Content.IntegrationTests/Tests/RoundEndTest.cs +++ b/Content.IntegrationTests/Tests/RoundEndTest.cs @@ -12,7 +12,7 @@ public sealed class RoundEndTest { private sealed class RoundEndTestSystem : EntitySystem { - public int Count; + public int RoundCount; public override void Initialize() { @@ -22,7 +22,7 @@ public override void Initialize() private void OnRoundEnd(RoundEndSystemChangedEvent ev) { - Interlocked.Increment(ref Count); + Interlocked.Increment(ref RoundCount); } } @@ -43,7 +43,7 @@ public async Task Test() var ticker = sysManager.GetEntitySystem(); var roundEndSystem = sysManager.GetEntitySystem(); var sys = server.System(); - sys.Count = 0; + sys.RoundCount = 0; await server.WaitAssertion(() => { @@ -128,8 +128,8 @@ Task CheckRunLevel(GameRunLevel level) async Task WaitForEvent() { var timeout = Task.Delay(TimeSpan.FromSeconds(10)); - var currentCount = Thread.VolatileRead(ref sys.Count); - while (currentCount == Thread.VolatileRead(ref sys.Count) && !timeout.IsCompleted) + var currentCount = Thread.VolatileRead(ref sys.RoundCount); + while (currentCount == Thread.VolatileRead(ref sys.RoundCount) && !timeout.IsCompleted) { await pair.RunTicksSync(5); } diff --git a/Content.IntegrationTests/Tests/Shitmed/Body/SpeciesBUiTest.cs b/Content.IntegrationTests/Tests/Shitmed/Body/SpeciesBUiTest.cs new file mode 100644 index 0000000000..f82a22d447 --- /dev/null +++ b/Content.IntegrationTests/Tests/Shitmed/Body/SpeciesBUiTest.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Content.Shared.Humanoid.Prototypes; +using Robust.Shared.GameObjects; +using Robust.Shared.Prototypes; + +namespace Content.IntegrationTests.Tests.Backmen.Body; + +[TestFixture] +public sealed class SpeciesBUiTest +{ + [TestPrototypes] + private const string Prototypes = @" +- type: entity + name: BaseMobSpeciesTest + id: BaseMobSpeciesTest + parent: BaseMobSpecies +"; + + private Dictionary GetInterfaces(UserInterfaceComponent comp) => + (Dictionary) + typeof(UserInterfaceComponent).GetField("Interfaces", BindingFlags.NonPublic | BindingFlags.Instance)! + .GetValue(comp); + + [Test] + public async Task AllSpeciesHaveBaseBUiTest() + { + await using var pair = await PoolManager.GetServerClient(new PoolSettings + { + Dirty = true, + Connected = false + }); + + var server = pair.Server; + var proto = server.ResolveDependency(); + var factoryComp = server.ResolveDependency(); + + await server.WaitAssertion(() => + { + var bUiSys = server.System(); + + Assert.That(proto.TryIndex("BaseMobSpeciesTest", out var baseEnt), Is.True); + Assert.That(baseEnt, Is.Not.Null); + Assert.That(baseEnt.TryGetComponent(out var bUiBase, factoryComp), Is.True); + Assert.That(bUiBase, Is.Not.Null); + var baseKeys = GetInterfaces(bUiBase).Keys.ToArray(); + + foreach (var species in proto.EnumeratePrototypes()) + { + var ent = proto.Index(species.Prototype); + Assert.That(ent.TryGetComponent(out var bUi, factoryComp), Is.True); + Assert.That(bUi, Is.Not.Null); + var states = GetInterfaces(bUiBase); + foreach (var key in baseKeys) + { + Assert.That(states.ContainsKey(key), Is.True, $"Species {species.ID} has not UserInterface of type enum.{key.GetType().Name}"); + } + } + }); + await pair.CleanReturnAsync(); + } +} diff --git a/Content.IntegrationTests/Tests/Slipping/SlippingTest.cs b/Content.IntegrationTests/Tests/Slipping/SlippingTest.cs index 511a720ed0..28da7a9465 100644 --- a/Content.IntegrationTests/Tests/Slipping/SlippingTest.cs +++ b/Content.IntegrationTests/Tests/Slipping/SlippingTest.cs @@ -34,11 +34,11 @@ private void OnSlip(EntityUid uid, SlipperyComponent component, ref SlipEvent ar public async Task BananaSlipTest() { var sys = SEntMan.System(); - var sprintWalks = sys.Config.GetCVar(CCVars.GamePressToSprint); + var sprintWalks = sys.Config.GetCVar(CCVars.DefaultWalk); await SpawnTarget("TrashBananaPeel"); - var modifier = Comp(Player).SprintSpeedModifier; - Assert.That(modifier, Is.EqualTo(1), "Player is not moving at full speed."); + // var modifier = Comp(Player).SprintSpeedModifier; + // Assert.That(modifier, Is.EqualTo(1), "Player is not moving at full speed."); // Yeeting this pointless Assert because it's not actually important. // Player is to the left of the banana peel and has not slipped. #pragma warning disable NUnit2045 diff --git a/Content.IntegrationTests/Tests/Station/EvacShuttleTest.cs b/Content.IntegrationTests/Tests/Station/EvacShuttleTest.cs new file mode 100644 index 0000000000..dd7422056d --- /dev/null +++ b/Content.IntegrationTests/Tests/Station/EvacShuttleTest.cs @@ -0,0 +1,127 @@ +using System.Linq; +using Content.Server.GameTicking; +using Content.Server.Shuttles.Components; +using Content.Server.Shuttles.Systems; +using Content.Server.Station.Components; +using Content.Shared.CCVar; +using Content.Shared.Shuttles.Components; +using Robust.Shared.GameObjects; +using Robust.Shared.Map.Components; + +namespace Content.IntegrationTests.Tests.Station; + +[TestFixture] +[TestOf(typeof(EmergencyShuttleSystem))] +public sealed class EvacShuttleTest +{ + /// + /// Ensure that the emergency shuttle can be called, and that it will travel to centcomm + /// + [Test] + public async Task EmergencyEvacTest() + { + await using var pair = await PoolManager.GetServerClient(new PoolSettings { DummyTicker = true, Dirty = true }); + var server = pair.Server; + var entMan = server.EntMan; + var ticker = server.System(); + + // Dummy ticker tests should not have centcomm + Assert.That(entMan.Count(), Is.Zero); + + Assert.That(pair.Server.CfgMan.GetCVar(CCVars.GridFill), Is.False); + pair.Server.CfgMan.SetCVar(CCVars.EmergencyShuttleEnabled, true); + pair.Server.CfgMan.SetCVar(CCVars.GameDummyTicker, false); + var gameMap = pair.Server.CfgMan.GetCVar(CCVars.GameMap); + pair.Server.CfgMan.SetCVar(CCVars.GameMap, "Dev"); + + await server.WaitPost(() => ticker.RestartRound()); + await pair.RunTicksSync(25); + Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.InRound)); + + // Find the station, centcomm, and shuttle, and ftl map. + + Assert.That(entMan.Count(), Is.EqualTo(1)); + Assert.That(entMan.Count(), Is.EqualTo(1)); + Assert.That(entMan.Count(), Is.EqualTo(1)); + Assert.That(entMan.Count(), Is.EqualTo(1)); + Assert.That(entMan.Count(), Is.EqualTo(0)); + + var station = (Entity) entMan.AllComponentsList().Single(); + var data = entMan.GetComponent(station); + var shuttleData = entMan.GetComponent(station); + + var saltern = data.Grids.Single(); + Assert.That(entMan.HasComponent(saltern)); + + var shuttle = shuttleData.EmergencyShuttle!.Value; + Assert.That(entMan.HasComponent(shuttle)); + Assert.That(entMan.HasComponent(shuttle)); + + var centcomm = station.Comp.Entity!.Value; + Assert.That(entMan.HasComponent(centcomm)); + + var centcommMap = station.Comp.MapEntity!.Value; + Assert.That(entMan.HasComponent(centcommMap)); + Assert.That(server.Transform(centcomm).MapUid, Is.EqualTo(centcommMap)); + + var salternXform = server.Transform(saltern); + Assert.That(salternXform.MapUid, Is.Not.Null); + Assert.That(salternXform.MapUid, Is.Not.EqualTo(centcommMap)); + + var shuttleXform = server.Transform(shuttle); + Assert.That(shuttleXform.MapUid, Is.Not.Null); + Assert.That(shuttleXform.MapUid, Is.EqualTo(centcommMap)); + + // All of these should have been map-initialized. + var mapSys = entMan.System(); + Assert.That(mapSys.IsInitialized(centcommMap), Is.True); + Assert.That(mapSys.IsInitialized(salternXform.MapUid), Is.True); + Assert.That(mapSys.IsPaused(centcommMap), Is.False); + Assert.That(mapSys.IsPaused(salternXform.MapUid!.Value), Is.False); + + EntityLifeStage LifeStage(EntityUid uid) => entMan.GetComponent(uid).EntityLifeStage; + Assert.That(LifeStage(saltern), Is.EqualTo(EntityLifeStage.MapInitialized)); + Assert.That(LifeStage(shuttle), Is.EqualTo(EntityLifeStage.MapInitialized)); + Assert.That(LifeStage(centcomm), Is.EqualTo(EntityLifeStage.MapInitialized)); + Assert.That(LifeStage(centcommMap), Is.EqualTo(EntityLifeStage.MapInitialized)); + Assert.That(LifeStage(salternXform.MapUid.Value), Is.EqualTo(EntityLifeStage.MapInitialized)); + + // Set up shuttle timing + var shuttleSys = server.System(); + var evacSys = server.System(); + evacSys.TransitTime = shuttleSys.DefaultTravelTime; // Absolute minimum transit time, so the test has to run for at least this long + // TODO SHUTTLE fix spaghetti + + var dockTime = server.CfgMan.GetCVar(CCVars.EmergencyShuttleDockTime); + server.CfgMan.SetCVar(CCVars.EmergencyShuttleDockTime, 2); + + // Call evac shuttle. + await pair.WaitCommand("callshuttle 0:02"); + await pair.RunSeconds(3); + + // Shuttle should have arrived on the station + Assert.That(shuttleXform.MapUid, Is.EqualTo(salternXform.MapUid)); + + await pair.RunSeconds(2); + + // Shuttle should be FTLing back to centcomm + Assert.That(entMan.Count(), Is.EqualTo(1)); + var ftl = (Entity) entMan.AllComponentsList().Single(); + Assert.That(entMan.HasComponent(ftl)); + Assert.That(ftl.Owner, Is.Not.EqualTo(centcommMap)); + Assert.That(ftl.Owner, Is.Not.EqualTo(salternXform.MapUid)); + Assert.That(shuttleXform.MapUid, Is.EqualTo(ftl.Owner)); + + // Shuttle should have arrived at centcomm + await pair.RunSeconds(shuttleSys.DefaultTravelTime); + Assert.That(shuttleXform.MapUid, Is.EqualTo(centcommMap)); + + // Round should be ending now + Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.PostRound)); + + server.CfgMan.SetCVar(CCVars.EmergencyShuttleDockTime, dockTime); + pair.Server.CfgMan.SetCVar(CCVars.EmergencyShuttleEnabled, false); + pair.Server.CfgMan.SetCVar(CCVars.GameMap, gameMap); + await pair.CleanReturnAsync(); + } +} diff --git a/Content.IntegrationTests/Tests/StorageTest.cs b/Content.IntegrationTests/Tests/StorageTest.cs index 659b310661..2d28534347 100644 --- a/Content.IntegrationTests/Tests/StorageTest.cs +++ b/Content.IntegrationTests/Tests/StorageTest.cs @@ -92,23 +92,32 @@ public async Task TestSufficientSpaceForFill() var allSizes = protoMan.EnumeratePrototypes().ToList(); allSizes.Sort(); - Assert.Multiple(() => + await Assert.MultipleAsync(async () => { foreach (var proto in pair.GetPrototypesWithComponent()) { if (proto.HasComponent(compFact)) continue; - if (!proto.TryGetComponent("Storage", out var storage)) + StorageComponent? storage = null; + ItemComponent? item = null; + StorageFillComponent fill = default!; + var size = 0; + await server.WaitAssertion(() => { - Assert.Fail($"Entity {proto.ID} has storage-fill without a storage component!"); - continue; - } + if (!proto.TryGetComponent("Storage", out storage)) + { + Assert.Fail($"Entity {proto.ID} has storage-fill without a storage component!"); + return; + } - proto.TryGetComponent("Item", out var item); + proto.TryGetComponent("Item", out item); + fill = (StorageFillComponent) proto.Components[id].Component; + size = GetFillSize(fill, false, protoMan, itemSys); + }); - var fill = (StorageFillComponent) proto.Components[id].Component; - var size = GetFillSize(fill, false, protoMan, itemSys); + if (storage == null) + continue; var maxSize = storage.MaxItemSize; if (storage.MaxItemSize == null) @@ -138,7 +147,13 @@ public async Task TestSufficientSpaceForFill() if (!protoMan.TryIndex(entry.PrototypeId, out var fillItem)) continue; - if (!fillItem.TryGetComponent("Item", out var entryItem)) + ItemComponent? entryItem = null; + await server.WaitPost(() => + { + fillItem.TryGetComponent("Item", out entryItem); + }); + + if (entryItem == null) continue; Assert.That(protoMan.Index(entryItem.Size).Weight, @@ -164,25 +179,25 @@ public async Task TestSufficientSpaceForEntityStorageFill() var itemSys = entMan.System(); - Assert.Multiple(() => + foreach (var proto in pair.GetPrototypesWithComponent()) { - foreach (var proto in pair.GetPrototypesWithComponent()) - { - if (proto.HasComponent(compFact)) - continue; + if (proto.HasComponent(compFact)) + continue; - if (!proto.TryGetComponent("EntityStorage", out var entStorage)) - { + await server.WaitAssertion(() => + { + if (!proto.TryGetComponent("EntityStorage", out EntityStorageComponent? entStorage)) Assert.Fail($"Entity {proto.ID} has storage-fill without a storage component!"); - continue; - } + + if (entStorage == null) + return; var fill = (StorageFillComponent) proto.Components[id].Component; var size = GetFillSize(fill, true, protoMan, itemSys); Assert.That(size, Is.LessThanOrEqualTo(entStorage.Capacity), $"{proto.ID} storage fill is too large."); - } - }); + }); + } await pair.CleanReturnAsync(); } diff --git a/Content.IntegrationTests/Tests/Utility/EntityWhitelistTest.cs b/Content.IntegrationTests/Tests/Utility/EntityWhitelistTest.cs index 6b47ec4d8e..19b25816fa 100644 --- a/Content.IntegrationTests/Tests/Utility/EntityWhitelistTest.cs +++ b/Content.IntegrationTests/Tests/Utility/EntityWhitelistTest.cs @@ -64,7 +64,8 @@ public async Task Test() var testMap = await pair.CreateTestMap(); var mapCoordinates = testMap.MapCoords; - var sEntities = server.ResolveDependency(); + var sEntities = server.EntMan; + var sys = server.System(); await server.WaitAssertion(() => { @@ -80,22 +81,14 @@ await server.WaitAssertion(() => Components = new[] { $"{ValidComponent}" }, Tags = new() { "WhitelistTestValidTag" } }; - whitelistInst.UpdateRegistrations(); - Assert.That(whitelistInst, Is.Not.Null); Assert.Multiple(() => { - Assert.That(whitelistInst.Components, Is.Not.Null); - Assert.That(whitelistInst.Tags, Is.Not.Null); - }); - - Assert.Multiple(() => - { - Assert.That(whitelistInst.IsValid(validComponent), Is.True); - Assert.That(whitelistInst.IsValid(WhitelistTestValidTag), Is.True); + Assert.That(sys.IsValid(whitelistInst, validComponent), Is.True); + Assert.That(sys.IsValid(whitelistInst, WhitelistTestValidTag), Is.True); - Assert.That(whitelistInst.IsValid(invalidComponent), Is.False); - Assert.That(whitelistInst.IsValid(WhitelistTestInvalidTag), Is.False); + Assert.That(sys.IsValid(whitelistInst, invalidComponent), Is.False); + Assert.That(sys.IsValid(whitelistInst, WhitelistTestInvalidTag), Is.False); }); // Test from serialized @@ -111,11 +104,11 @@ await server.WaitAssertion(() => Assert.Multiple(() => { - Assert.That(whitelistSer.IsValid(validComponent), Is.True); - Assert.That(whitelistSer.IsValid(WhitelistTestValidTag), Is.True); + Assert.That(sys.IsValid(whitelistSer, validComponent), Is.True); + Assert.That(sys.IsValid(whitelistSer, WhitelistTestValidTag), Is.True); - Assert.That(whitelistSer.IsValid(invalidComponent), Is.False); - Assert.That(whitelistSer.IsValid(WhitelistTestInvalidTag), Is.False); + Assert.That(sys.IsValid(whitelistSer, invalidComponent), Is.False); + Assert.That(sys.IsValid(whitelistSer, WhitelistTestInvalidTag), Is.False); }); }); await pair.CleanReturnAsync(); diff --git a/Content.Server.Database/Migrations/Postgres/20240623005121_BanTemplate.Designer.cs b/Content.Server.Database/Migrations/Postgres/20240623005121_BanTemplate.Designer.cs new file mode 100644 index 0000000000..2fb55ddfec --- /dev/null +++ b/Content.Server.Database/Migrations/Postgres/20240623005121_BanTemplate.Designer.cs @@ -0,0 +1,1847 @@ +// +using System; +using System.Net; +using System.Text.Json; +using Content.Server.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using NpgsqlTypes; + +#nullable disable + +namespace Content.Server.Database.Migrations.Postgres +{ + [DbContext(typeof(PostgresServerDbContext))] + [Migration("20240623005113_BanTemplate")] + partial class BanTemplate + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("AdminRankId") + .HasColumnType("integer") + .HasColumnName("admin_rank_id"); + + b.Property("Title") + .HasColumnType("text") + .HasColumnName("title"); + + b.HasKey("UserId") + .HasName("PK_admin"); + + b.HasIndex("AdminRankId") + .HasDatabaseName("IX_admin_admin_rank_id"); + + b.ToTable("admin", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_flag_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdminId") + .HasColumnType("uuid") + .HasColumnName("admin_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("text") + .HasColumnName("flag"); + + b.Property("Negative") + .HasColumnType("boolean") + .HasColumnName("negative"); + + b.HasKey("Id") + .HasName("PK_admin_flag"); + + b.HasIndex("AdminId") + .HasDatabaseName("IX_admin_flag_admin_id"); + + b.HasIndex("Flag", "AdminId") + .IsUnique(); + + b.ToTable("admin_flag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Id") + .HasColumnType("integer") + .HasColumnName("admin_log_id"); + + b.Property("Date") + .HasColumnType("timestamp with time zone") + .HasColumnName("date"); + + b.Property("Impact") + .HasColumnType("smallint") + .HasColumnName("impact"); + + b.Property("Json") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("json"); + + b.Property("Message") + .IsRequired() + .HasColumnType("text") + .HasColumnName("message"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("type"); + + b.HasKey("RoundId", "Id") + .HasName("PK_admin_log"); + + b.HasIndex("Date"); + + b.HasIndex("Message") + .HasAnnotation("Npgsql:TsVectorConfig", "english"); + + NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("Message"), "GIN"); + + b.HasIndex("Type") + .HasDatabaseName("IX_admin_log_type"); + + b.ToTable("admin_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => + { + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("LogId") + .HasColumnType("integer") + .HasColumnName("log_id"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.HasKey("RoundId", "LogId", "PlayerUserId") + .HasName("PK_admin_log_player"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_log_player_player_user_id"); + + b.ToTable("admin_log_player", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_messages_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Seen") + .HasColumnType("boolean") + .HasColumnName("seen"); + + b.HasKey("Id") + .HasName("PK_admin_messages"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_messages_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_messages_round_id"); + + b.ToTable("admin_messages", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_notes_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .IsRequired() + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Secret") + .HasColumnType("boolean") + .HasColumnName("secret"); + + b.Property("Severity") + .HasColumnType("integer") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_admin_notes"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_notes_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_notes_round_id"); + + b.ToTable("admin_notes", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_rank_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("PK_admin_rank"); + + b.ToTable("admin_rank", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_rank_flag_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdminRankId") + .HasColumnType("integer") + .HasColumnName("admin_rank_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("text") + .HasColumnName("flag"); + + b.HasKey("Id") + .HasName("PK_admin_rank_flag"); + + b.HasIndex("AdminRankId"); + + b.HasIndex("Flag", "AdminRankId") + .IsUnique(); + + b.ToTable("admin_rank_flag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_watchlists_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .IsRequired() + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.HasKey("Id") + .HasName("PK_admin_watchlists"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_watchlists_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_watchlists_round_id"); + + b.ToTable("admin_watchlists", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("antag_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AntagName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("antag_name"); + + b.Property("ProfileId") + .HasColumnType("integer") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_antag"); + + b.HasIndex("ProfileId", "AntagName") + .IsUnique(); + + b.ToTable("antag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AssignedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("assigned_user_id_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("PK_assigned_user_id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.HasIndex("UserName") + .IsUnique(); + + b.ToTable("assigned_user_id", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.BanTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("ban_template_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AutoDelete") + .HasColumnType("boolean") + .HasColumnName("auto_delete"); + + b.Property("ExemptFlags") + .HasColumnType("integer") + .HasColumnName("exempt_flags"); + + b.Property("Hidden") + .HasColumnType("boolean") + .HasColumnName("hidden"); + + b.Property("Length") + .HasColumnType("interval") + .HasColumnName("length"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text") + .HasColumnName("reason"); + + b.Property("Severity") + .HasColumnType("integer") + .HasColumnName("severity"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text") + .HasColumnName("title"); + + b.HasKey("Id") + .HasName("PK_ban_template"); + + b.ToTable("ban_template", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("connection_log_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasColumnType("inet") + .HasColumnName("address"); + + b.Property("Denied") + .HasColumnType("smallint") + .HasColumnName("denied"); + + b.Property("HWId") + .HasColumnType("bytea") + .HasColumnName("hwid"); + + b.Property("ServerId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("server_id"); + + b.Property("Time") + .HasColumnType("timestamp with time zone") + .HasColumnName("time"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("PK_connection_log"); + + b.HasIndex("ServerId") + .HasDatabaseName("IX_connection_log_server_id"); + + b.HasIndex("UserId"); + + b.ToTable("connection_log", null, t => + { + t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("job_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("JobName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("job_name"); + + b.Property("Priority") + .HasColumnType("integer") + .HasColumnName("priority"); + + b.Property("ProfileId") + .HasColumnType("integer") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_job"); + + b.HasIndex("ProfileId"); + + b.HasIndex("ProfileId", "JobName") + .IsUnique(); + + b.HasIndex(new[] { "ProfileId" }, "IX_job_one_high_priority") + .IsUnique() + .HasFilter("priority = 3"); + + b.ToTable("job", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Loadout", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("loadout_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("LoadoutName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("loadout_name"); + + b.Property("ProfileId") + .HasColumnType("integer") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_loadout"); + + b.HasIndex("ProfileId", "LoadoutName") + .IsUnique(); + + b.ToTable("loadout", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.PlayTime", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("play_time_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("PlayerId") + .HasColumnType("uuid") + .HasColumnName("player_id"); + + b.Property("TimeSpent") + .HasColumnType("interval") + .HasColumnName("time_spent"); + + b.Property("Tracker") + .IsRequired() + .HasColumnType("text") + .HasColumnName("tracker"); + + b.HasKey("Id") + .HasName("PK_play_time"); + + b.HasIndex("PlayerId", "Tracker") + .IsUnique(); + + b.ToTable("play_time", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("player_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FirstSeenTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("first_seen_time"); + + b.Property("LastReadRules") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_read_rules"); + + b.Property("LastSeenAddress") + .IsRequired() + .HasColumnType("inet") + .HasColumnName("last_seen_address"); + + b.Property("LastSeenHWId") + .HasColumnType("bytea") + .HasColumnName("last_seen_hwid"); + + b.Property("LastSeenTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_seen_time"); + + b.Property("LastSeenUserName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("last_seen_user_name"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_player"); + + b.HasAlternateKey("UserId") + .HasName("ak_player_user_id"); + + b.HasIndex("LastSeenUserName"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("player", null, t => + { + t.HasCheckConstraint("LastSeenAddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= last_seen_address"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("preference_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdminOOCColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("admin_ooc_color"); + + b.Property("SelectedCharacterSlot") + .HasColumnType("integer") + .HasColumnName("selected_character_slot"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_preference"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("preference", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("profile_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Age") + .HasColumnType("integer") + .HasColumnName("age"); + + b.Property("Backpack") + .IsRequired() + .HasColumnType("text") + .HasColumnName("backpack"); + + b.Property("CharacterName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("char_name"); + + b.Property("Clothing") + .IsRequired() + .HasColumnType("text") + .HasColumnName("clothing"); + + b.Property("EyeColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("eye_color"); + + b.Property("FacialHairColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("facial_hair_color"); + + b.Property("FacialHairName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("facial_hair_name"); + + b.Property("FlavorText") + .IsRequired() + .HasColumnType("text") + .HasColumnName("flavor_text"); + + b.Property("Gender") + .IsRequired() + .HasColumnType("text") + .HasColumnName("gender"); + + b.Property("HairColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("hair_color"); + + b.Property("HairName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("hair_name"); + + b.Property("Markings") + .HasColumnType("jsonb") + .HasColumnName("markings"); + + b.Property("PreferenceId") + .HasColumnType("integer") + .HasColumnName("preference_id"); + + b.Property("PreferenceUnavailable") + .HasColumnType("integer") + .HasColumnName("pref_unavailable"); + + b.Property("Sex") + .IsRequired() + .HasColumnType("text") + .HasColumnName("sex"); + + b.Property("SkinColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("skin_color"); + + b.Property("Slot") + .HasColumnType("integer") + .HasColumnName("slot"); + + b.Property("SpawnPriority") + .HasColumnType("integer") + .HasColumnName("spawn_priority"); + + b.Property("Species") + .IsRequired() + .HasColumnType("text") + .HasColumnName("species"); + + b.HasKey("Id") + .HasName("PK_profile"); + + b.HasIndex("PreferenceId") + .HasDatabaseName("IX_profile_preference_id"); + + b.HasIndex("Slot", "PreferenceId") + .IsUnique(); + + b.ToTable("profile", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("round_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ServerId") + .HasColumnType("integer") + .HasColumnName("server_id"); + + b.Property("StartDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValue(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)) + .HasColumnName("start_date"); + + b.HasKey("Id") + .HasName("PK_round"); + + b.HasIndex("ServerId") + .HasDatabaseName("IX_round_server_id"); + + b.HasIndex("StartDate"); + + b.ToTable("round", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Server", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("PK_server"); + + b.ToTable("server", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_ban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .HasColumnType("inet") + .HasColumnName("address"); + + b.Property("AutoDelete") + .HasColumnType("boolean") + .HasColumnName("auto_delete"); + + b.Property("BanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("uuid") + .HasColumnName("banning_admin"); + + b.Property("ExemptFlags") + .HasColumnType("integer") + .HasColumnName("exempt_flags"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("HWId") + .HasColumnType("bytea") + .HasColumnName("hwid"); + + b.Property("Hidden") + .HasColumnType("boolean") + .HasColumnName("hidden"); + + b.Property("LastEditedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text") + .HasColumnName("reason"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Severity") + .HasColumnType("integer") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_server_ban"); + + b.HasIndex("Address"); + + b.HasIndex("BanningAdmin"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_server_ban_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_server_ban_round_id"); + + b.ToTable("server_ban", null, t => + { + t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + + t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanExemption", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("Flags") + .HasColumnType("integer") + .HasColumnName("flags"); + + b.HasKey("UserId") + .HasName("PK_server_ban_exemption"); + + b.ToTable("server_ban_exemption", null, t => + { + t.HasCheckConstraint("FlagsNotZero", "flags != 0"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanHit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_ban_hit_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BanId") + .HasColumnType("integer") + .HasColumnName("ban_id"); + + b.Property("ConnectionId") + .HasColumnType("integer") + .HasColumnName("connection_id"); + + b.HasKey("Id") + .HasName("PK_server_ban_hit"); + + b.HasIndex("BanId") + .HasDatabaseName("IX_server_ban_hit_ban_id"); + + b.HasIndex("ConnectionId") + .HasDatabaseName("IX_server_ban_hit_connection_id"); + + b.ToTable("server_ban_hit", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_role_ban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .HasColumnType("inet") + .HasColumnName("address"); + + b.Property("BanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("uuid") + .HasColumnName("banning_admin"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("HWId") + .HasColumnType("bytea") + .HasColumnName("hwid"); + + b.Property("Hidden") + .HasColumnType("boolean") + .HasColumnName("hidden"); + + b.Property("LastEditedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text") + .HasColumnName("reason"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("role_id"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Severity") + .HasColumnType("integer") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_server_role_ban"); + + b.HasIndex("Address"); + + b.HasIndex("BanningAdmin"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_server_role_ban_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_server_role_ban_round_id"); + + b.ToTable("server_role_ban", null, t => + { + t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + + t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("role_unban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BanId") + .HasColumnType("integer") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("unban_time"); + + b.Property("UnbanningAdmin") + .HasColumnType("uuid") + .HasColumnName("unbanning_admin"); + + b.HasKey("Id") + .HasName("PK_server_role_unban"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("server_role_unban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("unban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BanId") + .HasColumnType("integer") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("unban_time"); + + b.Property("UnbanningAdmin") + .HasColumnType("uuid") + .HasColumnName("unbanning_admin"); + + b.HasKey("Id") + .HasName("PK_server_unban"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("server_unban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Trait", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("trait_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ProfileId") + .HasColumnType("integer") + .HasColumnName("profile_id"); + + b.Property("TraitName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("trait_name"); + + b.HasKey("Id") + .HasName("PK_trait"); + + b.HasIndex("ProfileId", "TraitName") + .IsUnique(); + + b.ToTable("trait", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.UploadedResourceLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("uploaded_resource_log_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Data") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("data"); + + b.Property("Date") + .HasColumnType("timestamp with time zone") + .HasColumnName("date"); + + b.Property("Path") + .IsRequired() + .HasColumnType("text") + .HasColumnName("path"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_uploaded_resource_log"); + + b.ToTable("uploaded_resource_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Whitelist", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("UserId") + .HasName("PK_whitelist"); + + b.ToTable("whitelist", (string)null); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.Property("PlayersId") + .HasColumnType("integer") + .HasColumnName("players_id"); + + b.Property("RoundsId") + .HasColumnType("integer") + .HasColumnName("rounds_id"); + + b.HasKey("PlayersId", "RoundsId") + .HasName("PK_player_round"); + + b.HasIndex("RoundsId") + .HasDatabaseName("IX_player_round_rounds_id"); + + b.ToTable("player_round", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.HasOne("Content.Server.Database.AdminRank", "AdminRank") + .WithMany("Admins") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_admin_rank_admin_rank_id"); + + b.Navigation("AdminRank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.HasOne("Content.Server.Database.Admin", "Admin") + .WithMany("Flags") + .HasForeignKey("AdminId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_flag_admin_admin_id"); + + b.Navigation("Admin"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany("AdminLogs") + .HasForeignKey("RoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_round_round_id"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => + { + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminLogs") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_player_player_player_user_id"); + + b.HasOne("Content.Server.Database.AdminLog", "Log") + .WithMany("Players") + .HasForeignKey("RoundId", "LogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_player_admin_log_round_id_log_id"); + + b.Navigation("Log"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminMessage", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminMessagesCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminMessagesDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminMessagesLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminMessagesReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_messages_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_messages_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminNote", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminNotesCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminNotesDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminNotesLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminNotesReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_notes_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_notes_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.HasOne("Content.Server.Database.AdminRank", "Rank") + .WithMany("Flags") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_rank_flag_admin_rank_admin_rank_id"); + + b.Navigation("Rank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminWatchlistsCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminWatchlistsDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminWatchlistsLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminWatchlistsReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_watchlists_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_watchlists_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Antags") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_antag_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.HasOne("Content.Server.Database.Server", "Server") + .WithMany("ConnectionLogs") + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.SetNull) + .IsRequired() + .HasConstraintName("FK_connection_log_server_server_id"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Jobs") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_job_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.Loadout", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Loadouts") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_loadout_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.HasOne("Content.Server.Database.Preference", "Preference") + .WithMany("Profiles") + .HasForeignKey("PreferenceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_preference_preference_id"); + + b.Navigation("Preference"); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.HasOne("Content.Server.Database.Server", "Server") + .WithMany("Rounds") + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_round_server_server_id"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminServerBansCreated") + .HasForeignKey("BanningAdmin") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_ban_player_banning_admin"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminServerBansLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_ban_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_server_ban_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanHit", b => + { + b.HasOne("Content.Server.Database.ServerBan", "Ban") + .WithMany("BanHits") + .HasForeignKey("BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_ban_hit_server_ban_ban_id"); + + b.HasOne("Content.Server.Database.ConnectionLog", "Connection") + .WithMany("BanHits") + .HasForeignKey("ConnectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_ban_hit_connection_log_connection_id"); + + b.Navigation("Ban"); + + b.Navigation("Connection"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminServerRoleBansCreated") + .HasForeignKey("BanningAdmin") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_role_ban_player_banning_admin"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminServerRoleBansLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_role_ban_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_server_role_ban_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => + { + b.HasOne("Content.Server.Database.ServerRoleBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.ServerRoleUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_role_unban_server_role_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerUnban", b => + { + b.HasOne("Content.Server.Database.ServerBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.ServerUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_unban_server_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.Trait", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Traits") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_trait_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.HasOne("Content.Server.Database.Player", null) + .WithMany() + .HasForeignKey("PlayersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_player_round_player_players_id"); + + b.HasOne("Content.Server.Database.Round", null) + .WithMany() + .HasForeignKey("RoundsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_player_round_round_rounds_id"); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.Navigation("Players"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Navigation("Admins"); + + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.Navigation("BanHits"); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.Navigation("AdminLogs"); + + b.Navigation("AdminMessagesCreated"); + + b.Navigation("AdminMessagesDeleted"); + + b.Navigation("AdminMessagesLastEdited"); + + b.Navigation("AdminMessagesReceived"); + + b.Navigation("AdminNotesCreated"); + + b.Navigation("AdminNotesDeleted"); + + b.Navigation("AdminNotesLastEdited"); + + b.Navigation("AdminNotesReceived"); + + b.Navigation("AdminServerBansCreated"); + + b.Navigation("AdminServerBansLastEdited"); + + b.Navigation("AdminServerRoleBansCreated"); + + b.Navigation("AdminServerRoleBansLastEdited"); + + b.Navigation("AdminWatchlistsCreated"); + + b.Navigation("AdminWatchlistsDeleted"); + + b.Navigation("AdminWatchlistsLastEdited"); + + b.Navigation("AdminWatchlistsReceived"); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Navigation("Profiles"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Navigation("Antags"); + + b.Navigation("Jobs"); + + b.Navigation("Loadouts"); + + b.Navigation("Traits"); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.Navigation("AdminLogs"); + }); + + modelBuilder.Entity("Content.Server.Database.Server", b => + { + b.Navigation("ConnectionLogs"); + + b.Navigation("Rounds"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.Navigation("BanHits"); + + b.Navigation("Unban"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.Navigation("Unban"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Content.Server.Database/Migrations/Postgres/20240623005121_BanTemplate.cs b/Content.Server.Database/Migrations/Postgres/20240623005121_BanTemplate.cs new file mode 100644 index 0000000000..192e87ac96 --- /dev/null +++ b/Content.Server.Database/Migrations/Postgres/20240623005121_BanTemplate.cs @@ -0,0 +1,42 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Content.Server.Database.Migrations.Postgres +{ + /// + public partial class BanTemplate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "ban_template", + columns: table => new + { + ban_template_id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + title = table.Column(type: "text", nullable: false), + length = table.Column(type: "interval", nullable: false), + reason = table.Column(type: "text", nullable: false), + exempt_flags = table.Column(type: "integer", nullable: false), + severity = table.Column(type: "integer", nullable: false), + auto_delete = table.Column(type: "boolean", nullable: false), + hidden = table.Column(type: "boolean", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ban_template", x => x.ban_template_id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ban_template"); + } + } +} diff --git a/Content.Server.Database/Migrations/Postgres/20240926173711_1984 Backpack Prefs.Designer.cs b/Content.Server.Database/Migrations/Postgres/20240926173711_1984 Backpack Prefs.Designer.cs new file mode 100644 index 0000000000..0a583c10a0 --- /dev/null +++ b/Content.Server.Database/Migrations/Postgres/20240926173711_1984 Backpack Prefs.Designer.cs @@ -0,0 +1,1815 @@ +// +using System; +using System.Net; +using System.Text.Json; +using Content.Server.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using NpgsqlTypes; + +#nullable disable + +namespace Content.Server.Database.Migrations.Postgres +{ + [DbContext(typeof(PostgresServerDbContext))] + [Migration("20240926173711_1984 Backpack Prefs")] + partial class _1984BackpackPrefs + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("AdminRankId") + .HasColumnType("integer") + .HasColumnName("admin_rank_id"); + + b.Property("Title") + .HasColumnType("text") + .HasColumnName("title"); + + b.HasKey("UserId") + .HasName("PK_admin"); + + b.HasIndex("AdminRankId") + .HasDatabaseName("IX_admin_admin_rank_id"); + + b.ToTable("admin", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_flag_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdminId") + .HasColumnType("uuid") + .HasColumnName("admin_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("text") + .HasColumnName("flag"); + + b.Property("Negative") + .HasColumnType("boolean") + .HasColumnName("negative"); + + b.HasKey("Id") + .HasName("PK_admin_flag"); + + b.HasIndex("AdminId") + .HasDatabaseName("IX_admin_flag_admin_id"); + + b.HasIndex("Flag", "AdminId") + .IsUnique(); + + b.ToTable("admin_flag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Id") + .HasColumnType("integer") + .HasColumnName("admin_log_id"); + + b.Property("Date") + .HasColumnType("timestamp with time zone") + .HasColumnName("date"); + + b.Property("Impact") + .HasColumnType("smallint") + .HasColumnName("impact"); + + b.Property("Json") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("json"); + + b.Property("Message") + .IsRequired() + .HasColumnType("text") + .HasColumnName("message"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("type"); + + b.HasKey("RoundId", "Id") + .HasName("PK_admin_log"); + + b.HasIndex("Date"); + + b.HasIndex("Message") + .HasAnnotation("Npgsql:TsVectorConfig", "english"); + + NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("Message"), "GIN"); + + b.HasIndex("Type") + .HasDatabaseName("IX_admin_log_type"); + + b.ToTable("admin_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => + { + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("LogId") + .HasColumnType("integer") + .HasColumnName("log_id"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.HasKey("RoundId", "LogId", "PlayerUserId") + .HasName("PK_admin_log_player"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_log_player_player_user_id"); + + b.ToTable("admin_log_player", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_messages_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("Dismissed") + .HasColumnType("boolean") + .HasColumnName("dismissed"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Seen") + .HasColumnType("boolean") + .HasColumnName("seen"); + + b.HasKey("Id") + .HasName("PK_admin_messages"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_messages_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_messages_round_id"); + + b.ToTable("admin_messages", null, t => + { + t.HasCheckConstraint("NotDismissedAndSeen", "NOT dismissed OR seen"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.AdminNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_notes_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .IsRequired() + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Secret") + .HasColumnType("boolean") + .HasColumnName("secret"); + + b.Property("Severity") + .HasColumnType("integer") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_admin_notes"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_notes_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_notes_round_id"); + + b.ToTable("admin_notes", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_rank_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("PK_admin_rank"); + + b.ToTable("admin_rank", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_rank_flag_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdminRankId") + .HasColumnType("integer") + .HasColumnName("admin_rank_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("text") + .HasColumnName("flag"); + + b.HasKey("Id") + .HasName("PK_admin_rank_flag"); + + b.HasIndex("AdminRankId"); + + b.HasIndex("Flag", "AdminRankId") + .IsUnique(); + + b.ToTable("admin_rank_flag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_watchlists_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .IsRequired() + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.HasKey("Id") + .HasName("PK_admin_watchlists"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_watchlists_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_watchlists_round_id"); + + b.ToTable("admin_watchlists", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("antag_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AntagName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("antag_name"); + + b.Property("ProfileId") + .HasColumnType("integer") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_antag"); + + b.HasIndex("ProfileId", "AntagName") + .IsUnique(); + + b.ToTable("antag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AssignedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("assigned_user_id_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("PK_assigned_user_id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.HasIndex("UserName") + .IsUnique(); + + b.ToTable("assigned_user_id", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("connection_log_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasColumnType("inet") + .HasColumnName("address"); + + b.Property("Denied") + .HasColumnType("smallint") + .HasColumnName("denied"); + + b.Property("HWId") + .HasColumnType("bytea") + .HasColumnName("hwid"); + + b.Property("ServerId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("server_id"); + + b.Property("Time") + .HasColumnType("timestamp with time zone") + .HasColumnName("time"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("PK_connection_log"); + + b.HasIndex("ServerId") + .HasDatabaseName("IX_connection_log_server_id"); + + b.HasIndex("UserId"); + + b.ToTable("connection_log", null, t => + { + t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("job_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("JobName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("job_name"); + + b.Property("Priority") + .HasColumnType("integer") + .HasColumnName("priority"); + + b.Property("ProfileId") + .HasColumnType("integer") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_job"); + + b.HasIndex("ProfileId"); + + b.HasIndex("ProfileId", "JobName") + .IsUnique(); + + b.HasIndex(new[] { "ProfileId" }, "IX_job_one_high_priority") + .IsUnique() + .HasFilter("priority = 3"); + + b.ToTable("job", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Loadout", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("loadout_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("LoadoutName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("loadout_name"); + + b.Property("ProfileId") + .HasColumnType("integer") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_loadout"); + + b.HasIndex("ProfileId", "LoadoutName") + .IsUnique(); + + b.ToTable("loadout", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.PlayTime", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("play_time_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("PlayerId") + .HasColumnType("uuid") + .HasColumnName("player_id"); + + b.Property("TimeSpent") + .HasColumnType("interval") + .HasColumnName("time_spent"); + + b.Property("Tracker") + .IsRequired() + .HasColumnType("text") + .HasColumnName("tracker"); + + b.HasKey("Id") + .HasName("PK_play_time"); + + b.HasIndex("PlayerId", "Tracker") + .IsUnique(); + + b.ToTable("play_time", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("player_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FirstSeenTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("first_seen_time"); + + b.Property("LastReadRules") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_read_rules"); + + b.Property("LastSeenAddress") + .IsRequired() + .HasColumnType("inet") + .HasColumnName("last_seen_address"); + + b.Property("LastSeenHWId") + .HasColumnType("bytea") + .HasColumnName("last_seen_hwid"); + + b.Property("LastSeenTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_seen_time"); + + b.Property("LastSeenUserName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("last_seen_user_name"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_player"); + + b.HasAlternateKey("UserId") + .HasName("ak_player_user_id"); + + b.HasIndex("LastSeenUserName"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("player", null, t => + { + t.HasCheckConstraint("LastSeenAddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= last_seen_address"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("preference_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdminOOCColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("admin_ooc_color"); + + b.Property("SelectedCharacterSlot") + .HasColumnType("integer") + .HasColumnName("selected_character_slot"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_preference"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("preference", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("profile_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Age") + .HasColumnType("integer") + .HasColumnName("age"); + + b.Property("Backpack") + .IsRequired() + .HasColumnType("text") + .HasColumnName("backpack"); + + b.Property("CharacterName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("char_name"); + + b.Property("Clothing") + .IsRequired() + .HasColumnType("text") + .HasColumnName("clothing"); + + b.Property("EyeColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("eye_color"); + + b.Property("FacialHairColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("facial_hair_color"); + + b.Property("FacialHairName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("facial_hair_name"); + + b.Property("FlavorText") + .IsRequired() + .HasColumnType("text") + .HasColumnName("flavor_text"); + + b.Property("Gender") + .IsRequired() + .HasColumnType("text") + .HasColumnName("gender"); + + b.Property("HairColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("hair_color"); + + b.Property("HairName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("hair_name"); + + b.Property("Height") + .HasColumnType("real") + .HasColumnName("height"); + + b.Property("Markings") + .HasColumnType("jsonb") + .HasColumnName("markings"); + + b.Property("PreferenceId") + .HasColumnType("integer") + .HasColumnName("preference_id"); + + b.Property("PreferenceUnavailable") + .HasColumnType("integer") + .HasColumnName("pref_unavailable"); + + b.Property("Sex") + .IsRequired() + .HasColumnType("text") + .HasColumnName("sex"); + + b.Property("SkinColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("skin_color"); + + b.Property("Slot") + .HasColumnType("integer") + .HasColumnName("slot"); + + b.Property("SpawnPriority") + .HasColumnType("integer") + .HasColumnName("spawn_priority"); + + b.Property("Species") + .IsRequired() + .HasColumnType("text") + .HasColumnName("species"); + + b.Property("Width") + .HasColumnType("real") + .HasColumnName("width"); + + b.HasKey("Id") + .HasName("PK_profile"); + + b.HasIndex("PreferenceId") + .HasDatabaseName("IX_profile_preference_id"); + + b.HasIndex("Slot", "PreferenceId") + .IsUnique(); + + b.ToTable("profile", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("round_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ServerId") + .HasColumnType("integer") + .HasColumnName("server_id"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("start_date"); + + b.HasKey("Id") + .HasName("PK_round"); + + b.HasIndex("ServerId") + .HasDatabaseName("IX_round_server_id"); + + b.HasIndex("StartDate"); + + b.ToTable("round", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Server", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("PK_server"); + + b.ToTable("server", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_ban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .HasColumnType("inet") + .HasColumnName("address"); + + b.Property("AutoDelete") + .HasColumnType("boolean") + .HasColumnName("auto_delete"); + + b.Property("BanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("uuid") + .HasColumnName("banning_admin"); + + b.Property("ExemptFlags") + .HasColumnType("integer") + .HasColumnName("exempt_flags"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("HWId") + .HasColumnType("bytea") + .HasColumnName("hwid"); + + b.Property("Hidden") + .HasColumnType("boolean") + .HasColumnName("hidden"); + + b.Property("LastEditedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text") + .HasColumnName("reason"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Severity") + .HasColumnType("integer") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_server_ban"); + + b.HasIndex("Address"); + + b.HasIndex("BanningAdmin"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_server_ban_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_server_ban_round_id"); + + b.ToTable("server_ban", null, t => + { + t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + + t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanExemption", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("Flags") + .HasColumnType("integer") + .HasColumnName("flags"); + + b.HasKey("UserId") + .HasName("PK_server_ban_exemption"); + + b.ToTable("server_ban_exemption", null, t => + { + t.HasCheckConstraint("FlagsNotZero", "flags != 0"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanHit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_ban_hit_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BanId") + .HasColumnType("integer") + .HasColumnName("ban_id"); + + b.Property("ConnectionId") + .HasColumnType("integer") + .HasColumnName("connection_id"); + + b.HasKey("Id") + .HasName("PK_server_ban_hit"); + + b.HasIndex("BanId") + .HasDatabaseName("IX_server_ban_hit_ban_id"); + + b.HasIndex("ConnectionId") + .HasDatabaseName("IX_server_ban_hit_connection_id"); + + b.ToTable("server_ban_hit", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_role_ban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .HasColumnType("inet") + .HasColumnName("address"); + + b.Property("BanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("uuid") + .HasColumnName("banning_admin"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("HWId") + .HasColumnType("bytea") + .HasColumnName("hwid"); + + b.Property("Hidden") + .HasColumnType("boolean") + .HasColumnName("hidden"); + + b.Property("LastEditedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text") + .HasColumnName("reason"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("role_id"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Severity") + .HasColumnType("integer") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_server_role_ban"); + + b.HasIndex("Address"); + + b.HasIndex("BanningAdmin"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_server_role_ban_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_server_role_ban_round_id"); + + b.ToTable("server_role_ban", null, t => + { + t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + + t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("role_unban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BanId") + .HasColumnType("integer") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("unban_time"); + + b.Property("UnbanningAdmin") + .HasColumnType("uuid") + .HasColumnName("unbanning_admin"); + + b.HasKey("Id") + .HasName("PK_server_role_unban"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("server_role_unban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("unban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BanId") + .HasColumnType("integer") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("unban_time"); + + b.Property("UnbanningAdmin") + .HasColumnType("uuid") + .HasColumnName("unbanning_admin"); + + b.HasKey("Id") + .HasName("PK_server_unban"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("server_unban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Trait", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("trait_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ProfileId") + .HasColumnType("integer") + .HasColumnName("profile_id"); + + b.Property("TraitName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("trait_name"); + + b.HasKey("Id") + .HasName("PK_trait"); + + b.HasIndex("ProfileId", "TraitName") + .IsUnique(); + + b.ToTable("trait", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.UploadedResourceLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("uploaded_resource_log_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Data") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("data"); + + b.Property("Date") + .HasColumnType("timestamp with time zone") + .HasColumnName("date"); + + b.Property("Path") + .IsRequired() + .HasColumnType("text") + .HasColumnName("path"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_uploaded_resource_log"); + + b.ToTable("uploaded_resource_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Whitelist", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("UserId") + .HasName("PK_whitelist"); + + b.ToTable("whitelist", (string)null); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.Property("PlayersId") + .HasColumnType("integer") + .HasColumnName("players_id"); + + b.Property("RoundsId") + .HasColumnType("integer") + .HasColumnName("rounds_id"); + + b.HasKey("PlayersId", "RoundsId") + .HasName("PK_player_round"); + + b.HasIndex("RoundsId") + .HasDatabaseName("IX_player_round_rounds_id"); + + b.ToTable("player_round", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.HasOne("Content.Server.Database.AdminRank", "AdminRank") + .WithMany("Admins") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_admin_rank_admin_rank_id"); + + b.Navigation("AdminRank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.HasOne("Content.Server.Database.Admin", "Admin") + .WithMany("Flags") + .HasForeignKey("AdminId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_flag_admin_admin_id"); + + b.Navigation("Admin"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany("AdminLogs") + .HasForeignKey("RoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_round_round_id"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => + { + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminLogs") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_player_player_player_user_id"); + + b.HasOne("Content.Server.Database.AdminLog", "Log") + .WithMany("Players") + .HasForeignKey("RoundId", "LogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_player_admin_log_round_id_log_id"); + + b.Navigation("Log"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminMessage", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminMessagesCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminMessagesDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminMessagesLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminMessagesReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_messages_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_messages_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminNote", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminNotesCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminNotesDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminNotesLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminNotesReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_notes_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_notes_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.HasOne("Content.Server.Database.AdminRank", "Rank") + .WithMany("Flags") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_rank_flag_admin_rank_admin_rank_id"); + + b.Navigation("Rank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminWatchlistsCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminWatchlistsDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminWatchlistsLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminWatchlistsReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_watchlists_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_watchlists_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Antags") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_antag_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.HasOne("Content.Server.Database.Server", "Server") + .WithMany("ConnectionLogs") + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.SetNull) + .IsRequired() + .HasConstraintName("FK_connection_log_server_server_id"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Jobs") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_job_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.Loadout", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Loadouts") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_loadout_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.HasOne("Content.Server.Database.Preference", "Preference") + .WithMany("Profiles") + .HasForeignKey("PreferenceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_preference_preference_id"); + + b.Navigation("Preference"); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.HasOne("Content.Server.Database.Server", "Server") + .WithMany("Rounds") + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_round_server_server_id"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminServerBansCreated") + .HasForeignKey("BanningAdmin") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_ban_player_banning_admin"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminServerBansLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_ban_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_server_ban_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanHit", b => + { + b.HasOne("Content.Server.Database.ServerBan", "Ban") + .WithMany("BanHits") + .HasForeignKey("BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_ban_hit_server_ban_ban_id"); + + b.HasOne("Content.Server.Database.ConnectionLog", "Connection") + .WithMany("BanHits") + .HasForeignKey("ConnectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_ban_hit_connection_log_connection_id"); + + b.Navigation("Ban"); + + b.Navigation("Connection"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminServerRoleBansCreated") + .HasForeignKey("BanningAdmin") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_role_ban_player_banning_admin"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminServerRoleBansLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_role_ban_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_server_role_ban_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => + { + b.HasOne("Content.Server.Database.ServerRoleBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.ServerRoleUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_role_unban_server_role_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerUnban", b => + { + b.HasOne("Content.Server.Database.ServerBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.ServerUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_unban_server_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.Trait", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Traits") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_trait_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.HasOne("Content.Server.Database.Player", null) + .WithMany() + .HasForeignKey("PlayersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_player_round_player_players_id"); + + b.HasOne("Content.Server.Database.Round", null) + .WithMany() + .HasForeignKey("RoundsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_player_round_round_rounds_id"); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.Navigation("Players"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Navigation("Admins"); + + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.Navigation("BanHits"); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.Navigation("AdminLogs"); + + b.Navigation("AdminMessagesCreated"); + + b.Navigation("AdminMessagesDeleted"); + + b.Navigation("AdminMessagesLastEdited"); + + b.Navigation("AdminMessagesReceived"); + + b.Navigation("AdminNotesCreated"); + + b.Navigation("AdminNotesDeleted"); + + b.Navigation("AdminNotesLastEdited"); + + b.Navigation("AdminNotesReceived"); + + b.Navigation("AdminServerBansCreated"); + + b.Navigation("AdminServerBansLastEdited"); + + b.Navigation("AdminServerRoleBansCreated"); + + b.Navigation("AdminServerRoleBansLastEdited"); + + b.Navigation("AdminWatchlistsCreated"); + + b.Navigation("AdminWatchlistsDeleted"); + + b.Navigation("AdminWatchlistsLastEdited"); + + b.Navigation("AdminWatchlistsReceived"); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Navigation("Profiles"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Navigation("Antags"); + + b.Navigation("Jobs"); + + b.Navigation("Loadouts"); + + b.Navigation("Traits"); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.Navigation("AdminLogs"); + }); + + modelBuilder.Entity("Content.Server.Database.Server", b => + { + b.Navigation("ConnectionLogs"); + + b.Navigation("Rounds"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.Navigation("BanHits"); + + b.Navigation("Unban"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.Navigation("Unban"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Content.Server.Database/Migrations/Postgres/20240926173711_1984 Backpack Prefs.cs b/Content.Server.Database/Migrations/Postgres/20240926173711_1984 Backpack Prefs.cs new file mode 100644 index 0000000000..0e137db06c --- /dev/null +++ b/Content.Server.Database/Migrations/Postgres/20240926173711_1984 Backpack Prefs.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Content.Server.Database.Migrations.Postgres +{ + /// + public partial class _1984BackpackPrefs : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/Content.Server.Database/Migrations/Postgres/20241001054803_CustomSpecieName.Designer.cs b/Content.Server.Database/Migrations/Postgres/20241001054803_CustomSpecieName.Designer.cs new file mode 100644 index 0000000000..d4f96b21e5 --- /dev/null +++ b/Content.Server.Database/Migrations/Postgres/20241001054803_CustomSpecieName.Designer.cs @@ -0,0 +1,1820 @@ +// +using System; +using System.Net; +using System.Text.Json; +using Content.Server.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using NpgsqlTypes; + +#nullable disable + +namespace Content.Server.Database.Migrations.Postgres +{ + [DbContext(typeof(PostgresServerDbContext))] + [Migration("20241001054803_CustomSpecieName")] + partial class CustomSpecieName + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("AdminRankId") + .HasColumnType("integer") + .HasColumnName("admin_rank_id"); + + b.Property("Title") + .HasColumnType("text") + .HasColumnName("title"); + + b.HasKey("UserId") + .HasName("PK_admin"); + + b.HasIndex("AdminRankId") + .HasDatabaseName("IX_admin_admin_rank_id"); + + b.ToTable("admin", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_flag_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdminId") + .HasColumnType("uuid") + .HasColumnName("admin_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("text") + .HasColumnName("flag"); + + b.Property("Negative") + .HasColumnType("boolean") + .HasColumnName("negative"); + + b.HasKey("Id") + .HasName("PK_admin_flag"); + + b.HasIndex("AdminId") + .HasDatabaseName("IX_admin_flag_admin_id"); + + b.HasIndex("Flag", "AdminId") + .IsUnique(); + + b.ToTable("admin_flag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Id") + .HasColumnType("integer") + .HasColumnName("admin_log_id"); + + b.Property("Date") + .HasColumnType("timestamp with time zone") + .HasColumnName("date"); + + b.Property("Impact") + .HasColumnType("smallint") + .HasColumnName("impact"); + + b.Property("Json") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("json"); + + b.Property("Message") + .IsRequired() + .HasColumnType("text") + .HasColumnName("message"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("type"); + + b.HasKey("RoundId", "Id") + .HasName("PK_admin_log"); + + b.HasIndex("Date"); + + b.HasIndex("Message") + .HasAnnotation("Npgsql:TsVectorConfig", "english"); + + NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("Message"), "GIN"); + + b.HasIndex("Type") + .HasDatabaseName("IX_admin_log_type"); + + b.ToTable("admin_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => + { + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("LogId") + .HasColumnType("integer") + .HasColumnName("log_id"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.HasKey("RoundId", "LogId", "PlayerUserId") + .HasName("PK_admin_log_player"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_log_player_player_user_id"); + + b.ToTable("admin_log_player", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_messages_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("Dismissed") + .HasColumnType("boolean") + .HasColumnName("dismissed"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Seen") + .HasColumnType("boolean") + .HasColumnName("seen"); + + b.HasKey("Id") + .HasName("PK_admin_messages"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_messages_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_messages_round_id"); + + b.ToTable("admin_messages", null, t => + { + t.HasCheckConstraint("NotDismissedAndSeen", "NOT dismissed OR seen"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.AdminNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_notes_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .IsRequired() + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Secret") + .HasColumnType("boolean") + .HasColumnName("secret"); + + b.Property("Severity") + .HasColumnType("integer") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_admin_notes"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_notes_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_notes_round_id"); + + b.ToTable("admin_notes", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_rank_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("PK_admin_rank"); + + b.ToTable("admin_rank", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_rank_flag_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdminRankId") + .HasColumnType("integer") + .HasColumnName("admin_rank_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("text") + .HasColumnName("flag"); + + b.HasKey("Id") + .HasName("PK_admin_rank_flag"); + + b.HasIndex("AdminRankId"); + + b.HasIndex("Flag", "AdminRankId") + .IsUnique(); + + b.ToTable("admin_rank_flag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_watchlists_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .IsRequired() + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.HasKey("Id") + .HasName("PK_admin_watchlists"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_watchlists_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_watchlists_round_id"); + + b.ToTable("admin_watchlists", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("antag_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AntagName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("antag_name"); + + b.Property("ProfileId") + .HasColumnType("integer") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_antag"); + + b.HasIndex("ProfileId", "AntagName") + .IsUnique(); + + b.ToTable("antag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AssignedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("assigned_user_id_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("PK_assigned_user_id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.HasIndex("UserName") + .IsUnique(); + + b.ToTable("assigned_user_id", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("connection_log_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasColumnType("inet") + .HasColumnName("address"); + + b.Property("Denied") + .HasColumnType("smallint") + .HasColumnName("denied"); + + b.Property("HWId") + .HasColumnType("bytea") + .HasColumnName("hwid"); + + b.Property("ServerId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("server_id"); + + b.Property("Time") + .HasColumnType("timestamp with time zone") + .HasColumnName("time"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("PK_connection_log"); + + b.HasIndex("ServerId") + .HasDatabaseName("IX_connection_log_server_id"); + + b.HasIndex("UserId"); + + b.ToTable("connection_log", null, t => + { + t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("job_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("JobName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("job_name"); + + b.Property("Priority") + .HasColumnType("integer") + .HasColumnName("priority"); + + b.Property("ProfileId") + .HasColumnType("integer") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_job"); + + b.HasIndex("ProfileId"); + + b.HasIndex("ProfileId", "JobName") + .IsUnique(); + + b.HasIndex(new[] { "ProfileId" }, "IX_job_one_high_priority") + .IsUnique() + .HasFilter("priority = 3"); + + b.ToTable("job", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Loadout", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("loadout_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("LoadoutName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("loadout_name"); + + b.Property("ProfileId") + .HasColumnType("integer") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_loadout"); + + b.HasIndex("ProfileId", "LoadoutName") + .IsUnique(); + + b.ToTable("loadout", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.PlayTime", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("play_time_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("PlayerId") + .HasColumnType("uuid") + .HasColumnName("player_id"); + + b.Property("TimeSpent") + .HasColumnType("interval") + .HasColumnName("time_spent"); + + b.Property("Tracker") + .IsRequired() + .HasColumnType("text") + .HasColumnName("tracker"); + + b.HasKey("Id") + .HasName("PK_play_time"); + + b.HasIndex("PlayerId", "Tracker") + .IsUnique(); + + b.ToTable("play_time", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("player_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FirstSeenTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("first_seen_time"); + + b.Property("LastReadRules") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_read_rules"); + + b.Property("LastSeenAddress") + .IsRequired() + .HasColumnType("inet") + .HasColumnName("last_seen_address"); + + b.Property("LastSeenHWId") + .HasColumnType("bytea") + .HasColumnName("last_seen_hwid"); + + b.Property("LastSeenTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_seen_time"); + + b.Property("LastSeenUserName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("last_seen_user_name"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_player"); + + b.HasAlternateKey("UserId") + .HasName("ak_player_user_id"); + + b.HasIndex("LastSeenUserName"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("player", null, t => + { + t.HasCheckConstraint("LastSeenAddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= last_seen_address"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("preference_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdminOOCColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("admin_ooc_color"); + + b.Property("SelectedCharacterSlot") + .HasColumnType("integer") + .HasColumnName("selected_character_slot"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_preference"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("preference", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("profile_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Age") + .HasColumnType("integer") + .HasColumnName("age"); + + b.Property("Backpack") + .IsRequired() + .HasColumnType("text") + .HasColumnName("backpack"); + + b.Property("CharacterName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("char_name"); + + b.Property("Clothing") + .IsRequired() + .HasColumnType("text") + .HasColumnName("clothing"); + + b.Property("CustomSpecieName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("custom_specie_name"); + + b.Property("EyeColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("eye_color"); + + b.Property("FacialHairColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("facial_hair_color"); + + b.Property("FacialHairName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("facial_hair_name"); + + b.Property("FlavorText") + .IsRequired() + .HasColumnType("text") + .HasColumnName("flavor_text"); + + b.Property("Gender") + .IsRequired() + .HasColumnType("text") + .HasColumnName("gender"); + + b.Property("HairColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("hair_color"); + + b.Property("HairName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("hair_name"); + + b.Property("Height") + .HasColumnType("real") + .HasColumnName("height"); + + b.Property("Markings") + .HasColumnType("jsonb") + .HasColumnName("markings"); + + b.Property("PreferenceId") + .HasColumnType("integer") + .HasColumnName("preference_id"); + + b.Property("PreferenceUnavailable") + .HasColumnType("integer") + .HasColumnName("pref_unavailable"); + + b.Property("Sex") + .IsRequired() + .HasColumnType("text") + .HasColumnName("sex"); + + b.Property("SkinColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("skin_color"); + + b.Property("Slot") + .HasColumnType("integer") + .HasColumnName("slot"); + + b.Property("SpawnPriority") + .HasColumnType("integer") + .HasColumnName("spawn_priority"); + + b.Property("Species") + .IsRequired() + .HasColumnType("text") + .HasColumnName("species"); + + b.Property("Width") + .HasColumnType("real") + .HasColumnName("width"); + + b.HasKey("Id") + .HasName("PK_profile"); + + b.HasIndex("PreferenceId") + .HasDatabaseName("IX_profile_preference_id"); + + b.HasIndex("Slot", "PreferenceId") + .IsUnique(); + + b.ToTable("profile", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("round_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ServerId") + .HasColumnType("integer") + .HasColumnName("server_id"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("start_date"); + + b.HasKey("Id") + .HasName("PK_round"); + + b.HasIndex("ServerId") + .HasDatabaseName("IX_round_server_id"); + + b.HasIndex("StartDate"); + + b.ToTable("round", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Server", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("PK_server"); + + b.ToTable("server", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_ban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .HasColumnType("inet") + .HasColumnName("address"); + + b.Property("AutoDelete") + .HasColumnType("boolean") + .HasColumnName("auto_delete"); + + b.Property("BanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("uuid") + .HasColumnName("banning_admin"); + + b.Property("ExemptFlags") + .HasColumnType("integer") + .HasColumnName("exempt_flags"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("HWId") + .HasColumnType("bytea") + .HasColumnName("hwid"); + + b.Property("Hidden") + .HasColumnType("boolean") + .HasColumnName("hidden"); + + b.Property("LastEditedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text") + .HasColumnName("reason"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Severity") + .HasColumnType("integer") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_server_ban"); + + b.HasIndex("Address"); + + b.HasIndex("BanningAdmin"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_server_ban_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_server_ban_round_id"); + + b.ToTable("server_ban", null, t => + { + t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + + t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanExemption", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("Flags") + .HasColumnType("integer") + .HasColumnName("flags"); + + b.HasKey("UserId") + .HasName("PK_server_ban_exemption"); + + b.ToTable("server_ban_exemption", null, t => + { + t.HasCheckConstraint("FlagsNotZero", "flags != 0"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanHit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_ban_hit_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BanId") + .HasColumnType("integer") + .HasColumnName("ban_id"); + + b.Property("ConnectionId") + .HasColumnType("integer") + .HasColumnName("connection_id"); + + b.HasKey("Id") + .HasName("PK_server_ban_hit"); + + b.HasIndex("BanId") + .HasDatabaseName("IX_server_ban_hit_ban_id"); + + b.HasIndex("ConnectionId") + .HasDatabaseName("IX_server_ban_hit_connection_id"); + + b.ToTable("server_ban_hit", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_role_ban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .HasColumnType("inet") + .HasColumnName("address"); + + b.Property("BanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("uuid") + .HasColumnName("banning_admin"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("HWId") + .HasColumnType("bytea") + .HasColumnName("hwid"); + + b.Property("Hidden") + .HasColumnType("boolean") + .HasColumnName("hidden"); + + b.Property("LastEditedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text") + .HasColumnName("reason"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("role_id"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Severity") + .HasColumnType("integer") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_server_role_ban"); + + b.HasIndex("Address"); + + b.HasIndex("BanningAdmin"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_server_role_ban_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_server_role_ban_round_id"); + + b.ToTable("server_role_ban", null, t => + { + t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + + t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("role_unban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BanId") + .HasColumnType("integer") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("unban_time"); + + b.Property("UnbanningAdmin") + .HasColumnType("uuid") + .HasColumnName("unbanning_admin"); + + b.HasKey("Id") + .HasName("PK_server_role_unban"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("server_role_unban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("unban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BanId") + .HasColumnType("integer") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("unban_time"); + + b.Property("UnbanningAdmin") + .HasColumnType("uuid") + .HasColumnName("unbanning_admin"); + + b.HasKey("Id") + .HasName("PK_server_unban"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("server_unban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Trait", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("trait_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ProfileId") + .HasColumnType("integer") + .HasColumnName("profile_id"); + + b.Property("TraitName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("trait_name"); + + b.HasKey("Id") + .HasName("PK_trait"); + + b.HasIndex("ProfileId", "TraitName") + .IsUnique(); + + b.ToTable("trait", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.UploadedResourceLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("uploaded_resource_log_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Data") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("data"); + + b.Property("Date") + .HasColumnType("timestamp with time zone") + .HasColumnName("date"); + + b.Property("Path") + .IsRequired() + .HasColumnType("text") + .HasColumnName("path"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_uploaded_resource_log"); + + b.ToTable("uploaded_resource_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Whitelist", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("UserId") + .HasName("PK_whitelist"); + + b.ToTable("whitelist", (string)null); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.Property("PlayersId") + .HasColumnType("integer") + .HasColumnName("players_id"); + + b.Property("RoundsId") + .HasColumnType("integer") + .HasColumnName("rounds_id"); + + b.HasKey("PlayersId", "RoundsId") + .HasName("PK_player_round"); + + b.HasIndex("RoundsId") + .HasDatabaseName("IX_player_round_rounds_id"); + + b.ToTable("player_round", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.HasOne("Content.Server.Database.AdminRank", "AdminRank") + .WithMany("Admins") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_admin_rank_admin_rank_id"); + + b.Navigation("AdminRank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.HasOne("Content.Server.Database.Admin", "Admin") + .WithMany("Flags") + .HasForeignKey("AdminId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_flag_admin_admin_id"); + + b.Navigation("Admin"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany("AdminLogs") + .HasForeignKey("RoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_round_round_id"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => + { + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminLogs") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_player_player_player_user_id"); + + b.HasOne("Content.Server.Database.AdminLog", "Log") + .WithMany("Players") + .HasForeignKey("RoundId", "LogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_player_admin_log_round_id_log_id"); + + b.Navigation("Log"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminMessage", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminMessagesCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminMessagesDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminMessagesLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminMessagesReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_messages_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_messages_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminNote", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminNotesCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminNotesDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminNotesLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminNotesReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_notes_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_notes_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.HasOne("Content.Server.Database.AdminRank", "Rank") + .WithMany("Flags") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_rank_flag_admin_rank_admin_rank_id"); + + b.Navigation("Rank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminWatchlistsCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminWatchlistsDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminWatchlistsLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminWatchlistsReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_watchlists_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_watchlists_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Antags") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_antag_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.HasOne("Content.Server.Database.Server", "Server") + .WithMany("ConnectionLogs") + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.SetNull) + .IsRequired() + .HasConstraintName("FK_connection_log_server_server_id"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Jobs") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_job_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.Loadout", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Loadouts") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_loadout_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.HasOne("Content.Server.Database.Preference", "Preference") + .WithMany("Profiles") + .HasForeignKey("PreferenceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_preference_preference_id"); + + b.Navigation("Preference"); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.HasOne("Content.Server.Database.Server", "Server") + .WithMany("Rounds") + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_round_server_server_id"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminServerBansCreated") + .HasForeignKey("BanningAdmin") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_ban_player_banning_admin"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminServerBansLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_ban_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_server_ban_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanHit", b => + { + b.HasOne("Content.Server.Database.ServerBan", "Ban") + .WithMany("BanHits") + .HasForeignKey("BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_ban_hit_server_ban_ban_id"); + + b.HasOne("Content.Server.Database.ConnectionLog", "Connection") + .WithMany("BanHits") + .HasForeignKey("ConnectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_ban_hit_connection_log_connection_id"); + + b.Navigation("Ban"); + + b.Navigation("Connection"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminServerRoleBansCreated") + .HasForeignKey("BanningAdmin") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_role_ban_player_banning_admin"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminServerRoleBansLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_role_ban_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_server_role_ban_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => + { + b.HasOne("Content.Server.Database.ServerRoleBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.ServerRoleUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_role_unban_server_role_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerUnban", b => + { + b.HasOne("Content.Server.Database.ServerBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.ServerUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_unban_server_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.Trait", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Traits") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_trait_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.HasOne("Content.Server.Database.Player", null) + .WithMany() + .HasForeignKey("PlayersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_player_round_player_players_id"); + + b.HasOne("Content.Server.Database.Round", null) + .WithMany() + .HasForeignKey("RoundsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_player_round_round_rounds_id"); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.Navigation("Players"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Navigation("Admins"); + + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.Navigation("BanHits"); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.Navigation("AdminLogs"); + + b.Navigation("AdminMessagesCreated"); + + b.Navigation("AdminMessagesDeleted"); + + b.Navigation("AdminMessagesLastEdited"); + + b.Navigation("AdminMessagesReceived"); + + b.Navigation("AdminNotesCreated"); + + b.Navigation("AdminNotesDeleted"); + + b.Navigation("AdminNotesLastEdited"); + + b.Navigation("AdminNotesReceived"); + + b.Navigation("AdminServerBansCreated"); + + b.Navigation("AdminServerBansLastEdited"); + + b.Navigation("AdminServerRoleBansCreated"); + + b.Navigation("AdminServerRoleBansLastEdited"); + + b.Navigation("AdminWatchlistsCreated"); + + b.Navigation("AdminWatchlistsDeleted"); + + b.Navigation("AdminWatchlistsLastEdited"); + + b.Navigation("AdminWatchlistsReceived"); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Navigation("Profiles"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Navigation("Antags"); + + b.Navigation("Jobs"); + + b.Navigation("Loadouts"); + + b.Navigation("Traits"); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.Navigation("AdminLogs"); + }); + + modelBuilder.Entity("Content.Server.Database.Server", b => + { + b.Navigation("ConnectionLogs"); + + b.Navigation("Rounds"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.Navigation("BanHits"); + + b.Navigation("Unban"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.Navigation("Unban"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Content.Server.Database/Migrations/Postgres/20241001054803_CustomSpecieName.cs b/Content.Server.Database/Migrations/Postgres/20241001054803_CustomSpecieName.cs new file mode 100644 index 0000000000..6c40d6240f --- /dev/null +++ b/Content.Server.Database/Migrations/Postgres/20241001054803_CustomSpecieName.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Content.Server.Database.Migrations.Postgres +{ + /// + public partial class CustomSpecieName : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "custom_specie_name", + table: "profile", + type: "text", + nullable: false, + defaultValue: ""); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "custom_specie_name", + table: "profile"); + } + } +} \ No newline at end of file diff --git a/Content.Server.Database/Migrations/Postgres/20241018043329_RoleWhitelist.Designer.cs b/Content.Server.Database/Migrations/Postgres/20241018043329_RoleWhitelist.Designer.cs new file mode 100644 index 0000000000..159af4d192 --- /dev/null +++ b/Content.Server.Database/Migrations/Postgres/20241018043329_RoleWhitelist.Designer.cs @@ -0,0 +1,1896 @@ +// +using System; +using System.Net; +using System.Text.Json; +using Content.Server.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using NpgsqlTypes; + +#nullable disable + +namespace Content.Server.Database.Migrations.Postgres +{ + [DbContext(typeof(PostgresServerDbContext))] + [Migration("20241018043329_RoleWhitelist")] + partial class RoleWhitelist + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("AdminRankId") + .HasColumnType("integer") + .HasColumnName("admin_rank_id"); + + b.Property("Title") + .HasColumnType("text") + .HasColumnName("title"); + + b.HasKey("UserId") + .HasName("PK_admin"); + + b.HasIndex("AdminRankId") + .HasDatabaseName("IX_admin_admin_rank_id"); + + b.ToTable("admin", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_flag_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdminId") + .HasColumnType("uuid") + .HasColumnName("admin_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("text") + .HasColumnName("flag"); + + b.Property("Negative") + .HasColumnType("boolean") + .HasColumnName("negative"); + + b.HasKey("Id") + .HasName("PK_admin_flag"); + + b.HasIndex("AdminId") + .HasDatabaseName("IX_admin_flag_admin_id"); + + b.HasIndex("Flag", "AdminId") + .IsUnique(); + + b.ToTable("admin_flag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Id") + .HasColumnType("integer") + .HasColumnName("admin_log_id"); + + b.Property("Date") + .HasColumnType("timestamp with time zone") + .HasColumnName("date"); + + b.Property("Impact") + .HasColumnType("smallint") + .HasColumnName("impact"); + + b.Property("Json") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("json"); + + b.Property("Message") + .IsRequired() + .HasColumnType("text") + .HasColumnName("message"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("type"); + + b.HasKey("RoundId", "Id") + .HasName("PK_admin_log"); + + b.HasIndex("Date"); + + b.HasIndex("Message") + .HasAnnotation("Npgsql:TsVectorConfig", "english"); + + NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("Message"), "GIN"); + + b.HasIndex("Type") + .HasDatabaseName("IX_admin_log_type"); + + b.ToTable("admin_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => + { + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("LogId") + .HasColumnType("integer") + .HasColumnName("log_id"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.HasKey("RoundId", "LogId", "PlayerUserId") + .HasName("PK_admin_log_player"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_log_player_player_user_id"); + + b.ToTable("admin_log_player", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_messages_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("Dismissed") + .HasColumnType("boolean") + .HasColumnName("dismissed"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Seen") + .HasColumnType("boolean") + .HasColumnName("seen"); + + b.HasKey("Id") + .HasName("PK_admin_messages"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_messages_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_messages_round_id"); + + b.ToTable("admin_messages", null, t => + { + t.HasCheckConstraint("NotDismissedAndSeen", "NOT dismissed OR seen"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.AdminNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_notes_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .IsRequired() + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Secret") + .HasColumnType("boolean") + .HasColumnName("secret"); + + b.Property("Severity") + .HasColumnType("integer") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_admin_notes"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_notes_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_notes_round_id"); + + b.ToTable("admin_notes", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_rank_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("PK_admin_rank"); + + b.ToTable("admin_rank", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_rank_flag_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdminRankId") + .HasColumnType("integer") + .HasColumnName("admin_rank_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("text") + .HasColumnName("flag"); + + b.HasKey("Id") + .HasName("PK_admin_rank_flag"); + + b.HasIndex("AdminRankId"); + + b.HasIndex("Flag", "AdminRankId") + .IsUnique(); + + b.ToTable("admin_rank_flag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_watchlists_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .IsRequired() + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.HasKey("Id") + .HasName("PK_admin_watchlists"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_watchlists_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_watchlists_round_id"); + + b.ToTable("admin_watchlists", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("antag_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AntagName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("antag_name"); + + b.Property("ProfileId") + .HasColumnType("integer") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_antag"); + + b.HasIndex("ProfileId", "AntagName") + .IsUnique(); + + b.ToTable("antag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AssignedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("assigned_user_id_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("PK_assigned_user_id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.HasIndex("UserName") + .IsUnique(); + + b.ToTable("assigned_user_id", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.BanTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("ban_template_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AutoDelete") + .HasColumnType("boolean") + .HasColumnName("auto_delete"); + + b.Property("ExemptFlags") + .HasColumnType("integer") + .HasColumnName("exempt_flags"); + + b.Property("Hidden") + .HasColumnType("boolean") + .HasColumnName("hidden"); + + b.Property("Length") + .HasColumnType("interval") + .HasColumnName("length"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text") + .HasColumnName("reason"); + + b.Property("Severity") + .HasColumnType("integer") + .HasColumnName("severity"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text") + .HasColumnName("title"); + + b.HasKey("Id") + .HasName("PK_ban_template"); + + b.ToTable("ban_template", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("connection_log_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasColumnType("inet") + .HasColumnName("address"); + + b.Property("Denied") + .HasColumnType("smallint") + .HasColumnName("denied"); + + b.Property("HWId") + .HasColumnType("bytea") + .HasColumnName("hwid"); + + b.Property("ServerId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("server_id"); + + b.Property("Time") + .HasColumnType("timestamp with time zone") + .HasColumnName("time"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("PK_connection_log"); + + b.HasIndex("ServerId") + .HasDatabaseName("IX_connection_log_server_id"); + + b.HasIndex("UserId"); + + b.ToTable("connection_log", null, t => + { + t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("job_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("JobName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("job_name"); + + b.Property("Priority") + .HasColumnType("integer") + .HasColumnName("priority"); + + b.Property("ProfileId") + .HasColumnType("integer") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_job"); + + b.HasIndex("ProfileId"); + + b.HasIndex("ProfileId", "JobName") + .IsUnique(); + + b.HasIndex(new[] { "ProfileId" }, "IX_job_one_high_priority") + .IsUnique() + .HasFilter("priority = 3"); + + b.ToTable("job", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Loadout", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("loadout_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("LoadoutName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("loadout_name"); + + b.Property("ProfileId") + .HasColumnType("integer") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_loadout"); + + b.HasIndex("ProfileId", "LoadoutName") + .IsUnique(); + + b.ToTable("loadout", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.PlayTime", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("play_time_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("PlayerId") + .HasColumnType("uuid") + .HasColumnName("player_id"); + + b.Property("TimeSpent") + .HasColumnType("interval") + .HasColumnName("time_spent"); + + b.Property("Tracker") + .IsRequired() + .HasColumnType("text") + .HasColumnName("tracker"); + + b.HasKey("Id") + .HasName("PK_play_time"); + + b.HasIndex("PlayerId", "Tracker") + .IsUnique(); + + b.ToTable("play_time", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("player_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FirstSeenTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("first_seen_time"); + + b.Property("LastReadRules") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_read_rules"); + + b.Property("LastSeenAddress") + .IsRequired() + .HasColumnType("inet") + .HasColumnName("last_seen_address"); + + b.Property("LastSeenHWId") + .HasColumnType("bytea") + .HasColumnName("last_seen_hwid"); + + b.Property("LastSeenTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_seen_time"); + + b.Property("LastSeenUserName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("last_seen_user_name"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_player"); + + b.HasAlternateKey("UserId") + .HasName("ak_player_user_id"); + + b.HasIndex("LastSeenUserName"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("player", null, t => + { + t.HasCheckConstraint("LastSeenAddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= last_seen_address"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("preference_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdminOOCColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("admin_ooc_color"); + + b.Property("SelectedCharacterSlot") + .HasColumnType("integer") + .HasColumnName("selected_character_slot"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_preference"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("preference", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("profile_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Age") + .HasColumnType("integer") + .HasColumnName("age"); + + b.Property("Backpack") + .IsRequired() + .HasColumnType("text") + .HasColumnName("backpack"); + + b.Property("CharacterName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("char_name"); + + b.Property("Clothing") + .IsRequired() + .HasColumnType("text") + .HasColumnName("clothing"); + + b.Property("CustomSpecieName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("custom_specie_name"); + + b.Property("EyeColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("eye_color"); + + b.Property("FacialHairColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("facial_hair_color"); + + b.Property("FacialHairName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("facial_hair_name"); + + b.Property("FlavorText") + .IsRequired() + .HasColumnType("text") + .HasColumnName("flavor_text"); + + b.Property("Gender") + .IsRequired() + .HasColumnType("text") + .HasColumnName("gender"); + + b.Property("HairColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("hair_color"); + + b.Property("HairName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("hair_name"); + + b.Property("Height") + .HasColumnType("real") + .HasColumnName("height"); + + b.Property("Markings") + .HasColumnType("jsonb") + .HasColumnName("markings"); + + b.Property("PreferenceId") + .HasColumnType("integer") + .HasColumnName("preference_id"); + + b.Property("PreferenceUnavailable") + .HasColumnType("integer") + .HasColumnName("pref_unavailable"); + + b.Property("Sex") + .IsRequired() + .HasColumnType("text") + .HasColumnName("sex"); + + b.Property("SkinColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("skin_color"); + + b.Property("Slot") + .HasColumnType("integer") + .HasColumnName("slot"); + + b.Property("SpawnPriority") + .HasColumnType("integer") + .HasColumnName("spawn_priority"); + + b.Property("Species") + .IsRequired() + .HasColumnType("text") + .HasColumnName("species"); + + b.Property("Width") + .HasColumnType("real") + .HasColumnName("width"); + + b.HasKey("Id") + .HasName("PK_profile"); + + b.HasIndex("PreferenceId") + .HasDatabaseName("IX_profile_preference_id"); + + b.HasIndex("Slot", "PreferenceId") + .IsUnique(); + + b.ToTable("profile", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b => + { + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("RoleId") + .HasColumnType("text") + .HasColumnName("role_id"); + + b.HasKey("PlayerUserId", "RoleId") + .HasName("PK_role_whitelists"); + + b.ToTable("role_whitelists", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("round_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ServerId") + .HasColumnType("integer") + .HasColumnName("server_id"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("start_date"); + + b.HasKey("Id") + .HasName("PK_round"); + + b.HasIndex("ServerId") + .HasDatabaseName("IX_round_server_id"); + + b.HasIndex("StartDate"); + + b.ToTable("round", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Server", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("PK_server"); + + b.ToTable("server", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_ban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .HasColumnType("inet") + .HasColumnName("address"); + + b.Property("AutoDelete") + .HasColumnType("boolean") + .HasColumnName("auto_delete"); + + b.Property("BanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("uuid") + .HasColumnName("banning_admin"); + + b.Property("ExemptFlags") + .HasColumnType("integer") + .HasColumnName("exempt_flags"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("HWId") + .HasColumnType("bytea") + .HasColumnName("hwid"); + + b.Property("Hidden") + .HasColumnType("boolean") + .HasColumnName("hidden"); + + b.Property("LastEditedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text") + .HasColumnName("reason"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Severity") + .HasColumnType("integer") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_server_ban"); + + b.HasIndex("Address"); + + b.HasIndex("BanningAdmin"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_server_ban_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_server_ban_round_id"); + + b.ToTable("server_ban", null, t => + { + t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + + t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanExemption", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("Flags") + .HasColumnType("integer") + .HasColumnName("flags"); + + b.HasKey("UserId") + .HasName("PK_server_ban_exemption"); + + b.ToTable("server_ban_exemption", null, t => + { + t.HasCheckConstraint("FlagsNotZero", "flags != 0"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanHit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_ban_hit_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BanId") + .HasColumnType("integer") + .HasColumnName("ban_id"); + + b.Property("ConnectionId") + .HasColumnType("integer") + .HasColumnName("connection_id"); + + b.HasKey("Id") + .HasName("PK_server_ban_hit"); + + b.HasIndex("BanId") + .HasDatabaseName("IX_server_ban_hit_ban_id"); + + b.HasIndex("ConnectionId") + .HasDatabaseName("IX_server_ban_hit_connection_id"); + + b.ToTable("server_ban_hit", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_role_ban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .HasColumnType("inet") + .HasColumnName("address"); + + b.Property("BanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("uuid") + .HasColumnName("banning_admin"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("HWId") + .HasColumnType("bytea") + .HasColumnName("hwid"); + + b.Property("Hidden") + .HasColumnType("boolean") + .HasColumnName("hidden"); + + b.Property("LastEditedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text") + .HasColumnName("reason"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("role_id"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Severity") + .HasColumnType("integer") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_server_role_ban"); + + b.HasIndex("Address"); + + b.HasIndex("BanningAdmin"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_server_role_ban_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_server_role_ban_round_id"); + + b.ToTable("server_role_ban", null, t => + { + t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + + t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("role_unban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BanId") + .HasColumnType("integer") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("unban_time"); + + b.Property("UnbanningAdmin") + .HasColumnType("uuid") + .HasColumnName("unbanning_admin"); + + b.HasKey("Id") + .HasName("PK_server_role_unban"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("server_role_unban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("unban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BanId") + .HasColumnType("integer") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("unban_time"); + + b.Property("UnbanningAdmin") + .HasColumnType("uuid") + .HasColumnName("unbanning_admin"); + + b.HasKey("Id") + .HasName("PK_server_unban"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("server_unban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Trait", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("trait_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ProfileId") + .HasColumnType("integer") + .HasColumnName("profile_id"); + + b.Property("TraitName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("trait_name"); + + b.HasKey("Id") + .HasName("PK_trait"); + + b.HasIndex("ProfileId", "TraitName") + .IsUnique(); + + b.ToTable("trait", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.UploadedResourceLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("uploaded_resource_log_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Data") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("data"); + + b.Property("Date") + .HasColumnType("timestamp with time zone") + .HasColumnName("date"); + + b.Property("Path") + .IsRequired() + .HasColumnType("text") + .HasColumnName("path"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_uploaded_resource_log"); + + b.ToTable("uploaded_resource_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Whitelist", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("UserId") + .HasName("PK_whitelist"); + + b.ToTable("whitelist", (string)null); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.Property("PlayersId") + .HasColumnType("integer") + .HasColumnName("players_id"); + + b.Property("RoundsId") + .HasColumnType("integer") + .HasColumnName("rounds_id"); + + b.HasKey("PlayersId", "RoundsId") + .HasName("PK_player_round"); + + b.HasIndex("RoundsId") + .HasDatabaseName("IX_player_round_rounds_id"); + + b.ToTable("player_round", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.HasOne("Content.Server.Database.AdminRank", "AdminRank") + .WithMany("Admins") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_admin_rank_admin_rank_id"); + + b.Navigation("AdminRank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.HasOne("Content.Server.Database.Admin", "Admin") + .WithMany("Flags") + .HasForeignKey("AdminId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_flag_admin_admin_id"); + + b.Navigation("Admin"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany("AdminLogs") + .HasForeignKey("RoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_round_round_id"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => + { + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminLogs") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_player_player_player_user_id"); + + b.HasOne("Content.Server.Database.AdminLog", "Log") + .WithMany("Players") + .HasForeignKey("RoundId", "LogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_player_admin_log_round_id_log_id"); + + b.Navigation("Log"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminMessage", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminMessagesCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminMessagesDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminMessagesLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminMessagesReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_messages_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_messages_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminNote", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminNotesCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminNotesDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminNotesLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminNotesReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_notes_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_notes_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.HasOne("Content.Server.Database.AdminRank", "Rank") + .WithMany("Flags") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_rank_flag_admin_rank_admin_rank_id"); + + b.Navigation("Rank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminWatchlistsCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminWatchlistsDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminWatchlistsLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminWatchlistsReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_watchlists_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_watchlists_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Antags") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_antag_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.HasOne("Content.Server.Database.Server", "Server") + .WithMany("ConnectionLogs") + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.SetNull) + .IsRequired() + .HasConstraintName("FK_connection_log_server_server_id"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Jobs") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_job_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.Loadout", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Loadouts") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_loadout_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.HasOne("Content.Server.Database.Preference", "Preference") + .WithMany("Profiles") + .HasForeignKey("PreferenceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_preference_preference_id"); + + b.Navigation("Preference"); + }); + + modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b => + { + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("JobWhitelists") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_role_whitelists_player_player_user_id"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.HasOne("Content.Server.Database.Server", "Server") + .WithMany("Rounds") + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_round_server_server_id"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminServerBansCreated") + .HasForeignKey("BanningAdmin") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_ban_player_banning_admin"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminServerBansLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_ban_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_server_ban_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanHit", b => + { + b.HasOne("Content.Server.Database.ServerBan", "Ban") + .WithMany("BanHits") + .HasForeignKey("BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_ban_hit_server_ban_ban_id"); + + b.HasOne("Content.Server.Database.ConnectionLog", "Connection") + .WithMany("BanHits") + .HasForeignKey("ConnectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_ban_hit_connection_log_connection_id"); + + b.Navigation("Ban"); + + b.Navigation("Connection"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminServerRoleBansCreated") + .HasForeignKey("BanningAdmin") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_role_ban_player_banning_admin"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminServerRoleBansLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_role_ban_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_server_role_ban_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => + { + b.HasOne("Content.Server.Database.ServerRoleBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.ServerRoleUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_role_unban_server_role_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerUnban", b => + { + b.HasOne("Content.Server.Database.ServerBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.ServerUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_unban_server_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.Trait", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Traits") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_trait_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.HasOne("Content.Server.Database.Player", null) + .WithMany() + .HasForeignKey("PlayersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_player_round_player_players_id"); + + b.HasOne("Content.Server.Database.Round", null) + .WithMany() + .HasForeignKey("RoundsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_player_round_round_rounds_id"); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.Navigation("Players"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Navigation("Admins"); + + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.Navigation("BanHits"); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.Navigation("AdminLogs"); + + b.Navigation("AdminMessagesCreated"); + + b.Navigation("AdminMessagesDeleted"); + + b.Navigation("AdminMessagesLastEdited"); + + b.Navigation("AdminMessagesReceived"); + + b.Navigation("AdminNotesCreated"); + + b.Navigation("AdminNotesDeleted"); + + b.Navigation("AdminNotesLastEdited"); + + b.Navigation("AdminNotesReceived"); + + b.Navigation("AdminServerBansCreated"); + + b.Navigation("AdminServerBansLastEdited"); + + b.Navigation("AdminServerRoleBansCreated"); + + b.Navigation("AdminServerRoleBansLastEdited"); + + b.Navigation("AdminWatchlistsCreated"); + + b.Navigation("AdminWatchlistsDeleted"); + + b.Navigation("AdminWatchlistsLastEdited"); + + b.Navigation("AdminWatchlistsReceived"); + + b.Navigation("JobWhitelists"); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Navigation("Profiles"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Navigation("Antags"); + + b.Navigation("Jobs"); + + b.Navigation("Loadouts"); + + b.Navigation("Traits"); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.Navigation("AdminLogs"); + }); + + modelBuilder.Entity("Content.Server.Database.Server", b => + { + b.Navigation("ConnectionLogs"); + + b.Navigation("Rounds"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.Navigation("BanHits"); + + b.Navigation("Unban"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.Navigation("Unban"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Content.Server.Database/Migrations/Postgres/20241018043329_RoleWhitelist.cs b/Content.Server.Database/Migrations/Postgres/20241018043329_RoleWhitelist.cs new file mode 100644 index 0000000000..13cd23a7fc --- /dev/null +++ b/Content.Server.Database/Migrations/Postgres/20241018043329_RoleWhitelist.cs @@ -0,0 +1,40 @@ +#nullable disable + +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Content.Server.Database.Migrations.Postgres +{ + /// + public partial class RoleWhitelist : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "role_whitelists", + columns: table => new + { + player_user_id = table.Column(type: "uuid", nullable: false), + role_id = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_role_whitelists", x => new { x.player_user_id, x.role_id }); + table.ForeignKey( + name: "FK_role_whitelists_player_player_user_id", + column: x => x.player_user_id, + principalTable: "player", + principalColumn: "user_id", + onDelete: ReferentialAction.Cascade); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "role_whitelists"); + } + } +} diff --git a/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs b/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs index 8fbcbf2835..0282063649 100644 --- a/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs +++ b/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs @@ -512,6 +512,51 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("assigned_user_id", (string)null); }); + modelBuilder.Entity("Content.Server.Database.BanTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("ban_template_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AutoDelete") + .HasColumnType("boolean") + .HasColumnName("auto_delete"); + + b.Property("ExemptFlags") + .HasColumnType("integer") + .HasColumnName("exempt_flags"); + + b.Property("Hidden") + .HasColumnType("boolean") + .HasColumnName("hidden"); + + b.Property("Length") + .HasColumnType("interval") + .HasColumnName("length"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text") + .HasColumnName("reason"); + + b.Property("Severity") + .HasColumnType("integer") + .HasColumnName("severity"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text") + .HasColumnName("title"); + + b.HasKey("Id") + .HasName("PK_ban_template"); + + b.ToTable("ban_template", (string)null); + }); + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => { b.Property("Id") @@ -777,6 +822,11 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("text") .HasColumnName("clothing"); + b.Property("CustomSpecieName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("custom_specie_name"); + b.Property("EyeColor") .IsRequired() .HasColumnType("text") @@ -812,16 +862,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("text") .HasColumnName("hair_name"); - // Parkstation-HeightSlider Start b.Property("Height") .HasColumnType("real") .HasColumnName("height"); - b.Property("Width") - .HasColumnType("real") - .HasColumnName("width"); - // Parkstation-HeightSlider End - b.Property("Markings") .HasColumnType("jsonb") .HasColumnName("markings"); @@ -857,6 +901,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("text") .HasColumnName("species"); + b.Property("Width") + .HasColumnType("real") + .HasColumnName("width"); + b.HasKey("Id") .HasName("PK_profile"); @@ -869,6 +917,22 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("profile", (string)null); }); + modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b => + { + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("RoleId") + .HasColumnType("text") + .HasColumnName("role_id"); + + b.HasKey("PlayerUserId", "RoleId") + .HasName("PK_role_whitelists"); + + b.ToTable("role_whitelists", (string)null); + }); + modelBuilder.Entity("Content.Server.Database.Round", b => { b.Property("Id") @@ -1568,6 +1632,19 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Preference"); }); + modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b => + { + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("JobWhitelists") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_role_whitelists_player_player_user_id"); + + b.Navigation("Player"); + }); + modelBuilder.Entity("Content.Server.Database.Round", b => { b.HasOne("Content.Server.Database.Server", "Server") @@ -1767,6 +1844,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("AdminWatchlistsLastEdited"); b.Navigation("AdminWatchlistsReceived"); + + b.Navigation("JobWhitelists"); }); modelBuilder.Entity("Content.Server.Database.Preference", b => diff --git a/Content.Server.Database/Migrations/Sqlite/20240623005113_BanTemplate.Designer.cs b/Content.Server.Database/Migrations/Sqlite/20240623005113_BanTemplate.Designer.cs new file mode 100644 index 0000000000..0bf74f84a7 --- /dev/null +++ b/Content.Server.Database/Migrations/Sqlite/20240623005113_BanTemplate.Designer.cs @@ -0,0 +1,1774 @@ +// +using System; +using Content.Server.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Content.Server.Database.Migrations.Sqlite +{ + [DbContext(typeof(SqliteServerDbContext))] + [Migration("20240623005113_BanTemplate")] + partial class BanTemplate + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.0"); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("AdminRankId") + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_id"); + + b.Property("Title") + .HasColumnType("TEXT") + .HasColumnName("title"); + + b.HasKey("UserId") + .HasName("PK_admin"); + + b.HasIndex("AdminRankId") + .HasDatabaseName("IX_admin_admin_rank_id"); + + b.ToTable("admin", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_flag_id"); + + b.Property("AdminId") + .HasColumnType("TEXT") + .HasColumnName("admin_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("flag"); + + b.Property("Negative") + .HasColumnType("INTEGER") + .HasColumnName("negative"); + + b.HasKey("Id") + .HasName("PK_admin_flag"); + + b.HasIndex("AdminId") + .HasDatabaseName("IX_admin_flag_admin_id"); + + b.HasIndex("Flag", "AdminId") + .IsUnique(); + + b.ToTable("admin_flag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Id") + .HasColumnType("INTEGER") + .HasColumnName("admin_log_id"); + + b.Property("Date") + .HasColumnType("TEXT") + .HasColumnName("date"); + + b.Property("Impact") + .HasColumnType("INTEGER") + .HasColumnName("impact"); + + b.Property("Json") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("json"); + + b.Property("Message") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("Type") + .HasColumnType("INTEGER") + .HasColumnName("type"); + + b.HasKey("RoundId", "Id") + .HasName("PK_admin_log"); + + b.HasIndex("Date"); + + b.HasIndex("Type") + .HasDatabaseName("IX_admin_log_type"); + + b.ToTable("admin_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => + { + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("LogId") + .HasColumnType("INTEGER") + .HasColumnName("log_id"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.HasKey("RoundId", "LogId", "PlayerUserId") + .HasName("PK_admin_log_player"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_log_player_player_user_id"); + + b.ToTable("admin_log_player", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_messages_id"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("TEXT") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("INTEGER") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("TEXT") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("TEXT") + .HasColumnName("deleted_by_id"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Seen") + .HasColumnType("INTEGER") + .HasColumnName("seen"); + + b.HasKey("Id") + .HasName("PK_admin_messages"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_messages_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_messages_round_id"); + + b.ToTable("admin_messages", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_notes_id"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("TEXT") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("INTEGER") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("TEXT") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("TEXT") + .HasColumnName("deleted_by_id"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Secret") + .HasColumnType("INTEGER") + .HasColumnName("secret"); + + b.Property("Severity") + .HasColumnType("INTEGER") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_admin_notes"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_notes_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_notes_round_id"); + + b.ToTable("admin_notes", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_id"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("PK_admin_rank"); + + b.ToTable("admin_rank", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_flag_id"); + + b.Property("AdminRankId") + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("flag"); + + b.HasKey("Id") + .HasName("PK_admin_rank_flag"); + + b.HasIndex("AdminRankId"); + + b.HasIndex("Flag", "AdminRankId") + .IsUnique(); + + b.ToTable("admin_rank_flag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_watchlists_id"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("TEXT") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("INTEGER") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("TEXT") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("TEXT") + .HasColumnName("deleted_by_id"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.HasKey("Id") + .HasName("PK_admin_watchlists"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_watchlists_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_watchlists_round_id"); + + b.ToTable("admin_watchlists", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("antag_id"); + + b.Property("AntagName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("antag_name"); + + b.Property("ProfileId") + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_antag"); + + b.HasIndex("ProfileId", "AntagName") + .IsUnique(); + + b.ToTable("antag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AssignedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("assigned_user_id_id"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("PK_assigned_user_id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.HasIndex("UserName") + .IsUnique(); + + b.ToTable("assigned_user_id", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.BanTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("ban_template_id"); + + b.Property("AutoDelete") + .HasColumnType("INTEGER") + .HasColumnName("auto_delete"); + + b.Property("ExemptFlags") + .HasColumnType("INTEGER") + .HasColumnName("exempt_flags"); + + b.Property("Hidden") + .HasColumnType("INTEGER") + .HasColumnName("hidden"); + + b.Property("Length") + .HasColumnType("TEXT") + .HasColumnName("length"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("reason"); + + b.Property("Severity") + .HasColumnType("INTEGER") + .HasColumnName("severity"); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("title"); + + b.HasKey("Id") + .HasName("PK_ban_template"); + + b.ToTable("ban_template", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("connection_log_id"); + + b.Property("Address") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("address"); + + b.Property("Denied") + .HasColumnType("INTEGER") + .HasColumnName("denied"); + + b.Property("HWId") + .HasColumnType("BLOB") + .HasColumnName("hwid"); + + b.Property("ServerId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0) + .HasColumnName("server_id"); + + b.Property("Time") + .HasColumnType("TEXT") + .HasColumnName("time"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("PK_connection_log"); + + b.HasIndex("ServerId") + .HasDatabaseName("IX_connection_log_server_id"); + + b.HasIndex("UserId"); + + b.ToTable("connection_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("job_id"); + + b.Property("JobName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("job_name"); + + b.Property("Priority") + .HasColumnType("INTEGER") + .HasColumnName("priority"); + + b.Property("ProfileId") + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_job"); + + b.HasIndex("ProfileId"); + + b.HasIndex("ProfileId", "JobName") + .IsUnique(); + + b.HasIndex(new[] { "ProfileId" }, "IX_job_one_high_priority") + .IsUnique() + .HasFilter("priority = 3"); + + b.ToTable("job", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Loadout", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("loadout_id"); + + b.Property("LoadoutName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("loadout_name"); + + b.Property("ProfileId") + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_loadout"); + + b.HasIndex("ProfileId", "LoadoutName") + .IsUnique(); + + b.ToTable("loadout", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.PlayTime", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("play_time_id"); + + b.Property("PlayerId") + .HasColumnType("TEXT") + .HasColumnName("player_id"); + + b.Property("TimeSpent") + .HasColumnType("TEXT") + .HasColumnName("time_spent"); + + b.Property("Tracker") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("tracker"); + + b.HasKey("Id") + .HasName("PK_play_time"); + + b.HasIndex("PlayerId", "Tracker") + .IsUnique(); + + b.ToTable("play_time", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("player_id"); + + b.Property("FirstSeenTime") + .HasColumnType("TEXT") + .HasColumnName("first_seen_time"); + + b.Property("LastReadRules") + .HasColumnType("TEXT") + .HasColumnName("last_read_rules"); + + b.Property("LastSeenAddress") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("last_seen_address"); + + b.Property("LastSeenHWId") + .HasColumnType("BLOB") + .HasColumnName("last_seen_hwid"); + + b.Property("LastSeenTime") + .HasColumnType("TEXT") + .HasColumnName("last_seen_time"); + + b.Property("LastSeenUserName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("last_seen_user_name"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_player"); + + b.HasAlternateKey("UserId") + .HasName("ak_player_user_id"); + + b.HasIndex("LastSeenUserName"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("player", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("preference_id"); + + b.Property("AdminOOCColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("admin_ooc_color"); + + b.Property("SelectedCharacterSlot") + .HasColumnType("INTEGER") + .HasColumnName("selected_character_slot"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_preference"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("preference", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.Property("Age") + .HasColumnType("INTEGER") + .HasColumnName("age"); + + b.Property("Backpack") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("backpack"); + + b.Property("CharacterName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("char_name"); + + b.Property("Clothing") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("clothing"); + + b.Property("EyeColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("eye_color"); + + b.Property("FacialHairColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("facial_hair_color"); + + b.Property("FacialHairName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("facial_hair_name"); + + b.Property("FlavorText") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("flavor_text"); + + b.Property("Gender") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("gender"); + + b.Property("HairColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("hair_color"); + + b.Property("HairName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("hair_name"); + + b.Property("Markings") + .HasColumnType("jsonb") + .HasColumnName("markings"); + + b.Property("PreferenceId") + .HasColumnType("INTEGER") + .HasColumnName("preference_id"); + + b.Property("PreferenceUnavailable") + .HasColumnType("INTEGER") + .HasColumnName("pref_unavailable"); + + b.Property("Sex") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("sex"); + + b.Property("SkinColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("skin_color"); + + b.Property("Slot") + .HasColumnType("INTEGER") + .HasColumnName("slot"); + + b.Property("SpawnPriority") + .HasColumnType("INTEGER") + .HasColumnName("spawn_priority"); + + b.Property("Species") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("species"); + + b.HasKey("Id") + .HasName("PK_profile"); + + b.HasIndex("PreferenceId") + .HasDatabaseName("IX_profile_preference_id"); + + b.HasIndex("Slot", "PreferenceId") + .IsUnique(); + + b.ToTable("profile", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("ServerId") + .HasColumnType("INTEGER") + .HasColumnName("server_id"); + + b.Property("StartDate") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)) + .HasColumnName("start_date"); + + b.HasKey("Id") + .HasName("PK_round"); + + b.HasIndex("ServerId") + .HasDatabaseName("IX_round_server_id"); + + b.HasIndex("StartDate"); + + b.ToTable("round", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Server", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("server_id"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("PK_server"); + + b.ToTable("server", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("server_ban_id"); + + b.Property("Address") + .HasColumnType("TEXT") + .HasColumnName("address"); + + b.Property("AutoDelete") + .HasColumnType("INTEGER") + .HasColumnName("auto_delete"); + + b.Property("BanTime") + .HasColumnType("TEXT") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("banning_admin"); + + b.Property("ExemptFlags") + .HasColumnType("INTEGER") + .HasColumnName("exempt_flags"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("HWId") + .HasColumnType("BLOB") + .HasColumnName("hwid"); + + b.Property("Hidden") + .HasColumnType("INTEGER") + .HasColumnName("hidden"); + + b.Property("LastEditedAt") + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("reason"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Severity") + .HasColumnType("INTEGER") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_server_ban"); + + b.HasIndex("Address"); + + b.HasIndex("BanningAdmin"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_server_ban_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_server_ban_round_id"); + + b.ToTable("server_ban", null, t => + { + t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanExemption", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("Flags") + .HasColumnType("INTEGER") + .HasColumnName("flags"); + + b.HasKey("UserId") + .HasName("PK_server_ban_exemption"); + + b.ToTable("server_ban_exemption", null, t => + { + t.HasCheckConstraint("FlagsNotZero", "flags != 0"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanHit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("server_ban_hit_id"); + + b.Property("BanId") + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.Property("ConnectionId") + .HasColumnType("INTEGER") + .HasColumnName("connection_id"); + + b.HasKey("Id") + .HasName("PK_server_ban_hit"); + + b.HasIndex("BanId") + .HasDatabaseName("IX_server_ban_hit_ban_id"); + + b.HasIndex("ConnectionId") + .HasDatabaseName("IX_server_ban_hit_connection_id"); + + b.ToTable("server_ban_hit", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("server_role_ban_id"); + + b.Property("Address") + .HasColumnType("TEXT") + .HasColumnName("address"); + + b.Property("BanTime") + .HasColumnType("TEXT") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("banning_admin"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("HWId") + .HasColumnType("BLOB") + .HasColumnName("hwid"); + + b.Property("Hidden") + .HasColumnType("INTEGER") + .HasColumnName("hidden"); + + b.Property("LastEditedAt") + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("reason"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("role_id"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Severity") + .HasColumnType("INTEGER") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_server_role_ban"); + + b.HasIndex("Address"); + + b.HasIndex("BanningAdmin"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_server_role_ban_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_server_role_ban_round_id"); + + b.ToTable("server_role_ban", null, t => + { + t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("role_unban_id"); + + b.Property("BanId") + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("TEXT") + .HasColumnName("unban_time"); + + b.Property("UnbanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("unbanning_admin"); + + b.HasKey("Id") + .HasName("PK_server_role_unban"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("server_role_unban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("unban_id"); + + b.Property("BanId") + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("TEXT") + .HasColumnName("unban_time"); + + b.Property("UnbanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("unbanning_admin"); + + b.HasKey("Id") + .HasName("PK_server_unban"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("server_unban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Trait", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("trait_id"); + + b.Property("ProfileId") + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.Property("TraitName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("trait_name"); + + b.HasKey("Id") + .HasName("PK_trait"); + + b.HasIndex("ProfileId", "TraitName") + .IsUnique(); + + b.ToTable("trait", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.UploadedResourceLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("uploaded_resource_log_id"); + + b.Property("Data") + .IsRequired() + .HasColumnType("BLOB") + .HasColumnName("data"); + + b.Property("Date") + .HasColumnType("TEXT") + .HasColumnName("date"); + + b.Property("Path") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("path"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_uploaded_resource_log"); + + b.ToTable("uploaded_resource_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Whitelist", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("UserId") + .HasName("PK_whitelist"); + + b.ToTable("whitelist", (string)null); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.Property("PlayersId") + .HasColumnType("INTEGER") + .HasColumnName("players_id"); + + b.Property("RoundsId") + .HasColumnType("INTEGER") + .HasColumnName("rounds_id"); + + b.HasKey("PlayersId", "RoundsId") + .HasName("PK_player_round"); + + b.HasIndex("RoundsId") + .HasDatabaseName("IX_player_round_rounds_id"); + + b.ToTable("player_round", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.HasOne("Content.Server.Database.AdminRank", "AdminRank") + .WithMany("Admins") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_admin_rank_admin_rank_id"); + + b.Navigation("AdminRank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.HasOne("Content.Server.Database.Admin", "Admin") + .WithMany("Flags") + .HasForeignKey("AdminId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_flag_admin_admin_id"); + + b.Navigation("Admin"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany("AdminLogs") + .HasForeignKey("RoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_round_round_id"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => + { + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminLogs") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_player_player_player_user_id"); + + b.HasOne("Content.Server.Database.AdminLog", "Log") + .WithMany("Players") + .HasForeignKey("RoundId", "LogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_player_admin_log_round_id_log_id"); + + b.Navigation("Log"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminMessage", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminMessagesCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminMessagesDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminMessagesLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminMessagesReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_messages_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_messages_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminNote", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminNotesCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminNotesDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminNotesLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminNotesReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_notes_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_notes_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.HasOne("Content.Server.Database.AdminRank", "Rank") + .WithMany("Flags") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_rank_flag_admin_rank_admin_rank_id"); + + b.Navigation("Rank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminWatchlistsCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminWatchlistsDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminWatchlistsLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminWatchlistsReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_watchlists_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_watchlists_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Antags") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_antag_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.HasOne("Content.Server.Database.Server", "Server") + .WithMany("ConnectionLogs") + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.SetNull) + .IsRequired() + .HasConstraintName("FK_connection_log_server_server_id"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Jobs") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_job_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.Loadout", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Loadouts") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_loadout_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.HasOne("Content.Server.Database.Preference", "Preference") + .WithMany("Profiles") + .HasForeignKey("PreferenceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_preference_preference_id"); + + b.Navigation("Preference"); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.HasOne("Content.Server.Database.Server", "Server") + .WithMany("Rounds") + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_round_server_server_id"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminServerBansCreated") + .HasForeignKey("BanningAdmin") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_ban_player_banning_admin"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminServerBansLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_ban_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_server_ban_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanHit", b => + { + b.HasOne("Content.Server.Database.ServerBan", "Ban") + .WithMany("BanHits") + .HasForeignKey("BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_ban_hit_server_ban_ban_id"); + + b.HasOne("Content.Server.Database.ConnectionLog", "Connection") + .WithMany("BanHits") + .HasForeignKey("ConnectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_ban_hit_connection_log_connection_id"); + + b.Navigation("Ban"); + + b.Navigation("Connection"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminServerRoleBansCreated") + .HasForeignKey("BanningAdmin") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_role_ban_player_banning_admin"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminServerRoleBansLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_role_ban_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_server_role_ban_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => + { + b.HasOne("Content.Server.Database.ServerRoleBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.ServerRoleUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_role_unban_server_role_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerUnban", b => + { + b.HasOne("Content.Server.Database.ServerBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.ServerUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_unban_server_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.Trait", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Traits") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_trait_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.HasOne("Content.Server.Database.Player", null) + .WithMany() + .HasForeignKey("PlayersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_player_round_player_players_id"); + + b.HasOne("Content.Server.Database.Round", null) + .WithMany() + .HasForeignKey("RoundsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_player_round_round_rounds_id"); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.Navigation("Players"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Navigation("Admins"); + + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.Navigation("BanHits"); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.Navigation("AdminLogs"); + + b.Navigation("AdminMessagesCreated"); + + b.Navigation("AdminMessagesDeleted"); + + b.Navigation("AdminMessagesLastEdited"); + + b.Navigation("AdminMessagesReceived"); + + b.Navigation("AdminNotesCreated"); + + b.Navigation("AdminNotesDeleted"); + + b.Navigation("AdminNotesLastEdited"); + + b.Navigation("AdminNotesReceived"); + + b.Navigation("AdminServerBansCreated"); + + b.Navigation("AdminServerBansLastEdited"); + + b.Navigation("AdminServerRoleBansCreated"); + + b.Navigation("AdminServerRoleBansLastEdited"); + + b.Navigation("AdminWatchlistsCreated"); + + b.Navigation("AdminWatchlistsDeleted"); + + b.Navigation("AdminWatchlistsLastEdited"); + + b.Navigation("AdminWatchlistsReceived"); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Navigation("Profiles"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Navigation("Antags"); + + b.Navigation("Jobs"); + + b.Navigation("Loadouts"); + + b.Navigation("Traits"); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.Navigation("AdminLogs"); + }); + + modelBuilder.Entity("Content.Server.Database.Server", b => + { + b.Navigation("ConnectionLogs"); + + b.Navigation("Rounds"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.Navigation("BanHits"); + + b.Navigation("Unban"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.Navigation("Unban"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Content.Server.Database/Migrations/Sqlite/20240623005113_BanTemplate.cs b/Content.Server.Database/Migrations/Sqlite/20240623005113_BanTemplate.cs new file mode 100644 index 0000000000..ffe5a47550 --- /dev/null +++ b/Content.Server.Database/Migrations/Sqlite/20240623005113_BanTemplate.cs @@ -0,0 +1,41 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Content.Server.Database.Migrations.Sqlite +{ + /// + public partial class BanTemplate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "ban_template", + columns: table => new + { + ban_template_id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + title = table.Column(type: "TEXT", nullable: false), + length = table.Column(type: "TEXT", nullable: false), + reason = table.Column(type: "TEXT", nullable: false), + exempt_flags = table.Column(type: "INTEGER", nullable: false), + severity = table.Column(type: "INTEGER", nullable: false), + auto_delete = table.Column(type: "INTEGER", nullable: false), + hidden = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ban_template", x => x.ban_template_id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ban_template"); + } + } +} diff --git a/Content.Server.Database/Migrations/Sqlite/20240926173707_1984 Backpack Prefs.Designer.cs b/Content.Server.Database/Migrations/Sqlite/20240926173707_1984 Backpack Prefs.Designer.cs new file mode 100644 index 0000000000..490cc07d36 --- /dev/null +++ b/Content.Server.Database/Migrations/Sqlite/20240926173707_1984 Backpack Prefs.Designer.cs @@ -0,0 +1,1744 @@ +// +using System; +using Content.Server.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Content.Server.Database.Migrations.Sqlite +{ + [DbContext(typeof(SqliteServerDbContext))] + [Migration("20240926173707_1984 Backpack Prefs")] + partial class _1984BackpackPrefs + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.0"); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("AdminRankId") + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_id"); + + b.Property("Title") + .HasColumnType("TEXT") + .HasColumnName("title"); + + b.HasKey("UserId") + .HasName("PK_admin"); + + b.HasIndex("AdminRankId") + .HasDatabaseName("IX_admin_admin_rank_id"); + + b.ToTable("admin", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_flag_id"); + + b.Property("AdminId") + .HasColumnType("TEXT") + .HasColumnName("admin_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("flag"); + + b.Property("Negative") + .HasColumnType("INTEGER") + .HasColumnName("negative"); + + b.HasKey("Id") + .HasName("PK_admin_flag"); + + b.HasIndex("AdminId") + .HasDatabaseName("IX_admin_flag_admin_id"); + + b.HasIndex("Flag", "AdminId") + .IsUnique(); + + b.ToTable("admin_flag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Id") + .HasColumnType("INTEGER") + .HasColumnName("admin_log_id"); + + b.Property("Date") + .HasColumnType("TEXT") + .HasColumnName("date"); + + b.Property("Impact") + .HasColumnType("INTEGER") + .HasColumnName("impact"); + + b.Property("Json") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("json"); + + b.Property("Message") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("Type") + .HasColumnType("INTEGER") + .HasColumnName("type"); + + b.HasKey("RoundId", "Id") + .HasName("PK_admin_log"); + + b.HasIndex("Date"); + + b.HasIndex("Type") + .HasDatabaseName("IX_admin_log_type"); + + b.ToTable("admin_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => + { + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("LogId") + .HasColumnType("INTEGER") + .HasColumnName("log_id"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.HasKey("RoundId", "LogId", "PlayerUserId") + .HasName("PK_admin_log_player"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_log_player_player_user_id"); + + b.ToTable("admin_log_player", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_messages_id"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("TEXT") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("INTEGER") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("TEXT") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("TEXT") + .HasColumnName("deleted_by_id"); + + b.Property("Dismissed") + .HasColumnType("INTEGER") + .HasColumnName("dismissed"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Seen") + .HasColumnType("INTEGER") + .HasColumnName("seen"); + + b.HasKey("Id") + .HasName("PK_admin_messages"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_messages_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_messages_round_id"); + + b.ToTable("admin_messages", null, t => + { + t.HasCheckConstraint("NotDismissedAndSeen", "NOT dismissed OR seen"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.AdminNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_notes_id"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("TEXT") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("INTEGER") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("TEXT") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("TEXT") + .HasColumnName("deleted_by_id"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Secret") + .HasColumnType("INTEGER") + .HasColumnName("secret"); + + b.Property("Severity") + .HasColumnType("INTEGER") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_admin_notes"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_notes_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_notes_round_id"); + + b.ToTable("admin_notes", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_id"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("PK_admin_rank"); + + b.ToTable("admin_rank", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_flag_id"); + + b.Property("AdminRankId") + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("flag"); + + b.HasKey("Id") + .HasName("PK_admin_rank_flag"); + + b.HasIndex("AdminRankId"); + + b.HasIndex("Flag", "AdminRankId") + .IsUnique(); + + b.ToTable("admin_rank_flag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_watchlists_id"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("TEXT") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("INTEGER") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("TEXT") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("TEXT") + .HasColumnName("deleted_by_id"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.HasKey("Id") + .HasName("PK_admin_watchlists"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_watchlists_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_watchlists_round_id"); + + b.ToTable("admin_watchlists", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("antag_id"); + + b.Property("AntagName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("antag_name"); + + b.Property("ProfileId") + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_antag"); + + b.HasIndex("ProfileId", "AntagName") + .IsUnique(); + + b.ToTable("antag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AssignedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("assigned_user_id_id"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("PK_assigned_user_id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.HasIndex("UserName") + .IsUnique(); + + b.ToTable("assigned_user_id", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("connection_log_id"); + + b.Property("Address") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("address"); + + b.Property("Denied") + .HasColumnType("INTEGER") + .HasColumnName("denied"); + + b.Property("HWId") + .HasColumnType("BLOB") + .HasColumnName("hwid"); + + b.Property("ServerId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0) + .HasColumnName("server_id"); + + b.Property("Time") + .HasColumnType("TEXT") + .HasColumnName("time"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("PK_connection_log"); + + b.HasIndex("ServerId") + .HasDatabaseName("IX_connection_log_server_id"); + + b.HasIndex("UserId"); + + b.ToTable("connection_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("job_id"); + + b.Property("JobName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("job_name"); + + b.Property("Priority") + .HasColumnType("INTEGER") + .HasColumnName("priority"); + + b.Property("ProfileId") + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_job"); + + b.HasIndex("ProfileId"); + + b.HasIndex("ProfileId", "JobName") + .IsUnique(); + + b.HasIndex(new[] { "ProfileId" }, "IX_job_one_high_priority") + .IsUnique() + .HasFilter("priority = 3"); + + b.ToTable("job", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Loadout", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("loadout_id"); + + b.Property("LoadoutName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("loadout_name"); + + b.Property("ProfileId") + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_loadout"); + + b.HasIndex("ProfileId", "LoadoutName") + .IsUnique(); + + b.ToTable("loadout", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.PlayTime", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("play_time_id"); + + b.Property("PlayerId") + .HasColumnType("TEXT") + .HasColumnName("player_id"); + + b.Property("TimeSpent") + .HasColumnType("TEXT") + .HasColumnName("time_spent"); + + b.Property("Tracker") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("tracker"); + + b.HasKey("Id") + .HasName("PK_play_time"); + + b.HasIndex("PlayerId", "Tracker") + .IsUnique(); + + b.ToTable("play_time", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("player_id"); + + b.Property("FirstSeenTime") + .HasColumnType("TEXT") + .HasColumnName("first_seen_time"); + + b.Property("LastReadRules") + .HasColumnType("TEXT") + .HasColumnName("last_read_rules"); + + b.Property("LastSeenAddress") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("last_seen_address"); + + b.Property("LastSeenHWId") + .HasColumnType("BLOB") + .HasColumnName("last_seen_hwid"); + + b.Property("LastSeenTime") + .HasColumnType("TEXT") + .HasColumnName("last_seen_time"); + + b.Property("LastSeenUserName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("last_seen_user_name"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_player"); + + b.HasAlternateKey("UserId") + .HasName("ak_player_user_id"); + + b.HasIndex("LastSeenUserName"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("player", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("preference_id"); + + b.Property("AdminOOCColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("admin_ooc_color"); + + b.Property("SelectedCharacterSlot") + .HasColumnType("INTEGER") + .HasColumnName("selected_character_slot"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_preference"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("preference", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.Property("Age") + .HasColumnType("INTEGER") + .HasColumnName("age"); + + b.Property("Backpack") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("backpack"); + + b.Property("CharacterName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("char_name"); + + b.Property("Clothing") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("clothing"); + + b.Property("EyeColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("eye_color"); + + b.Property("FacialHairColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("facial_hair_color"); + + b.Property("FacialHairName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("facial_hair_name"); + + b.Property("FlavorText") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("flavor_text"); + + b.Property("Gender") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("gender"); + + b.Property("HairColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("hair_color"); + + b.Property("HairName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("hair_name"); + + b.Property("Height") + .HasColumnType("REAL") + .HasColumnName("height"); + + b.Property("Markings") + .HasColumnType("jsonb") + .HasColumnName("markings"); + + b.Property("PreferenceId") + .HasColumnType("INTEGER") + .HasColumnName("preference_id"); + + b.Property("PreferenceUnavailable") + .HasColumnType("INTEGER") + .HasColumnName("pref_unavailable"); + + b.Property("Sex") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("sex"); + + b.Property("SkinColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("skin_color"); + + b.Property("Slot") + .HasColumnType("INTEGER") + .HasColumnName("slot"); + + b.Property("SpawnPriority") + .HasColumnType("INTEGER") + .HasColumnName("spawn_priority"); + + b.Property("Species") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("species"); + + b.Property("Width") + .HasColumnType("REAL") + .HasColumnName("width"); + + b.HasKey("Id") + .HasName("PK_profile"); + + b.HasIndex("PreferenceId") + .HasDatabaseName("IX_profile_preference_id"); + + b.HasIndex("Slot", "PreferenceId") + .IsUnique(); + + b.ToTable("profile", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("ServerId") + .HasColumnType("INTEGER") + .HasColumnName("server_id"); + + b.Property("StartDate") + .HasColumnType("TEXT") + .HasColumnName("start_date"); + + b.HasKey("Id") + .HasName("PK_round"); + + b.HasIndex("ServerId") + .HasDatabaseName("IX_round_server_id"); + + b.HasIndex("StartDate"); + + b.ToTable("round", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Server", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("server_id"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("PK_server"); + + b.ToTable("server", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("server_ban_id"); + + b.Property("Address") + .HasColumnType("TEXT") + .HasColumnName("address"); + + b.Property("AutoDelete") + .HasColumnType("INTEGER") + .HasColumnName("auto_delete"); + + b.Property("BanTime") + .HasColumnType("TEXT") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("banning_admin"); + + b.Property("ExemptFlags") + .HasColumnType("INTEGER") + .HasColumnName("exempt_flags"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("HWId") + .HasColumnType("BLOB") + .HasColumnName("hwid"); + + b.Property("Hidden") + .HasColumnType("INTEGER") + .HasColumnName("hidden"); + + b.Property("LastEditedAt") + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("reason"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Severity") + .HasColumnType("INTEGER") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_server_ban"); + + b.HasIndex("Address"); + + b.HasIndex("BanningAdmin"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_server_ban_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_server_ban_round_id"); + + b.ToTable("server_ban", null, t => + { + t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanExemption", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("Flags") + .HasColumnType("INTEGER") + .HasColumnName("flags"); + + b.HasKey("UserId") + .HasName("PK_server_ban_exemption"); + + b.ToTable("server_ban_exemption", null, t => + { + t.HasCheckConstraint("FlagsNotZero", "flags != 0"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanHit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("server_ban_hit_id"); + + b.Property("BanId") + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.Property("ConnectionId") + .HasColumnType("INTEGER") + .HasColumnName("connection_id"); + + b.HasKey("Id") + .HasName("PK_server_ban_hit"); + + b.HasIndex("BanId") + .HasDatabaseName("IX_server_ban_hit_ban_id"); + + b.HasIndex("ConnectionId") + .HasDatabaseName("IX_server_ban_hit_connection_id"); + + b.ToTable("server_ban_hit", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("server_role_ban_id"); + + b.Property("Address") + .HasColumnType("TEXT") + .HasColumnName("address"); + + b.Property("BanTime") + .HasColumnType("TEXT") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("banning_admin"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("HWId") + .HasColumnType("BLOB") + .HasColumnName("hwid"); + + b.Property("Hidden") + .HasColumnType("INTEGER") + .HasColumnName("hidden"); + + b.Property("LastEditedAt") + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("reason"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("role_id"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Severity") + .HasColumnType("INTEGER") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_server_role_ban"); + + b.HasIndex("Address"); + + b.HasIndex("BanningAdmin"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_server_role_ban_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_server_role_ban_round_id"); + + b.ToTable("server_role_ban", null, t => + { + t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("role_unban_id"); + + b.Property("BanId") + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("TEXT") + .HasColumnName("unban_time"); + + b.Property("UnbanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("unbanning_admin"); + + b.HasKey("Id") + .HasName("PK_server_role_unban"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("server_role_unban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("unban_id"); + + b.Property("BanId") + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("TEXT") + .HasColumnName("unban_time"); + + b.Property("UnbanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("unbanning_admin"); + + b.HasKey("Id") + .HasName("PK_server_unban"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("server_unban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Trait", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("trait_id"); + + b.Property("ProfileId") + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.Property("TraitName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("trait_name"); + + b.HasKey("Id") + .HasName("PK_trait"); + + b.HasIndex("ProfileId", "TraitName") + .IsUnique(); + + b.ToTable("trait", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.UploadedResourceLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("uploaded_resource_log_id"); + + b.Property("Data") + .IsRequired() + .HasColumnType("BLOB") + .HasColumnName("data"); + + b.Property("Date") + .HasColumnType("TEXT") + .HasColumnName("date"); + + b.Property("Path") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("path"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_uploaded_resource_log"); + + b.ToTable("uploaded_resource_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Whitelist", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("UserId") + .HasName("PK_whitelist"); + + b.ToTable("whitelist", (string)null); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.Property("PlayersId") + .HasColumnType("INTEGER") + .HasColumnName("players_id"); + + b.Property("RoundsId") + .HasColumnType("INTEGER") + .HasColumnName("rounds_id"); + + b.HasKey("PlayersId", "RoundsId") + .HasName("PK_player_round"); + + b.HasIndex("RoundsId") + .HasDatabaseName("IX_player_round_rounds_id"); + + b.ToTable("player_round", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.HasOne("Content.Server.Database.AdminRank", "AdminRank") + .WithMany("Admins") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_admin_rank_admin_rank_id"); + + b.Navigation("AdminRank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.HasOne("Content.Server.Database.Admin", "Admin") + .WithMany("Flags") + .HasForeignKey("AdminId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_flag_admin_admin_id"); + + b.Navigation("Admin"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany("AdminLogs") + .HasForeignKey("RoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_round_round_id"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => + { + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminLogs") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_player_player_player_user_id"); + + b.HasOne("Content.Server.Database.AdminLog", "Log") + .WithMany("Players") + .HasForeignKey("RoundId", "LogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_player_admin_log_round_id_log_id"); + + b.Navigation("Log"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminMessage", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminMessagesCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminMessagesDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminMessagesLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminMessagesReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_messages_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_messages_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminNote", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminNotesCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminNotesDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminNotesLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminNotesReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_notes_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_notes_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.HasOne("Content.Server.Database.AdminRank", "Rank") + .WithMany("Flags") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_rank_flag_admin_rank_admin_rank_id"); + + b.Navigation("Rank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminWatchlistsCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminWatchlistsDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminWatchlistsLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminWatchlistsReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_watchlists_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_watchlists_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Antags") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_antag_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.HasOne("Content.Server.Database.Server", "Server") + .WithMany("ConnectionLogs") + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.SetNull) + .IsRequired() + .HasConstraintName("FK_connection_log_server_server_id"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Jobs") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_job_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.Loadout", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Loadouts") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_loadout_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.HasOne("Content.Server.Database.Preference", "Preference") + .WithMany("Profiles") + .HasForeignKey("PreferenceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_preference_preference_id"); + + b.Navigation("Preference"); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.HasOne("Content.Server.Database.Server", "Server") + .WithMany("Rounds") + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_round_server_server_id"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminServerBansCreated") + .HasForeignKey("BanningAdmin") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_ban_player_banning_admin"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminServerBansLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_ban_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_server_ban_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanHit", b => + { + b.HasOne("Content.Server.Database.ServerBan", "Ban") + .WithMany("BanHits") + .HasForeignKey("BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_ban_hit_server_ban_ban_id"); + + b.HasOne("Content.Server.Database.ConnectionLog", "Connection") + .WithMany("BanHits") + .HasForeignKey("ConnectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_ban_hit_connection_log_connection_id"); + + b.Navigation("Ban"); + + b.Navigation("Connection"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminServerRoleBansCreated") + .HasForeignKey("BanningAdmin") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_role_ban_player_banning_admin"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminServerRoleBansLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_role_ban_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_server_role_ban_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => + { + b.HasOne("Content.Server.Database.ServerRoleBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.ServerRoleUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_role_unban_server_role_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerUnban", b => + { + b.HasOne("Content.Server.Database.ServerBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.ServerUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_unban_server_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.Trait", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Traits") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_trait_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.HasOne("Content.Server.Database.Player", null) + .WithMany() + .HasForeignKey("PlayersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_player_round_player_players_id"); + + b.HasOne("Content.Server.Database.Round", null) + .WithMany() + .HasForeignKey("RoundsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_player_round_round_rounds_id"); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.Navigation("Players"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Navigation("Admins"); + + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.Navigation("BanHits"); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.Navigation("AdminLogs"); + + b.Navigation("AdminMessagesCreated"); + + b.Navigation("AdminMessagesDeleted"); + + b.Navigation("AdminMessagesLastEdited"); + + b.Navigation("AdminMessagesReceived"); + + b.Navigation("AdminNotesCreated"); + + b.Navigation("AdminNotesDeleted"); + + b.Navigation("AdminNotesLastEdited"); + + b.Navigation("AdminNotesReceived"); + + b.Navigation("AdminServerBansCreated"); + + b.Navigation("AdminServerBansLastEdited"); + + b.Navigation("AdminServerRoleBansCreated"); + + b.Navigation("AdminServerRoleBansLastEdited"); + + b.Navigation("AdminWatchlistsCreated"); + + b.Navigation("AdminWatchlistsDeleted"); + + b.Navigation("AdminWatchlistsLastEdited"); + + b.Navigation("AdminWatchlistsReceived"); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Navigation("Profiles"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Navigation("Antags"); + + b.Navigation("Jobs"); + + b.Navigation("Loadouts"); + + b.Navigation("Traits"); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.Navigation("AdminLogs"); + }); + + modelBuilder.Entity("Content.Server.Database.Server", b => + { + b.Navigation("ConnectionLogs"); + + b.Navigation("Rounds"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.Navigation("BanHits"); + + b.Navigation("Unban"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.Navigation("Unban"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Content.Server.Database/Migrations/Sqlite/20240926173707_1984 Backpack Prefs.cs b/Content.Server.Database/Migrations/Sqlite/20240926173707_1984 Backpack Prefs.cs new file mode 100644 index 0000000000..19bc59a6e6 --- /dev/null +++ b/Content.Server.Database/Migrations/Sqlite/20240926173707_1984 Backpack Prefs.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Content.Server.Database.Migrations.Sqlite +{ + /// + public partial class _1984BackpackPrefs : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/Content.Server.Database/Migrations/Sqlite/20241001054735_CustomSpecieName.Designer.cs b/Content.Server.Database/Migrations/Sqlite/20241001054735_CustomSpecieName.Designer.cs new file mode 100644 index 0000000000..c724031d56 --- /dev/null +++ b/Content.Server.Database/Migrations/Sqlite/20241001054735_CustomSpecieName.Designer.cs @@ -0,0 +1,1749 @@ +// +using System; +using Content.Server.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Content.Server.Database.Migrations.Sqlite +{ + [DbContext(typeof(SqliteServerDbContext))] + [Migration("20241001054735_CustomSpecieName")] + partial class CustomSpecieName + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.0"); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("AdminRankId") + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_id"); + + b.Property("Title") + .HasColumnType("TEXT") + .HasColumnName("title"); + + b.HasKey("UserId") + .HasName("PK_admin"); + + b.HasIndex("AdminRankId") + .HasDatabaseName("IX_admin_admin_rank_id"); + + b.ToTable("admin", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_flag_id"); + + b.Property("AdminId") + .HasColumnType("TEXT") + .HasColumnName("admin_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("flag"); + + b.Property("Negative") + .HasColumnType("INTEGER") + .HasColumnName("negative"); + + b.HasKey("Id") + .HasName("PK_admin_flag"); + + b.HasIndex("AdminId") + .HasDatabaseName("IX_admin_flag_admin_id"); + + b.HasIndex("Flag", "AdminId") + .IsUnique(); + + b.ToTable("admin_flag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Id") + .HasColumnType("INTEGER") + .HasColumnName("admin_log_id"); + + b.Property("Date") + .HasColumnType("TEXT") + .HasColumnName("date"); + + b.Property("Impact") + .HasColumnType("INTEGER") + .HasColumnName("impact"); + + b.Property("Json") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("json"); + + b.Property("Message") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("Type") + .HasColumnType("INTEGER") + .HasColumnName("type"); + + b.HasKey("RoundId", "Id") + .HasName("PK_admin_log"); + + b.HasIndex("Date"); + + b.HasIndex("Type") + .HasDatabaseName("IX_admin_log_type"); + + b.ToTable("admin_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => + { + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("LogId") + .HasColumnType("INTEGER") + .HasColumnName("log_id"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.HasKey("RoundId", "LogId", "PlayerUserId") + .HasName("PK_admin_log_player"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_log_player_player_user_id"); + + b.ToTable("admin_log_player", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_messages_id"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("TEXT") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("INTEGER") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("TEXT") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("TEXT") + .HasColumnName("deleted_by_id"); + + b.Property("Dismissed") + .HasColumnType("INTEGER") + .HasColumnName("dismissed"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Seen") + .HasColumnType("INTEGER") + .HasColumnName("seen"); + + b.HasKey("Id") + .HasName("PK_admin_messages"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_messages_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_messages_round_id"); + + b.ToTable("admin_messages", null, t => + { + t.HasCheckConstraint("NotDismissedAndSeen", "NOT dismissed OR seen"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.AdminNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_notes_id"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("TEXT") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("INTEGER") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("TEXT") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("TEXT") + .HasColumnName("deleted_by_id"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Secret") + .HasColumnType("INTEGER") + .HasColumnName("secret"); + + b.Property("Severity") + .HasColumnType("INTEGER") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_admin_notes"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_notes_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_notes_round_id"); + + b.ToTable("admin_notes", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_id"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("PK_admin_rank"); + + b.ToTable("admin_rank", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_flag_id"); + + b.Property("AdminRankId") + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("flag"); + + b.HasKey("Id") + .HasName("PK_admin_rank_flag"); + + b.HasIndex("AdminRankId"); + + b.HasIndex("Flag", "AdminRankId") + .IsUnique(); + + b.ToTable("admin_rank_flag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_watchlists_id"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("TEXT") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("INTEGER") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("TEXT") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("TEXT") + .HasColumnName("deleted_by_id"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.HasKey("Id") + .HasName("PK_admin_watchlists"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_watchlists_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_watchlists_round_id"); + + b.ToTable("admin_watchlists", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("antag_id"); + + b.Property("AntagName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("antag_name"); + + b.Property("ProfileId") + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_antag"); + + b.HasIndex("ProfileId", "AntagName") + .IsUnique(); + + b.ToTable("antag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AssignedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("assigned_user_id_id"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("PK_assigned_user_id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.HasIndex("UserName") + .IsUnique(); + + b.ToTable("assigned_user_id", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("connection_log_id"); + + b.Property("Address") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("address"); + + b.Property("Denied") + .HasColumnType("INTEGER") + .HasColumnName("denied"); + + b.Property("HWId") + .HasColumnType("BLOB") + .HasColumnName("hwid"); + + b.Property("ServerId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0) + .HasColumnName("server_id"); + + b.Property("Time") + .HasColumnType("TEXT") + .HasColumnName("time"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("PK_connection_log"); + + b.HasIndex("ServerId") + .HasDatabaseName("IX_connection_log_server_id"); + + b.HasIndex("UserId"); + + b.ToTable("connection_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("job_id"); + + b.Property("JobName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("job_name"); + + b.Property("Priority") + .HasColumnType("INTEGER") + .HasColumnName("priority"); + + b.Property("ProfileId") + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_job"); + + b.HasIndex("ProfileId"); + + b.HasIndex("ProfileId", "JobName") + .IsUnique(); + + b.HasIndex(new[] { "ProfileId" }, "IX_job_one_high_priority") + .IsUnique() + .HasFilter("priority = 3"); + + b.ToTable("job", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Loadout", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("loadout_id"); + + b.Property("LoadoutName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("loadout_name"); + + b.Property("ProfileId") + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_loadout"); + + b.HasIndex("ProfileId", "LoadoutName") + .IsUnique(); + + b.ToTable("loadout", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.PlayTime", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("play_time_id"); + + b.Property("PlayerId") + .HasColumnType("TEXT") + .HasColumnName("player_id"); + + b.Property("TimeSpent") + .HasColumnType("TEXT") + .HasColumnName("time_spent"); + + b.Property("Tracker") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("tracker"); + + b.HasKey("Id") + .HasName("PK_play_time"); + + b.HasIndex("PlayerId", "Tracker") + .IsUnique(); + + b.ToTable("play_time", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("player_id"); + + b.Property("FirstSeenTime") + .HasColumnType("TEXT") + .HasColumnName("first_seen_time"); + + b.Property("LastReadRules") + .HasColumnType("TEXT") + .HasColumnName("last_read_rules"); + + b.Property("LastSeenAddress") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("last_seen_address"); + + b.Property("LastSeenHWId") + .HasColumnType("BLOB") + .HasColumnName("last_seen_hwid"); + + b.Property("LastSeenTime") + .HasColumnType("TEXT") + .HasColumnName("last_seen_time"); + + b.Property("LastSeenUserName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("last_seen_user_name"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_player"); + + b.HasAlternateKey("UserId") + .HasName("ak_player_user_id"); + + b.HasIndex("LastSeenUserName"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("player", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("preference_id"); + + b.Property("AdminOOCColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("admin_ooc_color"); + + b.Property("SelectedCharacterSlot") + .HasColumnType("INTEGER") + .HasColumnName("selected_character_slot"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_preference"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("preference", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.Property("Age") + .HasColumnType("INTEGER") + .HasColumnName("age"); + + b.Property("Backpack") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("backpack"); + + b.Property("CharacterName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("char_name"); + + b.Property("Clothing") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("clothing"); + + b.Property("CustomSpecieName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("custom_specie_name"); + + b.Property("EyeColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("eye_color"); + + b.Property("FacialHairColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("facial_hair_color"); + + b.Property("FacialHairName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("facial_hair_name"); + + b.Property("FlavorText") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("flavor_text"); + + b.Property("Gender") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("gender"); + + b.Property("HairColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("hair_color"); + + b.Property("HairName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("hair_name"); + + b.Property("Height") + .HasColumnType("REAL") + .HasColumnName("height"); + + b.Property("Markings") + .HasColumnType("jsonb") + .HasColumnName("markings"); + + b.Property("PreferenceId") + .HasColumnType("INTEGER") + .HasColumnName("preference_id"); + + b.Property("PreferenceUnavailable") + .HasColumnType("INTEGER") + .HasColumnName("pref_unavailable"); + + b.Property("Sex") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("sex"); + + b.Property("SkinColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("skin_color"); + + b.Property("Slot") + .HasColumnType("INTEGER") + .HasColumnName("slot"); + + b.Property("SpawnPriority") + .HasColumnType("INTEGER") + .HasColumnName("spawn_priority"); + + b.Property("Species") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("species"); + + b.Property("Width") + .HasColumnType("REAL") + .HasColumnName("width"); + + b.HasKey("Id") + .HasName("PK_profile"); + + b.HasIndex("PreferenceId") + .HasDatabaseName("IX_profile_preference_id"); + + b.HasIndex("Slot", "PreferenceId") + .IsUnique(); + + b.ToTable("profile", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("ServerId") + .HasColumnType("INTEGER") + .HasColumnName("server_id"); + + b.Property("StartDate") + .HasColumnType("TEXT") + .HasColumnName("start_date"); + + b.HasKey("Id") + .HasName("PK_round"); + + b.HasIndex("ServerId") + .HasDatabaseName("IX_round_server_id"); + + b.HasIndex("StartDate"); + + b.ToTable("round", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Server", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("server_id"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("PK_server"); + + b.ToTable("server", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("server_ban_id"); + + b.Property("Address") + .HasColumnType("TEXT") + .HasColumnName("address"); + + b.Property("AutoDelete") + .HasColumnType("INTEGER") + .HasColumnName("auto_delete"); + + b.Property("BanTime") + .HasColumnType("TEXT") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("banning_admin"); + + b.Property("ExemptFlags") + .HasColumnType("INTEGER") + .HasColumnName("exempt_flags"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("HWId") + .HasColumnType("BLOB") + .HasColumnName("hwid"); + + b.Property("Hidden") + .HasColumnType("INTEGER") + .HasColumnName("hidden"); + + b.Property("LastEditedAt") + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("reason"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Severity") + .HasColumnType("INTEGER") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_server_ban"); + + b.HasIndex("Address"); + + b.HasIndex("BanningAdmin"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_server_ban_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_server_ban_round_id"); + + b.ToTable("server_ban", null, t => + { + t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanExemption", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("Flags") + .HasColumnType("INTEGER") + .HasColumnName("flags"); + + b.HasKey("UserId") + .HasName("PK_server_ban_exemption"); + + b.ToTable("server_ban_exemption", null, t => + { + t.HasCheckConstraint("FlagsNotZero", "flags != 0"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanHit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("server_ban_hit_id"); + + b.Property("BanId") + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.Property("ConnectionId") + .HasColumnType("INTEGER") + .HasColumnName("connection_id"); + + b.HasKey("Id") + .HasName("PK_server_ban_hit"); + + b.HasIndex("BanId") + .HasDatabaseName("IX_server_ban_hit_ban_id"); + + b.HasIndex("ConnectionId") + .HasDatabaseName("IX_server_ban_hit_connection_id"); + + b.ToTable("server_ban_hit", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("server_role_ban_id"); + + b.Property("Address") + .HasColumnType("TEXT") + .HasColumnName("address"); + + b.Property("BanTime") + .HasColumnType("TEXT") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("banning_admin"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("HWId") + .HasColumnType("BLOB") + .HasColumnName("hwid"); + + b.Property("Hidden") + .HasColumnType("INTEGER") + .HasColumnName("hidden"); + + b.Property("LastEditedAt") + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("reason"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("role_id"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Severity") + .HasColumnType("INTEGER") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_server_role_ban"); + + b.HasIndex("Address"); + + b.HasIndex("BanningAdmin"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_server_role_ban_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_server_role_ban_round_id"); + + b.ToTable("server_role_ban", null, t => + { + t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("role_unban_id"); + + b.Property("BanId") + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("TEXT") + .HasColumnName("unban_time"); + + b.Property("UnbanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("unbanning_admin"); + + b.HasKey("Id") + .HasName("PK_server_role_unban"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("server_role_unban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("unban_id"); + + b.Property("BanId") + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("TEXT") + .HasColumnName("unban_time"); + + b.Property("UnbanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("unbanning_admin"); + + b.HasKey("Id") + .HasName("PK_server_unban"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("server_unban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Trait", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("trait_id"); + + b.Property("ProfileId") + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.Property("TraitName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("trait_name"); + + b.HasKey("Id") + .HasName("PK_trait"); + + b.HasIndex("ProfileId", "TraitName") + .IsUnique(); + + b.ToTable("trait", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.UploadedResourceLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("uploaded_resource_log_id"); + + b.Property("Data") + .IsRequired() + .HasColumnType("BLOB") + .HasColumnName("data"); + + b.Property("Date") + .HasColumnType("TEXT") + .HasColumnName("date"); + + b.Property("Path") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("path"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_uploaded_resource_log"); + + b.ToTable("uploaded_resource_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Whitelist", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("UserId") + .HasName("PK_whitelist"); + + b.ToTable("whitelist", (string)null); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.Property("PlayersId") + .HasColumnType("INTEGER") + .HasColumnName("players_id"); + + b.Property("RoundsId") + .HasColumnType("INTEGER") + .HasColumnName("rounds_id"); + + b.HasKey("PlayersId", "RoundsId") + .HasName("PK_player_round"); + + b.HasIndex("RoundsId") + .HasDatabaseName("IX_player_round_rounds_id"); + + b.ToTable("player_round", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.HasOne("Content.Server.Database.AdminRank", "AdminRank") + .WithMany("Admins") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_admin_rank_admin_rank_id"); + + b.Navigation("AdminRank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.HasOne("Content.Server.Database.Admin", "Admin") + .WithMany("Flags") + .HasForeignKey("AdminId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_flag_admin_admin_id"); + + b.Navigation("Admin"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany("AdminLogs") + .HasForeignKey("RoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_round_round_id"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => + { + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminLogs") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_player_player_player_user_id"); + + b.HasOne("Content.Server.Database.AdminLog", "Log") + .WithMany("Players") + .HasForeignKey("RoundId", "LogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_player_admin_log_round_id_log_id"); + + b.Navigation("Log"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminMessage", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminMessagesCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminMessagesDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminMessagesLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminMessagesReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_messages_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_messages_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminNote", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminNotesCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminNotesDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminNotesLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminNotesReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_notes_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_notes_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.HasOne("Content.Server.Database.AdminRank", "Rank") + .WithMany("Flags") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_rank_flag_admin_rank_admin_rank_id"); + + b.Navigation("Rank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminWatchlistsCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminWatchlistsDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminWatchlistsLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminWatchlistsReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_watchlists_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_watchlists_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Antags") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_antag_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.HasOne("Content.Server.Database.Server", "Server") + .WithMany("ConnectionLogs") + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.SetNull) + .IsRequired() + .HasConstraintName("FK_connection_log_server_server_id"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Jobs") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_job_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.Loadout", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Loadouts") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_loadout_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.HasOne("Content.Server.Database.Preference", "Preference") + .WithMany("Profiles") + .HasForeignKey("PreferenceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_preference_preference_id"); + + b.Navigation("Preference"); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.HasOne("Content.Server.Database.Server", "Server") + .WithMany("Rounds") + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_round_server_server_id"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminServerBansCreated") + .HasForeignKey("BanningAdmin") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_ban_player_banning_admin"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminServerBansLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_ban_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_server_ban_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanHit", b => + { + b.HasOne("Content.Server.Database.ServerBan", "Ban") + .WithMany("BanHits") + .HasForeignKey("BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_ban_hit_server_ban_ban_id"); + + b.HasOne("Content.Server.Database.ConnectionLog", "Connection") + .WithMany("BanHits") + .HasForeignKey("ConnectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_ban_hit_connection_log_connection_id"); + + b.Navigation("Ban"); + + b.Navigation("Connection"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminServerRoleBansCreated") + .HasForeignKey("BanningAdmin") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_role_ban_player_banning_admin"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminServerRoleBansLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_role_ban_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_server_role_ban_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => + { + b.HasOne("Content.Server.Database.ServerRoleBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.ServerRoleUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_role_unban_server_role_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerUnban", b => + { + b.HasOne("Content.Server.Database.ServerBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.ServerUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_unban_server_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.Trait", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Traits") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_trait_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.HasOne("Content.Server.Database.Player", null) + .WithMany() + .HasForeignKey("PlayersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_player_round_player_players_id"); + + b.HasOne("Content.Server.Database.Round", null) + .WithMany() + .HasForeignKey("RoundsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_player_round_round_rounds_id"); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.Navigation("Players"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Navigation("Admins"); + + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.Navigation("BanHits"); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.Navigation("AdminLogs"); + + b.Navigation("AdminMessagesCreated"); + + b.Navigation("AdminMessagesDeleted"); + + b.Navigation("AdminMessagesLastEdited"); + + b.Navigation("AdminMessagesReceived"); + + b.Navigation("AdminNotesCreated"); + + b.Navigation("AdminNotesDeleted"); + + b.Navigation("AdminNotesLastEdited"); + + b.Navigation("AdminNotesReceived"); + + b.Navigation("AdminServerBansCreated"); + + b.Navigation("AdminServerBansLastEdited"); + + b.Navigation("AdminServerRoleBansCreated"); + + b.Navigation("AdminServerRoleBansLastEdited"); + + b.Navigation("AdminWatchlistsCreated"); + + b.Navigation("AdminWatchlistsDeleted"); + + b.Navigation("AdminWatchlistsLastEdited"); + + b.Navigation("AdminWatchlistsReceived"); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Navigation("Profiles"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Navigation("Antags"); + + b.Navigation("Jobs"); + + b.Navigation("Loadouts"); + + b.Navigation("Traits"); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.Navigation("AdminLogs"); + }); + + modelBuilder.Entity("Content.Server.Database.Server", b => + { + b.Navigation("ConnectionLogs"); + + b.Navigation("Rounds"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.Navigation("BanHits"); + + b.Navigation("Unban"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.Navigation("Unban"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Content.Server.Database/Migrations/Sqlite/20241001054735_CustomSpecieName.cs b/Content.Server.Database/Migrations/Sqlite/20241001054735_CustomSpecieName.cs new file mode 100644 index 0000000000..a1e968045b --- /dev/null +++ b/Content.Server.Database/Migrations/Sqlite/20241001054735_CustomSpecieName.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Content.Server.Database.Migrations.Sqlite +{ + /// + public partial class CustomSpecieName : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "custom_specie_name", + table: "profile", + type: "TEXT", + nullable: false, + defaultValue: ""); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "custom_specie_name", + table: "profile"); + } + } +} diff --git a/Content.Server.Database/Migrations/Sqlite/20241018043307_RoleWhitelist.Designer.cs b/Content.Server.Database/Migrations/Sqlite/20241018043307_RoleWhitelist.Designer.cs new file mode 100644 index 0000000000..bef2d1038f --- /dev/null +++ b/Content.Server.Database/Migrations/Sqlite/20241018043307_RoleWhitelist.Designer.cs @@ -0,0 +1,1823 @@ +// +using System; +using Content.Server.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Content.Server.Database.Migrations.Sqlite +{ + [DbContext(typeof(SqliteServerDbContext))] + [Migration("20241018043307_RoleWhitelist")] + partial class RoleWhitelist + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.0"); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("AdminRankId") + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_id"); + + b.Property("Title") + .HasColumnType("TEXT") + .HasColumnName("title"); + + b.HasKey("UserId") + .HasName("PK_admin"); + + b.HasIndex("AdminRankId") + .HasDatabaseName("IX_admin_admin_rank_id"); + + b.ToTable("admin", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_flag_id"); + + b.Property("AdminId") + .HasColumnType("TEXT") + .HasColumnName("admin_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("flag"); + + b.Property("Negative") + .HasColumnType("INTEGER") + .HasColumnName("negative"); + + b.HasKey("Id") + .HasName("PK_admin_flag"); + + b.HasIndex("AdminId") + .HasDatabaseName("IX_admin_flag_admin_id"); + + b.HasIndex("Flag", "AdminId") + .IsUnique(); + + b.ToTable("admin_flag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Id") + .HasColumnType("INTEGER") + .HasColumnName("admin_log_id"); + + b.Property("Date") + .HasColumnType("TEXT") + .HasColumnName("date"); + + b.Property("Impact") + .HasColumnType("INTEGER") + .HasColumnName("impact"); + + b.Property("Json") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("json"); + + b.Property("Message") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("Type") + .HasColumnType("INTEGER") + .HasColumnName("type"); + + b.HasKey("RoundId", "Id") + .HasName("PK_admin_log"); + + b.HasIndex("Date"); + + b.HasIndex("Type") + .HasDatabaseName("IX_admin_log_type"); + + b.ToTable("admin_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => + { + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("LogId") + .HasColumnType("INTEGER") + .HasColumnName("log_id"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.HasKey("RoundId", "LogId", "PlayerUserId") + .HasName("PK_admin_log_player"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_log_player_player_user_id"); + + b.ToTable("admin_log_player", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_messages_id"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("TEXT") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("INTEGER") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("TEXT") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("TEXT") + .HasColumnName("deleted_by_id"); + + b.Property("Dismissed") + .HasColumnType("INTEGER") + .HasColumnName("dismissed"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Seen") + .HasColumnType("INTEGER") + .HasColumnName("seen"); + + b.HasKey("Id") + .HasName("PK_admin_messages"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_messages_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_messages_round_id"); + + b.ToTable("admin_messages", null, t => + { + t.HasCheckConstraint("NotDismissedAndSeen", "NOT dismissed OR seen"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.AdminNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_notes_id"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("TEXT") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("INTEGER") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("TEXT") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("TEXT") + .HasColumnName("deleted_by_id"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Secret") + .HasColumnType("INTEGER") + .HasColumnName("secret"); + + b.Property("Severity") + .HasColumnType("INTEGER") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_admin_notes"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_notes_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_notes_round_id"); + + b.ToTable("admin_notes", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_id"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("PK_admin_rank"); + + b.ToTable("admin_rank", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_flag_id"); + + b.Property("AdminRankId") + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("flag"); + + b.HasKey("Id") + .HasName("PK_admin_rank_flag"); + + b.HasIndex("AdminRankId"); + + b.HasIndex("Flag", "AdminRankId") + .IsUnique(); + + b.ToTable("admin_rank_flag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_watchlists_id"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("TEXT") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("INTEGER") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("TEXT") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("TEXT") + .HasColumnName("deleted_by_id"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.HasKey("Id") + .HasName("PK_admin_watchlists"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_watchlists_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_watchlists_round_id"); + + b.ToTable("admin_watchlists", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("antag_id"); + + b.Property("AntagName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("antag_name"); + + b.Property("ProfileId") + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_antag"); + + b.HasIndex("ProfileId", "AntagName") + .IsUnique(); + + b.ToTable("antag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AssignedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("assigned_user_id_id"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("PK_assigned_user_id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.HasIndex("UserName") + .IsUnique(); + + b.ToTable("assigned_user_id", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.BanTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("ban_template_id"); + + b.Property("AutoDelete") + .HasColumnType("INTEGER") + .HasColumnName("auto_delete"); + + b.Property("ExemptFlags") + .HasColumnType("INTEGER") + .HasColumnName("exempt_flags"); + + b.Property("Hidden") + .HasColumnType("INTEGER") + .HasColumnName("hidden"); + + b.Property("Length") + .HasColumnType("TEXT") + .HasColumnName("length"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("reason"); + + b.Property("Severity") + .HasColumnType("INTEGER") + .HasColumnName("severity"); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("title"); + + b.HasKey("Id") + .HasName("PK_ban_template"); + + b.ToTable("ban_template", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("connection_log_id"); + + b.Property("Address") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("address"); + + b.Property("Denied") + .HasColumnType("INTEGER") + .HasColumnName("denied"); + + b.Property("HWId") + .HasColumnType("BLOB") + .HasColumnName("hwid"); + + b.Property("ServerId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0) + .HasColumnName("server_id"); + + b.Property("Time") + .HasColumnType("TEXT") + .HasColumnName("time"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("PK_connection_log"); + + b.HasIndex("ServerId") + .HasDatabaseName("IX_connection_log_server_id"); + + b.HasIndex("UserId"); + + b.ToTable("connection_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("job_id"); + + b.Property("JobName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("job_name"); + + b.Property("Priority") + .HasColumnType("INTEGER") + .HasColumnName("priority"); + + b.Property("ProfileId") + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_job"); + + b.HasIndex("ProfileId"); + + b.HasIndex("ProfileId", "JobName") + .IsUnique(); + + b.HasIndex(new[] { "ProfileId" }, "IX_job_one_high_priority") + .IsUnique() + .HasFilter("priority = 3"); + + b.ToTable("job", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Loadout", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("loadout_id"); + + b.Property("LoadoutName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("loadout_name"); + + b.Property("ProfileId") + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_loadout"); + + b.HasIndex("ProfileId", "LoadoutName") + .IsUnique(); + + b.ToTable("loadout", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.PlayTime", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("play_time_id"); + + b.Property("PlayerId") + .HasColumnType("TEXT") + .HasColumnName("player_id"); + + b.Property("TimeSpent") + .HasColumnType("TEXT") + .HasColumnName("time_spent"); + + b.Property("Tracker") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("tracker"); + + b.HasKey("Id") + .HasName("PK_play_time"); + + b.HasIndex("PlayerId", "Tracker") + .IsUnique(); + + b.ToTable("play_time", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("player_id"); + + b.Property("FirstSeenTime") + .HasColumnType("TEXT") + .HasColumnName("first_seen_time"); + + b.Property("LastReadRules") + .HasColumnType("TEXT") + .HasColumnName("last_read_rules"); + + b.Property("LastSeenAddress") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("last_seen_address"); + + b.Property("LastSeenHWId") + .HasColumnType("BLOB") + .HasColumnName("last_seen_hwid"); + + b.Property("LastSeenTime") + .HasColumnType("TEXT") + .HasColumnName("last_seen_time"); + + b.Property("LastSeenUserName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("last_seen_user_name"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_player"); + + b.HasAlternateKey("UserId") + .HasName("ak_player_user_id"); + + b.HasIndex("LastSeenUserName"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("player", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("preference_id"); + + b.Property("AdminOOCColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("admin_ooc_color"); + + b.Property("SelectedCharacterSlot") + .HasColumnType("INTEGER") + .HasColumnName("selected_character_slot"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_preference"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("preference", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.Property("Age") + .HasColumnType("INTEGER") + .HasColumnName("age"); + + b.Property("Backpack") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("backpack"); + + b.Property("CharacterName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("char_name"); + + b.Property("Clothing") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("clothing"); + + b.Property("CustomSpecieName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("custom_specie_name"); + + b.Property("EyeColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("eye_color"); + + b.Property("FacialHairColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("facial_hair_color"); + + b.Property("FacialHairName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("facial_hair_name"); + + b.Property("FlavorText") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("flavor_text"); + + b.Property("Gender") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("gender"); + + b.Property("HairColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("hair_color"); + + b.Property("HairName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("hair_name"); + + b.Property("Height") + .HasColumnType("REAL") + .HasColumnName("height"); + + b.Property("Markings") + .HasColumnType("jsonb") + .HasColumnName("markings"); + + b.Property("PreferenceId") + .HasColumnType("INTEGER") + .HasColumnName("preference_id"); + + b.Property("PreferenceUnavailable") + .HasColumnType("INTEGER") + .HasColumnName("pref_unavailable"); + + b.Property("Sex") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("sex"); + + b.Property("SkinColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("skin_color"); + + b.Property("Slot") + .HasColumnType("INTEGER") + .HasColumnName("slot"); + + b.Property("SpawnPriority") + .HasColumnType("INTEGER") + .HasColumnName("spawn_priority"); + + b.Property("Species") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("species"); + + b.Property("Width") + .HasColumnType("REAL") + .HasColumnName("width"); + + b.HasKey("Id") + .HasName("PK_profile"); + + b.HasIndex("PreferenceId") + .HasDatabaseName("IX_profile_preference_id"); + + b.HasIndex("Slot", "PreferenceId") + .IsUnique(); + + b.ToTable("profile", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b => + { + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("RoleId") + .HasColumnType("TEXT") + .HasColumnName("role_id"); + + b.HasKey("PlayerUserId", "RoleId") + .HasName("PK_role_whitelists"); + + b.ToTable("role_whitelists", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("ServerId") + .HasColumnType("INTEGER") + .HasColumnName("server_id"); + + b.Property("StartDate") + .HasColumnType("TEXT") + .HasColumnName("start_date"); + + b.HasKey("Id") + .HasName("PK_round"); + + b.HasIndex("ServerId") + .HasDatabaseName("IX_round_server_id"); + + b.HasIndex("StartDate"); + + b.ToTable("round", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Server", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("server_id"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("PK_server"); + + b.ToTable("server", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("server_ban_id"); + + b.Property("Address") + .HasColumnType("TEXT") + .HasColumnName("address"); + + b.Property("AutoDelete") + .HasColumnType("INTEGER") + .HasColumnName("auto_delete"); + + b.Property("BanTime") + .HasColumnType("TEXT") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("banning_admin"); + + b.Property("ExemptFlags") + .HasColumnType("INTEGER") + .HasColumnName("exempt_flags"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("HWId") + .HasColumnType("BLOB") + .HasColumnName("hwid"); + + b.Property("Hidden") + .HasColumnType("INTEGER") + .HasColumnName("hidden"); + + b.Property("LastEditedAt") + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("reason"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Severity") + .HasColumnType("INTEGER") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_server_ban"); + + b.HasIndex("Address"); + + b.HasIndex("BanningAdmin"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_server_ban_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_server_ban_round_id"); + + b.ToTable("server_ban", null, t => + { + t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanExemption", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("Flags") + .HasColumnType("INTEGER") + .HasColumnName("flags"); + + b.HasKey("UserId") + .HasName("PK_server_ban_exemption"); + + b.ToTable("server_ban_exemption", null, t => + { + t.HasCheckConstraint("FlagsNotZero", "flags != 0"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanHit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("server_ban_hit_id"); + + b.Property("BanId") + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.Property("ConnectionId") + .HasColumnType("INTEGER") + .HasColumnName("connection_id"); + + b.HasKey("Id") + .HasName("PK_server_ban_hit"); + + b.HasIndex("BanId") + .HasDatabaseName("IX_server_ban_hit_ban_id"); + + b.HasIndex("ConnectionId") + .HasDatabaseName("IX_server_ban_hit_connection_id"); + + b.ToTable("server_ban_hit", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("server_role_ban_id"); + + b.Property("Address") + .HasColumnType("TEXT") + .HasColumnName("address"); + + b.Property("BanTime") + .HasColumnType("TEXT") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("banning_admin"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("HWId") + .HasColumnType("BLOB") + .HasColumnName("hwid"); + + b.Property("Hidden") + .HasColumnType("INTEGER") + .HasColumnName("hidden"); + + b.Property("LastEditedAt") + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("reason"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("role_id"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Severity") + .HasColumnType("INTEGER") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_server_role_ban"); + + b.HasIndex("Address"); + + b.HasIndex("BanningAdmin"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_server_role_ban_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_server_role_ban_round_id"); + + b.ToTable("server_role_ban", null, t => + { + t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("role_unban_id"); + + b.Property("BanId") + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("TEXT") + .HasColumnName("unban_time"); + + b.Property("UnbanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("unbanning_admin"); + + b.HasKey("Id") + .HasName("PK_server_role_unban"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("server_role_unban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("unban_id"); + + b.Property("BanId") + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("TEXT") + .HasColumnName("unban_time"); + + b.Property("UnbanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("unbanning_admin"); + + b.HasKey("Id") + .HasName("PK_server_unban"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("server_unban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Trait", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("trait_id"); + + b.Property("ProfileId") + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.Property("TraitName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("trait_name"); + + b.HasKey("Id") + .HasName("PK_trait"); + + b.HasIndex("ProfileId", "TraitName") + .IsUnique(); + + b.ToTable("trait", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.UploadedResourceLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("uploaded_resource_log_id"); + + b.Property("Data") + .IsRequired() + .HasColumnType("BLOB") + .HasColumnName("data"); + + b.Property("Date") + .HasColumnType("TEXT") + .HasColumnName("date"); + + b.Property("Path") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("path"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_uploaded_resource_log"); + + b.ToTable("uploaded_resource_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Whitelist", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("UserId") + .HasName("PK_whitelist"); + + b.ToTable("whitelist", (string)null); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.Property("PlayersId") + .HasColumnType("INTEGER") + .HasColumnName("players_id"); + + b.Property("RoundsId") + .HasColumnType("INTEGER") + .HasColumnName("rounds_id"); + + b.HasKey("PlayersId", "RoundsId") + .HasName("PK_player_round"); + + b.HasIndex("RoundsId") + .HasDatabaseName("IX_player_round_rounds_id"); + + b.ToTable("player_round", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.HasOne("Content.Server.Database.AdminRank", "AdminRank") + .WithMany("Admins") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_admin_rank_admin_rank_id"); + + b.Navigation("AdminRank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.HasOne("Content.Server.Database.Admin", "Admin") + .WithMany("Flags") + .HasForeignKey("AdminId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_flag_admin_admin_id"); + + b.Navigation("Admin"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany("AdminLogs") + .HasForeignKey("RoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_round_round_id"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => + { + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminLogs") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_player_player_player_user_id"); + + b.HasOne("Content.Server.Database.AdminLog", "Log") + .WithMany("Players") + .HasForeignKey("RoundId", "LogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_player_admin_log_round_id_log_id"); + + b.Navigation("Log"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminMessage", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminMessagesCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminMessagesDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminMessagesLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminMessagesReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_messages_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_messages_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminNote", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminNotesCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminNotesDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminNotesLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminNotesReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_notes_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_notes_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.HasOne("Content.Server.Database.AdminRank", "Rank") + .WithMany("Flags") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_rank_flag_admin_rank_admin_rank_id"); + + b.Navigation("Rank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminWatchlistsCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminWatchlistsDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminWatchlistsLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminWatchlistsReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_watchlists_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_watchlists_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Antags") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_antag_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.HasOne("Content.Server.Database.Server", "Server") + .WithMany("ConnectionLogs") + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.SetNull) + .IsRequired() + .HasConstraintName("FK_connection_log_server_server_id"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Jobs") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_job_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.Loadout", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Loadouts") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_loadout_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.HasOne("Content.Server.Database.Preference", "Preference") + .WithMany("Profiles") + .HasForeignKey("PreferenceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_preference_preference_id"); + + b.Navigation("Preference"); + }); + + modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b => + { + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("JobWhitelists") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_role_whitelists_player_player_user_id"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.HasOne("Content.Server.Database.Server", "Server") + .WithMany("Rounds") + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_round_server_server_id"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminServerBansCreated") + .HasForeignKey("BanningAdmin") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_ban_player_banning_admin"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminServerBansLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_ban_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_server_ban_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanHit", b => + { + b.HasOne("Content.Server.Database.ServerBan", "Ban") + .WithMany("BanHits") + .HasForeignKey("BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_ban_hit_server_ban_ban_id"); + + b.HasOne("Content.Server.Database.ConnectionLog", "Connection") + .WithMany("BanHits") + .HasForeignKey("ConnectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_ban_hit_connection_log_connection_id"); + + b.Navigation("Ban"); + + b.Navigation("Connection"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminServerRoleBansCreated") + .HasForeignKey("BanningAdmin") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_role_ban_player_banning_admin"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminServerRoleBansLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_role_ban_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_server_role_ban_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => + { + b.HasOne("Content.Server.Database.ServerRoleBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.ServerRoleUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_role_unban_server_role_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerUnban", b => + { + b.HasOne("Content.Server.Database.ServerBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.ServerUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_unban_server_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.Trait", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Traits") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_trait_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.HasOne("Content.Server.Database.Player", null) + .WithMany() + .HasForeignKey("PlayersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_player_round_player_players_id"); + + b.HasOne("Content.Server.Database.Round", null) + .WithMany() + .HasForeignKey("RoundsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_player_round_round_rounds_id"); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.Navigation("Players"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Navigation("Admins"); + + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.Navigation("BanHits"); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.Navigation("AdminLogs"); + + b.Navigation("AdminMessagesCreated"); + + b.Navigation("AdminMessagesDeleted"); + + b.Navigation("AdminMessagesLastEdited"); + + b.Navigation("AdminMessagesReceived"); + + b.Navigation("AdminNotesCreated"); + + b.Navigation("AdminNotesDeleted"); + + b.Navigation("AdminNotesLastEdited"); + + b.Navigation("AdminNotesReceived"); + + b.Navigation("AdminServerBansCreated"); + + b.Navigation("AdminServerBansLastEdited"); + + b.Navigation("AdminServerRoleBansCreated"); + + b.Navigation("AdminServerRoleBansLastEdited"); + + b.Navigation("AdminWatchlistsCreated"); + + b.Navigation("AdminWatchlistsDeleted"); + + b.Navigation("AdminWatchlistsLastEdited"); + + b.Navigation("AdminWatchlistsReceived"); + + b.Navigation("JobWhitelists"); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Navigation("Profiles"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Navigation("Antags"); + + b.Navigation("Jobs"); + + b.Navigation("Loadouts"); + + b.Navigation("Traits"); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.Navigation("AdminLogs"); + }); + + modelBuilder.Entity("Content.Server.Database.Server", b => + { + b.Navigation("ConnectionLogs"); + + b.Navigation("Rounds"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.Navigation("BanHits"); + + b.Navigation("Unban"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.Navigation("Unban"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Content.Server.Database/Migrations/Sqlite/20241018043307_RoleWhitelist.cs b/Content.Server.Database/Migrations/Sqlite/20241018043307_RoleWhitelist.cs new file mode 100644 index 0000000000..9d192fc685 --- /dev/null +++ b/Content.Server.Database/Migrations/Sqlite/20241018043307_RoleWhitelist.cs @@ -0,0 +1,40 @@ +#nullable disable + +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Content.Server.Database.Migrations.Sqlite +{ + /// + public partial class RoleWhitelist : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "role_whitelists", + columns: table => new + { + player_user_id = table.Column(type: "TEXT", nullable: false), + role_id = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_role_whitelists", x => new { x.player_user_id, x.role_id }); + table.ForeignKey( + name: "FK_role_whitelists_player_player_user_id", + column: x => x.player_user_id, + principalTable: "player", + principalColumn: "user_id", + onDelete: ReferentialAction.Cascade); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "role_whitelists"); + } + } +} diff --git a/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs b/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs index e711247bc3..c7a79b9717 100644 --- a/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs +++ b/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs @@ -483,6 +483,49 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("assigned_user_id", (string)null); }); + modelBuilder.Entity("Content.Server.Database.BanTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("ban_template_id"); + + b.Property("AutoDelete") + .HasColumnType("INTEGER") + .HasColumnName("auto_delete"); + + b.Property("ExemptFlags") + .HasColumnType("INTEGER") + .HasColumnName("exempt_flags"); + + b.Property("Hidden") + .HasColumnType("INTEGER") + .HasColumnName("hidden"); + + b.Property("Length") + .HasColumnType("TEXT") + .HasColumnName("length"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("reason"); + + b.Property("Severity") + .HasColumnType("INTEGER") + .HasColumnName("severity"); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("title"); + + b.HasKey("Id") + .HasName("PK_ban_template"); + + b.ToTable("ban_template", (string)null); + }); + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => { b.Property("Id") @@ -728,6 +771,11 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("TEXT") .HasColumnName("clothing"); + b.Property("CustomSpecieName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("custom_specie_name"); + b.Property("EyeColor") .IsRequired() .HasColumnType("TEXT") @@ -763,16 +811,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("TEXT") .HasColumnName("hair_name"); - // Parkstation-HeightSlider Start b.Property("Height") .HasColumnType("REAL") .HasColumnName("height"); - b.Property("Width") - .HasColumnType("REAL") - .HasColumnName("width"); - // Parkstation-HeightSlider End - b.Property("Markings") .HasColumnType("jsonb") .HasColumnName("markings"); @@ -808,6 +850,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("TEXT") .HasColumnName("species"); + b.Property("Width") + .HasColumnType("REAL") + .HasColumnName("width"); + b.HasKey("Id") .HasName("PK_profile"); @@ -820,6 +866,22 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("profile", (string)null); }); + modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b => + { + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("RoleId") + .HasColumnType("TEXT") + .HasColumnName("role_id"); + + b.HasKey("PlayerUserId", "RoleId") + .HasName("PK_role_whitelists"); + + b.ToTable("role_whitelists", (string)null); + }); + modelBuilder.Entity("Content.Server.Database.Round", b => { b.Property("Id") @@ -1497,6 +1559,19 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Preference"); }); + modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b => + { + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("JobWhitelists") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_role_whitelists_player_player_user_id"); + + b.Navigation("Player"); + }); + modelBuilder.Entity("Content.Server.Database.Round", b => { b.HasOne("Content.Server.Database.Server", "Server") @@ -1696,6 +1771,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("AdminWatchlistsLastEdited"); b.Navigation("AdminWatchlistsReceived"); + + b.Navigation("JobWhitelists"); }); modelBuilder.Entity("Content.Server.Database.Preference", b => diff --git a/Content.Server.Database/Model.cs b/Content.Server.Database/Model.cs index 6c6ae279df..e698805cfc 100644 --- a/Content.Server.Database/Model.cs +++ b/Content.Server.Database/Model.cs @@ -40,6 +40,8 @@ protected ServerDbContext(DbContextOptions options) : base(options) public DbSet AdminNotes { get; set; } = null!; public DbSet AdminWatchlists { get; set; } = null!; public DbSet AdminMessages { get; set; } = null!; + public DbSet BanTemplate { get; set; } = null!; + public DbSet RoleWhitelists { get; set; } = null!; protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -48,19 +50,19 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .IsUnique(); modelBuilder.Entity() - .HasIndex(p => new {p.Slot, PrefsId = p.PreferenceId}) + .HasIndex(p => new { p.Slot, PrefsId = p.PreferenceId }) .IsUnique(); modelBuilder.Entity() - .HasIndex(p => new {HumanoidProfileId = p.ProfileId, p.AntagName}) + .HasIndex(p => new { HumanoidProfileId = p.ProfileId, p.AntagName }) .IsUnique(); modelBuilder.Entity() - .HasIndex(p => new {HumanoidProfileId = p.ProfileId, p.TraitName}) + .HasIndex(p => new { HumanoidProfileId = p.ProfileId, p.TraitName }) .IsUnique(); modelBuilder.Entity() - .HasIndex(p => new {HumanoidProfileId = p.ProfileId, p.LoadoutName}) + .HasIndex(p => new { HumanoidProfileId = p.ProfileId, p.LoadoutName }) .IsUnique(); modelBuilder.Entity() @@ -90,15 +92,15 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .OnDelete(DeleteBehavior.SetNull); modelBuilder.Entity() - .HasIndex(f => new {f.Flag, f.AdminId}) + .HasIndex(f => new { f.Flag, f.AdminId }) .IsUnique(); modelBuilder.Entity() - .HasIndex(f => new {f.Flag, f.AdminRankId}) + .HasIndex(f => new { f.Flag, f.AdminRankId }) .IsUnique(); modelBuilder.Entity() - .HasKey(log => new {log.RoundId, log.Id}); + .HasKey(log => new { log.RoundId, log.Id }); modelBuilder.Entity() .Property(log => log.Id); @@ -123,7 +125,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasIndex(round => round.StartDate); modelBuilder.Entity() - .HasKey(logPlayer => new {logPlayer.RoundId, logPlayer.LogId, logPlayer.PlayerUserId}); + .HasKey(logPlayer => new { logPlayer.RoundId, logPlayer.LogId, logPlayer.PlayerUserId }); modelBuilder.Entity() .HasIndex(p => p.PlayerUserId); @@ -300,6 +302,13 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasForeignKey(ban => ban.LastEditedById) .HasPrincipalKey(author => author.UserId) .OnDelete(DeleteBehavior.SetNull); + + modelBuilder.Entity() + .HasOne(w => w.Player) + .WithMany(p => p.JobWhitelists) + .HasForeignKey(w => w.PlayerUserId) + .HasPrincipalKey(p => p.UserId) + .OnDelete(DeleteBehavior.Cascade); } public virtual IQueryable SearchLogs(IQueryable query, string searchText) @@ -330,6 +339,7 @@ public class Profile public int Slot { get; set; } [Column("char_name")] public string CharacterName { get; set; } = null!; public string FlavorText { get; set; } = null!; + public string CustomSpecieName { get; set; } = null!; public int Age { get; set; } public string Sex { get; set; } = null!; public string Gender { get; set; } = null!; @@ -455,6 +465,7 @@ public class Player public List AdminServerBansLastEdited { get; set; } = null!; public List AdminServerRoleBansCreated { get; set; } = null!; public List AdminServerRoleBansLastEdited { get; set; } = null!; + public List JobWhitelists { get; set; } = null!; } [Table("whitelist")] @@ -595,7 +606,7 @@ public interface IUnbanCommon public enum ServerBanExemptFlags { // @formatter:off - None = 0, + None = 0, /// /// Ban is a datacenter range, connections usually imply usage of a VPN service. @@ -1024,4 +1035,68 @@ public class AdminMessage : IAdminRemarksCommon /// public bool Dismissed { get; set; } } + + [PrimaryKey(nameof(PlayerUserId), nameof(RoleId))] + public class RoleWhitelist + { + [Required, ForeignKey("Player")] + public Guid PlayerUserId { get; set; } + public Player Player { get; set; } = default!; + + [Required] + public string RoleId { get; set; } = default!; + } + + /// + /// Defines a template that admins can use to quickly fill out ban information. + /// + /// + /// + /// This information is not currently used by the game itself, but it is used by SS14.Admin. + /// + /// + public sealed class BanTemplate + { + public int Id { get; set; } + + /// + /// Title of the ban template. This is purely for reference by admins and not copied into the ban. + /// + public required string Title { get; set; } + + /// + /// How long the ban should last. 0 for permanent. + /// + public TimeSpan Length { get; set; } + + /// + /// The reason for the ban. + /// + /// + public string Reason { get; set; } = ""; + + /// + /// Exemptions granted to the ban. + /// + /// + public ServerBanExemptFlags ExemptFlags { get; set; } + + /// + /// Severity of the ban + /// + /// + public NoteSeverity Severity { get; set; } + + /// + /// Ban will be automatically deleted once expired. + /// + /// + public bool AutoDelete { get; set; } + + /// + /// Ban is not visible to players in the remarks menu. + /// + /// + public bool Hidden { get; set; } + } } diff --git a/Content.Server/Abilities/Mime/MimePowersSystem.cs b/Content.Server/Abilities/Mime/MimePowersSystem.cs index c1d2643d6f..57163a96a5 100644 --- a/Content.Server/Abilities/Mime/MimePowersSystem.cs +++ b/Content.Server/Abilities/Mime/MimePowersSystem.cs @@ -58,9 +58,6 @@ private void OnComponentInit(EntityUid uid, MimePowersComponent component, Compo EnsureComp(uid); _alertsSystem.ShowAlert(uid, AlertType.VowOfSilence); _actionsSystem.AddAction(uid, ref component.InvisibleWallActionEntity, component.InvisibleWallAction, uid); - //Nyano - Summary: Add Psionic Ability to Mime. - if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) - psionic.PsionicAbility = component.InvisibleWallActionEntity; } /// diff --git a/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Bluespace.cs b/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Bluespace.cs new file mode 100644 index 0000000000..698f49a112 --- /dev/null +++ b/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Bluespace.cs @@ -0,0 +1,79 @@ +using Content.Shared.Abilities.Psionics; +using Content.Shared.Actions.Events; +using Content.Shared.Mobs.Components; +using System.Linq; +using System.Numerics; +using Content.Shared.Database; +using Robust.Shared.Collections; + +namespace Content.Server.Abilities.Psionics; + +public sealed partial class AnomalyPowerSystem +{ + /// + /// This function handles emulating the effects of a "Bluespace Anomaly", using the caster as the "Anomaly", + /// while substituting their Psionic casting stats for "Severity and Stability". + /// Essentially, scramble the location of entities near the caster(possibly to include the caster). + /// + private void DoBluespaceAnomalyEffects(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args, bool overcharged = false) + { + if (args.Bluespace is null) + return; + + if (overcharged) + BluespaceSupercrit(uid, component, args); + else BluespacePulse(uid, component, args); + } + + private void BluespaceSupercrit(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args) + { + var xform = Transform(uid); + var mapPos = _xform.GetWorldPosition(xform); + var radius = args.Bluespace!.Value.SupercriticalTeleportRadius * component.CurrentAmplification; + var gridBounds = new Box2(mapPos - new Vector2(radius, radius), mapPos + new Vector2(radius, radius)); + var mobs = new HashSet>(); + _lookup.GetEntitiesInRange(xform.Coordinates, args.Bluespace!.Value.MaxShuffleRadius, mobs); + foreach (var comp in mobs) + { + if (args.Bluespace!.Value.SupercritTeleportsCaster && comp.Owner == uid) + continue; + + var ent = comp.Owner; + var randomX = _random.NextFloat(gridBounds.Left, gridBounds.Right); + var randomY = _random.NextFloat(gridBounds.Bottom, gridBounds.Top); + + var pos = new Vector2(randomX, randomY); + + _adminLogger.Add(LogType.Teleport, $"{ToPrettyString(ent)} has been teleported to {pos} by the supercritical {ToPrettyString(uid)} at {mapPos}"); + + _xform.SetWorldPosition(ent, pos); + _audio.PlayPvs(args.Bluespace!.Value.TeleportSound, ent); + } + } + + private void BluespacePulse(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args) + { + var xformQuery = GetEntityQuery(); + var xform = xformQuery.GetComponent(uid); + var range = args.Bluespace!.Value.MaxShuffleRadius * component.CurrentAmplification; + var mobs = new HashSet>(); + _lookup.GetEntitiesInRange(xform.Coordinates, range, mobs); + var allEnts = new ValueList(mobs.Select(m => m.Owner)) { uid }; + var coords = new ValueList(); + foreach (var ent in allEnts) + { + if (args.Bluespace!.Value.PulseTeleportsCaster && ent == uid + || !xformQuery.TryGetComponent(ent, out var allXform)) + continue; + + coords.Add(_xform.GetWorldPosition(allXform)); + } + + _random.Shuffle(coords); + for (var i = 0; i < allEnts.Count; i++) + { + _adminLogger.Add(LogType.Teleport, $"{ToPrettyString(allEnts[i])} has been shuffled to {coords[i]} by the {ToPrettyString(uid)} at {xform.Coordinates}"); + _xform.SetWorldPosition(allEnts[i], coords[i]); + } + } +} \ No newline at end of file diff --git a/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Electricity.cs b/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Electricity.cs new file mode 100644 index 0000000000..3f494aafb1 --- /dev/null +++ b/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Electricity.cs @@ -0,0 +1,39 @@ +using Content.Shared.Abilities.Psionics; +using Content.Shared.Actions.Events; + +namespace Content.Server.Abilities.Psionics; + +public sealed partial class AnomalyPowerSystem +{ + /// + /// This function handles emulating the effects of a "Electrical Anomaly", using the caster as the "Anomaly", + /// while substituting their Psionic casting stats for "Severity and Stability". + /// This fires lightning bolts at random entities near the caster. + /// + private void DoElectricityAnomalyEffects(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args, bool overcharged = false) + { + if (args.Electricity is null) + return; + + if (overcharged) + ElectricitySupercrit(uid, component, args); + else ElectricityPulse(uid, component, args); + } + + private void ElectricitySupercrit(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args) + { + var range = args.Electricity!.Value.MaxElectrocuteRange * component.CurrentAmplification; + + _emp.EmpPulse(_xform.GetMapCoordinates(uid), range, args.Electricity!.Value.EmpEnergyConsumption, args.Electricity!.Value.EmpDisabledDuration); + _lightning.ShootRandomLightnings(uid, range, args.Electricity!.Value.MaxBoltCount * (int) component.CurrentAmplification, arcDepth: (int) component.CurrentDampening); + } + + private void ElectricityPulse(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args) + { + var range = args.Electricity!.Value.MaxElectrocuteRange * component.CurrentAmplification; + + int boltCount = (int) MathF.Floor(MathHelper.Lerp(args.Electricity!.Value.MinBoltCount, args.Electricity!.Value.MaxBoltCount, component.CurrentAmplification)); + + _lightning.ShootRandomLightnings(uid, range, boltCount); + } +} \ No newline at end of file diff --git a/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.EntitySpawn.cs b/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.EntitySpawn.cs new file mode 100644 index 0000000000..456f278486 --- /dev/null +++ b/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.EntitySpawn.cs @@ -0,0 +1,79 @@ +using Content.Shared.Abilities.Psionics; +using Content.Shared.Actions.Events; +using Content.Shared.Random.Helpers; +using Robust.Shared.Random; +using Content.Shared.Anomaly.Effects.Components; +using Robust.Shared.Map.Components; + +namespace Content.Server.Abilities.Psionics; + +public sealed partial class AnomalyPowerSystem +{ + private const string NoGrid = "entity-anomaly-no-grid"; + + /// + /// This function handles emulating the effects of an "Entity Anomaly", using the caster as the "Anomaly", + /// while substituting their Psionic casting stats for "Severity and Stability". + /// Essentially, spawn entities on random tiles in a radius around the caster. + /// + private void DoEntityAnomalyEffects(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args, bool overcharged = false) + { + if (args.EntitySpawnEntries is null) + return; + + if (Transform(uid).GridUid is null) + { + _popup.PopupEntity(Loc.GetString(NoGrid), uid, uid); + return; + } + + if (overcharged) + EntitySupercrit(uid, component, args); + else EntityPulse(uid, component, args); + } + + private void EntitySupercrit(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args) + { + foreach (var entry in args.EntitySpawnEntries!) + { + if (!entry.Settings.SpawnOnSuperCritical) + continue; + + SpawnEntities(uid, component, entry); + } + } + + private void EntityPulse(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args) + { + if (args.EntitySpawnEntries is null) + return; + + foreach (var entry in args.EntitySpawnEntries!) + { + if (!entry.Settings.SpawnOnPulse) + continue; + + SpawnEntities(uid, component, entry); + } + } + + private void SpawnEntities(EntityUid uid, PsionicComponent component, EntitySpawnSettingsEntry entry) + { + if (!TryComp(Transform(uid).GridUid, out var grid)) + return; + + var tiles = _anomalySystem.GetSpawningPoints(uid, + component.CurrentDampening, + component.CurrentAmplification, + entry.Settings, + _glimmerSystem.Glimmer / 1000, + component.CurrentAmplification, + component.CurrentAmplification); + + if (tiles is null) + return; + + foreach (var tileref in tiles) + Spawn(_random.Pick(entry.Spawns), _mapSystem.ToCenterCoordinates(tileref, grid)); + } +} diff --git a/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Explosion.cs b/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Explosion.cs new file mode 100644 index 0000000000..06501afa71 --- /dev/null +++ b/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Explosion.cs @@ -0,0 +1,52 @@ +using Content.Shared.Abilities.Psionics; +using Content.Shared.Actions.Events; + +namespace Content.Server.Abilities.Psionics; + +public sealed partial class AnomalyPowerSystem +{ + /// + /// This function handles emulating the effects of a "Explosion Anomaly", using the caster as the "Anomaly", + /// while substituting their Psionic casting stats for "Severity and Stability". + /// Generates an explosion centered on the caster. + /// + private void DoExplosionAnomalyEffects(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args, bool overcharged = false) + { + if (args.Explosion is null) + return; + + if (overcharged) + ExplosionSupercrit(uid, component, args); + else ExplosionPulse(uid, component, args); + } + + private void ExplosionSupercrit(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args) + { + if (args.Explosion!.Value.SupercritExplosionPrototype is null) + return; + + var explosion = args.Explosion!.Value; + _boom.QueueExplosion( + uid, + explosion.SupercritExplosionPrototype, + explosion.SupercritTotalIntensity * component.CurrentAmplification, + explosion.SupercritDropoff / component.CurrentDampening, + explosion.SupercritMaxTileIntensity * component.CurrentDampening + ); + } + + private void ExplosionPulse(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args) + { + if (args.Explosion!.Value.ExplosionPrototype is null) + return; + + var explosion = args.Explosion!.Value; + _boom.QueueExplosion( + uid, + explosion.ExplosionPrototype, + explosion.TotalIntensity * component.CurrentAmplification, + explosion.Dropoff / component.CurrentDampening, + explosion.MaxTileIntensity * component.CurrentDampening + ); + } +} \ No newline at end of file diff --git a/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.GasProducer.cs b/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.GasProducer.cs new file mode 100644 index 0000000000..267f826f35 --- /dev/null +++ b/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.GasProducer.cs @@ -0,0 +1,110 @@ +using Content.Shared.Abilities.Psionics; +using Content.Shared.Actions.Events; +using Robust.Shared.Map.Components; +using System.Linq; +using System.Numerics; + +namespace Content.Server.Abilities.Psionics; + +public sealed partial class AnomalyPowerSystem +{ + private void DoGasProducerAnomalyEffects(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args, bool overcharged = false) + { + if (args.Gas is null) + return; + + if (overcharged) + GasProducerSupercrit(uid, component, args); + else GasProducerPulse(uid, component, args); + } + + private void GasProducerSupercrit(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args) + { + var xform = Transform(uid); + if (!TryComp(xform.GridUid, out var grid)) + return; + + var gas = args.Gas!.Value.SupercritReleasedGas; + var mols = args.Gas!.Value.SupercritMoleAmount * component.CurrentAmplification; + var radius = args.Gas!.Value.SupercritSpawnRadius * component.CurrentAmplification; + var count = args.Gas!.Value.SupercritTileCount * component.CurrentDampening; + var temp = args.Gas!.Value.SupercritTempChange * component.CurrentDampening; + var localpos = xform.Coordinates.Position; + var tilerefs = grid.GetLocalTilesIntersecting( + new Box2(localpos + new Vector2(-radius, -radius), localpos + new Vector2(radius, radius))).ToArray(); + + if (tilerefs.Length == 0) + return; + + var mixture = _atmosphere.GetTileMixture((uid, xform), true); + if (mixture != null) + { + mixture.AdjustMoles(gas, mols); + mixture.Temperature += temp; + } + + if (count == 0) + return; + + _random.Shuffle(tilerefs); + var amountCounter = 0; + foreach (var tileref in tilerefs) + { + var mix = _atmosphere.GetTileMixture(xform.GridUid, xform.MapUid, tileref.GridIndices, true); + amountCounter++; + if (mix is not { }) + continue; + + mix.AdjustMoles(gas, mols); + mix.Temperature += temp; + + if (amountCounter >= count) + return; + } + } + + private void GasProducerPulse(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args) + { + var xform = Transform(uid); + if (!TryComp(xform.GridUid, out var grid)) + return; + + var gas = args.Gas!.Value.ReleasedGas; + var mols = args.Gas!.Value.MoleAmount * component.CurrentAmplification; + var radius = args.Gas!.Value.SpawnRadius * component.CurrentAmplification; + var count = args.Gas!.Value.TileCount * component.CurrentDampening; + var temp = args.Gas!.Value.TempChange * component.CurrentDampening; + var localpos = xform.Coordinates.Position; + var tilerefs = grid.GetLocalTilesIntersecting( + new Box2(localpos + new Vector2(-radius, -radius), localpos + new Vector2(radius, radius))).ToArray(); + + if (tilerefs.Length == 0) + return; + + var mixture = _atmosphere.GetTileMixture((uid, xform), true); + if (mixture != null) + { + mixture.AdjustMoles(gas, mols); + mixture.Temperature += temp; + } + + if (count == 0) + return; + + _random.Shuffle(tilerefs); + var amountCounter = 0; + foreach (var tileref in tilerefs) + { + var mix = _atmosphere.GetTileMixture(xform.GridUid, xform.MapUid, tileref.GridIndices, true); + amountCounter++; + if (mix is not { }) + continue; + + mix.AdjustMoles(gas, mols); + mix.Temperature += temp; + + if (amountCounter >= count) + return; + } + } +} \ No newline at end of file diff --git a/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Gravity.cs b/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Gravity.cs new file mode 100644 index 0000000000..532cb846d4 --- /dev/null +++ b/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Gravity.cs @@ -0,0 +1,78 @@ +using Content.Shared.Abilities.Psionics; +using Content.Shared.Actions.Events; +using Robust.Shared.Physics.Components; +using Content.Shared.Physics; +using System.Linq; +using Robust.Shared.Map; +using Robust.Shared.Map.Components; + +namespace Content.Server.Abilities.Psionics; + +public sealed partial class AnomalyPowerSystem +{ + private void DoGravityAnomalyEffects(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args, bool overcharged = false) + { + if (args.Gravity is null) + return; + + if (overcharged) + GravitySupercrit(uid, component, args); + else GravityPulse(uid, component, args); + } + + private void GravitySupercrit(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args) + { + var xform = Transform(uid); + if (!TryComp(xform.GridUid, out MapGridComponent? grid)) + return; + + var gravity = args.Gravity!.Value; + var worldPos = _xform.GetWorldPosition(xform); + var tileref = _mapSystem.GetTilesIntersecting( + xform.GridUid.Value, + grid, + new Circle(worldPos, gravity.SpaceRange)) + .ToArray(); + + var tiles = tileref.Select(t => (t.GridIndices, Tile.Empty)).ToList(); + _mapSystem.SetTiles(xform.GridUid.Value, grid, tiles); + + var range = gravity.MaxThrowRange * component.CurrentDampening; + var strength = gravity.MaxThrowStrength * component.CurrentAmplification; + var lookup = _lookup.GetEntitiesInRange(uid, range, LookupFlags.Dynamic | LookupFlags.Sundries); + var xformQuery = GetEntityQuery(); + var physQuery = GetEntityQuery(); + + foreach (var ent in lookup) + { + if (physQuery.TryGetComponent(ent, out var phys) + && (phys.CollisionMask & (int) CollisionGroup.GhostImpassable) != 0) + continue; + + var foo = _xform.GetWorldPosition(ent, xformQuery) - worldPos; + _throwing.TryThrow(ent, foo * 5, strength, uid, 0); + } + } + + private void GravityPulse(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args) + { + var gravity = args.Gravity!.Value; + var xform = Transform(uid); + var range = gravity.MaxThrowRange * component.CurrentDampening; + var strength = gravity.MaxThrowStrength * component.CurrentAmplification; + var lookup = _lookup.GetEntitiesInRange(uid, range, LookupFlags.Dynamic | LookupFlags.Sundries); + var xformQuery = GetEntityQuery(); + var worldPos = _xform.GetWorldPosition(xform, xformQuery); + var physQuery = GetEntityQuery(); + + foreach (var ent in lookup) + { + if (physQuery.TryGetComponent(ent, out var phys) + && (phys.CollisionMask & (int) CollisionGroup.GhostImpassable) != 0) + continue; + + var foo = _xform.GetWorldPosition(ent, xformQuery) - worldPos; + _throwing.TryThrow(ent, foo * 10, strength, uid, 0); + } + } +} \ No newline at end of file diff --git a/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Injection.cs b/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Injection.cs new file mode 100644 index 0000000000..e2b1139271 --- /dev/null +++ b/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Injection.cs @@ -0,0 +1,78 @@ +using Content.Shared.Abilities.Psionics; +using Content.Shared.Actions.Events; +using Content.Shared.Chemistry.Components.SolutionManager; +using System.Linq; + +namespace Content.Server.Abilities.Psionics; + +public sealed partial class AnomalyPowerSystem +{ + private EntityQuery _injectableQuery; + private void DoInjectionAnomalyEffects(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args, bool overcharged = false) + { + if (args.Injection is null) + return; + + if (overcharged) + InjectionSupercrit(uid, component, args); + else InjectionPulse(uid, component, args); + } + + private void InjectionSupercrit(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args) + { + var injection = args.Injection!.Value; + var injectRadius = injection.SuperCriticalInjectRadius * component.CurrentAmplification; + var maxInject = injection.SuperCriticalSolutionInjection * component.CurrentDampening; + + if (!_solutionContainer.TryGetSolution(uid, injection.Solution, out _, out var sol)) + return; + + //We get all the entity in the radius into which the reagent will be injected. + var xformQuery = GetEntityQuery(); + var xform = xformQuery.GetComponent(uid); + var allEnts = _lookup.GetEntitiesInRange(_xform.GetMapCoordinates(uid), injectRadius) + .Select(x => x.Owner).ToList(); + + //for each matching entity found + foreach (var ent in allEnts) + { + if (!_solutionContainer.TryGetInjectableSolution(ent, out var injectable, out _) + || !_injectableQuery.TryGetComponent(ent, out var injEnt) + || !_solutionContainer.TryTransferSolution(injectable.Value, sol, maxInject)) + continue; + + //Spawn Effect + var uidXform = Transform(ent); + Spawn(injection.VisualEffectPrototype, uidXform.Coordinates); + } + } + + private void InjectionPulse(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args) + { + var injection = args.Injection!.Value; + var injectRadius = injection.InjectRadius * component.CurrentAmplification; + var maxInject = injection.MaxSolutionInjection * component.CurrentDampening; + + if (!_solutionContainer.TryGetSolution(uid, injection.Solution, out _, out var sol)) + return; + + //We get all the entity in the radius into which the reagent will be injected. + var xformQuery = GetEntityQuery(); + var xform = xformQuery.GetComponent(uid); + var allEnts = _lookup.GetEntitiesInRange(_xform.GetMapCoordinates(uid), injectRadius) + .Select(x => x.Owner).ToList(); + + //for each matching entity found + foreach (var ent in allEnts) + { + if (!_solutionContainer.TryGetInjectableSolution(ent, out var injectable, out _) + || !_injectableQuery.TryGetComponent(ent, out var injEnt) + || !_solutionContainer.TryTransferSolution(injectable.Value, sol, maxInject)) + continue; + + //Spawn Effect + var uidXform = Transform(ent); + Spawn(injection.VisualEffectPrototype, uidXform.Coordinates); + } + } +} \ No newline at end of file diff --git a/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Puddle.cs b/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Puddle.cs new file mode 100644 index 0000000000..a53b5e4930 --- /dev/null +++ b/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Puddle.cs @@ -0,0 +1,38 @@ +using Content.Shared.Abilities.Psionics; +using Content.Shared.Actions.Events; + +namespace Content.Server.Abilities.Psionics; + +public sealed partial class AnomalyPowerSystem +{ + private void DoPuddleAnomalyEffects(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args, bool overcharged = false) + { + if (args.Puddle is null) + return; + + if (overcharged) + PuddleSupercrit(uid, args); + else PuddlePulse(uid, component, args); + } + + private void PuddleSupercrit(EntityUid uid, AnomalyPowerActionEvent args) + { + var puddle = args.Puddle!.Value; + if (!_solutionContainer.TryGetSolution(uid, puddle.Solution, out _, out var sol)) + return; + + var xform = Transform(uid); + _puddle.TrySpillAt(xform.Coordinates, sol, out _); + } + + private void PuddlePulse(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args) + { + var puddle = args.Puddle!.Value; + if (!_solutionContainer.TryGetSolution(uid, puddle.Solution, out var sol, out _)) + return; + + var xform = Transform(uid); + var puddleSol = _solutionContainer.SplitSolution(sol.Value, puddle.MaxPuddleSize * component.CurrentAmplification); + _puddle.TrySplashSpillAt(uid, xform.Coordinates, puddleSol, out _); + } +} \ No newline at end of file diff --git a/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Pyroclastic.cs b/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Pyroclastic.cs new file mode 100644 index 0000000000..0ff6fc28b8 --- /dev/null +++ b/Content.Server/Abilities/Psionics/Abilities/AnomalyPowerSystem.Pyroclastic.cs @@ -0,0 +1,49 @@ +using Content.Shared.Abilities.Psionics; +using Content.Shared.Actions.Events; +using Content.Server.Atmos.Components; +using Robust.Shared.Map; + +namespace Content.Server.Abilities.Psionics; + +public sealed partial class AnomalyPowerSystem +{ + private void DoPyroclasticAnomalyEffects(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args, bool overcharged = false) + { + if (args.Pyroclastic is null) + return; + + if (overcharged) + PyroclasticSupercrit(uid, component, args); + else PyroclasticPulse(uid, component, args); + } + + private void PyroclasticSupercrit(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args) + { + var pyroclastic = args.Pyroclastic!.Value; + var xform = Transform(uid); + var ignitionRadius = pyroclastic.SupercritMaximumIgnitionRadius * component.CurrentAmplification; + IgniteNearby(uid, xform.Coordinates, component.CurrentAmplification, ignitionRadius); + } + + private void PyroclasticPulse(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args) + { + var pyroclastic = args.Pyroclastic!.Value; + var xform = Transform(uid); + var ignitionRadius = pyroclastic.MaximumIgnitionRadius * component.CurrentAmplification; + IgniteNearby(uid, xform.Coordinates, component.CurrentAmplification, ignitionRadius); + } + + private void IgniteNearby(EntityUid uid, EntityCoordinates coordinates, float severity, float radius) + { + var flammables = new HashSet>(); + _lookup.GetEntitiesInRange(coordinates, radius, flammables); + + foreach (var flammable in flammables) + { + var ent = flammable.Owner; + var stackAmount = 1 + (int) (severity / 0.15f); + _flammable.AdjustFireStacks(ent, stackAmount, flammable); + _flammable.Ignite(ent, uid, flammable); + } + } +} \ No newline at end of file diff --git a/Content.Server/Abilities/Psionics/Abilities/DarkSwapSystem.cs b/Content.Server/Abilities/Psionics/Abilities/DarkSwapSystem.cs new file mode 100644 index 0000000000..fd394e0a22 --- /dev/null +++ b/Content.Server/Abilities/Psionics/Abilities/DarkSwapSystem.cs @@ -0,0 +1,58 @@ +using Content.Shared.Abilities.Psionics; +using Content.Shared.Actions.Events; +using Content.Shared.Shadowkin; +using Content.Shared.Physics; +using Content.Shared.Popups; +using Content.Shared.Maps; +using Robust.Server.GameObjects; + +namespace Content.Server.Abilities.Psionics +{ + public sealed class DarkSwapSystem : EntitySystem + { + [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly PhysicsSystem _physics = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnPowerUsed); + } + + private void OnPowerUsed(DarkSwapActionEvent args) + { + if (TryComp(args.Performer, out var ethereal)) + { + var tileref = Transform(args.Performer).Coordinates.GetTileRef(); + if (tileref != null + && _physics.GetEntitiesIntersectingBody(args.Performer, (int) CollisionGroup.Impassable).Count > 0) + { + _popup.PopupEntity(Loc.GetString("revenant-in-solid"), args.Performer, args.Performer); + return; + } + + if (_psionics.OnAttemptPowerUse(args.Performer, "DarkSwap", args.ManaCost / 2, args.CheckInsulation)) + { + RemComp(args.Performer, ethereal); + args.Handled = true; + } + } + else if (_psionics.OnAttemptPowerUse(args.Performer, "DarkSwap", args.ManaCost, args.CheckInsulation)) + { + var newethereal = EnsureComp(args.Performer); + newethereal.Darken = true; + + SpawnAtPosition("ShadowkinShadow", Transform(args.Performer).Coordinates); + SpawnAtPosition("EffectFlashShadowkinDarkSwapOn", Transform(args.Performer).Coordinates); + + args.Handled = true; + } + + if (args.Handled) + _psionics.LogPowerUsed(args.Performer, "DarkSwap", 0, 0); + } + } +} + + diff --git a/Content.Server/Abilities/Psionics/Abilities/DispelPowerSystem.cs b/Content.Server/Abilities/Psionics/Abilities/DispelPowerSystem.cs index d338a5a5bc..ecffc86c76 100644 --- a/Content.Server/Abilities/Psionics/Abilities/DispelPowerSystem.cs +++ b/Content.Server/Abilities/Psionics/Abilities/DispelPowerSystem.cs @@ -1,4 +1,3 @@ -using Content.Shared.Actions; using Content.Shared.StatusEffect; using Content.Shared.Abilities.Psionics; using Content.Shared.Damage; @@ -6,11 +5,8 @@ using Content.Server.Guardian; using Content.Server.Bible.Components; using Content.Server.Popups; -using Robust.Shared.Prototypes; using Robust.Shared.Player; using Robust.Shared.Random; -using Robust.Shared.Timing; -using Content.Shared.Mind; using Content.Shared.Actions.Events; using Robust.Shared.Audio.Systems; @@ -18,24 +14,18 @@ namespace Content.Server.Abilities.Psionics { public sealed class DispelPowerSystem : EntitySystem { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly StatusEffectsSystem _statusEffects = default!; - [Dependency] private readonly SharedActionsSystem _actions = default!; [Dependency] private readonly DamageableSystem _damageableSystem = default!; [Dependency] private readonly GuardianSystem _guardianSystem = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; [Dependency] private readonly SharedAudioSystem _audioSystem = default!; [Dependency] private readonly PopupSystem _popupSystem = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly SharedMindSystem _mindSystem = default!; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnShutdown); SubscribeLocalEvent(OnPowerUsed); SubscribeLocalEvent(OnDispelled); @@ -46,32 +36,9 @@ public override void Initialize() SubscribeLocalEvent(OnRevenantDispelled); } - private void OnInit(EntityUid uid, DispelPowerComponent component, ComponentInit args) - { - _actions.AddAction(uid, ref component.DispelActionEntity, component.DispelActionId ); - _actions.TryGetActionData( component.DispelActionEntity, out var actionData ); - if (actionData is { UseDelay: not null }) - _actions.StartUseDelay(component.DispelActionEntity); - if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) - { - psionic.PsionicAbility = component.DispelActionEntity; - psionic.ActivePowers.Add(component); - } - } - - private void OnShutdown(EntityUid uid, DispelPowerComponent component, ComponentShutdown args) - { - _actions.RemoveAction(uid, component.DispelActionEntity); - - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Remove(component); - } - } - private void OnPowerUsed(DispelPowerActionEvent args) { - if (HasComp(args.Target)) + if (!_psionics.OnAttemptPowerUse(args.Performer, "dispel")) return; var ev = new DispelledEvent(); diff --git a/Content.Server/Abilities/Psionics/Abilities/HealOtherPowerSystem.cs b/Content.Server/Abilities/Psionics/Abilities/HealOtherPowerSystem.cs new file mode 100644 index 0000000000..6a2e90dd88 --- /dev/null +++ b/Content.Server/Abilities/Psionics/Abilities/HealOtherPowerSystem.cs @@ -0,0 +1,170 @@ +using Robust.Shared.Player; +using Content.Server.DoAfter; +using Content.Shared.Abilities.Psionics; +using Content.Shared.Damage; +using Content.Shared.DoAfter; +using Content.Shared.Popups; +using Content.Shared.Psionics.Events; +using Content.Shared.Examine; +using static Content.Shared.Examine.ExamineSystemShared; +using Robust.Shared.Timing; +using Content.Shared.Actions.Events; +using Robust.Server.Audio; +using Content.Server.Atmos.Rotting; +using Content.Shared.Mobs.Systems; +using Content.Shared.Mobs; +using Content.Shared.Mobs.Components; +using Content.Shared.Psionics.Glimmer; + +namespace Content.Server.Abilities.Psionics; + +public sealed class RevivifyPowerSystem : EntitySystem +{ + [Dependency] private readonly AudioSystem _audioSystem = default!; + [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; + [Dependency] private readonly SharedPopupSystem _popupSystem = default!; + [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly ExamineSystemShared _examine = default!; + [Dependency] private readonly DamageableSystem _damageable = default!; + [Dependency] private readonly RottingSystem _rotting = default!; + [Dependency] private readonly MobThresholdSystem _mobThreshold = default!; + [Dependency] private readonly MobStateSystem _mobState = default!; + [Dependency] private readonly GlimmerSystem _glimmer = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnPowerUsed); + SubscribeLocalEvent(OnDispelled); + SubscribeLocalEvent(OnDoAfter); + } + + + private void OnPowerUsed(EntityUid uid, PsionicComponent component, PsionicHealOtherPowerActionEvent args) + { + if (!_psionics.OnAttemptPowerUse(args.Performer, args.PowerName)) + return; + + args.ModifiedAmplification = _psionics.ModifiedAmplification(uid, component); + args.ModifiedDampening = _psionics.ModifiedDampening(uid, component); + + if (!args.Immediate) + AttemptDoAfter(uid, component, args); + else ActivatePower(uid, component, args); + + if (args.PopupText is not null + && _glimmer.Glimmer > args.GlimmerPopupThreshold * args.ModifiedDampening) + _popupSystem.PopupEntity(Loc.GetString(args.PopupText, ("entity", uid)), uid, + Filter.Pvs(uid).RemoveWhereAttachedEntity(entity => !_examine.InRangeUnOccluded(uid, entity, ExamineRange, null)), + true, + args.PopupType); + + if (args.PlaySound + && _glimmer.Glimmer > args.GlimmerSoundThreshold * args.ModifiedDampening) + _audioSystem.PlayPvs(args.SoundUse, uid, args.AudioParams); + + // Sanitize the Glimmer inputs because otherwise the game will crash if someone makes MaxGlimmer lower than MinGlimmer. + var minGlimmer = (int) Math.Round(MathF.MinMagnitude(args.MinGlimmer, args.MaxGlimmer) + * args.ModifiedAmplification - args.ModifiedDampening); + var maxGlimmer = (int) Math.Round(MathF.MaxMagnitude(args.MinGlimmer, args.MaxGlimmer) + * args.ModifiedAmplification - args.ModifiedDampening); + + _psionics.LogPowerUsed(uid, args.PowerName, minGlimmer, maxGlimmer); + args.Handled = true; + } + + private void AttemptDoAfter(EntityUid uid, PsionicComponent component, PsionicHealOtherPowerActionEvent args) + { + var ev = new PsionicHealOtherDoAfterEvent(_gameTiming.CurTime); + if (args.HealingAmount is not null) + ev.HealingAmount = args.HealingAmount; + if (args.RotReduction is not null) + ev.RotReduction = args.RotReduction.Value; + + ev.ModifiedAmplification = args.ModifiedAmplification; + ev.ModifiedDampening = args.ModifiedDampening; + ev.DoRevive = args.DoRevive; + var doAfterArgs = new DoAfterArgs(EntityManager, uid, args.UseDelay, ev, uid, target: args.Target) + { + BreakOnUserMove = args.BreakOnUserMove, + BreakOnTargetMove = args.BreakOnTargetMove, + Hidden = _glimmer.Glimmer > args.GlimmerDoAfterVisibilityThreshold * args.ModifiedDampening, + }; + + if (!_doAfterSystem.TryStartDoAfter(doAfterArgs, out var doAfterId)) + return; + + component.DoAfter = doAfterId; + } + + private void OnDispelled(EntityUid uid, PsionicComponent component, DispelledEvent args) + { + if (component.DoAfter is null) + return; + + _doAfterSystem.Cancel(component.DoAfter); + component.DoAfter = null; + args.Handled = true; + } + + private void OnDoAfter(EntityUid uid, PsionicComponent component, PsionicHealOtherDoAfterEvent args) + { + // It's entirely possible for the caster to stop being Psionic(due to mindbreaking) mid cast + if (component is null) + return; + component.DoAfter = null; + + // The target can also cease existing mid-cast + // Or the DoAfter is cancelled(such as if the caster moves). + if (args.Target is null + || args.Cancelled) + return; + + if (args.RotReduction is not null) + _rotting.ReduceAccumulator(args.Target.Value, TimeSpan.FromSeconds(args.RotReduction.Value * args.ModifiedAmplification)); + + if (!TryComp(args.Target.Value, out var damageableComponent)) + return; + + if (args.HealingAmount is not null) + _damageable.TryChangeDamage(args.Target.Value, args.HealingAmount * args.ModifiedAmplification, true, false, damageableComponent, uid); + + if (!args.DoRevive + || _rotting.IsRotten(args.Target.Value) + || !TryComp(args.Target.Value, out var mob) + || !_mobState.IsDead(args.Target.Value, mob) + || !_mobThreshold.TryGetThresholdForState(args.Target.Value, MobState.Dead, out var threshold) + || damageableComponent.TotalDamage > threshold) + return; + + _mobState.ChangeMobState(args.Target.Value, MobState.Critical, mob, uid); + } + + // This would be the same thing as OnDoAfter, except that here the target isn't nullable, so I have to reuse code with different arguments. + private void ActivatePower(EntityUid uid, PsionicComponent component, PsionicHealOtherPowerActionEvent args) + { + if (component is null) + return; + + if (args.RotReduction is not null) + _rotting.ReduceAccumulator(args.Target, TimeSpan.FromSeconds(args.RotReduction.Value * args.ModifiedAmplification)); + + if (!TryComp(args.Target, out var damageableComponent)) + return; + + if (args.HealingAmount is not null) + _damageable.TryChangeDamage(args.Target, args.HealingAmount * args.ModifiedAmplification, true, false, damageableComponent, uid); + + if (!args.DoRevive + || _rotting.IsRotten(args.Target) + || !TryComp(args.Target, out var mob) + || !_mobState.IsDead(args.Target, mob) + || !_mobThreshold.TryGetThresholdForState(args.Target, MobState.Dead, out var threshold) + || damageableComponent.TotalDamage > threshold) + return; + + _mobState.ChangeMobState(args.Target, MobState.Critical, mob, uid); + } +} diff --git a/Content.Server/Abilities/Psionics/Abilities/MetapsionicPowerSystem.cs b/Content.Server/Abilities/Psionics/Abilities/MetapsionicPowerSystem.cs index b775117b71..24ef344f63 100644 --- a/Content.Server/Abilities/Psionics/Abilities/MetapsionicPowerSystem.cs +++ b/Content.Server/Abilities/Psionics/Abilities/MetapsionicPowerSystem.cs @@ -1,60 +1,27 @@ -using Content.Shared.Actions; using Content.Shared.Abilities.Psionics; -using Content.Shared.StatusEffect; using Content.Shared.Popups; -using Robust.Shared.Prototypes; -using Robust.Shared.Timing; -using Content.Shared.Mind; using Content.Shared.Actions.Events; namespace Content.Server.Abilities.Psionics { public sealed class MetapsionicPowerSystem : EntitySystem { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly StatusEffectsSystem _statusEffects = default!; - [Dependency] private readonly SharedActionsSystem _actions = default!; [Dependency] private readonly EntityLookupSystem _lookup = default!; [Dependency] private readonly SharedPopupSystem _popups = default!; [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly SharedMindSystem _mindSystem = default!; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnShutdown); SubscribeLocalEvent(OnPowerUsed); } - private void OnInit(EntityUid uid, MetapsionicPowerComponent component, ComponentInit args) - { - _actions.AddAction(uid, ref component.MetapsionicActionEntity, component.MetapsionicActionId ); - _actions.TryGetActionData( component.MetapsionicActionEntity, out var actionData ); - if (actionData is { UseDelay: not null }) - _actions.StartUseDelay(component.MetapsionicActionEntity); - if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) - { - psionic.PsionicAbility = component.MetapsionicActionEntity; - psionic.ActivePowers.Add(component); - } - - } - - private void OnShutdown(EntityUid uid, MetapsionicPowerComponent component, ComponentShutdown args) - { - _actions.RemoveAction(uid, component.MetapsionicActionEntity); - - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Remove(component); - } - } - private void OnPowerUsed(EntityUid uid, MetapsionicPowerComponent component, MetapsionicPowerActionEvent args) { + if (!_psionics.OnAttemptPowerUse(args.Performer, "metapsionic pulse")) + return; + foreach (var entity in _lookup.GetEntitiesInRange(uid, component.Range)) { if (HasComp(entity) && entity != uid && !HasComp(entity) && diff --git a/Content.Server/Abilities/Psionics/Abilities/MindSwapPowerSystem.cs b/Content.Server/Abilities/Psionics/Abilities/MindSwapPowerSystem.cs index b23224cab4..869bf269ab 100644 --- a/Content.Server/Abilities/Psionics/Abilities/MindSwapPowerSystem.cs +++ b/Content.Server/Abilities/Psionics/Abilities/MindSwapPowerSystem.cs @@ -10,8 +10,6 @@ using Content.Server.Popups; using Content.Server.Psionics; using Content.Server.GameTicking; -using Robust.Shared.Prototypes; -using Robust.Shared.Timing; using Content.Shared.Mind; using Content.Shared.Actions.Events; @@ -19,10 +17,8 @@ namespace Content.Server.Abilities.Psionics { public sealed class MindSwapPowerSystem : EntitySystem { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly SharedActionsSystem _actions = default!; [Dependency] private readonly MobStateSystem _mobStateSystem = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; [Dependency] private readonly PopupSystem _popupSystem = default!; [Dependency] private readonly MindSystem _mindSystem = default!; @@ -31,8 +27,6 @@ public sealed class MindSwapPowerSystem : EntitySystem public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnShutdown); SubscribeLocalEvent(OnPowerUsed); SubscribeLocalEvent(OnPowerReturned); SubscribeLocalEvent(OnDispelled); @@ -42,34 +36,10 @@ public override void Initialize() SubscribeLocalEvent(OnSwapInit); } - private void OnInit(EntityUid uid, MindSwapPowerComponent component, ComponentInit args) - { - _actions.AddAction(uid, ref component.MindSwapActionEntity, component.MindSwapActionId ); - _actions.TryGetActionData( component.MindSwapActionEntity, out var actionData ); - if (actionData is { UseDelay: not null }) - _actions.StartUseDelay(component.MindSwapActionEntity); - if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) - { - psionic.PsionicAbility = component.MindSwapActionEntity; - psionic.ActivePowers.Add(component); - } - } - - private void OnShutdown(EntityUid uid, MindSwapPowerComponent component, ComponentShutdown args) - { - _actions.RemoveAction(uid, component.MindSwapActionEntity); - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Remove(component); - } - } - private void OnPowerUsed(MindSwapPowerActionEvent args) { - if (!(TryComp(args.Target, out var damageable) && damageable.DamageContainerID == "Biological")) - return; - - if (HasComp(args.Target)) + if (!_psionics.OnAttemptPowerUse(args.Performer, "mind swap") + || !(TryComp(args.Target, out var damageable) && damageable.DamageContainerID == "Biological")) return; Swap(args.Performer, args.Target); @@ -147,12 +117,10 @@ private void OnGhostAttempt(GhostAttemptHandleEvent args) private void OnSwapInit(EntityUid uid, MindSwappedComponent component, ComponentInit args) { - _actions.AddAction(uid, ref component.MindSwapReturnActionEntity, component.MindSwapReturnActionId ); - _actions.TryGetActionData( component.MindSwapReturnActionEntity, out var actionData ); + _actions.AddAction(uid, ref component.MindSwapReturnActionEntity, component.MindSwapReturnActionId); + _actions.TryGetActionData(component.MindSwapReturnActionEntity, out var actionData); if (actionData is { UseDelay: not null }) _actions.StartUseDelay(component.MindSwapReturnActionEntity); - if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) - psionic.PsionicAbility = component.MindSwapReturnActionEntity; } public void Swap(EntityUid performer, EntityUid target, bool end = false) @@ -165,11 +133,13 @@ public void Swap(EntityUid performer, EntityUid target, bool end = false) MindComponent? targetMind = null; // This is here to prevent missing MindContainerComponent Resolve errors. - if(!_mindSystem.TryGetMind(performer, out var performerMindId, out performerMind)){ + if (!_mindSystem.TryGetMind(performer, out var performerMindId, out performerMind)) + { performerMind = null; }; - if(!_mindSystem.TryGetMind(target, out var targetMindId, out targetMind)){ + if (!_mindSystem.TryGetMind(target, out var targetMindId, out targetMind)) + { targetMind = null; }; //This is a terrible way to 'unattach' minds. I wanted to use UnVisit but in TransferTo's code they say diff --git a/Content.Server/Abilities/Psionics/Abilities/NoosphericZapPowerSystem.cs b/Content.Server/Abilities/Psionics/Abilities/NoosphericZapPowerSystem.cs index 0fd261ef12..22c4f2e500 100644 --- a/Content.Server/Abilities/Psionics/Abilities/NoosphericZapPowerSystem.cs +++ b/Content.Server/Abilities/Psionics/Abilities/NoosphericZapPowerSystem.cs @@ -1,64 +1,28 @@ -using Content.Shared.Actions; using Content.Shared.Abilities.Psionics; -using Content.Server.Psionics; using Content.Shared.StatusEffect; using Content.Server.Stunnable; using Content.Server.Beam; -using Robust.Shared.Prototypes; -using Robust.Shared.Timing; -using Content.Server.Mind; using Content.Shared.Actions.Events; namespace Content.Server.Abilities.Psionics { public sealed class NoosphericZapPowerSystem : EntitySystem { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly SharedActionsSystem _actions = default!; [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; [Dependency] private readonly StunSystem _stunSystem = default!; [Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly BeamSystem _beam = default!; - [Dependency] private readonly MindSystem _mindSystem = default!; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnShutdown); SubscribeLocalEvent(OnPowerUsed); } - private void OnInit(EntityUid uid, NoosphericZapPowerComponent component, ComponentInit args) - { - _actions.AddAction(uid, ref component.NoosphericZapActionEntity, component.NoosphericZapActionId ); - _actions.TryGetActionData( component.NoosphericZapActionEntity, out var actionData ); - if (actionData is { UseDelay: not null }) - _actions.StartUseDelay(component.NoosphericZapActionEntity); - if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) - { - psionic.PsionicAbility = component.NoosphericZapActionEntity; - psionic.ActivePowers.Add(component); - } - } - - private void OnShutdown(EntityUid uid, NoosphericZapPowerComponent component, ComponentShutdown args) - { - _actions.RemoveAction(uid, component.NoosphericZapActionEntity); - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Remove(component); - } - } - private void OnPowerUsed(NoosphericZapPowerActionEvent args) { - if (!HasComp(args.Target)) - return; - - if (HasComp(args.Target)) + if (!_psionics.OnAttemptPowerUse(args.Performer, "noospheric zap")) return; _beam.TryCreateBeam(args.Performer, args.Target, "LightningNoospheric"); diff --git a/Content.Server/Abilities/Psionics/Abilities/PsionicInvisibilityPowerSystem.cs b/Content.Server/Abilities/Psionics/Abilities/PsionicInvisibilityPowerSystem.cs index 5ca1dc7a6d..c6a01912a0 100644 --- a/Content.Server/Abilities/Psionics/Abilities/PsionicInvisibilityPowerSystem.cs +++ b/Content.Server/Abilities/Psionics/Abilities/PsionicInvisibilityPowerSystem.cs @@ -6,11 +6,6 @@ using Content.Shared.Stealth; using Content.Shared.Stealth.Components; using Content.Server.Psionics; -using Robust.Shared.Prototypes; -using Robust.Shared.Player; -using Robust.Shared.Audio; -using Robust.Shared.Timing; -using Content.Server.Mind; using Content.Shared.Actions.Events; using Robust.Shared.Audio.Systems; @@ -18,20 +13,15 @@ namespace Content.Server.Abilities.Psionics { public sealed class PsionicInvisibilityPowerSystem : EntitySystem { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly SharedActionsSystem _actions = default!; [Dependency] private readonly SharedStunSystem _stunSystem = default!; [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; [Dependency] private readonly SharedStealthSystem _stealth = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly MindSystem _mindSystem = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnShutdown); SubscribeLocalEvent(OnPowerUsed); SubscribeLocalEvent(OnPowerOff); SubscribeLocalEvent(OnStart); @@ -39,37 +29,16 @@ public override void Initialize() SubscribeLocalEvent(OnDamageChanged); } - private void OnInit(EntityUid uid, PsionicInvisibilityPowerComponent component, ComponentInit args) - { - _actions.AddAction(uid, ref component.PsionicInvisibilityActionEntity, component.PsionicInvisibilityActionId ); - _actions.TryGetActionData( component.PsionicInvisibilityActionEntity, out var actionData ); - if (actionData is { UseDelay: not null }) - _actions.StartUseDelay(component.PsionicInvisibilityActionEntity); - if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) - { - psionic.PsionicAbility = component.PsionicInvisibilityActionEntity; - psionic.ActivePowers.Add(component); - } - } - - private void OnShutdown(EntityUid uid, PsionicInvisibilityPowerComponent component, ComponentShutdown args) - { - _actions.RemoveAction(uid, component.PsionicInvisibilityActionEntity); - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Remove(component); - } - } - private void OnPowerUsed(EntityUid uid, PsionicInvisibilityPowerComponent component, PsionicInvisibilityPowerActionEvent args) { - if (HasComp(uid)) + if (!_psionics.OnAttemptPowerUse(args.Performer, "psionic invisibility") + || HasComp(uid)) return; ToggleInvisibility(args.Performer); var action = Spawn(PsionicInvisibilityUsedComponent.PsionicInvisibilityUsedActionPrototype); _actions.AddAction(uid, action, action); - _actions.TryGetActionData( action, out var actionData ); + _actions.TryGetActionData(action, out var actionData); if (actionData is { UseDelay: not null }) _actions.StartUseDelay(action); @@ -125,7 +94,8 @@ public void ToggleInvisibility(EntityUid uid) if (!HasComp(uid)) { EnsureComp(uid); - } else + } + else { RemComp(uid); } diff --git a/Content.Server/Abilities/Psionics/Abilities/PsionicRegenerationPowerSystem.cs b/Content.Server/Abilities/Psionics/Abilities/PsionicRegenerationPowerSystem.cs index 097a0cb750..d7ad2d49ab 100644 --- a/Content.Server/Abilities/Psionics/Abilities/PsionicRegenerationPowerSystem.cs +++ b/Content.Server/Abilities/Psionics/Abilities/PsionicRegenerationPowerSystem.cs @@ -1,72 +1,48 @@ using Robust.Shared.Audio; -using Robust.Server.GameObjects; using Robust.Shared.Player; -using Robust.Shared.Prototypes; using Content.Server.Body.Components; using Content.Server.Body.Systems; -using Content.Server.Chemistry.Containers.EntitySystems; -using Content.Server.Chemistry.EntitySystems; using Content.Server.DoAfter; using Content.Shared.Abilities.Psionics; -using Content.Shared.Actions; using Content.Shared.Chemistry.Components; using Content.Shared.DoAfter; using Content.Shared.FixedPoint; using Content.Shared.Popups; using Content.Shared.Psionics.Events; -using Content.Shared.Tag; using Content.Shared.Examine; using static Content.Shared.Examine.ExamineSystemShared; using Robust.Shared.Timing; -using Content.Server.Mind; using Content.Shared.Actions.Events; -using Content.Shared.Chemistry.EntitySystems; using Robust.Server.Audio; namespace Content.Server.Abilities.Psionics { public sealed class PsionicRegenerationPowerSystem : EntitySystem { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly SharedActionsSystem _actions = default!; - [Dependency] private readonly SolutionContainerSystem _solutionSystem = default!; [Dependency] private readonly BloodstreamSystem _bloodstreamSystem = default!; [Dependency] private readonly AudioSystem _audioSystem = default!; - [Dependency] private readonly TagSystem _tagSystem = default!; [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; [Dependency] private readonly SharedPopupSystem _popupSystem = default!; [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly MindSystem _mindSystem = default!; [Dependency] private readonly ExamineSystemShared _examine = default!; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnShutdown); SubscribeLocalEvent(OnPowerUsed); SubscribeLocalEvent(OnDispelled); SubscribeLocalEvent(OnDoAfter); } - private void OnInit(EntityUid uid, PsionicRegenerationPowerComponent component, ComponentInit args) - { - _actions.AddAction(uid, ref component.PsionicRegenerationActionEntity, component.PsionicRegenerationActionId ); - _actions.TryGetActionData( component.PsionicRegenerationActionEntity, out var actionData ); - if (actionData is { UseDelay: not null }) - _actions.StartUseDelay(component.PsionicRegenerationActionEntity); - if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) - { - psionic.PsionicAbility = component.PsionicRegenerationActionEntity; - psionic.ActivePowers.Add(component); - } - } private void OnPowerUsed(EntityUid uid, PsionicRegenerationPowerComponent component, PsionicRegenerationPowerActionEvent args) { + if (!_psionics.OnAttemptPowerUse(args.Performer, "psionic regeneration")) + return; + var ev = new PsionicRegenerationDoAfterEvent(_gameTiming.CurTime); var doAfterArgs = new DoAfterArgs(EntityManager, uid, component.UseDelay, ev, uid); @@ -86,16 +62,6 @@ private void OnPowerUsed(EntityUid uid, PsionicRegenerationPowerComponent compon args.Handled = true; } - private void OnShutdown(EntityUid uid, PsionicRegenerationPowerComponent component, ComponentShutdown args) - { - _actions.RemoveAction(uid, component.PsionicRegenerationActionEntity); - - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Remove(component); - } - } - private void OnDispelled(EntityUid uid, PsionicRegenerationPowerComponent component, DispelledEvent args) { if (component.DoAfter == null) diff --git a/Content.Server/Abilities/Psionics/Abilities/PyrokinesisPowerSystem.cs b/Content.Server/Abilities/Psionics/Abilities/PyrokinesisPowerSystem.cs index 407b72c6b5..4a75083602 100644 --- a/Content.Server/Abilities/Psionics/Abilities/PyrokinesisPowerSystem.cs +++ b/Content.Server/Abilities/Psionics/Abilities/PyrokinesisPowerSystem.cs @@ -1,57 +1,27 @@ -using Content.Shared.Actions; using Content.Shared.Abilities.Psionics; using Content.Server.Atmos.Components; using Content.Server.Atmos.EntitySystems; using Content.Server.Popups; -using Robust.Shared.Prototypes; -using Robust.Shared.Timing; -using Content.Server.Mind; using Content.Shared.Actions.Events; namespace Content.Server.Abilities.Psionics { public sealed class PyrokinesisPowerSystem : EntitySystem { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly SharedActionsSystem _actions = default!; [Dependency] private readonly FlammableSystem _flammableSystem = default!; [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; [Dependency] private readonly PopupSystem _popupSystem = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly MindSystem _mindSystem = default!; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnShutdown); SubscribeLocalEvent(OnPowerUsed); } - - private void OnInit(EntityUid uid, PyrokinesisPowerComponent component, ComponentInit args) - { - _actions.AddAction(uid, ref component.PyrokinesisActionEntity, component.PyrokinesisActionId ); - _actions.TryGetActionData( component.PyrokinesisActionEntity, out var actionData ); - if (actionData is { UseDelay: not null }) - _actions.StartUseDelay(component.PyrokinesisActionEntity); - if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) - { - psionic.PsionicAbility = component.PyrokinesisActionEntity; - psionic.ActivePowers.Add(component); - } - } - - private void OnShutdown(EntityUid uid, PyrokinesisPowerComponent component, ComponentShutdown args) - { - _actions.RemoveAction(uid, component.PyrokinesisActionEntity); - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Remove(component); - } - } - private void OnPowerUsed(PyrokinesisPowerActionEvent args) { + if (!_psionics.OnAttemptPowerUse(args.Performer, "pyrokinesis")) + return; + if (!TryComp(args.Target, out var flammableComponent)) return; diff --git a/Content.Server/Abilities/Psionics/Abilities/TelegnosisPowerSystem.cs b/Content.Server/Abilities/Psionics/Abilities/TelegnosisPowerSystem.cs index f7ae04b61e..abbbdfacc5 100644 --- a/Content.Server/Abilities/Psionics/Abilities/TelegnosisPowerSystem.cs +++ b/Content.Server/Abilities/Psionics/Abilities/TelegnosisPowerSystem.cs @@ -1,57 +1,26 @@ -using Content.Shared.Actions; -using Content.Shared.StatusEffect; using Content.Shared.Abilities.Psionics; using Content.Shared.Mind.Components; -using Robust.Shared.Prototypes; -using Robust.Shared.Timing; -using Content.Server.Mind; using Content.Shared.Actions.Events; namespace Content.Server.Abilities.Psionics { public sealed class TelegnosisPowerSystem : EntitySystem { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly StatusEffectsSystem _statusEffects = default!; - [Dependency] private readonly SharedActionsSystem _actions = default!; [Dependency] private readonly MindSwapPowerSystem _mindSwap = default!; [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly MindSystem _mindSystem = default!; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnShutdown); SubscribeLocalEvent(OnPowerUsed); SubscribeLocalEvent(OnMindRemoved); } - private void OnInit(EntityUid uid, TelegnosisPowerComponent component, ComponentInit args) - { - _actions.AddAction(uid, ref component.TelegnosisActionEntity, component.TelegnosisActionId ); - _actions.TryGetActionData( component.TelegnosisActionEntity, out var actionData ); - if (actionData is { UseDelay: not null }) - _actions.StartUseDelay(component.TelegnosisActionEntity); - if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) - { - psionic.PsionicAbility = component.TelegnosisActionEntity; - psionic.ActivePowers.Add(component); - } - } - - private void OnShutdown(EntityUid uid, TelegnosisPowerComponent component, ComponentShutdown args) - { - _actions.RemoveAction(uid, component.TelegnosisActionEntity); - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Remove(component); - } - } - private void OnPowerUsed(EntityUid uid, TelegnosisPowerComponent component, TelegnosisPowerActionEvent args) { + if (!_psionics.OnAttemptPowerUse(args.Performer, "telegnosis")) + return; + var projection = Spawn(component.Prototype, Transform(uid).Coordinates); Transform(projection).AttachToGridOrMap(); _mindSwap.Swap(uid, projection); diff --git a/Content.Server/Abilities/Psionics/AnomalyPowerSystem.cs b/Content.Server/Abilities/Psionics/AnomalyPowerSystem.cs new file mode 100644 index 0000000000..ff9910c400 --- /dev/null +++ b/Content.Server/Abilities/Psionics/AnomalyPowerSystem.cs @@ -0,0 +1,119 @@ +using Content.Shared.Abilities.Psionics; +using Content.Shared.Actions.Events; +using Content.Shared.Psionics.Glimmer; +using Robust.Shared.Random; +using Content.Shared.Anomaly; +using Robust.Shared.Audio.Systems; +using Content.Shared.Actions; +using Content.Shared.Damage; +using Content.Server.Popups; +using Content.Shared.Administration.Logs; +using Content.Server.Lightning; +using Content.Server.Emp; +using Content.Server.Explosion.EntitySystems; +using Content.Server.Atmos.EntitySystems; +using Content.Shared.Throwing; +using Content.Server.Chemistry.Containers.EntitySystems; +using Content.Server.Fluids.EntitySystems; + +namespace Content.Server.Abilities.Psionics; + +public sealed partial class AnomalyPowerSystem : EntitySystem +{ + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly GlimmerSystem _glimmerSystem = default!; + [Dependency] private readonly SharedAnomalySystem _anomalySystem = default!; + [Dependency] private readonly SharedMapSystem _mapSystem = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; + [Dependency] private readonly SharedActionsSystem _actions = default!; + [Dependency] private readonly DamageableSystem _damageable = default!; + [Dependency] private readonly PopupSystem _popup = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly SharedTransformSystem _xform = default!; + [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; + [Dependency] private readonly LightningSystem _lightning = default!; + [Dependency] private readonly EmpSystem _emp = default!; + [Dependency] private readonly ExplosionSystem _boom = default!; + [Dependency] private readonly AtmosphereSystem _atmosphere = default!; + [Dependency] private readonly ThrowingSystem _throwing = default!; + [Dependency] private readonly SolutionContainerSystem _solutionContainer = default!; + [Dependency] private readonly PuddleSystem _puddle = default!; + [Dependency] private readonly FlammableSystem _flammable = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnPowerUsed); + } + + private void OnPowerUsed(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args) + { + if (!_psionics.OnAttemptPowerUse(args.Performer, args.Settings.PowerName, args.Settings.ManaCost, args.Settings.CheckInsulation)) + return; + + var overcharged = args.Settings.DoSupercritical ? _glimmerSystem.Glimmer * component.CurrentAmplification + > Math.Min(args.Settings.SupercriticalThreshold * component.CurrentDampening, args.Settings.MaxSupercriticalThreshold) + : false; + + // Behold the wall of nullable logic gates. + DoBluespaceAnomalyEffects(uid, component, args, overcharged); + DoElectricityAnomalyEffects(uid, component, args, overcharged); + DoEntityAnomalyEffects(uid, component, args, overcharged); + DoExplosionAnomalyEffects(uid, component, args, overcharged); + DoGasProducerAnomalyEffects(uid, component, args, overcharged); + DoGravityAnomalyEffects(uid, component, args, overcharged); + DoInjectionAnomalyEffects(uid, component, args, overcharged); + DoPuddleAnomalyEffects(uid, component, args, overcharged); + DoPyroclasticAnomalyEffects(uid, component, args, overcharged); + DoAnomalySounds(uid, component, args, overcharged); + DoGlimmerEffects(uid, component, args, overcharged); + + if (overcharged) + DoOverchargedEffects(uid, component, args); + + args.Handled = true; + } + + public void DoAnomalySounds(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args, bool overcharged = false) + { + if (overcharged && args.Settings.SupercriticalSound is not null) + { + _audio.PlayPvs(args.Settings.SupercriticalSound, uid); + return; + } + + if (args.Settings.PulseSound is null + || _glimmerSystem.Glimmer < args.Settings.GlimmerSoundThreshold * component.CurrentDampening) + return; + + _audio.PlayEntity(args.Settings.PulseSound, uid, uid); + } + + public void DoGlimmerEffects(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args, bool overcharged = false) + { + var minGlimmer = (int) Math.Round(MathF.MinMagnitude(args.Settings.MinGlimmer, args.Settings.MaxGlimmer) + * (overcharged ? args.Settings.SupercriticalGlimmerMultiplier : 1) + * component.CurrentAmplification - component.CurrentDampening); + var maxGlimmer = (int) Math.Round(MathF.MaxMagnitude(args.Settings.MinGlimmer, args.Settings.MaxGlimmer) + * (overcharged ? args.Settings.SupercriticalGlimmerMultiplier : 1) + * component.CurrentAmplification - component.CurrentDampening); + + _psionics.LogPowerUsed(uid, args.Settings.PowerName, minGlimmer, maxGlimmer); + } + + public void DoOverchargedEffects(EntityUid uid, PsionicComponent component, AnomalyPowerActionEvent args) + { + if (args.Settings.OverchargeFeedback is not null + && Loc.TryGetString(args.Settings.OverchargeFeedback, out var popup)) + _popup.PopupEntity(popup, uid, uid); + + if (args.Settings.OverchargeRecoil is not null + && TryComp(uid, out var damageable)) + _damageable.TryChangeDamage(uid, args.Settings.OverchargeRecoil / component.CurrentDampening, true, true, damageable, uid); + + if (args.Settings.OverchargeCooldown > 0) + foreach (var action in component.Actions) + _actions.SetCooldown(action.Value, TimeSpan.FromSeconds(args.Settings.OverchargeCooldown / component.CurrentDampening)); + } +} diff --git a/Content.Server/Abilities/Psionics/InnatePsionicPowersComponent.cs b/Content.Server/Abilities/Psionics/InnatePsionicPowersComponent.cs new file mode 100644 index 0000000000..fe9c7511cb --- /dev/null +++ b/Content.Server/Abilities/Psionics/InnatePsionicPowersComponent.cs @@ -0,0 +1,15 @@ +using Content.Shared.Psionics; +using Robust.Shared.Prototypes; + +namespace Content.Server.Abilities.Psionics +{ + [RegisterComponent] + public sealed partial class InnatePsionicPowersComponent : Component + { + /// + /// The list of all powers to be added on Startup + /// + [DataField] + public List> PowersToAdd = new(); + } +} diff --git a/Content.Server/Abilities/Psionics/PsionicAbilitiesSystem.cs b/Content.Server/Abilities/Psionics/PsionicAbilitiesSystem.cs index e59696aa90..bdf295615e 100644 --- a/Content.Server/Abilities/Psionics/PsionicAbilitiesSystem.cs +++ b/Content.Server/Abilities/Psionics/PsionicAbilitiesSystem.cs @@ -1,15 +1,17 @@ using Content.Shared.Abilities.Psionics; using Content.Shared.Actions; +using Content.Shared.Popups; using Content.Shared.Psionics.Glimmer; using Content.Shared.Random; using Content.Shared.Random.Helpers; -using Content.Server.EUI; -using Content.Server.Psionics; -using Content.Server.Mind; using Content.Shared.StatusEffect; using Robust.Shared.Random; using Robust.Shared.Prototypes; -using Robust.Shared.Player; +using Robust.Shared.Serialization.Manager; +using Content.Shared.Psionics; +using System.Linq; +using Robust.Server.Player; +using Content.Server.Chat.Managers; namespace Content.Server.Abilities.Psionics { @@ -18,110 +20,370 @@ public sealed class PsionicAbilitiesSystem : EntitySystem [Dependency] private readonly IComponentFactory _componentFactory = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly SharedActionsSystem _actionsSystem = default!; - [Dependency] private readonly EuiManager _euiManager = default!; [Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!; [Dependency] private readonly GlimmerSystem _glimmerSystem = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly MindSystem _mindSystem = default!; + [Dependency] private readonly SharedActionsSystem _actions = default!; + [Dependency] private readonly SharedPopupSystem _popups = default!; + [Dependency] private readonly ISerializationManager _serialization = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IChatManager _chatManager = default!; + [Dependency] private readonly PsionicFamiliarSystem _psionicFamiliar = default!; - private ISawmill _sawmill = default!; + private ProtoId _pool = "RandomPsionicPowerPool"; + private const string GenericInitializationMessage = "generic-power-initialization-feedback"; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnPlayerAttached); + SubscribeLocalEvent(InnatePowerStartup); + SubscribeLocalEvent(OnPsionicShutdown); } - private void OnPlayerAttached(EntityUid uid, PsionicAwaitingPlayerComponent component, PlayerAttachedEvent args) + /// + /// Special use-case for a InnatePsionicPowers, which allows an entity to start with any number of Psionic Powers. + /// + private void InnatePowerStartup(EntityUid uid, InnatePsionicPowersComponent comp, MapInitEvent args) { - if (TryComp(uid, out var bonus) && bonus.Warn == true) - _euiManager.OpenEui(new AcceptPsionicsEui(uid, this), args.Player); - else - AddRandomPsionicPower(uid); - RemCompDeferred(uid); + // Any entity with InnatePowers should also be psionic, but in case they aren't already... + EnsureComp(uid, out var psionic); + + foreach (var proto in comp.PowersToAdd) + if (!psionic.ActivePowers.Contains(_prototypeManager.Index(proto))) + InitializePsionicPower(uid, _prototypeManager.Index(proto), psionic, false); } - public void AddPsionics(EntityUid uid, bool warn = true) + private void OnPsionicShutdown(EntityUid uid, PsionicComponent component, ComponentShutdown args) { - if (Deleted(uid) - || HasComp(uid)) + if (!EntityManager.EntityExists(uid) + || HasComp(uid)) return; - if (!_mindSystem.TryGetMind(uid, out _, out var mind)) - { - EnsureComp(uid); + KillFamiliars(component); + RemoveAllPsionicPowers(uid); + } + + /// + /// The most shorthand route to creating a Psion. If an entity is not already psionic, it becomes one. This also adds a random new PsionicPower. + /// To create a "Latent Psychic"(Psion with no powers) just add or ensure the PsionicComponent normally. + /// + public void AddPsionics(EntityUid uid) + { + if (Deleted(uid)) return; - } - if (!_mindSystem.TryGetSession(mind, out var client)) + AddRandomPsionicPower(uid); + } + + /// + /// Pretty straightforward, adds a random psionic power to a given Entity. If that Entity is not already Psychic, it will be made one. + /// If an entity already has all possible powers, this will not add any new ones. + /// + public void AddRandomPsionicPower(EntityUid uid) + { + // We need to EnsureComp here to make sure that we aren't iterating over a component that: + // A: Isn't fully initialized + // B: Is in the process of being shutdown/deleted + // Imagine my surprise when I found out Resolve doesn't check for that. + // TODO: This EnsureComp will be 1984'd in a separate PR, when I rework how you get psionics in the first place. + EnsureComp(uid, out var psionic); + + if (!_prototypeManager.TryIndex(_pool.Id, out var pool)) return; - if (warn && HasComp(uid)) - _euiManager.OpenEui(new AcceptPsionicsEui(uid, this), client); - else - AddRandomPsionicPower(uid); + var newPool = pool.Weights.Keys.ToList(); + newPool.RemoveAll(s => + _prototypeManager.TryIndex(s, out var p) && + psionic.ActivePowers.Contains(p)); + + if (newPool.Count == 0) + return; + + var newProto = _random.Pick(newPool); + if (!_prototypeManager.TryIndex(newProto, out var newPower)) + return; + + InitializePsionicPower(uid, newPower); + + _glimmerSystem.Glimmer += _random.Next(1, (int) Math.Round(1 + psionic.CurrentAmplification + psionic.CurrentDampening)); } - public void AddPsionics(EntityUid uid, string powerComp) + /// + /// Initializes a new Psionic Power on a given entity, assuming the entity does not already have said power initialized. + /// + public void InitializePsionicPower(EntityUid uid, PsionicPowerPrototype proto, PsionicComponent psionic, bool playFeedback = true) { - if (Deleted(uid) - || HasComp(uid)) + if (!_prototypeManager.HasIndex(proto.ID) + || psionic.ActivePowers.Contains(proto)) return; - AddComp(uid); + psionic.ActivePowers.Add(proto); - var newComponent = (Component) _componentFactory.GetComponent(powerComp); - newComponent.Owner = uid; + AddPsionicActions(uid, proto, psionic); + AddPsionicPowerComponents(uid, proto); + AddPsionicStatSources(proto, psionic); + RefreshPsionicModifiers(uid, psionic); + SendFeedbackMessage(uid, proto, playFeedback); + UpdatePowerSlots(psionic); + //UpdatePsionicDanger(uid, psionic); // TODO: After Glimmer Refactor + //SendFeedbackAudio(uid, proto, playPopup); // TODO: This one is coming next! + } - EntityManager.AddComponent(uid, newComponent); + /// + /// Initializes a new Psionic Power on a given entity, assuming the entity does not already have said power initialized. + /// + public void InitializePsionicPower(EntityUid uid, PsionicPowerPrototype proto, bool playFeedback = true) + { + EnsureComp(uid, out var psionic); + + InitializePsionicPower(uid, proto, psionic, playFeedback); } - public void AddRandomPsionicPower(EntityUid uid) + /// + /// Updates a Psion's casting stats, call this anytime a system adds a new source of Amp or Damp. + /// + public void RefreshPsionicModifiers(EntityUid uid, PsionicComponent comp) { - AddComp(uid); + var ampModifier = 0f; + var dampModifier = 0f; + foreach (var (_, source) in comp.AmplificationSources) + ampModifier += source; + foreach (var (_, source) in comp.DampeningSources) + dampModifier += source; - if (!_prototypeManager.TryIndex("RandomPsionicPowerPool", out var pool)) - { - _sawmill.Error("Can't index the random psionic power pool!"); - return; - } + var ev = new OnSetPsionicStatsEvent(ampModifier, dampModifier); + RaiseLocalEvent(uid, ref ev); + ampModifier = ev.AmplificationChangedAmount; + dampModifier = ev.DampeningChangedAmount; + + comp.CurrentAmplification = ampModifier; + comp.CurrentDampening = dampModifier; + } - // uh oh, stinky! - var newComponent = (Component) _componentFactory.GetComponent(pool.Pick()); - newComponent.Owner = uid; + /// + /// Updates a Psion's casting stats, call this anytime a system adds a new source of Amp or Damp. + /// Variant function for systems that didn't already have the PsionicComponent. + /// + public void RefreshPsionicModifiers(EntityUid uid) + { + if (!TryComp(uid, out var comp)) + return; - EntityManager.AddComponent(uid, newComponent); + RefreshPsionicModifiers(uid, comp); + } - _glimmerSystem.Glimmer += _random.Next(1, 5); + /// + /// A more advanced form of removing powers. Mindbreaking not only removes all psionic powers, + /// it also disables the possibility of obtaining new ones. + /// + public void MindBreak(EntityUid uid) + { + RemoveAllPsionicPowers(uid, true); } - public void RemovePsionics(EntityUid uid) + /// + /// Remove all Psionic powers, with accompanying actions, components, and casting stat sources, from a given Psion. + /// Optionally, the Psion can also be rendered permanently non-Psionic. + /// + public void RemoveAllPsionicPowers(EntityUid uid, bool mindbreak = false) { if (!TryComp(uid, out var psionic) || !psionic.Removable) return; - if (!_prototypeManager.TryIndex("RandomPsionicPowerPool", out var pool)) + RemovePsionicActions(uid, psionic); + + var newPsionic = psionic.ActivePowers.ToList(); + foreach (var proto in newPsionic) + { + if (!_prototypeManager.TryIndex(proto.ID, out var power)) + continue; + + RemovePsionicPowerComponents(uid, proto); + + // If we're mindbreaking, we can skip the casting stats since the PsionicComponent is getting 1984'd. + if (!mindbreak) + RemovePsionicStatSources(uid, power, psionic); + } + + if (mindbreak) + { + EnsureComp(uid); + _statusEffectsSystem.TryAddStatusEffect(uid, psionic.MindbreakingStutterCondition, + TimeSpan.FromMinutes(psionic.MindbreakingStutterTime * psionic.CurrentAmplification * psionic.CurrentDampening), + false, + psionic.MindbreakingStutterAccent); + + _popups.PopupEntity(Loc.GetString(psionic.MindbreakingFeedback, ("entity", MetaData(uid).EntityName)), uid, uid, PopupType.MediumCaution); + + KillFamiliars(psionic); + RemComp(uid); + RemComp(uid); + + var ev = new OnMindbreakEvent(); + RaiseLocalEvent(uid, ref ev); + + return; + } + RefreshPsionicModifiers(uid, psionic); + } + + /// + /// Add all actions associated with a specific Psionic Power + /// + private void AddPsionicActions(EntityUid uid, PsionicPowerPrototype proto, PsionicComponent psionic) + { + foreach (var id in proto.Actions) { - _sawmill.Error("Can't index the random psionic power pool!"); + EntityUid? actionId = null; + if (_actions.AddAction(uid, ref actionId, id)) + { + _actions.StartUseDelay(actionId); + psionic.Actions.Add(id, actionId); + } + } + } + + /// + /// Add all components associated with a specific Psionic power. + /// + private void AddPsionicPowerComponents(EntityUid uid, PsionicPowerPrototype proto) + { + if (proto.Components is null) return; + + foreach (var entry in proto.Components.Values) + { + if (HasComp(uid, entry.Component.GetType())) + continue; + + var comp = (Component) _serialization.CreateCopy(entry.Component, notNullableOverride: true); + comp.Owner = uid; + EntityManager.AddComponent(uid, comp); } + } + + /// + /// Update the Amplification and Dampening sources of a Psion to include a new Power. + /// + private void AddPsionicStatSources(PsionicPowerPrototype proto, PsionicComponent psionic) + { + if (proto.AmplificationModifier != 0) + psionic.AmplificationSources.Add(proto.Name, proto.AmplificationModifier); + + if (proto.DampeningModifier != 0) + psionic.DampeningSources.Add(proto.Name, proto.DampeningModifier); + } + + /// + /// Displays a message to alert the player when they have obtained a new psionic power. These generally will not play for Innate powers. + /// Chat messages of this nature should be written in the first-person. + /// Popup feedback should be no more than a sentence, while the full Initialization Feedback can be as much as a paragraph of text. + /// + private void SendFeedbackMessage(EntityUid uid, PsionicPowerPrototype proto, bool playFeedback = true) + { + if (!playFeedback + || !_playerManager.TryGetSessionByEntity(uid, out var session) + || session is null) + return; + + if (proto.InitializationPopup is null) + _popups.PopupEntity(Loc.GetString(GenericInitializationMessage), uid, uid, PopupType.MediumCaution); + else _popups.PopupEntity(Loc.GetString(proto.InitializationPopup), uid, uid, PopupType.MediumCaution); + + if (proto.InitializationFeedback is null) + return; + + if (!Loc.TryGetString(proto.InitializationFeedback, out var feedback)) + return; + var feedbackMessage = $"[font size={proto.InitializationFeedbackFontSize}][color={proto.InitializationFeedbackColor}]{feedback}[/color][/font]"; + _chatManager.ChatMessageToOne( + proto.InitializationFeedbackChannel, + feedbackMessage, + feedbackMessage, + EntityUid.Invalid, + false, + session.Channel); + } + + private void UpdatePowerSlots(PsionicComponent psionic) + { + var slotsUsed = 0; + foreach (var power in psionic.ActivePowers) + slotsUsed += power.PowerSlotCost; - foreach (var compName in pool.Weights.Keys) + psionic.PowerSlotsTaken = slotsUsed; + } + + /// + /// Psions over a certain power threshold become a glimmer source. This cannot be fully implemented until after I rework Glimmer + /// + //private void UpdatePsionicDanger(EntityUid uid, PsionicComponent psionic) + //{ + // if (psionic.PowerSlotsTaken <= psionic.PowerSlots) + // return; + // + // EnsureComp(uid, out var glimmerSource); + // glimmerSource.SecondsPerGlimmer = 10 / (psionic.PowerSlotsTaken - psionic.PowerSlots); + //} + + /// + /// Remove all Psychic Actions listed in an entity's Psionic Component. Unfortunately, removing actions associated with a specific Power Prototype is not supported. + /// + private void RemovePsionicActions(EntityUid uid, PsionicComponent psionic) + { + if (psionic.Actions is null) + return; + + foreach (var action in psionic.Actions) + _actionsSystem.RemoveAction(uid, action.Value); + } + + /// + /// Remove all Components associated with a specific Psionic Power. + /// + private void RemovePsionicPowerComponents(EntityUid uid, PsionicPowerPrototype proto) + { + if (proto.Components is null) + return; + + foreach (var comp in proto.Components) { - // component moment - var comp = _componentFactory.GetComponent(compName); - if (EntityManager.TryGetComponent(uid, comp.GetType(), out var psionicPower)) - RemComp(uid, psionicPower); + var powerComp = (Component) _componentFactory.GetComponent(comp.Key); + if (!EntityManager.HasComponent(uid, powerComp.GetType())) + continue; + + EntityManager.RemoveComponent(uid, powerComp.GetType()); } - if (psionic.PsionicAbility != null - && _actionsSystem.TryGetActionData(psionic.PsionicAbility, out var psiAbility) - && psiAbility is not null) - _actionsSystem.RemoveAction(uid, psionic.PsionicAbility); + } + + /// + /// Remove all stat sources associated with a specific Psionic Power. + /// + private void RemovePsionicStatSources(EntityUid uid, PsionicPowerPrototype proto, PsionicComponent psionic) + { + if (proto.AmplificationModifier != 0) + psionic.AmplificationSources.Remove(proto.Name); - _statusEffectsSystem.TryAddStatusEffect(uid, "Stutter", TimeSpan.FromMinutes(5), false, "StutteringAccent"); + if (proto.DampeningModifier != 0) + psionic.DampeningSources.Remove(proto.Name); - RemComp(uid); + RefreshPsionicModifiers(uid, psionic); + } + + private void KillFamiliars(PsionicComponent component) + { + if (component.Familiars.Count <= 0) + return; + + foreach (var familiar in component.Familiars) + { + if (!TryComp(familiar, out var familiarComponent) + || !familiarComponent.DespawnOnMasterDeath) + continue; + + _psionicFamiliar.DespawnFamiliar(familiar, familiarComponent); + } } } } diff --git a/Content.Server/Abilities/Psionics/PsionicFamiliarSystem.cs b/Content.Server/Abilities/Psionics/PsionicFamiliarSystem.cs new file mode 100644 index 0000000000..d382c1f231 --- /dev/null +++ b/Content.Server/Abilities/Psionics/PsionicFamiliarSystem.cs @@ -0,0 +1,140 @@ +using Content.Server.NPC; +using Content.Server.NPC.Components; +using Content.Server.NPC.HTN; +using Content.Server.NPC.Systems; +using Content.Server.Popups; +using Content.Shared.Abilities.Psionics; +using Content.Shared.Actions.Events; +using Content.Shared.Interaction.Events; +using Content.Shared.Mobs; +using Robust.Shared.Map; +using System.Numerics; + +namespace Content.Server.Abilities.Psionics; + +public sealed partial class PsionicFamiliarSystem : EntitySystem +{ + [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; + [Dependency] private readonly NpcFactionSystem _factions = default!; + [Dependency] private readonly NPCSystem _npc = default!; + [Dependency] private readonly HTNSystem _htn = default!; + [Dependency] private readonly PopupSystem _popup = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnSummon); + SubscribeLocalEvent(OnFamiliarShutdown); + SubscribeLocalEvent(OnFamiliarAttack); + SubscribeLocalEvent(OnFamiliarDeath); + } + + private void OnSummon(EntityUid uid, PsionicComponent psionicComponent, SummonPsionicFamiliarActionEvent args) + { + if (psionicComponent.Familiars.Count >= psionicComponent.FamiliarLimit + || !_psionics.OnAttemptPowerUse(args.Performer, args.PowerName, args.ManaCost, args.CheckInsulation) + || args.Handled || args.FamiliarProto is null) + return; + + args.Handled = true; + var familiar = Spawn(args.FamiliarProto, Transform(uid).Coordinates); + EnsureComp(familiar, out var familiarComponent); + familiarComponent.Master = uid; + psionicComponent.Familiars.Add(familiar); + Dirty(familiar, familiarComponent); + Dirty(uid, psionicComponent); + + InheritFactions(uid, familiar, familiarComponent); + HandleBlackboards(uid, familiar, args); + DoGlimmerEffects(uid, psionicComponent, args); + } + + private void InheritFactions(EntityUid uid, EntityUid familiar, PsionicFamiliarComponent familiarComponent) + { + if (!familiarComponent.InheritMasterFactions + || !TryComp(uid, out var masterFactions) + || masterFactions.Factions.Count <= 0) + return; + + EnsureComp(familiar, out var familiarFactions); + foreach (var faction in masterFactions.Factions) + { + if (familiarFactions.Factions.Contains(faction)) + continue; + + _factions.AddFaction(familiar, faction, true); + } + } + + private void HandleBlackboards(EntityUid master, EntityUid familiar, SummonPsionicFamiliarActionEvent args) + { + if (!args.FollowMaster + || !TryComp(familiar, out var htnComponent)) + return; + + _npc.SetBlackboard(familiar, NPCBlackboard.FollowTarget, new EntityCoordinates(master, Vector2.Zero), htnComponent); + _htn.Replan(htnComponent); + } + + private void DoGlimmerEffects(EntityUid uid, PsionicComponent component, SummonPsionicFamiliarActionEvent args) + { + if (!args.DoGlimmerEffects + || args.MinGlimmer == 0 && args.MaxGlimmer == 0) + return; + + var minGlimmer = (int) Math.Round(MathF.MinMagnitude(args.MinGlimmer, args.MaxGlimmer) + * component.CurrentAmplification - component.CurrentDampening); + var maxGlimmer = (int) Math.Round(MathF.MaxMagnitude(args.MinGlimmer, args.MaxGlimmer) + * component.CurrentAmplification - component.CurrentDampening); + + _psionics.LogPowerUsed(uid, args.PowerName, minGlimmer, maxGlimmer); + } + + private void OnFamiliarShutdown(EntityUid uid, PsionicFamiliarComponent component, ComponentShutdown args) + { + if (!Exists(component.Master) + || !TryComp(component.Master, out var psionicComponent) + || !psionicComponent.Familiars.Contains(uid)) + return; + + psionicComponent.Familiars.Remove(uid); + } + + private void OnFamiliarAttack(EntityUid uid, PsionicFamiliarComponent component, AttackAttemptEvent args) + { + if (component.CanAttackMaster || args.Target is null + || args.Target != component.Master) + return; + + args.Cancel(); + if (!Loc.TryGetString(component.AttackMasterText, out var attackFailMessage)) + return; + + _popup.PopupEntity(attackFailMessage, uid, uid, component.AttackPopupType); + } + + private void OnFamiliarDeath(EntityUid uid, PsionicFamiliarComponent component, MobStateChangedEvent args) + { + if (!component.DespawnOnFamiliarDeath + || args.NewMobState != MobState.Dead) + return; + + DespawnFamiliar(uid, component); + } + + public void DespawnFamiliar(EntityUid uid) + { + if (!TryComp(uid, out var familiarComponent)) + return; + + DespawnFamiliar(uid, familiarComponent); + } + + public void DespawnFamiliar(EntityUid uid, PsionicFamiliarComponent component) + { + var popupText = Loc.GetString(component.DespawnText, ("entity", MetaData(uid).EntityName)); + _popup.PopupEntity(popupText, uid, component.DespawnPopopType); + QueueDel(uid); + } +} diff --git a/Content.Server/Access/Components/IdExaminableComponent.cs b/Content.Server/Access/Components/IdExaminableComponent.cs deleted file mode 100644 index 2def517f40..0000000000 --- a/Content.Server/Access/Components/IdExaminableComponent.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Content.Server.Access.Systems; - -namespace Content.Server.Access.Components; - -[RegisterComponent, Access(typeof(IdExaminableSystem))] -public sealed partial class IdExaminableComponent : Component -{ -} diff --git a/Content.Server/Access/Systems/AccessOverriderSystem.cs b/Content.Server/Access/Systems/AccessOverriderSystem.cs index d0b8c31ab6..4e7b796503 100644 --- a/Content.Server/Access/Systems/AccessOverriderSystem.cs +++ b/Content.Server/Access/Systems/AccessOverriderSystem.cs @@ -28,7 +28,6 @@ public sealed class AccessOverriderSystem : SharedAccessOverriderSystem [Dependency] private readonly PopupSystem _popupSystem = default!; [Dependency] private readonly SharedAudioSystem _audioSystem = default!; [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; - [Dependency] private readonly SharedContainerSystem _containerSystem = default!; public override void Initialize() { @@ -69,16 +68,13 @@ private void AfterInteractOn(EntityUid uid, AccessOverriderComponent component, private void OnDoAfter(EntityUid uid, AccessOverriderComponent component, AccessOverriderDoAfterEvent args) { - if (!TryComp(args.User, out ActorComponent? actor)) - return; - if (args.Handled || args.Cancelled) return; if (args.Args.Target != null) { component.TargetAccessReaderId = args.Args.Target.Value; - _userInterface.TryOpen(uid, AccessOverriderUiKey.Key, actor.PlayerSession); + _userInterface.OpenUi(uid, AccessOverriderUiKey.Key, args.User); UpdateUserInterface(uid, component, args); } @@ -95,7 +91,7 @@ private void OnClose(EntityUid uid, AccessOverriderComponent component, BoundUIC private void OnWriteToTargetAccessReaderIdMessage(EntityUid uid, AccessOverriderComponent component, WriteToTargetAccessReaderIdMessage args) { - if (args.Session.AttachedEntity is not { Valid: true } player) + if (args.Actor is not { Valid: true } player) return; TryWriteToTargetAccessReaderId(uid, args.AccessList, player, component); @@ -155,22 +151,19 @@ private void UpdateUserInterface(EntityUid uid, AccessOverriderComponent compone targetLabel, targetLabelColor); - _userInterface.TrySetUiState(uid, AccessOverriderUiKey.Key, newState); + _userInterface.SetUiState(uid, AccessOverriderUiKey.Key, newState); } private List> ConvertAccessHashSetsToList(List>> accessHashsets) { - List> accessList = new List>(); + var accessList = new List>(); + + if (accessHashsets.Count <= 0) + return accessList; - if (accessHashsets != null && accessHashsets.Any()) + foreach (var hashSet in accessHashsets) { - foreach (HashSet> hashSet in accessHashsets) - { - foreach (ProtoId hash in hashSet.ToArray()) - { - accessList.Add(hash); - } - } + accessList.AddRange(hashSet); } return accessList; diff --git a/Content.Server/Access/Systems/AgentIDCardSystem.cs b/Content.Server/Access/Systems/AgentIDCardSystem.cs index bd4d3b3f23..d5e9dc357d 100644 --- a/Content.Server/Access/Systems/AgentIDCardSystem.cs +++ b/Content.Server/Access/Systems/AgentIDCardSystem.cs @@ -61,14 +61,14 @@ private void OnAfterInteract(EntityUid uid, AgentIDCardComponent component, Afte private void AfterUIOpen(EntityUid uid, AgentIDCardComponent component, AfterActivatableUIOpenEvent args) { - if (!_uiSystem.TryGetUi(uid, AgentIDCardUiKey.Key, out var ui)) + if (!_uiSystem.HasUi(uid, AgentIDCardUiKey.Key)) return; if (!TryComp(uid, out var idCard)) return; - var state = new AgentIDCardBoundUserInterfaceState(idCard.FullName ?? "", idCard.JobTitle ?? "", component.Icons); - _uiSystem.SetUiState(ui, state, args.Session); + var state = new AgentIDCardBoundUserInterfaceState(idCard.FullName ?? "", idCard.JobTitle ?? "", idCard.JobIcon ?? "", component.Icons); + _uiSystem.SetUiState(uid, AgentIDCardUiKey.Key, state); } private void OnJobChanged(EntityUid uid, AgentIDCardComponent comp, AgentIDCardJobChangedMessage args) @@ -94,7 +94,7 @@ private void OnJobIconChanged(EntityUid uid, AgentIDCardComponent comp, AgentIDC return; } - if (!_prototypeManager.TryIndex(args.JobIcon, out var jobIcon)) + if (!_prototypeManager.TryIndex(args.JobIconId, out var jobIcon)) { return; } diff --git a/Content.Server/Access/Systems/IdCardConsoleSystem.cs b/Content.Server/Access/Systems/IdCardConsoleSystem.cs index db8b9d036e..e680b0c6f4 100644 --- a/Content.Server/Access/Systems/IdCardConsoleSystem.cs +++ b/Content.Server/Access/Systems/IdCardConsoleSystem.cs @@ -41,7 +41,7 @@ public override void Initialize() private void OnWriteToTargetIdMessage(EntityUid uid, IdCardConsoleComponent component, WriteToTargetIdMessage args) { - if (args.Session.AttachedEntity is not { Valid: true } player) + if (args.Actor is not { Valid: true } player) return; TryWriteToTargetId(uid, args.FullName, args.JobTitle, args.AccessList, args.JobPrototype, player, component); @@ -104,7 +104,7 @@ private void UpdateUserInterface(EntityUid uid, IdCardConsoleComponent component Name(targetId)); } - _userInterface.TrySetUiState(uid, IdCardConsoleUiKey.Key, newState); + _userInterface.SetUiState(uid, IdCardConsoleUiKey.Key, newState); } /// diff --git a/Content.Server/Access/Systems/IdCardSystem.cs b/Content.Server/Access/Systems/IdCardSystem.cs index 6b3d8db595..9cd9976cea 100644 --- a/Content.Server/Access/Systems/IdCardSystem.cs +++ b/Content.Server/Access/Systems/IdCardSystem.cs @@ -7,8 +7,6 @@ using Content.Shared.Access.Systems; using Content.Shared.Database; using Content.Shared.Popups; -using Content.Shared.Roles; -using Content.Shared.StatusIcon; using Robust.Shared.Prototypes; using Robust.Shared.Random; @@ -20,20 +18,13 @@ public sealed class IdCardSystem : SharedIdCardSystem [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IAdminLogManager _adminLogger = default!; - [Dependency] private readonly MetaDataSystem _metaSystem = default!; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(OnMicrowaved); } - private void OnMapInit(EntityUid uid, IdCardComponent id, MapInitEvent args) - { - UpdateEntityName(uid, id); - } - private void OnMicrowaved(EntityUid uid, IdCardComponent component, BeingMicrowavedEvent args) { if (TryComp(uid, out var access)) @@ -81,143 +72,4 @@ private void OnMicrowaved(EntityUid uid, IdCardComponent component, BeingMicrowa $"{ToPrettyString(args.Microwave)} added {random.ID} access to {ToPrettyString(uid):entity}"); } } - - /// - /// Attempts to change the job title of a card. - /// Returns true/false. - /// - /// - /// If provided with a player's EntityUid to the player parameter, adds the change to the admin logs. - /// - public bool TryChangeJobTitle(EntityUid uid, string? jobTitle, IdCardComponent? id = null, EntityUid? player = null) - { - if (!Resolve(uid, ref id)) - return false; - - if (!string.IsNullOrWhiteSpace(jobTitle)) - { - jobTitle = jobTitle.Trim(); - - if (jobTitle.Length > IdCardConsoleComponent.MaxJobTitleLength) - jobTitle = jobTitle[..IdCardConsoleComponent.MaxJobTitleLength]; - } - else - { - jobTitle = null; - } - - if (id.JobTitle == jobTitle) - return true; - id.JobTitle = jobTitle; - Dirty(uid, id); - UpdateEntityName(uid, id); - - if (player != null) - { - _adminLogger.Add(LogType.Identity, LogImpact.Low, - $"{ToPrettyString(player.Value):player} has changed the job title of {ToPrettyString(uid):entity} to {jobTitle} "); - } - return true; - } - - public bool TryChangeJobIcon(EntityUid uid, StatusIconPrototype jobIcon, IdCardComponent? id = null, EntityUid? player = null) - { - if (!Resolve(uid, ref id)) - { - return false; - } - - if (id.JobIcon == jobIcon.ID) - { - return true; - } - - id.JobIcon = jobIcon.ID; - Dirty(uid, id); - - if (player != null) - { - _adminLogger.Add(LogType.Identity, LogImpact.Low, - $"{ToPrettyString(player.Value):player} has changed the job icon of {ToPrettyString(uid):entity} to {jobIcon} "); - } - - return true; - } - - public bool TryChangeJobDepartment(EntityUid uid, JobPrototype job, IdCardComponent? id = null) - { - if (!Resolve(uid, ref id)) - return false; - - id.JobDepartments.Clear(); - foreach (var department in _prototypeManager.EnumeratePrototypes()) - { - if (department.Roles.Contains(job.ID)) - id.JobDepartments.Add("department-" + department.ID); - } - - Dirty(uid, id); - - return true; - } - - /// - /// Attempts to change the full name of a card. - /// Returns true/false. - /// - /// - /// If provided with a player's EntityUid to the player parameter, adds the change to the admin logs. - /// - public bool TryChangeFullName(EntityUid uid, string? fullName, IdCardComponent? id = null, EntityUid? player = null) - { - if (!Resolve(uid, ref id)) - return false; - - if (!string.IsNullOrWhiteSpace(fullName)) - { - fullName = fullName.Trim(); - if (fullName.Length > IdCardConsoleComponent.MaxFullNameLength) - fullName = fullName[..IdCardConsoleComponent.MaxFullNameLength]; - } - else - { - fullName = null; - } - - if (id.FullName == fullName) - return true; - id.FullName = fullName; - Dirty(uid, id); - UpdateEntityName(uid, id); - - if (player != null) - { - _adminLogger.Add(LogType.Identity, LogImpact.Low, - $"{ToPrettyString(player.Value):player} has changed the name of {ToPrettyString(uid):entity} to {fullName} "); - } - return true; - } - - /// - /// Changes the name of the id's owner. - /// - /// - /// If either or is empty, it's replaced by placeholders. - /// If both are empty, the original entity's name is restored. - /// - private void UpdateEntityName(EntityUid uid, IdCardComponent? id = null) - { - if (!Resolve(uid, ref id)) - return; - - var jobSuffix = string.IsNullOrWhiteSpace(id.JobTitle) ? string.Empty : $" ({id.JobTitle})"; - - var val = string.IsNullOrWhiteSpace(id.FullName) - ? Loc.GetString("access-id-card-component-owner-name-job-title-text", - ("jobSuffix", jobSuffix)) - : Loc.GetString("access-id-card-component-owner-full-name-job-title-text", - ("fullName", id.FullName), - ("jobSuffix", jobSuffix)); - _metaSystem.SetEntityName(uid, val); - } } diff --git a/Content.Server/Actions/ActionOnInteractSystem.cs b/Content.Server/Actions/ActionOnInteractSystem.cs index c9a5f4b5d0..b6eec0ce0f 100644 --- a/Content.Server/Actions/ActionOnInteractSystem.cs +++ b/Content.Server/Actions/ActionOnInteractSystem.cs @@ -1,3 +1,4 @@ +using System.Linq; using Content.Shared.Actions; using Content.Shared.Interaction; using Robust.Shared.Random; @@ -38,16 +39,27 @@ private void OnMapInit(EntityUid uid, ActionOnInteractComponent component, MapIn private void OnActivate(EntityUid uid, ActionOnInteractComponent component, ActivateInWorldEvent args) { - if (args.Handled || component.ActionEntities == null) + if (args.Handled) return; - var options = GetValidActions(component.ActionEntities); + if (component.ActionEntities is not {} actionEnts) + { + if (!TryComp(uid, out var actionsContainerComponent)) + return; + + actionEnts = actionsContainerComponent.Container.ContainedEntities.ToList(); + } + + var options = GetValidActions(actionEnts); if (options.Count == 0) return; var (actId, act) = _random.Pick(options); if (act.Event != null) + { act.Event.Performer = args.User; + act.Event.Action = actId; + } _actions.PerformAction(args.User, null, actId, act, act.Event, _timing.CurTime, false); args.Handled = true; @@ -55,16 +67,24 @@ private void OnActivate(EntityUid uid, ActionOnInteractComponent component, Acti private void OnAfterInteract(EntityUid uid, ActionOnInteractComponent component, AfterInteractEvent args) { - if (args.Handled || component.ActionEntities == null) + if (args.Handled) return; + if (component.ActionEntities is not {} actionEnts) + { + if (!TryComp(uid, out var actionsContainerComponent)) + return; + + actionEnts = actionsContainerComponent.Container.ContainedEntities.ToList(); + } + // First, try entity target actions if (args.Target != null) { - var entOptions = GetValidActions(component.ActionEntities, args.CanReach); + var entOptions = GetValidActions(actionEnts, args.CanReach); for (var i = entOptions.Count - 1; i >= 0; i--) { - var action = entOptions[i].Comp; + var action = entOptions[i]; if (!_actions.ValidateEntityTarget(args.User, args.Target.Value, action)) entOptions.RemoveAt(i); } @@ -75,6 +95,7 @@ private void OnAfterInteract(EntityUid uid, ActionOnInteractComponent component, if (entAct.Event != null) { entAct.Event.Performer = args.User; + entAct.Event.Action = entActId; entAct.Event.Target = args.Target.Value; } @@ -88,7 +109,7 @@ private void OnAfterInteract(EntityUid uid, ActionOnInteractComponent component, var options = GetValidActions(component.ActionEntities, args.CanReach); for (var i = options.Count - 1; i >= 0; i--) { - var action = options[i].Comp; + var action = options[i]; if (!_actions.ValidateWorldTarget(args.User, args.ClickLocation, action)) options.RemoveAt(i); } @@ -100,6 +121,7 @@ private void OnAfterInteract(EntityUid uid, ActionOnInteractComponent component, if (act.Event != null) { act.Event.Performer = args.User; + act.Event.Action = actId; act.Event.Target = args.ClickLocation; } diff --git a/Content.Server/Administration/BanList/BanListEui.cs b/Content.Server/Administration/BanList/BanListEui.cs index 3a86a42d53..8ddc7459d7 100644 --- a/Content.Server/Administration/BanList/BanListEui.cs +++ b/Content.Server/Administration/BanList/BanListEui.cs @@ -65,13 +65,23 @@ private async Task LoadBans(NetUserId userId) unban = new SharedServerUnban(unbanningAdmin, ban.Unban.UnbanTime.UtcDateTime); } + (string, int cidrMask)? ip = ("*Hidden*", 0); + var hwid = "*Hidden*"; + + if (_admins.HasAdminFlag(Player, AdminFlags.Pii)) + { + ip = ban.Address is { } address + ? (address.address.ToString(), address.cidrMask) + : null; + + hwid = ban.HWId == null ? null : Convert.ToBase64String(ban.HWId.Value.AsSpan()); + } + Bans.Add(new SharedServerBan( ban.Id, ban.UserId, - ban.Address is { } address - ? (address.address.ToString(), address.cidrMask) - : null, - ban.HWId == null ? null : Convert.ToBase64String(ban.HWId.Value.AsSpan()), + ip, + hwid, ban.BanTime.UtcDateTime, ban.ExpirationTime?.UtcDateTime, ban.Reason, @@ -96,13 +106,22 @@ private async Task LoadRoleBans(NetUserId userId) unban = new SharedServerUnban(unbanningAdmin, ban.Unban.UnbanTime.UtcDateTime); } + (string, int cidrMask)? ip = ("*Hidden*", 0); + var hwid = "*Hidden*"; + + if (_admins.HasAdminFlag(Player, AdminFlags.Pii)) + { + ip = ban.Address is { } address + ? (address.address.ToString(), address.cidrMask) + : null; + + hwid = ban.HWId == null ? null : Convert.ToBase64String(ban.HWId.Value.AsSpan()); + } RoleBans.Add(new SharedServerRoleBan( ban.Id, ban.UserId, - ban.Address is { } address - ? (address.address.ToString(), address.cidrMask) - : null, - ban.HWId == null ? null : Convert.ToBase64String(ban.HWId.Value.AsSpan()), + ip, + hwid, ban.BanTime.UtcDateTime, ban.ExpirationTime?.UtcDateTime, ban.Reason, diff --git a/Content.Server/Administration/Commands/JobWhitelistCommands.cs b/Content.Server/Administration/Commands/JobWhitelistCommands.cs new file mode 100644 index 0000000000..f06cecabd7 --- /dev/null +++ b/Content.Server/Administration/Commands/JobWhitelistCommands.cs @@ -0,0 +1,214 @@ +using System.Linq; +using Content.Server.Database; +using Content.Server.Players.JobWhitelist; +using Content.Shared.Administration; +using Content.Shared.Roles; +using Robust.Server.Player; +using Robust.Shared.Console; +using Robust.Shared.Prototypes; + +namespace Content.Server.Administration.Commands; + +[AdminCommand(AdminFlags.Ban)] +public sealed class JobWhitelistAddCommand : LocalizedCommands +{ + [Dependency] private readonly IServerDbManager _db = default!; + [Dependency] private readonly JobWhitelistManager _jobWhitelist = default!; + [Dependency] private readonly IPlayerLocator _playerLocator = default!; + [Dependency] private readonly IPlayerManager _players = default!; + [Dependency] private readonly IPrototypeManager _prototypes = default!; + + public override string Command => "jobwhitelistadd"; + + public override async void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (args.Length != 2) + { + shell.WriteError(Loc.GetString("shell-wrong-arguments-number-need-specific", + ("properAmount", 2), + ("currentAmount", args.Length))); + shell.WriteLine(Help); + return; + } + + var player = args[0].Trim(); + var job = new ProtoId(args[1].Trim()); + if (!_prototypes.TryIndex(job, out var jobPrototype)) + { + shell.WriteError(Loc.GetString("cmd-jobwhitelist-job-does-not-exist", ("job", job.Id))); + shell.WriteLine(Help); + return; + } + + var data = await _playerLocator.LookupIdByNameAsync(player); + if (data != null) + { + var guid = data.UserId; + var isWhitelisted = await _db.IsJobWhitelisted(guid, job); + if (isWhitelisted) + { + shell.WriteLine(Loc.GetString("cmd-jobwhitelist-already-whitelisted", + ("player", player), + ("jobId", job.Id), + ("jobName", jobPrototype.LocalizedName))); + return; + } + + _jobWhitelist.AddWhitelist(guid, job); + shell.WriteLine(Loc.GetString("cmd-jobwhitelistadd-added", + ("player", player), + ("jobId", job.Id), + ("jobName", jobPrototype.LocalizedName))); + return; + } + + shell.WriteError(Loc.GetString("cmd-jobwhitelist-player-not-found", ("player", player))); + } + + public override CompletionResult GetCompletion(IConsoleShell shell, string[] args) + { + if (args.Length == 1) + { + return CompletionResult.FromHintOptions( + _players.Sessions.Select(s => s.Name), + Loc.GetString("cmd-jobwhitelist-hint-player")); + } + + if (args.Length == 2) + { + return CompletionResult.FromHintOptions( + _prototypes.EnumeratePrototypes().Select(p => p.ID), + Loc.GetString("cmd-jobwhitelist-hint-job")); + } + + return CompletionResult.Empty; + } +} + +[AdminCommand(AdminFlags.Ban)] +public sealed class GetJobWhitelistCommand : LocalizedCommands +{ + [Dependency] private readonly IServerDbManager _db = default!; + [Dependency] private readonly IPlayerLocator _playerLocator = default!; + [Dependency] private readonly IPlayerManager _players = default!; + + public override string Command => "jobwhitelistget"; + + public override async void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (args.Length == 0) + { + shell.WriteError("This command needs at least one argument."); + shell.WriteLine(Help); + return; + } + + var player = string.Join(' ', args).Trim(); + var data = await _playerLocator.LookupIdByNameAsync(player); + if (data != null) + { + var guid = data.UserId; + var whitelists = await _db.GetJobWhitelists(guid); + if (whitelists.Count == 0) + { + shell.WriteLine(Loc.GetString("cmd-jobwhitelistget-whitelisted-none", ("player", player))); + return; + } + + shell.WriteLine(Loc.GetString("cmd-jobwhitelistget-whitelisted-for", + ("player", player), + ("jobs", string.Join(", ", whitelists)))); + return; + } + + shell.WriteError(Loc.GetString("cmd-jobwhitelist-player-not-found", ("player", player))); + } + + public override CompletionResult GetCompletion(IConsoleShell shell, string[] args) + { + if (args.Length == 1) + { + return CompletionResult.FromHintOptions( + _players.Sessions.Select(s => s.Name), + Loc.GetString("cmd-jobwhitelist-hint-player")); + } + + return CompletionResult.Empty; + } +} + +[AdminCommand(AdminFlags.Ban)] +public sealed class RemoveJobWhitelistCommand : LocalizedCommands +{ + [Dependency] private readonly IServerDbManager _db = default!; + [Dependency] private readonly JobWhitelistManager _jobWhitelist = default!; + [Dependency] private readonly IPlayerLocator _playerLocator = default!; + [Dependency] private readonly IPlayerManager _players = default!; + [Dependency] private readonly IPrototypeManager _prototypes = default!; + + public override string Command => "jobwhitelistremove"; + + public override async void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (args.Length != 2) + { + shell.WriteError(Loc.GetString("shell-wrong-arguments-number-need-specific", + ("properAmount", 2), + ("currentAmount", args.Length))); + shell.WriteLine(Help); + return; + } + + var player = args[0].Trim(); + var job = new ProtoId(args[1].Trim()); + if (!_prototypes.TryIndex(job, out var jobPrototype)) + { + shell.WriteError(Loc.GetString("cmd-jobwhitelist-job-does-not-exist", ("job", job))); + shell.WriteLine(Help); + return; + } + + var data = await _playerLocator.LookupIdByNameAsync(player); + if (data != null) + { + var guid = data.UserId; + var isWhitelisted = await _db.IsJobWhitelisted(guid, job); + if (!isWhitelisted) + { + shell.WriteError(Loc.GetString("cmd-jobwhitelistremove-was-not-whitelisted", + ("player", player), + ("jobId", job.Id), + ("jobName", jobPrototype.LocalizedName))); + return; + } + + _jobWhitelist.RemoveWhitelist(guid, job); + shell.WriteLine(Loc.GetString("cmd-jobwhitelistremove-removed", + ("player", player), + ("jobId", job.Id), + ("jobName", jobPrototype.LocalizedName))); + return; + } + + shell.WriteError(Loc.GetString("cmd-jobwhitelist-player-not-found", ("player", player))); + } + + public override CompletionResult GetCompletion(IConsoleShell shell, string[] args) + { + if (args.Length == 1) + { + return CompletionResult.FromHintOptions( + _players.Sessions.Select(s => s.Name), + Loc.GetString("cmd-jobwhitelist-hint-player")); + } + + if (args.Length == 2) + { + return CompletionResult.FromHintOptions( + _prototypes.EnumeratePrototypes().Select(p => p.ID), + Loc.GetString("cmd-jobwhitelist-hint-job")); + } + + return CompletionResult.Empty; + } +} diff --git a/Content.Server/Administration/Commands/JobWhitelistsCommand.cs b/Content.Server/Administration/Commands/JobWhitelistsCommand.cs new file mode 100644 index 0000000000..6ad44a0c73 --- /dev/null +++ b/Content.Server/Administration/Commands/JobWhitelistsCommand.cs @@ -0,0 +1,61 @@ +using System.Linq; +using Content.Server.Administration; +using Content.Server.EUI; +using Content.Shared.Administration; +using Robust.Shared.Console; +using Robust.Server.Player; + +namespace Content.Server.Administration.Commands; + +/// +/// Opens the job whitelists panel for editing player whitelists. +/// To use this ingame it's easiest to first open the player panel, then hit Job Whitelists. +/// +[AdminCommand(AdminFlags.Whitelist)] +public sealed class JobWhitelistsCommand : LocalizedCommands +{ + [Dependency] private readonly EuiManager _eui = default!; + [Dependency] private readonly IPlayerLocator _locator = default!; + [Dependency] private readonly IPlayerManager _players = default!; + + public override string Command => "jobwhitelists"; + + public override async void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (shell.Player is not {} player) + { + shell.WriteError(Loc.GetString("shell-cannot-run-command-from-server")); + return; + } + + if (args.Length != 1) + { + shell.WriteLine(Loc.GetString("cmd-ban-invalid-arguments")); + shell.WriteLine(Help); + } + + var playerarg = string.Join(' ', args).Trim(); + var located = await _locator.LookupIdByNameAsync(playerarg); + if (located is null) + { + shell.WriteError(Loc.GetString("cmd-jobwhitelists-player-err")); + return; + } + + var ui = new JobWhitelistsEui(located.UserId, located.Username); + ui.LoadWhitelists(); + _eui.OpenEui(ui, player); + } + + public override CompletionResult GetCompletion(IConsoleShell shell, string[] args) + { + if (args.Length == 1) + { + return CompletionResult.FromHintOptions( + _players.Sessions.Select(s => s.Name), + Loc.GetString("cmd-jobwhitelist-hint-player")); + } + + return CompletionResult.Empty; + } +} diff --git a/Content.Server/Administration/Commands/SetOutfitCommand.cs b/Content.Server/Administration/Commands/SetOutfitCommand.cs index 79e73ce3d9..e19c5b72fa 100644 --- a/Content.Server/Administration/Commands/SetOutfitCommand.cs +++ b/Content.Server/Administration/Commands/SetOutfitCommand.cs @@ -12,6 +12,9 @@ using Robust.Shared.Console; using Robust.Shared.Player; using Robust.Shared.Prototypes; +using Content.Server.Silicon.IPC; +using Content.Shared.Radio.Components; +using Content.Shared.Cluwne; namespace Content.Server.Administration.Commands { @@ -127,6 +130,13 @@ public static bool SetOutfit(EntityUid target, string gear, IEntityManager entit } } + if (entityManager.HasComponent(target)) + return true; //Fuck it, nuclear option for not Cluwning an IPC because that causes a crash that SOMEHOW ignores null checks. + if (entityManager.HasComponent(target)) + { + var encryption = new InternalEncryptionKeySpawner(); + encryption.TryInsertEncryptionKey(target, startingGear, entityManager); + } return true; } } diff --git a/Content.Server/Administration/Components/HeadstandComponent.cs b/Content.Server/Administration/Components/HeadstandComponent.cs index 8472b5ad36..2ab097fad4 100644 --- a/Content.Server/Administration/Components/HeadstandComponent.cs +++ b/Content.Server/Administration/Components/HeadstandComponent.cs @@ -3,7 +3,7 @@ namespace Content.Server.Administration.Components; -[RegisterComponent, NetworkedComponent] +[RegisterComponent] public sealed partial class HeadstandComponent : SharedHeadstandComponent { diff --git a/Content.Server/Administration/Components/KillSignComponent.cs b/Content.Server/Administration/Components/KillSignComponent.cs index e29ce202dd..11479c32fc 100644 --- a/Content.Server/Administration/Components/KillSignComponent.cs +++ b/Content.Server/Administration/Components/KillSignComponent.cs @@ -3,6 +3,5 @@ namespace Content.Server.Administration.Components; -[NetworkedComponent, RegisterComponent] -public sealed partial class KillSignComponent : SharedKillSignComponent -{ } +[RegisterComponent] +public sealed partial class KillSignComponent : SharedKillSignComponent; diff --git a/Content.Server/Administration/JobWhitelistsEui.cs b/Content.Server/Administration/JobWhitelistsEui.cs new file mode 100644 index 0000000000..9d50a72183 --- /dev/null +++ b/Content.Server/Administration/JobWhitelistsEui.cs @@ -0,0 +1,90 @@ +using System.Threading.Tasks; +using Content.Server.Administration.Managers; +using Content.Server.Database; +using Content.Server.EUI; +using Content.Server.Players.JobWhitelist; +using Content.Shared.Administration; +using Content.Shared.Administration; +using Content.Shared.Eui; +using Content.Shared.Roles; +using Robust.Shared.Log; +using Robust.Shared.Network; +using Robust.Shared.Prototypes; + +namespace Content.Server.Administration; + +public sealed class JobWhitelistsEui : BaseEui +{ + [Dependency] private readonly IAdminManager _admin = default!; + [Dependency] private readonly ILogManager _log = default!; + [Dependency] private readonly IPrototypeManager _proto = default!; + [Dependency] private readonly IServerDbManager _db = default!; + [Dependency] private readonly JobWhitelistManager _jobWhitelist = default!; + + private readonly ISawmill _sawmill; + + public NetUserId PlayerId; + public string PlayerName; + + public HashSet> Whitelists = new(); + + public JobWhitelistsEui(NetUserId playerId, string playerName) + { + IoCManager.InjectDependencies(this); + + _sawmill = _log.GetSawmill("admin.job_whitelists_eui"); + + PlayerId = playerId; + PlayerName = playerName; + } + + public async void LoadWhitelists() + { + var jobs = await _db.GetJobWhitelists(PlayerId.UserId); + foreach (var id in jobs) + { + if (_proto.HasIndex(id)) + Whitelists.Add(id); + } + + StateDirty(); + } + + public override EuiStateBase GetNewState() + { + return new JobWhitelistsEuiState(PlayerName, Whitelists); + } + + public override void HandleMessage(EuiMessageBase msg) + { + base.HandleMessage(msg); + + if (msg is not SetJobWhitelistedMessage args) + return; + + if (!_admin.HasAdminFlag(Player, AdminFlags.Whitelist)) + { + _sawmill.Warning($"{Player.Name} ({Player.UserId}) tried to change role whitelists for {PlayerName} without whitelists flag"); + return; + } + + if (!_proto.HasIndex(args.Job)) + return; + + if (args.Whitelisting) + { + _jobWhitelist.AddWhitelist(PlayerId, args.Job); + Whitelists.Add(args.Job); + } + else + { + _jobWhitelist.RemoveWhitelist(PlayerId, args.Job); + Whitelists.Remove(args.Job); + } + + var verb = args.Whitelisting ? "added" : "removed"; + _sawmill.Info($"{Player.Name} ({Player.UserId}) {verb} whitelist for {args.Job} to player {PlayerName} ({PlayerId.UserId})"); + + StateDirty(); + } +} diff --git a/Content.Server/Administration/Managers/BanManager.cs b/Content.Server/Administration/Managers/BanManager.cs index 3a05b934b2..68bd817026 100644 --- a/Content.Server/Administration/Managers/BanManager.cs +++ b/Content.Server/Administration/Managers/BanManager.cs @@ -73,7 +73,9 @@ private async Task AddRoleBan(ServerRoleBanDef banDef) public HashSet? GetRoleBans(NetUserId playerUserId) { - return _cachedRoleBans.TryGetValue(playerUserId, out var roleBans) ? roleBans.Select(banDef => banDef.Role).ToHashSet() : null; + return _cachedRoleBans.TryGetValue(playerUserId, out var roleBans) + ? roleBans.Select(banDef => banDef.Role).ToHashSet() + : null; } private async Task CacheDbRoleBans(NetUserId userId, IPAddress? address = null, ImmutableArray? hwId = null) @@ -263,13 +265,13 @@ public async Task PardonRoleBan(int banId, NetUserId? unbanningAdmin, Da return $"Pardoned ban with id {banId}"; } - public HashSet? GetJobBans(NetUserId playerUserId) + public HashSet>? GetJobBans(NetUserId playerUserId) { if (!_cachedRoleBans.TryGetValue(playerUserId, out var roleBans)) return null; return roleBans .Where(ban => ban.Role.StartsWith(JobPrefix, StringComparison.Ordinal)) - .Select(ban => ban.Role[JobPrefix.Length..]) + .Select(ban => new ProtoId(ban.Role[JobPrefix.Length..])) .ToHashSet(); } #endregion diff --git a/Content.Server/Administration/Managers/IBanManager.cs b/Content.Server/Administration/Managers/IBanManager.cs index dafe3d35bd..b60e0a2535 100644 --- a/Content.Server/Administration/Managers/IBanManager.cs +++ b/Content.Server/Administration/Managers/IBanManager.cs @@ -2,8 +2,10 @@ using System.Net; using System.Threading.Tasks; using Content.Shared.Database; +using Content.Shared.Roles; using Robust.Shared.Network; using Robust.Shared.Player; +using Robust.Shared.Prototypes; namespace Content.Server.Administration.Managers; @@ -24,7 +26,7 @@ public interface IBanManager /// Reason for the ban public void CreateServerBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableArray? hwid, uint? minutes, NoteSeverity severity, string reason); public HashSet? GetRoleBans(NetUserId playerUserId); - public HashSet? GetJobBans(NetUserId playerUserId); + public HashSet>? GetJobBans(NetUserId playerUserId); /// /// Creates a job ban for the specified target, username or GUID diff --git a/Content.Server/Administration/ServerApi.cs b/Content.Server/Administration/ServerApi.cs index 6f10ef9b47..04fd38598f 100644 --- a/Content.Server/Administration/ServerApi.cs +++ b/Content.Server/Administration/ServerApi.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Content.Server.Administration.Systems; using Content.Server.GameTicking; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Presets; using Content.Server.GameTicking.Rules.Components; using Content.Server.Maps; diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs b/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs index 9849d2df79..4103b8a8aa 100644 --- a/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs +++ b/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs @@ -1,23 +1,37 @@ -using Content.Server.GameTicking.Rules; +using Content.Server.Administration.Commands; +using Content.Server.Antag; +using Content.Server.GameTicking.Rules.Components; using Content.Server.Zombies; using Content.Shared.Administration; using Content.Shared.Database; -using Content.Shared.Humanoid; using Content.Shared.Mind.Components; +using Content.Shared.Roles; using Content.Shared.Verbs; using Robust.Shared.Player; +using Robust.Shared.Prototypes; using Robust.Shared.Utility; namespace Content.Server.Administration.Systems; public sealed partial class AdminVerbSystem { + [Dependency] private readonly AntagSelectionSystem _antag = default!; [Dependency] private readonly ZombieSystem _zombie = default!; - [Dependency] private readonly ThiefRuleSystem _thief = default!; - [Dependency] private readonly TraitorRuleSystem _traitorRule = default!; - [Dependency] private readonly NukeopsRuleSystem _nukeopsRule = default!; - [Dependency] private readonly PiratesRuleSystem _piratesRule = default!; - [Dependency] private readonly RevolutionaryRuleSystem _revolutionaryRule = default!; + + [ValidatePrototypeId] + private const string DefaultTraitorRule = "Traitor"; + + [ValidatePrototypeId] + private const string DefaultNukeOpRule = "LoneOpsSpawn"; + + [ValidatePrototypeId] + private const string DefaultRevsRule = "Revolutionary"; + + [ValidatePrototypeId] + private const string DefaultThiefRule = "Thief"; + + [ValidatePrototypeId] + private const string PirateGearId = "PirateGear"; // All antag verbs have names so invokeverb works. private void AddAntagVerbs(GetVerbsEvent args) @@ -30,9 +44,11 @@ private void AddAntagVerbs(GetVerbsEvent args) if (!_adminManager.HasAdminFlag(player, AdminFlags.Fun)) return; - if (!HasComp(args.Target)) + if (!HasComp(args.Target) || !TryComp(args.Target, out var targetActor)) return; + var targetPlayer = targetActor.PlayerSession; + Verb traitor = new() { Text = Loc.GetString("admin-verb-text-make-traitor"), @@ -40,9 +56,7 @@ private void AddAntagVerbs(GetVerbsEvent args) Icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/Structures/Wallmounts/posters.rsi"), "poster5_contraband"), Act = () => { - // if its a monkey or mouse or something dont give uplink or objectives - var isHuman = HasComp(args.Target); - _traitorRule.MakeTraitorAdmin(args.Target, giveUplink: isHuman, giveObjectives: isHuman); + _antag.ForceMakeAntag(targetPlayer, DefaultTraitorRule); }, Impact = LogImpact.High, Message = Loc.GetString("admin-verb-make-traitor"), @@ -71,7 +85,7 @@ private void AddAntagVerbs(GetVerbsEvent args) Icon = new SpriteSpecifier.Rsi(new("/Textures/Structures/Wallmounts/signs.rsi"), "radiation"), Act = () => { - _nukeopsRule.MakeLoneNukie(args.Target); + _antag.ForceMakeAntag(targetPlayer, DefaultNukeOpRule); }, Impact = LogImpact.High, Message = Loc.GetString("admin-verb-make-nuclear-operative"), @@ -85,14 +99,14 @@ private void AddAntagVerbs(GetVerbsEvent args) Icon = new SpriteSpecifier.Rsi(new("/Textures/Clothing/Head/Hats/pirate.rsi"), "icon"), Act = () => { - _piratesRule.MakePirate(args.Target); + // pirates just get an outfit because they don't really have logic associated with them + SetOutfitCommand.SetOutfit(args.Target, PirateGearId, EntityManager); }, Impact = LogImpact.High, Message = Loc.GetString("admin-verb-make-pirate"), }; args.Verbs.Add(pirate); - //todo come here at some point dear lort. Verb headRev = new() { Text = Loc.GetString("admin-verb-text-make-head-rev"), @@ -100,7 +114,7 @@ private void AddAntagVerbs(GetVerbsEvent args) Icon = new SpriteSpecifier.Rsi(new("/Textures/Interface/Misc/job_icons.rsi"), "HeadRevolutionary"), Act = () => { - _revolutionaryRule.OnHeadRevAdmin(args.Target); + _antag.ForceMakeAntag(targetPlayer, DefaultRevsRule); }, Impact = LogImpact.High, Message = Loc.GetString("admin-verb-make-head-rev"), @@ -111,10 +125,10 @@ private void AddAntagVerbs(GetVerbsEvent args) { Text = Loc.GetString("admin-verb-text-make-thief"), Category = VerbCategory.Antag, - Icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/Clothing/Hands/Gloves/ihscombat.rsi"), "icon"), + Icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/Clothing/Hands/Gloves/Color/black.rsi"), "icon"), Act = () => { - _thief.AdminMakeThief(args.Target, false); //Midround add pacified is bad + _antag.ForceMakeAntag(targetPlayer, DefaultThiefRule); }, Impact = LogImpact.High, Message = Loc.GetString("admin-verb-make-thief"), diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs b/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs index 8ee52ad03e..4e6af6ceea 100644 --- a/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs +++ b/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs @@ -18,7 +18,6 @@ using Content.Server.Storage.EntitySystems; using Content.Server.Tabletop; using Content.Server.Tabletop.Components; -using Content.Server.Terminator.Systems; using Content.Shared.Administration; using Content.Shared.Administration.Components; using Content.Shared.Body.Components; @@ -31,7 +30,6 @@ using Content.Shared.Electrocution; using Content.Shared.Interaction.Components; using Content.Shared.Inventory; -using Content.Shared.Mind.Components; using Content.Shared.Mobs; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; @@ -74,7 +72,6 @@ public sealed partial class AdminVerbSystem [Dependency] private readonly PopupSystem _popupSystem = default!; [Dependency] private readonly SharedPhysicsSystem _physics = default!; [Dependency] private readonly TabletopSystem _tabletopSystem = default!; - [Dependency] private readonly TerminatorSystem _terminator = default!; [Dependency] private readonly VomitSystem _vomitSystem = default!; [Dependency] private readonly WeldableSystem _weldableSystem = default!; [Dependency] private readonly SharedContentEyeSystem _eyeSystem = default!; @@ -152,7 +149,7 @@ private void AddSmiteVerbs(GetVerbsEvent args) Act = () => { // Fuck you. Burn Forever. - flammable.FireStacks = FlammableSystem.MaximumFireStacks; + flammable.FireStacks = flammable.MaximumFireStacks; _flammableSystem.Ignite(args.Target, args.User); var xform = Transform(args.Target); _popupSystem.PopupEntity(Loc.GetString("admin-smite-set-alight-self"), args.Target, @@ -824,27 +821,5 @@ private void AddSmiteVerbs(GetVerbsEvent args) Impact = LogImpact.Extreme, }; args.Verbs.Add(superBonk); - - Verb terminate = new() - { - Text = "Terminate", - Category = VerbCategory.Smite, - Icon = new SpriteSpecifier.Rsi(new ("Mobs/Species/Terminator/parts.rsi"), "skull_icon"), - Act = () => - { - if (!TryComp(args.Target, out var mindContainer) || mindContainer.Mind == null) - return; - - var coords = Transform(args.Target).Coordinates; - var mindId = mindContainer.Mind.Value; - _terminator.CreateSpawner(coords, mindId); - - _popupSystem.PopupEntity(Loc.GetString("admin-smite-terminate-prompt"), args.Target, - args.Target, PopupType.LargeCaution); - }, - Impact = LogImpact.Extreme, - Message = Loc.GetString("admin-smite-terminate-description") - }; - args.Verbs.Add(terminate); } } diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.cs b/Content.Server/Administration/Systems/AdminVerbSystem.cs index f5b237b449..5bb75b4c99 100644 --- a/Content.Server/Administration/Systems/AdminVerbSystem.cs +++ b/Content.Server/Administration/Systems/AdminVerbSystem.cs @@ -463,7 +463,7 @@ private void AddDebugVerbs(GetVerbsEvent args) Text = Loc.GetString("configure-verb-get-data-text"), Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/settings.svg.192dpi.png")), Category = VerbCategory.Debug, - Act = () => _uiSystem.TryOpen(args.Target, ConfigurationUiKey.Key, actor.PlayerSession) + Act = () => _uiSystem.OpenUi(args.Target, ConfigurationUiKey.Key, actor.PlayerSession) }; args.Verbs.Add(verb); } diff --git a/Content.Server/Administration/Systems/BwoinkSystem.cs b/Content.Server/Administration/Systems/BwoinkSystem.cs index 2e236688ac..a07115544b 100644 --- a/Content.Server/Administration/Systems/BwoinkSystem.cs +++ b/Content.Server/Administration/Systems/BwoinkSystem.cs @@ -25,7 +25,7 @@ namespace Content.Server.Administration.Systems { [UsedImplicitly] - public sealed class BwoinkSystem : SharedBwoinkSystem + public sealed partial class BwoinkSystem : SharedBwoinkSystem { [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IAdminManager _adminManager = default!; @@ -36,6 +36,9 @@ public sealed class BwoinkSystem : SharedBwoinkSystem [Dependency] private readonly SharedMindSystem _minds = default!; [Dependency] private readonly IAfkManager _afkManager = default!; + [GeneratedRegex(@"^https://discord\.com/api/webhooks/(\d+)/((?!.*/).*)$")] + private static partial Regex DiscordRegex(); + private ISawmill _sawmill = default!; private readonly HttpClient _httpClient = new(); private string _webhookUrl = string.Empty; @@ -157,7 +160,7 @@ private async void OnWebhookChanged(string url) return; // Basic sanity check and capturing webhook ID and token - var match = Regex.Match(url, @"^https://discord\.com/api/webhooks/(\d+)/((?!.*/).*)$"); + var match = DiscordRegex().Match(url); if (!match.Success) { diff --git a/Content.Server/Advertise/EntitySystems/SpeakOnUIClosedSystem.cs b/Content.Server/Advertise/EntitySystems/SpeakOnUIClosedSystem.cs index 939fc9a2db..6b77157820 100644 --- a/Content.Server/Advertise/EntitySystems/SpeakOnUIClosedSystem.cs +++ b/Content.Server/Advertise/EntitySystems/SpeakOnUIClosedSystem.cs @@ -5,6 +5,7 @@ using Content.Shared.Chat; using Robust.Shared.Prototypes; using Robust.Shared.Random; +using ActivatableUIComponent = Content.Shared.UserInterface.ActivatableUIComponent; namespace Content.Server.Advertise; diff --git a/Content.Server/Alert/Click/CheckHealth.cs b/Content.Server/Alert/Click/CheckHealth.cs new file mode 100644 index 0000000000..31beff69c2 --- /dev/null +++ b/Content.Server/Alert/Click/CheckHealth.cs @@ -0,0 +1,44 @@ +using Content.Server.Chat.Managers; +using Content.Shared.Alert; +using Content.Shared.Chat; +using Content.Shared.Damage; +using Content.Shared.HealthExaminable; +using JetBrains.Annotations; +using Robust.Server.Player; +using Robust.Shared.Player; + +namespace Content.Server.Alert.Click; + +[UsedImplicitly] +[DataDefinition] +public sealed partial class CheckHealth : IAlertClick +{ + public void AlertClicked(EntityUid player) + { + var chatManager = IoCManager.Resolve(); + var entityManager = IoCManager.Resolve(); + var playerManager = IoCManager.Resolve(); + + var healthExaminableSystem = entityManager.System(); + + if (!entityManager.TryGetComponent(player, out HealthExaminableComponent? healthExaminable) || + !entityManager.TryGetComponent(player, out DamageableComponent? damageable) || + !playerManager.TryGetSessionByEntity(player, out var session)) + return; + + var baseMsg = Loc.GetString("health-alert-start"); + SendMessage(chatManager, baseMsg, session); + var markup = healthExaminableSystem.GetMarkup(player, (player, healthExaminable), damageable).ToMarkup(); + SendMessage(chatManager, markup, session); + } + + private static void SendMessage(IChatManager chatManager, string msg, ICommonSession session) + { + chatManager.ChatMessageToOne(ChatChannel.Emotes, + msg, + msg, + EntityUid.Invalid, + false, + session.Channel); + } +} diff --git a/Content.Server/AlertLevel/AlertLevelSystem.cs b/Content.Server/AlertLevel/AlertLevelSystem.cs index b290d95a5c..46b02c0e97 100644 --- a/Content.Server/AlertLevel/AlertLevelSystem.cs +++ b/Content.Server/AlertLevel/AlertLevelSystem.cs @@ -98,6 +98,16 @@ private void OnPrototypeReload(PrototypesReloadedEventArgs args) RaiseLocalEvent(new AlertLevelPrototypeReloadedEvent()); } + public string GetLevel(EntityUid station, AlertLevelComponent? alert = null) + { + if (!Resolve(station, ref alert)) + { + return string.Empty; + } + + return alert.CurrentLevel; + } + public float GetAlertLevelDelay(EntityUid station, AlertLevelComponent? alert = null) { if (!Resolve(station, ref alert)) diff --git a/Content.Server/Ame/EntitySystems/AmeControllerSystem.cs b/Content.Server/Ame/EntitySystems/AmeControllerSystem.cs index 5bf78bde85..1b323d6643 100644 --- a/Content.Server/Ame/EntitySystems/AmeControllerSystem.cs +++ b/Content.Server/Ame/EntitySystems/AmeControllerSystem.cs @@ -129,11 +129,11 @@ public void UpdateUi(EntityUid uid, AmeControllerComponent? controller = null) if (!Resolve(uid, ref controller)) return; - if (!_userInterfaceSystem.TryGetUi(uid, AmeControllerUiKey.Key, out var bui)) + if (!_userInterfaceSystem.HasUi(uid, AmeControllerUiKey.Key)) return; var state = GetUiState(uid, controller); - _userInterfaceSystem.SetUiState(bui, state); + _userInterfaceSystem.SetUiState(uid, AmeControllerUiKey.Key, state); controller.NextUIUpdate = _gameTiming.CurTime + controller.UpdateUIPeriod; } @@ -324,7 +324,7 @@ private void OnPowerChanged(EntityUid uid, AmeControllerComponent comp, ref Powe private void OnUiButtonPressed(EntityUid uid, AmeControllerComponent comp, UiButtonPressedMessage msg) { - var user = msg.Session.AttachedEntity; + var user = msg.Actor; if (!Exists(user)) return; @@ -334,7 +334,7 @@ private void OnUiButtonPressed(EntityUid uid, AmeControllerComponent comp, UiBut _ => true, }; - if (!PlayerCanUseController(uid, user!.Value, needsPower, comp)) + if (!PlayerCanUseController(uid, user, needsPower, comp)) return; _audioSystem.PlayPvs(comp.ClickSound, uid, AudioParams.Default.WithVolume(-2f)); diff --git a/Content.Server/Anomaly/AnomalySystem.Generator.cs b/Content.Server/Anomaly/AnomalySystem.Generator.cs index 7aa1a8935f..056a985cbe 100644 --- a/Content.Server/Anomaly/AnomalySystem.Generator.cs +++ b/Content.Server/Anomaly/AnomalySystem.Generator.cs @@ -61,7 +61,7 @@ public void UpdateGeneratorUi(EntityUid uid, AnomalyGeneratorComponent component var materialAmount = _material.GetMaterialAmount(uid, component.RequiredMaterial); var state = new AnomalyGeneratorUserInterfaceState(component.CooldownEndTime, materialAmount, component.MaterialPerAnomaly); - _ui.TrySetUiState(uid, AnomalyGeneratorUiKey.Key, state); + _ui.SetUiState(uid, AnomalyGeneratorUiKey.Key, state); } public void TryGeneratorCreateAnomaly(EntityUid uid, AnomalyGeneratorComponent? component = null) diff --git a/Content.Server/Anomaly/AnomalySystem.Scanner.cs b/Content.Server/Anomaly/AnomalySystem.Scanner.cs index bce508903d..39c0d08b55 100644 --- a/Content.Server/Anomaly/AnomalySystem.Scanner.cs +++ b/Content.Server/Anomaly/AnomalySystem.Scanner.cs @@ -31,7 +31,8 @@ private void OnScannerAnomalyShutdown(ref AnomalyShutdownEvent args) { if (component.ScannedAnomaly != args.Anomaly) continue; - _ui.TryCloseAll(uid, AnomalyScannerUiKey.Key); + + _ui.CloseUi(uid, AnomalyScannerUiKey.Key); } } @@ -108,7 +109,7 @@ private void OnDoAfter(EntityUid uid, AnomalyScannerComponent component, DoAfter Popup.PopupEntity(Loc.GetString("anomaly-scanner-component-scan-complete"), uid); UpdateScannerWithNewAnomaly(uid, args.Args.Target.Value, component); - if (TryComp(args.Args.User, out var actor)) _ui.TryOpen(uid, AnomalyScannerUiKey.Key, actor.PlayerSession); + _ui.OpenUi(uid, AnomalyScannerUiKey.Key, args.User); args.Handled = true; } @@ -123,7 +124,7 @@ public void UpdateScannerUi(EntityUid uid, AnomalyScannerComponent? component = nextPulse = anomalyComponent.NextPulseTime; var state = new AnomalyScannerUserInterfaceState(GetScannerMessage(component), nextPulse); - _ui.TrySetUiState(uid, AnomalyScannerUiKey.Key, state); + _ui.SetUiState(uid, AnomalyScannerUiKey.Key, state); } public void UpdateScannerWithNewAnomaly(EntityUid scanner, EntityUid anomaly, AnomalyScannerComponent? scannerComp = null, AnomalyComponent? anomalyComp = null) diff --git a/Content.Server/Anomaly/AnomalySystem.Vessel.cs b/Content.Server/Anomaly/AnomalySystem.Vessel.cs index 35de5c1a64..9a6c99f820 100644 --- a/Content.Server/Anomaly/AnomalySystem.Vessel.cs +++ b/Content.Server/Anomaly/AnomalySystem.Vessel.cs @@ -1,4 +1,5 @@ using Content.Server.Anomaly.Components; +using Content.Server.Construction; using Content.Server.Power.EntitySystems; using Content.Shared.Anomaly; using Content.Shared.Anomaly.Components; @@ -20,6 +21,7 @@ private void InitializeVessel() { SubscribeLocalEvent(OnVesselShutdown); SubscribeLocalEvent(OnVesselMapInit); + SubscribeLocalEvent(OnUpgradeExamine); SubscribeLocalEvent(OnVesselInteractUsing); SubscribeLocalEvent(OnExamined); SubscribeLocalEvent(OnVesselGetPointsPerSecond); @@ -65,6 +67,11 @@ private void OnVesselMapInit(EntityUid uid, AnomalyVesselComponent component, Ma UpdateVesselAppearance(uid, component); } + private void OnUpgradeExamine(EntityUid uid, AnomalyVesselComponent component, UpgradeExamineEvent args) + { + args.AddPercentageUpgrade("anomaly-vessel-component-upgrade-output", component.PointMultiplier); + } + private void OnVesselInteractUsing(EntityUid uid, AnomalyVesselComponent component, InteractUsingEvent args) { if (component.Anomaly != null || diff --git a/Content.Server/Anomaly/AnomalySystem.cs b/Content.Server/Anomaly/AnomalySystem.cs index 29c83ab029..fac50fb409 100644 --- a/Content.Server/Anomaly/AnomalySystem.cs +++ b/Content.Server/Anomaly/AnomalySystem.cs @@ -42,9 +42,6 @@ public sealed partial class AnomalySystem : SharedAnomalySystem [Dependency] private readonly RadiationSystem _radiation = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly UserInterfaceSystem _ui = default!; - [Dependency] private readonly IComponentFactory _componentFactory = default!; - [Dependency] private readonly ISerializationManager _serialization = default!; - [Dependency] private readonly IEntityManager _entity = default!; public const float MinParticleVariation = 0.8f; public const float MaxParticleVariation = 1.2f; diff --git a/Content.Server/Antag/AntagSelectionPlayerPool.cs b/Content.Server/Antag/AntagSelectionPlayerPool.cs new file mode 100644 index 0000000000..87873e96d1 --- /dev/null +++ b/Content.Server/Antag/AntagSelectionPlayerPool.cs @@ -0,0 +1,27 @@ +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Robust.Shared.Player; +using Robust.Shared.Random; + +namespace Content.Server.Antag; + +public sealed class AntagSelectionPlayerPool (List> orderedPools) +{ + public bool TryPickAndTake(IRobustRandom random, [NotNullWhen(true)] out ICommonSession? session) + { + session = null; + + foreach (var pool in orderedPools) + { + if (pool.Count == 0) + continue; + + session = random.PickAndTake(pool); + break; + } + + return session != null; + } + + public int Count => orderedPools.Sum(p => p.Count); +} diff --git a/Content.Server/Antag/AntagSelectionSystem.API.cs b/Content.Server/Antag/AntagSelectionSystem.API.cs new file mode 100644 index 0000000000..77f543cdcf --- /dev/null +++ b/Content.Server/Antag/AntagSelectionSystem.API.cs @@ -0,0 +1,359 @@ +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Content.Server.Antag.Components; +using Content.Server.GameTicking.Rules.Components; +using Content.Server.Objectives; +using Content.Shared.Chat; +using Content.Shared.Mind; +using Content.Shared.Preferences; +using JetBrains.Annotations; +using Robust.Shared.Audio; +using Robust.Shared.Enums; +using Robust.Shared.Player; + +namespace Content.Server.Antag; + +public sealed partial class AntagSelectionSystem +{ + /// + /// Tries to get the next non-filled definition based on the current amount of selected minds and other factors. + /// + public bool TryGetNextAvailableDefinition(Entity ent, + [NotNullWhen(true)] out AntagSelectionDefinition? definition) + { + definition = null; + + var totalTargetCount = GetTargetAntagCount(ent); + var mindCount = ent.Comp.SelectedMinds.Count; + if (mindCount >= totalTargetCount) + return false; + + // TODO ANTAG fix this + // If here are two definitions with 1/10 and 10/10 slots filled, this will always return the second definition + // even though it has already met its target + // AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA I fucking hate game ticker code. + // It needs to track selected minds for each definition independently. + foreach (var def in ent.Comp.Definitions) + { + var target = GetTargetAntagCount(ent, null, def); + + if (mindCount < target) + { + definition = def; + return true; + } + + mindCount -= target; + } + + return false; + } + + /// + /// Gets the number of antagonists that should be present for a given rule based on the provided pool. + /// A null pool will simply use the player count. + /// + public int GetTargetAntagCount(Entity ent, int? playerCount = null) + { + var count = 0; + foreach (var def in ent.Comp.Definitions) + { + count += GetTargetAntagCount(ent, playerCount, def); + } + + return count; + } + + public int GetTotalPlayerCount(IList pool) + { + var count = 0; + foreach (var session in pool) + { + if (session.Status is SessionStatus.Disconnected or SessionStatus.Zombie) + continue; + + count++; + } + + return count; + } + + /// + /// Gets the number of antagonists that should be present for a given antag definition based on the provided pool. + /// A null pool will simply use the player count. + /// + public int GetTargetAntagCount(Entity ent, int? playerCount, AntagSelectionDefinition def) + { + // TODO ANTAG + // make pool non-nullable + // Review uses and ensure that people are INTENTIONALLY including players in the lobby if this is a mid-round + // antag selection. + var poolSize = playerCount ?? GetTotalPlayerCount(_playerManager.Sessions); + + // factor in other definitions' affect on the count. + var countOffset = 0; + foreach (var otherDef in ent.Comp.Definitions) + { + countOffset += Math.Clamp(poolSize / otherDef.PlayerRatio, otherDef.Min, otherDef.Max) * otherDef.PlayerRatio; + } + // make sure we don't double-count the current selection + countOffset -= Math.Clamp((poolSize + countOffset) / def.PlayerRatio, def.Min, def.Max) * def.PlayerRatio; + + return Math.Clamp((poolSize - countOffset) / def.PlayerRatio, def.Min, def.Max); + } + + /// + /// Returns identifiable information for all antagonists to be used in a round end summary. + /// + /// + /// A list containing, in order, the antag's mind, the session data, and the original name stored as a string. + /// + public List<(EntityUid, SessionData, string)> GetAntagIdentifiers(Entity ent) + { + if (!Resolve(ent, ref ent.Comp, false)) + return new List<(EntityUid, SessionData, string)>(); + + var output = new List<(EntityUid, SessionData, string)>(); + foreach (var (mind, name) in ent.Comp.SelectedMinds) + { + if (!TryComp(mind, out var mindComp) || mindComp.OriginalOwnerUserId == null) + continue; + + if (!_playerManager.TryGetPlayerData(mindComp.OriginalOwnerUserId.Value, out var data)) + continue; + + output.Add((mind, data, name)); + } + return output; + } + + /// + /// Returns all the minds of antagonists. + /// + public List> GetAntagMinds(Entity ent) + { + if (!Resolve(ent, ref ent.Comp, false)) + return new(); + + var output = new List>(); + foreach (var (mind, _) in ent.Comp.SelectedMinds) + { + if (!TryComp(mind, out var mindComp) || mindComp.OriginalOwnerUserId == null) + continue; + + output.Add((mind, mindComp)); + } + return output; + } + + /// + /// Helper specifically for + /// + public List GetAntagMindEntityUids(Entity ent) + { + if (!Resolve(ent, ref ent.Comp, false)) + return new(); + + return ent.Comp.SelectedMinds.Select(p => p.Item1).ToList(); + } + + /// + /// Checks if a given session has the primary antag preferences for a given definition + /// + public bool HasPrimaryAntagPreference(ICommonSession? session, AntagSelectionDefinition def) + { + if (session == null) + return true; + + if (def.PrefRoles.Count == 0) + return false; + + var pref = (HumanoidCharacterProfile) _pref.GetPreferences(session.UserId).SelectedCharacter; + return pref.AntagPreferences.Any(p => def.PrefRoles.Contains(p)); + } + + /// + /// Checks if a given session has the fallback antag preferences for a given definition + /// + public bool HasFallbackAntagPreference(ICommonSession? session, AntagSelectionDefinition def) + { + if (session == null) + return true; + + if (def.FallbackRoles.Count == 0) + return false; + + var pref = (HumanoidCharacterProfile) _pref.GetPreferences(session.UserId).SelectedCharacter; + return pref.AntagPreferences.Any(p => def.FallbackRoles.Contains(p)); + } + + /// + /// Returns all the antagonists for this rule who are currently alive + /// + public IEnumerable GetAliveAntags(Entity ent) + { + if (!Resolve(ent, ref ent.Comp, false)) + yield break; + + var minds = GetAntagMinds(ent); + foreach (var mind in minds) + { + if (_mind.IsCharacterDeadIc(mind)) + continue; + + if (mind.Comp.OriginalOwnedEntity != null) + yield return GetEntity(mind.Comp.OriginalOwnedEntity.Value); + } + } + + /// + /// Returns the number of alive antagonists for this rule. + /// + public int GetAliveAntagCount(Entity ent) + { + if (!Resolve(ent, ref ent.Comp, false)) + return 0; + + var numbah = 0; + var minds = GetAntagMinds(ent); + foreach (var mind in minds) + { + if (_mind.IsCharacterDeadIc(mind)) + continue; + + numbah++; + } + + return numbah; + } + + /// + /// Returns if there are any remaining antagonists alive for this rule. + /// + public bool AnyAliveAntags(Entity ent) + { + if (!Resolve(ent, ref ent.Comp, false)) + return false; + + return GetAliveAntags(ent).Any(); + } + + /// + /// Checks if all the antagonists for this rule are alive. + /// + public bool AllAntagsAlive(Entity ent) + { + if (!Resolve(ent, ref ent.Comp, false)) + return false; + + return GetAliveAntagCount(ent) == ent.Comp.SelectedMinds.Count; + } + + /// + /// Helper method to send the briefing text and sound to a player entity + /// + /// The entity chosen to be antag + /// The briefing text to send + /// The color the briefing should be, null for default + /// The sound to briefing/greeting sound to play + public void SendBriefing(EntityUid entity, string briefing, Color? briefingColor, SoundSpecifier? briefingSound) + { + if (!_mind.TryGetMind(entity, out _, out var mindComponent)) + return; + + if (mindComponent.Session == null) + return; + + SendBriefing(mindComponent.Session, briefing, briefingColor, briefingSound); + } + + /// + /// Helper method to send the briefing text and sound to a list of sessions + /// + /// The sessions that will be sent the briefing + /// The briefing text to send + /// The color the briefing should be, null for default + /// The sound to briefing/greeting sound to play + [PublicAPI] + public void SendBriefing(List sessions, string briefing, Color? briefingColor, SoundSpecifier? briefingSound) + { + foreach (var session in sessions) + { + SendBriefing(session, briefing, briefingColor, briefingSound); + } + } + + /// + /// Helper method to send the briefing text and sound to a session + /// + /// The player chosen to be an antag + /// The briefing data + public void SendBriefing( + ICommonSession? session, + BriefingData? data) + { + if (session == null || data == null) + return; + + var text = data.Value.Text == null ? string.Empty : Loc.GetString(data.Value.Text); + SendBriefing(session, text, data.Value.Color, data.Value.Sound); + } + + /// + /// Helper method to send the briefing text and sound to a session + /// + /// The player chosen to be an antag + /// The briefing text to send + /// The color the briefing should be, null for default + /// The sound to briefing/greeting sound to play + public void SendBriefing( + ICommonSession? session, + string briefing, + Color? briefingColor, + SoundSpecifier? briefingSound) + { + if (session == null) + return; + + _audio.PlayGlobal(briefingSound, session); + if (!string.IsNullOrEmpty(briefing)) + { + var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", briefing)); + _chat.ChatMessageToOne(ChatChannel.Server, briefing, wrappedMessage, default, false, session.Channel, + briefingColor); + } + } + + /// + /// This technically is a gamerule-ent-less way to make an entity an antag. + /// You should almost never be using this. + /// + public void ForceMakeAntag(ICommonSession? player, string defaultRule) where T : Component + { + var rule = ForceGetGameRuleEnt(defaultRule); + + if (!TryGetNextAvailableDefinition(rule, out var def)) + def = rule.Comp.Definitions.Last(); + + MakeAntag(rule, player, def.Value); + } + + /// + /// Tries to grab one of the weird specific antag gamerule ents or starts a new one. + /// This is gross code but also most of this is pretty gross to begin with. + /// + public Entity ForceGetGameRuleEnt(string id) where T : Component + { + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out _, out var comp)) + { + return (uid, comp); + } + var ruleEnt = GameTicker.AddGameRule(id); + RemComp(ruleEnt); + var antag = Comp(ruleEnt); + antag.SelectionsComplete = true; // don't do normal selection. + GameTicker.StartGameRule(ruleEnt); + return (ruleEnt, antag); + } +} diff --git a/Content.Server/Antag/AntagSelectionSystem.cs b/Content.Server/Antag/AntagSelectionSystem.cs index b11c562df5..cd4d836e68 100644 --- a/Content.Server/Antag/AntagSelectionSystem.cs +++ b/Content.Server/Antag/AntagSelectionSystem.cs @@ -1,347 +1,453 @@ +using System.Linq; +using Content.Server.Antag.Components; +using Content.Server.Chat.Managers; +using Content.Server.GameTicking; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules; -using Content.Server.GameTicking.Rules.Components; +using Content.Server.Ghost.Roles; +using Content.Server.Ghost.Roles.Components; using Content.Server.Mind; using Content.Server.Preferences.Managers; +using Content.Server.Roles; using Content.Server.Roles.Jobs; using Content.Server.Shuttles.Components; +using Content.Server.Station.Systems; using Content.Shared.Antag; +using Content.Shared.GameTicking; +using Content.Shared.Ghost; using Content.Shared.Humanoid; using Content.Shared.Players; -using Content.Shared.Preferences; -using Content.Shared.Roles; using Robust.Server.Audio; -using Robust.Shared.Audio; +using Robust.Server.GameObjects; +using Robust.Server.Player; +using Robust.Shared.Enums; +using Robust.Shared.Map; using Robust.Shared.Player; -using Robust.Shared.Prototypes; using Robust.Shared.Random; -using System.Linq; -using Content.Shared.Chat; -using Robust.Shared.Enums; +using Robust.Shared.Utility; namespace Content.Server.Antag; -public sealed class AntagSelectionSystem : GameRuleSystem +public sealed partial class AntagSelectionSystem : GameRuleSystem { - [Dependency] private readonly IServerPreferencesManager _prefs = default!; - [Dependency] private readonly AudioSystem _audioSystem = default!; + [Dependency] private readonly IChatManager _chat = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IServerPreferencesManager _pref = default!; + [Dependency] private readonly AudioSystem _audio = default!; + [Dependency] private readonly GhostRoleSystem _ghostRole = default!; [Dependency] private readonly JobSystem _jobs = default!; - [Dependency] private readonly MindSystem _mindSystem = default!; - [Dependency] private readonly SharedRoleSystem _roleSystem = default!; + [Dependency] private readonly MindSystem _mind = default!; + [Dependency] private readonly RoleSystem _role = default!; + [Dependency] private readonly StationSpawningSystem _stationSpawning = default!; + [Dependency] private readonly TransformSystem _transform = default!; - #region Eligible Player Selection - /// - /// Get all players that are eligible for an antag role - /// - /// All sessions from which to select eligible players - /// The prototype to get eligible players for - /// Should jobs that prohibit antag roles (ie Heads, Sec, Interns) be included - /// Should players already selected as antags be eligible - /// Should we ignore if the player has enabled this specific role - /// A custom condition that each player is tested against, if it returns true the player is excluded from eligibility - /// List of all player entities that match the requirements - public List GetEligiblePlayers(IEnumerable playerSessions, - ProtoId antagPrototype, - bool includeAllJobs = false, - AntagAcceptability acceptableAntags = AntagAcceptability.NotExclusive, - bool ignorePreferences = false, - bool allowNonHumanoids = false, - Func? customExcludeCondition = null) + // arbitrary random number to give late joining some mild interest. + public const float LateJoinRandomChance = 0.5f; + + /// + public override void Initialize() { - var eligiblePlayers = new List(); + base.Initialize(); - foreach (var player in playerSessions) - { - if (IsPlayerEligible(player, antagPrototype, includeAllJobs, acceptableAntags, ignorePreferences, allowNonHumanoids, customExcludeCondition)) - eligiblePlayers.Add(player.AttachedEntity!.Value); - } + SubscribeLocalEvent(OnTakeGhostRole); - return eligiblePlayers; + SubscribeLocalEvent(OnPlayerSpawning); + SubscribeLocalEvent(OnJobsAssigned); + SubscribeLocalEvent(OnSpawnComplete); } - /// - /// Get all sessions that are eligible for an antag role, can be run prior to sessions being attached to an entity - /// This does not exclude sessions that have already been chosen as antags - that must be handled manually - /// - /// All sessions from which to select eligible players - /// The prototype to get eligible players for - /// Should we ignore if the player has enabled this specific role - /// List of all player sessions that match the requirements - public List GetEligibleSessions(IEnumerable playerSessions, ProtoId antagPrototype, bool ignorePreferences = false) + private void OnTakeGhostRole(Entity ent, ref TakeGhostRoleEvent args) { - var eligibleSessions = new List(); + if (args.TookRole) + return; - foreach (var session in playerSessions) + if (ent.Comp.Rule is not { } rule || ent.Comp.Definition is not { } def) + return; + + if (!Exists(rule) || !TryComp(rule, out var select)) + return; + + MakeAntag((rule, select), args.Player, def, ignoreSpawner: true); + args.TookRole = true; + _ghostRole.UnregisterGhostRole((ent, Comp(ent))); + } + + private void OnPlayerSpawning(RulePlayerSpawningEvent args) + { + var pool = args.PlayerPool; + + var query = QueryActiveRules(); + while (query.MoveNext(out var uid, out _, out var comp, out _)) { - if (IsSessionEligible(session, antagPrototype, ignorePreferences)) - eligibleSessions.Add(session); - } + if (comp.SelectionTime != AntagSelectionTime.PrePlayerSpawn) + continue; - return eligibleSessions; + if (comp.SelectionsComplete) + continue; + + ChooseAntags((uid, comp), pool); + + foreach (var session in comp.SelectedSessions) + { + args.PlayerPool.Remove(session); + GameTicker.PlayerJoinGame(session); + } + } } - /// - /// Test eligibility of the player for a specific antag role - /// - /// The player session to test - /// The prototype to get eligible players for - /// Should jobs that prohibit antag roles (ie Heads, Sec, Interns) be included - /// Should players already selected as antags be eligible - /// Should we ignore if the player has enabled this specific role - /// A function, accepting an EntityUid and returning bool. Each player is tested against this, returning truw will exclude the player from eligibility - /// True if the player session matches the requirements, false otherwise - public bool IsPlayerEligible(ICommonSession session, - ProtoId antagPrototype, - bool includeAllJobs = false, - AntagAcceptability acceptableAntags = AntagAcceptability.NotExclusive, - bool ignorePreferences = false, - bool allowNonHumanoids = false, - Func? customExcludeCondition = null) + private void OnJobsAssigned(RulePlayerJobsAssignedEvent args) { - if (!IsSessionEligible(session, antagPrototype, ignorePreferences)) - return false; + var query = QueryActiveRules(); + while (query.MoveNext(out var uid, out _, out var comp, out _)) + { + if (comp.SelectionTime != AntagSelectionTime.PostPlayerSpawn) + continue; - //Ensure the player has a mind - if (session.GetMind() is not { } playerMind) - return false; + ChooseAntags((uid, comp), args.Players); + } + } - //Ensure the player has an attached entity - if (session.AttachedEntity is not { } playerEntity) - return false; + private void OnSpawnComplete(PlayerSpawnCompleteEvent args) + { + if (!args.LateJoin) + return; - //Ignore latejoined players, ie those on the arrivals station - if (HasComp(playerEntity)) - return false; + // TODO: this really doesn't handle multiple latejoin definitions well + // eventually this should probably store the players per definition with some kind of unique identifier. + // something to figure out later. - //Exclude jobs that cannot be antag, unless explicitly allowed - if (!includeAllJobs && !_jobs.CanBeAntag(session)) - return false; + var query = QueryActiveRules(); + var rules = new List<(EntityUid, AntagSelectionComponent)>(); + while (query.MoveNext(out var uid, out _, out var antag, out _)) + { + rules.Add((uid, antag)); + } + RobustRandom.Shuffle(rules); - //Check if the entity is already an antag - switch (acceptableAntags) + foreach (var (uid, antag) in rules) { - //If we dont want to select any antag roles - case AntagAcceptability.None: - { - if (_roleSystem.MindIsAntagonist(playerMind)) - return false; - break; - } - //If we dont want to select exclusive antag roles - case AntagAcceptability.NotExclusive: - { - if (_roleSystem.MindIsExclusiveAntagonist(playerMind)) - return false; - break; - } + if (!RobustRandom.Prob(LateJoinRandomChance)) + continue; + + if (!antag.Definitions.Any(p => p.LateJoinAdditional)) + continue; + + DebugTools.AssertEqual(antag.SelectionTime, AntagSelectionTime.PostPlayerSpawn); + + if (!TryGetNextAvailableDefinition((uid, antag), out var def)) + continue; + + if (TryMakeAntag((uid, antag), args.Player, def.Value)) + break; } + } - //Unless explictly allowed, ignore non humanoids (eg pets) - if (!allowNonHumanoids && !HasComp(playerEntity)) - return false; + protected override void Added(EntityUid uid, AntagSelectionComponent component, GameRuleComponent gameRule, GameRuleAddedEvent args) + { + base.Added(uid, component, gameRule, args); - //If a custom condition was provided, test it and exclude the player if it returns true - if (customExcludeCondition != null && customExcludeCondition(playerEntity)) - return false; + for (var i = 0; i < component.Definitions.Count; i++) + { + var def = component.Definitions[i]; + if (def.MinRange != null) + { + def.Min = def.MinRange.Value.Next(RobustRandom); + } - return true; + if (def.MaxRange != null) + { + def.Max = def.MaxRange.Value.Next(RobustRandom); + } + } } - /// - /// Check if the session is eligible for a role, can be run prior to the session being attached to an entity - /// - /// Player session to check - /// Which antag prototype to check for - /// Ignore if the player has enabled this antag - /// True if the session matches the requirements, false otherwise - public bool IsSessionEligible(ICommonSession session, ProtoId antagPrototype, bool ignorePreferences = false) + protected override void Started(EntityUid uid, AntagSelectionComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args) { - //Exclude disconnected or zombie sessions - //No point giving antag roles to them - if (session.Status == SessionStatus.Disconnected || - session.Status == SessionStatus.Zombie) - return false; + base.Started(uid, component, gameRule, args); - //Check the player has this antag preference selected - //Unless we are ignoring preferences, in which case add them anyway - var pref = (HumanoidCharacterProfile) _prefs.GetPreferences(session.UserId).SelectedCharacter; - if (!pref.AntagPreferences.Contains(antagPrototype.Id) && !ignorePreferences) - return false; + // If the round has not yet started, we defer antag selection until roundstart + if (GameTicker.RunLevel != GameRunLevel.InRound) + return; - return true; + if (component.SelectionsComplete) + return; + + var players = _playerManager.Sessions + .Where(x => GameTicker.PlayerGameStatuses[x.UserId] == PlayerGameStatus.JoinedGame) + .ToList(); + + ChooseAntags((uid, component), players); } - #endregion /// - /// Helper method to calculate the number of antags to select based upon the number of players + /// Chooses antagonists from the given selection of players /// - /// How many players there are on the server - /// How many players should there be for an additional antag - /// Maximum number of antags allowed - /// The number of antags that should be chosen - public int CalculateAntagCount(int playerCount, int playersPerAntag, int maxAntags) + public void ChooseAntags(Entity ent, IList pool) { - return Math.Clamp(playerCount / playersPerAntag, 1, maxAntags); + if (ent.Comp.SelectionsComplete) + return; + + foreach (var def in ent.Comp.Definitions) + { + ChooseAntags(ent, pool, def); + } + + ent.Comp.SelectionsComplete = true; } - #region Antag Selection /// - /// Selects a set number of entities from several lists, prioritising the first list till its empty, then second list etc + /// Chooses antagonists from the given selection of players for the given antag definition. /// - /// Array of lists, which are chosen from in order until the correct number of items are selected - /// How many items to select - /// Up to the specified count of elements from all provided lists - public List ChooseAntags(int count, params List[] eligiblePlayerLists) + public void ChooseAntags(Entity ent, IList pool, AntagSelectionDefinition def) { - var chosenPlayers = new List(); - foreach (var playerList in eligiblePlayerLists) + var playerPool = GetPlayerPool(ent, pool, def); + var count = GetTargetAntagCount(ent, GetTotalPlayerCount(pool), def); + + for (var i = 0; i < count; i++) { - //Remove all chosen players from this list, to prevent duplicates - foreach (var chosenPlayer in chosenPlayers) + var session = (ICommonSession?) null; + if (def.PickPlayer) { - playerList.Remove(chosenPlayer); - } + if (!playerPool.TryPickAndTake(RobustRandom, out session)) + break; - //If we have reached the desired number of players, skip - if (chosenPlayers.Count >= count) - continue; + if (ent.Comp.SelectedSessions.Contains(session)) + continue; + } - //Pick and choose a random number of players from this list - chosenPlayers.AddRange(ChooseAntags(count - chosenPlayers.Count, playerList)); + MakeAntag(ent, session, def); } - return chosenPlayers; } + /// - /// Helper method to choose antags from a list + /// Tries to makes a given player into the specified antagonist. /// - /// List of eligible players - /// How many to choose - /// Up to the specified count of elements from the provided list - public List ChooseAntags(int count, List eligiblePlayers) + public bool TryMakeAntag(Entity ent, ICommonSession? session, AntagSelectionDefinition def, bool ignoreSpawner = false, bool checkPref = true) { - var chosenPlayers = new List(); - - for (var i = 0; i < count; i++) - { - if (eligiblePlayers.Count == 0) - break; + if (checkPref && !HasPrimaryAntagPreference(session, def)) + return false; - chosenPlayers.Add(RobustRandom.PickAndTake(eligiblePlayers)); - } + if (!IsSessionValid(ent, session, def) || !IsEntityValid(session?.AttachedEntity, def)) + return false; - return chosenPlayers; + MakeAntag(ent, session, def, ignoreSpawner); + return true; } /// - /// Selects a set number of sessions from several lists, prioritising the first list till its empty, then second list etc + /// Makes a given player into the specified antagonist. /// - /// Array of lists, which are chosen from in order until the correct number of items are selected - /// How many items to select - /// Up to the specified count of elements from all provided lists - public List ChooseAntags(int count, params List[] eligiblePlayerLists) + public void MakeAntag(Entity ent, ICommonSession? session, AntagSelectionDefinition def, bool ignoreSpawner = false) { - var chosenPlayers = new List(); - foreach (var playerList in eligiblePlayerLists) + var antagEnt = (EntityUid?) null; + var isSpawner = false; + + if (session != null) + { + ent.Comp.SelectedSessions.Add(session); + + // we shouldn't be blocking the entity if they're just a ghost or smth. + if (!HasComp(session.AttachedEntity)) + antagEnt = session.AttachedEntity; + } + else if (!ignoreSpawner && def.SpawnerPrototype != null) // don't add spawners if we have a player, dummy. + { + antagEnt = Spawn(def.SpawnerPrototype); + isSpawner = true; + } + + if (!antagEnt.HasValue) { - //Remove all chosen players from this list, to prevent duplicates - foreach (var chosenPlayer in chosenPlayers) + var getEntEv = new AntagSelectEntityEvent(session, ent); + RaiseLocalEvent(ent, ref getEntEv, true); + + if (!getEntEv.Handled) { - playerList.Remove(chosenPlayer); + throw new InvalidOperationException($"Attempted to make {session} antagonist in gamerule {ToPrettyString(ent)} but there was no valid entity for player."); } - //If we have reached the desired number of players, skip - if (chosenPlayers.Count >= count) - continue; + antagEnt = getEntEv.Entity; + } - //Pick and choose a random number of players from this list - chosenPlayers.AddRange(ChooseAntags(count - chosenPlayers.Count, playerList)); + if (antagEnt is not { } player) + return; + + var getPosEv = new AntagSelectLocationEvent(session, ent); + RaiseLocalEvent(ent, ref getPosEv, true); + if (getPosEv.Handled) + { + var playerXform = Transform(player); + var pos = RobustRandom.Pick(getPosEv.Coordinates); + _transform.SetMapCoordinates((player, playerXform), pos); } - return chosenPlayers; - } - /// - /// Helper method to choose sessions from a list - /// - /// List of eligible sessions - /// How many to choose - /// Up to the specified count of elements from the provided list - public List ChooseAntags(int count, List eligiblePlayers) - { - var chosenPlayers = new List(); - for (int i = 0; i < count; i++) + if (isSpawner) { - if (eligiblePlayers.Count == 0) - break; + if (!TryComp(player, out var spawnerComp)) + { + Log.Error("Antag spawner with GhostRoleAntagSpawnerComponent."); + return; + } - chosenPlayers.Add(RobustRandom.PickAndTake(eligiblePlayers)); + spawnerComp.Rule = ent; + spawnerComp.Definition = def; + return; } - return chosenPlayers; + EntityManager.AddComponents(player, def.Components); + _stationSpawning.EquipStartingGear(player, def.StartingGear); + + if (session != null) + { + var curMind = session.GetMind(); + if (curMind == null) + { + curMind = _mind.CreateMind(session.UserId, Name(antagEnt.Value)); + _mind.SetUserId(curMind.Value, session.UserId); + } + + _mind.TransferTo(curMind.Value, antagEnt, ghostCheckOverride: true); + _role.MindAddRoles(curMind.Value, def.MindComponents); + ent.Comp.SelectedMinds.Add((curMind.Value, Name(player))); + SendBriefing(session, def.Briefing); + } + + var afterEv = new AfterAntagEntitySelectedEvent(session, player, ent, def); + RaiseLocalEvent(ent, ref afterEv, true); } - #endregion - #region Briefings /// - /// Helper method to send the briefing text and sound to a list of entities + /// Gets an ordered player pool based on player preferences and the antagonist definition. /// - /// The players chosen to be antags - /// The briefing text to send - /// The color the briefing should be, null for default - /// The sound to briefing/greeting sound to play - public void SendBriefing(List entities, string briefing, Color? briefingColor, SoundSpecifier? briefingSound) + public AntagSelectionPlayerPool GetPlayerPool(Entity ent, IList sessions, AntagSelectionDefinition def) { - foreach (var entity in entities) + var preferredList = new List(); + var fallbackList = new List(); + foreach (var session in sessions) { - SendBriefing(entity, briefing, briefingColor, briefingSound); + if (!IsSessionValid(ent, session, def) || !IsEntityValid(session.AttachedEntity, def)) + continue; + + if (HasPrimaryAntagPreference(session, def)) + { + preferredList.Add(session); + } + else if (HasFallbackAntagPreference(session, def)) + { + fallbackList.Add(session); + } } + + return new AntagSelectionPlayerPool(new() { preferredList, fallbackList }); } /// - /// Helper method to send the briefing text and sound to a player entity + /// Checks if a given session is valid for an antagonist. /// - /// The entity chosen to be antag - /// The briefing text to send - /// The color the briefing should be, null for default - /// The sound to briefing/greeting sound to play - public void SendBriefing(EntityUid entity, string briefing, Color? briefingColor, SoundSpecifier? briefingSound) + public bool IsSessionValid(Entity ent, ICommonSession? session, AntagSelectionDefinition def, EntityUid? mind = null) { - if (!_mindSystem.TryGetMind(entity, out _, out var mindComponent)) - return; + if (session == null) + return true; - if (mindComponent.Session == null) - return; + if (session.Status is SessionStatus.Disconnected or SessionStatus.Zombie) + return false; - SendBriefing(mindComponent.Session, briefing, briefingColor, briefingSound); - } + if (ent.Comp.SelectedSessions.Contains(session)) + return false; - /// - /// Helper method to send the briefing text and sound to a list of sessions - /// - /// - /// - /// - /// + mind ??= session.GetMind(); - public void SendBriefing(List sessions, string briefing, Color? briefingColor, SoundSpecifier? briefingSound) - { - foreach (var session in sessions) + // If the player has not spawned in as any entity (e.g., in the lobby), they can be given an antag role/entity. + if (mind == null) + return true; + + //todo: we need some way to check that we're not getting the same role twice. (double picking thieves or zombies through midrounds) + + switch (def.MultiAntagSetting) { - SendBriefing(session, briefing, briefingColor, briefingSound); + case AntagAcceptability.None: + { + if (_role.MindIsAntagonist(mind)) + return false; + break; + } + case AntagAcceptability.NotExclusive: + { + if (_role.MindIsExclusiveAntagonist(mind)) + return false; + break; + } } + + // todo: expand this to allow for more fine antag-selection logic for game rules. + if (!_jobs.CanBeAntag(session)) + return false; + + return true; } + /// - /// Helper method to send the briefing text and sound to a session + /// Checks if a given entity (mind/session not included) is valid for a given antagonist. /// - /// The player chosen to be an antag - /// The briefing text to send - /// The color the briefing should be, null for default - /// The sound to briefing/greeting sound to play - - public void SendBriefing(ICommonSession session, string briefing, Color? briefingColor, SoundSpecifier? briefingSound) + public bool IsEntityValid(EntityUid? entity, AntagSelectionDefinition def) { - _audioSystem.PlayGlobal(briefingSound, session); - var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", briefing)); - ChatManager.ChatMessageToOne(ChatChannel.Server, briefing, wrappedMessage, default, false, session.Channel, briefingColor); + // If the player has not spawned in as any entity (e.g., in the lobby), they can be given an antag role/entity. + if (entity == null) + return true; + + if (HasComp(entity)) + return false; + + if (!def.AllowNonHumans && !HasComp(entity)) + return false; + + if (def.Whitelist != null) + { + if (!def.Whitelist.IsValid(entity.Value)) + return false; + } + + if (def.Blacklist != null) + { + if (def.Blacklist.IsValid(entity.Value)) + return false; + } + + return true; } - #endregion } + +/// +/// Event raised on a game rule entity in order to determine what the antagonist entity will be. +/// Only raised if the selected player's current entity is invalid. +/// +[ByRefEvent] +public record struct AntagSelectEntityEvent(ICommonSession? Session, Entity GameRule) +{ + public readonly ICommonSession? Session = Session; + + public bool Handled => Entity != null; + + public EntityUid? Entity; +} + +/// +/// Event raised on a game rule entity to determine the location for the antagonist. +/// +[ByRefEvent] +public record struct AntagSelectLocationEvent(ICommonSession? Session, Entity GameRule) +{ + public readonly ICommonSession? Session = Session; + + public bool Handled => Coordinates.Any(); + + public List Coordinates = new(); +} + +/// +/// Event raised on a game rule entity after the setup logic for an antag is complete. +/// Used for applying additional more complex setup logic. +/// +[ByRefEvent] +public readonly record struct AfterAntagEntitySelectedEvent(ICommonSession? Session, EntityUid EntityUid, Entity GameRule, AntagSelectionDefinition Def); diff --git a/Content.Server/Antag/Components/AntagSelectionComponent.cs b/Content.Server/Antag/Components/AntagSelectionComponent.cs new file mode 100644 index 0000000000..096be14049 --- /dev/null +++ b/Content.Server/Antag/Components/AntagSelectionComponent.cs @@ -0,0 +1,189 @@ +using Content.Server.Administration.Systems; +using Content.Server.Destructible.Thresholds; +using Content.Shared.Antag; +using Content.Shared.Roles; +using Content.Shared.Storage; +using Content.Shared.Whitelist; +using Robust.Shared.Audio; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; + +namespace Content.Server.Antag.Components; + +[RegisterComponent, Access(typeof(AntagSelectionSystem), typeof(AdminVerbSystem))] +public sealed partial class AntagSelectionComponent : Component +{ + /// + /// Has the primary selection of antagonists finished yet? + /// + [DataField] + public bool SelectionsComplete; + + /// + /// The definitions for the antagonists + /// + [DataField] + public List Definitions = new(); + + /// + /// The minds and original names of the players selected to be antagonists. + /// + [DataField] + public List<(EntityUid, string)> SelectedMinds = new(); + + /// + /// When the antag selection will occur. + /// + [DataField] + public AntagSelectionTime SelectionTime = AntagSelectionTime.PostPlayerSpawn; + + /// + /// Cached sessions of players who are chosen. Used so we don't have to rebuild the pool multiple times in a tick. + /// Is not serialized. + /// + public HashSet SelectedSessions = new(); +} + +[DataDefinition] +public partial struct AntagSelectionDefinition() +{ + /// + /// A list of antagonist roles that are used for selecting which players will be antagonists. + /// + [DataField] + public List> PrefRoles = new(); + + /// + /// Fallback for . Useful if you need multiple role preferences for a team antagonist. + /// + [DataField] + public List> FallbackRoles = new(); + + /// + /// Should we allow people who already have an antagonist role? + /// + [DataField] + public AntagAcceptability MultiAntagSetting = AntagAcceptability.None; + + /// + /// The minimum number of this antag. + /// + [DataField] + public int Min = 1; + + /// + /// The maximum number of this antag. + /// + [DataField] + public int Max = 1; + + /// + /// A range used to randomly select + /// + [DataField] + public MinMax? MinRange; + + /// + /// A range used to randomly select + /// + [DataField] + public MinMax? MaxRange; + + /// + /// a player to antag ratio: used to determine the amount of antags that will be present. + /// + [DataField] + public int PlayerRatio = 10; + + /// + /// Whether or not players should be picked to inhabit this antag or not. + /// + [DataField] + public bool PickPlayer = true; + + /// + /// If true, players that latejoin into a round have a chance of being converted into antagonists. + /// + [DataField] + public bool LateJoinAdditional = false; + + //todo: find out how to do this with minimal boilerplate: filler department, maybe? + //public HashSet> JobBlacklist = new() + + /// + /// Mostly just here for legacy compatibility and reducing boilerplate + /// + [DataField] + public bool AllowNonHumans = false; + + /// + /// A whitelist for selecting which players can become this antag. + /// + [DataField] + public EntityWhitelist? Whitelist; + + /// + /// A blacklist for selecting which players can become this antag. + /// + [DataField] + public EntityWhitelist? Blacklist; + + /// + /// Components added to the player. + /// + [DataField] + public ComponentRegistry Components = new(); + + /// + /// Components added to the player's mind. + /// + [DataField] + public ComponentRegistry MindComponents = new(); + + /// + /// A set of starting gear that's equipped to the player. + /// + [DataField] + public ProtoId? StartingGear; + + /// + /// A briefing shown to the player. + /// + [DataField] + public BriefingData? Briefing; + + /// + /// A spawner used to defer the selection of this particular definition. + /// + /// + /// Not the cleanest way of doing this code but it's just an odd specific behavior. + /// Sue me. + /// + [DataField] + public EntProtoId? SpawnerPrototype; +} + +/// +/// Contains data used to generate a briefing. +/// +[DataDefinition] +public partial struct BriefingData +{ + /// + /// The text shown + /// + [DataField] + public LocId? Text; + + /// + /// The color of the text. + /// + [DataField] + public Color? Color; + + /// + /// The sound played. + /// + [DataField] + public SoundSpecifier? Sound; +} diff --git a/Content.Server/Antag/Components/GhostRoleAntagSpawnerComponent.cs b/Content.Server/Antag/Components/GhostRoleAntagSpawnerComponent.cs new file mode 100644 index 0000000000..fcaa4d4267 --- /dev/null +++ b/Content.Server/Antag/Components/GhostRoleAntagSpawnerComponent.cs @@ -0,0 +1,14 @@ +namespace Content.Server.Antag.Components; + +/// +/// Ghost role spawner that creates an antag for the associated gamerule. +/// +[RegisterComponent, Access(typeof(AntagSelectionSystem))] +public sealed partial class GhostRoleAntagSpawnerComponent : Component +{ + [DataField] + public EntityUid? Rule; + + [DataField] + public AntagSelectionDefinition? Definition; +} diff --git a/Content.Server/Antag/Mimic/MobReplacementRuleComponent.cs b/Content.Server/Antag/Mimic/MobReplacementRuleComponent.cs index 0824d48ae2..0c7e257d2b 100644 --- a/Content.Server/Antag/Mimic/MobReplacementRuleComponent.cs +++ b/Content.Server/Antag/Mimic/MobReplacementRuleComponent.cs @@ -23,7 +23,7 @@ public sealed partial class MobReplacementRuleComponent : Component /// Chance per-entity. /// [DataField] - public float Chance = 0.001f; + public float Chance = 0.004f; [DataField] public bool DoAnnouncement = true; diff --git a/Content.Server/Antag/MobReplacementRuleSystem.cs b/Content.Server/Antag/MobReplacementRuleSystem.cs index ba09c84bce..dc8103497e 100644 --- a/Content.Server/Antag/MobReplacementRuleSystem.cs +++ b/Content.Server/Antag/MobReplacementRuleSystem.cs @@ -1,10 +1,8 @@ using System.Numerics; -using Content.Server.Advertise.Components; -using Content.Server.Advertise.EntitySystems; using Content.Server.Antag.Mimic; using Content.Server.Chat.Systems; using Content.Server.GameTicking.Rules; -using Content.Server.GameTicking.Rules.Components; +using Content.Server.GameTicking.Components; using Content.Server.NPC.Systems; using Content.Server.Station.Systems; using Content.Server.GameTicking; @@ -21,6 +19,8 @@ using Content.Server.NPC.HTN; using Content.Server.NPC; using Content.Shared.Weapons.Melee; +using Content.Server.Advertise.EntitySystems; +using Content.Server.Advertise.Components; using Content.Server.Power.Components; using Content.Shared.CombatMode; @@ -37,7 +37,7 @@ public sealed class MobReplacementRuleSystem : GameRuleSystem - /// It's like Build a Bear, but MURDER - /// - /// + /// It's like Build a Bear, but MURDER public void BuildAMimicWorkshop(EntityUid uid, MobReplacementRuleComponent component) { var metaData = MetaData(uid); var vendorPrototype = metaData.EntityPrototype; - var mimicProto = _prototype.Index(component.Proto); + var mimicProto = _prototype.Index(component.Proto); var vendorComponents = vendorPrototype?.Components.Keys .Where(n => n != "Transform" && n != "MetaData") @@ -131,11 +126,8 @@ public void BuildAMimicWorkshop(EntityUid uid, MobReplacementRuleComponent compo && component.VendorModify) SetupMimicVendor(uid, component, vendor); } - /// - /// This handles getting the entity ready to be a hostile NPC - /// - /// - /// + + /// This handles getting the entity ready to be a hostile NPC private void SetupMimicNPC(EntityUid uid, MobReplacementRuleComponent component) { _physics.SetBodyType(uid, BodyType.KinematicController); @@ -161,12 +153,7 @@ private void SetupMimicNPC(EntityUid uid, MobReplacementRuleComponent component) _npc.WakeNPC(uid, htn); } - /// /// Handling specific interactions with vending machines - /// - /// - /// - /// private void SetupMimicVendor(EntityUid uid, MobReplacementRuleComponent mimicComponent, AdvertiseComponent vendorComponent) { vendorComponent.MinimumWait = 5; diff --git a/Content.Server/Arachne/ArachneSystem.cs b/Content.Server/Arachne/ArachneSystem.cs deleted file mode 100644 index 9cdefb441b..0000000000 --- a/Content.Server/Arachne/ArachneSystem.cs +++ /dev/null @@ -1,231 +0,0 @@ -using Content.Shared.Arachne; -using Content.Shared.Actions; -using Content.Shared.IdentityManagement; -using Content.Shared.Verbs; -using Content.Shared.Buckle.Components; -using Content.Shared.DoAfter; -using Content.Shared.Stunnable; -using Content.Shared.Eye.Blinding.Systems; -using Content.Shared.Containers.ItemSlots; -using Content.Shared.Damage; -using Content.Shared.Inventory; -using Content.Shared.Administration.Logs; -using Content.Shared.Database; -using Content.Shared.Humanoid; -using Content.Shared.Nutrition.EntitySystems; -using Content.Server.Buckle.Systems; -using Content.Server.Popups; -using Content.Server.DoAfter; -using Content.Server.Body.Components; -using Content.Server.Vampiric; -using Content.Server.Speech.Components; -using Robust.Shared.Physics.Components; -using Robust.Shared.Containers; -using Robust.Shared.Map; -using Robust.Shared.Utility; -using Robust.Server.Console; - -namespace Content.Server.Arachne -{ - public sealed class ArachneSystem : EntitySystem - { - [Dependency] private readonly PopupSystem _popupSystem = default!; - [Dependency] private readonly DoAfterSystem _doAfter = default!; - [Dependency] private readonly BuckleSystem _buckleSystem = default!; - [Dependency] private readonly ItemSlotsSystem _itemSlots = default!; - [Dependency] private readonly BlindableSystem _blindableSystem = default!; - [Dependency] private readonly DamageableSystem _damageableSystem = default!; - - [Dependency] private readonly IServerConsoleHost _host = default!; - [Dependency] private readonly BloodSuckerSystem _bloodSuckerSystem = default!; - [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; - - private const string BodySlot = "body_slot"; - - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent>(AddCocoonVerb); - - SubscribeLocalEvent(OnCocEntInserted); - SubscribeLocalEvent(OnCocEntRemoved); - SubscribeLocalEvent(OnDamageChanged); - SubscribeLocalEvent>(AddSuccVerb); - SubscribeLocalEvent(OnCocoonDoAfter); - } - - private void AddCocoonVerb(EntityUid uid, ArachneComponent component, GetVerbsEvent args) - { - if (!args.CanAccess || !args.CanInteract) - return; - - if (args.Target == uid) - return; - - if (!TryComp(args.Target, out var bloodstream)) - return; - - if (bloodstream.BloodReagent != component.WebBloodReagent) - return; - - InnateVerb verb = new() - { - Act = () => - { - StartCocooning(uid, component, args.Target); - }, - Text = Loc.GetString("cocoon"), - Priority = 2 - }; - args.Verbs.Add(verb); - } - - private void OnCocEntInserted(EntityUid uid, CocoonComponent component, EntInsertedIntoContainerMessage args) - { - _blindableSystem.UpdateIsBlind(args.Entity); - EnsureComp(args.Entity); - - if (TryComp(args.Entity, out var currentAccent)) - { - component.WasReplacementAccent = true; - component.OldAccent = currentAccent.Accent; - currentAccent.Accent = "mumble"; - } else - { - component.WasReplacementAccent = false; - var replacement = EnsureComp(args.Entity); - replacement.Accent = "mumble"; - } - } - - private void OnCocEntRemoved(EntityUid uid, CocoonComponent component, EntRemovedFromContainerMessage args) - { - if (component.WasReplacementAccent && TryComp(args.Entity, out var replacement)) - { - replacement.Accent = component.OldAccent; - } else - { - RemComp(args.Entity); - } - - RemComp(args.Entity); - _blindableSystem.UpdateIsBlind(args.Entity); - } - - private void OnDamageChanged(EntityUid uid, CocoonComponent component, DamageChangedEvent args) - { - if (!args.DamageIncreased) - return; - - if (args.DamageDelta == null) - return; - - var body = _itemSlots.GetItemOrNull(uid, BodySlot); - - if (body == null) - return; - - var damage = args.DamageDelta * component.DamagePassthrough; - _damageableSystem.TryChangeDamage(body, damage); - } - - private void AddSuccVerb(EntityUid uid, CocoonComponent component, GetVerbsEvent args) - { - if (!args.CanAccess || !args.CanInteract) - return; - - if (!TryComp(args.User, out var sucker)) - return; - - if (!sucker.WebRequired) - return; - - var victim = _itemSlots.GetItemOrNull(uid, BodySlot); - - if (victim == null) - return; - - if (!TryComp(victim, out var stream)) - return; - - AlternativeVerb verb = new() - { - Act = () => - { - _bloodSuckerSystem.StartSuccDoAfter(args.User, victim.Value, sucker, stream, false); // start doafter - }, - Text = Loc.GetString("action-name-suck-blood"), - Icon = new SpriteSpecifier.Texture(new ("/Textures/Nyanotrasen/Icons/verbiconfangs.png")), - Priority = 2 - }; - args.Verbs.Add(verb); - } - - private void OnEntRemoved(EntityUid uid, WebComponent web, EntRemovedFromContainerMessage args) - { - if (!TryComp(uid, out var strap)) - return; - - if (HasComp(args.Entity)) - _buckleSystem.StrapSetEnabled(uid, false, strap); - } - - private void StartCocooning(EntityUid uid, ArachneComponent component, EntityUid target) - { - _popupSystem.PopupEntity(Loc.GetString("cocoon-start-third-person", ("target", Identity.Entity(target, EntityManager)), ("spider", Identity.Entity(uid, EntityManager))), uid, - Shared.Popups.PopupType.MediumCaution); - - _popupSystem.PopupEntity(Loc.GetString("cocoon-start-second-person", ("target", Identity.Entity(target, EntityManager))), uid, uid, Shared.Popups.PopupType.Medium); - - var delay = component.CocoonDelay; - - if (HasComp(target)) - delay *= component.CocoonKnockdownMultiplier; - - // Is it good practice to use empty data just to disambiguate doafters - // Who knows, there's no docs! - var ev = new ArachneCocoonDoAfterEvent(); - - var args = new DoAfterArgs(EntityManager, uid, delay, ev, uid, target: target) - { - BreakOnUserMove = true, - BreakOnTargetMove = true, - }; - - _doAfter.TryStartDoAfter(args); - } - - private void OnCocoonDoAfter(EntityUid uid, ArachneComponent component, ArachneCocoonDoAfterEvent args) - { - if (args.Handled || args.Cancelled || args.Args.Target == null) - return; - - var spawnProto = HasComp(args.Args.Target) ? "CocoonedHumanoid" : "CocoonSmall"; - Transform(args.Args.Target.Value).AttachToGridOrMap(); - var cocoon = Spawn(spawnProto, Transform(args.Args.Target.Value).Coordinates); - - if (!TryComp(cocoon, out var slots)) - return; - - // todo: our species should use scale visuals probably... - // TODO: We need a client-accessible notion of scale influence here. - /* if (spawnProto == "CocoonedHumanoid" && TryComp(args.Args.Target.Value, out var sprite)) */ - /* { */ - /* // why the fuck is this only available as a console command. */ - /* _host.ExecuteCommand(null, "scale " + cocoon + " " + sprite.Scale.Y); */ - if (TryComp(args.Args.Target.Value, out var physics)) - { - var scale = Math.Clamp(1 / (35 / physics.FixturesMass), 0.35, 2.5); - _host.ExecuteCommand(null, "scale " + cocoon + " " + scale); - } - _itemSlots.SetLock(cocoon, BodySlot, false, slots); - _itemSlots.TryInsert(cocoon, BodySlot, args.Args.Target.Value, args.Args.User); - _itemSlots.SetLock(cocoon, BodySlot, true, slots); - - var impact = (spawnProto == "CocoonedHumanoid") ? LogImpact.High : LogImpact.Medium; - - _adminLogger.Add(LogType.Action, impact, $"{ToPrettyString(args.Args.User):player} cocooned {ToPrettyString(args.Args.Target.Value):target}"); - args.Handled = true; - } - } -} diff --git a/Content.Server/Arachne/CocoonComponent.cs b/Content.Server/Arachne/CocoonComponent.cs deleted file mode 100644 index 42ecf27971..0000000000 --- a/Content.Server/Arachne/CocoonComponent.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Content.Server.Arachne -{ - [RegisterComponent] - public sealed partial class CocoonComponent : Component - { - public bool WasReplacementAccent = false; - - public string OldAccent = ""; - - [DataField("damagePassthrough")] - public float DamagePassthrough = 0.5f; - } -} diff --git a/Content.Server/Arcade/BlockGame/BlockGame.Ui.cs b/Content.Server/Arcade/BlockGame/BlockGame.Ui.cs index ef69600783..cd22f1f6d3 100644 --- a/Content.Server/Arcade/BlockGame/BlockGame.Ui.cs +++ b/Content.Server/Arcade/BlockGame/BlockGame.Ui.cs @@ -157,39 +157,37 @@ private void InputTick(float frameTime) /// The message to broadcase to all players/spectators. private void SendMessage(BoundUserInterfaceMessage message) { - if (_uiSystem.TryGetUi(_owner, BlockGameUiKey.Key, out var bui)) - _uiSystem.SendUiMessage(bui, message); + _uiSystem.ServerSendUiMessage(_entityManager.GetEntity(message.Entity), BlockGameUiKey.Key, message); } /// /// Handles sending a message to a specific player/spectator. /// /// The message to send to a specific player/spectator. - /// The target recipient. - private void SendMessage(BoundUserInterfaceMessage message, ICommonSession session) + /// The target recipient. + private void SendMessage(BoundUserInterfaceMessage message, EntityUid actor) { - if (_uiSystem.TryGetUi(_owner, BlockGameUiKey.Key, out var bui)) - _uiSystem.TrySendUiMessage(bui, message, session); + _uiSystem.ServerSendUiMessage(_entityManager.GetEntity(message.Entity), BlockGameUiKey.Key, message, actor); } /// /// Handles sending the current state of the game to a player that has just opened the UI. /// - /// The target recipient. - public void UpdateNewPlayerUI(ICommonSession session) + /// The target recipient. + public void UpdateNewPlayerUI(EntityUid actor) { if (_gameOver) { - SendMessage(new BlockGameMessages.BlockGameGameOverScreenMessage(Points, _highScorePlacement?.LocalPlacement, _highScorePlacement?.GlobalPlacement), session); + SendMessage(new BlockGameMessages.BlockGameGameOverScreenMessage(Points, _highScorePlacement?.LocalPlacement, _highScorePlacement?.GlobalPlacement), actor); return; } if (Paused) - SendMessage(new BlockGameMessages.BlockGameSetScreenMessage(BlockGameMessages.BlockGameScreen.Pause, Started), session); + SendMessage(new BlockGameMessages.BlockGameSetScreenMessage(BlockGameMessages.BlockGameScreen.Pause, Started), actor); else - SendMessage(new BlockGameMessages.BlockGameSetScreenMessage(BlockGameMessages.BlockGameScreen.Game, Started), session); + SendMessage(new BlockGameMessages.BlockGameSetScreenMessage(BlockGameMessages.BlockGameScreen.Game, Started), actor); - FullUpdate(session); + FullUpdate(actor); } /// @@ -209,14 +207,14 @@ private void FullUpdate() /// Handles broadcasting the full player-visible game state to a specific player/spectator. /// /// The target recipient. - private void FullUpdate(ICommonSession session) + private void FullUpdate(EntityUid actor) { - UpdateFieldUI(session); - SendNextPieceUpdate(session); - SendHoldPieceUpdate(session); - SendLevelUpdate(session); - SendPointsUpdate(session); - SendHighscoreUpdate(session); + UpdateFieldUI(actor); + SendNextPieceUpdate(actor); + SendHoldPieceUpdate(actor); + SendLevelUpdate(actor); + SendPointsUpdate(actor); + SendHighscoreUpdate(actor); } /// @@ -234,14 +232,13 @@ public void UpdateFieldUI() /// /// Handles broadcasting the current location of all of the blocks in the playfield + the active piece to a specific player/spectator. /// - /// The target recipient. - public void UpdateFieldUI(ICommonSession session) + public void UpdateFieldUI(EntityUid actor) { if (!Started) return; var computedField = ComputeField(); - SendMessage(new BlockGameMessages.BlockGameVisualUpdateMessage(computedField.ToArray(), BlockGameMessages.BlockGameVisualType.GameField), session); + SendMessage(new BlockGameMessages.BlockGameVisualUpdateMessage(computedField.ToArray(), BlockGameMessages.BlockGameVisualType.GameField), actor); } /// @@ -282,10 +279,9 @@ private void SendNextPieceUpdate() /// /// Broadcasts the state of the next queued piece to a specific viewer. /// - /// The target recipient. - private void SendNextPieceUpdate(ICommonSession session) + private void SendNextPieceUpdate(EntityUid actor) { - SendMessage(new BlockGameMessages.BlockGameVisualUpdateMessage(NextPiece.BlocksForPreview(), BlockGameMessages.BlockGameVisualType.NextBlock), session); + SendMessage(new BlockGameMessages.BlockGameVisualUpdateMessage(NextPiece.BlocksForPreview(), BlockGameMessages.BlockGameVisualType.NextBlock), actor); } /// @@ -302,13 +298,12 @@ private void SendHoldPieceUpdate() /// /// Broadcasts the state of the currently held piece to a specific viewer. /// - /// The target recipient. - private void SendHoldPieceUpdate(ICommonSession session) + private void SendHoldPieceUpdate(EntityUid actor) { if (HeldPiece.HasValue) - SendMessage(new BlockGameMessages.BlockGameVisualUpdateMessage(HeldPiece.Value.BlocksForPreview(), BlockGameMessages.BlockGameVisualType.HoldBlock), session); + SendMessage(new BlockGameMessages.BlockGameVisualUpdateMessage(HeldPiece.Value.BlocksForPreview(), BlockGameMessages.BlockGameVisualType.HoldBlock), actor); else - SendMessage(new BlockGameMessages.BlockGameVisualUpdateMessage(Array.Empty(), BlockGameMessages.BlockGameVisualType.HoldBlock), session); + SendMessage(new BlockGameMessages.BlockGameVisualUpdateMessage(Array.Empty(), BlockGameMessages.BlockGameVisualType.HoldBlock), actor); } /// @@ -322,10 +317,9 @@ private void SendLevelUpdate() /// /// Broadcasts the current game level to a specific viewer. /// - /// The target recipient. - private void SendLevelUpdate(ICommonSession session) + private void SendLevelUpdate(EntityUid actor) { - SendMessage(new BlockGameMessages.BlockGameLevelUpdateMessage(Level), session); + SendMessage(new BlockGameMessages.BlockGameLevelUpdateMessage(Level), actor); } /// @@ -339,10 +333,9 @@ private void SendPointsUpdate() /// /// Broadcasts the current game score to a specific viewer. /// - /// The target recipient. - private void SendPointsUpdate(ICommonSession session) + private void SendPointsUpdate(EntityUid actor) { - SendMessage(new BlockGameMessages.BlockGameScoreUpdateMessage(Points), session); + SendMessage(new BlockGameMessages.BlockGameScoreUpdateMessage(Points), actor); } /// @@ -356,9 +349,8 @@ private void SendHighscoreUpdate() /// /// Broadcasts the current game high score positions to a specific viewer. /// - /// The target recipient. - private void SendHighscoreUpdate(ICommonSession session) + private void SendHighscoreUpdate(EntityUid actor) { - SendMessage(new BlockGameMessages.BlockGameHighScoreUpdateMessage(_arcadeSystem.GetLocalHighscores(), _arcadeSystem.GetGlobalHighscores()), session); + SendMessage(new BlockGameMessages.BlockGameHighScoreUpdateMessage(_arcadeSystem.GetLocalHighscores(), _arcadeSystem.GetGlobalHighscores()), actor); } } diff --git a/Content.Server/Arcade/BlockGame/BlockGame.cs b/Content.Server/Arcade/BlockGame/BlockGame.cs index 3af1828d56..a6707af408 100644 --- a/Content.Server/Arcade/BlockGame/BlockGame.cs +++ b/Content.Server/Arcade/BlockGame/BlockGame.cs @@ -2,6 +2,7 @@ using Robust.Server.GameObjects; using Robust.Shared.Random; using System.Linq; +using Content.Shared.Mood; namespace Content.Server.Arcade.BlockGame; @@ -9,8 +10,8 @@ public sealed partial class BlockGame { [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IRobustRandom _random = default!; - private readonly ArcadeSystem _arcadeSystem = default!; - private readonly UserInterfaceSystem _uiSystem = default!; + private readonly ArcadeSystem _arcadeSystem; + private readonly UserInterfaceSystem _uiSystem; /// /// What entity is currently hosting this game of NT-BG. @@ -78,10 +79,12 @@ private void InvokeGameover() _gameOver = true; if (_entityManager.TryGetComponent(_owner, out var cabinet) - && _entityManager.TryGetComponent(cabinet.Player?.AttachedEntity, out var meta)) + && _entityManager.TryGetComponent(cabinet.Player, out var meta)) { _highScorePlacement = _arcadeSystem.RegisterHighScore(meta.EntityName, Points); SendHighscoreUpdate(); + var ev = new MoodEffectEvent("ArcadePlay"); + _entityManager.EventBus.RaiseLocalEvent(meta.Owner, ev); } SendMessage(new BlockGameMessages.BlockGameGameOverScreenMessage(Points, _highScorePlacement?.LocalPlacement, _highScorePlacement?.GlobalPlacement)); } diff --git a/Content.Server/Arcade/BlockGame/BlockGameArcadeComponent.cs b/Content.Server/Arcade/BlockGame/BlockGameArcadeComponent.cs index 5613d91544..75952b0a33 100644 --- a/Content.Server/Arcade/BlockGame/BlockGameArcadeComponent.cs +++ b/Content.Server/Arcade/BlockGame/BlockGameArcadeComponent.cs @@ -13,10 +13,10 @@ public sealed partial class BlockGameArcadeComponent : Component /// /// The player currently playing the active session of NT-BG. /// - public ICommonSession? Player = null; + public EntityUid? Player = null; /// /// The players currently viewing (but not playing) the active session of NT-BG. /// - public readonly List Spectators = new(); + public readonly List Spectators = new(); } diff --git a/Content.Server/Arcade/BlockGame/BlockGameArcadeSystem.cs b/Content.Server/Arcade/BlockGame/BlockGameArcadeSystem.cs index ad65c5cca6..561cad8d7e 100644 --- a/Content.Server/Arcade/BlockGame/BlockGameArcadeSystem.cs +++ b/Content.Server/Arcade/BlockGame/BlockGameArcadeSystem.cs @@ -37,14 +37,12 @@ public override void Update(float frameTime) } } - private void UpdatePlayerStatus(EntityUid uid, ICommonSession session, PlayerBoundUserInterface? bui = null, BlockGameArcadeComponent? blockGame = null) + private void UpdatePlayerStatus(EntityUid uid, EntityUid actor, BlockGameArcadeComponent? blockGame = null) { if (!Resolve(uid, ref blockGame)) return; - if (bui == null && !_uiSystem.TryGetUi(uid, BlockGameUiKey.Key, out bui)) - return; - _uiSystem.TrySendUiMessage(bui, new BlockGameMessages.BlockGameUserStatusMessage(blockGame.Player == session), session); + _uiSystem.ServerSendUiMessage(uid, BlockGameUiKey.Key, new BlockGameMessages.BlockGameUserStatusMessage(blockGame.Player == actor), actor); } private void OnComponentInit(EntityUid uid, BlockGameArcadeComponent component, ComponentInit args) @@ -54,33 +52,21 @@ private void OnComponentInit(EntityUid uid, BlockGameArcadeComponent component, private void OnAfterUIOpen(EntityUid uid, BlockGameArcadeComponent component, AfterActivatableUIOpenEvent args) { - if (!TryComp(args.User, out var actor)) - return; - if (!_uiSystem.TryGetUi(uid, BlockGameUiKey.Key, out var bui)) - return; - - var session = actor.PlayerSession; - if (!bui.SubscribedSessions.Contains(session)) - return; - if (component.Player == null) - component.Player = session; + component.Player = args.Actor; else - component.Spectators.Add(session); + component.Spectators.Add(args.Actor); - UpdatePlayerStatus(uid, session, bui, component); - component.Game?.UpdateNewPlayerUI(session); + UpdatePlayerStatus(uid, args.Actor, component); + component.Game?.UpdateNewPlayerUI(args.Actor); } private void OnAfterUiClose(EntityUid uid, BlockGameArcadeComponent component, BoundUIClosedEvent args) { - if (args.Session is not { } session) - return; - - if (component.Player != session) + if (component.Player != args.Actor) { - component.Spectators.Remove(session); - UpdatePlayerStatus(uid, session, blockGame: component); + component.Spectators.Remove(args.Actor); + UpdatePlayerStatus(uid, args.Actor, blockGame: component); return; } @@ -88,11 +74,11 @@ private void OnAfterUiClose(EntityUid uid, BlockGameArcadeComponent component, B if (component.Spectators.Count > 0) { component.Player = component.Spectators[0]; - component.Spectators.Remove(component.Player); - UpdatePlayerStatus(uid, component.Player, blockGame: component); + component.Spectators.Remove(component.Player.Value); + UpdatePlayerStatus(uid, component.Player.Value, blockGame: component); } - UpdatePlayerStatus(uid, temp, blockGame: component); + UpdatePlayerStatus(uid, temp.Value, blockGame: component); } private void OnBlockPowerChanged(EntityUid uid, BlockGameArcadeComponent component, ref PowerChangedEvent args) @@ -100,8 +86,7 @@ private void OnBlockPowerChanged(EntityUid uid, BlockGameArcadeComponent compone if (args.Powered) return; - if (_uiSystem.TryGetUi(uid, BlockGameUiKey.Key, out var bui)) - _uiSystem.CloseAll(bui); + _uiSystem.CloseUi(uid, BlockGameUiKey.Key); component.Player = null; component.Spectators.Clear(); } @@ -112,7 +97,7 @@ private void OnPlayerAction(EntityUid uid, BlockGameArcadeComponent component, B return; if (!BlockGameUiKey.Key.Equals(msg.UiKey)) return; - if (msg.Session != component.Player) + if (msg.Actor != component.Player) return; if (msg.PlayerAction == BlockGamePlayerAction.NewGame) diff --git a/Content.Server/Arcade/SpaceVillainGame/SpaceVillainArcadeSystem.cs b/Content.Server/Arcade/SpaceVillainGame/SpaceVillainArcadeSystem.cs index f60d88ebf7..07a4d044ca 100644 --- a/Content.Server/Arcade/SpaceVillainGame/SpaceVillainArcadeSystem.cs +++ b/Content.Server/Arcade/SpaceVillainGame/SpaceVillainArcadeSystem.cs @@ -2,6 +2,7 @@ using Content.Shared.UserInterface; using Content.Server.Advertise; using Content.Server.Advertise.Components; +using Content.Shared.Mood; using static Content.Shared.Arcade.SharedSpaceVillainArcadeComponent; using Robust.Server.GameObjects; using Robust.Shared.Audio; @@ -76,6 +77,8 @@ private void OnSVPlayerAction(EntityUid uid, SpaceVillainArcadeComponent compone if (!TryComp(uid, out var power) || !power.Powered) return; + RaiseLocalEvent(EntityManager.GetEntity(msg.Entity), new MoodEffectEvent("ArcadePlay")); + switch (msg.PlayerAction) { case PlayerAction.Attack: @@ -90,12 +93,10 @@ private void OnSVPlayerAction(EntityUid uid, SpaceVillainArcadeComponent compone _audioSystem.PlayPvs(component.NewGameSound, uid, AudioParams.Default.WithVolume(-4f)); component.Game = new SpaceVillainGame(uid, component, this); - if (_uiSystem.TryGetUi(uid, SpaceVillainArcadeUiKey.Key, out var bui)) - _uiSystem.SendUiMessage(bui, component.Game.GenerateMetaDataMessage()); + _uiSystem.ServerSendUiMessage(uid, SpaceVillainArcadeUiKey.Key, component.Game.GenerateMetaDataMessage()); break; case PlayerAction.RequestData: - if (_uiSystem.TryGetUi(uid, SpaceVillainArcadeUiKey.Key, out bui)) - _uiSystem.SendUiMessage(bui, component.Game.GenerateMetaDataMessage()); + _uiSystem.ServerSendUiMessage(uid, SpaceVillainArcadeUiKey.Key, component.Game.GenerateMetaDataMessage()); break; } } @@ -110,7 +111,6 @@ private void OnSVillainPower(EntityUid uid, SpaceVillainArcadeComponent componen if (TryComp(uid, out var power) && power.Powered) return; - if (_uiSystem.TryGetUi(uid, SpaceVillainArcadeUiKey.Key, out var bui)) - _uiSystem.CloseAll(bui); + _uiSystem.CloseUi(uid, SpaceVillainArcadeUiKey.Key); } } diff --git a/Content.Server/Arcade/SpaceVillainGame/SpaceVillainGame.Ui.cs b/Content.Server/Arcade/SpaceVillainGame/SpaceVillainGame.Ui.cs index 890e9888a7..ebcfb8e3f6 100644 --- a/Content.Server/Arcade/SpaceVillainGame/SpaceVillainGame.Ui.cs +++ b/Content.Server/Arcade/SpaceVillainGame/SpaceVillainGame.Ui.cs @@ -9,8 +9,7 @@ public sealed partial class SpaceVillainGame /// private void UpdateUi(EntityUid uid, bool metadata = false) { - if (_uiSystem.TryGetUi(uid, SpaceVillainArcadeUiKey.Key, out var bui)) - _uiSystem.SendUiMessage(bui, metadata ? GenerateMetaDataMessage() : GenerateUpdateMessage()); + _uiSystem.ServerSendUiMessage(uid, SpaceVillainArcadeUiKey.Key, metadata ? GenerateMetaDataMessage() : GenerateUpdateMessage()); } private void UpdateUi(EntityUid uid, string message1, string message2, bool metadata = false) diff --git a/Content.Server/Atmos/Components/BreathToolComponent.cs b/Content.Server/Atmos/Components/BreathToolComponent.cs index f3688ef7ff..ae17a5d872 100644 --- a/Content.Server/Atmos/Components/BreathToolComponent.cs +++ b/Content.Server/Atmos/Components/BreathToolComponent.cs @@ -12,9 +12,10 @@ public sealed partial class BreathToolComponent : Component /// /// Tool is functional only in allowed slots /// - [DataField("allowedSlots")] + [DataField] public SlotFlags AllowedSlots = SlotFlags.MASK | SlotFlags.HEAD; public bool IsFunctional; + public EntityUid? ConnectedInternalsEntity; } } diff --git a/Content.Server/Atmos/Components/FlammableComponent.cs b/Content.Server/Atmos/Components/FlammableComponent.cs index 33a163228f..9f39af540d 100644 --- a/Content.Server/Atmos/Components/FlammableComponent.cs +++ b/Content.Server/Atmos/Components/FlammableComponent.cs @@ -11,49 +11,65 @@ public sealed partial class FlammableComponent : Component [ViewVariables(VVAccess.ReadWrite)] [DataField] - public bool OnFire { get; set; } + public bool OnFire; [ViewVariables(VVAccess.ReadWrite)] [DataField] - public float FireStacks { get; set; } + public float FireStacks; [ViewVariables(VVAccess.ReadWrite)] - [DataField("fireSpread")] + [DataField] + public float MaximumFireStacks = 10f; + + [ViewVariables(VVAccess.ReadWrite)] + [DataField] + public float MinimumFireStacks = -10f; + + [ViewVariables(VVAccess.ReadWrite)] + [DataField] + public string FlammableFixtureID = "flammable"; + + [ViewVariables(VVAccess.ReadWrite)] + [DataField] + public float MinIgnitionTemperature = 373.15f; + + [ViewVariables(VVAccess.ReadWrite)] + [DataField] public bool FireSpread { get; private set; } = false; [ViewVariables(VVAccess.ReadWrite)] - [DataField("canResistFire")] + [DataField] public bool CanResistFire { get; private set; } = false; - [DataField("damage", required: true)] + [DataField(required: true)] [ViewVariables(VVAccess.ReadWrite)] public DamageSpecifier Damage = new(); // Empty by default, we don't want any funny NREs. /// /// Used for the fixture created to handle passing firestacks when two flammable objects collide. /// - [DataField("flammableCollisionShape")] + [DataField] public IPhysShape FlammableCollisionShape = new PhysShapeCircle(0.35f); /// /// Should the component be set on fire by interactions with isHot entities /// [ViewVariables(VVAccess.ReadWrite)] - [DataField("alwaysCombustible")] + [DataField] public bool AlwaysCombustible = false; /// /// Can the component anyhow lose its FireStacks? /// [ViewVariables(VVAccess.ReadWrite)] - [DataField("canExtinguish")] + [DataField] public bool CanExtinguish = true; /// /// How many firestacks should be applied to component when being set on fire? /// [ViewVariables(VVAccess.ReadWrite)] - [DataField("firestacksOnIgnite")] + [DataField] public float FirestacksOnIgnite = 2.0f; /// diff --git a/Content.Server/Atmos/Components/GasMixtureHolderComponent.cs b/Content.Server/Atmos/Components/GasMixtureHolderComponent.cs index 9d533f6ec4..98feb43346 100644 --- a/Content.Server/Atmos/Components/GasMixtureHolderComponent.cs +++ b/Content.Server/Atmos/Components/GasMixtureHolderComponent.cs @@ -1,4 +1,6 @@ -namespace Content.Server.Atmos.Components +using Content.Shared.Atmos; + +namespace Content.Server.Atmos.Components { [RegisterComponent] public sealed partial class GasMixtureHolderComponent : Component, IGasMixtureHolder diff --git a/Content.Server/Atmos/Components/MapAtmosphereComponent.cs b/Content.Server/Atmos/Components/MapAtmosphereComponent.cs index 6bdef901d4..bdd05e7849 100644 --- a/Content.Server/Atmos/Components/MapAtmosphereComponent.cs +++ b/Content.Server/Atmos/Components/MapAtmosphereComponent.cs @@ -1,3 +1,4 @@ +using Content.Shared.Atmos; using Content.Shared.Atmos.Components; using Content.Shared.Atmos.EntitySystems; diff --git a/Content.Server/Atmos/Consoles/AtmosAlertsComputerSystem.cs b/Content.Server/Atmos/Consoles/AtmosAlertsComputerSystem.cs new file mode 100644 index 0000000000..5d040cd7b3 --- /dev/null +++ b/Content.Server/Atmos/Consoles/AtmosAlertsComputerSystem.cs @@ -0,0 +1,356 @@ +using Content.Server.Atmos.Monitor.Components; +using Content.Server.DeviceNetwork.Components; +using Content.Server.Power.Components; +using Content.Shared.Atmos; +using Content.Shared.Atmos.Components; +using Content.Shared.Atmos.Consoles; +using Content.Shared.Atmos.Monitor; +using Content.Shared.Atmos.Monitor.Components; +using Content.Shared.Pinpointer; +using Robust.Server.GameObjects; +using Robust.Shared.Map.Components; +using Robust.Shared.Player; +using Robust.Shared.ContentPack; +using Robust.Shared.Prototypes; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Content.Shared.Access.Components; +using Content.Shared.Database; +using Content.Shared.NameIdentifier; +using Content.Shared.Stacks; +using JetBrains.Annotations; +using Robust.Shared.Utility; + +namespace Content.Server.Atmos.Monitor.Systems; + +public sealed class AtmosAlertsComputerSystem : SharedAtmosAlertsComputerSystem +{ + [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; + [Dependency] private readonly AirAlarmSystem _airAlarmSystem = default!; + [Dependency] private readonly AtmosDeviceNetworkSystem _atmosDevNet = default!; + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + + private const float UpdateTime = 1.0f; + + // Note: this data does not need to be saved + private float _updateTimer = 1.0f; + + public override void Initialize() + { + base.Initialize(); + + // Console events + SubscribeLocalEvent(OnConsoleInit); + SubscribeLocalEvent(OnConsoleParentChanged); + SubscribeLocalEvent(OnFocusChangedMessage); + + // Grid events + SubscribeLocalEvent(OnGridSplit); + SubscribeLocalEvent(OnDeviceAnchorChanged); + } + + #region Event handling + + private void OnConsoleInit(EntityUid uid, AtmosAlertsComputerComponent component, ComponentInit args) + { + InitalizeConsole(uid, component); + } + + private void OnConsoleParentChanged(EntityUid uid, AtmosAlertsComputerComponent component, EntParentChangedMessage args) + { + InitalizeConsole(uid, component); + } + + private void OnFocusChangedMessage(EntityUid uid, AtmosAlertsComputerComponent component, AtmosAlertsComputerFocusChangeMessage args) + { + component.FocusDevice = args.FocusDevice; + } + + private void OnGridSplit(ref GridSplitEvent args) + { + // Collect grids + var allGrids = args.NewGrids.ToList(); + + if (!allGrids.Contains(args.Grid)) + allGrids.Add(args.Grid); + + // Update atmos monitoring consoles that stand upon an updated grid + var query = AllEntityQuery(); + while (query.MoveNext(out var ent, out var entConsole, out var entXform)) + { + if (entXform.GridUid == null) + continue; + + if (!allGrids.Contains(entXform.GridUid.Value)) + continue; + + InitalizeConsole(ent, entConsole); + } + } + + private void OnDeviceAnchorChanged(EntityUid uid, AtmosAlertsDeviceComponent component, AnchorStateChangedEvent args) + { + var xform = Transform(uid); + var gridUid = xform.GridUid; + + if (gridUid == null) + return; + + if (!TryGetAtmosDeviceNavMapData(uid, component, xform, gridUid.Value, out var data)) + return; + + var netEntity = EntityManager.GetNetEntity(uid); + + var query = AllEntityQuery(); + while (query.MoveNext(out var ent, out var entConsole, out var entXform)) + { + if (gridUid != entXform.GridUid) + continue; + + if (args.Anchored) + entConsole.AtmosDevices.Add(data.Value); + + else if (!args.Anchored) + entConsole.AtmosDevices.RemoveWhere(x => x.NetEntity == netEntity); + } + } + + #endregion + + public override void Update(float frameTime) + { + base.Update(frameTime); + + _updateTimer += frameTime; + + if (_updateTimer >= UpdateTime) + { + _updateTimer -= UpdateTime; + + // Keep a list of UI entries for each gridUid, in case multiple consoles stand on the same grid + var airAlarmEntriesForEachGrid = new Dictionary(); + var fireAlarmEntriesForEachGrid = new Dictionary(); + + var query = AllEntityQuery(); + while (query.MoveNext(out var ent, out var entConsole, out var entXform)) + { + if (entXform?.GridUid == null) + continue; + + // Make a list of alarm state data for all the air and fire alarms on the grid + if (!airAlarmEntriesForEachGrid.TryGetValue(entXform.GridUid.Value, out var airAlarmEntries)) + { + airAlarmEntries = GetAlarmStateData(entXform.GridUid.Value, AtmosAlertsComputerGroup.AirAlarm).ToArray(); + airAlarmEntriesForEachGrid[entXform.GridUid.Value] = airAlarmEntries; + } + + if (!fireAlarmEntriesForEachGrid.TryGetValue(entXform.GridUid.Value, out var fireAlarmEntries)) + { + fireAlarmEntries = GetAlarmStateData(entXform.GridUid.Value, AtmosAlertsComputerGroup.FireAlarm).ToArray(); + fireAlarmEntriesForEachGrid[entXform.GridUid.Value] = fireAlarmEntries; + } + + // Determine the highest level of alert for the console (based on non-silenced alarms) + var highestAlert = AtmosAlarmType.Invalid; + + foreach (var entry in airAlarmEntries) + { + if (entry.AlarmState > highestAlert && !entConsole.SilencedDevices.Contains(entry.NetEntity)) + highestAlert = entry.AlarmState; + } + + foreach (var entry in fireAlarmEntries) + { + if (entry.AlarmState > highestAlert && !entConsole.SilencedDevices.Contains(entry.NetEntity)) + highestAlert = entry.AlarmState; + } + + // Update the appearance of the console based on the highest recorded level of alert + if (TryComp(ent, out var entAppearance)) + _appearance.SetData(ent, AtmosAlertsComputerVisuals.ComputerLayerScreen, (int) highestAlert, entAppearance); + + // If the console UI is open, send UI data to each subscribed session + UpdateUIState(ent, airAlarmEntries, fireAlarmEntries, entConsole, entXform); + } + } + } + + public void UpdateUIState + (EntityUid uid, + AtmosAlertsComputerEntry[] airAlarmStateData, + AtmosAlertsComputerEntry[] fireAlarmStateData, + AtmosAlertsComputerComponent component, + TransformComponent xform) + { + if (!_uiSystem.IsUiOpen(uid, AtmosAlertsComputerUiKey.Key)) + return; + + var gridUid = xform.GridUid!.Value; + + if (!HasComp(gridUid)) + return; + + // The grid must have a NavMapComponent to visualize the map in the UI + EnsureComp(gridUid); + + // Gathering remaining data to be send to the client + var focusAlarmData = GetFocusAlarmData(uid, GetEntity(component.FocusDevice), gridUid); + + // Set the UI state + _uiSystem.SetUiState(uid, AtmosAlertsComputerUiKey.Key, + new AtmosAlertsComputerBoundInterfaceState(airAlarmStateData, fireAlarmStateData, focusAlarmData)); + } + + private List GetAlarmStateData(EntityUid gridUid, AtmosAlertsComputerGroup group) + { + var alarmStateData = new List(); + + var queryAlarms = AllEntityQuery(); + while (queryAlarms.MoveNext(out var ent, out var entDevice, out var entAtmosAlarmable, out var entDeviceNetwork, out var entXform)) + { + if (entXform.GridUid != gridUid) + continue; + + if (!entXform.Anchored) + continue; + + if (entDevice.Group != group) + continue; + + // If emagged, change the alarm type to normal + var alarmState = (entAtmosAlarmable.LastAlarmState == AtmosAlarmType.Emagged) ? AtmosAlarmType.Normal : entAtmosAlarmable.LastAlarmState; + + // Unpowered alarms can't sound + if (TryComp(ent, out var entAPCPower) && !entAPCPower.Powered) + alarmState = AtmosAlarmType.Invalid; + + var entry = new AtmosAlertsComputerEntry + (GetNetEntity(ent), + GetNetCoordinates(entXform.Coordinates), + entDevice.Group, + alarmState, + MetaData(ent).EntityName, + entDeviceNetwork.Address); + + alarmStateData.Add(entry); + } + + return alarmStateData; + } + + private AtmosAlertsFocusDeviceData? GetFocusAlarmData(EntityUid uid, EntityUid? focusDevice, EntityUid gridUid) + { + if (focusDevice == null) + return null; + + var focusDeviceXform = Transform(focusDevice.Value); + + if (!focusDeviceXform.Anchored || + focusDeviceXform.GridUid != gridUid || + !TryComp(focusDevice.Value, out var focusDeviceAirAlarm)) + { + return null; + } + + // Force update the sensors attached to the alarm + if (!_uiSystem.IsUiOpen(focusDevice.Value, SharedAirAlarmInterfaceKey.Key)) + { + _atmosDevNet.Register(focusDevice.Value, null); + _atmosDevNet.Sync(focusDevice.Value, null); + + foreach ((var address, var _) in focusDeviceAirAlarm.SensorData) + _atmosDevNet.Register(uid, null); + } + + // Get the sensor data + var temperatureData = (_airAlarmSystem.CalculateTemperatureAverage(focusDeviceAirAlarm), AtmosAlarmType.Normal); + var pressureData = (_airAlarmSystem.CalculatePressureAverage(focusDeviceAirAlarm), AtmosAlarmType.Normal); + var gasData = new Dictionary(); + + foreach ((var address, var sensorData) in focusDeviceAirAlarm.SensorData) + { + if (sensorData.TemperatureThreshold.CheckThreshold(sensorData.Temperature, out var temperatureState) && + (int) temperatureState > (int) temperatureData.Item2) + { + temperatureData = (temperatureData.Item1, temperatureState); + } + + if (sensorData.PressureThreshold.CheckThreshold(sensorData.Pressure, out var pressureState) && + (int) pressureState > (int) pressureData.Item2) + { + pressureData = (pressureData.Item1, pressureState); + } + + if (focusDeviceAirAlarm.SensorData.Sum(g => g.Value.TotalMoles) > 1e-8) + { + foreach ((var gas, var threshold) in sensorData.GasThresholds) + { + if (!gasData.ContainsKey(gas)) + { + float mol = _airAlarmSystem.CalculateGasMolarConcentrationAverage(focusDeviceAirAlarm, gas, out var percentage); + + if (mol < 1e-8) + continue; + + gasData[gas] = (mol, percentage, AtmosAlarmType.Normal); + } + + if (threshold.CheckThreshold(gasData[gas].Item2, out var gasState) && + (int) gasState > (int) gasData[gas].Item3) + { + gasData[gas] = (gasData[gas].Item1, gasData[gas].Item2, gasState); + } + } + } + } + + return new AtmosAlertsFocusDeviceData(GetNetEntity(focusDevice.Value), temperatureData, pressureData, gasData); + } + + private HashSet GetAllAtmosDeviceNavMapData(EntityUid gridUid) + { + var atmosDeviceNavMapData = new HashSet(); + + var query = AllEntityQuery(); + while (query.MoveNext(out var ent, out var entComponent, out var entXform)) + { + if (TryGetAtmosDeviceNavMapData(ent, entComponent, entXform, gridUid, out var data)) + atmosDeviceNavMapData.Add(data.Value); + } + + return atmosDeviceNavMapData; + } + + private bool TryGetAtmosDeviceNavMapData + (EntityUid uid, + AtmosAlertsDeviceComponent component, + TransformComponent xform, + EntityUid gridUid, + [NotNullWhen(true)] out AtmosAlertsDeviceNavMapData? output) + { + output = null; + + if (xform.GridUid != gridUid) + return false; + + if (!xform.Anchored) + return false; + + output = new AtmosAlertsDeviceNavMapData(GetNetEntity(uid), GetNetCoordinates(xform.Coordinates), component.Group); + + return true; + } + + private void InitalizeConsole(EntityUid uid, AtmosAlertsComputerComponent component) + { + var xform = Transform(uid); + + if (xform.GridUid == null) + return; + + var grid = xform.GridUid.Value; + component.AtmosDevices = GetAllAtmosDeviceNavMapData(grid); + + Dirty(uid, component); + } +} diff --git a/Content.Server/Atmos/EntitySystems/AirFilterSystem.cs b/Content.Server/Atmos/EntitySystems/AirFilterSystem.cs index 2ab15cfb17..c3344c830c 100644 --- a/Content.Server/Atmos/EntitySystems/AirFilterSystem.cs +++ b/Content.Server/Atmos/EntitySystems/AirFilterSystem.cs @@ -12,7 +12,6 @@ namespace Content.Server.Atmos.EntitySystems; public sealed class AirFilterSystem : EntitySystem { [Dependency] private readonly AtmosphereSystem _atmosphere = default!; - [Dependency] private readonly IMapManager _map = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; public override void Initialize() diff --git a/Content.Server/Atmos/EntitySystems/AirtightSystem.cs b/Content.Server/Atmos/EntitySystems/AirtightSystem.cs index 152fba8fc4..cd07da7112 100644 --- a/Content.Server/Atmos/EntitySystems/AirtightSystem.cs +++ b/Content.Server/Atmos/EntitySystems/AirtightSystem.cs @@ -12,6 +12,7 @@ public sealed class AirtightSystem : EntitySystem [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; [Dependency] private readonly ExplosionSystem _explosionSystem = default!; + [Dependency] private readonly SharedMapSystem _mapSystem = default!; public override void Initialize() { @@ -24,16 +25,19 @@ public override void Initialize() private void OnAirtightInit(Entity airtight, ref ComponentInit args) { - var xform = EntityManager.GetComponent(airtight); - - if (airtight.Comp.FixAirBlockedDirectionInitialize) + // TODO AIRTIGHT what FixAirBlockedDirectionInitialize even for? + if (!airtight.Comp.FixAirBlockedDirectionInitialize) { - var moveEvent = new MoveEvent(airtight, default, default, Angle.Zero, xform.LocalRotation, xform, false); - if (AirtightMove(airtight, ref moveEvent)) - return; + UpdatePosition(airtight); + return; } - UpdatePosition(airtight); + var xform = Transform(airtight); + airtight.Comp.CurrentAirBlockedDirection = + (int) Rotate((AtmosDirection) airtight.Comp.InitialAirBlockedDirection, xform.LocalRotation); + UpdatePosition(airtight, xform); + var airtightEv = new AirtightChanged(airtight, airtight, false, default); + RaiseLocalEvent(airtight, ref airtightEv, true); } private void OnAirtightShutdown(Entity airtight, ref ComponentShutdown args) @@ -41,13 +45,8 @@ private void OnAirtightShutdown(Entity airtight, ref Componen var xform = Transform(airtight); // If the grid is deleting no point updating atmos. - if (HasComp(xform.GridUid) && - MetaData(xform.GridUid.Value).EntityLifeStage > EntityLifeStage.MapInitialized) - { - return; - } - - SetAirblocked(airtight, false, xform); + if (xform.GridUid != null && LifeStage(xform.GridUid.Value) <= EntityLifeStage.MapInitialized) + SetAirblocked(airtight, false, xform); } private void OnAirtightPositionChanged(EntityUid uid, AirtightComponent airtight, ref AnchorStateChangedEvent args) @@ -59,12 +58,14 @@ private void OnAirtightPositionChanged(EntityUid uid, AirtightComponent airtight var gridId = xform.GridUid; var coords = xform.Coordinates; - - var tilePos = grid.TileIndicesFor(coords); + var tilePos = _mapSystem.TileIndicesFor(gridId.Value, grid, coords); // Update and invalidate new position. airtight.LastPosition = (gridId.Value, tilePos); InvalidatePosition(gridId.Value, tilePos); + + var airtightEv = new AirtightChanged(uid, airtight, false, (gridId.Value, tilePos)); + RaiseLocalEvent(uid, ref airtightEv, true); } private void OnAirtightReAnchor(EntityUid uid, AirtightComponent airtight, ref ReAnchorEvent args) @@ -74,24 +75,20 @@ private void OnAirtightReAnchor(EntityUid uid, AirtightComponent airtight, ref R // Update and invalidate new position. airtight.LastPosition = (gridId, args.TilePos); InvalidatePosition(gridId, args.TilePos); - } - } - private void OnAirtightMoved(Entity airtight, ref MoveEvent ev) - { - AirtightMove(airtight, ref ev); + var airtightEv = new AirtightChanged(uid, airtight, false, (gridId, args.TilePos)); + RaiseLocalEvent(uid, ref airtightEv, true); + } } - private bool AirtightMove(Entity ent, ref MoveEvent ev) + private void OnAirtightMoved(Entity ent, ref MoveEvent ev) { var (owner, airtight) = ent; - airtight.CurrentAirBlockedDirection = (int) Rotate((AtmosDirection)airtight.InitialAirBlockedDirection, ev.NewRotation); var pos = airtight.LastPosition; UpdatePosition(ent, ev.Component); - var airtightEv = new AirtightChanged(owner, airtight, pos); + var airtightEv = new AirtightChanged(owner, airtight, false, pos); RaiseLocalEvent(owner, ref airtightEv, true); - return true; } public void SetAirblocked(Entity airtight, bool airblocked, TransformComponent? xform = null) @@ -105,7 +102,7 @@ public void SetAirblocked(Entity airtight, bool airblocked, T var pos = airtight.Comp.LastPosition; airtight.Comp.AirBlocked = airblocked; UpdatePosition(airtight, xform); - var airtightEv = new AirtightChanged(airtight, airtight, pos); + var airtightEv = new AirtightChanged(airtight, airtight, true, pos); RaiseLocalEvent(airtight, ref airtightEv, true); } @@ -152,7 +149,13 @@ private AtmosDirection Rotate(AtmosDirection myDirection, Angle myAngle) } } + /// + /// Raised upon the airtight status being changed via anchoring, movement, etc. + /// + /// + /// + /// Whether the changed + /// [ByRefEvent] - public readonly record struct AirtightChanged(EntityUid Entity, AirtightComponent Airtight, - (EntityUid Grid, Vector2i Tile) Position); + public readonly record struct AirtightChanged(EntityUid Entity, AirtightComponent Airtight, bool AirBlockedChanged, (EntityUid Grid, Vector2i Tile) Position); } diff --git a/Content.Server/Atmos/EntitySystems/AtmosExposedSystem.cs b/Content.Server/Atmos/EntitySystems/AtmosExposedSystem.cs index 9590b9aa54..39469e993f 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosExposedSystem.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosExposedSystem.cs @@ -1,3 +1,4 @@ +using Content.Shared.Atmos; using Robust.Shared.Map; namespace Content.Server.Atmos.EntitySystems diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.API.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.API.cs index 614d550c2f..df31db6ba0 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.API.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.API.cs @@ -1,10 +1,10 @@ using System.Linq; using Content.Server.Atmos.Components; using Content.Server.Atmos.Piping.Components; -using Content.Server.Atmos.Reactions; using Content.Server.NodeContainer.NodeGroups; using Content.Shared.Atmos; using Content.Shared.Atmos.Components; +using Content.Shared.Atmos.Reactions; using Robust.Shared.Map.Components; using Robust.Shared.Utility; diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Commands.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Commands.cs index a5e37398c6..f711b235af 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Commands.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Commands.cs @@ -3,6 +3,7 @@ using Content.Server.Atmos.Components; using Content.Shared.Administration; using Content.Shared.Atmos; +using Content.Shared.Atmos.Components; using Robust.Shared.Console; using Robust.Shared.Map; using Robust.Shared.Map.Components; @@ -84,44 +85,72 @@ private void FixGridAtmosCommand(IConsoleShell shell, string argstr, string[] ar continue; } - var transform = Transform(euid.Value); + // Force Invalidate & update air on all tiles + Entity grid = + new(euid.Value, gridAtmosphere, Comp(euid.Value), gridComp, Transform(euid.Value)); - foreach (var (indices, tileMain) in gridAtmosphere.Tiles) - { - var tile = tileMain.Air; - if (tile == null) - continue; + RebuildGridTiles(grid); - if (!_mapSystem.TryGetTile(gridComp, indices, out var gTile) || gTile.IsEmpty) - { - gridAtmosphere.Tiles.Remove(indices); + var query = GetEntityQuery(); + foreach (var (indices, tile) in gridAtmosphere.Tiles.ToArray()) + { + if (tile.Air is not {Immutable: false} air) continue; - } - if (tile.Immutable && !IsTileSpace(euid, transform.MapUid, indices)) - { - tile = new GasMixture(tile.Volume) { Temperature = tile.Temperature }; - tileMain.Air = tile; - } - - tile.Clear(); + air.Clear(); var mixtureId = 0; - foreach (var entUid in gridComp.GetAnchoredEntities(indices)) + var enumerator = _mapSystem.GetAnchoredEntitiesEnumerator(grid, grid, indices); + while (enumerator.MoveNext(out var entUid)) { - if (!TryComp(entUid, out AtmosFixMarkerComponent? afm)) - continue; - mixtureId = afm.Mode; - break; + if (query.TryComp(entUid, out var marker)) + mixtureId = marker.Mode; } - var mixture = mixtures[mixtureId]; - Merge(tile, mixture); - tile.Temperature = mixture.Temperature; - gridAtmosphere.InvalidatedCoords.Add(indices); + var mixture = mixtures[mixtureId]; + Merge(air, mixture); + air.Temperature = mixture.Temperature; } } } + /// + /// Clears & re-creates all references to s stored on a grid. + /// + private void RebuildGridTiles( + Entity ent) + { + foreach (var indices in ent.Comp1.Tiles.Keys) + { + InvalidateVisuals((ent, ent), indices); + } + + var atmos = ent.Comp1; + atmos.MapTiles.Clear(); + atmos.ActiveTiles.Clear(); + atmos.ExcitedGroups.Clear(); + atmos.HotspotTiles.Clear(); + atmos.SuperconductivityTiles.Clear(); + atmos.HighPressureDelta.Clear(); + atmos.CurrentRunTiles.Clear(); + atmos.CurrentRunExcitedGroups.Clear(); + atmos.InvalidatedCoords.Clear(); + atmos.CurrentRunInvalidatedTiles.Clear(); + atmos.PossiblyDisconnectedTiles.Clear(); + atmos.Tiles.Clear(); + + var volume = GetVolumeForTiles(ent); + TryComp(ent.Comp4.MapUid, out MapAtmosphereComponent? mapAtmos); + + var enumerator = _map.GetAllTilesEnumerator(ent, ent); + while (enumerator.MoveNext(out var tileRef)) + { + var tile = GetOrNewTile(ent, ent, tileRef.Value.GridIndices); + UpdateTileData(ent, mapAtmos, tile); + UpdateAdjacentTiles(ent, tile, activate: true); + UpdateTileAir(ent, tile, volume); + } + } + private CompletionResult FixGridAtmosCommandCompletions(IConsoleShell shell, string[] args) { MapId? playerMap = null; diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Gases.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Gases.cs index f8ee4f4192..70c4639e48 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Gases.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Gases.cs @@ -2,6 +2,7 @@ using System.Runtime.CompilerServices; using Content.Server.Atmos.Reactions; using Content.Shared.Atmos; +using Content.Shared.Atmos.Reactions; using Robust.Shared.Prototypes; using DependencyAttribute = Robust.Shared.IoC.DependencyAttribute; diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.GridAtmosphere.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.GridAtmosphere.cs index 4b9ef49a40..3830745f68 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.GridAtmosphere.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.GridAtmosphere.cs @@ -2,6 +2,7 @@ using Content.Server.Atmos.Reactions; using Content.Shared.Atmos; using Content.Shared.Atmos.Components; +using Content.Shared.Atmos.Reactions; using Robust.Shared.Map; using Robust.Shared.Map.Components; using Robust.Shared.Utility; diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Hotspot.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Hotspot.cs index 7163b4cd44..db95223733 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Hotspot.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Hotspot.cs @@ -2,6 +2,7 @@ using Content.Server.Atmos.Reactions; using Content.Shared.Atmos; using Content.Shared.Atmos.Components; +using Content.Shared.Atmos.Reactions; using Content.Shared.Audio; using Content.Shared.Database; using Robust.Shared.Audio; diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Map.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Map.cs index 25b3b801f7..4b77d9c70d 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Map.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Map.cs @@ -1,4 +1,5 @@ using Content.Server.Atmos.Components; +using Content.Shared.Atmos; using Content.Shared.Atmos.Components; using Robust.Shared.GameStates; using Robust.Shared.Map.Components; diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs index bd023e8574..85b1a93e20 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs @@ -30,13 +30,15 @@ public sealed partial class AtmosphereSystem private int _currentRunAtmosphereIndex; private bool _simulationPaused; - private TileAtmosphere GetOrNewTile(EntityUid owner, GridAtmosphereComponent atmosphere, Vector2i index) + private TileAtmosphere GetOrNewTile(EntityUid owner, GridAtmosphereComponent atmosphere, Vector2i index, bool invalidateNew = true) { var tile = atmosphere.Tiles.GetOrNew(index, out var existing); if (existing) return tile; - atmosphere.InvalidatedCoords.Add(index); + if (invalidateNew) + atmosphere.InvalidatedCoords.Add(index); + tile.GridIndex = owner; tile.GridIndices = index; return tile; @@ -68,7 +70,7 @@ private bool ProcessRevalidate(Entity= Atmospherics.HazardHighPressure) { var damageScale = MathF.Min(((pressure / Atmospherics.HazardHighPressure) - 1) * Atmospherics.PressureDamageCoefficient, Atmospherics.MaxHighPressureDamage); - _damageableSystem.TryChangeDamage(uid, barotrauma.Damage * damageScale, true, false); + _damageableSystem.TryChangeDamage(uid, barotrauma.Damage * damageScale, true, false, canSever: false); + RaiseLocalEvent(uid, new MoodEffectEvent("MobHighPressure")); + if (!barotrauma.TakingDamage) { barotrauma.TakingDamage = true; diff --git a/Content.Server/Atmos/EntitySystems/FlammableSystem.cs b/Content.Server/Atmos/EntitySystems/FlammableSystem.cs index d34de937a4..ec0e7b0709 100644 --- a/Content.Server/Atmos/EntitySystems/FlammableSystem.cs +++ b/Content.Server/Atmos/EntitySystems/FlammableSystem.cs @@ -12,6 +12,7 @@ using Content.Shared.Damage; using Content.Shared.Database; using Content.Shared.Interaction; +using Content.Shared.Inventory; using Content.Shared.Physics; using Content.Shared.Popups; using Content.Shared.Projectiles; @@ -23,6 +24,7 @@ using Content.Shared.Weapons.Melee.Events; using Content.Shared.FixedPoint; using Robust.Server.Audio; +using Content.Shared.Mood; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Events; using Robust.Shared.Physics.Systems; @@ -41,18 +43,18 @@ public sealed class FlammableSystem : EntitySystem [Dependency] private readonly AlertsSystem _alertsSystem = default!; [Dependency] private readonly FixtureSystem _fixture = default!; [Dependency] private readonly IAdminLogManager _adminLogger = default!; + [Dependency] private readonly InventorySystem _inventory = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly UseDelaySystem _useDelay = default!; [Dependency] private readonly AudioSystem _audio = default!; [Dependency] private readonly IRobustRandom _random = default!; - public const float MinimumFireStacks = -10f; - public const float MaximumFireStacks = 20f; - private const float UpdateTime = 1f; + private EntityQuery _inventoryQuery; + private EntityQuery _physicsQuery; - public const float MinIgnitionTemperature = 373.15f; - public const string FlammableFixtureID = "flammable"; + // This should probably be moved to the component, requires a rewrite, all fires tick at the same time + private const float UpdateTime = 1f; private float _timer; @@ -62,6 +64,9 @@ public override void Initialize() { UpdatesAfter.Add(typeof(AtmosphereSystem)); + _inventoryQuery = GetEntityQuery(); + _physicsQuery = GetEntityQuery(); + SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(OnInteractUsing); SubscribeLocalEvent(OnCollide); @@ -130,7 +135,7 @@ private void OnMapInit(EntityUid uid, FlammableComponent component, MapInitEvent if (!TryComp(uid, out var body)) return; - _fixture.TryCreateFixture(uid, component.FlammableCollisionShape, FlammableFixtureID, hard: false, + _fixture.TryCreateFixture(uid, component.FlammableCollisionShape, component.FlammableFixtureID, hard: false, collisionMask: (int) CollisionGroup.FullTileLayer, body: body); } @@ -188,7 +193,7 @@ private void OnCollide(EntityUid uid, FlammableComponent flammable, ref StartCol // Normal hard collisions, though this isn't generally possible since most flammable things are mobs // which don't collide with one another, shouldn't work here. - if (args.OtherFixtureId != FlammableFixtureID && args.OurFixtureId != FlammableFixtureID) + if (args.OtherFixtureId != flammable.FlammableFixtureID && args.OurFixtureId != flammable.FlammableFixtureID) return; if (!flammable.FireSpread) @@ -203,7 +208,17 @@ private void OnCollide(EntityUid uid, FlammableComponent flammable, ref StartCol if (flammable.OnFire && otherFlammable.OnFire) { // Both are on fire -> equalize fire stacks. - var avg = (flammable.FireStacks + otherFlammable.FireStacks) / 2; + // Weight each thing's firestacks by its mass + var mass1 = 1f; + var mass2 = 1f; + if (_physicsQuery.TryComp(uid, out var physics) && _physicsQuery.TryComp(otherUid, out var otherPhys)) + { + mass1 = physics.Mass; + mass2 = otherPhys.Mass; + } + + var total = mass1 + mass2; + var avg = (flammable.FireStacks * mass1 + otherFlammable.FireStacks * mass2) / total; flammable.FireStacks = flammable.CanExtinguish ? avg : Math.Max(flammable.FireStacks, avg); otherFlammable.FireStacks = otherFlammable.CanExtinguish ? avg : Math.Max(otherFlammable.FireStacks, avg); UpdateAppearance(uid, flammable); @@ -212,25 +227,24 @@ private void OnCollide(EntityUid uid, FlammableComponent flammable, ref StartCol } // Only one is on fire -> attempt to spread the fire. - if (flammable.OnFire) + var (srcUid, srcFlammable, destUid, destFlammable) = flammable.OnFire + ? (uid, flammable, otherUid, otherFlammable) + : (otherUid, otherFlammable, uid, flammable); + + // if the thing on fire has less mass, spread less firestacks and vice versa + var ratio = 0.5f; + if (_physicsQuery.TryComp(srcUid, out var srcPhysics) && _physicsQuery.TryComp(destUid, out var destPhys)) { - otherFlammable.FireStacks += flammable.FireStacks / 2; - Ignite(otherUid, uid, otherFlammable); - if (flammable.CanExtinguish) - { - flammable.FireStacks /= 2; - UpdateAppearance(uid, flammable); - } + ratio *= srcPhysics.Mass / destPhys.Mass; } - else + + var lost = srcFlammable.FireStacks * ratio; + destFlammable.FireStacks += lost; + Ignite(destUid, srcUid, destFlammable); + if (srcFlammable.CanExtinguish) { - flammable.FireStacks += otherFlammable.FireStacks / 2; - Ignite(uid, otherUid, flammable); - if (otherFlammable.CanExtinguish) - { - otherFlammable.FireStacks /= 2; - UpdateAppearance(otherUid, otherFlammable); - } + srcFlammable.FireStacks -= lost; + UpdateAppearance(srcUid, srcFlammable); } } @@ -241,7 +255,7 @@ private void OnIsHot(EntityUid uid, FlammableComponent flammable, IsHotEvent arg private void OnTileFire(Entity ent, ref TileFireEvent args) { - var tempDelta = args.Temperature - MinIgnitionTemperature; + var tempDelta = args.Temperature - ent.Comp.MinIgnitionTemperature; _fireEvents.TryGetValue(ent, out var maxTemp); @@ -277,7 +291,7 @@ public void AdjustFireStacks(EntityUid uid, float relativeFireStacks, FlammableC if (relativeFireStacks > 0) relativeFireStacks *= flammable.FireStackIncreaseMultiplier; - flammable.FireStacks = MathF.Min(MathF.Max(MinimumFireStacks, flammable.FireStacks + relativeFireStacks), MaximumFireStacks); + flammable.FireStacks = MathF.Min(MathF.Max(flammable.MinimumFireStacks, flammable.FireStacks + relativeFireStacks), flammable.MaximumFireStacks); if (flammable.OnFire && flammable.FireStacks <= 0) Extinguish(uid, flammable); @@ -410,10 +424,12 @@ public override void Update(float frameTime) if (!flammable.OnFire) { _alertsSystem.ClearAlert(uid, AlertType.Fire); + RaiseLocalEvent(uid, new MoodRemoveEffectEvent("OnFire")); continue; } _alertsSystem.ShowAlert(uid, AlertType.Fire); + RaiseLocalEvent(uid, new MoodEffectEvent("OnFire")); if (flammable.FireStacks > 0) { @@ -426,15 +442,20 @@ public override void Update(float frameTime) continue; } - EnsureComp(uid); - _ignitionSourceSystem.SetIgnited(uid); - - var damageScale = MathF.Min( flammable.FireStacks, 5); + var source = EnsureComp(uid); + _ignitionSourceSystem.SetIgnited((uid, source)); if (TryComp(uid, out TemperatureComponent? temp)) - _temperatureSystem.ChangeHeat(uid, 12500 * damageScale, false, temp); + _temperatureSystem.ChangeHeat(uid, 12500 * flammable.FireStacks, false, temp); + + var ev = new GetFireProtectionEvent(); + // let the thing on fire handle it + RaiseLocalEvent(uid, ref ev); + // and whatever it's wearing + if (_inventoryQuery.TryComp(uid, out var inv)) + _inventory.RelayEvent((uid, inv), ref ev); - _damageableSystem.TryChangeDamage(uid, flammable.Damage * damageScale, interruptsDoAfters: false); + _damageableSystem.TryChangeDamage(uid, flammable.Damage * flammable.FireStacks * ev.Multiplier, interruptsDoAfters: false); AdjustFireStacks(uid, flammable.FirestackFade * (flammable.Resisting ? 10f : 1f), flammable); } diff --git a/Content.Server/Atmos/EntitySystems/GasAnalyzerSystem.cs b/Content.Server/Atmos/EntitySystems/GasAnalyzerSystem.cs index 552ee14232..65977f8c14 100644 --- a/Content.Server/Atmos/EntitySystems/GasAnalyzerSystem.cs +++ b/Content.Server/Atmos/EntitySystems/GasAnalyzerSystem.cs @@ -95,9 +95,9 @@ private void ActivateAnalyzer(EntityUid uid, GasAnalyzerComponent component, Ent component.Enabled = true; Dirty(component); UpdateAppearance(uid, component); - if(!HasComp(uid)) - AddComp(uid); + EnsureComp(uid); UpdateAnalyzer(uid, component); + OpenUserInterface(uid, user, component); } /// @@ -118,8 +118,7 @@ private void DisableAnalyzer(EntityUid uid, GasAnalyzerComponent? component = nu if (!Resolve(uid, ref component)) return; - if (user != null && TryComp(user, out var actor)) - _userInterface.TryClose(uid, GasAnalyzerUiKey.Key, actor.PlayerSession); + _userInterface.CloseUi(uid, GasAnalyzerUiKey.Key, user); component.Enabled = false; Dirty(component); @@ -132,8 +131,6 @@ private void DisableAnalyzer(EntityUid uid, GasAnalyzerComponent? component = nu /// private void OnDisabledMessage(EntityUid uid, GasAnalyzerComponent component, GasAnalyzerDisableMessage message) { - if (message.Session.AttachedEntity is not {Valid: true}) - return; DisableAnalyzer(uid, component); } @@ -142,10 +139,7 @@ private void OpenUserInterface(EntityUid uid, EntityUid user, GasAnalyzerCompone if (!Resolve(uid, ref component, false)) return; - if (!TryComp(user, out var actor)) - return; - - _userInterface.TryOpen(uid, GasAnalyzerUiKey.Key, actor.PlayerSession); + _userInterface.OpenUi(uid, GasAnalyzerUiKey.Key, user); } /// @@ -169,7 +163,7 @@ private bool UpdateAnalyzer(EntityUid uid, GasAnalyzerComponent? component = nul // Check if position is out of range => don't update and disable if (!component.LastPosition.Value.InRange(EntityManager, _transform, userPos, SharedInteractionSystem.InteractionRange)) { - if(component.User is { } userId && component.Enabled) + if (component.User is { } userId && component.Enabled) _popup.PopupEntity(Loc.GetString("gas-analyzer-shutoff"), userId, userId); DisableAnalyzer(uid, component, component.User); return false; @@ -182,13 +176,13 @@ private bool UpdateAnalyzer(EntityUid uid, GasAnalyzerComponent? component = nul var tileMixture = _atmo.GetContainingMixture(uid, true); if (tileMixture != null) { - gasMixList.Add(new GasMixEntry(Loc.GetString("gas-analyzer-window-environment-tab-label"), tileMixture.Pressure, tileMixture.Temperature, + gasMixList.Add(new GasMixEntry(Loc.GetString("gas-analyzer-window-environment-tab-label"), tileMixture.Volume, tileMixture.Pressure, tileMixture.Temperature, GenerateGasEntryArray(tileMixture))); } else { // No gases were found - gasMixList.Add(new GasMixEntry(Loc.GetString("gas-analyzer-window-environment-tab-label"), 0f, 0f)); + gasMixList.Add(new GasMixEntry(Loc.GetString("gas-analyzer-window-environment-tab-label"), 0f, 0f, 0f)); } var deviceFlipped = false; @@ -209,8 +203,8 @@ private bool UpdateAnalyzer(EntityUid uid, GasAnalyzerComponent? component = nul { foreach (var mixes in ev.GasMixtures) { - if(mixes.Value != null) - gasMixList.Add(new GasMixEntry(mixes.Key, mixes.Value.Pressure, mixes.Value.Temperature, GenerateGasEntryArray(mixes.Value))); + if (mixes.Item2 != null) + gasMixList.Add(new GasMixEntry(mixes.Item1, mixes.Item2.Volume, mixes.Item2.Pressure, mixes.Item2.Temperature, GenerateGasEntryArray(mixes.Item2))); } deviceFlipped = ev.DeviceFlipped; @@ -223,7 +217,16 @@ private bool UpdateAnalyzer(EntityUid uid, GasAnalyzerComponent? component = nul foreach (var pair in node.Nodes) { if (pair.Value is PipeNode pipeNode) - gasMixList.Add(new GasMixEntry(pair.Key, pipeNode.Air.Pressure, pipeNode.Air.Temperature, GenerateGasEntryArray(pipeNode.Air))); + { + // check if the volume is zero for some reason so we don't divide by zero + if (pipeNode.Air.Volume == 0f) + continue; + // only display the gas in the analyzed pipe element, not the whole system + var pipeAir = pipeNode.Air.Clone(); + pipeAir.Multiply(pipeNode.Volume / pipeNode.Air.Volume); + pipeAir.Volume = pipeNode.Volume; + gasMixList.Add(new GasMixEntry(pair.Key, pipeAir.Volume, pipeAir.Pressure, pipeAir.Temperature, GenerateGasEntryArray(pipeAir))); + } } } } @@ -233,7 +236,7 @@ private bool UpdateAnalyzer(EntityUid uid, GasAnalyzerComponent? component = nul if (gasMixList.Count == 0) return false; - _userInterface.TrySendUiMessage(uid, GasAnalyzerUiKey.Key, + _userInterface.ServerSendUiMessage(uid, GasAnalyzerUiKey.Key, new GasAnalyzerUserMessage(gasMixList.ToArray(), component.Target != null ? Name(component.Target.Value) : string.Empty, GetNetEntity(component.Target) ?? NetEntity.Invalid, @@ -286,9 +289,9 @@ private GasEntry[] GenerateGasEntryArray(GasMixture? mixture) public sealed class GasAnalyzerScanEvent : EntityEventArgs { /// - /// Key is the mix name (ex "pipe", "inlet", "filter"), value is the pipe direction and GasMixture. Add all mixes that should be reported when scanned. + /// The string is for the name (ex "pipe", "inlet", "filter"), GasMixture for the corresponding gas mix. Add all mixes that should be reported when scanned. /// - public Dictionary? GasMixtures; + public List<(string, GasMixture?)>? GasMixtures; /// /// If the device is flipped. Flipped is defined as when the inline input is 90 degrees CW to the side input diff --git a/Content.Server/Atmos/EntitySystems/GasTankSystem.cs b/Content.Server/Atmos/EntitySystems/GasTankSystem.cs index 80842416e8..07594820fc 100644 --- a/Content.Server/Atmos/EntitySystems/GasTankSystem.cs +++ b/Content.Server/Atmos/EntitySystems/GasTankSystem.cs @@ -75,7 +75,7 @@ private void OnGasTankSetPressure(Entity ent, ref GasTankSetPr public void UpdateUserInterface(Entity ent, bool initialUpdate = false) { var (owner, component) = ent; - _ui.TrySetUiState(owner, SharedGasTankUiKey.Key, + _ui.SetUiState(owner, SharedGasTankUiKey.Key, new GasTankBoundUserInterfaceState { TankPressure = component.Air?.Pressure ?? 0, @@ -359,7 +359,8 @@ public void CheckStatus(Entity ent) /// private void OnAnalyzed(EntityUid uid, GasTankComponent component, GasAnalyzerScanEvent args) { - args.GasMixtures = new Dictionary { {Name(uid), component.Air} }; + args.GasMixtures ??= new List<(string, GasMixture?)>(); + args.GasMixtures.Add((Name(uid), component.Air)); } private void OnGasTankPrice(EntityUid uid, GasTankComponent component, ref PriceCalculationEvent args) diff --git a/Content.Server/Atmos/IGasMixtureHolder.cs b/Content.Server/Atmos/IGasMixtureHolder.cs index 96efa6b983..65d7ba69a7 100644 --- a/Content.Server/Atmos/IGasMixtureHolder.cs +++ b/Content.Server/Atmos/IGasMixtureHolder.cs @@ -1,4 +1,6 @@ -namespace Content.Server.Atmos +using Content.Shared.Atmos; + +namespace Content.Server.Atmos { public interface IGasMixtureHolder { diff --git a/Content.Server/Atmos/IGasReactionEffect.cs b/Content.Server/Atmos/IGasReactionEffect.cs index bd229694bb..9fc9231908 100644 --- a/Content.Server/Atmos/IGasReactionEffect.cs +++ b/Content.Server/Atmos/IGasReactionEffect.cs @@ -1,5 +1,6 @@ using Content.Server.Atmos.EntitySystems; -using Content.Server.Atmos.Reactions; +using Content.Shared.Atmos; +using Content.Shared.Atmos.Reactions; namespace Content.Server.Atmos { diff --git a/Content.Server/Atmos/Monitor/Components/AirAlarmComponent.cs b/Content.Server/Atmos/Monitor/Components/AirAlarmComponent.cs index 7030d607a6..93f704fe21 100644 --- a/Content.Server/Atmos/Monitor/Components/AirAlarmComponent.cs +++ b/Content.Server/Atmos/Monitor/Components/AirAlarmComponent.cs @@ -24,8 +24,6 @@ public sealed partial class AirAlarmComponent : Component public readonly Dictionary ScrubberData = new(); public readonly Dictionary SensorData = new(); - public HashSet ActivePlayers = new(); - public bool CanSync = true; /// diff --git a/Content.Server/Atmos/Monitor/Systems/AirAlarmSystem.cs b/Content.Server/Atmos/Monitor/Systems/AirAlarmSystem.cs index 2922d0796a..04a9023c1d 100644 --- a/Content.Server/Atmos/Monitor/Systems/AirAlarmSystem.cs +++ b/Content.Server/Atmos/Monitor/Systems/AirAlarmSystem.cs @@ -223,8 +223,7 @@ private void OnPowerChanged(EntityUid uid, AirAlarmComponent component, ref Powe private void OnClose(EntityUid uid, AirAlarmComponent component, BoundUIClosedEvent args) { - component.ActivePlayers.Remove(args.Session.UserId); - if (component.ActivePlayers.Count == 0) + if (!_ui.IsUiOpen(uid, SharedAirAlarmInterfaceKey.Key)) RemoveActiveInterface(uid); } @@ -247,9 +246,6 @@ private void OnShutdown(EntityUid uid, AirAlarmComponent component, ComponentShu private void OnActivate(EntityUid uid, AirAlarmComponent component, ActivateInWorldEvent args) { - if (!TryComp(args.User, out var actor)) - return; - if (TryComp(uid, out var panel) && panel.Open) { args.Handled = false; @@ -259,10 +255,7 @@ private void OnActivate(EntityUid uid, AirAlarmComponent component, ActivateInWo if (!this.IsPowered(uid, EntityManager)) return; - var ui = _ui.GetUiOrNull(uid, SharedAirAlarmInterfaceKey.Key); - if (ui != null) - _ui.OpenUi(ui, actor.PlayerSession); - component.ActivePlayers.Add(actor.PlayerSession.UserId); + _ui.OpenUi(uid, SharedAirAlarmInterfaceKey.Key, args.User); AddActiveInterface(uid); SyncAllDevices(uid); UpdateUI(uid, component); @@ -270,7 +263,7 @@ private void OnActivate(EntityUid uid, AirAlarmComponent component, ActivateInWo private void OnResyncAll(EntityUid uid, AirAlarmComponent component, AirAlarmResyncAllDevicesMessage args) { - if (!AccessCheck(uid, args.Session.AttachedEntity, component)) + if (!AccessCheck(uid, args.Actor, component)) { return; } @@ -285,7 +278,7 @@ private void OnResyncAll(EntityUid uid, AirAlarmComponent component, AirAlarmRes private void OnUpdateAlarmMode(EntityUid uid, AirAlarmComponent component, AirAlarmUpdateAlarmModeMessage args) { - if (AccessCheck(uid, args.Session.AttachedEntity, component)) + if (AccessCheck(uid, args.Actor, component)) { var addr = string.Empty; if (TryComp(uid, out var netConn)) @@ -309,7 +302,7 @@ private void OnUpdateAutoMode(EntityUid uid, AirAlarmComponent component, AirAla private void OnUpdateThreshold(EntityUid uid, AirAlarmComponent component, AirAlarmUpdateAlarmThresholdMessage args) { - if (AccessCheck(uid, args.Session.AttachedEntity, component)) + if (AccessCheck(uid, args.Actor, component)) SetThreshold(uid, args.Address, args.Type, args.Threshold, args.Gas); else UpdateUI(uid, component); @@ -317,7 +310,7 @@ private void OnUpdateThreshold(EntityUid uid, AirAlarmComponent component, AirAl private void OnUpdateDeviceData(EntityUid uid, AirAlarmComponent component, AirAlarmUpdateDeviceDataMessage args) { - if (AccessCheck(uid, args.Session.AttachedEntity, component) + if (AccessCheck(uid, args.Actor, component) && _deviceList.ExistsInDeviceList(uid, args.Address)) { SetDeviceData(uid, args.Address, args.Data); @@ -330,7 +323,7 @@ private void OnUpdateDeviceData(EntityUid uid, AirAlarmComponent component, AirA private void OnCopyDeviceData(EntityUid uid, AirAlarmComponent component, AirAlarmCopyDeviceDataMessage args) { - if (!AccessCheck(uid, args.Session.AttachedEntity, component)) + if (!AccessCheck(uid, args.Actor, component)) { UpdateUI(uid, component); return; @@ -377,7 +370,7 @@ private bool AccessCheck(EntityUid uid, EntityUid? user, AirAlarmComponent? comp private void OnAtmosAlarm(EntityUid uid, AirAlarmComponent component, AtmosAlarmEvent args) { - if (component.ActivePlayers.Count != 0) + if (_ui.IsUiOpen(uid, SharedAirAlarmInterfaceKey.Key)) { SyncAllDevices(uid); } @@ -571,7 +564,7 @@ private void RemoveActiveInterface(EntityUid uid) /// private void ForceCloseAllInterfaces(EntityUid uid) { - _ui.TryCloseAll(uid, SharedAirAlarmInterfaceKey.Key); + _ui.CloseUi(uid, SharedAirAlarmInterfaceKey.Key); } private void OnAtmosUpdate(EntityUid uid, AirAlarmComponent alarm, ref AtmosDeviceUpdateEvent args) @@ -593,6 +586,21 @@ public float CalculateTemperatureAverage(AirAlarmComponent alarm) : 0f; } + public float CalculateGasMolarConcentrationAverage(AirAlarmComponent alarm, Gas gas, out float percentage) + { + percentage = 0f; + + var data = alarm.SensorData.Values.SelectMany(v => v.Gases.Where(g => g.Key == gas)); + + if (data.Count() == 0) + return 0f; + + var averageMol = data.Select(kvp => kvp.Value).Average(); + percentage = data.Select(kvp => kvp.Value).Sum() / alarm.SensorData.Values.Select(v => v.TotalMoles).Sum(); + + return averageMol; + } + public void UpdateUI(EntityUid uid, AirAlarmComponent? alarm = null, DeviceNetworkComponent? devNet = null, AtmosAlarmableComponent? alarmable = null) { if (!Resolve(uid, ref alarm, ref devNet, ref alarmable)) @@ -639,7 +647,7 @@ public void UpdateUI(EntityUid uid, AirAlarmComponent? alarm = null, DeviceNetwo highestAlarm = AtmosAlarmType.Normal; } - _ui.TrySetUiState( + _ui.SetUiState( uid, SharedAirAlarmInterfaceKey.Key, new AirAlarmUIState(devNet.Address, deviceCount, pressure, temperature, dataToSend, alarm.CurrentMode, alarm.CurrentTab, highestAlarm.Value, alarm.AutoMode)); diff --git a/Content.Server/Atmos/Piping/Binary/Components/GasPortComponent.cs b/Content.Server/Atmos/Piping/Binary/Components/GasPortComponent.cs index fb7ee6d5cf..315362383c 100644 --- a/Content.Server/Atmos/Piping/Binary/Components/GasPortComponent.cs +++ b/Content.Server/Atmos/Piping/Binary/Components/GasPortComponent.cs @@ -1,3 +1,5 @@ +using Content.Shared.Atmos; + namespace Content.Server.Atmos.Piping.Binary.Components { [RegisterComponent] diff --git a/Content.Server/Atmos/Piping/Binary/Components/GasRecyclerComponent.cs b/Content.Server/Atmos/Piping/Binary/Components/GasRecyclerComponent.cs index e1eb0072b9..aa7e3e0b36 100644 --- a/Content.Server/Atmos/Piping/Binary/Components/GasRecyclerComponent.cs +++ b/Content.Server/Atmos/Piping/Binary/Components/GasRecyclerComponent.cs @@ -1,11 +1,12 @@ using Content.Shared.Atmos; +using Content.Shared.Construction.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; namespace Content.Server.Atmos.Piping.Binary.Components { [RegisterComponent] public sealed partial class GasRecyclerComponent : Component { - [ViewVariables(VVAccess.ReadOnly)] [DataField("reacting")] public Boolean Reacting { get; set; } = false; @@ -17,10 +18,28 @@ public sealed partial class GasRecyclerComponent : Component [DataField("outlet")] public string OutletName { get; set; } = "outlet"; - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public float MinTemp = 300 + Atmospherics.T0C; - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] + public float BaseMinTemp = 300 + Atmospherics.T0C; + + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string MachinePartMinTemp = "Capacitor"; + + [DataField] + public float PartRatingMinTempMultiplier = 0.95f; + + [ViewVariables(VVAccess.ReadWrite)] public float MinPressure = 30 * Atmospherics.OneAtmosphere; + + [DataField] + public float BaseMinPressure = 30 * Atmospherics.OneAtmosphere; + + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string MachinePartMinPressure = "Manipulator"; + + [DataField] + public float PartRatingMinPressureMultiplier = 0.8f; } } diff --git a/Content.Server/Atmos/Piping/Binary/EntitySystems/GasPressurePumpSystem.cs b/Content.Server/Atmos/Piping/Binary/EntitySystems/GasPressurePumpSystem.cs index af25d04df9..83b7b67ba4 100644 --- a/Content.Server/Atmos/Piping/Binary/EntitySystems/GasPressurePumpSystem.cs +++ b/Content.Server/Atmos/Piping/Binary/EntitySystems/GasPressurePumpSystem.cs @@ -98,7 +98,7 @@ private void OnPumpLeaveAtmosphere(EntityUid uid, GasPressurePumpComponent pump, UpdateAppearance(uid, pump); DirtyUI(uid, pump); - _userInterfaceSystem.TryCloseAll(uid, GasPressurePumpUiKey.Key); + _userInterfaceSystem.CloseUi(uid, GasPressurePumpUiKey.Key); } private void OnPumpActivate(EntityUid uid, GasPressurePumpComponent pump, ActivateInWorldEvent args) @@ -108,7 +108,7 @@ private void OnPumpActivate(EntityUid uid, GasPressurePumpComponent pump, Activa if (Transform(uid).Anchored) { - _userInterfaceSystem.TryOpen(uid, GasPressurePumpUiKey.Key, actor.PlayerSession); + _userInterfaceSystem.OpenUi(uid, GasPressurePumpUiKey.Key, actor.PlayerSession); DirtyUI(uid, pump); } else @@ -123,7 +123,7 @@ private void OnToggleStatusMessage(EntityUid uid, GasPressurePumpComponent pump, { pump.Enabled = args.Enabled; _adminLogger.Add(LogType.AtmosPowerChanged, LogImpact.Medium, - $"{ToPrettyString(args.Session.AttachedEntity!.Value):player} set the power on {ToPrettyString(uid):device} to {args.Enabled}"); + $"{ToPrettyString(args.Actor):player} set the power on {ToPrettyString(uid):device} to {args.Enabled}"); DirtyUI(uid, pump); UpdateAppearance(uid, pump); } @@ -132,7 +132,7 @@ private void OnOutputPressureChangeMessage(EntityUid uid, GasPressurePumpCompone { pump.TargetPressure = Math.Clamp(args.Pressure, 0f, Atmospherics.MaxOutputPressure); _adminLogger.Add(LogType.AtmosPressureChanged, LogImpact.Medium, - $"{ToPrettyString(args.Session.AttachedEntity!.Value):player} set the pressure on {ToPrettyString(uid):device} to {args.Pressure}kPa"); + $"{ToPrettyString(args.Actor):player} set the pressure on {ToPrettyString(uid):device} to {args.Pressure}kPa"); DirtyUI(uid, pump); } @@ -142,7 +142,7 @@ private void DirtyUI(EntityUid uid, GasPressurePumpComponent? pump) if (!Resolve(uid, ref pump)) return; - _userInterfaceSystem.TrySetUiState(uid, GasPressurePumpUiKey.Key, + _userInterfaceSystem.SetUiState(uid, GasPressurePumpUiKey.Key, new GasPressurePumpBoundUserInterfaceState(EntityManager.GetComponent(uid).EntityName, pump.TargetPressure, pump.Enabled)); } diff --git a/Content.Server/Atmos/Piping/Binary/EntitySystems/GasRecyclerSystem.cs b/Content.Server/Atmos/Piping/Binary/EntitySystems/GasRecyclerSystem.cs index 3ebc509492..40b9d88846 100644 --- a/Content.Server/Atmos/Piping/Binary/EntitySystems/GasRecyclerSystem.cs +++ b/Content.Server/Atmos/Piping/Binary/EntitySystems/GasRecyclerSystem.cs @@ -1,6 +1,7 @@ using Content.Server.Atmos.EntitySystems; using Content.Server.Atmos.Piping.Binary.Components; using Content.Server.Atmos.Piping.Components; +using Content.Server.Construction; using Content.Server.NodeContainer; using Content.Server.NodeContainer.EntitySystems; using Content.Server.NodeContainer.Nodes; @@ -28,6 +29,8 @@ public override void Initialize() SubscribeLocalEvent(OnUpdate); SubscribeLocalEvent(OnDisabled); SubscribeLocalEvent(OnExamined); + SubscribeLocalEvent(OnRefreshParts); + SubscribeLocalEvent(OnUpgradeExamine); } private void OnEnabled(EntityUid uid, GasRecyclerComponent comp, ref AtmosDeviceEnabledEvent args) @@ -116,5 +119,20 @@ private void UpdateAppearance(EntityUid uid, GasRecyclerComponent? comp = null) _appearance.SetData(uid, PumpVisuals.Enabled, comp.Reacting); } + + private void OnRefreshParts(EntityUid uid, GasRecyclerComponent component, RefreshPartsEvent args) + { + var ratingTemp = args.PartRatings[component.MachinePartMinTemp]; + var ratingPressure = args.PartRatings[component.MachinePartMinPressure]; + + component.MinTemp = component.BaseMinTemp * MathF.Pow(component.PartRatingMinTempMultiplier, ratingTemp - 1); + component.MinPressure = component.BaseMinPressure * MathF.Pow(component.PartRatingMinPressureMultiplier, ratingPressure - 1); + } + + private void OnUpgradeExamine(EntityUid uid, GasRecyclerComponent component, UpgradeExamineEvent args) + { + args.AddPercentageUpgrade("gas-recycler-upgrade-min-temp", component.MinTemp / component.BaseMinTemp); + args.AddPercentageUpgrade("gas-recycler-upgrade-min-pressure", component.MinPressure / component.BaseMinPressure); + } } } diff --git a/Content.Server/Atmos/Piping/Binary/EntitySystems/GasVolumePumpSystem.cs b/Content.Server/Atmos/Piping/Binary/EntitySystems/GasVolumePumpSystem.cs index e4767c4061..cbcd1f4fa3 100644 --- a/Content.Server/Atmos/Piping/Binary/EntitySystems/GasVolumePumpSystem.cs +++ b/Content.Server/Atmos/Piping/Binary/EntitySystems/GasVolumePumpSystem.cs @@ -128,7 +128,7 @@ private void OnVolumePumpLeaveAtmosphere(EntityUid uid, GasVolumePumpComponent p UpdateAppearance(uid, pump); DirtyUI(uid, pump); - _userInterfaceSystem.TryCloseAll(uid, GasVolumePumpUiKey.Key); + _userInterfaceSystem.CloseUi(uid, GasVolumePumpUiKey.Key); } private void OnPumpActivate(EntityUid uid, GasVolumePumpComponent pump, ActivateInWorldEvent args) @@ -138,7 +138,7 @@ private void OnPumpActivate(EntityUid uid, GasVolumePumpComponent pump, Activate if (Transform(uid).Anchored) { - _userInterfaceSystem.TryOpen(uid, GasVolumePumpUiKey.Key, actor.PlayerSession); + _userInterfaceSystem.OpenUi(uid, GasVolumePumpUiKey.Key, actor.PlayerSession); DirtyUI(uid, pump); } else @@ -153,7 +153,7 @@ private void OnToggleStatusMessage(EntityUid uid, GasVolumePumpComponent pump, G { pump.Enabled = args.Enabled; _adminLogger.Add(LogType.AtmosPowerChanged, LogImpact.Medium, - $"{ToPrettyString(args.Session.AttachedEntity!.Value):player} set the power on {ToPrettyString(uid):device} to {args.Enabled}"); + $"{ToPrettyString(args.Actor):player} set the power on {ToPrettyString(uid):device} to {args.Enabled}"); DirtyUI(uid, pump); UpdateAppearance(uid, pump); } @@ -162,7 +162,7 @@ private void OnTransferRateChangeMessage(EntityUid uid, GasVolumePumpComponent p { pump.TransferRate = Math.Clamp(args.TransferRate, 0f, pump.MaxTransferRate); _adminLogger.Add(LogType.AtmosVolumeChanged, LogImpact.Medium, - $"{ToPrettyString(args.Session.AttachedEntity!.Value):player} set the transfer rate on {ToPrettyString(uid):device} to {args.TransferRate}"); + $"{ToPrettyString(args.Actor):player} set the transfer rate on {ToPrettyString(uid):device} to {args.TransferRate}"); DirtyUI(uid, pump); } @@ -171,7 +171,7 @@ private void DirtyUI(EntityUid uid, GasVolumePumpComponent? pump) if (!Resolve(uid, ref pump)) return; - _userInterfaceSystem.TrySetUiState(uid, GasVolumePumpUiKey.Key, + _userInterfaceSystem.SetUiState(uid, GasVolumePumpUiKey.Key, new GasVolumePumpBoundUserInterfaceState(Name(uid), pump.TransferRate, pump.Enabled)); } diff --git a/Content.Server/Atmos/Piping/Trinary/EntitySystems/GasFilterSystem.cs b/Content.Server/Atmos/Piping/Trinary/EntitySystems/GasFilterSystem.cs index fbd4260469..007d304e98 100644 --- a/Content.Server/Atmos/Piping/Trinary/EntitySystems/GasFilterSystem.cs +++ b/Content.Server/Atmos/Piping/Trinary/EntitySystems/GasFilterSystem.cs @@ -73,7 +73,7 @@ private void OnFilterUpdated(EntityUid uid, GasFilterComponent filter, ref Atmos if (filter.FilteredGas.HasValue) { - var filteredOut = new GasMixture() {Temperature = removed.Temperature}; + var filteredOut = new GasMixture() { Temperature = removed.Temperature }; filteredOut.SetMoles(filter.FilteredGas.Value, removed.GetMoles(filter.FilteredGas.Value)); removed.SetMoles(filter.FilteredGas.Value, 0f); @@ -94,7 +94,7 @@ private void OnFilterLeaveAtmosphere(EntityUid uid, GasFilterComponent filter, r _ambientSoundSystem.SetAmbience(uid, false); DirtyUI(uid, filter); - _userInterfaceSystem.TryCloseAll(uid, GasFilterUiKey.Key); + _userInterfaceSystem.CloseUi(uid, GasFilterUiKey.Key); } private void OnFilterActivate(EntityUid uid, GasFilterComponent filter, ActivateInWorldEvent args) @@ -104,7 +104,7 @@ private void OnFilterActivate(EntityUid uid, GasFilterComponent filter, Activate if (EntityManager.GetComponent(uid).Anchored) { - _userInterfaceSystem.TryOpen(uid, GasFilterUiKey.Key, actor.PlayerSession); + _userInterfaceSystem.OpenUi(uid, GasFilterUiKey.Key, actor.PlayerSession); DirtyUI(uid, filter); } else @@ -120,7 +120,7 @@ private void DirtyUI(EntityUid uid, GasFilterComponent? filter) if (!Resolve(uid, ref filter)) return; - _userInterfaceSystem.TrySetUiState(uid, GasFilterUiKey.Key, + _userInterfaceSystem.SetUiState(uid, GasFilterUiKey.Key, new GasFilterBoundUserInterfaceState(MetaData(uid).EntityName, filter.TransferRate, filter.Enabled, filter.FilteredGas)); } @@ -136,7 +136,7 @@ private void OnToggleStatusMessage(EntityUid uid, GasFilterComponent filter, Gas { filter.Enabled = args.Enabled; _adminLogger.Add(LogType.AtmosPowerChanged, LogImpact.Medium, - $"{ToPrettyString(args.Session.AttachedEntity!.Value):player} set the power on {ToPrettyString(uid):device} to {args.Enabled}"); + $"{ToPrettyString(args.Actor):player} set the power on {ToPrettyString(uid):device} to {args.Enabled}"); DirtyUI(uid, filter); UpdateAppearance(uid, filter); } @@ -145,7 +145,7 @@ private void OnTransferRateChangeMessage(EntityUid uid, GasFilterComponent filte { filter.TransferRate = Math.Clamp(args.Rate, 0f, filter.MaxTransferRate); _adminLogger.Add(LogType.AtmosVolumeChanged, LogImpact.Medium, - $"{ToPrettyString(args.Session.AttachedEntity!.Value):player} set the transfer rate on {ToPrettyString(uid):device} to {args.Rate}"); + $"{ToPrettyString(args.Actor):player} set the transfer rate on {ToPrettyString(uid):device} to {args.Rate}"); DirtyUI(uid, filter); } @@ -158,7 +158,7 @@ private void OnSelectGasMessage(EntityUid uid, GasFilterComponent filter, GasFil { filter.FilteredGas = parsedGas; _adminLogger.Add(LogType.AtmosFilterChanged, LogImpact.Medium, - $"{ToPrettyString(args.Session.AttachedEntity!.Value):player} set the filter on {ToPrettyString(uid):device} to {parsedGas.ToString()}"); + $"{ToPrettyString(args.Actor):player} set the filter on {ToPrettyString(uid):device} to {parsedGas.ToString()}"); DirtyUI(uid, filter); } else @@ -170,7 +170,7 @@ private void OnSelectGasMessage(EntityUid uid, GasFilterComponent filter, GasFil { filter.FilteredGas = null; _adminLogger.Add(LogType.AtmosFilterChanged, LogImpact.Medium, - $"{ToPrettyString(args.Session.AttachedEntity!.Value):player} set the filter on {ToPrettyString(uid):device} to none"); + $"{ToPrettyString(args.Actor):player} set the filter on {ToPrettyString(uid):device} to none"); DirtyUI(uid, filter); } } @@ -180,17 +180,30 @@ private void OnSelectGasMessage(EntityUid uid, GasFilterComponent filter, GasFil /// private void OnFilterAnalyzed(EntityUid uid, GasFilterComponent component, GasAnalyzerScanEvent args) { - if (!EntityManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer)) - return; - - args.GasMixtures ??= new Dictionary(); + args.GasMixtures ??= new List<(string, GasMixture?)>(); - if(_nodeContainer.TryGetNode(nodeContainer, component.InletName, out PipeNode? inlet)) - args.GasMixtures.Add(Loc.GetString("gas-analyzer-window-text-inlet"), inlet.Air); - if(_nodeContainer.TryGetNode(nodeContainer, component.FilterName, out PipeNode? filterNode)) - args.GasMixtures.Add(Loc.GetString("gas-analyzer-window-text-filter"), filterNode.Air); - if(_nodeContainer.TryGetNode(nodeContainer, component.OutletName, out PipeNode? outlet)) - args.GasMixtures.Add(Loc.GetString("gas-analyzer-window-text-outlet"), outlet.Air); + // multiply by volume fraction to make sure to send only the gas inside the analyzed pipe element, not the whole pipe system + if (_nodeContainer.TryGetNode(uid, component.InletName, out PipeNode? inlet) && inlet.Air.Volume != 0f) + { + var inletAirLocal = inlet.Air.Clone(); + inletAirLocal.Multiply(inlet.Volume / inlet.Air.Volume); + inletAirLocal.Volume = inlet.Volume; + args.GasMixtures.Add((Loc.GetString("gas-analyzer-window-text-inlet"), inletAirLocal)); + } + if (_nodeContainer.TryGetNode(uid, component.FilterName, out PipeNode? filterNode) && filterNode.Air.Volume != 0f) + { + var filterNodeAirLocal = filterNode.Air.Clone(); + filterNodeAirLocal.Multiply(filterNode.Volume / filterNode.Air.Volume); + filterNodeAirLocal.Volume = filterNode.Volume; + args.GasMixtures.Add((Loc.GetString("gas-analyzer-window-text-filter"), filterNodeAirLocal)); + } + if (_nodeContainer.TryGetNode(uid, component.OutletName, out PipeNode? outlet) && outlet.Air.Volume != 0f) + { + var outletAirLocal = outlet.Air.Clone(); + outletAirLocal.Multiply(outlet.Volume / outlet.Air.Volume); + outletAirLocal.Volume = outlet.Volume; + args.GasMixtures.Add((Loc.GetString("gas-analyzer-window-text-outlet"), outletAirLocal)); + } args.DeviceFlipped = inlet != null && filterNode != null && inlet.CurrentPipeDirection.ToDirection() == filterNode.CurrentPipeDirection.ToDirection().GetClockwise90Degrees(); } diff --git a/Content.Server/Atmos/Piping/Trinary/EntitySystems/GasMixerSystem.cs b/Content.Server/Atmos/Piping/Trinary/EntitySystems/GasMixerSystem.cs index ba8ebf3c9a..4ab8572843 100644 --- a/Content.Server/Atmos/Piping/Trinary/EntitySystems/GasMixerSystem.cs +++ b/Content.Server/Atmos/Piping/Trinary/EntitySystems/GasMixerSystem.cs @@ -134,7 +134,7 @@ private void OnMixerLeaveAtmosphere(EntityUid uid, GasMixerComponent mixer, ref DirtyUI(uid, mixer); UpdateAppearance(uid, mixer); - _userInterfaceSystem.TryCloseAll(uid, GasFilterUiKey.Key); + _userInterfaceSystem.CloseUi(uid, GasFilterUiKey.Key); } private void OnMixerActivate(EntityUid uid, GasMixerComponent mixer, ActivateInWorldEvent args) @@ -144,7 +144,7 @@ private void OnMixerActivate(EntityUid uid, GasMixerComponent mixer, ActivateInW if (Transform(uid).Anchored) { - _userInterfaceSystem.TryOpen(uid, GasMixerUiKey.Key, actor.PlayerSession); + _userInterfaceSystem.OpenUi(uid, GasMixerUiKey.Key, actor.PlayerSession); DirtyUI(uid, mixer); } else @@ -160,7 +160,7 @@ private void DirtyUI(EntityUid uid, GasMixerComponent? mixer) if (!Resolve(uid, ref mixer)) return; - _userInterfaceSystem.TrySetUiState(uid, GasMixerUiKey.Key, + _userInterfaceSystem.SetUiState(uid, GasMixerUiKey.Key, new GasMixerBoundUserInterfaceState(EntityManager.GetComponent(uid).EntityName, mixer.TargetPressure, mixer.Enabled, mixer.InletOneConcentration)); } @@ -176,7 +176,7 @@ private void OnToggleStatusMessage(EntityUid uid, GasMixerComponent mixer, GasMi { mixer.Enabled = args.Enabled; _adminLogger.Add(LogType.AtmosPowerChanged, LogImpact.Medium, - $"{ToPrettyString(args.Session.AttachedEntity!.Value):player} set the power on {ToPrettyString(uid):device} to {args.Enabled}"); + $"{ToPrettyString(args.Actor):player} set the power on {ToPrettyString(uid):device} to {args.Enabled}"); DirtyUI(uid, mixer); UpdateAppearance(uid, mixer); } @@ -185,7 +185,7 @@ private void OnOutputPressureChangeMessage(EntityUid uid, GasMixerComponent mixe { mixer.TargetPressure = Math.Clamp(args.Pressure, 0f, mixer.MaxTargetPressure); _adminLogger.Add(LogType.AtmosPressureChanged, LogImpact.Medium, - $"{ToPrettyString(args.Session.AttachedEntity!.Value):player} set the pressure on {ToPrettyString(uid):device} to {args.Pressure}kPa"); + $"{ToPrettyString(args.Actor):player} set the pressure on {ToPrettyString(uid):device} to {args.Pressure}kPa"); DirtyUI(uid, mixer); } @@ -196,7 +196,7 @@ private void OnChangeNodePercentageMessage(EntityUid uid, GasMixerComponent mixe mixer.InletOneConcentration = nodeOne; mixer.InletTwoConcentration = 1.0f - mixer.InletOneConcentration; _adminLogger.Add(LogType.AtmosRatioChanged, LogImpact.Medium, - $"{EntityManager.ToPrettyString(args.Session.AttachedEntity!.Value):player} set the ratio on {EntityManager.ToPrettyString(uid):device} to {mixer.InletOneConcentration}:{mixer.InletTwoConcentration}"); + $"{EntityManager.ToPrettyString(args.Actor):player} set the ratio on {EntityManager.ToPrettyString(uid):device} to {mixer.InletOneConcentration}:{mixer.InletTwoConcentration}"); DirtyUI(uid, mixer); } @@ -205,19 +205,31 @@ private void OnChangeNodePercentageMessage(EntityUid uid, GasMixerComponent mixe /// private void OnMixerAnalyzed(EntityUid uid, GasMixerComponent component, GasAnalyzerScanEvent args) { - if (!EntityManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer)) - return; - - var gasMixDict = new Dictionary(); + args.GasMixtures ??= new List<(string, GasMixture?)>(); - if(_nodeContainer.TryGetNode(nodeContainer, component.InletOneName, out PipeNode? inletOne)) - gasMixDict.Add($"{inletOne.CurrentPipeDirection} {Loc.GetString("gas-analyzer-window-text-inlet")}", inletOne.Air); - if(_nodeContainer.TryGetNode(nodeContainer, component.InletTwoName, out PipeNode? inletTwo)) - gasMixDict.Add($"{inletTwo.CurrentPipeDirection} {Loc.GetString("gas-analyzer-window-text-inlet")}", inletTwo.Air); - if(_nodeContainer.TryGetNode(nodeContainer, component.OutletName, out PipeNode? outlet)) - gasMixDict.Add(Loc.GetString("gas-analyzer-window-text-outlet"), outlet.Air); + // multiply by volume fraction to make sure to send only the gas inside the analyzed pipe element, not the whole pipe system + if (_nodeContainer.TryGetNode(uid, component.InletOneName, out PipeNode? inletOne) && inletOne.Air.Volume != 0f) + { + var inletOneAirLocal = inletOne.Air.Clone(); + inletOneAirLocal.Multiply(inletOne.Volume / inletOne.Air.Volume); + inletOneAirLocal.Volume = inletOne.Volume; + args.GasMixtures.Add(($"{inletOne.CurrentPipeDirection} {Loc.GetString("gas-analyzer-window-text-inlet")}", inletOneAirLocal)); + } + if (_nodeContainer.TryGetNode(uid, component.InletTwoName, out PipeNode? inletTwo) && inletTwo.Air.Volume != 0f) + { + var inletTwoAirLocal = inletTwo.Air.Clone(); + inletTwoAirLocal.Multiply(inletTwo.Volume / inletTwo.Air.Volume); + inletTwoAirLocal.Volume = inletTwo.Volume; + args.GasMixtures.Add(($"{inletTwo.CurrentPipeDirection} {Loc.GetString("gas-analyzer-window-text-inlet")}", inletTwoAirLocal)); + } + if (_nodeContainer.TryGetNode(uid, component.OutletName, out PipeNode? outlet) && outlet.Air.Volume != 0f) + { + var outletAirLocal = outlet.Air.Clone(); + outletAirLocal.Multiply(outlet.Volume / outlet.Air.Volume); + outletAirLocal.Volume = outlet.Volume; + args.GasMixtures.Add((Loc.GetString("gas-analyzer-window-text-outlet"), outletAirLocal)); + } - args.GasMixtures = gasMixDict; args.DeviceFlipped = inletOne != null && inletTwo != null && inletOne.CurrentPipeDirection.ToDirection() == inletTwo.CurrentPipeDirection.ToDirection().GetClockwise90Degrees(); } } diff --git a/Content.Server/Atmos/Piping/Trinary/EntitySystems/PressureControlledValveSystem.cs b/Content.Server/Atmos/Piping/Trinary/EntitySystems/PressureControlledValveSystem.cs index 1bab2abd8e..d567e19f02 100644 --- a/Content.Server/Atmos/Piping/Trinary/EntitySystems/PressureControlledValveSystem.cs +++ b/Content.Server/Atmos/Piping/Trinary/EntitySystems/PressureControlledValveSystem.cs @@ -1,7 +1,5 @@ -using Content.Server.Atmos.EntitySystems; using Content.Server.Atmos.Piping.Components; using Content.Server.Atmos.Piping.Trinary.Components; -using Content.Server.NodeContainer; using Content.Server.NodeContainer.EntitySystems; using Content.Server.NodeContainer.Nodes; using Content.Shared.Atmos.Piping; @@ -13,7 +11,6 @@ namespace Content.Server.Atmos.Piping.Trinary.EntitySystems [UsedImplicitly] public sealed class PressureControlledValveSystem : EntitySystem { - [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; [Dependency] private readonly SharedAmbientSoundSystem _ambientSoundSystem = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly NodeContainerSystem _nodeContainer = default!; @@ -40,39 +37,26 @@ private void OnUpdate(EntityUid uid, PressureControlledValveComponent comp, ref return; } - // If output is higher than input, flip input/output to enable bidirectional flow. - if (outletNode.Air.Pressure > inletNode.Air.Pressure) - { - PipeNode temp = outletNode; - outletNode = inletNode; - inletNode = temp; - } - - float control = (controlNode.Air.Pressure - outletNode.Air.Pressure) - comp.Threshold; - float transferRate; - if (control < 0) - { - comp.Enabled = false; - transferRate = 0; - } - else + // If the pressure in either inlet or outlet exceeds the side pressure, act as an open pipe. + if (!comp.Enabled && (controlNode.Air.Pressure < inletNode.Air.Pressure + || controlNode.Air.Pressure < outletNode.Air.Pressure)) { + inletNode.AddAlwaysReachable(outletNode); + outletNode.AddAlwaysReachable(inletNode); comp.Enabled = true; - transferRate = Math.Min(control * comp.Gain, comp.MaxTransferRate * _atmosphereSystem.PumpSpeedup()); + UpdateAppearance(uid, comp); + _ambientSoundSystem.SetAmbience(uid, true); + return; } - UpdateAppearance(uid, comp); - // We multiply the transfer rate in L/s by the seconds passed since the last process to get the liters. - var transferVolume = transferRate * args.dt; - if (transferVolume <= 0) - { - _ambientSoundSystem.SetAmbience(uid, false); + if (!comp.Enabled) return; - } - _ambientSoundSystem.SetAmbience(uid, true); - var removed = inletNode.Air.RemoveVolume(transferVolume); - _atmosphereSystem.Merge(outletNode.Air, removed); + inletNode.RemoveAlwaysReachable(outletNode); + outletNode.RemoveAlwaysReachable(inletNode); + comp.Enabled = false; + UpdateAppearance(uid, comp); + _ambientSoundSystem.SetAmbience(uid, false); } private void OnFilterLeaveAtmosphere(EntityUid uid, PressureControlledValveComponent comp, ref AtmosDeviceDisabledEvent args) diff --git a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCanisterSystem.cs b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCanisterSystem.cs index d53e29c949..60ae230b1b 100644 --- a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCanisterSystem.cs +++ b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasCanisterSystem.cs @@ -101,7 +101,7 @@ private void DirtyUI(EntityUid uid, tankPressure = tankComponent.Air.Pressure; } - _ui.TrySetUiState(uid, GasCanisterUiKey.Key, + _ui.SetUiState(uid, GasCanisterUiKey.Key, new GasCanisterBoundUserInterfaceState(Name(uid), canister.Air.Pressure, portStatus, tankLabel, tankPressure, canister.ReleasePressure, canister.ReleaseValve, canister.MinReleasePressure, canister.MaxReleasePressure)); @@ -109,19 +109,19 @@ private void DirtyUI(EntityUid uid, private void OnHoldingTankEjectMessage(EntityUid uid, GasCanisterComponent canister, GasCanisterHoldingTankEjectMessage args) { - if (canister.GasTankSlot.Item == null || args.Session.AttachedEntity == null) + if (canister.GasTankSlot.Item == null) return; var item = canister.GasTankSlot.Item; - _slots.TryEjectToHands(uid, canister.GasTankSlot, args.Session.AttachedEntity); - _adminLogger.Add(LogType.CanisterTankEjected, LogImpact.Medium, $"Player {ToPrettyString(args.Session.AttachedEntity.GetValueOrDefault()):player} ejected tank {ToPrettyString(item):tank} from {ToPrettyString(uid):canister}"); + _slots.TryEjectToHands(uid, canister.GasTankSlot, args.Actor); + _adminLogger.Add(LogType.CanisterTankEjected, LogImpact.Medium, $"Player {ToPrettyString(args.Actor):player} ejected tank {ToPrettyString(item):tank} from {ToPrettyString(uid):canister}"); } private void OnCanisterChangeReleasePressure(EntityUid uid, GasCanisterComponent canister, GasCanisterChangeReleasePressureMessage args) { var pressure = Math.Clamp(args.Pressure, canister.MinReleasePressure, canister.MaxReleasePressure); - _adminLogger.Add(LogType.CanisterPressure, LogImpact.Medium, $"{ToPrettyString(args.Session.AttachedEntity.GetValueOrDefault()):player} set the release pressure on {ToPrettyString(uid):canister} to {args.Pressure}"); + _adminLogger.Add(LogType.CanisterPressure, LogImpact.Medium, $"{ToPrettyString(args.Actor):player} set the release pressure on {ToPrettyString(uid):canister} to {args.Pressure}"); canister.ReleasePressure = pressure; DirtyUI(uid, canister); @@ -134,14 +134,14 @@ private void OnCanisterChangeReleaseValve(EntityUid uid, GasCanisterComponent ca impact = canister.GasTankSlot.HasItem ? LogImpact.Medium : LogImpact.High; var containedGasDict = new Dictionary(); - var containedGasArray = Gas.GetValues(typeof(Gas)); + var containedGasArray = Enum.GetValues(typeof(Gas)); for (int i = 0; i < containedGasArray.Length; i++) { containedGasDict.Add((Gas)i, canister.Air[i]); } - _adminLogger.Add(LogType.CanisterValve, impact, $"{ToPrettyString(args.Session.AttachedEntity.GetValueOrDefault()):player} set the valve on {ToPrettyString(uid):canister} to {args.Valve:valveState} while it contained [{string.Join(", ", containedGasDict)}]"); + _adminLogger.Add(LogType.CanisterValve, impact, $"{ToPrettyString(args.Actor):player} set the valve on {ToPrettyString(uid):canister} to {args.Valve:valveState} while it contained [{string.Join(", ", containedGasDict)}]"); canister.ReleaseValve = args.Valve; DirtyUI(uid, canister); @@ -222,7 +222,7 @@ private void OnCanisterActivate(EntityUid uid, GasCanisterComponent component, A if (args.Handled) return; - _ui.TryOpen(uid, GasCanisterUiKey.Key, actor.PlayerSession); + _ui.OpenUi(uid, GasCanisterUiKey.Key, actor.PlayerSession); args.Handled = true; } @@ -234,7 +234,7 @@ private void OnCanisterInteractHand(EntityUid uid, GasCanisterComponent componen if (CheckLocked(uid, component, args.User)) return; - _ui.TryOpen(uid, GasCanisterUiKey.Key, actor.PlayerSession); + _ui.OpenUi(uid, GasCanisterUiKey.Key, actor.PlayerSession); args.Handled = true; } @@ -316,9 +316,17 @@ private void CalculateCanisterPrice(EntityUid uid, GasCanisterComponent componen /// /// Returns the gas mixture for the gas analyzer /// - private void OnAnalyzed(EntityUid uid, GasCanisterComponent component, GasAnalyzerScanEvent args) + private void OnAnalyzed(EntityUid uid, GasCanisterComponent canisterComponent, GasAnalyzerScanEvent args) { - args.GasMixtures = new Dictionary { {Name(uid), component.Air} }; + args.GasMixtures ??= new List<(string, GasMixture?)>(); + args.GasMixtures.Add((Name(uid), canisterComponent.Air)); + // if a tank is inserted show it on the analyzer as well + if (canisterComponent.GasTankSlot.Item != null) + { + var tank = canisterComponent.GasTankSlot.Item.Value; + var tankComponent = Comp(tank); + args.GasMixtures.Add((Name(tank), tankComponent.Air)); + } } /// diff --git a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasThermoMachineSystem.cs b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasThermoMachineSystem.cs index 720fd5b5b9..01eab560a1 100644 --- a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasThermoMachineSystem.cs +++ b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasThermoMachineSystem.cs @@ -144,7 +144,7 @@ private bool IsHeater(GasThermoMachineComponent comp) private void OnToggleMessage(EntityUid uid, GasThermoMachineComponent thermoMachine, GasThermomachineToggleMessage args) { var powerState = _power.TryTogglePower(uid); - _adminLogger.Add(LogType.AtmosPowerChanged, $"{ToPrettyString(args.Session.AttachedEntity)} turned {(powerState ? "On" : "Off")} {ToPrettyString(uid)}"); + _adminLogger.Add(LogType.AtmosPowerChanged, $"{ToPrettyString(args.Actor)} turned {(powerState ? "On" : "Off")} {ToPrettyString(uid)}"); DirtyUI(uid, thermoMachine); } @@ -155,7 +155,7 @@ private void OnChangeTemperature(EntityUid uid, GasThermoMachineComponent thermo else thermoMachine.TargetTemperature = MathF.Max(args.Temperature, thermoMachine.MinTemperature); thermoMachine.TargetTemperature = MathF.Max(thermoMachine.TargetTemperature, Atmospherics.TCMB); - _adminLogger.Add(LogType.AtmosTemperatureChanged, $"{ToPrettyString(args.Session.AttachedEntity)} set temperature on {ToPrettyString(uid)} to {thermoMachine.TargetTemperature}"); + _adminLogger.Add(LogType.AtmosTemperatureChanged, $"{ToPrettyString(args.Actor)} set temperature on {ToPrettyString(uid)} to {thermoMachine.TargetTemperature}"); DirtyUI(uid, thermoMachine); } @@ -168,8 +168,8 @@ private void DirtyUI(EntityUid uid, GasThermoMachineComponent? thermoMachine, Us if (!Resolve(uid, ref powerReceiver)) return; - _userInterfaceSystem.TrySetUiState(uid, ThermomachineUiKey.Key, - new GasThermomachineBoundUserInterfaceState(thermoMachine.MinTemperature, thermoMachine.MaxTemperature, thermoMachine.TargetTemperature, !powerReceiver.PowerDisabled, IsHeater(thermoMachine)), null, ui); + _userInterfaceSystem.SetUiState(uid, ThermomachineUiKey.Key, + new GasThermomachineBoundUserInterfaceState(thermoMachine.MinTemperature, thermoMachine.MaxTemperature, thermoMachine.TargetTemperature, !powerReceiver.PowerDisabled, IsHeater(thermoMachine))); } private void OnExamined(EntityUid uid, GasThermoMachineComponent thermoMachine, ExaminedEvent args) diff --git a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentPumpSystem.cs b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentPumpSystem.cs index a986385f5e..7c12cf3f77 100644 --- a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentPumpSystem.cs +++ b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentPumpSystem.cs @@ -80,7 +80,7 @@ private void OnGasVentPumpUpdated(EntityUid uid, GasVentPumpComponent vent, ref return; } - var timeDelta = args.dt; + var timeDelta = args.dt; var pressureDelta = timeDelta * vent.TargetPressureChange; if (vent.PumpDirection == VentPumpDirection.Releasing && pipe.Air.Pressure > 0) @@ -292,7 +292,7 @@ private void OnExamine(EntityUid uid, GasVentPumpComponent component, ExaminedEv /// private void OnAnalyzed(EntityUid uid, GasVentPumpComponent component, GasAnalyzerScanEvent args) { - var gasMixDict = new Dictionary(); + args.GasMixtures ??= new List<(string, GasMixture?)>(); // these are both called pipe, above it switches using this so I duplicated that...? var nodeName = component.PumpDirection switch @@ -301,10 +301,14 @@ private void OnAnalyzed(EntityUid uid, GasVentPumpComponent component, GasAnalyz VentPumpDirection.Siphoning => component.Outlet, _ => throw new ArgumentOutOfRangeException() }; - if (_nodeContainer.TryGetNode(uid, nodeName, out PipeNode? pipe)) - gasMixDict.Add(nodeName, pipe.Air); - - args.GasMixtures = gasMixDict; + // multiply by volume fraction to make sure to send only the gas inside the analyzed pipe element, not the whole pipe system + if (_nodeContainer.TryGetNode(uid, nodeName, out PipeNode? pipe) && pipe.Air.Volume != 0f) + { + var pipeAirLocal = pipe.Air.Clone(); + pipeAirLocal.Multiply(pipe.Volume / pipe.Air.Volume); + pipeAirLocal.Volume = pipe.Volume; + args.GasMixtures.Add((nodeName, pipeAirLocal)); + } } private void OnWeldChanged(EntityUid uid, GasVentPumpComponent component, ref WeldableChangedEvent args) diff --git a/Content.Server/Atmos/Portable/PortableScrubberComponent.cs b/Content.Server/Atmos/Portable/PortableScrubberComponent.cs index ae9a5da963..fbe2d3f95a 100644 --- a/Content.Server/Atmos/Portable/PortableScrubberComponent.cs +++ b/Content.Server/Atmos/Portable/PortableScrubberComponent.cs @@ -1,4 +1,6 @@ using Content.Shared.Atmos; +using Content.Shared.Construction.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; namespace Content.Server.Atmos.Portable { @@ -37,13 +39,51 @@ public sealed partial class PortableScrubberComponent : Component /// /// Maximum internal pressure before it refuses to take more. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public float MaxPressure = 2500; /// - /// The speed at which gas is scrubbed from the environment. + /// The base amount of maximum internal pressure /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] + public float BaseMaxPressure = 2500; + + /// + /// The machine part that modifies the maximum internal pressure + /// + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string MachinePartMaxPressure = "MatterBin"; + + /// + /// How much the will affect the pressure. + /// The value will be multiplied by this amount for each increasing part tier. + /// + [DataField] + public float PartRatingMaxPressureModifier = 1.5f; + + /// + /// The speed at which gas is scrubbed from the environment. + /// + [ViewVariables(VVAccess.ReadWrite)] public float TransferRate = 800; + + /// + /// The base speed at which gas is scrubbed from the environment. + /// + [DataField] + public float BaseTransferRate = 800; + + /// + /// The machine part which modifies the speed of + /// + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string MachinePartTransferRate = "Manipulator"; + + /// + /// How much the will modify the rate. + /// The value will be multiplied by this amount for each increasing part tier. + /// + [DataField] + public float PartRatingTransferRateModifier = 1.4f; } } diff --git a/Content.Server/Atmos/Portable/PortableScrubberSystem.cs b/Content.Server/Atmos/Portable/PortableScrubberSystem.cs index f9043c091a..fbe40deedb 100644 --- a/Content.Server/Atmos/Portable/PortableScrubberSystem.cs +++ b/Content.Server/Atmos/Portable/PortableScrubberSystem.cs @@ -12,7 +12,9 @@ using Content.Server.NodeContainer.NodeGroups; using Content.Server.Audio; using Content.Server.Administration.Logs; +using Content.Server.Construction; using Content.Server.NodeContainer.EntitySystems; +using Content.Shared.Atmos; using Content.Shared.Database; namespace Content.Server.Atmos.Portable @@ -38,6 +40,8 @@ public override void Initialize() SubscribeLocalEvent(OnExamined); SubscribeLocalEvent(OnDestroyed); SubscribeLocalEvent(OnScrubberAnalyzed); + SubscribeLocalEvent(OnRefreshParts); + SubscribeLocalEvent(OnUpgradeExamine); } private bool IsFull(PortableScrubberComponent component) @@ -151,10 +155,23 @@ private void UpdateAppearance(EntityUid uid, bool isFull, bool isRunning) /// private void OnScrubberAnalyzed(EntityUid uid, PortableScrubberComponent component, GasAnalyzerScanEvent args) { - args.GasMixtures ??= new Dictionary { { Name(uid), component.Air } }; - // If it's connected to a port, include the port side - if (_nodeContainer.TryGetNode(uid, component.PortName, out PipeNode? port)) - args.GasMixtures.Add(component.PortName, port.Air); + args.GasMixtures ??= new List<(string, GasMixture?)>(); + args.GasMixtures.Add((Name(uid), component.Air)); + } + + private void OnRefreshParts(EntityUid uid, PortableScrubberComponent component, RefreshPartsEvent args) + { + var pressureRating = args.PartRatings[component.MachinePartMaxPressure]; + var transferRating = args.PartRatings[component.MachinePartTransferRate]; + + component.MaxPressure = component.BaseMaxPressure * MathF.Pow(component.PartRatingMaxPressureModifier, pressureRating - 1); + component.TransferRate = component.BaseTransferRate * MathF.Pow(component.PartRatingTransferRateModifier, transferRating - 1); + } + + private void OnUpgradeExamine(EntityUid uid, PortableScrubberComponent component, UpgradeExamineEvent args) + { + args.AddPercentageUpgrade("portable-scrubber-component-upgrade-max-pressure", component.MaxPressure / component.BaseMaxPressure); + args.AddPercentageUpgrade("portable-scrubber-component-upgrade-transfer-rate", component.TransferRate / component.BaseTransferRate); } } } diff --git a/Content.Server/Atmos/Portable/SpaceHeaterSystem.cs b/Content.Server/Atmos/Portable/SpaceHeaterSystem.cs index 8094b0e1a6..70eea2d9b7 100644 --- a/Content.Server/Atmos/Portable/SpaceHeaterSystem.cs +++ b/Content.Server/Atmos/Portable/SpaceHeaterSystem.cs @@ -163,7 +163,7 @@ private void DirtyUI(EntityUid uid, SpaceHeaterComponent? spaceHeater) { return; } - _userInterfaceSystem.TrySetUiState(uid, SpaceHeaterUiKey.Key, + _userInterfaceSystem.SetUiState(uid, SpaceHeaterUiKey.Key, new SpaceHeaterBoundUserInterfaceState(spaceHeater.MinTemperature, spaceHeater.MaxTemperature, thermoMachine.TargetTemperature, !powerReceiver.PowerDisabled, spaceHeater.Mode, spaceHeater.PowerLevel)); } diff --git a/Content.Server/Atmos/Reactions/AmmoniaOxygenReaction.cs b/Content.Server/Atmos/Reactions/AmmoniaOxygenReaction.cs index 197034ce54..2c071afab1 100644 --- a/Content.Server/Atmos/Reactions/AmmoniaOxygenReaction.cs +++ b/Content.Server/Atmos/Reactions/AmmoniaOxygenReaction.cs @@ -1,5 +1,6 @@ using Content.Server.Atmos.EntitySystems; using Content.Shared.Atmos; +using Content.Shared.Atmos.Reactions; using JetBrains.Annotations; namespace Content.Server.Atmos.Reactions; diff --git a/Content.Server/Atmos/Reactions/FrezonCoolantReaction.cs b/Content.Server/Atmos/Reactions/FrezonCoolantReaction.cs index 051ee8202d..475c149cf3 100644 --- a/Content.Server/Atmos/Reactions/FrezonCoolantReaction.cs +++ b/Content.Server/Atmos/Reactions/FrezonCoolantReaction.cs @@ -1,5 +1,6 @@ using Content.Server.Atmos.EntitySystems; using Content.Shared.Atmos; +using Content.Shared.Atmos.Reactions; using JetBrains.Annotations; namespace Content.Server.Atmos.Reactions; diff --git a/Content.Server/Atmos/Reactions/FrezonProductionReaction.cs b/Content.Server/Atmos/Reactions/FrezonProductionReaction.cs index 4ffd9c2f5b..e3d3ece6b6 100644 --- a/Content.Server/Atmos/Reactions/FrezonProductionReaction.cs +++ b/Content.Server/Atmos/Reactions/FrezonProductionReaction.cs @@ -1,5 +1,6 @@ using Content.Server.Atmos.EntitySystems; using Content.Shared.Atmos; +using Content.Shared.Atmos.Reactions; using JetBrains.Annotations; namespace Content.Server.Atmos.Reactions; diff --git a/Content.Server/Atmos/Reactions/GasReactionPrototype.cs b/Content.Server/Atmos/Reactions/GasReactionPrototype.cs index 0ee29de3bf..48a113bb9a 100644 --- a/Content.Server/Atmos/Reactions/GasReactionPrototype.cs +++ b/Content.Server/Atmos/Reactions/GasReactionPrototype.cs @@ -1,22 +1,10 @@ using Content.Server.Atmos.EntitySystems; using Content.Shared.Atmos; +using Content.Shared.Atmos.Reactions; using Robust.Shared.Prototypes; namespace Content.Server.Atmos.Reactions { - [Flags] - public enum ReactionResult : byte - { - NoReaction = 0, - Reacting = 1, - StopReactions = 2, - } - - public enum GasReaction : byte - { - Fire = 0, - } - [Prototype("gasReaction")] public sealed partial class GasReactionPrototype : IPrototype { diff --git a/Content.Server/Atmos/Reactions/N2ODecompositionReaction.cs b/Content.Server/Atmos/Reactions/N2ODecompositionReaction.cs index 7fce663dc3..367c0eb7b9 100644 --- a/Content.Server/Atmos/Reactions/N2ODecompositionReaction.cs +++ b/Content.Server/Atmos/Reactions/N2ODecompositionReaction.cs @@ -1,5 +1,6 @@ using Content.Server.Atmos.EntitySystems; using Content.Shared.Atmos; +using Content.Shared.Atmos.Reactions; using JetBrains.Annotations; namespace Content.Server.Atmos.Reactions; diff --git a/Content.Server/Atmos/Reactions/PlasmaFireReaction.cs b/Content.Server/Atmos/Reactions/PlasmaFireReaction.cs index e7ab7835fd..98d567d4ed 100644 --- a/Content.Server/Atmos/Reactions/PlasmaFireReaction.cs +++ b/Content.Server/Atmos/Reactions/PlasmaFireReaction.cs @@ -1,5 +1,6 @@ using Content.Server.Atmos.EntitySystems; using Content.Shared.Atmos; +using Content.Shared.Atmos.Reactions; using JetBrains.Annotations; namespace Content.Server.Atmos.Reactions diff --git a/Content.Server/Atmos/Reactions/TritiumFireReaction.cs b/Content.Server/Atmos/Reactions/TritiumFireReaction.cs index 7103859a90..3ad0a4b04d 100644 --- a/Content.Server/Atmos/Reactions/TritiumFireReaction.cs +++ b/Content.Server/Atmos/Reactions/TritiumFireReaction.cs @@ -1,5 +1,6 @@ using Content.Server.Atmos.EntitySystems; using Content.Shared.Atmos; +using Content.Shared.Atmos.Reactions; using JetBrains.Annotations; namespace Content.Server.Atmos.Reactions diff --git a/Content.Server/Atmos/Reactions/WaterVaporReaction.cs b/Content.Server/Atmos/Reactions/WaterVaporReaction.cs index 8db8fdbd66..e06c4b75ff 100644 --- a/Content.Server/Atmos/Reactions/WaterVaporReaction.cs +++ b/Content.Server/Atmos/Reactions/WaterVaporReaction.cs @@ -1,5 +1,7 @@ using Content.Server.Atmos.EntitySystems; using Content.Server.Fluids.EntitySystems; +using Content.Shared.Atmos; +using Content.Shared.Atmos.Reactions; using Content.Shared.Chemistry.Components; using Content.Shared.FixedPoint; using Content.Shared.Maps; diff --git a/Content.Server/Atmos/Rotting/RottingSystem.cs b/Content.Server/Atmos/Rotting/RottingSystem.cs index 5070b3f197..40bdf65743 100644 --- a/Content.Server/Atmos/Rotting/RottingSystem.cs +++ b/Content.Server/Atmos/Rotting/RottingSystem.cs @@ -46,6 +46,29 @@ private void OnTempIsRotting(EntityUid uid, TemperatureComponent component, ref args.Handled = component.CurrentTemperature < Atmospherics.T0C + 0.85f; } + + public void ReduceAccumulator(EntityUid uid, TimeSpan time) + { + if (!TryComp(uid, out var perishable)) + return; + + if (!TryComp(uid, out var rotting)) + { + perishable.RotAccumulator -= time; + return; + } + var total = (rotting.TotalRotTime + perishable.RotAccumulator) - time; + + if (total < perishable.RotAfter) + { + RemCompDeferred(uid, rotting); + perishable.RotAccumulator = total; + } + + else + rotting.TotalRotTime = total - perishable.RotAfter; + } + /// /// Is anything speeding up the decay? /// e.g. buried in a grave diff --git a/Content.Server/Atmos/Serialization/TileAtmosCollectionSerializer.cs b/Content.Server/Atmos/Serialization/TileAtmosCollectionSerializer.cs index 00be83e86d..5b30d65e48 100644 --- a/Content.Server/Atmos/Serialization/TileAtmosCollectionSerializer.cs +++ b/Content.Server/Atmos/Serialization/TileAtmosCollectionSerializer.cs @@ -1,4 +1,5 @@ using System.Globalization; +using Content.Shared.Atmos; using Robust.Shared.Serialization; using Robust.Shared.Serialization.Manager; using Robust.Shared.Serialization.Markdown; diff --git a/Content.Server/Atmos/TileMixtureEnumerator.cs b/Content.Server/Atmos/TileMixtureEnumerator.cs index 20440032da..5601615f50 100644 --- a/Content.Server/Atmos/TileMixtureEnumerator.cs +++ b/Content.Server/Atmos/TileMixtureEnumerator.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; +using Content.Shared.Atmos; namespace Content.Server.Atmos; diff --git a/Content.Server/Audio/Jukebox/JukeboxSystem.cs b/Content.Server/Audio/Jukebox/JukeboxSystem.cs new file mode 100644 index 0000000000..cc9235e3d7 --- /dev/null +++ b/Content.Server/Audio/Jukebox/JukeboxSystem.cs @@ -0,0 +1,156 @@ +using Content.Server.Power.Components; +using Content.Server.Power.EntitySystems; +using Content.Shared.Audio.Jukebox; +using Robust.Server.GameObjects; +using Robust.Shared.Audio; +using Robust.Shared.Audio.Components; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; +using JukeboxComponent = Content.Shared.Audio.Jukebox.JukeboxComponent; + +namespace Content.Server.Audio.Jukebox; + + +public sealed class JukeboxSystem : SharedJukeboxSystem +{ + [Dependency] private readonly IPrototypeManager _protoManager = default!; + [Dependency] private readonly AppearanceSystem _appearanceSystem = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnJukeboxSelected); + SubscribeLocalEvent(OnJukeboxPlay); + SubscribeLocalEvent(OnJukeboxPause); + SubscribeLocalEvent(OnJukeboxStop); + SubscribeLocalEvent(OnJukeboxSetTime); + SubscribeLocalEvent(OnComponentInit); + SubscribeLocalEvent(OnComponentShutdown); + + SubscribeLocalEvent(OnPowerChanged); + } + + private void OnComponentInit(EntityUid uid, JukeboxComponent component, ComponentInit args) + { + if (HasComp(uid)) + { + TryUpdateVisualState(uid, component); + } + } + + private void OnJukeboxPlay(EntityUid uid, JukeboxComponent component, ref JukeboxPlayingMessage args) + { + if (Exists(component.AudioStream)) + { + Audio.SetState(component.AudioStream, AudioState.Playing); + } + else + { + component.AudioStream = Audio.Stop(component.AudioStream); + + if (string.IsNullOrEmpty(component.SelectedSongId) || + !_protoManager.TryIndex(component.SelectedSongId, out var jukeboxProto)) + { + return; + } + + component.AudioStream = Audio.PlayPvs(jukeboxProto.Path, uid, AudioParams.Default.WithMaxDistance(10f))?.Entity; + Dirty(uid, component); + } + } + + private void OnJukeboxPause(Entity ent, ref JukeboxPauseMessage args) + { + Audio.SetState(ent.Comp.AudioStream, AudioState.Paused); + } + + private void OnJukeboxSetTime(EntityUid uid, JukeboxComponent component, JukeboxSetTimeMessage args) + { + if (TryComp(args.Actor, out ActorComponent? actorComp)) + { + var offset = actorComp.PlayerSession.Channel.Ping * 1.5f / 1000f; + Audio.SetPlaybackPosition(component.AudioStream, args.SongTime + offset); + } + } + + private void OnPowerChanged(Entity entity, ref PowerChangedEvent args) + { + TryUpdateVisualState(entity); + + if (!this.IsPowered(entity.Owner, EntityManager)) + { + Stop(entity); + } + } + + private void OnJukeboxStop(Entity entity, ref JukeboxStopMessage args) + { + Stop(entity); + } + + private void Stop(Entity entity) + { + Audio.SetState(entity.Comp.AudioStream, AudioState.Stopped); + Dirty(entity); + } + + private void OnJukeboxSelected(EntityUid uid, JukeboxComponent component, JukeboxSelectedMessage args) + { + if (!Audio.IsPlaying(component.AudioStream)) + { + component.SelectedSongId = args.SongId; + DirectSetVisualState(uid, JukeboxVisualState.Select); + component.Selecting = true; + component.AudioStream = Audio.Stop(component.AudioStream); + } + + Dirty(uid, component); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var comp)) + { + if (comp.Selecting) + { + comp.SelectAccumulator += frameTime; + if (comp.SelectAccumulator >= 0.5f) + { + comp.SelectAccumulator = 0f; + comp.Selecting = false; + + TryUpdateVisualState(uid, comp); + } + } + } + } + + private void OnComponentShutdown(EntityUid uid, JukeboxComponent component, ComponentShutdown args) + { + component.AudioStream = Audio.Stop(component.AudioStream); + } + + private void DirectSetVisualState(EntityUid uid, JukeboxVisualState state) + { + _appearanceSystem.SetData(uid, JukeboxVisuals.VisualState, state); + } + + private void TryUpdateVisualState(EntityUid uid, JukeboxComponent? jukeboxComponent = null) + { + if (!Resolve(uid, ref jukeboxComponent)) + return; + + var finalState = JukeboxVisualState.On; + + if (!this.IsPowered(uid, EntityManager)) + { + finalState = JukeboxVisualState.Off; + } + + _appearanceSystem.SetData(uid, JukeboxVisuals.VisualState, finalState); + } +} diff --git a/Content.Server/AutoVote/AutoVoteSystem.cs b/Content.Server/AutoVote/AutoVoteSystem.cs new file mode 100644 index 0000000000..7fb053b2f8 --- /dev/null +++ b/Content.Server/AutoVote/AutoVoteSystem.cs @@ -0,0 +1,54 @@ +using Robust.Shared.Configuration; +using Content.Server.Voting.Managers; +using Content.Shared.GameTicking; +using Content.Shared.Voting; +using Content.Shared.CCVar; +using Robust.Server.Player; +using Content.Server.GameTicking; + +namespace Content.Server.AutoVote; + +public sealed class AutoVoteSystem : EntitySystem +{ + [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] public readonly IVoteManager _voteManager = default!; + [Dependency] public readonly IPlayerManager _playerManager = default!; + + public bool _shouldVoteNextJoin = false; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnReturnedToLobby); + SubscribeLocalEvent(OnPlayerJoinedLobby); + } + + public void OnReturnedToLobby(RoundRestartCleanupEvent ev) => CallAutovote(); + + public void OnPlayerJoinedLobby(PlayerJoinedLobbyEvent ev) + { + if (!_shouldVoteNextJoin) + return; + + CallAutovote(); + _shouldVoteNextJoin = false; + } + + private void CallAutovote() + { + if (!_cfg.GetCVar(CCVars.AutoVoteEnabled)) + return; + + if (_playerManager.PlayerCount == 0) + { + _shouldVoteNextJoin = true; + return; + } + + if (_cfg.GetCVar(CCVars.MapAutoVoteEnabled)) + _voteManager.CreateStandardVote(null, StandardVoteType.Map); + if (_cfg.GetCVar(CCVars.PresetAutoVoteEnabled)) + _voteManager.CreateStandardVote(null, StandardVoteType.Preset); + } +} diff --git a/Content.Server/Bed/BedSystem.cs b/Content.Server/Bed/BedSystem.cs index 49021c142f..976ef5139c 100644 --- a/Content.Server/Bed/BedSystem.cs +++ b/Content.Server/Bed/BedSystem.cs @@ -2,6 +2,7 @@ using Content.Server.Bed.Components; using Content.Server.Bed.Sleep; using Content.Server.Body.Systems; +using Content.Server.Construction; using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; using Content.Shared.Bed; @@ -9,9 +10,11 @@ using Content.Shared.Body.Components; using Content.Shared.Buckle.Components; using Content.Shared.Damage; +using Content.Shared.Emag.Components; using Content.Shared.Emag.Systems; using Content.Shared.Mobs.Systems; using Robust.Shared.Timing; +using Content.Shared.Silicon.Components; // I shouldn't have to modify this. namespace Content.Server.Bed { @@ -31,6 +34,8 @@ public override void Initialize() SubscribeLocalEvent(OnBuckleChange); SubscribeLocalEvent(OnPowerChanged); SubscribeLocalEvent(OnEmagged); + SubscribeLocalEvent(OnRefreshParts); + SubscribeLocalEvent(OnUpgradeExamine); } private void ManageUpdateList(EntityUid uid, HealOnBuckleComponent component, ref BuckleChangeEvent args) @@ -65,7 +70,8 @@ public override void Update(float frameTime) foreach (var healedEntity in strapComponent.BuckledEntities) { - if (_mobStateSystem.IsDead(healedEntity)) + if (_mobStateSystem.IsDead(healedEntity) + || HasComp(healedEntity)) continue; var damage = bedComponent.Damage; @@ -124,5 +130,18 @@ private void UpdateMetabolisms(EntityUid uid, StasisBedComponent component, bool RaiseLocalEvent(buckledEntity, ref metabolicEvent); } } + + private void OnRefreshParts(EntityUid uid, StasisBedComponent component, RefreshPartsEvent args) + { + var metabolismRating = args.PartRatings[component.MachinePartMetabolismModifier]; + component.Multiplier = component.BaseMultiplier * metabolismRating; // Linear scaling so it's not OP + if (HasComp(uid)) + component.Multiplier = 1f / component.Multiplier; + } + + private void OnUpgradeExamine(EntityUid uid, StasisBedComponent component, UpgradeExamineEvent args) + { + args.AddPercentageUpgrade("stasis-bed-component-upgrade-stasis", component.Multiplier / component.BaseMultiplier); + } } } diff --git a/Content.Server/Bed/Components/StasisBedComponent.cs b/Content.Server/Bed/Components/StasisBedComponent.cs index e2175d6e64..bb4096a2a5 100644 --- a/Content.Server/Bed/Components/StasisBedComponent.cs +++ b/Content.Server/Bed/Components/StasisBedComponent.cs @@ -1,12 +1,21 @@ +using Content.Shared.Construction.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + namespace Content.Server.Bed.Components { [RegisterComponent] public sealed partial class StasisBedComponent : Component { + [DataField] + public float BaseMultiplier = 10f; + /// - /// What the metabolic update rate will be multiplied by (higher = slower metabolism) + /// What the metabolic update rate will be multiplied by (higher = slower metabolism) /// [ViewVariables(VVAccess.ReadWrite)] public float Multiplier = 10f; + + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string MachinePartMetabolismModifier = "Capacitor"; } } diff --git a/Content.Server/Bed/Cryostorage/CryostorageSystem.cs b/Content.Server/Bed/Cryostorage/CryostorageSystem.cs index 2e7f8c4235..1369fa20f1 100644 --- a/Content.Server/Bed/Cryostorage/CryostorageSystem.cs +++ b/Content.Server/Bed/Cryostorage/CryostorageSystem.cs @@ -79,9 +79,7 @@ private void OnBeforeUIOpened(Entity ent, ref BeforeActiva private void OnRemoveItemBuiMessage(Entity ent, ref CryostorageRemoveItemBuiMessage args) { var (_, comp) = ent; - if (args.Session.AttachedEntity is not { } attachedEntity) - return; - + var attachedEntity = args.Actor; var cryoContained = GetEntity(args.StoredEntity); if (!comp.StoredPlayers.Contains(cryoContained) || !IsInPausedMap(cryoContained)) @@ -114,6 +112,7 @@ private void OnRemoveItemBuiMessage(Entity ent, ref Cryost AdminLog.Add(LogType.Action, LogImpact.High, $"{ToPrettyString(attachedEntity):player} removed item {ToPrettyString(entity)} from cryostorage-contained player " + $"{ToPrettyString(cryoContained):player}, stored in cryostorage {ToPrettyString(ent)}"); + _container.TryRemoveFromContainer(entity.Value); _transform.SetCoordinates(entity.Value, Transform(attachedEntity).Coordinates); _hands.PickupOrDrop(attachedEntity, entity.Value); @@ -122,8 +121,8 @@ private void OnRemoveItemBuiMessage(Entity ent, ref Cryost private void UpdateCryostorageUIState(Entity ent) { - var state = new CryostorageBuiState(GetAllContainedData(ent).ToList()); - _ui.TrySetUiState(ent, CryostorageUIKey.Key, state); + var state = new CryostorageBuiState(GetAllContainedData(ent)); + _ui.SetUiState(ent.Owner, CryostorageUIKey.Key, state); } private void OnPlayerSpawned(Entity ent, ref PlayerSpawnCompleteEvent args) @@ -293,12 +292,17 @@ protected override void OnInsertedContainer(Entity ent, re _chatManager.ChatMessageToOne(ChatChannel.Server, msg, msg, uid, false, actor.PlayerSession.Channel); } - private IEnumerable GetAllContainedData(Entity ent) + private List GetAllContainedData(Entity ent) { + var data = new List(); + data.EnsureCapacity(ent.Comp.StoredPlayers.Count); + foreach (var contained in ent.Comp.StoredPlayers) { - yield return GetContainedData(contained); + data.Add(GetContainedData(contained)); } + + return data; } private CryostorageContainedPlayerData GetContainedData(EntityUid uid) diff --git a/Content.Server/Bed/Sleep/SleepingSystem.cs b/Content.Server/Bed/Sleep/SleepingSystem.cs index 5e4f0eddb5..4edb2ed28d 100644 --- a/Content.Server/Bed/Sleep/SleepingSystem.cs +++ b/Content.Server/Bed/Sleep/SleepingSystem.cs @@ -94,8 +94,13 @@ private void OnDamageChanged(EntityUid uid, SleepingComponent component, DamageC if (!args.DamageIncreased || args.DamageDelta == null) return; - if (args.DamageDelta.GetTotal() >= component.WakeThreshold) + /* Surgery needs this, sorry! If the nocturine gamers get too feisty + I'll probably just increase the threshold */ + + if (args.DamageDelta.GetTotal() >= component.WakeThreshold + && !HasComp(uid)) TryWaking(uid, component); + } private void OnSleepAction(EntityUid uid, MobStateComponent component, SleepActionEvent args) diff --git a/Content.Server/Bible/BibleSystem.cs b/Content.Server/Bible/BibleSystem.cs index c845b17230..2798103cc1 100644 --- a/Content.Server/Bible/BibleSystem.cs +++ b/Content.Server/Bible/BibleSystem.cs @@ -14,6 +14,7 @@ using Content.Shared.Popups; using Content.Shared.Timing; using Content.Shared.Verbs; +using Content.Shared.Mood; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Player; @@ -153,6 +154,8 @@ private void OnAfterInteract(EntityUid uid, BibleComponent component, AfterInter _audio.PlayPvs(component.HealSoundPath, args.User); _delay.TryResetDelay((uid, useDelay)); } + + RaiseLocalEvent(args.Target.Value, new MoodEffectEvent("GotBlessed")); } private void AddSummonVerb(EntityUid uid, SummonableComponent component, GetVerbsEvent args) diff --git a/Content.Server/Body/Components/BloodstreamComponent.cs b/Content.Server/Body/Components/BloodstreamComponent.cs index dd93da9598..f7024a6395 100644 --- a/Content.Server/Body/Components/BloodstreamComponent.cs +++ b/Content.Server/Body/Components/BloodstreamComponent.cs @@ -1,5 +1,6 @@ using Content.Server.Body.Systems; using Content.Server.Chemistry.EntitySystems; +using Content.Server.Traits; using Content.Server.Traits.Assorted; using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Reagent; @@ -83,6 +84,14 @@ public sealed partial class BloodstreamComponent : Component [DataField] public FixedPoint2 BloodRefreshAmount = 1.0f; + /// + /// How much hunger/thirst is used to regenerate one unit of blood. Set to zero to disable. + /// The actual thirst/hunger rate will scale with . + /// + /// Those will have no effect if the entity has no hunger/thirst components. + [DataField] + public float BloodRegenerationHunger = 1f, BloodRegenerationThirst = 1f; + /// /// How much blood needs to be in the temporary solution in order to create a puddle? /// @@ -172,18 +181,5 @@ public sealed partial class BloodstreamComponent : Component /// [ViewVariables(VVAccess.ReadWrite)] public TimeSpan StatusTime; - - /// - /// If this is true, the entity will not passively regenerate blood, - /// and instead will slowly lose blood. - /// - [DataField] - public bool HasBloodDeficiency = false; - - /// - /// How much reagent of blood should be removed with blood deficiency in each update interval? - /// - [DataField] - public FixedPoint2 BloodDeficiencyLossAmount; } } diff --git a/Content.Server/Body/Components/RespiratorImmuneComponent.cs b/Content.Server/Body/Components/RespiratorImmuneComponent.cs new file mode 100644 index 0000000000..afc261eff2 --- /dev/null +++ b/Content.Server/Body/Components/RespiratorImmuneComponent.cs @@ -0,0 +1,4 @@ +namespace Content.Server.Body.Components; + +[RegisterComponent] +public sealed partial class RespiratorImmuneComponent : Component { } \ No newline at end of file diff --git a/Content.Server/Body/Events/NaturalBloodRegenerationAttemptEvent.cs b/Content.Server/Body/Events/NaturalBloodRegenerationAttemptEvent.cs new file mode 100644 index 0000000000..8ad2b88efe --- /dev/null +++ b/Content.Server/Body/Events/NaturalBloodRegenerationAttemptEvent.cs @@ -0,0 +1,15 @@ +using Content.Shared.FixedPoint; + +namespace Content.Server.Body.Events; + +/// +/// Raised on a mob when its bloodstream tries to perform natural blood regeneration. +/// +[ByRefEvent] +public sealed class NaturalBloodRegenerationAttemptEvent : CancellableEntityEventArgs +{ + /// + /// How much blood the mob will regenerate on this tick. Can be negative. + /// + public FixedPoint2 Amount; +} diff --git a/Content.Server/Body/Systems/BloodstreamSystem.cs b/Content.Server/Body/Systems/BloodstreamSystem.cs index b37ac5efeb..d1fad6541b 100644 --- a/Content.Server/Body/Systems/BloodstreamSystem.cs +++ b/Content.Server/Body/Systems/BloodstreamSystem.cs @@ -1,9 +1,9 @@ using Content.Server.Body.Components; +using Content.Server.Body.Events; using Content.Server.Chemistry.Containers.EntitySystems; using Content.Server.Chemistry.ReactionEffects; using Content.Server.Fluids.EntitySystems; using Content.Server.Forensics; -using Content.Server.HealthExaminable; using Content.Server.Popups; using Content.Shared.Alert; using Content.Shared.Chemistry.Components; @@ -13,7 +13,10 @@ using Content.Shared.Damage.Prototypes; using Content.Shared.Drunk; using Content.Shared.FixedPoint; +using Content.Shared.HealthExaminable; using Content.Shared.Mobs.Systems; +using Content.Shared.Nutrition.Components; +using Content.Shared.Nutrition.EntitySystems; using Content.Shared.Popups; using Content.Shared.Rejuvenate; using Content.Shared.Speech.EntitySystems; @@ -39,6 +42,8 @@ public sealed class BloodstreamSystem : EntitySystem [Dependency] private readonly SharedStutteringSystem _stutteringSystem = default!; [Dependency] private readonly AlertsSystem _alertsSystem = default!; [Dependency] private readonly ForensicsSystem _forensicsSystem = default!; + [Dependency] private readonly HungerSystem _hunger = default!; + [Dependency] private readonly ThirstSystem _thirst = default!; public override void Initialize() { @@ -118,17 +123,9 @@ public override void Update(float frameTime) if (!_solutionContainerSystem.ResolveSolution(uid, bloodstream.BloodSolutionName, ref bloodstream.BloodSolution, out var bloodSolution)) continue; - // Removes blood for Blood Deficiency constantly. - if (bloodstream.HasBloodDeficiency) - { - if (!_mobStateSystem.IsDead(uid)) - RemoveBlood(uid, bloodstream.BloodDeficiencyLossAmount, bloodstream); - } - // Adds blood to their blood level if it is below the maximum. - else if (bloodSolution.Volume < bloodSolution.MaxVolume && !_mobStateSystem.IsDead(uid)) - { - TryModifyBloodLevel(uid, bloodstream.BloodRefreshAmount, bloodstream); - } + // Try to apply natural blood regeneration/bloodloss + if (!_mobStateSystem.IsDead(uid)) + TryDoNaturalRegeneration((uid, bloodstream), bloodSolution); // Removes blood from the bloodstream based on bleed amount (bleed rate) // as well as stop their bleeding to a certain extent. @@ -349,6 +346,14 @@ public void SetBloodLossThreshold(EntityUid uid, float threshold, BloodstreamCom comp.BloodlossThreshold = threshold; } + public void SetBloodMaxVolume(EntityUid uid, FixedPoint2 volume, BloodstreamComponent? comp = null) + { + if (!Resolve(uid, ref comp)) + return; + + comp.BloodMaxVolume = volume; + } + /// /// Attempts to modify the blood level of this entity directly. /// @@ -490,4 +495,35 @@ private void RemoveBlood(EntityUid uid, FixedPoint2 amount, BloodstreamComponent bloodSolution.RemoveReagent(component.BloodReagent, amount); } + + /// + /// Tries to apply natural blood regeneration/loss to the entity. Returns true if succesful. + /// + private bool TryDoNaturalRegeneration(Entity ent, Solution bloodSolution) + { + var ev = new NaturalBloodRegenerationAttemptEvent { Amount = ent.Comp.BloodRefreshAmount }; + RaiseLocalEvent(ent, ref ev); + + if (ev.Cancelled || (ev.Amount > 0 && bloodSolution.Volume >= bloodSolution.MaxVolume)) + return false; + + var usedHunger = ev.Amount * ent.Comp.BloodRegenerationHunger; + var usedThirst = ev.Amount * ent.Comp.BloodRegenerationThirst; + + // First, check if the entity has enough hunger/thirst + var hungerComp = CompOrNull(ent); + var thirstComp = CompOrNull(ent); + if (usedHunger > 0 && hungerComp is not null && (hungerComp.CurrentHunger < usedHunger || hungerComp.CurrentThreshold <= HungerThreshold.Starving) + || usedThirst > 0 && thirstComp is not null && (thirstComp.CurrentThirst < usedThirst || thirstComp.CurrentThirstThreshold <= ThirstThreshold.Parched)) + return false; + + // Then actually expend hunger and thirst (if necessary) and regenerate blood. + if (usedHunger > 0 && hungerComp is not null) + _hunger.ModifyHunger(ent, (float) -usedHunger, hungerComp); + + if (usedThirst > 0 && thirstComp is not null) + _thirst.ModifyThirst(ent, thirstComp, (float) -usedThirst); + + return TryModifyBloodLevel(ent, ev.Amount, ent.Comp); + } } diff --git a/Content.Server/Body/Systems/BodySystem.cs b/Content.Server/Body/Systems/BodySystem.cs index 37f78ed81a..eb0c1df652 100644 --- a/Content.Server/Body/Systems/BodySystem.cs +++ b/Content.Server/Body/Systems/BodySystem.cs @@ -1,9 +1,12 @@ +using System.Linq; using Content.Server.Body.Components; using Content.Server.GameTicking; using Content.Server.Humanoid; using Content.Shared.Body.Components; using Content.Shared.Body.Part; using Content.Shared.Body.Systems; +using Content.Shared.Damage; +using Content.Shared.Gibbing.Events; using Content.Shared.Humanoid; using Content.Shared.Mind; using Content.Shared.Mobs.Systems; @@ -22,8 +25,8 @@ public sealed class BodySystem : SharedBodySystem [Dependency] private readonly GameTicker _ticker = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly HumanoidAppearanceSystem _humanoidSystem = default!; + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly MobStateSystem _mobState = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedMindSystem _mindSystem = default!; public override void Initialize() @@ -96,8 +99,10 @@ protected override void RemovePart( return; var layers = HumanoidVisualLayersExtension.Sublayers(layer.Value); + _humanoidSystem.SetLayersVisibility( bodyEnt, layers, visible: false, permanent: true, humanoid); + _appearance.SetData(bodyEnt, layer, true); } public override HashSet GibBody( @@ -108,8 +113,9 @@ public override HashSet GibBody( Vector2? splatDirection = null, float splatModifier = 1, Angle splatCone = default, - SoundSpecifier? gibSoundOverride = null - ) + SoundSpecifier? gibSoundOverride = null, + GibType gib = GibType.Gib, + GibContentsOption contents = GibContentsOption.Drop) { if (!Resolve(bodyId, ref body, logMissing: false) || TerminatingOrDeleted(bodyId) @@ -123,7 +129,8 @@ public override HashSet GibBody( return new HashSet(); var gibs = base.GibBody(bodyId, gibOrgans, body, launchGibs: launchGibs, - splatDirection: splatDirection, splatModifier: splatModifier, splatCone:splatCone); + splatDirection: splatDirection, splatModifier: splatModifier, splatCone: splatCone, + gib: gib, contents: contents); var ev = new BeingGibbedEvent(gibs); RaiseLocalEvent(bodyId, ref ev); @@ -132,4 +139,46 @@ public override HashSet GibBody( return gibs; } + + public override HashSet GibPart( + EntityUid partId, + BodyPartComponent? part = null, + bool launchGibs = true, + Vector2? splatDirection = null, + float splatModifier = 1, + Angle splatCone = default, + SoundSpecifier? gibSoundOverride = null) + { + if (!Resolve(partId, ref part, logMissing: false) + || TerminatingOrDeleted(partId) + || EntityManager.IsQueuedForDeletion(partId)) + return new HashSet(); + + if (Transform(partId).MapUid is null) + return new HashSet(); + + var gibs = base.GibPart(partId, part, launchGibs: launchGibs, + splatDirection: splatDirection, splatModifier: splatModifier, splatCone: splatCone); + + var ev = new BeingGibbedEvent(gibs); + RaiseLocalEvent(partId, ref ev); + + QueueDel(partId); + + return gibs; + } + + protected override void ApplyPartMarkings(EntityUid target, BodyPartAppearanceComponent component) + { + return; + } + + protected override void RemoveBodyMarkings(EntityUid target, BodyPartAppearanceComponent partAppearance, HumanoidAppearanceComponent bodyAppearance) + { + foreach (var (visualLayer, markingList) in partAppearance.Markings) + foreach (var marking in markingList) + _humanoidSystem.RemoveMarking(target, marking.MarkingId, sync: false, humanoid: bodyAppearance); + + Dirty(target, bodyAppearance); + } } diff --git a/Content.Server/Body/Systems/BrainSystem.cs b/Content.Server/Body/Systems/BrainSystem.cs index 86d2cb61ff..ae14da0e81 100644 --- a/Content.Server/Body/Systems/BrainSystem.cs +++ b/Content.Server/Body/Systems/BrainSystem.cs @@ -2,6 +2,7 @@ using Content.Server.Ghost.Components; using Content.Shared.Body.Components; using Content.Shared.Body.Events; +using Content.Shared.Body.Organ; using Content.Shared.Mind; using Content.Shared.Mind.Components; using Content.Shared.Pointing; @@ -12,15 +13,34 @@ public sealed class BrainSystem : EntitySystem { [Dependency] private readonly SharedMindSystem _mindSystem = default!; + // Shitmed-Start public override void Initialize() { base.Initialize(); - SubscribeLocalEvent((uid, _, args) => HandleMind(args.Body, uid)); - SubscribeLocalEvent((uid, _, args) => HandleMind(uid, args.OldBody)); + SubscribeLocalEvent(HandleAddition); + SubscribeLocalEvent(HandleRemoval); SubscribeLocalEvent(OnPointAttempt); } + private void HandleRemoval(EntityUid uid, BrainComponent _, ref OrganRemovedFromBodyEvent args) + { + if (TerminatingOrDeleted(uid) || TerminatingOrDeleted(args.OldBody)) + return; + + // Prevents revival, should kill the user within a given timespan too. + EnsureComp(args.OldBody); + HandleMind(uid, args.OldBody); + } + private void HandleAddition(EntityUid uid, BrainComponent _, ref OrganAddedToBodyEvent args) + { + if (TerminatingOrDeleted(uid) || TerminatingOrDeleted(args.Body)) + return; + + RemComp(args.Body); + HandleMind(args.Body, uid); + } + // Shitmed-End private void HandleMind(EntityUid newEntity, EntityUid oldEntity) { if (TerminatingOrDeleted(newEntity) || TerminatingOrDeleted(oldEntity)) diff --git a/Content.Server/Body/Systems/InternalsSystem.cs b/Content.Server/Body/Systems/InternalsSystem.cs index 9607a808f6..db078e2f29 100644 --- a/Content.Server/Body/Systems/InternalsSystem.cs +++ b/Content.Server/Body/Systems/InternalsSystem.cs @@ -8,6 +8,7 @@ using Content.Shared.Hands.Components; using Content.Shared.Internals; using Content.Shared.Inventory; +using Content.Shared.Roles; using Content.Shared.Verbs; using Robust.Shared.Containers; using Robust.Shared.Utility; @@ -23,17 +24,29 @@ public sealed class InternalsSystem : EntitySystem [Dependency] private readonly InventorySystem _inventory = default!; [Dependency] private readonly PopupSystem _popupSystem = default!; - public const SlotFlags InventorySlots = SlotFlags.POCKET | SlotFlags.BELT; + private EntityQuery _internalsQuery; public override void Initialize() { base.Initialize(); + _internalsQuery = GetEntityQuery(); + SubscribeLocalEvent(OnInhaleLocation); SubscribeLocalEvent(OnInternalsStartup); SubscribeLocalEvent(OnInternalsShutdown); SubscribeLocalEvent>(OnGetInteractionVerbs); SubscribeLocalEvent(OnDoAfter); + + SubscribeLocalEvent(OnStartingGear); + } + + private void OnStartingGear(ref StartingGearEquippedEvent ev) + { + if (!_internalsQuery.TryComp(ev.Entity, out var internals) || internals.BreathToolEntity == null) + return; + + ToggleInternals(ev.Entity, ev.Entity, force: false, internals); } private void OnGetInteractionVerbs( @@ -218,7 +231,7 @@ private short GetSeverity(InternalsComponent component) if (component.BreathToolEntity is null || !AreInternalsWorking(component)) return 2; - // If pressure in the tank is below low pressure threshhold, flash warning on internals UI + // If pressure in the tank is below low pressure threshold, flash warning on internals UI if (TryComp(component.GasTankEntity, out var gasTank) && gasTank.IsLowPressure) { diff --git a/Content.Server/Body/Systems/RespiratorSystem.cs b/Content.Server/Body/Systems/RespiratorSystem.cs index c7266e2c46..8b2b19a2ac 100644 --- a/Content.Server/Body/Systems/RespiratorSystem.cs +++ b/Content.Server/Body/Systems/RespiratorSystem.cs @@ -7,9 +7,11 @@ using Content.Shared.Alert; using Content.Shared.Atmos; using Content.Shared.Body.Components; +using Content.Shared.Body.Organ; using Content.Shared.Damage; using Content.Shared.Database; using Content.Shared.Mobs.Systems; +using Content.Shared.Mood; using JetBrains.Annotations; using Robust.Shared.Timing; @@ -65,9 +67,12 @@ public override void Update(float frameTime) if (_mobState.IsDead(uid)) continue; + if (HasComp(uid)) + continue; + UpdateSaturation(uid, -(float) respirator.UpdateInterval.TotalSeconds, respirator); - if (!_mobState.IsIncapacitated(uid)) // cannot breathe in crit. + if (!_mobState.IsIncapacitated(uid) || HasComp(uid)) // Shitmed: cannot breathe in crit or when no brain. { switch (respirator.Status) { @@ -177,6 +182,7 @@ private void TakeSuffocationDamage(Entity ent) { _alertsSystem.ShowAlert(ent, comp.Alert); } + RaiseLocalEvent(ent, new MoodEffectEvent("Suffocating")); } _damageableSys.TryChangeDamage(ent, ent.Comp.Damage, interruptsDoAfters: false); diff --git a/Content.Server/Botany/Components/SeedExtractorComponent.cs b/Content.Server/Botany/Components/SeedExtractorComponent.cs index ddb04f213d..d765e079ce 100644 --- a/Content.Server/Botany/Components/SeedExtractorComponent.cs +++ b/Content.Server/Botany/Components/SeedExtractorComponent.cs @@ -1,4 +1,7 @@ using Content.Server.Botany.Systems; +using Content.Server.Construction; +using Content.Shared.Construction.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; namespace Content.Server.Botany.Components; @@ -7,14 +10,33 @@ namespace Content.Server.Botany.Components; public sealed partial class SeedExtractorComponent : Component { /// - /// The minimum amount of seed packets dropped. + /// The minimum amount of seed packets dropped with no machine upgrades. /// - [DataField("baseMinSeeds"), ViewVariables(VVAccess.ReadWrite)] + [DataField] public int BaseMinSeeds = 1; /// - /// The maximum amount of seed packets dropped. + /// The maximum amount of seed packets dropped with no machine upgrades. /// - [DataField("baseMaxSeeds"), ViewVariables(VVAccess.ReadWrite)] + [DataField] public int BaseMaxSeeds = 3; + + /// + /// Modifier to the amount of seeds outputted, set on . + /// + [ViewVariables(VVAccess.ReadWrite)] + public float SeedAmountMultiplier; + + /// + /// Machine part whose rating modifies the amount of seed packets dropped. + /// + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string MachinePartSeedAmount = "Manipulator"; + + /// + /// How much the machine part quality affects the amount of seeds outputted. + /// Going up a tier will multiply the seed output by this amount. + /// + [DataField] + public float PartRatingSeedAmountMultiplier = 1.5f; } diff --git a/Content.Server/Botany/SeedPrototype.cs b/Content.Server/Botany/SeedPrototype.cs index 2644da2a3b..dd3555f143 100644 --- a/Content.Server/Botany/SeedPrototype.cs +++ b/Content.Server/Botany/SeedPrototype.cs @@ -2,16 +2,16 @@ using Content.Server.Botany.Systems; using Content.Shared.Atmos; using Content.Shared.Chemistry.Reagent; +using Robust.Shared.Audio; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; using Robust.Shared.Utility; -using Robust.Shared.Audio; namespace Content.Server.Botany; [Prototype("seed")] -public sealed class SeedPrototype : SeedData, IPrototype +public sealed partial class SeedPrototype : SeedData, IPrototype { [IdDataField] public string ID { get; private init; } = default!; } @@ -70,7 +70,7 @@ public partial struct SeedChemQuantity /// When chemicals are added to produce, the potency of the seed is divided with this value. Final chemical amount is the result plus the `Min` value. /// Example: PotencyDivisor of 20 with seed potency of 55 results in 2.75, 55/20 = 2.75. If minimum is 1 then final result will be 3.75 of that chemical, 55/20+1 = 3.75. /// - [DataField("PotencyDivisor")] public int PotencyDivisor; + [DataField] public int PotencyDivisor; /// /// Inherent chemical is one that is NOT result of mutation or crossbreeding. These chemicals are removed if species mutation is executed. diff --git a/Content.Server/Botany/Systems/PlantHolderSystem.cs b/Content.Server/Botany/Systems/PlantHolderSystem.cs index 721536a7c0..c8842e1493 100644 --- a/Content.Server/Botany/Systems/PlantHolderSystem.cs +++ b/Content.Server/Botany/Systems/PlantHolderSystem.cs @@ -6,6 +6,7 @@ using Content.Server.Ghost.Roles.Components; using Content.Server.Kitchen.Components; using Content.Server.Popups; +using Content.Shared.Atmos; using Content.Shared.Botany; using Content.Shared.Burial.Components; using Content.Shared.Chemistry.Reagent; diff --git a/Content.Server/Botany/Systems/SeedExtractorSystem.cs b/Content.Server/Botany/Systems/SeedExtractorSystem.cs index f1ae6c9f11..4c547b96f0 100644 --- a/Content.Server/Botany/Systems/SeedExtractorSystem.cs +++ b/Content.Server/Botany/Systems/SeedExtractorSystem.cs @@ -1,8 +1,10 @@ using Content.Server.Botany.Components; +using Content.Server.Construction; using Content.Server.Popups; using Content.Server.Power.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Popups; +using Robust.Shared.Player; using Robust.Shared.Random; namespace Content.Server.Botany.Systems; @@ -18,6 +20,8 @@ public override void Initialize() base.Initialize(); SubscribeLocalEvent(OnInteractUsing); + SubscribeLocalEvent(OnRefreshParts); + SubscribeLocalEvent(OnUpgradeExamine); } private void OnInteractUsing(EntityUid uid, SeedExtractorComponent seedExtractor, InteractUsingEvent args) @@ -25,8 +29,7 @@ private void OnInteractUsing(EntityUid uid, SeedExtractorComponent seedExtractor if (!this.IsPowered(uid, EntityManager)) return; - if (!TryComp(args.Used, out ProduceComponent? produce)) - return; + if (!TryComp(args.Used, out ProduceComponent? produce)) return; if (!_botanySystem.TryGetSeed(produce, out var seed) || seed.Seedless) { _popupSystem.PopupCursor(Loc.GetString("seed-extractor-component-no-seeds",("name", args.Used)), @@ -39,7 +42,7 @@ private void OnInteractUsing(EntityUid uid, SeedExtractorComponent seedExtractor QueueDel(args.Used); - var amount = _random.Next(seedExtractor.BaseMinSeeds, seedExtractor.BaseMaxSeeds + 1); + var amount = (int) _random.NextFloat(seedExtractor.BaseMinSeeds, seedExtractor.BaseMaxSeeds + 1) * seedExtractor.SeedAmountMultiplier; var coords = Transform(uid).Coordinates; if (amount > 1) @@ -50,4 +53,15 @@ private void OnInteractUsing(EntityUid uid, SeedExtractorComponent seedExtractor _botanySystem.SpawnSeedPacket(seed, coords, args.User); } } + + private void OnRefreshParts(EntityUid uid, SeedExtractorComponent seedExtractor, RefreshPartsEvent args) + { + var manipulatorQuality = args.PartRatings[seedExtractor.MachinePartSeedAmount]; + seedExtractor.SeedAmountMultiplier = MathF.Pow(seedExtractor.PartRatingSeedAmountMultiplier, manipulatorQuality - 1); + } + + private void OnUpgradeExamine(EntityUid uid, SeedExtractorComponent seedExtractor, UpgradeExamineEvent args) + { + args.AddPercentageUpgrade("seed-extractor-component-upgrade-seed-yield", seedExtractor.SeedAmountMultiplier); + } } diff --git a/Content.Server/Cargo/Components/StationCargoOrderDatabaseComponent.cs b/Content.Server/Cargo/Components/StationCargoOrderDatabaseComponent.cs index c30db08bbe..2e3b2c2115 100644 --- a/Content.Server/Cargo/Components/StationCargoOrderDatabaseComponent.cs +++ b/Content.Server/Cargo/Components/StationCargoOrderDatabaseComponent.cs @@ -1,4 +1,6 @@ +using Content.Server.Station.Components; using Content.Shared.Cargo; +using Content.Shared.Cargo.Components; using Content.Shared.Cargo.Prototypes; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; @@ -38,3 +40,17 @@ public sealed partial class StationCargoOrderDatabaseComponent : Component [DataField] public EntProtoId PrinterOutput = "PaperCargoInvoice"; } + +/// +/// Event broadcast before a cargo order is fulfilled, allowing alternate systems to fulfill the order. +/// +[ByRefEvent] +public record struct FulfillCargoOrderEvent(Entity Station, CargoOrderData Order, Entity OrderConsole) +{ + public Entity OrderConsole = OrderConsole; + public Entity Station = Station; + public CargoOrderData Order = Order; + + public EntityUid? FulfillmentEntity; + public bool Handled = false; +} diff --git a/Content.Server/Cargo/Components/StationLogisticStatsDatabaseComponent.cs b/Content.Server/Cargo/Components/StationLogisticStatsDatabaseComponent.cs new file mode 100644 index 0000000000..ca1fbeaad0 --- /dev/null +++ b/Content.Server/Cargo/Components/StationLogisticStatsDatabaseComponent.cs @@ -0,0 +1,14 @@ +using Content.Shared.Cargo; +using Content.Shared.CartridgeLoader.Cartridges; + +namespace Content.Server.Cargo.Components; + +/// +/// Added to the abstract representation of a station to track stats related to mail delivery and income +/// +[RegisterComponent, Access(typeof(SharedCargoSystem))] +public sealed partial class StationLogisticStatsComponent : Component +{ + [DataField] + public MailStats Metrics { get; set; } +} diff --git a/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs b/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs index 22e5c67e17..e132e4f12a 100644 --- a/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs +++ b/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs @@ -52,7 +52,7 @@ private void OnBountyConsoleOpened(EntityUid uid, CargoBountyConsoleComponent co return; var untilNextSkip = bountyDb.NextSkipTime - _timing.CurTime; - _uiSystem.TrySetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(bountyDb.Bounties, untilNextSkip)); + _uiSystem.SetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(bountyDb.Bounties, untilNextSkip)); } private void OnPrintLabelMessage(EntityUid uid, CargoBountyConsoleComponent component, BountyPrintLabelMessage args) @@ -83,7 +83,7 @@ private void OnSkipBountyMessage(EntityUid uid, CargoBountyConsoleComponent comp if (!TryGetBountyFromId(station, args.BountyId, out var bounty)) return; - if (args.Session.AttachedEntity is not { Valid: true } mob) + if (args.Actor is not { Valid: true } mob) return; if (TryComp(uid, out var accessReaderComponent) && @@ -99,7 +99,7 @@ private void OnSkipBountyMessage(EntityUid uid, CargoBountyConsoleComponent comp FillBountyDatabase(station); db.NextSkipTime = _timing.CurTime + db.SkipDelay; var untilNextSkip = db.NextSkipTime - _timing.CurTime; - _uiSystem.TrySetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(db.Bounties, untilNextSkip)); + _uiSystem.SetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(db.Bounties, untilNextSkip)); _audio.PlayPvs(component.SkipSound, uid); } @@ -462,10 +462,12 @@ public void UpdateBountyConsoles() { if (_station.GetOwningStation(uid) is not { } station || !TryComp(station, out var db)) + { continue; + } var untilNextSkip = db.NextSkipTime - _timing.CurTime; - _uiSystem.TrySetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(db.Bounties, untilNextSkip), ui: ui); + _uiSystem.SetUiState((uid, ui), CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(db.Bounties, untilNextSkip)); } } diff --git a/Content.Server/Cargo/Systems/CargoSystem.Orders.cs b/Content.Server/Cargo/Systems/CargoSystem.Orders.cs index d8b55a7237..8978e6b2bf 100644 --- a/Content.Server/Cargo/Systems/CargoSystem.Orders.cs +++ b/Content.Server/Cargo/Systems/CargoSystem.Orders.cs @@ -102,12 +102,12 @@ private void UpdateConsole(float frameTime) private void OnApproveOrderMessage(EntityUid uid, CargoOrderConsoleComponent component, CargoConsoleApproveOrderMessage args) { - if (args.Session.AttachedEntity is not { Valid: true } player) + if (args.Actor is not { Valid: true } player) return; if (!_accessReaderSystem.IsAllowed(player, uid)) { - ConsolePopup(args.Session, Loc.GetString("cargo-console-order-not-allowed")); + ConsolePopup(args.Actor, Loc.GetString("cargo-console-order-not-allowed")); PlayDenySound(uid, component); return; } @@ -119,7 +119,7 @@ private void OnApproveOrderMessage(EntityUid uid, CargoOrderConsoleComponent com !TryComp(station, out StationDataComponent? stationData) || !TryGetOrderDatabase(station, out var orderDatabase)) { - ConsolePopup(args.Session, Loc.GetString("cargo-console-station-not-found")); + ConsolePopup(args.Actor, Loc.GetString("cargo-console-station-not-found")); PlayDenySound(uid, component); return; } @@ -134,7 +134,7 @@ private void OnApproveOrderMessage(EntityUid uid, CargoOrderConsoleComponent com // Invalid order if (!_protoMan.HasIndex(order.ProductId)) { - ConsolePopup(args.Session, Loc.GetString("cargo-console-invalid-product")); + ConsolePopup(args.Actor, Loc.GetString("cargo-console-invalid-product")); PlayDenySound(uid, component); return; } @@ -145,7 +145,7 @@ private void OnApproveOrderMessage(EntityUid uid, CargoOrderConsoleComponent com // Too many orders, avoid them getting spammed in the UI. if (amount >= capacity) { - ConsolePopup(args.Session, Loc.GetString("cargo-console-too-many")); + ConsolePopup(args.Actor, Loc.GetString("cargo-console-too-many")); PlayDenySound(uid, component); return; } @@ -156,7 +156,7 @@ private void OnApproveOrderMessage(EntityUid uid, CargoOrderConsoleComponent com if (cappedAmount != order.OrderQuantity) { order.OrderQuantity = cappedAmount; - ConsolePopup(args.Session, Loc.GetString("cargo-console-snip-snip")); + ConsolePopup(args.Actor, Loc.GetString("cargo-console-snip-snip")); PlayDenySound(uid, component); } @@ -165,18 +165,25 @@ private void OnApproveOrderMessage(EntityUid uid, CargoOrderConsoleComponent com // Not enough balance if (cost > bank.Balance) { - ConsolePopup(args.Session, Loc.GetString("cargo-console-insufficient-funds", ("cost", cost))); + ConsolePopup(args.Actor, Loc.GetString("cargo-console-insufficient-funds", ("cost", cost))); PlayDenySound(uid, component); return; } - var tradeDestination = TryFulfillOrder(stationData, order, orderDatabase); + var ev = new FulfillCargoOrderEvent((station.Value, stationData), order, (uid, component)); + RaiseLocalEvent(ref ev); + ev.FulfillmentEntity ??= station.Value; - if (tradeDestination == null) + if (!ev.Handled) { - ConsolePopup(args.Session, Loc.GetString("cargo-console-unfulfilled")); - PlayDenySound(uid, component); - return; + ev.FulfillmentEntity = TryFulfillOrder((station.Value, stationData), order, orderDatabase); + + if (ev.FulfillmentEntity == null) + { + ConsolePopup(args.Actor, Loc.GetString("cargo-console-unfulfilled")); + PlayDenySound(uid, component); + return; + } } _idCardSystem.TryFindIdCard(player, out var idCard); @@ -184,7 +191,16 @@ private void OnApproveOrderMessage(EntityUid uid, CargoOrderConsoleComponent com order.SetApproverData(idCard.Comp?.FullName, idCard.Comp?.JobTitle); _audio.PlayPvs(component.ConfirmSound, uid); - ConsolePopup(args.Session, Loc.GetString("cargo-console-trade-station", ("destination", MetaData(tradeDestination.Value).EntityName))); + var approverName = idCard.Comp?.FullName ?? Loc.GetString("access-reader-unknown-id"); + var approverJob = idCard.Comp?.JobTitle ?? Loc.GetString("access-reader-unknown-id"); + var message = Loc.GetString("cargo-console-unlock-approved-order-broadcast", + ("productName", Loc.GetString(order.ProductName)), + ("orderAmount", order.OrderQuantity), + ("approverName", approverName), + ("approverJob", approverJob), + ("cost", cost)); + _radio.SendRadioMessage(uid, message, component.AnnouncementChannel, uid, escapeMarkup: false); + ConsolePopup(args.Actor, Loc.GetString("cargo-console-trade-station", ("destination", MetaData(ev.FulfillmentEntity.Value).EntityName))); // Log order approval _adminLogger.Add(LogType.Action, LogImpact.Low, @@ -192,10 +208,10 @@ private void OnApproveOrderMessage(EntityUid uid, CargoOrderConsoleComponent com orderDatabase.Orders.Remove(order); DeductFunds(bank, cost); - UpdateOrders(station.Value, orderDatabase); + UpdateOrders(station.Value); } - private EntityUid? TryFulfillOrder(StationDataComponent stationData, CargoOrderData order, StationCargoOrderDatabaseComponent orderDatabase) + private EntityUid? TryFulfillOrder(Entity stationData, CargoOrderData order, StationCargoOrderDatabaseComponent orderDatabase) { // No slots at the trade station _listEnts.Clear(); @@ -257,7 +273,7 @@ private void OnRemoveOrderMessage(EntityUid uid, CargoOrderConsoleComponent comp private void OnAddOrderMessage(EntityUid uid, CargoOrderConsoleComponent component, CargoConsoleAddOrderMessage args) { - if (args.Session.AttachedEntity is not { Valid: true } player) + if (args.Actor is not { Valid: true } player) return; if (args.Amount <= 0) @@ -305,9 +321,9 @@ private void UpdateOrderState(EntityUid consoleUid, EntityUid? station) !TryComp(station, out var orderDatabase) || !TryComp(station, out var bankAccount)) return; - if (_uiSystem.TryGetUi(consoleUid, CargoConsoleUiKey.Orders, out var bui)) + if (_uiSystem.HasUi(consoleUid, CargoConsoleUiKey.Orders)) { - _uiSystem.SetUiState(bui, new CargoConsoleInterfaceState( + _uiSystem.SetUiState(consoleUid, CargoConsoleUiKey.Orders, new CargoConsoleInterfaceState( MetaData(station.Value).EntityName, GetOutstandingOrderCount(orderDatabase), orderDatabase.Capacity, @@ -317,9 +333,9 @@ private void UpdateOrderState(EntityUid consoleUid, EntityUid? station) } } - private void ConsolePopup(ICommonSession session, string text) + private void ConsolePopup(EntityUid actor, string text) { - _popup.PopupCursor(text, session); + _popup.PopupCursor(text, actor); } private void PlayDenySound(EntityUid uid, CargoOrderConsoleComponent component) @@ -329,7 +345,7 @@ private void PlayDenySound(EntityUid uid, CargoOrderConsoleComponent component) private static CargoOrderData GetOrderData(CargoConsoleAddOrderMessage args, CargoProductPrototype cargoProduct, int id) { - return new CargoOrderData(id, cargoProduct.Product, cargoProduct.Cost, args.Amount, args.Requester, args.Reason); + return new CargoOrderData(id, cargoProduct.Product, cargoProduct.Name, cargoProduct.Cost, args.Amount, args.Requester, args.Reason); } public static int GetOutstandingOrderCount(StationCargoOrderDatabaseComponent component) @@ -350,7 +366,7 @@ public static int GetOutstandingOrderCount(StationCargoOrderDatabaseComponent co /// Updates all of the cargo-related consoles for a particular station. /// This should be called whenever orders change. /// - private void UpdateOrders(EntityUid dbUid, StationCargoOrderDatabaseComponent _) + private void UpdateOrders(EntityUid dbUid) { // Order added so all consoles need updating. var orderQuery = AllEntityQuery(); @@ -378,19 +394,20 @@ private void UpdateOrders(EntityUid dbUid, StationCargoOrderDatabaseComponent _) public bool AddAndApproveOrder( EntityUid dbUid, string spawnId, + string name, int cost, int qty, string sender, string description, string dest, StationCargoOrderDatabaseComponent component, - StationDataComponent stationData + Entity stationData ) { DebugTools.Assert(_protoMan.HasIndex(spawnId)); // Make an order var id = GenerateOrderId(component); - var order = new CargoOrderData(id, spawnId, cost, qty, sender, description); + var order = new CargoOrderData(id, spawnId, name, cost, qty, sender, description); // Approve it now order.SetApproverData(dest, sender); @@ -406,7 +423,7 @@ StationDataComponent stationData private bool TryAddOrder(EntityUid dbUid, CargoOrderData data, StationCargoOrderDatabaseComponent component) { component.Orders.Add(data); - UpdateOrders(dbUid, component); + UpdateOrders(dbUid); return true; } @@ -424,7 +441,7 @@ public void RemoveOrder(EntityUid dbUid, int index, StationCargoOrderDatabaseCom { orderDB.Orders.RemoveAt(sequenceIdx); } - UpdateOrders(dbUid, orderDB); + UpdateOrders(dbUid); } public void ClearOrders(StationCargoOrderDatabaseComponent component) diff --git a/Content.Server/Cargo/Systems/CargoSystem.Shuttle.cs b/Content.Server/Cargo/Systems/CargoSystem.Shuttle.cs index 0484e05ced..1c5c7ed1c0 100644 --- a/Content.Server/Cargo/Systems/CargoSystem.Shuttle.cs +++ b/Content.Server/Cargo/Systems/CargoSystem.Shuttle.cs @@ -79,21 +79,20 @@ private void UpdateCargoShuttleConsoles(EntityUid shuttleUid, CargoShuttleCompon private void UpdatePalletConsoleInterface(EntityUid uid) { - var bui = _uiSystem.GetUi(uid, CargoPalletConsoleUiKey.Sale); if (Transform(uid).GridUid is not EntityUid gridUid) { - _uiSystem.SetUiState(bui, + _uiSystem.SetUiState(uid, CargoPalletConsoleUiKey.Sale, new CargoPalletConsoleInterfaceState(0, 0, false)); return; } GetPalletGoods(gridUid, out var toSell, out var amount); - _uiSystem.SetUiState(bui, + _uiSystem.SetUiState(uid, CargoPalletConsoleUiKey.Sale, new CargoPalletConsoleInterfaceState((int) amount, toSell.Count, true)); } private void OnPalletUIOpen(EntityUid uid, CargoPalletConsoleComponent component, BoundUIOpenedEvent args) { - var player = args.Session.AttachedEntity; + var player = args.Actor; if (player == null) return; @@ -111,7 +110,7 @@ private void OnPalletUIOpen(EntityUid uid, CargoPalletConsoleComponent component private void OnPalletAppraise(EntityUid uid, CargoPalletConsoleComponent component, CargoPalletAppraiseMessage args) { - var player = args.Session.AttachedEntity; + var player = args.Actor; if (player == null) return; @@ -133,8 +132,8 @@ private void UpdateShuttleState(EntityUid uid, EntityUid? station = null) var orders = GetProjectedOrders(station ?? EntityUid.Invalid, orderDatabase, shuttle); var shuttleName = orderDatabase?.Shuttle != null ? MetaData(orderDatabase.Shuttle.Value).EntityName : string.Empty; - if (_uiSystem.TryGetUi(uid, CargoConsoleUiKey.Shuttle, out var bui)) - _uiSystem.SetUiState(bui, new CargoShuttleConsoleBoundUserInterfaceState( + if (_uiSystem.HasUi(uid, CargoConsoleUiKey.Shuttle)) + _uiSystem.SetUiState(uid, CargoConsoleUiKey.Shuttle, new CargoShuttleConsoleBoundUserInterfaceState( station != null ? MetaData(station.Value).EntityName : Loc.GetString("cargo-shuttle-console-station-unknown"), string.IsNullOrEmpty(shuttleName) ? Loc.GetString("cargo-shuttle-console-shuttle-not-found") : shuttleName, orders @@ -179,7 +178,7 @@ private List GetProjectedOrders( // We won't be able to fit the whole order on, so make one // which represents the space we do have left: var reducedOrder = new CargoOrderData(order.OrderId, - order.ProductId, order.Price, spaceRemaining, order.Requester, order.Reason); + order.ProductId, order.ProductName, order.Price, spaceRemaining, order.Requester, order.Reason); orders.Add(reducedOrder); } else @@ -339,17 +338,16 @@ private bool CanSell(EntityUid uid, TransformComponent xform) private void OnPalletSale(EntityUid uid, CargoPalletConsoleComponent component, CargoPalletSellMessage args) { - var player = args.Session.AttachedEntity; + var player = args.Actor; if (player == null) return; - var bui = _uiSystem.GetUi(uid, CargoPalletConsoleUiKey.Sale); var xform = Transform(uid); if (xform.GridUid is not EntityUid gridUid) { - _uiSystem.SetUiState(bui, + _uiSystem.SetUiState(uid, CargoPalletConsoleUiKey.Sale, new CargoPalletConsoleInterfaceState(0, 0, false)); return; } diff --git a/Content.Server/Cargo/Systems/CargoSystem.Telepad.cs b/Content.Server/Cargo/Systems/CargoSystem.Telepad.cs index 42aabf2578..223dd2ac32 100644 --- a/Content.Server/Cargo/Systems/CargoSystem.Telepad.cs +++ b/Content.Server/Cargo/Systems/CargoSystem.Telepad.cs @@ -1,9 +1,15 @@ +using System.Linq; using Content.Server.Cargo.Components; +using Content.Server.Construction; +using Content.Server.Paper; using Content.Server.Power.Components; +using Content.Server.Power.EntitySystems; +using Content.Server.Station.Components; using Content.Shared.Cargo; using Content.Shared.Cargo.Components; using Content.Shared.DeviceLinking; using Robust.Shared.Audio; +using Robust.Shared.Random; using Robust.Shared.Utility; namespace Content.Server.Cargo.Systems; @@ -13,10 +19,46 @@ public sealed partial class CargoSystem private void InitializeTelepad() { SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnRefreshParts); + SubscribeLocalEvent(OnUpgradeExamine); + SubscribeLocalEvent(OnShutdown); SubscribeLocalEvent(OnTelepadPowerChange); // Shouldn't need re-anchored event SubscribeLocalEvent(OnTelepadAnchorChange); + SubscribeLocalEvent(OnTelepadFulfillCargoOrder); } + + private void OnTelepadFulfillCargoOrder(ref FulfillCargoOrderEvent args) + { + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var tele, out var xform)) + { + if (tele.CurrentState != CargoTelepadState.Idle) + continue; + + if (!this.IsPowered(uid, EntityManager)) + continue; + + if (_station.GetOwningStation(uid, xform) != args.Station) + continue; + + // todo cannot be fucking asked to figure out device linking rn but this shouldn't just default to the first port. + if (!TryComp(uid, out var sinkComponent) || + sinkComponent.LinkedSources.FirstOrNull() is not { } console || + console != args.OrderConsole.Owner) + continue; + + for (var i = 0; i < args.Order.OrderQuantity; i++) + { + tele.CurrentOrders.Add(args.Order); + } + tele.Accumulator = tele.Delay; + args.Handled = true; + args.FulfillmentEntity = uid; + return; + } + } + private void UpdateTelepad(float frameTime) { var query = EntityQueryEnumerator(); @@ -33,14 +75,6 @@ private void UpdateTelepad(float frameTime) continue; } - if (!TryComp(uid, out var sinkComponent) || - sinkComponent.LinkedSources.FirstOrNull() is not { } console || - !HasComp(console)) - { - comp.Accumulator = comp.Delay; - continue; - } - comp.Accumulator -= frameTime; // Uhh listen teleporting takes time and I just want the 1 float. @@ -51,21 +85,22 @@ private void UpdateTelepad(float frameTime) continue; } - var station = _station.GetOwningStation(console); - - if (!TryComp(station, out var orderDatabase) || - orderDatabase.Orders.Count == 0) + if (comp.CurrentOrders.Count == 0) { comp.Accumulator += comp.Delay; continue; } var xform = Transform(uid); - if (FulfillNextOrder(orderDatabase, xform.Coordinates, comp.PrinterOutput)) + var currentOrder = comp.CurrentOrders.First(); + if (FulfillOrder(currentOrder, xform.Coordinates, comp.PrinterOutput)) { _audio.PlayPvs(_audio.GetSound(comp.TeleportSound), uid, AudioParams.Default.WithVolume(-8f)); - UpdateOrders(station.Value, orderDatabase); + if (_station.GetOwningStation(uid) is { } station) + UpdateOrders(station); + + comp.CurrentOrders.Remove(currentOrder); comp.CurrentState = CargoTelepadState.Teleporting; _appearance.SetData(uid, CargoTelepadVisuals.State, CargoTelepadState.Teleporting, appearance); } @@ -79,6 +114,40 @@ private void OnInit(EntityUid uid, CargoTelepadComponent telepad, ComponentInit _linker.EnsureSinkPorts(uid, telepad.ReceiverPort); } + private void OnRefreshParts(EntityUid uid, CargoTelepadComponent component, RefreshPartsEvent args) + { + var rating = args.PartRatings[component.MachinePartTeleportDelay] - 1; + component.Delay = component.BaseDelay * MathF.Pow(component.PartRatingTeleportDelay, rating); + } + + private void OnUpgradeExamine(EntityUid uid, CargoTelepadComponent component, UpgradeExamineEvent args) + { + args.AddPercentageUpgrade("cargo-telepad-delay-upgrade", component.Delay / component.BaseDelay); + } + + private void OnShutdown(Entity ent, ref ComponentShutdown args) + { + if (ent.Comp.CurrentOrders.Count == 0) + return; + + if (_station.GetStations().Count == 0) + return; + + if (_station.GetOwningStation(ent) is not { } station) + { + station = _random.Pick(_station.GetStations().Where(HasComp).ToList()); + } + + if (!TryComp(station, out var db) || + !TryComp(station, out var data)) + return; + + foreach (var order in ent.Comp.CurrentOrders) + { + TryFulfillOrder((station, data), order, db); + } + } + private void SetEnabled(EntityUid uid, CargoTelepadComponent component, ApcPowerReceiverComponent? receiver = null, TransformComponent? xform = null) { diff --git a/Content.Server/Cargo/Systems/CargoSystem.cs b/Content.Server/Cargo/Systems/CargoSystem.cs index 3d6fc96472..e593299321 100644 --- a/Content.Server/Cargo/Systems/CargoSystem.cs +++ b/Content.Server/Cargo/Systems/CargoSystem.cs @@ -8,6 +8,7 @@ using Content.Server.Station.Systems; using Content.Shared.Access.Systems; using Content.Shared.Administration.Logs; +using Content.Server.Radio.EntitySystems; using Content.Shared.Cargo; using Content.Shared.Cargo.Components; using Content.Shared.Containers.ItemSlots; @@ -44,6 +45,7 @@ public sealed partial class CargoSystem : SharedCargoSystem [Dependency] private readonly StationSystem _station = default!; [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; [Dependency] private readonly MetaDataSystem _metaSystem = default!; + [Dependency] private readonly RadioSystem _radio = default!; [Dependency] private readonly IConfigurationManager _cfgManager = default!; [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IComponentFactory _factory = default!; diff --git a/Content.Server/Cargo/Systems/LogisticStatsSystem.cs b/Content.Server/Cargo/Systems/LogisticStatsSystem.cs new file mode 100644 index 0000000000..6abf4eb5a4 --- /dev/null +++ b/Content.Server/Cargo/Systems/LogisticStatsSystem.cs @@ -0,0 +1,63 @@ +using Content.Shared.Cargo; +using Content.Server.Cargo.Components; +using JetBrains.Annotations; + +namespace Content.Server.Cargo.Systems; + +public sealed partial class LogisticStatsSystem : SharedCargoSystem +{ + [PublicAPI] + public void AddOpenedMailEarnings(EntityUid uid, StationLogisticStatsComponent component, int earnedMoney) + { + component.Metrics = component.Metrics with + { + Earnings = component.Metrics.Earnings + earnedMoney, + OpenedCount = component.Metrics.OpenedCount + 1 + }; + UpdateLogisticsStats(uid); + } + + [PublicAPI] + public void AddExpiredMailLosses(EntityUid uid, StationLogisticStatsComponent component, int lostMoney) + { + component.Metrics = component.Metrics with + { + ExpiredLosses = component.Metrics.ExpiredLosses + lostMoney, + ExpiredCount = component.Metrics.ExpiredCount + 1 + }; + UpdateLogisticsStats(uid); + } + + [PublicAPI] + public void AddDamagedMailLosses(EntityUid uid, StationLogisticStatsComponent component, int lostMoney) + { + component.Metrics = component.Metrics with + { + DamagedLosses = component.Metrics.DamagedLosses + lostMoney, + DamagedCount = component.Metrics.DamagedCount + 1 + }; + UpdateLogisticsStats(uid); + } + + [PublicAPI] + public void AddTamperedMailLosses(EntityUid uid, StationLogisticStatsComponent component, int lostMoney) + { + component.Metrics = component.Metrics with + { + TamperedLosses = component.Metrics.TamperedLosses + lostMoney, + TamperedCount = component.Metrics.TamperedCount + 1 + }; + UpdateLogisticsStats(uid); + } + + private void UpdateLogisticsStats(EntityUid uid) => RaiseLocalEvent(new LogisticStatsUpdatedEvent(uid)); +} + +public sealed class LogisticStatsUpdatedEvent : EntityEventArgs +{ + public EntityUid Station; + public LogisticStatsUpdatedEvent(EntityUid station) + { + Station = station; + } +} diff --git a/Content.Server/Cargo/Systems/PricingSystem.cs b/Content.Server/Cargo/Systems/PricingSystem.cs index 9e1970d63c..f878eeee75 100644 --- a/Content.Server/Cargo/Systems/PricingSystem.cs +++ b/Content.Server/Cargo/Systems/PricingSystem.cs @@ -199,7 +199,7 @@ public double GetEstimatedPrice(EntityPrototype prototype) /// This fires off an event to calculate the price. /// Calculating the price of an entity that somehow contains itself will likely hang. /// - public double GetPrice(EntityUid uid) + public double GetPrice(EntityUid uid, bool includeContents = true) { var ev = new PriceCalculationEvent(); RaiseLocalEvent(uid, ref ev); @@ -222,7 +222,7 @@ public double GetPrice(EntityUid uid) price += GetStaticPrice(uid); } - if (TryComp(uid, out var containers)) + if (includeContents && TryComp(uid, out var containers)) { foreach (var container in containers.Containers.Values) { diff --git a/Content.Server/Carrying/CarryingSystem.cs b/Content.Server/Carrying/CarryingSystem.cs index 13338ea2b7..857c3861a7 100644 --- a/Content.Server/Carrying/CarryingSystem.cs +++ b/Content.Server/Carrying/CarryingSystem.cs @@ -146,7 +146,7 @@ private void OnThrow(EntityUid uid, CarryingComponent component, ref BeforeThrow private void OnParentChanged(EntityUid uid, CarryingComponent component, ref EntParentChangedMessage args) { var xform = Transform(uid); - if (xform.MapID != args.OldMapId || xform.ParentUid == xform.GridUid) + if (xform.MapUid != args.OldMapId || xform.ParentUid == xform.GridUid) return; DropCarried(uid, component.Carried); @@ -183,7 +183,10 @@ private void OnMoveInput(EntityUid uid, BeingCarriedComponent component, ref Mov // Check if the victim is in any way incapacitated, and if not make an escape attempt. // Escape time scales with the inverse of a mass contest. Being lighter makes escape harder. if (_actionBlockerSystem.CanInteract(uid, component.Carrier)) - _escapeInventorySystem.AttemptEscape(uid, component.Carrier, escape, _contests.MassContest(uid, component.Carrier, false, 2f)); + { + var disadvantage = _contests.MassContest(component.Carrier, uid, false, 2f); + _escapeInventorySystem.AttemptEscape(uid, component.Carrier, escape, disadvantage); + } } private void OnMoveAttempt(EntityUid uid, BeingCarriedComponent component, UpdateCanMoveEvent args) diff --git a/Content.Server/CartridgeLoader/CartridgeLoaderSystem.cs b/Content.Server/CartridgeLoader/CartridgeLoaderSystem.cs index 4a76aef911..7896a7822e 100644 --- a/Content.Server/CartridgeLoader/CartridgeLoaderSystem.cs +++ b/Content.Server/CartridgeLoader/CartridgeLoaderSystem.cs @@ -103,12 +103,12 @@ public void UpdateUiState(EntityUid loaderUid, ICommonSession? session, Cartridg if (!Resolve(loaderUid, ref loader)) return; - if (!_userInterfaceSystem.TryGetUi(loaderUid, loader.UiKey, out var ui)) + if (!_userInterfaceSystem.HasUi(loaderUid, loader.UiKey)) return; var programs = GetAvailablePrograms(loaderUid, loader); var state = new CartridgeLoaderUiState(programs, GetNetEntity(loader.ActiveProgram)); - _userInterfaceSystem.SetUiState(ui, state, session); + _userInterfaceSystem.SetUiState(loaderUid, loader.UiKey, state); } /// @@ -127,8 +127,8 @@ public void UpdateCartridgeUiState(EntityUid loaderUid, BoundUserInterfaceState if (!Resolve(loaderUid, ref loader)) return; - if (_userInterfaceSystem.TryGetUi(loaderUid, loader.UiKey, out var ui)) - _userInterfaceSystem.SetUiState(ui, state, session); + if (_userInterfaceSystem.HasUi(loaderUid, loader.UiKey)) + _userInterfaceSystem.SetUiState(loaderUid, loader.UiKey, state); } /// diff --git a/Content.Server/CartridgeLoader/Cartridges/MailMetricsCartridgeComponent.cs b/Content.Server/CartridgeLoader/Cartridges/MailMetricsCartridgeComponent.cs new file mode 100644 index 0000000000..380a4d90c0 --- /dev/null +++ b/Content.Server/CartridgeLoader/Cartridges/MailMetricsCartridgeComponent.cs @@ -0,0 +1,11 @@ +namespace Content.Server.CartridgeLoader.Cartridges; + +[RegisterComponent, Access(typeof(MailMetricsCartridgeSystem))] +public sealed partial class MailMetricsCartridgeComponent : Component +{ + /// + /// Station entity keeping track of logistics stats + /// + [DataField] + public EntityUid? Station; +} diff --git a/Content.Server/CartridgeLoader/Cartridges/MailMetricsCartridgeSystem.cs b/Content.Server/CartridgeLoader/Cartridges/MailMetricsCartridgeSystem.cs new file mode 100644 index 0000000000..00b6d0a16e --- /dev/null +++ b/Content.Server/CartridgeLoader/Cartridges/MailMetricsCartridgeSystem.cs @@ -0,0 +1,82 @@ +using Content.Server.Cargo.Components; +using Content.Server.Cargo.Systems; +using Content.Server.Mail.Components; +using Content.Server.Station.Systems; +using Content.Shared.CartridgeLoader; +using Content.Shared.CartridgeLoader.Cartridges; + +namespace Content.Server.CartridgeLoader.Cartridges; + +public sealed class MailMetricsCartridgeSystem : EntitySystem +{ + [Dependency] private readonly CartridgeLoaderSystem _cartridgeLoader = default!; + [Dependency] private readonly StationSystem _station = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnUiReady); + SubscribeLocalEvent(OnLogisticsStatsUpdated); + SubscribeLocalEvent(OnMapInit); + } + + private void OnUiReady(Entity ent, ref CartridgeUiReadyEvent args) + { + UpdateUI(ent, args.Loader); + } + + private void OnLogisticsStatsUpdated(LogisticStatsUpdatedEvent args) + { + UpdateAllCartridges(args.Station); + } + + private void OnMapInit(EntityUid uid, MailComponent mail, MapInitEvent args) + { + if (_station.GetOwningStation(uid) is { } station) + UpdateAllCartridges(station); + } + + private void UpdateAllCartridges(EntityUid station) + { + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var comp, out var cartridge)) + { + if (cartridge.LoaderUid is not { } loader || comp.Station != station) + continue; + UpdateUI((uid, comp), loader); + } + } + + private void UpdateUI(Entity ent, EntityUid loader) + { + if (_station.GetOwningStation(loader) is { } station) + ent.Comp.Station = station; + + if (!TryComp(ent.Comp.Station, out var logiStats)) + return; + + // Get station's logistic stats + var unopenedMailCount = GetUnopenedMailCount(ent.Comp.Station); + + // Send logistic stats to cartridge client + var state = new MailMetricUiState(logiStats.Metrics, unopenedMailCount); + _cartridgeLoader.UpdateCartridgeUiState(loader, state); + } + + + private int GetUnopenedMailCount(EntityUid? station) + { + var unopenedMail = 0; + + var query = EntityQueryEnumerator(); + + while (query.MoveNext(out var uid, out var comp)) + { + if (comp.IsLocked && _station.GetOwningStation(uid) == station) + unopenedMail++; + } + + return unopenedMail; + } +} diff --git a/Content.Server/Chapel/SacrificialAltarSystem.cs b/Content.Server/Chapel/SacrificialAltarSystem.cs new file mode 100644 index 0000000000..e7144e1185 --- /dev/null +++ b/Content.Server/Chapel/SacrificialAltarSystem.cs @@ -0,0 +1,129 @@ +using Content.Server.Bible.Components; +using Content.Shared.Abilities.Psionics; +using Content.Shared.Administration.Logs; +using Content.Shared.Body.Components; +using Content.Shared.Body.Systems; +using Content.Shared.Database; +using Content.Shared.Chapel; +using Content.Shared.DoAfter; +using Content.Shared.Humanoid; +using Content.Shared.Mind; +using Content.Shared.Popups; +using Content.Shared.Psionics.Glimmer; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; + +namespace Content.Server.Chapel; + +public sealed class SacrificialAltarSystem : SharedSacrificialAltarSystem +{ + [Dependency] private readonly GlimmerSystem _glimmer = default!; + [Dependency] private readonly IPrototypeManager _proto = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedBodySystem _body = default!; + [Dependency] private readonly SharedMindSystem _mind = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnDoAfter); + } + + private void OnDoAfter(Entity ent, ref SacrificeDoAfterEvent args) + { + ent.Comp.SacrificeStream = _audio.Stop(ent.Comp.SacrificeStream); + ent.Comp.DoAfter = null; + + if (args.Cancelled || args.Handled || args.Args.Target is not { } target + || !TryComp(target, out var psionic) + || !_mind.TryGetMind(target, out var _, out var _)) + return; + + _adminLogger.Add(LogType.Action, LogImpact.Extreme, $"{ToPrettyString(args.Args.User):player} sacrificed {ToPrettyString(target):target} on {ToPrettyString(ent):altar}"); + + // lower glimmer by a random amount + _glimmer.Glimmer -= (int) (ent.Comp.GlimmerReduction * psionic.CurrentAmplification); + + if (ent.Comp.RewardPool is not null && _random.Prob(ent.Comp.BaseItemChance * psionic.CurrentDampening)) + { + var proto = _proto.Index(_random.Pick(ent.Comp.RewardPool)); + Spawn(proto.ToString(), Transform(ent).Coordinates); + } + // TODO GOLEMS: create a soul crystal and transfer mind into it + + // finally gib the targets old body + if (TryComp(target, out var body)) + _body.GibBody(target, gibOrgans: false, body, launchGibs: true); + else + QueueDel(target); + } + + protected override void AttemptSacrifice(Entity ent, EntityUid user, EntityUid target) + { + if (ent.Comp.DoAfter != null) + return; + + // can't sacrifice yourself + if (user == target) + { + _popup.PopupEntity(Loc.GetString("altar-failure-reason-self"), ent, user, PopupType.SmallCaution); + return; + } + + // you need to be psionic OR bible user + if (!HasComp(user) && !HasComp(user)) + { + _popup.PopupEntity(Loc.GetString("altar-failure-reason-user"), ent, user, PopupType.SmallCaution); + return; + } + + // and no golems or familiars or whatever should be sacrificing + if (!HasComp(user)) + { + _popup.PopupEntity(Loc.GetString("altar-failure-reason-user-humanoid"), ent, user, PopupType.SmallCaution); + return; + } + + // prevent psichecking SSD people... + // notably there is no check in OnDoAfter so you can't alt f4 to survive being sacrificed + if (!HasComp(target) || _mind.GetMind(target) == null) + { + _popup.PopupEntity(Loc.GetString("altar-failure-reason-target-catatonic", ("target", target)), ent, user, PopupType.SmallCaution); + return; + } + + // TODO: there should be a penalty to the user for psichecking like this + if (!HasComp(target)) + { + _popup.PopupEntity(Loc.GetString("altar-failure-reason-target", ("target", target)), ent, user, PopupType.SmallCaution); + return; + } + + if (!HasComp(target)) + { + _popup.PopupEntity(Loc.GetString("altar-failure-reason-target-humanoid", ("target", target)), ent, user, PopupType.SmallCaution); + return; + } + + _popup.PopupEntity(Loc.GetString("altar-sacrifice-popup", ("user", user), ("target", target)), ent, PopupType.LargeCaution); + + ent.Comp.SacrificeStream = _audio.PlayPvs(ent.Comp.SacrificeSound, ent)?.Entity; + + var ev = new SacrificeDoAfterEvent(); + var args = new DoAfterArgs(EntityManager, user, ent.Comp.SacrificeTime, ev, target: target, eventTarget: ent) + { + BreakOnDamage = true, + BreakOnUserMove = true, + BreakOnTargetMove = true, + BreakOnWeightlessMove = true, + NeedHand = true + }; + DoAfter.TryStartDoAfter(args, out ent.Comp.DoAfter); + } +} diff --git a/Content.Server/Chat/Commands/AdminChatCommand.cs b/Content.Server/Chat/Commands/AdminChatCommand.cs index 979051e9d3..1a7ae050b6 100644 --- a/Content.Server/Chat/Commands/AdminChatCommand.cs +++ b/Content.Server/Chat/Commands/AdminChatCommand.cs @@ -5,7 +5,7 @@ namespace Content.Server.Chat.Commands { - [AdminCommand(AdminFlags.Admin)] + [AdminCommand(AdminFlags.Adminchat)] internal sealed class AdminChatCommand : IConsoleCommand { public string Command => "asay"; diff --git a/Content.Server/Chat/EmpathyChatSystem.cs b/Content.Server/Chat/EmpathyChatSystem.cs new file mode 100644 index 0000000000..b46bbdc34e --- /dev/null +++ b/Content.Server/Chat/EmpathyChatSystem.cs @@ -0,0 +1,83 @@ +using System.Linq; +using Robust.Shared.Utility; +using Content.Server.Chat.Managers; +using Content.Server.Language; +using Content.Server.Chat.Systems; +using Content.Server.Administration.Managers; +using Robust.Shared.Network; +using Robust.Shared.Player; +using Content.Shared.Chat; +using Content.Shared.Language; +using Robust.Shared.Prototypes; +using Content.Shared.Language.Components; + +namespace Content.Server.Chat; + +public sealed partial class EmpathyChatSystem : EntitySystem +{ + [Dependency] private readonly IChatManager _chatManager = default!; + [Dependency] private readonly LanguageSystem _language = default!; + [Dependency] private readonly IAdminManager _adminManager = default!; + [Dependency] private readonly IPrototypeManager _prototype = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnSpeak); + } + + private void OnSpeak(EntityUid uid, LanguageSpeakerComponent component, EntitySpokeEvent args) + { + if (args.Source != uid + || !args.Language.SpeechOverride.EmpathySpeech + || args.IsWhisper) + return; + + SendEmpathyChat(args.Source, args.Message, false); + } + + /// + /// Send a Message in the Shadowkin Empathy Chat. + /// + /// The entity making the message + /// The contents of the message + /// Set the ChatTransmitRange + public void SendEmpathyChat(EntityUid source, string message, bool hideChat) + { + var clients = GetEmpathChatClients(); + string wrappedMessage; + + wrappedMessage = Loc.GetString("chat-manager-send-empathy-chat-wrap-message", + ("source", source), + ("message", FormattedMessage.EscapeText(message))); + + _chatManager.ChatMessageToMany(ChatChannel.Telepathic, message, wrappedMessage, source, hideChat, true, clients.ToList(), Color.FromHex("#be3cc5")); + } + + private IEnumerable GetEmpathChatClients() + { + return Filter.Empty() + .AddWhereAttachedEntity(entity => + CanHearEmpathy(entity)) + .Recipients + .Union(_adminManager.ActiveAdmins) + .Select(p => p.Channel); + } + + /// + /// Check if an entity can hear Empathy. + /// (Admins will always be able to hear Empathy) + /// + /// The entity to check + public bool CanHearEmpathy(EntityUid entity) + { + var understood = _language.GetUnderstoodLanguages(entity); + for (int i = 0; i < understood.Count; i++) + { + var language = _prototype.Index(understood[i]); + if (language.SpeechOverride.EmpathySpeech) + return true; + } + return false; + } +} \ No newline at end of file diff --git a/Content.Server/Chat/Systems/ChatSystem.Emote.cs b/Content.Server/Chat/Systems/ChatSystem.Emote.cs index c18b945ec6..1aeed69e03 100644 --- a/Content.Server/Chat/Systems/ChatSystem.Emote.cs +++ b/Content.Server/Chat/Systems/ChatSystem.Emote.cs @@ -2,6 +2,7 @@ using System.Linq; using Content.Shared.Chat; using Content.Shared.Chat.Prototypes; +using Content.Shared.Speech; using Robust.Shared.Prototypes; using Robust.Shared.Random; @@ -50,7 +51,7 @@ private void CacheEmotes() /// The id of emote prototype. Should has valid /// Whether or not this message should appear in the adminlog window /// Conceptual range of transmission, if it shows in the chat window, if it shows to far-away ghosts or ghosts at all... - /// The name to use for the speaking entity. Usually this should just be modified via . If this is set, the event will not get raised. + /// The name to use for the speaking entity. Usually this should just be modified via . If this is set, the event will not get raised. public void TryEmoteWithChat( EntityUid source, string emoteId, @@ -73,7 +74,7 @@ public void TryEmoteWithChat( /// Whether or not this message should appear in the adminlog window /// Whether or not this message should appear in the chat window /// Conceptual range of transmission, if it shows in the chat window, if it shows to far-away ghosts or ghosts at all... - /// The name to use for the speaking entity. Usually this should just be modified via . If this is set, the event will not get raised. + /// The name to use for the speaking entity. Usually this should just be modified via . If this is set, the event will not get raised. public void TryEmoteWithChat( EntityUid source, EmotePrototype emote, @@ -83,6 +84,16 @@ public void TryEmoteWithChat( bool ignoreActionBlocker = false ) { + if (!(emote.Whitelist?.IsValid(source, EntityManager) ?? true)) + return; + if (emote.Blacklist?.IsValid(source, EntityManager) ?? false) + return; + + if (!emote.Available && + TryComp(source, out var speech) && + !speech.AllowedEmotes.Contains(emote.ID)) + return; + // check if proto has valid message for chat if (emote.ChatMessages.Count != 0) { diff --git a/Content.Server/Chat/Systems/ChatSystem.cs b/Content.Server/Chat/Systems/ChatSystem.cs index b4641928e4..bbca6f4d15 100644 --- a/Content.Server/Chat/Systems/ChatSystem.cs +++ b/Content.Server/Chat/Systems/ChatSystem.cs @@ -8,7 +8,6 @@ using Content.Server.Language; using Content.Server.Speech.Components; using Content.Server.Speech.EntitySystems; -using Content.Server.Chat; using Content.Server.Station.Components; using Content.Server.Station.Systems; using Content.Shared.ActionBlocker; @@ -44,7 +43,7 @@ namespace Content.Server.Chat.Systems; // Dear contributor. When I was introducing changes to this system only god and I knew what I was doing. // Now only god knows. Please don't touch this code ever again. If you do have to, increment this counter as a warning for others: -// TOTAL_HOURS_WASTED_HERE_EE = 17 +// TOTAL_HOURS_WASTED_HERE_EE = 18 // TODO refactor whatever active warzone this class and chatmanager have become /// @@ -147,7 +146,7 @@ private void OnGameChange(GameRunLevelChangedEvent ev) /// Whether or not this message should appear in the adminlog window /// /// The player doing the speaking - /// The name to use for the speaking entity. Usually this should just be modified via . If this is set, the event will not get raised. + /// The name to use for the speaking entity. Usually this should just be modified via . If this is set, the event will not get raised. public void TrySendInGameICMessage( EntityUid source, string message, @@ -170,7 +169,7 @@ public void TrySendInGameICMessage( /// Conceptual range of transmission, if it shows in the chat window, if it shows to far-away ghosts or ghosts at all... /// /// The player doing the speaking - /// The name to use for the speaking entity. Usually this should just be modified via . If this is set, the event will not get raised. + /// The name to use for the speaking entity. Usually this should just be modified via . If this is set, the event will not get raised. /// If set to true, action blocker will not be considered for whether an entity can send this message. public void TrySendInGameICMessage( EntityUid source, @@ -421,11 +420,11 @@ private void SendEntitySpeak( } else { - var nameEv = new TransformSpeakerNameEvent(source, Name(source)); + var nameEv = new TransformSpeakerSpeechEvent(source, Name(source)); RaiseLocalEvent(source, nameEv); - name = nameEv.Name; + name = nameEv.VoiceName ?? Name(source); // Check for a speech verb override - if (nameEv.SpeechVerb != null && _prototypeManager.TryIndex(nameEv.SpeechVerb, out var proto)) + if (nameEv.SpeechVerb != null && _prototypeManager.TryIndex(nameEv.SpeechVerb, out var proto)) speech = proto; } @@ -493,9 +492,9 @@ private void SendEntityWhisper( } else { - var nameEv = new TransformSpeakerNameEvent(source, Name(source)); + var nameEv = new TransformSpeakerSpeechEvent(source, Name(source)); RaiseLocalEvent(source, nameEv); - name = nameEv.Name; + name = nameEv.VoiceName ?? Name(source); } name = FormattedMessage.EscapeText(name); @@ -880,6 +879,9 @@ public string WrapMessage(LocId wrapId, InGameICChatType chatType, EntityUid sou var color = DefaultSpeakColor; if (language.SpeechOverride.Color is { } colorOverride) color = Color.InterpolateBetween(color, colorOverride, colorOverride.A); + var languageDisplay = language.IsVisibleLanguage + ? $"{language.ChatName} | " + : ""; return Loc.GetString(wrapId, ("color", color), @@ -887,7 +889,8 @@ public string WrapMessage(LocId wrapId, InGameICChatType chatType, EntityUid sou ("verb", Loc.GetString(verbId)), ("fontType", language.SpeechOverride.FontId ?? speech.FontId), ("fontSize", language.SpeechOverride.FontSize ?? speech.FontSize), - ("message", message)); + ("message", message), + ("language", languageDisplay)); } /// @@ -990,20 +993,6 @@ public record ExpandICChatRecipientstEvent(EntityUid Source, float VoiceRange, D { } -public sealed class TransformSpeakerNameEvent : EntityEventArgs -{ - public EntityUid Sender; - public string Name; - public string? SpeechVerb; - - public TransformSpeakerNameEvent(EntityUid sender, string name, string? speechVerb = null) - { - Sender = sender; - Name = name; - SpeechVerb = speechVerb; - } -} - /// /// Raised broadcast in order to transform speech.transmit /// diff --git a/Content.Server/Chat/TelepathicChatSystem.Psychognomy.cs b/Content.Server/Chat/TelepathicChatSystem.Psychognomy.cs new file mode 100644 index 0000000000..4ba990466c --- /dev/null +++ b/Content.Server/Chat/TelepathicChatSystem.Psychognomy.cs @@ -0,0 +1,171 @@ +using Content.Shared.Humanoid; +using Content.Shared.Damage; +using Content.Shared.Mobs.Components; +using Content.Shared.Physics; +using Content.Shared.Speech; +using Content.Shared.Speech.Muting; +using Content.Shared.CombatMode.Pacification; +using Content.Shared.CombatMode; +using Content.Shared.Nutrition.Components; +using Content.Server.Vampiric; +using Content.Shared.Abilities.Psionics; +using Content.Server.Abilities.Psionics; +using Content.Server.Cloning.Components; +using Content.Server.Psionics.Glimmer; +using Robust.Shared.GameObjects.Components.Localization; +using Robust.Shared.Enums; +using Robust.Shared.Physics; +using Robust.Shared.Physics.Components; +using Robust.Shared.Random; + +namespace Content.Server.Chat; +public sealed partial class TelepathicChatSystem +{ + public string SourceToDescriptor(EntityUid source) + { + var ev = new GetPsychognomicDescriptorEvent(); + RaiseLocalEvent(source, ev); + + ev.Descriptors.Add(Loc.GetString("p-descriptor-ignorant")); + + return _random.Pick(ev.Descriptors); + } + private void InitializePsychognomy() + { + SubscribeLocalEvent(DescribeHumanoid); + SubscribeLocalEvent(DescribeGrammar); + SubscribeLocalEvent(DescribeDamage); + SubscribeLocalEvent(DescribeMobState); + SubscribeLocalEvent(DescribeHunger); + SubscribeLocalEvent(DescribePhysics); + SubscribeLocalEvent(DescribeFixtures); + SubscribeLocalEvent(DescribeKarma); + SubscribeLocalEvent(DescribeGlimmerSource); + SubscribeLocalEvent(DescribePsion); + SubscribeLocalEvent(DescribeInnatePsionics); + SubscribeLocalEvent(DescribeBloodsucker); + } + + private void DescribeHumanoid(EntityUid uid, HumanoidAppearanceComponent component, GetPsychognomicDescriptorEvent ev) + { + if (component.Sex != Sex.Unsexed) + ev.Descriptors.Add(component.Sex == Sex.Male ? Loc.GetString("p-descriptor-male") : Loc.GetString("p-descriptor-female")); + + // Deliberately obfuscating a bit; psionic signatures use different standards + ev.Descriptors.Add(component.Age >= 100 ? Loc.GetString("p-descriptor-old") : Loc.GetString("p-descriptor-young")); + } + + private void DescribeGrammar(EntityUid uid, GrammarComponent component, GetPsychognomicDescriptorEvent ev) + { + if (component.Gender == Gender.Male || component.Gender == Gender.Female) + ev.Descriptors.Add(component.Gender == Gender.Male ? Loc.GetString("p-descriptor-masculine") : Loc.GetString("p-descriptor-feminine")); + } + + private void DescribeDamage(EntityUid uid, DamageableComponent component, GetPsychognomicDescriptorEvent ev) + { + if (component.DamageContainerID == "CorporealSpirit") + { + ev.Descriptors.Add(Loc.GetString("p-descriptor-liminal")); + if (!HasComp(uid)) + ev.Descriptors.Add(Loc.GetString("p-descriptor-old")); + return; + } + + ev.Descriptors.Add(Loc.GetString("p-descriptor-hylic")); + } + + private void DescribeMobState(EntityUid uid, MobStateComponent component, GetPsychognomicDescriptorEvent ev) + { + if (component.CurrentState != Shared.Mobs.MobState.Critical) + return; + + ev.Descriptors.Add(Loc.GetString("p-descriptor-liminal")); + } + + private void DescribeHunger(EntityUid uid, HungerComponent component, GetPsychognomicDescriptorEvent ev) + { + if (component.CurrentThreshold > HungerThreshold.Peckish) + return; + + ev.Descriptors.Add(Loc.GetString("p-descriptor-hungry")); + } + + private void DescribeFixtures(EntityUid uid, FixturesComponent component, GetPsychognomicDescriptorEvent ev) + { + foreach (var fixture in component.Fixtures.Values) + if (fixture.CollisionMask == (int) CollisionGroup.GhostImpassable) + { + ev.Descriptors.Add(Loc.GetString("p-descriptor-pneumatic")); + return; + } + } + + private void DescribePhysics(EntityUid uid, PhysicsComponent component, GetPsychognomicDescriptorEvent ev) + { + if (component.FixturesMass < 45) + ev.Descriptors.Add(Loc.GetString("p-descriptor-light")); + else if (component.FixturesMass > 70) + ev.Descriptors.Add(Loc.GetString("p-descriptor-heavy")); + } + + private void DescribeKarma(EntityUid uid, MetempsychosisKarmaComponent component, GetPsychognomicDescriptorEvent ev) + { + if (component.Score == 0) + return; + + ev.Descriptors.Add(Loc.GetString("p-descriptor-cyclic")); + } + + private void DescribeGlimmerSource(EntityUid uid, GlimmerSourceComponent component, GetPsychognomicDescriptorEvent ev) + { + if (component.AddToGlimmer) + ev.Descriptors.Add(Loc.GetString("p-descriptor-emanative")); + else + { + ev.Descriptors.Add(Loc.GetString("p-descriptor-vampiric")); + ev.Descriptors.Add(Loc.GetString("p-descriptor-hungry")); + } + } + + // This one's also a bit of a catch-all for "lacks component" + private void DescribePsion(EntityUid uid, PsionicComponent component, GetPsychognomicDescriptorEvent ev) + { + if (component.PsychognomicDescriptors != null) + { + foreach (var descriptor in component.PsychognomicDescriptors) + { + ev.Descriptors.Add(Loc.GetString(descriptor)); + } + } + + if (!HasComp(uid) || HasComp(uid)) + ev.Descriptors.Add(Loc.GetString("p-descriptor-dumb")); + + if (!HasComp(uid) || HasComp(uid)) + ev.Descriptors.Add(Loc.GetString("p-descriptor-passive")); + + foreach (var power in component.ActivePowers) + { + // TODO: Mime counts too and isn't added back to psions yet + if (power.ID != "PyrokinesisPower" && power.ID != "NoosphericZapPower") + continue; + + ev.Descriptors.Add(Loc.GetString("p-descriptor-kinetic")); + return; + } + } + + private void DescribeInnatePsionics(EntityUid uid, InnatePsionicPowersComponent component, GetPsychognomicDescriptorEvent ev) + { + ev.Descriptors.Add(Loc.GetString("p-descriptor-gnostic")); + } + + private void DescribeBloodsucker(EntityUid uid, BloodSuckerComponent component, GetPsychognomicDescriptorEvent ev) + { + ev.Descriptors.Add(Loc.GetString("p-descriptor-vampiric")); + } +} +public sealed class GetPsychognomicDescriptorEvent : EntityEventArgs +{ + public List Descriptors = new List(); +} diff --git a/Content.Server/Chat/TelepathicChatSystem.cs b/Content.Server/Chat/TelepathicChatSystem.cs index 8d44ead9d0..b1338035ad 100644 --- a/Content.Server/Chat/TelepathicChatSystem.cs +++ b/Content.Server/Chat/TelepathicChatSystem.cs @@ -3,6 +3,7 @@ using Content.Server.Chat.Managers; using Content.Server.Chat.Systems; using Content.Shared.Abilities.Psionics; +using Content.Shared.Psionics.Passives; using Content.Shared.Bed.Sleep; using Content.Shared.Chat; using Content.Shared.Database; @@ -16,113 +17,134 @@ using System.Linq; using System.Text; -namespace Content.Server.Chat +namespace Content.Server.Chat; + +/// +/// Extensions for Telepathic chat stuff +/// +public sealed partial class TelepathicChatSystem : EntitySystem { - /// - /// Extensions for Telepathic chat stuff - /// + [Dependency] private readonly IAdminManager _adminManager = default!; + [Dependency] private readonly IChatManager _chatManager = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly IAdminLogManager _adminLogger = default!; + [Dependency] private readonly GlimmerSystem _glimmerSystem = default!; + [Dependency] private readonly ChatSystem _chatSystem = default!; + + public override void Initialize() + { + base.Initialize(); + InitializePsychognomy(); + } - public sealed class TelepathicChatSystem : EntitySystem + private (IEnumerable normal, IEnumerable psychog) GetPsionicChatClients() { - [Dependency] private readonly IAdminManager _adminManager = default!; - [Dependency] private readonly IChatManager _chatManager = default!; - [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly IAdminLogManager _adminLogger = default!; - [Dependency] private readonly GlimmerSystem _glimmerSystem = default!; - [Dependency] private readonly ChatSystem _chatSystem = default!; - private IEnumerable GetPsionicChatClients() - { - return Filter.Empty() - .AddWhereAttachedEntity(IsEligibleForTelepathy) - .Recipients - .Select(p => p.ConnectedClient); - } + var psions = Filter.Empty() + .AddWhereAttachedEntity(IsEligibleForTelepathy) + .Recipients; - private IEnumerable GetAdminClients() - { - return _adminManager.ActiveAdmins - .Select(p => p.ConnectedClient); - } + var normalSessions = psions.Where(p => !HasComp(p.AttachedEntity)).Select(p => p.Channel); + var psychogSessions = psions.Where(p => HasComp(p.AttachedEntity)).Select(p => p.Channel); - private List GetDreamers(IEnumerable removeList) - { - var filtered = Filter.Empty() - .AddWhereAttachedEntity(entity => HasComp(entity) || HasComp(entity) && !HasComp(entity) && !HasComp(entity)) - .Recipients - .Select(p => p.ConnectedClient); + return (normalSessions, psychogSessions); + } - var filteredList = filtered.ToList(); + private IEnumerable GetAdminClients() + { + return _adminManager.ActiveAdmins + .Select(p => p.ConnectedClient); + } - foreach (var entity in removeList) - filteredList.Remove(entity); + private List GetDreamers(IEnumerable removeList) + { + var filtered = Filter.Empty() + .AddWhereAttachedEntity(entity => + HasComp(entity) && !HasComp(entity) + || HasComp(entity) + || HasComp(entity) && !HasComp(entity) && !HasComp(entity)) + .Recipients + .Select(p => p.ConnectedClient); - return filteredList; - } + var filteredList = filtered.ToList(); - private bool IsEligibleForTelepathy(EntityUid entity) - { - return HasComp(entity) - && !HasComp(entity) - && !HasComp(entity) - && (!TryComp(entity, out var mobstate) || mobstate.CurrentState == MobState.Alive); - } + foreach (var entity in removeList) + filteredList.Remove(entity); - public void SendTelepathicChat(EntityUid source, string message, bool hideChat) - { - if (!IsEligibleForTelepathy(source)) - return; + return filteredList; + } - var clients = GetPsionicChatClients(); - var admins = GetAdminClients(); - string messageWrap; - string adminMessageWrap; + private bool IsEligibleForTelepathy(EntityUid entity) + { + return HasComp(entity) + && !HasComp(entity) + && !HasComp(entity) + && (!TryComp(entity, out var mobstate) || mobstate.CurrentState == MobState.Alive); + } - messageWrap = Loc.GetString("chat-manager-send-telepathic-chat-wrap-message", - ("telepathicChannelName", Loc.GetString("chat-manager-telepathic-channel-name")), ("message", message)); + public void SendTelepathicChat(EntityUid source, string message, bool hideChat) + { + if (!IsEligibleForTelepathy(source)) + return; - adminMessageWrap = Loc.GetString("chat-manager-send-telepathic-chat-wrap-message-admin", - ("source", source), ("message", message)); + var clients = GetPsionicChatClients(); + var admins = GetAdminClients(); + string messageWrap; + string adminMessageWrap; - _adminLogger.Add(LogType.Chat, LogImpact.Low, $"Telepathic chat from {ToPrettyString(source):Player}: {message}"); + messageWrap = Loc.GetString("chat-manager-send-telepathic-chat-wrap-message", + ("telepathicChannelName", Loc.GetString("chat-manager-telepathic-channel-name")), ("message", message)); - _chatManager.ChatMessageToMany(ChatChannel.Telepathic, message, messageWrap, source, hideChat, true, clients.ToList(), Color.PaleVioletRed); + adminMessageWrap = Loc.GetString("chat-manager-send-telepathic-chat-wrap-message-admin", + ("source", source), ("message", message)); - _chatManager.ChatMessageToMany(ChatChannel.Telepathic, message, adminMessageWrap, source, hideChat, true, admins, Color.PaleVioletRed); + _adminLogger.Add(LogType.Chat, LogImpact.Low, $"Telepathic chat from {ToPrettyString(source):Player}: {message}"); - if (_random.Prob(0.1f)) - _glimmerSystem.Glimmer++; + _chatManager.ChatMessageToMany(ChatChannel.Telepathic, message, messageWrap, source, hideChat, true, clients.normal.ToList(), Color.PaleVioletRed); - if (_random.Prob(Math.Min(0.33f + ((float) _glimmerSystem.Glimmer / 1500), 1))) - { - float obfuscation = (0.25f + (float) _glimmerSystem.Glimmer / 2000); - var obfuscated = ObfuscateMessageReadability(message, obfuscation); - _chatManager.ChatMessageToMany(ChatChannel.Telepathic, obfuscated, messageWrap, source, hideChat, false, GetDreamers(clients), Color.PaleVioletRed); - } + _chatManager.ChatMessageToMany(ChatChannel.Telepathic, message, adminMessageWrap, source, hideChat, true, admins, Color.PaleVioletRed); - foreach (var repeater in EntityQuery()) - { - _chatSystem.TrySendInGameICMessage(repeater.Owner, message, InGameICChatType.Speak, false); - } + if (clients.psychog.Count() > 0) + { + var descriptor = SourceToDescriptor(source); + string psychogMessageWrap; + + psychogMessageWrap = Loc.GetString("chat-manager-send-telepathic-chat-wrap-message-psychognomy", + ("source", descriptor.ToUpper()), ("message", message)); + + _chatManager.ChatMessageToMany(ChatChannel.Telepathic, message, psychogMessageWrap, source, hideChat, true, clients.psychog.ToList(), Color.PaleVioletRed); } - private string ObfuscateMessageReadability(string message, float chance) + if (_random.Prob(0.1f)) + _glimmerSystem.Glimmer++; + + if (_random.Prob(Math.Min(0.33f + (float) _glimmerSystem.Glimmer / 1500, 1))) { - var modifiedMessage = new StringBuilder(message); + float obfuscation = 0.25f + (float) _glimmerSystem.Glimmer / 2000; + var obfuscated = ObfuscateMessageReadability(message, obfuscation); + _chatManager.ChatMessageToMany(ChatChannel.Telepathic, obfuscated, messageWrap, source, hideChat, false, GetDreamers(clients.normal.Concat(clients.psychog)), Color.PaleVioletRed); + } + + foreach (var repeater in EntityQuery()) + _chatSystem.TrySendInGameICMessage(repeater.Owner, message, InGameICChatType.Speak, false); + } + + private string ObfuscateMessageReadability(string message, float chance) + { + var modifiedMessage = new StringBuilder(message); - for (var i = 0; i < message.Length; i++) + for (var i = 0; i < message.Length; i++) + { + if (char.IsWhiteSpace((modifiedMessage[i]))) { - if (char.IsWhiteSpace((modifiedMessage[i]))) - { - continue; - } - - if (_random.Prob(1 - chance)) - { - modifiedMessage[i] = '~'; - } + continue; } - return modifiedMessage.ToString(); + if (_random.Prob(1 - chance)) + { + modifiedMessage[i] = '~'; + } } + + return modifiedMessage.ToString(); } -} +} \ No newline at end of file diff --git a/Content.Server/Chat/TelepathicRepeaterComponent.cs b/Content.Server/Chat/TelepathicRepeaterComponent.cs index 2183fafd4d..784310c5a2 100644 --- a/Content.Server/Chat/TelepathicRepeaterComponent.cs +++ b/Content.Server/Chat/TelepathicRepeaterComponent.cs @@ -1,11 +1,8 @@ -namespace Content.Server.Chat -{ - /// - /// Repeats whatever is happening in telepathic chat. - /// - [RegisterComponent] - public sealed partial class TelepathicRepeaterComponent : Component - { +namespace Content.Server.Chat; + +/// +/// Repeats whatever is happening in telepathic chat. +/// +[RegisterComponent] +public sealed partial class TelepathicRepeaterComponent : Component { } - } -} diff --git a/Content.Server/Chat/V2/Commands/DeleteChatMessageCommand.cs b/Content.Server/Chat/V2/Commands/DeleteChatMessageCommand.cs new file mode 100644 index 0000000000..1f9203d299 --- /dev/null +++ b/Content.Server/Chat/V2/Commands/DeleteChatMessageCommand.cs @@ -0,0 +1,36 @@ +using System.Diagnostics; +using Content.Server.Administration; +using Content.Server.Chat.V2.Repository; +using Content.Shared.Administration; +using Robust.Shared.Toolshed; +using Robust.Shared.Toolshed.Errors; +using Robust.Shared.Utility; + +namespace Content.Server.Chat.V2.Commands; + +[ToolshedCommand, AdminCommand(AdminFlags.Admin)] +public sealed class DeleteChatMessageCommand : ToolshedCommand +{ + [Dependency] private readonly IEntitySystemManager _manager = default!; + + [CommandImplementation("id")] + public void DeleteChatMessage([CommandInvocationContext] IInvocationContext ctx, [CommandArgument] uint messageId) + { + if (!_manager.GetEntitySystem().Delete(messageId)) + { + ctx.ReportError(new MessageIdDoesNotExist()); + } + } +} + +public record struct MessageIdDoesNotExist() : IConError +{ + public FormattedMessage DescribeInner() + { + return FormattedMessage.FromUnformatted(Loc.GetString("command-error-deletechatmessage-id-notexist")); + } + + public string? Expression { get; set; } + public Vector2i? IssueSpan { get; set; } + public StackTrace? Trace { get; set; } +} diff --git a/Content.Server/Chat/V2/Commands/NukeChatMessagesCommand.cs b/Content.Server/Chat/V2/Commands/NukeChatMessagesCommand.cs new file mode 100644 index 0000000000..3d8b69dd76 --- /dev/null +++ b/Content.Server/Chat/V2/Commands/NukeChatMessagesCommand.cs @@ -0,0 +1,41 @@ +using System.Diagnostics; +using Content.Server.Administration; +using Content.Server.Chat.V2.Repository; +using Content.Shared.Administration; +using Robust.Shared.Toolshed; +using Robust.Shared.Toolshed.Errors; +using Robust.Shared.Utility; + +namespace Content.Server.Chat.V2.Commands; + +[ToolshedCommand, AdminCommand(AdminFlags.Admin)] +public sealed class NukeChatMessagesCommand : ToolshedCommand +{ + [Dependency] private readonly IEntitySystemManager _manager = default!; + + [CommandImplementation("usernames")] + public void Command([CommandInvocationContext] IInvocationContext ctx, [CommandArgument] string usernamesCsv) + { + var usernames = usernamesCsv.Split(','); + + foreach (var username in usernames) + { + if (!_manager.GetEntitySystem().NukeForUsername(username, out var reason)) + { + ctx.ReportError(new NukeMessagesForUsernameError(reason)); + } + } + } +} + +public record struct NukeMessagesForUsernameError(string Reason) : IConError +{ + public FormattedMessage DescribeInner() + { + return FormattedMessage.FromUnformatted(Reason); + } + + public string? Expression { get; set; } + public Vector2i? IssueSpan { get; set; } + public StackTrace? Trace { get; set; } +} diff --git a/Content.Server/Chat/V2/Messages.cs b/Content.Server/Chat/V2/Messages.cs new file mode 100644 index 0000000000..31a563cbeb --- /dev/null +++ b/Content.Server/Chat/V2/Messages.cs @@ -0,0 +1,94 @@ +using Content.Shared.Chat.Prototypes; +using Content.Shared.Chat.V2; +using Content.Shared.Radio; + +namespace Content.Server.Chat.V2; + +/// +/// Raised locally when a comms announcement is made. +/// +public sealed class CommsAnnouncementCreatedEvent(EntityUid sender, EntityUid console, string message) : IChatEvent +{ + public uint Id { get; set; } + public EntityUid Sender { get; set; } = sender; + public string Message { get; set; } = message; + public MessageType Type => MessageType.Announcement; + public EntityUid Console = console; +} + +/// +/// Raised locally when a character speaks in Dead Chat. +/// +public sealed class DeadChatCreatedEvent(EntityUid speaker, string message, bool isAdmin) : IChatEvent +{ + public uint Id { get; set; } + public EntityUid Sender { get; set; } = speaker; + public string Message { get; set; } = message; + public MessageType Type => MessageType.DeadChat; + public bool IsAdmin = isAdmin; +} + +/// +/// Raised locally when a character emotes. +/// +public sealed class EmoteCreatedEvent(EntityUid sender, string message, float range) : IChatEvent +{ + public uint Id { get; set; } + public EntityUid Sender { get; set; } = sender; + public string Message { get; set; } = message; + public MessageType Type => MessageType.Emote; + public float Range = range; +} + +/// +/// Raised locally when a character talks in local. +/// +public sealed class LocalChatCreatedEvent(EntityUid speaker, string message, float range) : IChatEvent +{ + public uint Id { get; set; } + public EntityUid Sender { get; set; } = speaker; + public string Message { get; set; } = message; + public MessageType Type => MessageType.Local; + public float Range = range; +} + +/// +/// Raised locally when a character speaks in LOOC. +/// +public sealed class LoocCreatedEvent(EntityUid speaker, string message) : IChatEvent +{ + public uint Id { get; set; } + public EntityUid Sender { get; set; } = speaker; + public string Message { get; set; } = message; + public MessageType Type => MessageType.Looc; +} + +/// +/// Raised locally when a character speaks on the radio. +/// +public sealed class RadioCreatedEvent( + EntityUid speaker, + string message, + RadioChannelPrototype channel) + : IChatEvent +{ + public uint Id { get; set; } + public EntityUid Sender { get; set; } = speaker; + public string Message { get; set; } = message; + public RadioChannelPrototype Channel = channel; + public MessageType Type => MessageType.Radio; +} + +/// +/// Raised locally when a character whispers. +/// +public sealed class WhisperCreatedEvent(EntityUid speaker, string message, float minRange, float maxRange) : IChatEvent +{ + public uint Id { get; set; } + public EntityUid Sender { get; set; } = speaker; + public string Message { get; set; } = message; + public MessageType Type => MessageType.Whisper; + public float MinRange = minRange; + public float MaxRange = maxRange; +} + diff --git a/Content.Server/Chat/V2/Repository/ChatRepository.cs b/Content.Server/Chat/V2/Repository/ChatRepository.cs new file mode 100644 index 0000000000..06de37128f --- /dev/null +++ b/Content.Server/Chat/V2/Repository/ChatRepository.cs @@ -0,0 +1,196 @@ +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Runtime.InteropServices; +using Content.Shared.Chat.V2; +using Content.Shared.Chat.V2.Repository; +using Robust.Server.Player; +using Robust.Shared.Network; +using Robust.Shared.Replays; + +namespace Content.Server.Chat.V2.Repository; + +/// +/// Stores , gives them UIDs, and issues . +/// Allows for deletion of messages. +/// +public sealed class ChatRepositorySystem : EntitySystem +{ + [Dependency] private readonly IReplayRecordingManager _replay = default!; + [Dependency] private readonly IPlayerManager _player = default!; + + // Clocks should start at 1, as 0 indicates "clock not set" or "clock forgotten to be set by bad programmer". + private uint _nextMessageId = 1; + private Dictionary _messages = new(); + private Dictionary> _playerMessages = new(); + + public override void Initialize() + { + Refresh(); + + _replay.RecordingFinished += _ => + { + // TODO: resolve https://github.com/space-wizards/space-station-14/issues/25485 so we can dump the chat to disc. + Refresh(); + }; + } + + /// + /// Adds an to the repo and raises it with a UID for consumption elsewhere. + /// + /// The event to store and raise + /// If storing and raising succeeded. + public bool Add(IChatEvent ev) + { + if (!_player.TryGetSessionByEntity(ev.Sender, out var session)) + { + return false; + } + + var messageId = _nextMessageId; + + _nextMessageId++; + + ev.Id = messageId; + + var storedEv = new ChatRecord + { + UserName = session.Name, + UserId = session.UserId, + EntityName = Name(ev.Sender), + StoredEvent = ev + }; + + _messages[messageId] = storedEv; + + CollectionsMarshal.GetValueRefOrAddDefault(_playerMessages, storedEv.UserId, out _)?.Add(messageId); + + RaiseLocalEvent(ev.Sender, new MessageCreatedEvent(ev), true); + + return true; + } + + /// + /// Returns the event associated with a UID, if it exists. + /// + /// The UID of a event. + /// The event, if it exists. + public IChatEvent? GetEventFor(uint id) + { + return _messages.TryGetValue(id, out var record) ? record.StoredEvent : null; + } + + /// + /// Edits a specific message and issues a that says this happened both locally and + /// on the network. Note that this doesn't replay the message (yet), so translators and mutators won't act on it. + /// + /// The ID to edit + /// The new message to send + /// If patching did anything did anything + /// Should be used for admining and admemeing only. + public bool Patch(uint id, string message) + { + if (!_messages.TryGetValue(id, out var ev)) + { + return false; + } + + ev.StoredEvent.Message = message; + + RaiseLocalEvent(new MessagePatchedEvent(id, message)); + + return true; + } + + /// + /// Deletes a message from the repository and issues a that says this has happened + /// both locally and on the network. + /// + /// The ID to delete + /// If deletion did anything + /// Should only be used for adminning + public bool Delete(uint id) + { + if (!_messages.TryGetValue(id, out var ev)) + { + return false; + } + + _messages.Remove(id); + + if (_playerMessages.TryGetValue(ev.UserId, out var set)) + { + set.Remove(id); + } + + RaiseLocalEvent(new MessageDeletedEvent(id)); + + return true; + } + + /// + /// Nukes a user's entire chat history from the repo and issues a saying this has + /// happened. + /// + /// The user ID to nuke. + /// Why nuking failed, if it did. + /// If nuking did anything. + /// Note that this could be a very large event, as we send every single event ID over the wire. + /// By necessity we can't leak the player-source of chat messages (or if they even have the same origin) because of + /// client modders who could use that information to cheat/metagrudge/etc >:( + public bool NukeForUsername(string userName, [NotNullWhen(false)] out string? reason) + { + if (!_player.TryGetUserId(userName, out var userId)) + { + reason = Loc.GetString("command-error-nukechatmessages-usernames-usernamenotexist", ("username", userName)); + + return false; + } + + return NukeForUserId(userId, out reason); + } + + /// + /// Nukes a user's entire chat history from the repo and issues a saying this has + /// happened. + /// + /// The user ID to nuke. + /// Why nuking failed, if it did. + /// If nuking did anything. + /// Note that this could be a very large event, as we send every single event ID over the wire. + /// By necessity we can't leak the player-source of chat messages (or if they even have the same origin) because of + /// client modders who could use that information to cheat/metagrudge/etc >:( + public bool NukeForUserId(NetUserId userId, [NotNullWhen(false)] out string? reason) + { + if (!_playerMessages.TryGetValue(userId, out var dict)) + { + reason = Loc.GetString("command-error-nukechatmessages-usernames-usernamenomessages", ("userId", userId.UserId.ToString())); + + return false; + } + + foreach (var id in dict) + { + _messages.Remove(id); + } + + var ev = new MessagesNukedEvent(dict); + + CollectionsMarshal.GetValueRefOrAddDefault(_playerMessages, userId, out _)?.Clear(); + + RaiseLocalEvent(ev); + + reason = null; + + return true; + } + + /// + /// Dumps held chat storage data and refreshes the repo. + /// + public void Refresh() + { + _nextMessageId = 1; + _messages.Clear(); + _playerMessages.Clear(); + } +} diff --git a/Content.Server/Chemistry/Components/ReagentTankComponent.cs b/Content.Server/Chemistry/Components/ReagentTankComponent.cs deleted file mode 100644 index cc0d2657d7..0000000000 --- a/Content.Server/Chemistry/Components/ReagentTankComponent.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Content.Shared.FixedPoint; - - -namespace Content.Server.Chemistry.Components -{ - [RegisterComponent] - public sealed partial class ReagentTankComponent : Component - { - [DataField("transferAmount")] - [ViewVariables(VVAccess.ReadWrite)] - public FixedPoint2 TransferAmount { get; set; } = FixedPoint2.New(10); - - [DataField("tankType")] - [ViewVariables(VVAccess.ReadWrite)] - public ReagentTankType TankType { get; set; } = ReagentTankType.Unspecified; - } - - public enum ReagentTankType : byte - { - Unspecified, - Fuel - } -} diff --git a/Content.Server/Chemistry/Components/SolutionHeaterComponent.cs b/Content.Server/Chemistry/Components/SolutionHeaterComponent.cs index c1841e022c..bc1d44e82f 100644 --- a/Content.Server/Chemistry/Components/SolutionHeaterComponent.cs +++ b/Content.Server/Chemistry/Components/SolutionHeaterComponent.cs @@ -4,8 +4,26 @@ namespace Content.Server.Chemistry.Components; public sealed partial class SolutionHeaterComponent : Component { /// - /// How much heat is added per second to the solution, taking upgrades into account. + /// How much heat is added per second to the solution, with no upgrades. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] + public float BaseHeatPerSecond = 120; + + /// + /// How much heat is added per second to the solution, taking upgrades into account. + /// + [ViewVariables(VVAccess.ReadWrite)] public float HeatPerSecond; + + /// + /// The machine part that affects the heat multiplier. + /// + [DataField] + public string MachinePartHeatMultiplier = "Capacitor"; + + /// + /// How much each upgrade multiplies the heat by. + /// + [DataField] + public float PartRatingHeatMultiplier = 1.5f; } diff --git a/Content.Server/Chemistry/Containers/EntitySystems/SolutionContainerSystem.cs b/Content.Server/Chemistry/Containers/EntitySystems/SolutionContainerSystem.cs index 755312554c..3d99db1129 100644 --- a/Content.Server/Chemistry/Containers/EntitySystems/SolutionContainerSystem.cs +++ b/Content.Server/Chemistry/Containers/EntitySystems/SolutionContainerSystem.cs @@ -9,180 +9,37 @@ namespace Content.Server.Chemistry.Containers.EntitySystems; +[Obsolete("This is being depreciated. Use SharedSolutionContainerSystem instead!")] public sealed partial class SolutionContainerSystem : SharedSolutionContainerSystem { - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnMapInit); - SubscribeLocalEvent(OnComponentShutdown); - SubscribeLocalEvent(OnComponentShutdown); - } - - + [Obsolete("This is being depreciated. Use the ensure methods in SharedSolutionContainerSystem instead!")] public Solution EnsureSolution(Entity entity, string name) => EnsureSolution(entity, name, out _); + [Obsolete("This is being depreciated. Use the ensure methods in SharedSolutionContainerSystem instead!")] public Solution EnsureSolution(Entity entity, string name, out bool existed) => EnsureSolution(entity, name, FixedPoint2.Zero, out existed); + [Obsolete("This is being depreciated. Use the ensure methods in SharedSolutionContainerSystem instead!")] public Solution EnsureSolution(Entity entity, string name, FixedPoint2 maxVol, out bool existed) => EnsureSolution(entity, name, maxVol, null, out existed); + [Obsolete("This is being depreciated. Use the ensure methods in SharedSolutionContainerSystem instead!")] public Solution EnsureSolution(Entity entity, string name, FixedPoint2 maxVol, Solution? prototype, out bool existed) { - var (uid, meta) = entity; - if (!Resolve(uid, ref meta)) - throw new InvalidOperationException("Attempted to ensure solution on invalid entity."); - - var manager = EnsureComp(uid); - if (meta.EntityLifeStage >= EntityLifeStage.MapInitialized) - return EnsureSolutionEntity((uid, manager), name, maxVol, prototype, out existed).Comp.Solution; - else - return EnsureSolutionPrototype((uid, manager), name, maxVol, prototype, out existed); - } - - public void EnsureAllSolutions(Entity entity) - { - if (entity.Comp.Solutions is not { } prototypes) - return; - - foreach (var (name, prototype) in prototypes) - { - EnsureSolutionEntity((entity.Owner, entity.Comp), name, prototype.MaxVolume, prototype, out _); - } - - entity.Comp.Solutions = null; - Dirty(entity); - } - - public Entity EnsureSolutionEntity(Entity entity, string name, FixedPoint2 maxVol, Solution? prototype, out bool existed) - { - existed = true; - - var (uid, container) = entity; - - var solutionSlot = ContainerSystem.EnsureContainer(uid, $"solution@{name}", out existed); - if (!Resolve(uid, ref container, logMissing: false)) - { - existed = false; - container = AddComp(uid); - container.Containers.Add(name); - } - else if (!existed) - { - container.Containers.Add(name); - Dirty(uid, container); - } - - var needsInit = false; - SolutionComponent solutionComp; - if (solutionSlot.ContainedEntity is not { } solutionId) - { - prototype ??= new() { MaxVolume = maxVol }; - prototype.Name = name; - (solutionId, solutionComp, _) = SpawnSolutionUninitialized(solutionSlot, name, maxVol, prototype); - existed = false; - needsInit = true; - Dirty(uid, container); - } - else - { - solutionComp = Comp(solutionId); - DebugTools.Assert(TryComp(solutionId, out ContainedSolutionComponent? relation) && relation.Container == uid && relation.ContainerName == name); - DebugTools.Assert(solutionComp.Solution.Name == name); - - var solution = solutionComp.Solution; - solution.MaxVolume = FixedPoint2.Max(solution.MaxVolume, maxVol); - - // Depending on MapInitEvent order some systems can ensure solution empty solutions and conflict with the prototype solutions. - // We want the reagents from the prototype to exist even if something else already created the solution. - if (prototype is { Volume.Value: > 0 }) - solution.AddSolution(prototype, PrototypeManager); - - Dirty(solutionId, solutionComp); - } - - if (needsInit) - EntityManager.InitializeAndStartEntity(solutionId, Transform(solutionId).MapID); - - return (solutionId, solutionComp); - } - - private Solution EnsureSolutionPrototype(Entity entity, string name, FixedPoint2 maxVol, Solution? prototype, out bool existed) - { - existed = true; - - var (uid, container) = entity; - if (!Resolve(uid, ref container, logMissing: false)) - { - container = AddComp(uid); - existed = false; - } - - if (container.Solutions is null) - container.Solutions = new(SolutionContainerManagerComponent.DefaultCapacity); - - if (!container.Solutions.TryGetValue(name, out var solution)) - { - solution = prototype ?? new() { Name = name, MaxVolume = maxVol }; - container.Solutions.Add(name, solution); - existed = false; - } - else - solution.MaxVolume = FixedPoint2.Max(solution.MaxVolume, maxVol); - - Dirty(uid, container); - return solution; + EnsureSolution(entity, name, maxVol, prototype, out existed, out var solution); + return solution!;//solution is only ever null on the client, so we can suppress this } - - private Entity SpawnSolutionUninitialized(ContainerSlot container, string name, FixedPoint2 maxVol, Solution prototype) + [Obsolete("This is being depreciated. Use the ensure methods in SharedSolutionContainerSystem instead!")] + public Entity EnsureSolutionEntity( + Entity entity, + string name, + FixedPoint2 maxVol, + Solution? prototype, + out bool existed) { - var coords = new EntityCoordinates(container.Owner, Vector2.Zero); - var uid = EntityManager.CreateEntityUninitialized(null, coords, null); - - var solution = new SolutionComponent() { Solution = prototype }; - AddComp(uid, solution); - - var relation = new ContainedSolutionComponent() { Container = container.Owner, ContainerName = name }; - AddComp(uid, relation); - - MetaData.SetEntityName(uid, $"solution - {name}"); - ContainerSystem.Insert(uid, container, force: true); - - return (uid, solution, relation); + EnsureSolutionEntity(entity, name, out existed, out var solEnt, maxVol, prototype); + return solEnt!.Value;//solEnt is only ever null on the client, so we can suppress this } - - #region Event Handlers - - private void OnMapInit(Entity entity, ref MapInitEvent args) - { - EnsureAllSolutions(entity); - } - - private void OnComponentShutdown(Entity entity, ref ComponentShutdown args) - { - foreach (var name in entity.Comp.Containers) - { - if (ContainerSystem.TryGetContainer(entity, $"solution@{name}", out var solutionContainer)) - ContainerSystem.ShutdownContainer(solutionContainer); - } - entity.Comp.Containers.Clear(); - } - - private void OnComponentShutdown(Entity entity, ref ComponentShutdown args) - { - if (TryComp(entity.Comp.Container, out SolutionContainerManagerComponent? container)) - { - container.Containers.Remove(entity.Comp.ContainerName); - Dirty(entity.Comp.Container, container); - } - - if (ContainerSystem.TryGetContainer(entity, $"solution@{entity.Comp.ContainerName}", out var solutionContainer)) - ContainerSystem.ShutdownContainer(solutionContainer); - } - - #endregion Event Handlers } diff --git a/Content.Server/Chemistry/EntitySystems/ChemMasterSystem.cs b/Content.Server/Chemistry/EntitySystems/ChemMasterSystem.cs index ab91044574..289db75981 100644 --- a/Content.Server/Chemistry/EntitySystems/ChemMasterSystem.cs +++ b/Content.Server/Chemistry/EntitySystems/ChemMasterSystem.cs @@ -80,7 +80,7 @@ private void UpdateUiState(Entity ent, bool updateLabel = f chemMaster.Mode, BuildInputContainerInfo(inputContainer), BuildOutputContainerInfo(outputContainer), bufferReagents, bufferCurrentVolume, chemMaster.PillType, chemMaster.PillDosageLimit, updateLabel); - _userInterfaceSystem.TrySetUiState(owner, ChemMasterUiKey.Key, state); + _userInterfaceSystem.SetUiState(owner, ChemMasterUiKey.Key, state); } private void OnSetModeMessage(Entity chemMaster, ref ChemMasterSetModeMessage message) @@ -179,7 +179,7 @@ private void DiscardReagents(Entity chemMaster, ReagentId i private void OnCreatePillsMessage(Entity chemMaster, ref ChemMasterCreatePillsMessage message) { - var user = message.Session.AttachedEntity; + var user = message.Actor; var maybeContainer = _itemSlotsSystem.GetItemOrNull(chemMaster, SharedChemMaster.OutputSlotName); if (maybeContainer is not { Valid: true } container || !TryComp(container, out StorageComponent? storage)) @@ -218,18 +218,9 @@ private void OnCreatePillsMessage(Entity chemMaster, ref Ch pill.PillType = chemMaster.Comp.PillType; Dirty(item, pill); - if (user.HasValue) - { - // Log pill creation by a user - _adminLogger.Add(LogType.Action, LogImpact.Low, - $"{ToPrettyString(user.Value):user} printed {ToPrettyString(item):pill} {SolutionContainerSystem.ToPrettyString(itemSolution.Comp.Solution)}"); - } - else - { - // Log pill creation by magic? This should never happen... right? - _adminLogger.Add(LogType.Action, LogImpact.Low, - $"Unknown printed {ToPrettyString(item):pill} {SolutionContainerSystem.ToPrettyString(itemSolution.Comp.Solution)}"); - } + // Log pill creation by a user + _adminLogger.Add(LogType.Action, LogImpact.Low, + $"{ToPrettyString(user):user} printed {ToPrettyString(item):pill} {SharedSolutionContainerSystem.ToPrettyString(itemSolution.Comp.Solution)}"); } UpdateUiState(chemMaster); @@ -238,7 +229,7 @@ private void OnCreatePillsMessage(Entity chemMaster, ref Ch private void OnOutputToBottleMessage(Entity chemMaster, ref ChemMasterOutputToBottleMessage message) { - var user = message.Session.AttachedEntity; + var user = message.Actor; var maybeContainer = _itemSlotsSystem.GetItemOrNull(chemMaster, SharedChemMaster.OutputSlotName); if (maybeContainer is not { Valid: true } container || !_solutionContainerSystem.TryGetSolution(container, SharedChemMaster.BottleSolutionName, out var soln, out var solution)) @@ -260,18 +251,9 @@ private void OnOutputToBottleMessage(Entity chemMaster, ref _labelSystem.Label(container, message.Label); _solutionContainerSystem.TryAddSolution(soln.Value, withdrawal); - if (user.HasValue) - { - // Log bottle creation by a user - _adminLogger.Add(LogType.Action, LogImpact.Low, - $"{ToPrettyString(user.Value):user} bottled {ToPrettyString(container):bottle} {SolutionContainerSystem.ToPrettyString(solution)}"); - } - else - { - // Log bottle creation by magic? This should never happen... right? - _adminLogger.Add(LogType.Action, LogImpact.Low, - $"Unknown bottled {ToPrettyString(container):bottle} {SolutionContainerSystem.ToPrettyString(solution)}"); - } + // Log bottle creation by a user + _adminLogger.Add(LogType.Action, LogImpact.Low, + $"{ToPrettyString(user):user} bottled {ToPrettyString(container):bottle} {SharedSolutionContainerSystem.ToPrettyString(solution)}"); UpdateUiState(chemMaster); ClickSound(chemMaster); diff --git a/Content.Server/Chemistry/EntitySystems/HypospraySystem.cs b/Content.Server/Chemistry/EntitySystems/HypospraySystem.cs index dfbe45c035..56cc0f9670 100644 --- a/Content.Server/Chemistry/EntitySystems/HypospraySystem.cs +++ b/Content.Server/Chemistry/EntitySystems/HypospraySystem.cs @@ -25,7 +25,6 @@ public sealed class HypospraySystem : SharedHypospraySystem { [Dependency] private readonly AudioSystem _audio = default!; [Dependency] private readonly InteractionSystem _interaction = default!; - [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!; public override void Initialize() { diff --git a/Content.Server/Chemistry/EntitySystems/InjectorSystem.cs b/Content.Server/Chemistry/EntitySystems/InjectorSystem.cs index a6d13b2f7c..9dd1d429b7 100644 --- a/Content.Server/Chemistry/EntitySystems/InjectorSystem.cs +++ b/Content.Server/Chemistry/EntitySystems/InjectorSystem.cs @@ -124,7 +124,7 @@ private void InjectDoAfter(Entity injector, EntityUid target, Popup.PopupEntity(Loc.GetString("injector-component-injecting-user"), target, user); } - if (!SolutionContainers.TryGetSolution(injector.Owner, InjectorComponent.SolutionName, out _, out var solution)) + if (!SolutionContainers.TryGetSolution(injector.Owner, injector.Comp.SolutionName, out _, out var solution)) return; var actualDelay = MathHelper.Max(injector.Comp.Delay, TimeSpan.FromSeconds(1)); @@ -252,7 +252,7 @@ private void TryInjectIntoBloodstream(Entity injector, Entity private void TryInject(Entity injector, EntityUid targetEntity, Entity targetSolution, EntityUid user, bool asRefill) { - if (!SolutionContainers.TryGetSolution(injector.Owner, InjectorComponent.SolutionName, out var soln, + if (!SolutionContainers.TryGetSolution(injector.Owner, injector.Comp.SolutionName, out var soln, out var solution) || solution.Volume == 0) return; @@ -294,7 +294,7 @@ private void TryInject(Entity injector, EntityUid targetEntit private void AfterInject(Entity injector, EntityUid target) { // Automatically set syringe to draw after completely draining it. - if (SolutionContainers.TryGetSolution(injector.Owner, InjectorComponent.SolutionName, out _, + if (SolutionContainers.TryGetSolution(injector.Owner, injector.Comp.SolutionName, out _, out var solution) && solution.Volume == 0) { SetMode(injector, InjectorToggleMode.Draw); @@ -308,7 +308,7 @@ private void AfterInject(Entity injector, EntityUid target) private void AfterDraw(Entity injector, EntityUid target) { // Automatically set syringe to inject after completely filling it. - if (SolutionContainers.TryGetSolution(injector.Owner, InjectorComponent.SolutionName, out _, + if (SolutionContainers.TryGetSolution(injector.Owner, injector.Comp.SolutionName, out _, out var solution) && solution.AvailableVolume == 0) { SetMode(injector, InjectorToggleMode.Inject); @@ -322,7 +322,7 @@ private void AfterDraw(Entity injector, EntityUid target) private void TryDraw(Entity injector, Entity target, Entity targetSolution, EntityUid user) { - if (!SolutionContainers.TryGetSolution(injector.Owner, InjectorComponent.SolutionName, out var soln, + if (!SolutionContainers.TryGetSolution(injector.Owner, injector.Comp.SolutionName, out var soln, out var solution) || solution.AvailableVolume == 0) { return; diff --git a/Content.Server/Chemistry/EntitySystems/ReactionMixerSystem.cs b/Content.Server/Chemistry/EntitySystems/ReactionMixerSystem.cs index 032374d4a5..d5f7655f88 100644 --- a/Content.Server/Chemistry/EntitySystems/ReactionMixerSystem.cs +++ b/Content.Server/Chemistry/EntitySystems/ReactionMixerSystem.cs @@ -30,7 +30,7 @@ private void OnAfterInteract(Entity entity, ref AfterInt return; } - if (!_solutionContainers.TryGetMixableSolution(args.Target.Value, out var solution)) + if (!_solutionContainers.TryGetMixableSolution(args.Target.Value, out var solution, out _)) return; _popup.PopupEntity(Loc.GetString(entity.Comp.MixMessage, ("mixed", Identity.Entity(args.Target.Value, EntityManager)), ("mixer", Identity.Entity(entity.Owner, EntityManager))), args.User, args.User); diff --git a/Content.Server/Chemistry/EntitySystems/ReagentDispenserSystem.cs b/Content.Server/Chemistry/EntitySystems/ReagentDispenserSystem.cs index d6433da56a..c48bf086d3 100644 --- a/Content.Server/Chemistry/EntitySystems/ReagentDispenserSystem.cs +++ b/Content.Server/Chemistry/EntitySystems/ReagentDispenserSystem.cs @@ -62,7 +62,7 @@ private void UpdateUiState(Entity reagentDispenser) var inventory = GetInventory(reagentDispenser); var state = new ReagentDispenserBoundUserInterfaceState(outputContainerInfo, GetNetEntity(outputContainer), inventory, reagentDispenser.Comp.DispenseAmount); - _userInterfaceSystem.TrySetUiState(reagentDispenser, ReagentDispenserUiKey.Key, state); + _userInterfaceSystem.SetUiState(reagentDispenser.Owner, ReagentDispenserUiKey.Key, state); } private ContainerInfo? BuildOutputContainerInfo(EntityUid? container) @@ -81,9 +81,9 @@ private void UpdateUiState(Entity reagentDispenser) return null; } - private List>> GetInventory(Entity reagentDispenser) + private List GetInventory(Entity reagentDispenser) { - var inventory = new List>>(); + var inventory = new List(); for (var i = 0; i < reagentDispenser.Comp.NumSlots; i++) { @@ -99,15 +99,17 @@ private List>> GetInventory(En else continue; - // Add volume remaining label + // Get volume remaining and color of solution FixedPoint2 quantity = 0f; + var reagentColor = Color.White; if (storedContainer != null && _solutionContainerSystem.TryGetDrainableSolution(storedContainer.Value, out _, out var sol)) { quantity = sol.Volume; + reagentColor = sol.GetColor(_prototypeManager); } var storedAmount = Loc.GetString("reagent-dispenser-window-quantity-label-text", ("quantity", quantity)); - inventory.Add(new KeyValuePair>(storageSlotId, new KeyValuePair(reagentLabel, storedAmount))); + inventory.Add(new ReagentInventoryItem(storageSlotId, reagentLabel, storedAmount, reagentColor)); } return inventory; diff --git a/Content.Server/Chemistry/EntitySystems/SolutionHeaterSystem.cs b/Content.Server/Chemistry/EntitySystems/SolutionHeaterSystem.cs index 6e6373e10b..1ef589ab5c 100644 --- a/Content.Server/Chemistry/EntitySystems/SolutionHeaterSystem.cs +++ b/Content.Server/Chemistry/EntitySystems/SolutionHeaterSystem.cs @@ -1,5 +1,6 @@ using Content.Server.Chemistry.Components; using Content.Server.Chemistry.Containers.EntitySystems; +using Content.Server.Construction; using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; using Content.Shared.Chemistry; @@ -20,6 +21,8 @@ public override void Initialize() base.Initialize(); SubscribeLocalEvent(OnPowerChanged); + SubscribeLocalEvent(OnRefreshParts); + SubscribeLocalEvent(OnUpgradeExamine); SubscribeLocalEvent(OnItemPlaced); SubscribeLocalEvent(OnItemRemoved); } @@ -61,6 +64,18 @@ private void OnPowerChanged(Entity entity, ref PowerCha } } + private void OnRefreshParts(Entity entity, ref RefreshPartsEvent args) + { + var heatRating = args.PartRatings[entity.Comp.MachinePartHeatMultiplier] - 1; + + entity.Comp.HeatPerSecond = entity.Comp.BaseHeatPerSecond * MathF.Pow(entity.Comp.PartRatingHeatMultiplier, heatRating); + } + + private void OnUpgradeExamine(Entity entity, ref UpgradeExamineEvent args) + { + args.AddPercentageUpgrade("solution-heater-upgrade-heat", entity.Comp.HeatPerSecond / entity.Comp.BaseHeatPerSecond); + } + private void OnItemPlaced(Entity entity, ref ItemPlacedEvent args) { TryTurnOn(entity); diff --git a/Content.Server/Chemistry/EntitySystems/TransformableContainerSystem.cs b/Content.Server/Chemistry/EntitySystems/TransformableContainerSystem.cs index 94a3fe2186..c375d97b8c 100644 --- a/Content.Server/Chemistry/EntitySystems/TransformableContainerSystem.cs +++ b/Content.Server/Chemistry/EntitySystems/TransformableContainerSystem.cs @@ -20,7 +20,7 @@ public override void Initialize() SubscribeLocalEvent(OnSolutionChange); } - private void OnMapInit(Entity entity, ref MapInitEvent args) + private void OnMapInit(Entity entity, ref MapInitEvent args) { var meta = MetaData(entity.Owner); if (string.IsNullOrEmpty(entity.Comp.InitialName)) diff --git a/Content.Server/Chemistry/ReagentEffectConditions/OrganType.cs b/Content.Server/Chemistry/ReagentEffectConditions/OrganType.cs index 3b7bffb9cf..4ae13b6a6e 100644 --- a/Content.Server/Chemistry/ReagentEffectConditions/OrganType.cs +++ b/Content.Server/Chemistry/ReagentEffectConditions/OrganType.cs @@ -1,4 +1,4 @@ -using Content.Server.Body.Components; +using Content.Server.Body.Components; using Content.Shared.Body.Prototypes; using Content.Shared.Chemistry.Reagent; using Robust.Shared.Prototypes; @@ -35,7 +35,7 @@ public override bool Condition(ReagentEffectArgs args) public override string GuidebookExplanation(IPrototypeManager prototype) { return Loc.GetString("reagent-effect-condition-guidebook-organ-type", - ("name", prototype.Index(Type).Name), + ("name", prototype.Index(Type).LocalizedName), ("shouldhave", ShouldHave)); } } diff --git a/Content.Server/Chemistry/ReagentEffectConditions/ReagentThreshold.cs b/Content.Server/Chemistry/ReagentEffectConditions/ReagentThreshold.cs index 50be89581f..061af2b3ae 100644 --- a/Content.Server/Chemistry/ReagentEffectConditions/ReagentThreshold.cs +++ b/Content.Server/Chemistry/ReagentEffectConditions/ReagentThreshold.cs @@ -1,4 +1,4 @@ -using Content.Shared.Chemistry.Reagent; +using Content.Shared.Chemistry.Reagent; using Content.Shared.FixedPoint; using Robust.Shared.Prototypes; @@ -43,7 +43,7 @@ public override string GuidebookExplanation(IPrototypeManager prototype) prototype.TryIndex(Reagent, out reagentProto); return Loc.GetString("reagent-effect-condition-guidebook-reagent-threshold", - ("reagent", reagentProto?.LocalizedName ?? "this reagent"), + ("reagent", reagentProto?.LocalizedName ?? Loc.GetString("reagent-effect-condition-guidebook-this-reagent")), ("max", Max == FixedPoint2.MaxValue ? (float) int.MaxValue : Max.Float()), ("min", Min.Float())); } diff --git a/Content.Server/Chemistry/ReagentEffectConditions/TotalHunger.cs b/Content.Server/Chemistry/ReagentEffectConditions/TotalHunger.cs new file mode 100644 index 0000000000..1dd12e632a --- /dev/null +++ b/Content.Server/Chemistry/ReagentEffectConditions/TotalHunger.cs @@ -0,0 +1,35 @@ +using Content.Shared.Chemistry.Reagent; +using Content.Shared.Nutrition.Components; +using Content.Shared.FixedPoint; +using Robust.Shared.Prototypes; + +namespace Content.Server.Chemistry.ReagentEffectConditions +{ + public sealed partial class Hunger : ReagentEffectCondition + { + [DataField] + public float Max = float.PositiveInfinity; + + [DataField] + public float Min = 0; + + public override bool Condition(ReagentEffectArgs args) + { + if (args.EntityManager.TryGetComponent(args.SolutionEntity, out HungerComponent? hunger)) + { + var total = hunger.CurrentHunger; + if (total > Min && total < Max) + return true; + } + + return false; + } + + public override string GuidebookExplanation(IPrototypeManager prototype) + { + return Loc.GetString("reagent-effect-condition-guidebook-total-hunger", + ("max", float.IsPositiveInfinity(Max) ? (float) int.MaxValue : Max), + ("min", Min)); + } + } +} diff --git a/Content.Server/Chemistry/ReagentEffects/AdjustReagent.cs b/Content.Server/Chemistry/ReagentEffects/AdjustReagent.cs index e70626f1d3..16d69edd9a 100644 --- a/Content.Server/Chemistry/ReagentEffects/AdjustReagent.cs +++ b/Content.Server/Chemistry/ReagentEffects/AdjustReagent.cs @@ -1,4 +1,4 @@ -using Content.Shared.Body.Prototypes; +using Content.Shared.Body.Prototypes; using Content.Shared.Chemistry.Reagent; using Content.Shared.FixedPoint; using JetBrains.Annotations; @@ -74,7 +74,7 @@ public override void Effect(ReagentEffectArgs args) return Loc.GetString("reagent-effect-guidebook-adjust-reagent-group", ("chance", Probability), ("deltasign", MathF.Sign(Amount.Float())), - ("group", groupProto.ID), + ("group", groupProto.LocalizedName), ("amount", MathF.Abs(Amount.Float()))); } diff --git a/Content.Server/Chemistry/ReagentEffects/ChemAddMoodlet.cs b/Content.Server/Chemistry/ReagentEffects/ChemAddMoodlet.cs new file mode 100644 index 0000000000..f5bb629299 --- /dev/null +++ b/Content.Server/Chemistry/ReagentEffects/ChemAddMoodlet.cs @@ -0,0 +1,34 @@ +using Content.Shared.Chemistry.Reagent; +using Content.Shared.Mood; +using JetBrains.Annotations; +using Robust.Shared.Prototypes; + +namespace Content.Server.Chemistry.ReagentEffects; + +/// +/// Adds a moodlet to an entity. +/// +[UsedImplicitly] +public sealed partial class ChemAddMoodlet : ReagentEffect +{ + protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) + { + var protoMan = IoCManager.Resolve(); + return Loc.GetString("reagent-effect-guidebook-add-moodlet", + ("amount", protoMan.Index(MoodPrototype.Id).MoodChange), + ("timeout", protoMan.Index(MoodPrototype.Id).Timeout)); + } + + /// + /// The mood prototype to be applied to the using entity. + /// + [DataField(required: true)] + public ProtoId MoodPrototype = default!; + + public override void Effect(ReagentEffectArgs args) + { + var entityManager = IoCManager.Resolve(); + var ev = new MoodEffectEvent(MoodPrototype); + entityManager.EventBus.RaiseLocalEvent(args.SolutionEntity, ev); + } +} diff --git a/Content.Server/Chemistry/ReagentEffects/ChemCleanBoodstream.cs b/Content.Server/Chemistry/ReagentEffects/ChemCleanBloodstream.cs similarity index 100% rename from Content.Server/Chemistry/ReagentEffects/ChemCleanBoodstream.cs rename to Content.Server/Chemistry/ReagentEffects/ChemCleanBloodstream.cs diff --git a/Content.Server/Chemistry/ReagentEffects/HealthChange.cs b/Content.Server/Chemistry/ReagentEffects/HealthChange.cs index 2a6181c7f3..53933072b6 100644 --- a/Content.Server/Chemistry/ReagentEffects/HealthChange.cs +++ b/Content.Server/Chemistry/ReagentEffects/HealthChange.cs @@ -3,6 +3,7 @@ using Content.Shared.Damage.Prototypes; using Content.Shared.FixedPoint; using Content.Shared.Localizations; +using Content.Shared.Targeting; // Shitmed using JetBrains.Annotations; using Robust.Shared.Prototypes; using System.Linq; @@ -74,7 +75,7 @@ protected override string ReagentEffectGuidebookText(IPrototypeManager prototype damages.Add( Loc.GetString("health-change-display", - ("kind", group.ID), + ("kind", group.LocalizedName), ("amount", MathF.Abs(amount.Float())), ("deltasign", sign) )); @@ -96,7 +97,7 @@ protected override string ReagentEffectGuidebookText(IPrototypeManager prototype damages.Add( Loc.GetString("health-change-display", - ("kind", kind), + ("kind", prototype.Index(kind).LocalizedName), ("amount", MathF.Abs(amount.Float())), ("deltasign", sign) )); @@ -119,7 +120,12 @@ public override void Effect(ReagentEffectArgs args) args.SolutionEntity, Damage * scale, IgnoreResistances, - interruptsDoAfters: false); + interruptsDoAfters: false, + // Shitmed Start + targetPart: TargetBodyPart.All, + partMultiplier: 0.5f, + canSever: false); + // Shitmed End } } } diff --git a/Content.Server/Chemistry/ReagentEffects/MakeSentient.cs b/Content.Server/Chemistry/ReagentEffects/MakeSentient.cs index 8d5a583f6d..21bfe23923 100644 --- a/Content.Server/Chemistry/ReagentEffects/MakeSentient.cs +++ b/Content.Server/Chemistry/ReagentEffects/MakeSentient.cs @@ -1,15 +1,12 @@ using System.Linq; using Content.Server.Ghost.Roles.Components; using Content.Server.Language; -using Content.Server.Language.Events; using Content.Server.Speech.Components; using Content.Shared.Chemistry.Reagent; using Content.Shared.Language; using Content.Shared.Language.Systems; using Content.Shared.Mind.Components; using Robust.Shared.Prototypes; -using Content.Server.Psionics; -using Content.Shared.Body.Part; //Nyano - Summary: pulls in the ability for the sentient creature to become psionic. using Content.Shared.Humanoid; using Content.Shared.Language.Components; //Delta-V - Banning humanoids from becoming ghost roles. using Content.Shared.Language.Events; @@ -43,7 +40,7 @@ public override void Effect(ReagentEffectArgs args) if (!knowledge.SpokenLanguages.Contains(fallback)) knowledge.SpokenLanguages.Add(fallback); - IoCManager.Resolve().GetEntitySystem().UpdateEntityLanguages(uid, speaker); + IoCManager.Resolve().GetEntitySystem().UpdateEntityLanguages(uid); // Stops from adding a ghost role to things like people who already have a mind if (entityManager.TryGetComponent(uid, out var mindContainer) && mindContainer.HasMind) @@ -68,7 +65,6 @@ public override void Effect(ReagentEffectArgs args) ghostRole = entityManager.AddComponent(uid); entityManager.EnsureComponent(uid); - entityManager.EnsureComponent(uid); //Nyano - Summary:. Makes the animated body able to get psionics. var entityData = entityManager.GetComponent(uid); ghostRole.RoleName = entityData.EntityName; diff --git a/Content.Server/Chemistry/ReagentEffects/ReduceRotting.cs b/Content.Server/Chemistry/ReagentEffects/ReduceRotting.cs new file mode 100644 index 0000000000..cea4f85332 --- /dev/null +++ b/Content.Server/Chemistry/ReagentEffects/ReduceRotting.cs @@ -0,0 +1,31 @@ +using Content.Shared.Chemistry.Reagent; +using JetBrains.Annotations; +using Robust.Shared.Prototypes; +using Content.Server.Atmos.Rotting; + +namespace Content.Server.Chemistry.ReagentEffects +{ + /// + /// Reduces the rotting accumulator on the patient, making them revivable. + /// + [UsedImplicitly] + public sealed partial class ReduceRotting : ReagentEffect + { + [DataField("seconds")] + public double RottingAmount = 10; + + protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) + => Loc.GetString("reagent-effect-guidebook-reduce-rotting", + ("chance", Probability), + ("time", RottingAmount)); + public override void Effect(ReagentEffectArgs args) + { + if (args.Scale != 1f) + return; + + var rottingSys = args.EntityManager.EntitySysManager.GetEntitySystem(); + + rottingSys.ReduceAccumulator(args.SolutionEntity, TimeSpan.FromSeconds(RottingAmount)); + } + } +} diff --git a/Content.Server/Chemistry/ReagentEffects/ResetNarcolepsy.cs b/Content.Server/Chemistry/ReagentEffects/ResetNarcolepsy.cs index fa4f910891..2893505fc8 100644 --- a/Content.Server/Chemistry/ReagentEffects/ResetNarcolepsy.cs +++ b/Content.Server/Chemistry/ReagentEffects/ResetNarcolepsy.cs @@ -15,7 +15,7 @@ public sealed partial class ResetNarcolepsy : ReagentEffect /// The # of seconds the effect resets the narcolepsy timer to /// [DataField("TimerReset")] - public int TimerReset = 600; + public float TimerReset = 600; protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) => Loc.GetString("reagent-effect-guidebook-reset-narcolepsy", ("chance", Probability)); diff --git a/Content.Server/Cloning/CloningConsoleSystem.cs b/Content.Server/Cloning/CloningConsoleSystem.cs index c95c37312e..c0685024aa 100644 --- a/Content.Server/Cloning/CloningConsoleSystem.cs +++ b/Content.Server/Cloning/CloningConsoleSystem.cs @@ -32,7 +32,7 @@ public sealed class CloningConsoleSystem : EntitySystem [Dependency] private readonly MobStateSystem _mobStateSystem = default!; [Dependency] private readonly PowerReceiverSystem _powerReceiverSystem = default!; [Dependency] private readonly SharedMindSystem _mindSystem = default!; - + public override void Initialize() { base.Initialize(); @@ -52,14 +52,16 @@ private void OnInit(EntityUid uid, CloningConsoleComponent component, ComponentI } private void OnButtonPressed(EntityUid uid, CloningConsoleComponent consoleComponent, UiButtonPressedMessage args) { - if (!_powerReceiverSystem.IsPowered(uid)) + if (!_powerReceiverSystem.IsPowered(uid) + || consoleComponent.GeneticScanner is null + || consoleComponent.CloningPod is null + || !TryComp(consoleComponent.CloningPod.Value, out var cloningPod)) return; switch (args.Button) { case UiButton.Clone: - if (consoleComponent.GeneticScanner != null && consoleComponent.CloningPod != null) - TryClone(uid, consoleComponent.CloningPod.Value, consoleComponent.GeneticScanner.Value, consoleComponent: consoleComponent); + TryClone(uid, consoleComponent.CloningPod.Value, consoleComponent.GeneticScanner.Value, cloningPod, consoleComponent: consoleComponent); break; } UpdateUserInterface(uid, consoleComponent); @@ -93,13 +95,15 @@ private void OnMapInit(EntityUid uid, CloningConsoleComponent component, MapInit private void OnNewLink(EntityUid uid, CloningConsoleComponent component, NewLinkEvent args) { - if (TryComp(args.Sink, out var scanner) && args.SourcePort == CloningConsoleComponent.ScannerPort) + if (TryComp(args.Sink, out var scanner) + && args.SourcePort == CloningConsoleComponent.ScannerPort) { component.GeneticScanner = args.Sink; scanner.ConnectedConsole = uid; } - if (TryComp(args.Sink, out var pod) && args.SourcePort == CloningConsoleComponent.PodPort) + if (TryComp(args.Sink, out var pod) + && args.SourcePort == CloningConsoleComponent.PodPort) { component.CloningPod = args.Sink; pod.ConnectedConsole = uid; @@ -125,72 +129,74 @@ private void OnUIOpen(EntityUid uid, CloningConsoleComponent component, AfterAct private void OnAnchorChanged(EntityUid uid, CloningConsoleComponent component, ref AnchorStateChangedEvent args) { - if (args.Anchored) - { - RecheckConnections(uid, component.CloningPod, component.GeneticScanner, component); + if (!args.Anchored + || !RecheckConnections(uid, component.CloningPod, component.GeneticScanner, component)) return; - } + UpdateUserInterface(uid, component); } public void UpdateUserInterface(EntityUid consoleUid, CloningConsoleComponent consoleComponent) { - if (!_uiSystem.TryGetUi(consoleUid, CloningConsoleUiKey.Key, out var ui)) + if (!_uiSystem.HasUi(consoleUid, CloningConsoleUiKey.Key)) return; if (!_powerReceiverSystem.IsPowered(consoleUid)) { - _uiSystem.CloseAll(ui); + _uiSystem.CloseUis(consoleUid); return; } var newState = GetUserInterfaceState(consoleComponent); - _uiSystem.SetUiState(ui, newState); + _uiSystem.SetUiState(consoleUid, CloningConsoleUiKey.Key, newState); } - public void TryClone(EntityUid uid, EntityUid cloningPodUid, EntityUid scannerUid, CloningPodComponent? cloningPod = null, MedicalScannerComponent? scannerComp = null, CloningConsoleComponent? consoleComponent = null) + public void TryClone(EntityUid uid, EntityUid cloningPodUid, EntityUid scannerUid, CloningPodComponent cloningPod, MedicalScannerComponent? scannerComp = null, CloningConsoleComponent? consoleComponent = null) { - if (!Resolve(uid, ref consoleComponent) || !Resolve(cloningPodUid, ref cloningPod) || !Resolve(scannerUid, ref scannerComp)) - return; - - if (!Transform(cloningPodUid).Anchored || !Transform(scannerUid).Anchored) - return; - - if (!consoleComponent.CloningPodInRange || !consoleComponent.GeneticScannerInRange) + if (!Resolve(uid, ref consoleComponent) + || !Resolve(scannerUid, ref scannerComp) + || !Transform(cloningPodUid).Anchored + || !Transform(scannerUid).Anchored + || !consoleComponent.CloningPodInRange + || !consoleComponent.GeneticScannerInRange) return; var body = scannerComp.BodyContainer.ContainedEntity; - if (body is null) + if (body is null + || !_mindSystem.TryGetMind(body.Value, out var mindId, out var mind) + || mind.UserId.HasValue == false + || mind.Session == null) return; - if (!_mindSystem.TryGetMind(body.Value, out var mindId, out var mind)) - return; - - if (mind.UserId.HasValue == false || mind.Session == null) - return; - // Nyano: Adds scannerComp.MetemKarmaBonus - if (_cloningSystem.TryCloning(cloningPodUid, body.Value, (mindId, mind), cloningPod, scannerComp.CloningFailChanceMultiplier, scannerComp.MetemKarmaBonus)) - _adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(uid)} successfully cloned {ToPrettyString(body.Value)}."); + if (_cloningSystem.TryCloning(cloningPodUid, body.Value, (mindId, mind), cloningPod, scannerComp.CloningFailChanceMultiplier)) + { + _adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(uid)} started cloning {ToPrettyString(body.Value)}."); + _cloningSystem.AttemptCloning(cloningPodUid, cloningPod); + } } - public void RecheckConnections(EntityUid console, EntityUid? cloningPod, EntityUid? scanner, CloningConsoleComponent? consoleComp = null) + public bool RecheckConnections(EntityUid console, EntityUid? cloningPod, EntityUid? scanner, CloningConsoleComponent? consoleComp = null) { if (!Resolve(console, ref consoleComp)) - return; + return false; + var connected = true; if (scanner != null) { - Transform(scanner.Value).Coordinates.TryDistance(EntityManager, Transform((console)).Coordinates, out float scannerDistance); + Transform(scanner.Value).Coordinates.TryDistance(EntityManager, Transform(console).Coordinates, out float scannerDistance); consoleComp.GeneticScannerInRange = scannerDistance <= consoleComp.MaxDistance; + connected = false; } if (cloningPod != null) { - Transform(cloningPod.Value).Coordinates.TryDistance(EntityManager, Transform((console)).Coordinates, out float podDistance); + Transform(cloningPod.Value).Coordinates.TryDistance(EntityManager, Transform(console).Coordinates, out float podDistance); consoleComp.CloningPodInRange = podDistance <= consoleComp.MaxDistance; + connected = false; } UpdateUserInterface(console, consoleComp); + return connected; } private CloningConsoleBoundUserInterfaceState GetUserInterfaceState(CloningConsoleComponent consoleComponent) { @@ -206,25 +212,19 @@ private CloningConsoleBoundUserInterfaceState GetUserInterfaceState(CloningConso EntityUid? scanBody = scanner.BodyContainer.ContainedEntity; // GET STATE - if (scanBody == null || !HasComp(scanBody)) + if (scanBody == null + || !HasComp(scanBody)) clonerStatus = ClonerStatus.ScannerEmpty; else { scanBodyInfo = MetaData(scanBody.Value).EntityName; if (!_mobStateSystem.IsDead(scanBody.Value)) - { clonerStatus = ClonerStatus.ScannerOccupantAlive; - } - else - { - if (!_mindSystem.TryGetMind(scanBody.Value, out _, out var mind) || - mind.UserId == null || - !_playerManager.TryGetSessionById(mind.UserId.Value, out _)) - { - clonerStatus = ClonerStatus.NoMindDetected; - } - } + else if (!_mindSystem.TryGetMind(scanBody.Value, out _, out var mind) + || mind.UserId == null + || !_playerManager.TryGetSessionById(mind.UserId.Value, out _)) + clonerStatus = ClonerStatus.NoMindDetected; } } @@ -240,7 +240,7 @@ private CloningConsoleBoundUserInterfaceState GetUserInterfaceState(CloningConso EntityUid? cloneBody = clonePod.BodyContainer.ContainedEntity; clonerMindPresent = clonePod.Status == CloningPodStatus.Cloning; - if (HasComp(consoleComponent.CloningPod)) + if (clonePod.ActivelyCloning) { if (cloneBody != null) cloneBodyInfo = Identity.Name(cloneBody.Value, EntityManager); @@ -248,9 +248,7 @@ private CloningConsoleBoundUserInterfaceState GetUserInterfaceState(CloningConso } } else - { clonerStatus = ClonerStatus.NoClonerDetected; - } return new CloningConsoleBoundUserInterfaceState( scanBodyInfo, diff --git a/Content.Server/Cloning/CloningSystem.Utility.cs b/Content.Server/Cloning/CloningSystem.Utility.cs new file mode 100644 index 0000000000..d98e105e63 --- /dev/null +++ b/Content.Server/Cloning/CloningSystem.Utility.cs @@ -0,0 +1,361 @@ +using Content.Server.Cloning.Components; +using Content.Shared.Atmos; +using Content.Shared.CCVar; +using Content.Shared.Chemistry.Components; +using Content.Shared.Cloning; +using Content.Shared.Damage; +using Content.Shared.Emag.Components; +using Content.Shared.Humanoid; +using Content.Shared.Mind; +using Content.Shared.Mind.Components; +using Robust.Shared.Physics.Components; +using Robust.Shared.Random; +using Content.Shared.Speech; +using Content.Shared.Preferences; +using Content.Shared.Emoting; +using Content.Server.Speech.Components; +using Content.Server.StationEvents.Components; +using Content.Server.Ghost.Roles.Components; +using Robust.Shared.GameObjects.Components.Localization; +using Content.Shared.SSDIndicator; +using Content.Shared.Damage.ForceSay; +using Content.Shared.Chat; +using Content.Server.Body.Components; +using Content.Server.Language; +using Content.Shared.Abilities.Psionics; +using Content.Shared.Language.Components; +using Content.Shared.Nutrition.Components; +using Robust.Shared.Enums; + +namespace Content.Server.Cloning; + +public sealed partial class CloningSystem +{ + internal void TransferMindToClone(EntityUid mindId, MindComponent mind) + { + if (!ClonesWaitingForMind.TryGetValue(mind, out var entity) + || !EntityManager.EntityExists(entity) + || !TryComp(entity, out var mindComp) + || mindComp.Mind != null) + return; + + _mindSystem.TransferTo(mindId, entity, ghostCheckOverride: true, mind: mind); + _mindSystem.UnVisit(mindId, mind); + ClonesWaitingForMind.Remove(mind); + } + private void HandleMindAdded(EntityUid uid, BeingClonedComponent clonedComponent, MindAddedMessage message) + { + if (clonedComponent.Parent == EntityUid.Invalid + || !EntityManager.EntityExists(clonedComponent.Parent) + || !TryComp(clonedComponent.Parent, out var cloningPodComponent) + || uid != cloningPodComponent.BodyContainer.ContainedEntity) + { + EntityManager.RemoveComponent(uid); + return; + } + UpdateStatus(clonedComponent.Parent, CloningPodStatus.Cloning, cloningPodComponent); + } + + /// + /// Test if the body to be cloned has any conditions that would prevent cloning from taking place. + /// Or, if the body has a particular reason to make cloning more difficult. + /// + private bool CheckUncloneable(EntityUid uid, EntityUid bodyToClone, CloningPodComponent clonePod, out float cloningCostMultiplier) + { + var ev = new AttemptCloningEvent(uid, clonePod.DoMetempsychosis); + RaiseLocalEvent(bodyToClone, ref ev); + cloningCostMultiplier = ev.CloningCostMultiplier; + + if (ev.Cancelled && ev.CloningFailMessage is not null) + { + if (clonePod.ConnectedConsole is not null) + _chatSystem.TrySendInGameICMessage(clonePod.ConnectedConsole.Value, + Loc.GetString(ev.CloningFailMessage), + InGameICChatType.Speak, false); + return false; + } + + return true; + } + + /// + /// Checks the body's physics component and any previously obtained modifiers to determine biomass cost. + /// If there is insufficient biomass, the cloning cannot start. + /// + private bool CheckBiomassCost(EntityUid uid, PhysicsComponent physics, CloningPodComponent clonePod, float cloningCostMultiplier = 1) + { + if (clonePod.ConnectedConsole is null) + return false; + + var cloningCost = (int) Math.Round(physics.FixturesMass + * _config.GetCVar(CCVars.CloningBiomassCostMultiplier) + * clonePod.BiomassCostMultiplier + * cloningCostMultiplier); + + if (_material.GetMaterialAmount(uid, clonePod.RequiredMaterial) < cloningCost) + { + _chatSystem.TrySendInGameICMessage(clonePod.ConnectedConsole.Value, Loc.GetString("cloning-console-chat-error", ("units", cloningCost)), InGameICChatType.Speak, false); + return false; + } + + _material.TryChangeMaterialAmount(uid, clonePod.RequiredMaterial, -cloningCost); + clonePod.UsedBiomass = cloningCost; + + return true; + } + + /// + /// Tests the original body for genetic damage, while returning the cloning damage for later damage. + /// The body's cellular damage is also used as a potential failure state, giving a chance for the cloning to fail immediately. + /// + private bool CheckGeneticDamage(EntityUid uid, EntityUid bodyToClone, CloningPodComponent clonePod, out float geneticDamage, float failChanceModifier = 1) + { + geneticDamage = 0; + if (clonePod.DoMetempsychosis) + return false; + + if (TryComp(bodyToClone, out var damageable) + && damageable.Damage.DamageDict.TryGetValue("Cellular", out var cellularDmg) + && clonePod.ConnectedConsole is not null) + { + geneticDamage += (float) cellularDmg; + var chance = Math.Clamp((float) (cellularDmg / 100), 0, 1); + chance *= failChanceModifier; + + if (cellularDmg > 0) + _chatSystem.TrySendInGameICMessage(clonePod.ConnectedConsole.Value, Loc.GetString("cloning-console-cellular-warning", ("percent", Math.Round(100 - chance * 100))), InGameICChatType.Speak, false); + + if (_random.Prob(chance)) + { + CauseCloningFail(uid, clonePod); + return true; + } + } + return false; + } + + /// + /// When this condition is called, it sets the cloning pod to its fail condition. + /// Such that when the cloning timer ends, the body that would be created, is turned into clone soup. + /// + private void CauseCloningFail(EntityUid uid, CloningPodComponent component) + { + UpdateStatus(uid, CloningPodStatus.Gore, component); + component.FailedClone = true; + component.ActivelyCloning = true; + } + + /// + /// This is the success condition for cloning. At the end of the timer, if nothing interrupted it, this function is called to finish the cloning by dispensing the body. + /// + private void Eject(EntityUid uid, CloningPodComponent? clonePod) + { + if (!Resolve(uid, ref clonePod) + || clonePod.BodyContainer.ContainedEntity is null) + return; + + var entity = clonePod.BodyContainer.ContainedEntity.Value; + EntityManager.RemoveComponent(entity); + _containerSystem.Remove(entity, clonePod.BodyContainer); + clonePod.CloningProgress = 0f; + clonePod.UsedBiomass = 0; + UpdateStatus(uid, CloningPodStatus.Idle, clonePod); + clonePod.ActivelyCloning = false; + } + + /// + /// And now we turn it over to Chef Pod to make soup! + /// + private void EndFailedCloning(EntityUid uid, CloningPodComponent clonePod) + { + if (clonePod.BodyContainer.ContainedEntity is not null) + { + var entity = clonePod.BodyContainer.ContainedEntity.Value; + if (TryComp(entity, out var physics) + && TryComp(entity, out var bloodstream)) + MakeAHugeMess(uid, physics, bloodstream); + else MakeAHugeMess(uid); + + QueueDel(entity); + } + else MakeAHugeMess(uid); + + clonePod.FailedClone = false; + clonePod.CloningProgress = 0f; + UpdateStatus(uid, CloningPodStatus.Idle, clonePod); + if (HasComp(uid)) + { + _audio.PlayPvs(clonePod.ScreamSound, uid); + Spawn(clonePod.MobSpawnId, Transform(uid).Coordinates); + } + + if (!HasComp(uid)) + _material.SpawnMultipleFromMaterial(_random.Next(1, (int) (clonePod.UsedBiomass / 2.5)), clonePod.RequiredMaterial, Transform(uid).Coordinates); + + clonePod.UsedBiomass = 0; + clonePod.ActivelyCloning = false; + } + + /// + /// The body coming out of the machine isn't guaranteed to even be a Humanoid. + /// This function makes sure the body is "Human Playable", with no funny business. + /// + private void CleanupCloneComponents(EntityUid uid, EntityUid bodyToClone, bool forceOldProfile, bool doMetempsychosis) + { + if (forceOldProfile + && TryComp(bodyToClone, out var psionic)) + { + var newPsionic = _serialization.CreateCopy(psionic, null, false, true); + AddComp(uid, newPsionic, true); + } + + if (TryComp(bodyToClone, out var oldKnowLangs)) + { + var newKnowLangs = _serialization.CreateCopy(oldKnowLangs, null, false, true); + AddComp(uid, newKnowLangs, true); + } + + + if (TryComp(bodyToClone, out var oldSpeakLangs)) + { + var newSpeakLangs = _serialization.CreateCopy(oldSpeakLangs, null, false, true); + AddComp(uid, newSpeakLangs, true); + } + + if (doMetempsychosis) + EnsureComp(uid); + + EnsureComp(uid); + EnsureComp(uid); + EnsureComp(uid); + EnsureComp(uid); + EnsureComp(uid); + RemComp(uid); + RemComp(uid); + RemComp(uid); + RemComp(uid); + _tag.AddTag(uid, "DoorBumpOpener"); + } + + /// + /// When failing to clone, much of the failed body is dissolved into a slurry of Ammonia and Blood, which spills from the machine. + /// + /// + /// WOE BEFALLS WHOEVER FAILS TO CLONE A LAMIA + /// + private void MakeAHugeMess(EntityUid uid, PhysicsComponent? physics = null, BloodstreamComponent? blood = null) + { + var tileMix = _atmosphereSystem.GetTileMixture(Transform(uid).GridUid, null, _transformSystem.GetGridTilePositionOrDefault((uid, Transform(uid))), true); + Solution bloodSolution = new(); + + tileMix?.AdjustMoles(Gas.Ammonia, 0.5f + * ((physics is not null) + ? physics.Mass + : 71)); + + bloodSolution.AddReagent("Blood", 0.8f + * ((blood is not null) + ? blood.BloodMaxVolume + : 300)); + + _puddleSystem.TrySpillAt(uid, bloodSolution, out _); + } + + /// + /// Modify the clone's hunger and thirst values by an amount set in the cloningPod. + /// + private void UpdateHungerAndThirst(EntityUid uid, CloningPodComponent cloningPod) + { + if (cloningPod.HungerAdjustment != 0 + && TryComp(uid, out var hungerComponent)) + _hunger.SetHunger(uid, cloningPod.HungerAdjustment, hungerComponent); + + if (cloningPod.ThirstAdjustment != 0 + && TryComp(uid, out var thirstComponent)) + _thirst.SetThirst(uid, thirstComponent, cloningPod.ThirstAdjustment); + + if (cloningPod.DrunkTimer != 0) + _drunk.TryApplyDrunkenness(uid, cloningPod.DrunkTimer); + } + + /// + /// Updates the HumanoidAppearanceComponent of the clone. + /// If a species swap is occuring, this updates all relevant information as per server config. + /// + private void UpdateCloneAppearance( + EntityUid mob, + HumanoidCharacterProfile pref, + HumanoidAppearanceComponent humanoid, + List sexes, + Gender oldGender, + bool switchingSpecies, + bool forceOldProfile, + out Gender gender) + { + gender = oldGender; + if (!TryComp(mob, out var newHumanoid)) + return; + + if (switchingSpecies && !forceOldProfile) + { + var flavorText = _serialization.CreateCopy(pref.FlavorText, null, false, true); + var oldName = _serialization.CreateCopy(pref.Name, null, false, true); + + pref = HumanoidCharacterProfile.RandomWithSpecies(newHumanoid.Species); + + if (sexes.Contains(humanoid.Sex) + && _config.GetCVar(CCVars.CloningPreserveSex)) + pref = pref.WithSex(humanoid.Sex); + + if (_config.GetCVar(CCVars.CloningPreserveGender)) + pref = pref.WithGender(humanoid.Gender); + else gender = humanoid.Gender; + + if (_config.GetCVar(CCVars.CloningPreserveAge)) + pref = pref.WithAge(humanoid.Age); + + if (_config.GetCVar(CCVars.CloningPreserveHeight)) + pref = pref.WithHeight(humanoid.Height); + + if (_config.GetCVar(CCVars.CloningPreserveWidth)) + pref = pref.WithWidth(humanoid.Width); + + if (_config.GetCVar(CCVars.CloningPreserveName)) + pref = pref.WithName(oldName); + + if (_config.GetCVar(CCVars.CloningPreserveFlavorText)) + pref = pref.WithFlavorText(flavorText); + + _humanoidSystem.LoadProfile(mob, pref); + } + } + + /// + /// Optionally makes sure that pronoun preferences are preserved by the clone. + /// Although handled here, the swap (if it occurs) happens during UpdateCloneAppearance. + /// + /// + /// + private void UpdateGrammar(EntityUid mob, Gender gender) + { + var grammar = EnsureComp(mob); + grammar.ProperNoun = true; + grammar.Gender = gender; + Dirty(mob, grammar); + } + + /// + /// Optionally puts the clone in crit with high Cellular damage. + /// Medbay should use Cryogenics to "Finish" clones. Doxarubixadone is perfect for this. + /// + private void UpdateCloneDamage(EntityUid mob, CloningPodComponent clonePodComp, float geneticDamage) + { + if (!clonePodComp.DoGeneticDamage + || !HasComp(mob) + || !_thresholds.TryGetThresholdForState(mob, Shared.Mobs.MobState.Critical, out var threshold)) + return; + DamageSpecifier damage = new(); + damage.DamageDict.Add("Cellular", (int) threshold + 1 + geneticDamage); + _damageable.TryChangeDamage(mob, damage, true); + } +} diff --git a/Content.Server/Cloning/CloningSystem.cs b/Content.Server/Cloning/CloningSystem.cs index 92e658591a..72104bc381 100644 --- a/Content.Server/Cloning/CloningSystem.cs +++ b/Content.Server/Cloning/CloningSystem.cs @@ -1,6 +1,7 @@ using Content.Server.Atmos.EntitySystems; using Content.Server.Chat.Systems; using Content.Server.Cloning.Components; +using Content.Server.Construction; using Content.Server.DeviceLinking.Systems; using Content.Server.EUI; using Content.Server.Fluids.EntitySystems; @@ -9,13 +10,13 @@ using Content.Server.Materials; using Content.Server.Popups; using Content.Server.Power.EntitySystems; +using Content.Server.Traits.Assorted; using Content.Shared.Atmos; using Content.Shared.CCVar; using Content.Shared.Chemistry.Components; using Content.Shared.Cloning; using Content.Shared.Damage; using Content.Shared.DeviceLinking.Events; -using Content.Shared.Emag.Components; using Content.Shared.Emag.Systems; using Content.Shared.Examine; using Content.Shared.GameTicking; @@ -23,6 +24,7 @@ using Content.Shared.Mind; using Content.Shared.Mind.Components; using Content.Shared.Mobs.Systems; +using Content.Shared.Random; using Content.Shared.Roles.Jobs; using Robust.Server.Containers; using Robust.Server.GameObjects; @@ -33,430 +35,357 @@ using Robust.Shared.Physics.Components; using Robust.Shared.Prototypes; using Robust.Shared.Random; -using Content.Server.Traits.Assorted; //Nyano - Summary: allows the potential psionic ability to be written to the character. -using Content.Server.Psionics; //DeltaV needed for Psionic Systems -using Content.Shared.Speech; //DeltaV Start Metem Usings using Content.Shared.Tag; using Content.Shared.Preferences; -using Content.Shared.Emoting; -using Content.Server.Speech.Components; -using Content.Server.StationEvents.Components; -using Content.Server.Ghost.Roles.Components; -using Content.Server.Nyanotrasen.Cloning; using Content.Shared.Humanoid.Prototypes; -using Robust.Shared.GameObjects.Components.Localization; //DeltaV End Metem Usings -using Content.Server.EntityList; -using Content.Shared.SSDIndicator; -using Content.Shared.Damage.ForceSay; -using Content.Server.Polymorph.Components; -using Content.Shared.Chat; - -namespace Content.Server.Cloning +using Content.Shared.Random.Helpers; +using Content.Shared.Contests; +using Robust.Shared.Serialization.Manager; +using Robust.Shared.Utility; +using Timer = Robust.Shared.Timing.Timer; +using Content.Server.Power.Components; +using Content.Shared.Drunk; +using Content.Shared.Nutrition.EntitySystems; + +namespace Content.Server.Cloning; + +public sealed partial class CloningSystem : EntitySystem { - public sealed class CloningSystem : EntitySystem + [Dependency] private readonly DeviceLinkSystem _signalSystem = default!; + [Dependency] private readonly IPlayerManager _playerManager = null!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly EuiManager _euiManager = null!; + [Dependency] private readonly CloningConsoleSystem _cloningConsoleSystem = default!; + [Dependency] private readonly HumanoidAppearanceSystem _humanoidSystem = default!; + [Dependency] private readonly ContainerSystem _containerSystem = default!; + [Dependency] private readonly MobStateSystem _mobStateSystem = default!; + [Dependency] private readonly PowerReceiverSystem _powerReceiverSystem = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; + [Dependency] private readonly TransformSystem _transformSystem = default!; + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly PuddleSystem _puddleSystem = default!; + [Dependency] private readonly ChatSystem _chatSystem = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly IConfigurationManager _config = default!; + [Dependency] private readonly MaterialStorageSystem _material = default!; + [Dependency] private readonly PopupSystem _popupSystem = default!; + [Dependency] private readonly SharedMindSystem _mindSystem = default!; + [Dependency] private readonly MetaDataSystem _metaSystem = default!; + [Dependency] private readonly SharedJobSystem _jobs = default!; + [Dependency] private readonly TagSystem _tag = default!; + [Dependency] private readonly ContestsSystem _contests = default!; + [Dependency] private readonly ISerializationManager _serialization = default!; + [Dependency] private readonly DamageableSystem _damageable = default!; + [Dependency] private readonly HungerSystem _hunger = default!; + [Dependency] private readonly ThirstSystem _thirst = default!; + [Dependency] private readonly SharedDrunkSystem _drunk = default!; + [Dependency] private readonly MobThresholdSystem _thresholds = default!; + public readonly Dictionary ClonesWaitingForMind = new(); + + public override void Initialize() { - [Dependency] private readonly DeviceLinkSystem _signalSystem = default!; - [Dependency] private readonly IPlayerManager _playerManager = null!; - [Dependency] private readonly IPrototypeManager _prototype = default!; - [Dependency] private readonly EuiManager _euiManager = null!; - [Dependency] private readonly CloningConsoleSystem _cloningConsoleSystem = default!; - [Dependency] private readonly HumanoidAppearanceSystem _humanoidSystem = default!; - [Dependency] private readonly ContainerSystem _containerSystem = default!; - [Dependency] private readonly MobStateSystem _mobStateSystem = default!; - [Dependency] private readonly PowerReceiverSystem _powerReceiverSystem = default!; - [Dependency] private readonly IRobustRandom _robustRandom = default!; - [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; - [Dependency] private readonly TransformSystem _transformSystem = default!; - [Dependency] private readonly SharedAppearanceSystem _appearance = default!; - [Dependency] private readonly PuddleSystem _puddleSystem = default!; - [Dependency] private readonly ChatSystem _chatSystem = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly IConfigurationManager _configManager = default!; - [Dependency] private readonly MaterialStorageSystem _material = default!; - [Dependency] private readonly PopupSystem _popupSystem = default!; - [Dependency] private readonly SharedMindSystem _mindSystem = default!; - [Dependency] private readonly MetaDataSystem _metaSystem = default!; - [Dependency] private readonly SharedJobSystem _jobs = default!; - [Dependency] private readonly MetempsychoticMachineSystem _metem = default!; //DeltaV - [Dependency] private readonly TagSystem _tag = default!; //DeltaV - - public readonly Dictionary ClonesWaitingForMind = new(); - public const float EasyModeCloningCost = 0.7f; - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnComponentInit); - SubscribeLocalEvent(Reset); - SubscribeLocalEvent(HandleMindAdded); - SubscribeLocalEvent(OnPortDisconnected); - SubscribeLocalEvent(OnAnchor); - SubscribeLocalEvent(OnExamined); - SubscribeLocalEvent(OnEmagged); - } - - private void OnComponentInit(EntityUid uid, CloningPodComponent clonePod, ComponentInit args) - { - clonePod.BodyContainer = _containerSystem.EnsureContainer(uid, "clonepod-bodyContainer"); - _signalSystem.EnsureSinkPorts(uid, CloningPodComponent.PodPort); - } - - internal void TransferMindToClone(EntityUid mindId, MindComponent mind) - { - if (!ClonesWaitingForMind.TryGetValue(mind, out var entity) || - !EntityManager.EntityExists(entity) || - !TryComp(entity, out var mindComp) || - mindComp.Mind != null) - return; - - _mindSystem.TransferTo(mindId, entity, ghostCheckOverride: true, mind: mind); - _mindSystem.UnVisit(mindId, mind); - ClonesWaitingForMind.Remove(mind); - } + base.Initialize(); + + SubscribeLocalEvent(OnComponentInit); + SubscribeLocalEvent(Reset); + SubscribeLocalEvent(HandleMindAdded); + SubscribeLocalEvent(OnPortDisconnected); + SubscribeLocalEvent(OnAnchor); + SubscribeLocalEvent(OnExamined); + SubscribeLocalEvent(OnEmagged); + SubscribeLocalEvent(OnPowerChanged); + SubscribeLocalEvent(OnPartsRefreshed); + SubscribeLocalEvent(OnUpgradeExamine); + } + private void OnPartsRefreshed(EntityUid uid, CloningPodComponent component, RefreshPartsEvent args) + { + var materialRating = args.PartRatings[component.MachinePartMaterialUse]; + var speedRating = args.PartRatings[component.MachinePartCloningSpeed]; - private void HandleMindAdded(EntityUid uid, BeingClonedComponent clonedComponent, MindAddedMessage message) - { - if (clonedComponent.Parent == EntityUid.Invalid || - !EntityManager.EntityExists(clonedComponent.Parent) || - !TryComp(clonedComponent.Parent, out var cloningPodComponent) || - uid != cloningPodComponent.BodyContainer.ContainedEntity) - { - EntityManager.RemoveComponent(uid); - return; - } - UpdateStatus(clonedComponent.Parent, CloningPodStatus.Cloning, cloningPodComponent); - } + component.BiomassCostMultiplier = MathF.Pow(component.PartRatingMaterialMultiplier, materialRating - 1); + component.CloningTime = component.CloningTime * MathF.Pow(component.PartRatingSpeedMultiplier, speedRating - 1); + } - private void OnPortDisconnected(EntityUid uid, CloningPodComponent pod, PortDisconnectedEvent args) - { - pod.ConnectedConsole = null; - } + private void OnUpgradeExamine(EntityUid uid, CloningPodComponent component, UpgradeExamineEvent args) + { + args.AddPercentageUpgrade("cloning-pod-component-upgrade-speed", component.CloningTime / component.CloningTime); + args.AddPercentageUpgrade("cloning-pod-component-upgrade-biomass-requirement", component.BiomassCostMultiplier); + } + private void OnPortDisconnected(EntityUid uid, CloningPodComponent pod, PortDisconnectedEvent args) + { + pod.ConnectedConsole = null; + } - private void OnAnchor(EntityUid uid, CloningPodComponent component, ref AnchorStateChangedEvent args) - { - if (component.ConnectedConsole == null || !TryComp(component.ConnectedConsole, out var console)) - return; + private void OnAnchor(EntityUid uid, CloningPodComponent component, ref AnchorStateChangedEvent args) + { + if (component.ActivelyCloning) + CauseCloningFail(uid, component); - if (args.Anchored) - { - _cloningConsoleSystem.RecheckConnections(component.ConnectedConsole.Value, uid, console.GeneticScanner, console); - return; - } - _cloningConsoleSystem.UpdateUserInterface(component.ConnectedConsole.Value, console); - } + if (component.ConnectedConsole == null + || !TryComp(component.ConnectedConsole, out var console) + || !args.Anchored + || !_cloningConsoleSystem.RecheckConnections(component.ConnectedConsole.Value, uid, console.GeneticScanner, console)) + return; - private void OnExamined(EntityUid uid, CloningPodComponent component, ExaminedEvent args) - { - if (!args.IsInDetailsRange || !_powerReceiverSystem.IsPowered(uid)) - return; + _cloningConsoleSystem.UpdateUserInterface(component.ConnectedConsole.Value, console); + } - args.PushMarkup(Loc.GetString("cloning-pod-biomass", ("number", _material.GetMaterialAmount(uid, component.RequiredMaterial)))); - } - // Nyano: Adds float karmaBonus - public bool TryCloning(EntityUid uid, EntityUid bodyToClone, Entity mindEnt, CloningPodComponent? clonePod, float failChanceModifier = 1, float karmaBonus = 0.25f) - { - if (!Resolve(uid, ref clonePod)) - return false; + private void OnExamined(EntityUid uid, CloningPodComponent component, ExaminedEvent args) + { + if (!args.IsInDetailsRange + || !_powerReceiverSystem.IsPowered(uid)) + return; - if (HasComp(uid)) - return false; + args.PushMarkup(Loc.GetString("cloning-pod-biomass", ("number", _material.GetMaterialAmount(uid, component.RequiredMaterial)))); + } + private void OnComponentInit(EntityUid uid, CloningPodComponent clonePod, ComponentInit args) + { + clonePod.BodyContainer = _containerSystem.EnsureContainer(uid, "clonepod-bodyContainer"); + _signalSystem.EnsureSinkPorts(uid, CloningPodComponent.PodPort); + } - var mind = mindEnt.Comp; - if (ClonesWaitingForMind.TryGetValue(mind, out var clone)) - { - if (EntityManager.EntityExists(clone) && - !_mobStateSystem.IsDead(clone) && - TryComp(clone, out var cloneMindComp) && - (cloneMindComp.Mind == null || cloneMindComp.Mind == mindEnt)) - return false; // Mind already has clone + private void OnPowerChanged(EntityUid uid, CloningPodComponent component, PowerChangedEvent args) + { + if (!args.Powered && component.ActivelyCloning) + CauseCloningFail(uid, component); + } - ClonesWaitingForMind.Remove(mind); - } + /// + /// On emag, spawns a failed clone when cloning process fails which attacks nearby crew. + /// + private void OnEmagged(EntityUid uid, CloningPodComponent clonePod, ref GotEmaggedEvent args) + { + if (!this.IsPowered(uid, EntityManager)) + return; - if (mind.OwnedEntity != null && !_mobStateSystem.IsDead(mind.OwnedEntity.Value)) - return false; // Body controlled by mind is not dead + if (clonePod.ActivelyCloning) + CauseCloningFail(uid, clonePod); - // Yes, we still need to track down the client because we need to open the Eui - if (mind.UserId == null || !_playerManager.TryGetSessionById(mind.UserId.Value, out var client)) - return false; // If we can't track down the client, we can't offer transfer. That'd be quite bad. + _audio.PlayPvs(clonePod.SparkSound, uid); + _popupSystem.PopupEntity(Loc.GetString("cloning-pod-component-upgrade-emag-requirement"), uid); + args.Handled = true; + } - if (!TryComp(bodyToClone, out var humanoid)) - return false; // whatever body was to be cloned, was not a humanoid + private void Reset(RoundRestartCleanupEvent ev) + { + ClonesWaitingForMind.Clear(); + } - // Begin Nyano-code: allow paradox anomalies to be cloned. - var pref = humanoid.LastProfileLoaded; + /// + /// The master function behind Cloning, called by the cloning console via button press to start the cloning process. + /// + public bool TryCloning(EntityUid uid, EntityUid bodyToClone, Entity mindEnt, CloningPodComponent clonePod, float failChanceModifier = 1) + { + if (!_mobStateSystem.IsDead(bodyToClone) + || clonePod.ActivelyCloning + || clonePod.ConnectedConsole == null + || !CheckUncloneable(uid, bodyToClone, clonePod, out var cloningCostMultiplier) + || !TryComp(bodyToClone, out var humanoid) + || !TryComp(bodyToClone, out var physics)) + return false; + + var mind = mindEnt.Comp; + if (ClonesWaitingForMind.TryGetValue(mind, out var clone)) + { + if (EntityManager.EntityExists(clone) && + !_mobStateSystem.IsDead(clone) && + TryComp(clone, out var cloneMindComp) && + (cloneMindComp.Mind == null || cloneMindComp.Mind == mindEnt)) + return false; // Mind already has clone - if (pref == null) - return false; - // End Nyano-code - if (!_prototype.TryIndex(humanoid.Species, out var speciesPrototype)) - return false; + ClonesWaitingForMind.Remove(mind); + } - if (!TryComp(bodyToClone, out var physics)) - return false; + if (mind.OwnedEntity != null && !_mobStateSystem.IsDead(mind.OwnedEntity.Value) + || mind.UserId == null + || !_playerManager.TryGetSessionById(mind.UserId.Value, out var client) + || !CheckBiomassCost(uid, physics, clonePod, cloningCostMultiplier)) + return false; - var cloningCost = (int) Math.Round(physics.FixturesMass); + // Special handling for humanoid data related to metempsychosis. This function is needed for Paradox Anomaly code to play nice with reincarnated people + var pref = humanoid.LastProfileLoaded; + if (pref == null + || !_prototypeManager.TryIndex(humanoid.Species, out var speciesPrototype)) + return false; - if (_configManager.GetCVar(CCVars.BiomassEasyMode)) - cloningCost = (int) Math.Round(cloningCost * EasyModeCloningCost); + // Yes, this can return true without making a body. If it returns true, we're making clone soup instead. + if (CheckGeneticDamage(uid, bodyToClone, clonePod, out var geneticDamage, failChanceModifier)) + return true; - // Check if they have the uncloneable trait - if (TryComp(bodyToClone, out _)) - { - if (clonePod.ConnectedConsole != null) - { - _chatSystem.TrySendInGameICMessage(clonePod.ConnectedConsole.Value, - Loc.GetString("cloning-console-uncloneable-trait-error"), - InGameICChatType.Speak, false); - } + var mob = FetchAndSpawnMob(uid, clonePod, pref, speciesPrototype, humanoid, bodyToClone, geneticDamage); - return false; - } + var ev = new CloningEvent(bodyToClone, mob); + RaiseLocalEvent(bodyToClone, ref ev); - // biomass checks - var biomassAmount = _material.GetMaterialAmount(uid, clonePod.RequiredMaterial); + if (!ev.NameHandled) + _metaSystem.SetEntityName(mob, MetaData(bodyToClone).EntityName); - if (biomassAmount < cloningCost) - { - if (clonePod.ConnectedConsole != null) - _chatSystem.TrySendInGameICMessage(clonePod.ConnectedConsole.Value, Loc.GetString("cloning-console-chat-error", ("units", cloningCost)), InGameICChatType.Speak, false); - return false; - } + var cloneMindReturn = EntityManager.AddComponent(mob); + cloneMindReturn.Mind = mindEnt.Comp; + cloneMindReturn.Parent = uid; + _containerSystem.Insert(mob, clonePod.BodyContainer); + ClonesWaitingForMind.Add(mindEnt.Comp, mob); + UpdateStatus(uid, CloningPodStatus.NoMind, clonePod); + _euiManager.OpenEui(new AcceptCloningEui(mindEnt, mindEnt.Comp, this), client); - _material.TryChangeMaterialAmount(uid, clonePod.RequiredMaterial, -cloningCost); - clonePod.UsedBiomass = cloningCost; - // end of biomass checks + clonePod.ActivelyCloning = true; - // genetic damage checks - if (TryComp(bodyToClone, out var damageable) && - damageable.Damage.DamageDict.TryGetValue("Cellular", out var cellularDmg)) - { - var chance = Math.Clamp((float) (cellularDmg / 100), 0, 1); - chance *= failChanceModifier; + if (_jobs.MindTryGetJob(mindEnt, out _, out var prototype)) + foreach (var special in prototype.Special) + if (special is AddComponentSpecial) + special.AfterEquip(mob); - if (cellularDmg > 0 && clonePod.ConnectedConsole != null) - _chatSystem.TrySendInGameICMessage(clonePod.ConnectedConsole.Value, Loc.GetString("cloning-console-cellular-warning", ("percent", Math.Round(100 - chance * 100))), InGameICChatType.Speak, false); + return true; + } - if (_robustRandom.Prob(chance)) - { - UpdateStatus(uid, CloningPodStatus.Gore, clonePod); - clonePod.FailedClone = true; - AddComp(uid); - return true; - } - // End Nyano-code. - } - // end of genetic damage checks + /// + /// Begins the cloning timer, which at the end can either produce clone soup, or a functional body, depending on if anything interrupts the procedure. + /// + public void AttemptCloning(EntityUid cloningPod, CloningPodComponent cloningPodComponent) + { + if (cloningPodComponent.BodyContainer.ContainedEntity is { Valid: true } entity + && TryComp(entity, out var physics) + && physics.Mass > 71) + Timer.Spawn(TimeSpan.FromSeconds(cloningPodComponent.CloningTime * _contests.MassContest(entity, physics, true)), () => EndCloning(cloningPod, cloningPodComponent)); - var mob = FetchAndSpawnMob(clonePod, pref, speciesPrototype, humanoid, bodyToClone, karmaBonus); //DeltaV Replaces CloneAppearance with Metem/Clone via FetchAndSpawnMob + Timer.Spawn(TimeSpan.FromSeconds(cloningPodComponent.CloningTime), () => EndCloning(cloningPod, cloningPodComponent)); + } - ///Nyano - Summary: adds the potential psionic trait to the reanimated mob. - EnsureComp(mob); + /// + /// Ding, your body is ready. Time to find out if it's soup or solid. + /// + public void EndCloning(EntityUid cloningPod, CloningPodComponent cloningPodComponent) + { + if (!cloningPodComponent.ActivelyCloning + || !_powerReceiverSystem.IsPowered(cloningPod) + || cloningPodComponent.BodyContainer.ContainedEntity == null + || cloningPodComponent.FailedClone) + EndFailedCloning(cloningPod, cloningPodComponent); //Surprise, it's soup! - var ev = new CloningEvent(bodyToClone, mob); - RaiseLocalEvent(bodyToClone, ref ev); + Eject(cloningPod, cloningPodComponent); //Hey look, a body! + } - if (!ev.NameHandled) - _metaSystem.SetEntityName(mob, MetaData(bodyToClone).EntityName); + public void UpdateStatus(EntityUid podUid, CloningPodStatus status, CloningPodComponent cloningPod) + { + cloningPod.Status = status; + _appearance.SetData(podUid, CloningPodVisuals.Status, cloningPod.Status); + } - var cloneMindReturn = EntityManager.AddComponent(mob); - cloneMindReturn.Mind = mind; - cloneMindReturn.Parent = uid; - _containerSystem.Insert(mob, clonePod.BodyContainer); - ClonesWaitingForMind.Add(mind, mob); - UpdateStatus(uid, CloningPodStatus.NoMind, clonePod); - _euiManager.OpenEui(new AcceptCloningEui(mindEnt, mind, this), client); + /// + /// This function handles the Clone vs. Metem logic, as well as creation of the new body. + /// + private EntityUid FetchAndSpawnMob( + EntityUid clonePod, + CloningPodComponent clonePodComp, + HumanoidCharacterProfile pref, + SpeciesPrototype speciesPrototype, + HumanoidAppearanceComponent humanoid, + EntityUid bodyToClone, + float geneticDamage + ) + { + List sexes = new(); + bool switchingSpecies = false; + var toSpawn = speciesPrototype.Prototype; + var forceOldProfile = true; + var oldKarma = 0; + var oldGender = humanoid.Gender; + if (TryComp(bodyToClone, out var oldKarmaComp)) + oldKarma += oldKarmaComp.Score; + + if (clonePodComp.DoMetempsychosis) + { + toSpawn = GetSpawnEntity(bodyToClone, clonePodComp, speciesPrototype, oldKarma, out var newSpecies, out var changeProfile); + forceOldProfile = !changeProfile; + oldKarma++; - AddComp(uid); + if (changeProfile) + geneticDamage = 0; - // TODO: Ideally, components like this should be components on the mind entity so this isn't necessary. - // Add on special job components to the mob. - if (_jobs.MindTryGetJob(mindEnt, out _, out var prototype)) + if (newSpecies != null) { - foreach (var special in prototype.Special) - { - if (special is AddComponentSpecial) - special.AfterEquip(mob); - } - } - - return true; - } + sexes = newSpecies.Sexes; - public void UpdateStatus(EntityUid podUid, CloningPodStatus status, CloningPodComponent cloningPod) - { - cloningPod.Status = status; - _appearance.SetData(podUid, CloningPodVisuals.Status, cloningPod.Status); + if (speciesPrototype.ID != newSpecies.ID) + switchingSpecies = true; + } } + EntityUid mob = Spawn(toSpawn, _transformSystem.GetMapCoordinates(clonePod)); + EnsureComp(mob, out var newKarma); + newKarma.Score += oldKarma; - public override void Update(float frameTime) - { - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out var _, out var cloning)) - { - if (!_powerReceiverSystem.IsPowered(uid)) - continue; - - if (cloning.BodyContainer.ContainedEntity == null && !cloning.FailedClone) - continue; + UpdateCloneDamage(mob, clonePodComp, geneticDamage); + UpdateCloneAppearance(mob, pref, humanoid, sexes, oldGender, switchingSpecies, forceOldProfile, out var gender); + var ev = new CloningEvent(bodyToClone, mob); + RaiseLocalEvent(bodyToClone, ref ev); - cloning.CloningProgress += frameTime; - if (cloning.CloningProgress < cloning.CloningTime) - continue; + if (!ev.NameHandled) + _metaSystem.SetEntityName(mob, MetaData(bodyToClone).EntityName); - if (cloning.FailedClone) - EndFailedCloning(uid, cloning); - else - Eject(uid, cloning); - } - } - - /// - /// On emag, spawns a failed clone when cloning process fails which attacks nearby crew. - /// - private void OnEmagged(EntityUid uid, CloningPodComponent clonePod, ref GotEmaggedEvent args) - { - if (!this.IsPowered(uid, EntityManager)) - return; + UpdateGrammar(mob, gender); + CleanupCloneComponents(mob, bodyToClone, forceOldProfile, clonePodComp.DoMetempsychosis); + UpdateHungerAndThirst(mob, clonePodComp); - _audio.PlayPvs(clonePod.SparkSound, uid); - _popupSystem.PopupEntity(Loc.GetString("cloning-pod-component-upgrade-emag-requirement"), uid); - args.Handled = true; - } + return mob; + } - public void Eject(EntityUid uid, CloningPodComponent? clonePod) + public string GetSpawnEntity(EntityUid oldBody, CloningPodComponent component, SpeciesPrototype oldSpecies, int karma, out SpeciesPrototype? species, out bool changeProfile) + { + changeProfile = true; + species = oldSpecies; + if (!_prototypeManager.TryIndex(component.MetempsychoticHumanoidPool, out var humanoidPool) + || !_prototypeManager.TryIndex(humanoidPool.Pick(), out var speciesPrototype) + || !_prototypeManager.TryIndex(component.MetempsychoticNonHumanoidPool, out var nonHumanoidPool) + || !_prototypeManager.TryIndex(nonHumanoidPool.Pick(), out var entityPrototype)) { - if (!Resolve(uid, ref clonePod)) - return; - - if (clonePod.BodyContainer.ContainedEntity is not { Valid: true } entity || clonePod.CloningProgress < clonePod.CloningTime) - return; - - EntityManager.RemoveComponent(entity); - _containerSystem.Remove(entity, clonePod.BodyContainer); - clonePod.CloningProgress = 0f; - clonePod.UsedBiomass = 0; - UpdateStatus(uid, CloningPodStatus.Idle, clonePod); - RemCompDeferred(uid); + DebugTools.Assert("Could not index species for metempsychotic machine."); + changeProfile = false; + return oldSpecies.Prototype; } + var chance = (component.HumanoidBaseChance - karma * component.KarmaOffset) * _contests.MindContest(oldBody, true); - private void EndFailedCloning(EntityUid uid, CloningPodComponent clonePod) - { - clonePod.FailedClone = false; - clonePod.CloningProgress = 0f; - UpdateStatus(uid, CloningPodStatus.Idle, clonePod); - var transform = Transform(uid); - var indices = _transformSystem.GetGridTilePositionOrDefault((uid, transform)); - var tileMix = _atmosphereSystem.GetTileMixture(transform.GridUid, null, indices, true); - if (HasComp(uid)) - { - _audio.PlayPvs(clonePod.ScreamSound, uid); - Spawn(clonePod.MobSpawnId, transform.Coordinates); - } - - Solution bloodSolution = new(); - - var i = 0; - while (i < 1) - { - tileMix?.AdjustMoles(Gas.Ammonia, 6f); - bloodSolution.AddReagent("Blood", 50); - if (_robustRandom.Prob(0.2f)) - i++; - } - _puddleSystem.TrySpillAt(uid, bloodSolution, out _); - if (!HasComp(uid)) - { - _material.SpawnMultipleFromMaterial(_robustRandom.Next(1, (int) (clonePod.UsedBiomass / 2.5)), clonePod.RequiredMaterial, Transform(uid).Coordinates); - } + var ev = new ReincarnatingEvent(oldBody, chance); + RaiseLocalEvent(oldBody, ref ev); - clonePod.UsedBiomass = 0; - RemCompDeferred(uid); - } + chance = ev.OverrideChance + ? ev.ReincarnationChances + : chance * ev.ReincarnationChanceModifier; - /// - /// Start Nyano Code: Handles fetching the mob and any appearance stuff... - /// - private EntityUid FetchAndSpawnMob(CloningPodComponent clonePod, HumanoidCharacterProfile pref, SpeciesPrototype speciesPrototype, HumanoidAppearanceComponent humanoid, EntityUid bodyToClone, float karmaBonus) + switch (ev.ForcedType) { - List sexes = new(); - bool switchingSpecies = false; - bool applyKarma = false; - var toSpawn = speciesPrototype.Prototype; - TryComp(bodyToClone, out var oldKarma); - - if (TryComp(clonePod.Owner, out var metem)) - { - toSpawn = _metem.GetSpawnEntity(clonePod.Owner, karmaBonus, metem, speciesPrototype, out var newSpecies, oldKarma?.Score); - applyKarma = true; - - if (newSpecies != null) + case ForcedMetempsychosisType.None: + if (!ev.NeverTrulyClone + && chance > 1 + && _random.Prob(chance - 1)) { - sexes = newSpecies.Sexes; - - if (speciesPrototype.ID != newSpecies.ID) - switchingSpecies = true; - - speciesPrototype = newSpecies; + changeProfile = false; + return oldSpecies.Prototype; } - } - var mob = Spawn(toSpawn, Transform(clonePod.Owner).MapPosition); - if (TryComp(mob, out var newHumanoid)) - { - if (switchingSpecies || HasComp(bodyToClone)) + chance = Math.Clamp(chance, 0, 1); + if (_random.Prob(chance)) { - pref = HumanoidCharacterProfile.RandomWithSpecies(newHumanoid.Species); - if (sexes.Contains(humanoid.Sex)) - pref = pref.WithSex(humanoid.Sex); - - pref = pref.WithGender(humanoid.Gender); - pref = pref.WithAge(humanoid.Age); - + species = speciesPrototype; + return speciesPrototype.Prototype; } - _humanoidSystem.LoadProfile(mob, pref); - } - - if (applyKarma) - { - var karma = EnsureComp(mob); - karma.Score++; - if (oldKarma != null) - karma.Score += oldKarma.Score; - } - - var ev = new CloningEvent(bodyToClone, mob); - RaiseLocalEvent(bodyToClone, ref ev); - - if (!ev.NameHandled) - _metaSystem.SetEntityName(mob, MetaData(bodyToClone).EntityName); + species = null; + return entityPrototype.ID; - var grammar = EnsureComp(mob); - grammar.ProperNoun = true; - grammar.Gender = humanoid.Gender; - Dirty(grammar); + case ForcedMetempsychosisType.Clone: + changeProfile = false; + return oldSpecies.Prototype; - EnsureComp(mob); - EnsureComp(mob); - EnsureComp(mob); - EnsureComp(mob); - EnsureComp(mob); - EnsureComp(mob); - RemComp(mob); - RemComp(mob); - RemComp(mob); - RemComp(mob); + case ForcedMetempsychosisType.RandomHumanoid: + species = speciesPrototype; + return speciesPrototype.Prototype; - _tag.AddTag(mob, "DoorBumpOpener"); - - return mob; - } - //End Nyano Code - public void Reset(RoundRestartCleanupEvent ev) - { - ClonesWaitingForMind.Clear(); + case ForcedMetempsychosisType.RandomNonHumanoid: + species = null; + return entityPrototype.ID; } + changeProfile = false; + return oldSpecies.Prototype; } } diff --git a/Content.Server/Cloning/Components/ActiveCloningPodComponent.cs b/Content.Server/Cloning/Components/ActiveCloningPodComponent.cs deleted file mode 100644 index 11e0e36166..0000000000 --- a/Content.Server/Cloning/Components/ActiveCloningPodComponent.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Content.Server.Cloning.Components; - -/// -/// Shrimply a tracking component for pods that are cloning. -/// -[RegisterComponent] -public sealed partial class ActiveCloningPodComponent : Component -{ -} diff --git a/Content.Server/Nyanotrasen/Cloning/MetempsychosisKarmaComponent.cs b/Content.Server/Cloning/Components/MetempsychosisKarmaComponent.cs similarity index 80% rename from Content.Server/Nyanotrasen/Cloning/MetempsychosisKarmaComponent.cs rename to Content.Server/Cloning/Components/MetempsychosisKarmaComponent.cs index 246495cee0..5f7b7af1cd 100644 --- a/Content.Server/Nyanotrasen/Cloning/MetempsychosisKarmaComponent.cs +++ b/Content.Server/Cloning/Components/MetempsychosisKarmaComponent.cs @@ -1,4 +1,4 @@ -namespace Content.Server.Nyanotrasen.Cloning +namespace Content.Server.Cloning.Components { /// /// This tracks how many times you have already been cloned and lowers your chance of getting a humanoid each time. @@ -6,7 +6,7 @@ namespace Content.Server.Nyanotrasen.Cloning [RegisterComponent] public sealed partial class MetempsychosisKarmaComponent : Component { - [DataField("score")] + [DataField] public int Score = 0; } } diff --git a/Content.Server/Clothing/MagbootsSystem.cs b/Content.Server/Clothing/MagbootsSystem.cs index bc6880552f..f12558389e 100644 --- a/Content.Server/Clothing/MagbootsSystem.cs +++ b/Content.Server/Clothing/MagbootsSystem.cs @@ -1,7 +1,6 @@ using Content.Server.Atmos.Components; using Content.Shared.Alert; using Content.Shared.Clothing; -using Content.Shared.Inventory.Events; namespace Content.Server.Clothing; @@ -13,8 +12,8 @@ public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnGotEquipped); - SubscribeLocalEvent(OnGotUnequipped); + SubscribeLocalEvent(OnGotEquipped); + SubscribeLocalEvent(OnGotUnequipped); } protected override void UpdateMagbootEffects(EntityUid parent, EntityUid uid, bool state, MagbootsComponent? component) @@ -38,19 +37,13 @@ protected override void UpdateMagbootEffects(EntityUid parent, EntityUid uid, bo } } - private void OnGotUnequipped(EntityUid uid, MagbootsComponent component, GotUnequippedEvent args) + private void OnGotUnequipped(EntityUid uid, MagbootsComponent component, ref ClothingGotUnequippedEvent args) { - if (args.Slot == "shoes") - { - UpdateMagbootEffects(args.Equipee, uid, false, component); - } + UpdateMagbootEffects(args.Wearer, uid, false, component); } - private void OnGotEquipped(EntityUid uid, MagbootsComponent component, GotEquippedEvent args) + private void OnGotEquipped(EntityUid uid, MagbootsComponent component, ref ClothingGotEquippedEvent args) { - if (args.Slot == "shoes") - { - UpdateMagbootEffects(args.Equipee, uid, true, component); - } + UpdateMagbootEffects(args.Wearer, uid, true, component); } } diff --git a/Content.Server/Clothing/Systems/ChameleonClothingSystem.cs b/Content.Server/Clothing/Systems/ChameleonClothingSystem.cs index 6fbfd9f367..e20a6c3da9 100644 --- a/Content.Server/Clothing/Systems/ChameleonClothingSystem.cs +++ b/Content.Server/Clothing/Systems/ChameleonClothingSystem.cs @@ -64,7 +64,7 @@ private void UpdateUi(EntityUid uid, ChameleonClothingComponent? component = nul return; var state = new ChameleonBoundUserInterfaceState(component.Slot, component.Default); - _uiSystem.TrySetUiState(uid, ChameleonUiKey.Key, state); + _uiSystem.SetUiState(uid, ChameleonUiKey.Key, state); } /// diff --git a/Content.Server/Cocoon/CocoonerSystem.cs b/Content.Server/Cocoon/CocoonerSystem.cs new file mode 100644 index 0000000000..676eb9808b --- /dev/null +++ b/Content.Server/Cocoon/CocoonerSystem.cs @@ -0,0 +1,204 @@ +using Content.Shared.Cocoon; +using Content.Shared.IdentityManagement; +using Content.Shared.Verbs; +using Content.Shared.DoAfter; +using Content.Shared.Stunnable; +using Content.Shared.Eye.Blinding.Systems; +using Content.Shared.Containers.ItemSlots; +using Content.Shared.Damage; +using Content.Shared.Administration.Logs; +using Content.Shared.Database; +using Content.Shared.Humanoid; +using Content.Server.Popups; +using Content.Server.DoAfter; +using Content.Server.Speech.Components; +using Robust.Shared.Containers; +using Content.Shared.Mobs.Components; +using Content.Shared.Destructible; +using Robust.Shared.Random; +using Content.Shared.Nutrition.Components; +using Content.Shared.Storage; +using Robust.Shared.Utility; + +namespace Content.Server.Cocoon +{ + public sealed class CocooningSystem : EntitySystem + { + [Dependency] private readonly PopupSystem _popupSystem = default!; + [Dependency] private readonly DoAfterSystem _doAfter = default!; + [Dependency] private readonly ItemSlotsSystem _itemSlots = default!; + [Dependency] private readonly BlindableSystem _blindableSystem = default!; + [Dependency] private readonly DamageableSystem _damageableSystem = default!; + [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; + [Dependency] private readonly SharedDestructibleSystem _destructibleSystem = default!; + [Dependency] private readonly IRobustRandom _robustRandom = default!; + + private const string BodySlot = "body_slot"; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent>(AddVerbs); + SubscribeLocalEvent(OnCocEntInserted); + SubscribeLocalEvent(OnCocEntRemoved); + SubscribeLocalEvent(OnDamageChanged); + SubscribeLocalEvent(OnCocoonDoAfter); + SubscribeLocalEvent(OnUnCocoonDoAfter); + } + + private void AddVerbs(EntityUid uid, CocoonerComponent component, GetVerbsEvent args) + { + AddCocoonVerb(uid, component, args); + AddUnCocoonVerb(uid, component, args); + } + + private void AddCocoonVerb(EntityUid uid, CocoonerComponent component, GetVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract || !HasComp(args.Target)) + return; + + InnateVerb verb = new() + { + Act = () => + { + StartCocooning(uid, component, args.Target); + }, + Text = Loc.GetString("cocoon"), + Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/Actions/web.png")), + Priority = 2 + }; + args.Verbs.Add(verb); + } + + private void AddUnCocoonVerb(EntityUid uid, CocoonerComponent component, GetVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract || !HasComp(args.Target)) + return; + + InnateVerb verb = new() + { + Act = () => + { + StartUnCocooning(uid, component, args.Target); + }, + Text = Loc.GetString("uncocoon"), + Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/Actions/web.png")), + Priority = 2 + }; + args.Verbs.Add(verb); + } + + private void OnCocEntInserted(EntityUid uid, CocoonComponent component, EntInsertedIntoContainerMessage args) + { + component.Victim = args.Entity; + + if (TryComp(args.Entity, out var currentAccent)) + component.OldAccent = currentAccent.Accent; + + EnsureComp(args.Entity).Accent = "mumble"; + EnsureComp(args.Entity); + + _blindableSystem.UpdateIsBlind(args.Entity); + } + + private void OnCocEntRemoved(EntityUid uid, CocoonComponent component, EntRemovedFromContainerMessage args) + { + if (TryComp(args.Entity, out var replacement)) + if (component.OldAccent is not null) + replacement.Accent = component.OldAccent; + else + RemComp(args.Entity, replacement); + + + RemComp(args.Entity); + _blindableSystem.UpdateIsBlind(args.Entity); + } + + private void OnDamageChanged(EntityUid uid, CocoonComponent component, DamageChangedEvent args) + { + if (!args.DamageIncreased || args.DamageDelta == null || component.Victim == null) + return; + + var damage = args.DamageDelta * component.DamagePassthrough; + _damageableSystem.TryChangeDamage(component.Victim, damage); + } + + private void StartCocooning(EntityUid uid, CocoonerComponent component, EntityUid target) + { + _popupSystem.PopupEntity(Loc.GetString("cocoon-start-third-person", ("target", Identity.Entity(target, EntityManager)), ("spider", Identity.Entity(uid, EntityManager))), uid, + Shared.Popups.PopupType.MediumCaution); + + var delay = component.CocoonDelay; + + if (HasComp(target)) + delay *= component.CocoonKnockdownMultiplier; + + var args = new DoAfterArgs(EntityManager, uid, delay, new CocoonDoAfterEvent(), uid, target: target) + { + BreakOnUserMove = true, + BreakOnTargetMove = true, + }; + + _doAfter.TryStartDoAfter(args); + } + + private void StartUnCocooning(EntityUid uid, CocoonerComponent component, EntityUid target) + { + _popupSystem.PopupEntity(Loc.GetString("uncocoon-start-third-person", ("target", target), ("spider", Identity.Entity(uid, EntityManager))), uid, + Shared.Popups.PopupType.MediumCaution); + + var delay = component.CocoonDelay / 2; + + var args = new DoAfterArgs(EntityManager, uid, delay, new UnCocoonDoAfterEvent(), uid, target: target) + { + BreakOnUserMove = true, + BreakOnTargetMove = true, + }; + + _doAfter.TryStartDoAfter(args); + } + + private void OnCocoonDoAfter(EntityUid uid, CocoonerComponent component, CocoonDoAfterEvent args) + { + if (args.Handled || args.Cancelled || args.Args.Target == null) + return; + + var spawnProto = HasComp(args.Args.Target) ? "CocoonedHumanoid" : "CocoonSmall"; + Transform(args.Args.Target.Value).AttachToGridOrMap(); + var cocoon = Spawn(spawnProto, Transform(args.Args.Target.Value).Coordinates); + + if (!TryComp(cocoon, out var slots)) + return; + + _itemSlots.SetLock(cocoon, BodySlot, false, slots); + _itemSlots.TryInsert(cocoon, BodySlot, args.Args.Target.Value, args.Args.User); + _itemSlots.SetLock(cocoon, BodySlot, true, slots); + + var impact = (spawnProto == "CocoonedHumanoid") ? LogImpact.High : LogImpact.Medium; + + _adminLogger.Add(LogType.Action, impact, $"{ToPrettyString(args.Args.User):player} cocooned {ToPrettyString(args.Args.Target.Value):target}"); + args.Handled = true; + } + + private void OnUnCocoonDoAfter(EntityUid uid, CocoonerComponent component, UnCocoonDoAfterEvent args) + { + if (args.Handled || args.Cancelled || args.Args.Target == null) + return; + + if (TryComp(args.Args.Target.Value, out var butcher)) + { + var spawnEntities = EntitySpawnCollection.GetSpawns(butcher.SpawnedEntities, _robustRandom); + var coords = Transform(args.Args.Target.Value).MapPosition; + EntityUid popupEnt = default!; + foreach (var proto in spawnEntities) + popupEnt = Spawn(proto, coords.Offset(_robustRandom.NextVector2(0.25f))); + } + + _destructibleSystem.DestroyEntity(args.Args.Target.Value); + + _adminLogger.Add(LogType.Action, LogImpact.Low + , $"{ToPrettyString(args.Args.User):player} uncocooned {ToPrettyString(args.Args.Target.Value):target}"); + args.Handled = true; + } + } +} diff --git a/Content.Server/Communications/CommunicationsConsoleSystem.cs b/Content.Server/Communications/CommunicationsConsoleSystem.cs index b68a952105..0cdcb3a47f 100644 --- a/Content.Server/Communications/CommunicationsConsoleSystem.cs +++ b/Content.Server/Communications/CommunicationsConsoleSystem.cs @@ -86,8 +86,8 @@ public override void Update(float frameTime) comp.UIUpdateAccumulator -= UIUpdateInterval; - if (_uiSystem.TryGetUi(uid, CommunicationsConsoleUiKey.Key, out var ui) && ui.SubscribedSessions.Count > 0) - UpdateCommsConsoleInterface(uid, comp, ui); + if (_uiSystem.IsUiOpen(uid, CommunicationsConsoleUiKey.Key)) + UpdateCommsConsoleInterface(uid, comp); } base.Update(frameTime); @@ -140,11 +140,8 @@ public void UpdateCommsConsoleInterface() /// /// Updates the UI for a particular comms console. /// - public void UpdateCommsConsoleInterface(EntityUid uid, CommunicationsConsoleComponent comp, PlayerBoundUserInterface? ui = null) + public void UpdateCommsConsoleInterface(EntityUid uid, CommunicationsConsoleComponent comp) { - if (ui == null && !_uiSystem.TryGetUi(uid, CommunicationsConsoleUiKey.Key, out ui)) - return; - var stationUid = _stationSystem.GetOwningStation(uid); List? levels = null; string currentLevel = default!; @@ -172,7 +169,7 @@ public void UpdateCommsConsoleInterface(EntityUid uid, CommunicationsConsoleComp } } - _uiSystem.SetUiState(ui, new CommunicationsConsoleInterfaceState( + _uiSystem.SetUiState(uid, CommunicationsConsoleUiKey.Key, new CommunicationsConsoleInterfaceState( CanAnnounce(comp), CanCallOrRecall(comp), levels, @@ -203,7 +200,9 @@ private bool CanUse(EntityUid user, EntityUid console) private bool CanCallOrRecall(CommunicationsConsoleComponent comp) { // Defer to what the round end system thinks we should be able to do. - if (_emergency.EmergencyShuttleArrived || !_roundEndSystem.CanCallOrRecall()) + if (_emergency.EmergencyShuttleArrived + || !_roundEndSystem.CanCallOrRecall() + || !comp.CanShuttle) return false; // Calling shuttle checks @@ -223,12 +222,12 @@ private bool CanCallOrRecall(CommunicationsConsoleComponent comp) private void OnSelectAlertLevelMessage(EntityUid uid, CommunicationsConsoleComponent comp, CommunicationsConsoleSelectAlertLevelMessage message) { - if (message.Session.AttachedEntity is not { Valid: true } mob) + if (message.Actor is not { Valid: true } mob) return; if (!CanUse(mob, uid)) { - _popupSystem.PopupCursor(Loc.GetString("comms-console-permission-denied"), message.Session, PopupType.Medium); + _popupSystem.PopupCursor(Loc.GetString("comms-console-permission-denied"), message.Actor, PopupType.Medium); return; } @@ -245,7 +244,7 @@ private void OnAnnounceMessage(EntityUid uid, CommunicationsConsoleComponent com var maxLength = _cfg.GetCVar(CCVars.ChatMaxAnnouncementLength); var msg = SharedChatSystem.SanitizeAnnouncement(message.Message, maxLength); var author = Loc.GetString("comms-console-announcement-unknown-sender"); - if (message.Session.AttachedEntity is { Valid: true } mob) + if (message.Actor is { Valid: true } mob) { if (!CanAnnounce(comp)) { @@ -254,7 +253,7 @@ private void OnAnnounceMessage(EntityUid uid, CommunicationsConsoleComponent com if (!CanUse(mob, uid)) { - _popupSystem.PopupEntity(Loc.GetString("comms-console-permission-denied"), uid, message.Session); + _popupSystem.PopupEntity(Loc.GetString("comms-console-permission-denied"), uid, message.Actor); return; } @@ -267,7 +266,7 @@ private void OnAnnounceMessage(EntityUid uid, CommunicationsConsoleComponent com comp.AnnouncementCooldownRemaining = comp.Delay; UpdateCommsConsoleInterface(uid, comp); - var ev = new CommunicationConsoleAnnouncementEvent(uid, comp, msg, message.Session.AttachedEntity); + var ev = new CommunicationConsoleAnnouncementEvent(uid, comp, msg, message.Actor); RaiseLocalEvent(ref ev); // allow admemes with vv @@ -279,17 +278,16 @@ private void OnAnnounceMessage(EntityUid uid, CommunicationsConsoleComponent com { _announcer.SendAnnouncement("announce", Filter.Broadcast(), msg, title, comp.Color); - if (message.Session.AttachedEntity != null) - _adminLogger.Add(LogType.Chat, LogImpact.Low, $"{ToPrettyString(message.Session.AttachedEntity.Value):player} has sent the following global announcement: {msg}"); - + _adminLogger.Add(LogType.Chat, LogImpact.Low, $"{ToPrettyString(message.Actor):player} has sent the following global announcement: {msg}"); return; } + if (TryComp(_stationSystem.GetOwningStation(uid), out var stationData)) _announcer.SendAnnouncement("announce", _stationSystem.GetInStation(stationData), msg, title, comp.Color); - if (message.Session.AttachedEntity != null) - _adminLogger.Add(LogType.Chat, LogImpact.Low, $"{ToPrettyString(message.Session.AttachedEntity.Value):player} has sent the following station announcement: {msg}"); + _adminLogger.Add(LogType.Chat, LogImpact.Low, $"{ToPrettyString(message.Actor):player} has sent the following station announcement: {msg}"); + } private void OnBroadcastMessage(EntityUid uid, CommunicationsConsoleComponent component, CommunicationsConsoleBroadcastMessage message) @@ -304,8 +302,7 @@ private void OnBroadcastMessage(EntityUid uid, CommunicationsConsoleComponent co _deviceNetworkSystem.QueuePacket(uid, null, payload, net.TransmitFrequency); - if (message.Session.AttachedEntity != null) - _adminLogger.Add(LogType.DeviceNetwork, LogImpact.Low, $"{ToPrettyString(message.Session.AttachedEntity.Value):player} has sent the following broadcast: {message.Message:msg}"); + _adminLogger.Add(LogType.DeviceNetwork, LogImpact.Low, $"{ToPrettyString(message.Actor):player} has sent the following broadcast: {message.Message:msg}"); } private void OnCallShuttleMessage(EntityUid uid, CommunicationsConsoleComponent comp, CommunicationsConsoleCallEmergencyShuttleMessage message) @@ -313,12 +310,11 @@ private void OnCallShuttleMessage(EntityUid uid, CommunicationsConsoleComponent if (!CanCallOrRecall(comp)) return; - if (message.Session.AttachedEntity is not { Valid: true } mob) - return; + var mob = message.Actor; if (!CanUse(mob, uid)) { - _popupSystem.PopupEntity(Loc.GetString("comms-console-permission-denied"), uid, message.Session); + _popupSystem.PopupEntity(Loc.GetString("comms-console-permission-denied"), uid, message.Actor); return; } @@ -326,7 +322,7 @@ private void OnCallShuttleMessage(EntityUid uid, CommunicationsConsoleComponent RaiseLocalEvent(ref ev); if (ev.Cancelled) { - _popupSystem.PopupEntity(ev.Reason ?? Loc.GetString("comms-console-shuttle-unavailable"), uid, message.Session); + _popupSystem.PopupEntity(ev.Reason ?? Loc.GetString("comms-console-shuttle-unavailable"), uid, message.Actor); return; } @@ -339,17 +335,14 @@ private void OnRecallShuttleMessage(EntityUid uid, CommunicationsConsoleComponen if (!CanCallOrRecall(comp)) return; - if (message.Session.AttachedEntity is not { Valid: true } mob) - return; - - if (!CanUse(mob, uid)) + if (!CanUse(message.Actor, uid)) { - _popupSystem.PopupEntity(Loc.GetString("comms-console-permission-denied"), uid, message.Session); + _popupSystem.PopupEntity(Loc.GetString("comms-console-permission-denied"), uid, message.Actor); return; } _roundEndSystem.CancelRoundEndCountdown(uid); - _adminLogger.Add(LogType.Action, LogImpact.Extreme, $"{ToPrettyString(mob):player} has recalled the shuttle."); + _adminLogger.Add(LogType.Action, LogImpact.Extreme, $"{ToPrettyString(message.Actor):player} has recalled the shuttle."); } } diff --git a/Content.Server/Configurable/ConfigurationSystem.cs b/Content.Server/Configurable/ConfigurationSystem.cs index eb31149eca..5f5f1ef7d1 100644 --- a/Content.Server/Configurable/ConfigurationSystem.cs +++ b/Content.Server/Configurable/ConfigurationSystem.cs @@ -24,16 +24,14 @@ public override void Initialize() private void OnInteractUsing(EntityUid uid, ConfigurationComponent component, InteractUsingEvent args) { + // TODO use activatable ui system if (args.Handled) return; if (!TryComp(args.Used, out ToolComponent? tool) || !tool.Qualities.Contains(component.QualityNeeded)) return; - if (!TryComp(args.User, out ActorComponent? actor)) - return; - - args.Handled = _uiSystem.TryOpen(uid, ConfigurationUiKey.Key, actor.PlayerSession); + args.Handled = _uiSystem.TryOpenUi(uid, ConfigurationUiKey.Key, args.User); } private void OnStartup(EntityUid uid, ConfigurationComponent component, ComponentStartup args) @@ -43,8 +41,8 @@ private void OnStartup(EntityUid uid, ConfigurationComponent component, Componen private void UpdateUi(EntityUid uid, ConfigurationComponent component) { - if (_uiSystem.TryGetUi(uid, ConfigurationUiKey.Key, out var ui)) - _uiSystem.SetUiState(ui, new ConfigurationBoundUserInterfaceState(component.Config)); + if (_uiSystem.HasUi(uid, ConfigurationUiKey.Key)) + _uiSystem.SetUiState(uid, ConfigurationUiKey.Key, new ConfigurationBoundUserInterfaceState(component.Config)); } private void OnUpdate(EntityUid uid, ConfigurationComponent component, ConfigurationUpdatedMessage args) diff --git a/Content.Server/Construction/Components/WelderRefinableComponent.cs b/Content.Server/Construction/Components/WelderRefinableComponent.cs index dc3074f195..31b029e241 100644 --- a/Content.Server/Construction/Components/WelderRefinableComponent.cs +++ b/Content.Server/Construction/Components/WelderRefinableComponent.cs @@ -2,22 +2,24 @@ using Content.Shared.Storage; using Robust.Shared.Prototypes; -namespace Content.Server.Construction.Components +namespace Content.Server.Construction.Components; + +/// +/// Used for something that can be refined by welder. +/// For example, glass shard can be refined to glass sheet. +/// +[RegisterComponent] +public sealed partial class WelderRefinableComponent : Component { - /// - /// Used for something that can be refined by welder. - /// For example, glass shard can be refined to glass sheet. - /// - [RegisterComponent] - public sealed partial class WelderRefinableComponent : Component - { - [DataField] - public List RefineResult = new(); + [DataField] + public HashSet? RefineResult; + + [DataField] + public float RefineTime = 2f; - [DataField] - public float RefineTime = 2f; + [DataField] + public float RefineFuel; - [DataField] - public ProtoId QualityNeeded = "Welding"; - } + [DataField] + public ProtoId QualityNeeded = "Welding"; } diff --git a/Content.Server/Construction/Conditions/Locked.cs b/Content.Server/Construction/Conditions/Locked.cs index fde704b161..0b8c9118dc 100644 --- a/Content.Server/Construction/Conditions/Locked.cs +++ b/Content.Server/Construction/Conditions/Locked.cs @@ -15,7 +15,7 @@ public sealed partial class Locked : IGraphCondition public bool Condition(EntityUid uid, IEntityManager entityManager) { if (!entityManager.TryGetComponent(uid, out LockComponent? lockcomp)) - return false; + return true; return lockcomp.Locked == IsLocked; } @@ -25,7 +25,8 @@ public bool DoExamine(ExaminedEvent args) var entMan = IoCManager.Resolve(); var entity = args.Examined; - if (!entMan.TryGetComponent(entity, out LockComponent? lockcomp)) return false; + if (!entMan.TryGetComponent(entity, out LockComponent? lockcomp)) + return true; switch (IsLocked) { diff --git a/Content.Server/Construction/ConstructionSystem.Guided.cs b/Content.Server/Construction/ConstructionSystem.Guided.cs index fe7f9152c0..e096bc02c3 100644 --- a/Content.Server/Construction/ConstructionSystem.Guided.cs +++ b/Content.Server/Construction/ConstructionSystem.Guided.cs @@ -41,6 +41,18 @@ private void AddDeconstructVerb(EntityUid uid, ConstructionComponent component, component.Node == component.DeconstructionNode) return; + if (!_prototypeManager.TryIndex(component.Graph, out ConstructionGraphPrototype? graph)) + return; + + if (component.DeconstructionNode == null) + return; + + if (GetCurrentNode(uid, component) is not {} currentNode) + return; + + if (graph.Path(currentNode.Name, component.DeconstructionNode) is not {} path || path.Length == 0) + return; + Verb verb = new(); //verb.Category = VerbCategories.Construction; //TODO VERBS add more construction verbs? Until then, removing construction category diff --git a/Content.Server/Construction/ConstructionSystem.Interactions.cs b/Content.Server/Construction/ConstructionSystem.Interactions.cs index 946aaa0d3e..5361b65b1f 100644 --- a/Content.Server/Construction/ConstructionSystem.Interactions.cs +++ b/Content.Server/Construction/ConstructionSystem.Interactions.cs @@ -11,10 +11,8 @@ using Content.Shared.Interaction; using Content.Shared.Prying.Systems; using Content.Shared.Radio.EntitySystems; -using Content.Shared.Tools.Components; using Content.Shared.Tools.Systems; using Robust.Shared.Containers; -using Robust.Shared.Map; using Robust.Shared.Utility; #if EXCEPTION_TOLERANCE // ReSharper disable once RedundantUsingDirective @@ -370,7 +368,8 @@ private HandleResult HandleInteraction(EntityUid uid, object ev, ConstructionGra TimeSpan.FromSeconds(toolInsertStep.DoAfter), new [] { toolInsertStep.Tool }, new ConstructionInteractDoAfterEvent(EntityManager, interactUsing), - out var doAfter); + out var doAfter, + toolInsertStep.Fuel); return result && doAfter != null ? HandleResult.DoAfter : HandleResult.False; } diff --git a/Content.Server/Construction/ConstructionSystem.Machine.cs b/Content.Server/Construction/ConstructionSystem.Machine.cs index 2e670dbe40..65b0b70476 100644 --- a/Content.Server/Construction/ConstructionSystem.Machine.cs +++ b/Content.Server/Construction/ConstructionSystem.Machine.cs @@ -5,6 +5,7 @@ using Content.Shared.Construction.Prototypes; using Content.Shared.Verbs; using Robust.Shared.Containers; +using Robust.Shared.Map.Components; using Robust.Shared.Utility; namespace Content.Server.Construction; @@ -143,7 +144,7 @@ private void CreateBoardAndStockParts(EntityUid uid, MachineComponent component) var p = EntityManager.SpawnEntity(partProto.StockPartPrototype, xform.Coordinates); if (!_container.Insert(p, partContainer)) - throw new Exception($"Couldn't insert machine part of type {part} to machine with prototype {partProto.StockPartPrototype}!"); + throw new Exception($"Couldn't insert machine part of type {part} to machine with prototype {partProto.StockPartPrototype ?? "N/A"}!"); } } @@ -183,7 +184,7 @@ public sealed class RefreshPartsEvent : EntityEventArgs { public IReadOnlyList Parts = new List(); - public Dictionary PartRatings = new(); + public Dictionary PartRatings = new Dictionary(); } public sealed class UpgradeExamineEvent : EntityEventArgs diff --git a/Content.Server/Construction/PartExchangerSystem.cs b/Content.Server/Construction/PartExchangerSystem.cs index ee5edcbd0a..f364d1b547 100644 --- a/Content.Server/Construction/PartExchangerSystem.cs +++ b/Content.Server/Construction/PartExchangerSystem.cs @@ -10,6 +10,7 @@ using Robust.Shared.Containers; using Robust.Shared.Utility; using Content.Shared.Wires; +using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Collections; @@ -42,7 +43,7 @@ private void OnDoAfter(EntityUid uid, PartExchangerComponent component, DoAfterE if (args.Handled || args.Args.Target == null) return; - if (!TryComp(uid, out var storage)) + if (!TryComp(uid, out var storage) || storage.Container == null) return; //the parts are stored in here var machinePartQuery = GetEntityQuery(); diff --git a/Content.Server/Construction/RefiningSystem.cs b/Content.Server/Construction/RefiningSystem.cs index d4df8b0916..2ca32baf90 100644 --- a/Content.Server/Construction/RefiningSystem.cs +++ b/Content.Server/Construction/RefiningSystem.cs @@ -2,43 +2,50 @@ using Content.Server.Stack; using Content.Shared.Construction; using Content.Shared.Interaction; +using Content.Shared.Stacks; using Content.Shared.Storage; using SharedToolSystem = Content.Shared.Tools.Systems.SharedToolSystem; -namespace Content.Server.Construction +namespace Content.Server.Construction; + +public sealed class RefiningSystem : EntitySystem { - public sealed class RefiningSystem : EntitySystem + [Dependency] private readonly SharedToolSystem _toolSystem = default!; + [Dependency] private readonly StackSystem _stackSystem = default!; + + public override void Initialize() { - [Dependency] private readonly SharedToolSystem _toolSystem = default!; - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnInteractUsing); - SubscribeLocalEvent(OnDoAfter); - } + base.Initialize(); + SubscribeLocalEvent(OnInteractUsing); + SubscribeLocalEvent(OnDoAfter); + } - private void OnInteractUsing(EntityUid uid, WelderRefinableComponent component, InteractUsingEvent args) - { - if (args.Handled) - return; + private void OnInteractUsing(EntityUid uid, WelderRefinableComponent component, InteractUsingEvent args) + { + if (args.Handled) + return; - args.Handled = _toolSystem.UseTool(args.Used, args.User, uid, component.RefineTime, component.QualityNeeded, new WelderRefineDoAfterEvent()); - } + args.Handled = _toolSystem.UseTool(args.Used, args.User, uid, component.RefineTime, component.QualityNeeded, new WelderRefineDoAfterEvent(), fuel: component.RefineFuel); + } + + private void OnDoAfter(EntityUid uid, WelderRefinableComponent component, WelderRefineDoAfterEvent args) + { + if (args.Cancelled) + return; + + // Get last owner coordinates and delete it + var resultPosition = Transform(uid).Coordinates; + EntityManager.DeleteEntity(uid); - private void OnDoAfter(EntityUid uid, WelderRefinableComponent component, WelderRefineDoAfterEvent args) + // Spawn each result after refine + foreach (var ent in EntitySpawnCollection.GetSpawns(component.RefineResult ?? new())) { - if (args.Cancelled) - return; - - // get last owner coordinates and delete it - var resultPosition = Transform(uid).Coordinates; - EntityManager.DeleteEntity(uid); - - // spawn each result after refine - foreach (var ent in EntitySpawnCollection.GetSpawns(component.RefineResult)) - { - Spawn(ent, resultPosition); - } + var droppedEnt = Spawn(ent, resultPosition); + + // TODO: If something has a stack... Just use a prototype with a single thing in the stack. + // This is not a good way to do it. + if (TryComp(droppedEnt, out var stack)) + _stackSystem.SetCount(droppedEnt, 1, stack); } } } diff --git a/Content.Server/Containers/ThrowInsertContainerComponent.cs b/Content.Server/Containers/ThrowInsertContainerComponent.cs new file mode 100644 index 0000000000..7eb5f4657d --- /dev/null +++ b/Content.Server/Containers/ThrowInsertContainerComponent.cs @@ -0,0 +1,35 @@ +using Robust.Shared.Audio; + +namespace Content.Server.Containers; + +/// +/// Allows objects to fall inside the Container when thrown +/// +[RegisterComponent] +[Access(typeof(ThrowInsertContainerSystem))] +public sealed partial class ThrowInsertContainerComponent : Component +{ + [DataField(required: true)] + public string ContainerId = string.Empty; + + /// + /// Throw chance of hitting into the container + /// + [DataField] + public float Probability = 0.75f; + + /// + /// Sound played when an object is throw into the container. + /// + [DataField] + public SoundSpecifier? InsertSound = new SoundPathSpecifier("/Audio/Effects/trashbag1.ogg"); + + /// + /// Sound played when an item is thrown and misses the container. + /// + [DataField] + public SoundSpecifier? MissSound = new SoundPathSpecifier("/Audio/Effects/thudswoosh.ogg"); + + [DataField] + public LocId MissLocString = "container-thrown-missed"; +} diff --git a/Content.Server/Containers/ThrowInsertContainerSystem.cs b/Content.Server/Containers/ThrowInsertContainerSystem.cs new file mode 100644 index 0000000000..12bb602811 --- /dev/null +++ b/Content.Server/Containers/ThrowInsertContainerSystem.cs @@ -0,0 +1,50 @@ +using Content.Server.Administration.Logs; +using Content.Shared.Database; +using Content.Shared.Popups; +using Content.Shared.Throwing; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Containers; +using Robust.Shared.Random; + +namespace Content.Server.Containers; + +public sealed class ThrowInsertContainerSystem : EntitySystem +{ + [Dependency] private readonly IAdminLogManager _adminLogger = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedContainerSystem _containerSystem = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly IRobustRandom _random = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnThrowCollide); + } + + private void OnThrowCollide(Entity ent, ref ThrowHitByEvent args) + { + var container = _containerSystem.GetContainer(ent, ent.Comp.ContainerId); + + if (!_containerSystem.CanInsert(args.Thrown, container)) + return; + + + var rand = _random.NextFloat(); + if (_random.Prob(ent.Comp.Probability)) + { + _audio.PlayPvs(ent.Comp.MissSound, ent); + _popup.PopupEntity(Loc.GetString(ent.Comp.MissLocString), ent); + return; + } + + if (!_containerSystem.Insert(args.Thrown, container)) + throw new InvalidOperationException("Container insertion failed but CanInsert returned true"); + + _audio.PlayPvs(ent.Comp.InsertSound, ent); + + if (args.Component.Thrower != null) + _adminLogger.Add(LogType.Landed, LogImpact.Low, $"{ToPrettyString(args.Thrown)} thrown by {ToPrettyString(args.Component.Thrower.Value):player} landed in {ToPrettyString(ent)}"); + } +} diff --git a/Content.Server/Content.Server.csproj b/Content.Server/Content.Server.csproj index 8782454eac..70e404fdef 100644 --- a/Content.Server/Content.Server.csproj +++ b/Content.Server/Content.Server.csproj @@ -28,7 +28,6 @@ - diff --git a/Content.Server/Crayon/CrayonSystem.cs b/Content.Server/Crayon/CrayonSystem.cs index 32bb96e9e2..07a13d8a34 100644 --- a/Content.Server/Crayon/CrayonSystem.cs +++ b/Content.Server/Crayon/CrayonSystem.cs @@ -90,19 +90,14 @@ private void OnCrayonUse(EntityUid uid, CrayonComponent component, UseInHandEven if (args.Handled) return; - if (!TryComp(args.User, out var actor) || - !_uiSystem.TryGetUi(uid, SharedCrayonComponent.CrayonUiKey.Key, out var ui)) + if (!_uiSystem.HasUi(uid, SharedCrayonComponent.CrayonUiKey.Key)) { return; } - _uiSystem.ToggleUi(ui, actor.PlayerSession); - if (ui.SubscribedSessions.Contains(actor.PlayerSession)) - { - // Tell the user interface the selected stuff - _uiSystem.SetUiState(ui, new CrayonBoundUserInterfaceState(component.SelectedState, component.SelectableColor, component.Color)); - } + _uiSystem.TryToggleUi(uid, SharedCrayonComponent.CrayonUiKey.Key, args.User); + _uiSystem.SetUiState(uid, SharedCrayonComponent.CrayonUiKey.Key, new CrayonBoundUserInterfaceState(component.SelectedState, component.SelectableColor, component.Color)); args.Handled = true; } @@ -140,8 +135,8 @@ private void OnCrayonInit(EntityUid uid, CrayonComponent component, ComponentIni private void OnCrayonDropped(EntityUid uid, CrayonComponent component, DroppedEvent args) { - if (TryComp(args.User, out var actor)) - _uiSystem.TryClose(uid, SharedCrayonComponent.CrayonUiKey.Key, actor.PlayerSession); + // TODO: Use the existing event. + _uiSystem.CloseUi(uid, SharedCrayonComponent.CrayonUiKey.Key, args.User); } private void UseUpCrayon(EntityUid uid, EntityUid user) diff --git a/Content.Server/CrewManifest/CrewManifestSystem.cs b/Content.Server/CrewManifest/CrewManifestSystem.cs index 8b4cbac5c1..e742456015 100644 --- a/Content.Server/CrewManifest/CrewManifestSystem.cs +++ b/Content.Server/CrewManifest/CrewManifestSystem.cs @@ -100,12 +100,12 @@ private void OnBoundUiClose(EntityUid uid, CrewManifestViewerComponent component return; var owningStation = _stationSystem.GetOwningStation(uid); - if (owningStation == null || ev.Session is not { } session) + if (owningStation == null || !TryComp(ev.Actor, out ActorComponent? actorComp)) { return; } - CloseEui(owningStation.Value, session, uid); + CloseEui(owningStation.Value, actorComp.PlayerSession, uid); } /// @@ -136,12 +136,12 @@ private void OpenEuiFromBui(EntityUid uid, CrewManifestViewerComponent component { Log.Error( "{User} tried to open crew manifest from wrong UI: {Key}. Correct owned is {ExpectedKey}", - msg.Session, msg.UiKey, component.OwnerKey); + msg.Actor, msg.UiKey, component.OwnerKey); return; } var owningStation = _stationSystem.GetOwningStation(uid); - if (owningStation == null || msg.Session is not { } session) + if (owningStation == null || !TryComp(msg.Actor, out ActorComponent? actorComp)) { return; } @@ -151,7 +151,7 @@ private void OpenEuiFromBui(EntityUid uid, CrewManifestViewerComponent component return; } - OpenEui(owningStation.Value, session, uid); + OpenEui(owningStation.Value, actorComp.PlayerSession, uid); } /// diff --git a/Content.Server/CriminalRecords/Systems/CriminalRecordsConsoleSystem.cs b/Content.Server/CriminalRecords/Systems/CriminalRecordsConsoleSystem.cs index 4290726cc4..4389c68c04 100644 --- a/Content.Server/CriminalRecords/Systems/CriminalRecordsConsoleSystem.cs +++ b/Content.Server/CriminalRecords/Systems/CriminalRecordsConsoleSystem.cs @@ -77,7 +77,7 @@ private void OnChangeStatus(Entity ent, ref Cri msg.Status == SecurityStatus.Suspected != (msg.Reason != null)) return; - if (!CheckSelected(ent, msg.Session, out var mob, out var key)) + if (!CheckSelected(ent, msg.Actor, out var mob, out var key)) return; if (!_stationRecords.TryGetRecord(key.Value, out var record) || record.Status == msg.Status) @@ -150,7 +150,7 @@ private void OnChangeStatus(Entity ent, ref Cri private void OnAddHistory(Entity ent, ref CriminalRecordAddHistory msg) { - if (!CheckSelected(ent, msg.Session, out _, out var key)) + if (!CheckSelected(ent, msg.Actor, out _, out var key)) return; var line = msg.Line.Trim(); @@ -167,7 +167,7 @@ private void OnAddHistory(Entity ent, ref Crimi private void OnDeleteHistory(Entity ent, ref CriminalRecordDeleteHistory msg) { - if (!CheckSelected(ent, msg.Session, out _, out var key)) + if (!CheckSelected(ent, msg.Actor, out _, out var key)) return; if (!_criminalRecords.TryDeleteHistory(key.Value, msg.Index)) @@ -185,7 +185,7 @@ private void UpdateUserInterface(Entity ent) if (!TryComp(owningStation, out var stationRecords)) { - _ui.TrySetUiState(uid, CriminalRecordsConsoleKey.Key, new CriminalRecordsConsoleState()); + _ui.SetUiState(uid, CriminalRecordsConsoleKey.Key, new CriminalRecordsConsoleState()); return; } @@ -201,24 +201,22 @@ private void UpdateUserInterface(Entity ent) state.SelectedKey = id; } - _ui.TrySetUiState(uid, CriminalRecordsConsoleKey.Key, state); + _ui.SetUiState(uid, CriminalRecordsConsoleKey.Key, state); } /// /// Boilerplate that most actions use, if they require that a record be selected. /// Obviously shouldn't be used for selecting records. /// - private bool CheckSelected(Entity ent, ICommonSession session, + private bool CheckSelected(Entity ent, EntityUid user, [NotNullWhen(true)] out EntityUid? mob, [NotNullWhen(true)] out StationRecordKey? key) { key = null; mob = null; - if (session.AttachedEntity is not { } user) - return false; if (!_access.IsAllowed(user, ent)) { - _popup.PopupEntity(Loc.GetString("criminal-records-permission-denied"), ent, session); + _popup.PopupEntity(Loc.GetString("criminal-records-permission-denied"), ent, user); return false; } @@ -253,6 +251,8 @@ public void CheckNewIdentity(EntityUid uid) { var name = Identity.Name(uid, EntityManager); var xform = Transform(uid); + + // TODO use the entity's station? Not the station of the map that it happens to currently be on? var station = _station.GetStationInMap(xform.MapID); if (station != null && _stationRecords.GetRecordByName(station.Value, name) is { } id) diff --git a/Content.Server/CriminalRecords/Systems/CriminalRecordsHackerSystem.cs b/Content.Server/CriminalRecords/Systems/CriminalRecordsHackerSystem.cs new file mode 100644 index 0000000000..91285a1ca7 --- /dev/null +++ b/Content.Server/CriminalRecords/Systems/CriminalRecordsHackerSystem.cs @@ -0,0 +1,62 @@ +using Content.Server.Chat.Systems; +using Content.Server.Station.Systems; +using Content.Server.StationRecords.Systems; +using Content.Shared.CriminalRecords; +using Content.Shared.CriminalRecords.Components; +using Content.Shared.CriminalRecords.Systems; +using Content.Shared.Dataset; +using Content.Shared.Security; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; + +namespace Content.Server.CriminalRecords.Systems; + +public sealed class CriminalRecordsHackerSystem : SharedCriminalRecordsHackerSystem +{ + [Dependency] private readonly ChatSystem _chat = default!; + [Dependency] private readonly IPrototypeManager _proto = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly StationSystem _station = default!; + [Dependency] private readonly StationRecordsSystem _records = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnDoAfter); + } + + private void OnDoAfter(Entity ent, ref CriminalRecordsHackDoAfterEvent args) + { + if (args.Cancelled || args.Handled || args.Target == null) + return; + + if (_station.GetOwningStation(ent) is not {} station) + return; + + var reasons = _proto.Index(ent.Comp.Reasons); + foreach (var (key, record) in _records.GetRecordsOfType(station)) + { + var reason = _random.Pick(reasons.Values); + record.Status = SecurityStatus.Wanted; + record.Reason = reason; + // no radio message since spam + // no history since lazy and its easy to remove anyway + // main damage with this is existing arrest warrants are lost and to anger beepsky + } + + _chat.DispatchGlobalAnnouncement(Loc.GetString(ent.Comp.Announcement), playSound: true, colorOverride: Color.Red); + + // once is enough + RemComp(ent); + + var ev = new CriminalRecordsHackedEvent(ent, args.Target.Value); + RaiseLocalEvent(args.User, ref ev); + } +} + +/// +/// Raised on the user after hacking a criminal records console. +/// +[ByRefEvent] +public record struct CriminalRecordsHackedEvent(EntityUid User, EntityUid Target); diff --git a/Content.Server/Damage/Systems/DamageOnToolInteractSystem.cs b/Content.Server/Damage/Systems/DamageOnToolInteractSystem.cs index 264ec4a8ec..42676c1891 100644 --- a/Content.Server/Damage/Systems/DamageOnToolInteractSystem.cs +++ b/Content.Server/Damage/Systems/DamageOnToolInteractSystem.cs @@ -1,7 +1,5 @@ using Content.Server.Administration.Logs; using Content.Server.Damage.Components; -using Content.Server.Tools.Components; -using Content.Shared.Item; using Content.Shared.Damage; using Content.Shared.Database; using Content.Shared.Interaction; diff --git a/Content.Server/Database/ServerDbBase.cs b/Content.Server/Database/ServerDbBase.cs index af9220ee6d..8286defd11 100644 --- a/Content.Server/Database/ServerDbBase.cs +++ b/Content.Server/Database/ServerDbBase.cs @@ -17,6 +17,8 @@ using Robust.Shared.Enums; using Robust.Shared.Network; using Robust.Shared.Utility; +using Content.Shared.Roles; +using Robust.Shared.Prototypes; namespace Content.Server.Database { @@ -31,9 +33,11 @@ public ServerDbBase(ISawmill opsLog) } #region Preferences - public async Task GetPlayerPreferencesAsync(NetUserId userId) + public async Task GetPlayerPreferencesAsync( + NetUserId userId, + CancellationToken cancel = default) { - await using var db = await GetDb(); + await using var db = await GetDb(cancel); var prefs = await db.DbContext .Preference @@ -42,7 +46,7 @@ public ServerDbBase(ISawmill opsLog) .Include(p => p.Profiles).ThenInclude(h => h.Traits) .Include(p => p.Profiles).ThenInclude(h => h.Loadouts) .AsSingleQuery() - .SingleOrDefaultAsync(p => p.UserId == userId.UserId); + .SingleOrDefaultAsync(p => p.UserId == userId.UserId, cancel); if (prefs is null) return null; @@ -139,7 +143,7 @@ public async Task InitPrefsAsync(NetUserId userId, ICharacter await db.DbContext.SaveChangesAsync(); - return new PlayerPreferences(new[] {new KeyValuePair(0, defaultProfile)}, 0, Color.FromHex(prefs.AdminOOCColor)); + return new PlayerPreferences(new[] { new KeyValuePair(0, defaultProfile) }, 0, Color.FromHex(prefs.AdminOOCColor)); } public async Task DeleteSlotAndSetSelectedIndex(NetUserId userId, int deleteSlot, int newSlot) @@ -216,13 +220,13 @@ private static HumanoidCharacterProfile ConvertProfiles(Profile profile) profile.CharacterName, profile.FlavorText, profile.Species, + profile.CustomSpecieName, profile.Height, profile.Width, profile.Age, sex, gender, - new HumanoidCharacterAppearance - ( + new HumanoidCharacterAppearance( profile.HairName, Color.FromHex(profile.HairColor), profile.FacialHairName, @@ -231,14 +235,14 @@ private static HumanoidCharacterProfile ConvertProfiles(Profile profile) Color.FromHex(profile.SkinColor), markings ), - clothing, - backpack, spawnPriority, jobs, + clothing, + backpack, (PreferenceUnavailableMode) profile.PreferenceUnavailable, - antags.ToList(), - traits.ToList(), - loadouts.ToList() + antags.ToHashSet(), + traits.ToHashSet(), + loadouts.ToHashSet() ); } @@ -256,6 +260,7 @@ private static Profile ConvertProfiles(HumanoidCharacterProfile humanoid, int sl profile.CharacterName = humanoid.Name; profile.FlavorText = humanoid.FlavorText; profile.Species = humanoid.Species; + profile.CustomSpecieName = humanoid.Customspeciename; profile.Age = humanoid.Age; profile.Sex = humanoid.Sex.ToString(); profile.Gender = humanoid.Gender.ToString(); @@ -278,25 +283,25 @@ private static Profile ConvertProfiles(HumanoidCharacterProfile humanoid, int sl profile.Jobs.AddRange( humanoid.JobPriorities .Where(j => j.Value != JobPriority.Never) - .Select(j => new Job {JobName = j.Key, Priority = (DbJobPriority) j.Value}) + .Select(j => new Job { JobName = j.Key, Priority = (DbJobPriority) j.Value }) ); profile.Antags.Clear(); profile.Antags.AddRange( humanoid.AntagPreferences - .Select(a => new Antag {AntagName = a}) + .Select(a => new Antag { AntagName = a }) ); profile.Traits.Clear(); profile.Traits.AddRange( humanoid.TraitPreferences - .Select(t => new Trait {TraitName = t}) + .Select(t => new Trait { TraitName = t }) ); profile.Loadouts.Clear(); profile.Loadouts.AddRange( humanoid.LoadoutPreferences - .Select(t => new Loadout {LoadoutName = t}) + .Select(t => new Loadout { LoadoutName = t }) ); return profile; @@ -480,13 +485,13 @@ public async Task EditServerRoleBan(int id, string reason, NoteSeverity severity #endregion #region Playtime - public async Task> GetPlayTimes(Guid player) + public async Task> GetPlayTimes(Guid player, CancellationToken cancel) { - await using var db = await GetDb(); + await using var db = await GetDb(cancel); return await db.DbContext.PlayTime .Where(p => p.PlayerId == player) - .ToListAsync(); + .ToListAsync(cancel); } public async Task UpdatePlayTimes(IReadOnlyCollection updates) @@ -638,7 +643,7 @@ public async Task AddServerBanHitsAsync(int connection, IEnumerable GetAdminDataForAsync(NetUserId userId, CancellationToken cancel) { - await using var db = await GetDb(); + await using var db = await GetDb(cancel); return await db.DbContext.Admin .Include(p => p.Flags) @@ -653,7 +658,7 @@ public async Task AddServerBanHitsAsync(int connection, IEnumerable GetAdminRankDataForAsync(int id, CancellationToken cancel = default) { - await using var db = await GetDb(); + await using var db = await GetDb(cancel); return await db.DbContext.AdminRank .Include(r => r.Flags) @@ -662,7 +667,7 @@ public async Task AddServerBanHitsAsync(int connection, IEnumerable a.UserId == userId.UserId, cancel); db.DbContext.Admin.Remove(admin); @@ -672,7 +677,7 @@ public async Task RemoveAdminAsync(NetUserId userId, CancellationToken cancel) public async Task AddAdminAsync(Admin admin, CancellationToken cancel) { - await using var db = await GetDb(); + await using var db = await GetDb(cancel); db.DbContext.Admin.Add(admin); @@ -681,7 +686,7 @@ public async Task AddAdminAsync(Admin admin, CancellationToken cancel) public async Task UpdateAdminAsync(Admin admin, CancellationToken cancel) { - await using var db = await GetDb(); + await using var db = await GetDb(cancel); var existing = await db.DbContext.Admin.Include(a => a.Flags).SingleAsync(a => a.UserId == admin.UserId, cancel); existing.Flags = admin.Flags; @@ -693,7 +698,7 @@ public async Task UpdateAdminAsync(Admin admin, CancellationToken cancel) public async Task RemoveAdminRankAsync(int rankId, CancellationToken cancel) { - await using var db = await GetDb(); + await using var db = await GetDb(cancel); var admin = await db.DbContext.AdminRank.SingleAsync(a => a.Id == rankId, cancel); db.DbContext.AdminRank.Remove(admin); @@ -703,7 +708,7 @@ public async Task RemoveAdminRankAsync(int rankId, CancellationToken cancel) public async Task AddAdminRankAsync(AdminRank rank, CancellationToken cancel) { - await using var db = await GetDb(); + await using var db = await GetDb(cancel); db.DbContext.AdminRank.Add(rank); @@ -776,7 +781,7 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id} public async Task UpdateAdminRankAsync(AdminRank rank, CancellationToken cancel) { - await using var db = await GetDb(); + await using var db = await GetDb(cancel); var existing = await db.DbContext.AdminRank .Include(r => r.Flags) @@ -1233,7 +1238,7 @@ ban.Unban is null ban.LastEditedAt, ban.ExpirationTime, ban.Hidden, - new [] { ban.RoleId.Replace(BanManager.JobPrefix, null) }, + new[] { ban.RoleId.Replace(BanManager.JobPrefix, null) }, MakePlayerRecord(unbanningAdmin), ban.Unban?.UnbanTime); } @@ -1397,10 +1402,10 @@ public async Task> GetActiveWatchlists(Guid player) protected async Task> GetActiveWatchlistsImpl(DbGuard db, Guid player) { var entities = await (from watchlist in db.DbContext.AdminWatchlists - where watchlist.PlayerUserId == player && - !watchlist.Deleted && - (watchlist.ExpirationTime == null || DateTime.UtcNow < watchlist.ExpirationTime) - select watchlist) + where watchlist.PlayerUserId == player && + !watchlist.Deleted && + (watchlist.ExpirationTime == null || DateTime.UtcNow < watchlist.ExpirationTime) + select watchlist) .Include(note => note.Round) .ThenInclude(r => r!.Server) .Include(note => note.CreatedBy) @@ -1425,9 +1430,9 @@ public async Task> GetMessages(Guid player) protected async Task> GetMessagesImpl(DbGuard db, Guid player) { var entities = await (from message in db.DbContext.AdminMessages - where message.PlayerUserId == player && !message.Deleted && - (message.ExpirationTime == null || DateTime.UtcNow < message.ExpirationTime) - select message).Include(note => note.Round) + where message.PlayerUserId == player && !message.Deleted && + (message.ExpirationTime == null || DateTime.UtcNow < message.ExpirationTime) + select message).Include(note => note.Round) .ThenInclude(r => r!.Server) .Include(note => note.CreatedBy) .Include(note => note.LastEditedBy) @@ -1506,7 +1511,7 @@ protected async Task> GetGroupedServerRoleBansAsNo // Client side query, as EF can't do groups yet var bansEnumerable = bansQuery - .GroupBy(ban => new { ban.BanTime, CreatedBy = (Player?)ban.CreatedBy, ban.Reason, Unbanned = ban.Unban == null }) + .GroupBy(ban => new { ban.BanTime, CreatedBy = (Player?) ban.CreatedBy, ban.Reason, Unbanned = ban.Unban == null }) .Select(banGroup => banGroup) .ToArray(); @@ -1559,7 +1564,9 @@ public async Task HasPendingModelChanges() return db.DbContext.Database.HasPendingModelChanges(); } - protected abstract Task GetDb([CallerMemberName] string? name = null); + protected abstract Task GetDb( + CancellationToken cancel = default, + [CallerMemberName] string? name = null); protected void LogDbOp(string? name) { @@ -1572,5 +1579,64 @@ protected abstract class DbGuard : IAsyncDisposable public abstract ValueTask DisposeAsync(); } + + #region Job Whitelists + + public async Task AddJobWhitelist(Guid player, ProtoId job) + { + await using var db = await GetDb(); + var exists = await db.DbContext.RoleWhitelists + .Where(w => w.PlayerUserId == player) + .Where(w => w.RoleId == job.Id) + .AnyAsync(); + + if (exists) + return false; + + var whitelist = new RoleWhitelist + { + PlayerUserId = player, + RoleId = job + }; + db.DbContext.RoleWhitelists.Add(whitelist); + await db.DbContext.SaveChangesAsync(); + return true; + } + + public async Task> GetJobWhitelists(Guid player, CancellationToken cancel) + { + await using var db = await GetDb(cancel); + return await db.DbContext.RoleWhitelists + .Where(w => w.PlayerUserId == player) + .Select(w => w.RoleId) + .ToListAsync(cancellationToken: cancel); + } + + public async Task IsJobWhitelisted(Guid player, ProtoId job) + { + await using var db = await GetDb(); + return await db.DbContext.RoleWhitelists + .Where(w => w.PlayerUserId == player) + .Where(w => w.RoleId == job.Id) + .AnyAsync(); + } + + public async Task RemoveJobWhitelist(Guid player, ProtoId job) + { + await using var db = await GetDb(); + var entry = await db.DbContext.RoleWhitelists + .Where(w => w.PlayerUserId == player) + .Where(w => w.RoleId == job.Id) + .SingleOrDefaultAsync(); + + if (entry == null) + return false; + + db.DbContext.RoleWhitelists.Remove(entry); + await db.DbContext.SaveChangesAsync(); + return true; + } + + #endregion } } diff --git a/Content.Server/Database/ServerDbManager.cs b/Content.Server/Database/ServerDbManager.cs index 554dd30743..a9c5e8c43a 100644 --- a/Content.Server/Database/ServerDbManager.cs +++ b/Content.Server/Database/ServerDbManager.cs @@ -19,6 +19,8 @@ using Robust.Shared.Network; using LogLevel = Robust.Shared.Log.LogLevel; using MSLogLevel = Microsoft.Extensions.Logging.LogLevel; +using Content.Shared.Roles; +using Robust.Shared.Prototypes; namespace Content.Server.Database { @@ -29,7 +31,11 @@ public interface IServerDbManager void Shutdown(); #region Preferences - Task InitPrefsAsync(NetUserId userId, ICharacterProfile defaultProfile); + Task InitPrefsAsync( + NetUserId userId, + ICharacterProfile defaultProfile, + CancellationToken cancel); + Task SaveSelectedCharacterIndexAsync(NetUserId userId, int index); Task SaveCharacterSlotAsync(NetUserId userId, ICharacterProfile? profile, int slot); @@ -38,7 +44,7 @@ public interface IServerDbManager // Single method for two operations for transaction. Task DeleteSlotAndSetSelectedIndex(NetUserId userId, int deleteSlot, int newSlot); - Task GetPlayerPreferencesAsync(NetUserId userId); + Task GetPlayerPreferencesAsync(NetUserId userId, CancellationToken cancel); #endregion #region User Ids @@ -83,7 +89,7 @@ Task> GetServerBansAsync( IPAddress? address, NetUserId? userId, ImmutableArray? hwId, - bool includeUnbanned=true); + bool includeUnbanned = true); Task AddServerBanAsync(ServerBanDef serverBan); Task AddServerUnbanAsync(ServerUnbanDef serverBan); @@ -157,8 +163,9 @@ public Task EditServerRoleBan( /// Look up a player's role timers. /// /// The player to get the role timer information from. + /// /// All role timers belonging to the player. - Task> GetPlayTimes(Guid player); + Task> GetPlayTimes(Guid player, CancellationToken cancel = default); /// /// Update play time information in bulk. @@ -285,6 +292,18 @@ Task AddConnectionLogAsync( Task MarkMessageAsSeen(int id, bool dismissedToo); #endregion + + #region Job Whitelists + + Task AddJobWhitelist(Guid player, ProtoId job); + + + Task> GetJobWhitelists(Guid player, CancellationToken cancel = default); + Task IsJobWhitelisted(Guid player, ProtoId job); + + Task RemoveJobWhitelist(Guid player, ProtoId job); + + #endregion } public sealed class ServerDbManager : IServerDbManager @@ -346,7 +365,10 @@ public void Shutdown() _sqliteInMemoryConnection?.Dispose(); } - public Task InitPrefsAsync(NetUserId userId, ICharacterProfile defaultProfile) + public Task InitPrefsAsync( + NetUserId userId, + ICharacterProfile defaultProfile, + CancellationToken cancel) { DbWriteOpsMetric.Inc(); return RunDbCommand(() => _db.InitPrefsAsync(userId, defaultProfile)); @@ -376,10 +398,10 @@ public Task SaveAdminOOCColorAsync(NetUserId userId, Color color) return RunDbCommand(() => _db.SaveAdminOOCColorAsync(userId, color)); } - public Task GetPlayerPreferencesAsync(NetUserId userId) + public Task GetPlayerPreferencesAsync(NetUserId userId, CancellationToken cancel) { DbReadOpsMetric.Inc(); - return RunDbCommand(() => _db.GetPlayerPreferencesAsync(userId)); + return RunDbCommand(() => _db.GetPlayerPreferencesAsync(userId, cancel)); } public Task AssignUserIdAsync(string name, NetUserId userId) @@ -413,7 +435,7 @@ public Task> GetServerBansAsync( IPAddress? address, NetUserId? userId, ImmutableArray? hwId, - bool includeUnbanned=true) + bool includeUnbanned = true) { DbReadOpsMetric.Inc(); return RunDbCommand(() => _db.GetServerBansAsync(address, userId, hwId, includeUnbanned)); @@ -487,10 +509,10 @@ public Task EditServerRoleBan(int id, string reason, NoteSeverity severity, Date #region Playtime - public Task> GetPlayTimes(Guid player) + public Task> GetPlayTimes(Guid player, CancellationToken cancel) { DbReadOpsMetric.Inc(); - return RunDbCommand(() => _db.GetPlayTimes(player)); + return RunDbCommand(() => _db.GetPlayTimes(player, cancel)); } public Task UpdatePlayTimes(IReadOnlyCollection updates) @@ -784,7 +806,7 @@ public Task AddAdminMessage(int? roundId, Guid player, TimeSpan playtimeAtN return RunDbCommand(() => _db.GetServerRoleBanAsNoteAsync(id)); } - public Task> GetAllAdminRemarks(Guid player) + public Task> GetAllAdminRemarks(Guid player) { DbReadOpsMetric.Inc(); return RunDbCommand(() => _db.GetAllAdminRemarks(player)); @@ -861,6 +883,30 @@ public Task MarkMessageAsSeen(int id, bool dismissedToo) return RunDbCommand(() => _db.MarkMessageAsSeen(id, dismissedToo)); } + public Task AddJobWhitelist(Guid player, ProtoId job) + { + DbWriteOpsMetric.Inc(); + return RunDbCommand(() => _db.AddJobWhitelist(player, job)); + } + + public Task> GetJobWhitelists(Guid player, CancellationToken cancel = default) + { + DbReadOpsMetric.Inc(); + return RunDbCommand(() => _db.GetJobWhitelists(player, cancel)); + } + + public Task IsJobWhitelisted(Guid player, ProtoId job) + { + DbReadOpsMetric.Inc(); + return RunDbCommand(() => _db.IsJobWhitelisted(player, job)); + } + + public Task RemoveJobWhitelist(Guid player, ProtoId job) + { + DbWriteOpsMetric.Inc(); + return RunDbCommand(() => _db.RemoveJobWhitelist(player, job)); + } + // Wrapper functions to run DB commands from the thread pool. // This will avoid SynchronizationContext capturing and avoid running CPU work on the main thread. // For SQLite, this will also enable read parallelization (within limits). diff --git a/Content.Server/Database/ServerDbPostgres.cs b/Content.Server/Database/ServerDbPostgres.cs index c81e735868..fd4699fff4 100644 --- a/Content.Server/Database/ServerDbPostgres.cs +++ b/Content.Server/Database/ServerDbPostgres.cs @@ -527,22 +527,26 @@ protected override DateTime NormalizeDatabaseTime(DateTime time) return time; } - private async Task GetDbImpl([CallerMemberName] string? name = null) + private async Task GetDbImpl( + CancellationToken cancel = default, + [CallerMemberName] string? name = null) { LogDbOp(name); await _dbReadyTask; - await _prefsSemaphore.WaitAsync(); + await _prefsSemaphore.WaitAsync(cancel); if (_msLag > 0) - await Task.Delay(_msLag); + await Task.Delay(_msLag, cancel); return new DbGuardImpl(this, new PostgresServerDbContext(_options)); } - protected override async Task GetDb([CallerMemberName] string? name = null) + protected override async Task GetDb( + CancellationToken cancel = default, + [CallerMemberName] string? name = null) { - return await GetDbImpl(name); + return await GetDbImpl(cancel, name); } private sealed class DbGuardImpl : DbGuard diff --git a/Content.Server/Database/ServerDbSqlite.cs b/Content.Server/Database/ServerDbSqlite.cs index 88ecf82002..ffec90bb43 100644 --- a/Content.Server/Database/ServerDbSqlite.cs +++ b/Content.Server/Database/ServerDbSqlite.cs @@ -439,7 +439,7 @@ public override async Task AddConnectionLogAsync( public override async Task<((Admin, string? lastUserName)[] admins, AdminRank[])> GetAllAdminAndRanksAsync( CancellationToken cancel) { - await using var db = await GetDbImpl(); + await using var db = await GetDbImpl(cancel); var admins = await db.SqliteDbContext.Admin .Include(a => a.Flags) @@ -514,23 +514,27 @@ protected override DateTime NormalizeDatabaseTime(DateTime time) return DateTime.SpecifyKind(time, DateTimeKind.Utc); } - private async Task GetDbImpl([CallerMemberName] string? name = null) + private async Task GetDbImpl( + CancellationToken cancel = default, + [CallerMemberName] string? name = null) { LogDbOp(name); await _dbReadyTask; if (_msDelay > 0) - await Task.Delay(_msDelay); + await Task.Delay(_msDelay, cancel); - await _prefsSemaphore.WaitAsync(); + await _prefsSemaphore.WaitAsync(cancel); var dbContext = new SqliteServerDbContext(_options()); return new DbGuardImpl(this, dbContext); } - protected override async Task GetDb([CallerMemberName] string? name = null) + protected override async Task GetDb( + CancellationToken cancel = default, + [CallerMemberName] string? name = null) { - return await GetDbImpl(name).ConfigureAwait(false); + return await GetDbImpl(cancel, name).ConfigureAwait(false); } private sealed class DbGuardImpl : DbGuard @@ -569,9 +573,9 @@ public ConcurrencySemaphore(int maxCount, bool synchronous) _semaphore = new SemaphoreSlim(maxCount, maxCount); } - public Task WaitAsync() + public Task WaitAsync(CancellationToken cancel = default) { - var task = _semaphore.WaitAsync(); + var task = _semaphore.WaitAsync(cancel); if (_synchronous) { diff --git a/Content.Server/Database/UserDbDataManager.cs b/Content.Server/Database/UserDbDataManager.cs index f8b1611fd5..fde610a6fd 100644 --- a/Content.Server/Database/UserDbDataManager.cs +++ b/Content.Server/Database/UserDbDataManager.cs @@ -1,6 +1,5 @@ using System.Threading; using System.Threading.Tasks; -using Content.Server.Players.PlayTimeTracking; using Content.Server.Preferences.Managers; using Robust.Shared.Network; using Robust.Shared.Player; @@ -16,17 +15,23 @@ namespace Content.Server.Database; /// Actual loading code is handled by separate managers such as . /// This manager is simply a centralized "is loading done" controller for other code to rely on. /// -public sealed class UserDbDataManager +public sealed class UserDbDataManager : IPostInjectInit { - [Dependency] private readonly IServerPreferencesManager _prefs = default!; - [Dependency] private readonly PlayTimeTrackingManager _playTimeTracking = default!; + [Dependency] private readonly ILogManager _logManager = default!; private readonly Dictionary _users = new(); + private readonly List _onLoadPlayer = []; + private readonly List _onFinishLoad = []; + private readonly List _onPlayerDisconnect = []; + + private ISawmill _sawmill = default!; // TODO: Ideally connected/disconnected would be subscribed to IPlayerManager directly, // but this runs into ordering issues with game ticker. public void ClientConnected(ICommonSession session) { + _sawmill.Verbose($"Initiating load for user {session}"); + DebugTools.Assert(!_users.ContainsKey(session.UserId), "We should not have any cached data on client connect."); var cts = new CancellationTokenSource(); @@ -45,17 +50,67 @@ public void ClientDisconnected(ICommonSession session) data.Cancel.Cancel(); data.Cancel.Dispose(); - _prefs.OnClientDisconnected(session); - _playTimeTracking.ClientDisconnected(session); + foreach (var onDisconnect in _onPlayerDisconnect) + { + onDisconnect(session); + } } private async Task Load(ICommonSession session, CancellationToken cancel) { - await Task.WhenAll( - _prefs.LoadData(session, cancel), - _playTimeTracking.LoadData(session, cancel)); + // The task returned by this function is only ever observed by callers of WaitLoadComplete, + // which doesn't even happen currently if the lobby is enabled. + // As such, this task must NOT throw a non-cancellation error! + try + { + var tasks = new List(); + foreach (var action in _onLoadPlayer) + { + tasks.Add(action(session, cancel)); + } + + await Task.WhenAll(tasks); + + cancel.ThrowIfCancellationRequested(); + foreach (var action in _onFinishLoad) + { + action(session); + } + + _sawmill.Verbose($"Load complete for user {session}"); + } + catch (OperationCanceledException) + { + _sawmill.Debug($"Load cancelled for user {session}"); + + // We can rethrow the cancellation. + // This will make the task returned by WaitLoadComplete() also return a cancellation. + throw; + } + catch (Exception e) + { + // Must catch all exceptions here, otherwise task may go unobserved. + _sawmill.Error($"Load of user data failed: {e}"); + + // Kick them from server, since something is hosed. Let them try again I guess. + session.Channel.Disconnect("Loading of server user data failed, this is a bug."); + + // We throw a OperationCanceledException so users of WaitLoadComplete() always see cancellation here. + throw new OperationCanceledException("Load of user data cancelled due to unknown error"); + } } + /// + /// Wait for all on-database data for a user to be loaded. + /// + /// + /// The task returned by this function may end up in a cancelled state + /// (throwing ) if the user disconnects while loading or an error occurs. + /// + /// + /// + /// A task that completes when all on-database data for a user has finished loading. + /// public Task WaitLoadComplete(ICommonSession session) { return _users[session.UserId].Task; @@ -63,7 +118,7 @@ public Task WaitLoadComplete(ICommonSession session) public bool IsLoadComplete(ICommonSession session) { - return GetLoadTask(session).IsCompleted; + return GetLoadTask(session).IsCompletedSuccessfully; } public Task GetLoadTask(ICommonSession session) @@ -71,5 +126,31 @@ public Task GetLoadTask(ICommonSession session) return _users[session.UserId].Task; } + void IPostInjectInit.PostInject() + { + _sawmill = _logManager.GetSawmill("userdb"); + } + + public void AddOnLoadPlayer(OnLoadPlayer action) + { + _onLoadPlayer.Add(action); + } + + public void AddOnFinishLoad(OnFinishLoad action) + { + _onFinishLoad.Add(action); + } + + public void AddOnPlayerDisconnect(OnPlayerDisconnect action) + { + _onPlayerDisconnect.Add(action); + } + private sealed record UserData(CancellationTokenSource Cancel, Task Task); + + public delegate Task OnLoadPlayer(ICommonSession player, CancellationToken cancel); + + public delegate void OnFinishLoad(ICommonSession player); + + public delegate void OnPlayerDisconnect(ICommonSession player); } diff --git a/Content.Server/DeltaV/Implants/SubdermalBionicSyrinxImplantSystem.cs b/Content.Server/DeltaV/Implants/SubdermalBionicSyrinxImplantSystem.cs index 30a894a2d1..203872518f 100644 --- a/Content.Server/DeltaV/Implants/SubdermalBionicSyrinxImplantSystem.cs +++ b/Content.Server/DeltaV/Implants/SubdermalBionicSyrinxImplantSystem.cs @@ -1,113 +1,30 @@ -using Content.Server.Administration.Logs; -using Content.Server.Chat.Systems; -using Content.Server.Popups; -using Content.Server.VoiceMask; -using Content.Shared.Database; +using Content.Server.VoiceMask; using Content.Shared.Implants; -using Content.Shared.Implants.Components; -using Content.Shared.Popups; -using Content.Shared.Preferences; using Content.Shared.Tag; -using Content.Shared.VoiceMask; -using Robust.Server.GameObjects; -using Robust.Shared.Containers; namespace Content.Server.Implants; public sealed class SubdermalBionicSyrinxImplantSystem : EntitySystem { - [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; - [Dependency] private readonly PopupSystem _popupSystem = default!; - [Dependency] private readonly IAdminLogManager _adminLogger = default!; [Dependency] private readonly TagSystem _tag = default!; [ValidatePrototypeId] - public const string BionicSyrinxImplant = "BionicSyrinxImplant"; - + private const string BionicSyrinxImplant = "BionicSyrinxImplant"; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnInsert); - SubscribeLocalEvent(OnSpeakerNameTransform); - SubscribeLocalEvent(OnChangeName); - // We need to remove the SyrinxVoiceMaskComponent from the owner before the implant - // is removed, so we need to execute before the SubdermalImplantSystem. - SubscribeLocalEvent(OnRemove, before: new[] { typeof(SubdermalImplantSystem) }); + SubscribeLocalEvent(OnInsert); } - private void OnInsert(EntityUid uid, VoiceMaskerComponent component, ImplantImplantedEvent args) + private void OnInsert(EntityUid uid, VoiceMaskComponent component, ImplantImplantedEvent args) { if (!args.Implanted.HasValue || !_tag.HasTag(args.Implant, BionicSyrinxImplant)) return; - var voicemask = EnsureComp(args.Implanted.Value); - voicemask.VoiceName = MetaData(args.Implanted.Value).EntityName; - Dirty(args.Implanted.Value, voicemask); - } - - private void OnRemove(EntityUid uid, VoiceMaskerComponent component, EntGotRemovedFromContainerMessage args) - { - if (!TryComp(uid, out var implanted) || implanted.ImplantedEntity == null) - return; - - RemComp(implanted.ImplantedEntity.Value); - } - - /// - /// Copy from VoiceMaskSystem, adapted to work with SyrinxVoiceMaskComponent. - /// - private void OnChangeName(EntityUid uid, SyrinxVoiceMaskComponent component, VoiceMaskChangeNameMessage message) - { - if (message.Name.Length > HumanoidCharacterProfile.MaxNameLength || message.Name.Length <= 0) - { - _popupSystem.PopupEntity(Loc.GetString("voice-mask-popup-failure"), uid, message.Session, PopupType.SmallCaution); - return; - } - - component.VoiceName = message.Name; - if (message.Session.AttachedEntity != null) - _adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(message.Session.AttachedEntity.Value):player} set voice of {ToPrettyString(uid):mask}: {component.VoiceName}"); - else - _adminLogger.Add(LogType.Action, LogImpact.Medium, $"Voice of {ToPrettyString(uid):mask} set: {component.VoiceName}"); - - _popupSystem.PopupEntity(Loc.GetString("voice-mask-popup-success"), uid, message.Session); - TrySetLastKnownName(uid, message.Name); - UpdateUI(uid, component); - } - - /// - /// Copy from VoiceMaskSystem, adapted to work with SyrinxVoiceMaskComponent. - /// - private void TrySetLastKnownName(EntityUid implanted, string lastName) - { - if (!HasComp(implanted) - || !TryComp(implanted, out var maskComp)) - return; - - maskComp.LastSetName = lastName; - } - - /// - /// Copy from VoiceMaskSystem, adapted to work with SyrinxVoiceMaskComponent. - /// - private void UpdateUI(EntityUid owner, SyrinxVoiceMaskComponent? component = null) - { - if (!Resolve(owner, ref component, logMissing: false)) - return; - - if (_uiSystem.TryGetUi(owner, VoiceMaskUIKey.Key, out var bui)) - _uiSystem.SetUiState(bui, new VoiceMaskBuiState(component.VoiceName, null)); - } - - /// - /// Copy from VoiceMaskSystem, adapted to work with SyrinxVoiceMaskComponent. - /// - private void OnSpeakerNameTransform(EntityUid uid, SyrinxVoiceMaskComponent component, TransformSpeakerNameEvent args) - { - if (component.Enabled) - args.Name = component.VoiceName; + // Update the name so it's the entities default name. You can't take it off like a voice mask so it's important! + component.VoiceMaskName = Name(args.Implanted.Value); } } diff --git a/Content.Server/DeltaV/ParadoxAnomaly/Systems/ParadoxAnomalySystem.cs b/Content.Server/DeltaV/ParadoxAnomaly/Systems/ParadoxAnomalySystem.cs index 62d994dac3..142669f84a 100644 --- a/Content.Server/DeltaV/ParadoxAnomaly/Systems/ParadoxAnomalySystem.cs +++ b/Content.Server/DeltaV/ParadoxAnomaly/Systems/ParadoxAnomalySystem.cs @@ -6,7 +6,7 @@ using Content.Server.Psionics; using Content.Server.Spawners.Components; using Content.Server.Station.Systems; -using Content.Server.Terminator.Systems; +using Content.Shared.Abilities.Psionics; using Content.Shared.Humanoid; using Content.Shared.Humanoid.Prototypes; using Content.Shared.Mind; @@ -16,7 +16,6 @@ using Content.Shared.Roles.Jobs; using Robust.Shared.Prototypes; using Robust.Shared.Random; -using Robust.Shared.Utility; using System.Diagnostics.CodeAnalysis; namespace Content.Server.DeltaV.ParadoxAnomaly.Systems; @@ -38,7 +37,6 @@ public sealed class ParadoxAnomalySystem : EntitySystem [Dependency] private readonly SharedRoleSystem _role = default!; [Dependency] private readonly StationSystem _station = default!; [Dependency] private readonly StationSpawningSystem _stationSpawning = default!; - [Dependency] private readonly TerminatorSystem _terminator = default!; public override void Initialize() { @@ -123,7 +121,7 @@ private bool TrySpawnParadoxAnomaly(string rule, [NotNullWhen(true)] out EntityU var spawned = Spawn(species.Prototype, destination); // Set the kill target to the chosen player - _terminator.SetTarget(spawned, mindId); + // _terminator.SetTarget(spawned, mindId); _genericAntag.MakeAntag(spawned, rule); ////////////////////////// @@ -144,7 +142,7 @@ private bool TrySpawnParadoxAnomaly(string rule, [NotNullWhen(true)] out EntityU if (job.StartingGear != null && _proto.TryIndex(job.StartingGear, out var gear)) { - _stationSpawning.EquipStartingGear(spawned, gear, profile); + _stationSpawning.EquipStartingGear(spawned, gear); _stationSpawning.EquipIdCard(spawned, profile.Name, job, @@ -156,8 +154,9 @@ private bool TrySpawnParadoxAnomaly(string rule, [NotNullWhen(true)] out EntityU special.AfterEquip(spawned); } - var psi = EnsureComp(spawned); - _psionics.RollPsionics(spawned, psi, false, 100); + // TODO: In a future PR, make it so that the Paradox Anomaly spawns with a completely 1:1 clone of the victim's entire PsionicComponent. + if (HasComp(uid)) + EnsureComp(spawned); return spawned; } diff --git a/Content.Server/DeltaV/Station/Components/StationSurfaceComponent.cs b/Content.Server/DeltaV/Station/Components/StationSurfaceComponent.cs new file mode 100644 index 0000000000..56b2be8f6a --- /dev/null +++ b/Content.Server/DeltaV/Station/Components/StationSurfaceComponent.cs @@ -0,0 +1,23 @@ +using Content.Server.Station.Systems; +using Robust.Shared.Utility; + +namespace Content.Server.Station.Components; + +/// +/// Loads a surface map on mapinit. +/// +[RegisterComponent, Access(typeof(StationSurfaceSystem))] +public sealed partial class StationSurfaceComponent : Component +{ + /// + /// Path to the map to load. + /// + [DataField(required: true)] + public ResPath? MapPath; + + /// + /// The map that was loaded. + /// + [DataField] + public EntityUid? Map; +} diff --git a/Content.Server/DeltaV/Station/Systems/StationSurfaceSystem.cs b/Content.Server/DeltaV/Station/Systems/StationSurfaceSystem.cs new file mode 100644 index 0000000000..387d111735 --- /dev/null +++ b/Content.Server/DeltaV/Station/Systems/StationSurfaceSystem.cs @@ -0,0 +1,41 @@ +using Content.Server.Parallax; +using Content.Server.Station.Components; +using Robust.Server.GameObjects; + +namespace Content.Server.Station.Systems; + +public sealed class StationSurfaceSystem : EntitySystem +{ + [Dependency] private readonly BiomeSystem _biome = default!; + [Dependency] private readonly MapSystem _map = default!; + [Dependency] private readonly MapLoaderSystem _mapLoader = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnMapInit); + } + + private void OnMapInit(Entity ent, ref MapInitEvent args) + { + if (ent.Comp.MapPath is not {} path) + return; + + var map = _map.CreateMap(out var mapId); + if (!_mapLoader.TryLoad(mapId, path.ToString(), out _)) + { + Log.Error($"Failed to load surface map {ent.Comp.MapPath}!"); + Del(map); + return; + } + + // loading replaced the map entity with a new one so get the latest id + map = _map.GetMap(mapId); + _map.SetPaused(map, false); + + // Needs a cherrypick, but this system is unused entirely for now + //_biome.SetEnabled(map); // generate the terrain after the grids loaded to prevent it getting hidden under it + ent.Comp.Map = map; + } +} diff --git a/Content.Server/DeltaV/StationEvents/Components/GlimmerMobRuleComponent.cs b/Content.Server/DeltaV/StationEvents/Components/GlimmerMobRuleComponent.cs deleted file mode 100644 index bde0422a1a..0000000000 --- a/Content.Server/DeltaV/StationEvents/Components/GlimmerMobRuleComponent.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Content.Server.StationEvents.Events; -using Robust.Shared.Prototypes; - -namespace Content.Server.StationEvents.Components; - -[RegisterComponent, Access(typeof(GlimmerMobRule))] -public sealed partial class GlimmerMobRuleComponent : Component -{ - [DataField(required: true)] - public EntProtoId MobPrototype = string.Empty; -} diff --git a/Content.Server/DeltaV/StationEvents/Components/PirateRadioSpawnRuleComponent.cs b/Content.Server/DeltaV/StationEvents/Components/PirateRadioSpawnRuleComponent.cs deleted file mode 100644 index 432fbfb4ac..0000000000 --- a/Content.Server/DeltaV/StationEvents/Components/PirateRadioSpawnRuleComponent.cs +++ /dev/null @@ -1,36 +0,0 @@ -/* -* Delta-V - This file is licensed under AGPLv3 -* Copyright (c) 2024 Delta-V Contributors -* See AGPLv3.txt for details. -*/ - -using Content.Server.StationEvents.Events; - -namespace Content.Server.StationEvents.Components; - -[RegisterComponent, Access(typeof(PirateRadioSpawnRule))] -public sealed partial class PirateRadioSpawnRuleComponent : Component -{ - [DataField("PirateRadioShuttlePath")] - public string PirateRadioShuttlePath = "Maps/Shuttles/DeltaV/DV-pirateradio.yml"; - - [DataField("additionalRule")] - public EntityUid? AdditionalRule; - - [DataField("debrisCount")] - public int DebrisCount { get; set; } - - [DataField("distanceModifier")] - public float DistanceModifier { get; set; } - - [DataField("debrisDistanceModifier")] - public float DebrisDistanceModifier { get; set; } - - /// - /// "Stations of Unusual Size Constant", derived from the AABB.Width of Shoukou. - /// This Constant is used to check the size of a station relative to the reference point - /// - [DataField("sousk")] - public float SOUSK = 123.44f; - -} diff --git a/Content.Server/DeltaV/StationEvents/Events/GlimmerMobSpawnRule.cs b/Content.Server/DeltaV/StationEvents/Events/GlimmerMobSpawnRule.cs deleted file mode 100644 index ec9ec77031..0000000000 --- a/Content.Server/DeltaV/StationEvents/Events/GlimmerMobSpawnRule.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System.Linq; -using Robust.Shared.Random; -using Content.Server.GameTicking.Rules.Components; -using Content.Server.NPC.Components; -using Content.Server.Psionics.Glimmer; -using Content.Server.StationEvents.Components; -using Content.Shared.Psionics.Glimmer; -using Content.Shared.Abilities.Psionics; - -namespace Content.Server.StationEvents.Events; - -public sealed class GlimmerMobRule : StationEventSystem -{ - [Dependency] private readonly IRobustRandom _robustRandom = default!; - [Dependency] private readonly GlimmerSystem _glimmerSystem = default!; - - - protected override void Started(EntityUid uid, GlimmerMobRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args) - { - base.Started(uid, component, gameRule, args); - - var glimmerSources = EntityQuery().ToList(); - var normalSpawnLocations = EntityQuery().ToList(); - var hiddenSpawnLocations = EntityQuery().ToList(); - - var baseCount = Math.Max(1, EntityQuery().Count() / 10); - int multiplier = Math.Max(1, (int) _glimmerSystem.GetGlimmerTier() - 2); - - var total = baseCount * multiplier; - - int i = 0; - while (i < total) - { - if (glimmerSources.Count != 0 && _robustRandom.Prob(0.4f)) - { - Spawn(component.MobPrototype, _robustRandom.Pick(glimmerSources).Item2.Coordinates); - i++; - continue; - } - - if (normalSpawnLocations.Count != 0) - { - Spawn(component.MobPrototype, _robustRandom.Pick(normalSpawnLocations).Item2.Coordinates); - i++; - continue; - } - - if (hiddenSpawnLocations.Count != 0) - { - Spawn(component.MobPrototype, _robustRandom.Pick(hiddenSpawnLocations).Item2.Coordinates); - i++; - continue; - } - } - } -} diff --git a/Content.Server/DeltaV/StationEvents/Events/PirateRadioSpawnRule.cs b/Content.Server/DeltaV/StationEvents/Events/PirateRadioSpawnRule.cs deleted file mode 100644 index ba042d8966..0000000000 --- a/Content.Server/DeltaV/StationEvents/Events/PirateRadioSpawnRule.cs +++ /dev/null @@ -1,101 +0,0 @@ -/* -* Delta-V - This file is licensed under AGPLv3 -* Copyright (c) 2024 Delta-V Contributors -* See AGPLv3.txt for details. -*/ - -using Robust.Server.GameObjects; -using Robust.Server.Maps; -using Robust.Shared.Configuration; -using Robust.Shared.Map; -using Robust.Shared.Map.Components; -using Robust.Shared.Prototypes; -using Robust.Shared.Random; -using Robust.Shared.Utility; -using Content.Server.GameTicking; -using Content.Server.GameTicking.Rules.Components; -using Content.Server.StationEvents.Components; -using Content.Server.Station.Components; -using Content.Shared.Salvage; -using Content.Shared.Random.Helpers; -using System.Linq; -using Content.Shared.CCVar; - -namespace Content.Server.StationEvents.Events; - -public sealed class PirateRadioSpawnRule : StationEventSystem -{ - [Dependency] private readonly IEntityManager _entities = default!; - [Dependency] private readonly IMapManager _mapManager = default!; - [Dependency] private readonly MapLoaderSystem _map = default!; - [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly IConfigurationManager _confMan = default!; - - protected override void Started(EntityUid uid, PirateRadioSpawnRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args) - { - //Start of Syndicate Listening Outpost spawning system - base.Started(uid, component, gameRule, args); - var xformQuery = GetEntityQuery(); - var aabbs = EntityQuery().SelectMany(x => - x.Grids.Select(x => - xformQuery.GetComponent(x).WorldMatrix.TransformBox(_entities.GetComponent(x).LocalAABB))) - .ToArray(); - if (aabbs.Length < 1) return; - var aabb = aabbs[0]; - - for (var i = 1; i < aabbs.Length; i++) - { - aabb.Union(aabbs[i]); - } - var distanceFactorCoefficient = component.SOUSK / aabb.Width; - var distanceModifier = Math.Clamp(component.DistanceModifier, 1, 25); - var distanceModifierNormalized = distanceModifier * distanceFactorCoefficient; - var a = MathF.Max(aabb.Height / 2f, aabb.Width / 2f) * distanceModifierNormalized; - var randomoffset = _random.NextVector2(a, a * 2.5f); - var outpostOptions = new MapLoadOptions - { - Offset = aabb.Center + randomoffset, - LoadMap = false, - }; - if (!_map.TryLoad(GameTicker.DefaultMap, component.PirateRadioShuttlePath, out var outpostids, outpostOptions)) return; - //End of Syndicate Listening Outpost spawning system - - //Start of Debris Field Generation - var debrisSpawner = _confMan.GetCVar(CCVars.WorldgenEnabled); - if (debrisSpawner == true) return; - var debrisCount = Math.Clamp(component.DebrisCount, 0, 6); - if (debrisCount == 0) return; - var debrisDistanceModifier = Math.Clamp(component.DebrisDistanceModifier, 3, 10); - foreach (var id in outpostids) - { - if (!TryComp(id, out var grid)) return; - var outpostaabb = _entities.GetComponent(id).WorldMatrix.TransformBox(grid.LocalAABB); - var b = MathF.Max(outpostaabb.Height / 2f, aabb.Width / 2f) * debrisDistanceModifier; - var k = 1; - while (k < debrisCount + 1) - { - var debrisRandomOffset = _random.NextVector2(b, b * 2.5f); - var randomer = _random.NextVector2(b, b * 5f); //Second random vector to ensure the outpost isn't perfectly centered in the debris field - var debrisOptions = new MapLoadOptions - { - Offset = outpostaabb.Center + debrisRandomOffset + randomer, - LoadMap = false, - }; - - var salvageProto = _random.Pick(_prototypeManager.EnumeratePrototypes().ToList()); - _map.TryLoad(GameTicker.DefaultMap, salvageProto.MapPath.ToString(), out _, debrisOptions); - k++; - } - } - //End of Debris Field generation - } - - protected override void Ended(EntityUid uid, PirateRadioSpawnRuleComponent component, GameRuleComponent gameRule, GameRuleEndedEvent args) - { - base.Ended(uid, component, gameRule, args); - - if (component.AdditionalRule != null) - GameTicker.EndGameRule(component.AdditionalRule.Value); - } -} diff --git a/Content.Server/DeltaV/VoiceMask/SyrinxVoiceMaskComponent.cs b/Content.Server/DeltaV/VoiceMask/SyrinxVoiceMaskComponent.cs deleted file mode 100644 index 97c04039f9..0000000000 --- a/Content.Server/DeltaV/VoiceMask/SyrinxVoiceMaskComponent.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Content.Server.VoiceMask; - -[RegisterComponent] -public sealed partial class SyrinxVoiceMaskComponent : Component -{ - [ViewVariables(VVAccess.ReadWrite)] public bool Enabled = true; - - [ViewVariables(VVAccess.ReadWrite)] public string VoiceName = "Unknown"; -} diff --git a/Content.Server/Destructible/Thresholds/Behaviors/BurnBodyBehavior.cs b/Content.Server/Destructible/Thresholds/Behaviors/BurnBodyBehavior.cs index 28c4b2ef16..f0499dc6a2 100644 --- a/Content.Server/Destructible/Thresholds/Behaviors/BurnBodyBehavior.cs +++ b/Content.Server/Destructible/Thresholds/Behaviors/BurnBodyBehavior.cs @@ -17,12 +17,12 @@ public void Execute(EntityUid bodyId, DestructibleSystem system, EntityUid? caus var inventorySystem = system.EntityManager.System(); var sharedPopupSystem = system.EntityManager.System(); - if (!system.EntityManager.TryGetComponent(bodyId, out var comp)) - return; - - foreach (var item in inventorySystem.GetHandOrInventoryEntities(bodyId)) + if (system.EntityManager.TryGetComponent(bodyId, out var comp)) { - transformSystem.DropNextTo(item, bodyId); + foreach (var item in inventorySystem.GetHandOrInventoryEntities(bodyId)) + { + transformSystem.DropNextTo(item, bodyId); + } } sharedPopupSystem.PopupCoordinates(Loc.GetString("bodyburn-text-others", ("name", bodyId)), transformSystem.GetMoverCoordinates(bodyId), PopupType.LargeCaution); diff --git a/Content.Server/Destructible/Thresholds/Behaviors/GibBehavior.cs b/Content.Server/Destructible/Thresholds/Behaviors/GibBehavior.cs index c83fed1906..da054e24ac 100644 --- a/Content.Server/Destructible/Thresholds/Behaviors/GibBehavior.cs +++ b/Content.Server/Destructible/Thresholds/Behaviors/GibBehavior.cs @@ -1,4 +1,5 @@ using Content.Shared.Body.Components; +using Content.Shared.Gibbing.Events; using JetBrains.Annotations; namespace Content.Server.Destructible.Thresholds.Behaviors @@ -7,13 +8,15 @@ namespace Content.Server.Destructible.Thresholds.Behaviors [DataDefinition] public sealed partial class GibBehavior : IThresholdBehavior { + [DataField] public GibType GibType = GibType.Gib; + [DataField] public GibContentsOption GibContents = GibContentsOption.Drop; [DataField("recursive")] private bool _recursive = true; public void Execute(EntityUid owner, DestructibleSystem system, EntityUid? cause = null) { if (system.EntityManager.TryGetComponent(owner, out BodyComponent? body)) { - system.BodySystem.GibBody(owner, _recursive, body); + system.BodySystem.GibBody(owner, _recursive, body, gib: GibType, contents: GibContents); } } } diff --git a/Content.Server/Destructible/Thresholds/Behaviors/GibPartBehavior.cs b/Content.Server/Destructible/Thresholds/Behaviors/GibPartBehavior.cs new file mode 100644 index 0000000000..f9e39ba884 --- /dev/null +++ b/Content.Server/Destructible/Thresholds/Behaviors/GibPartBehavior.cs @@ -0,0 +1,19 @@ +using Content.Shared.Body.Components; +using Content.Shared.Body.Part; +using JetBrains.Annotations; + +namespace Content.Server.Destructible.Thresholds.Behaviors; + +[UsedImplicitly] +[DataDefinition] +public sealed partial class GibPartBehavior : IThresholdBehavior +{ + public void Execute(EntityUid owner, DestructibleSystem system, EntityUid? cause = null) + { + if (!system.EntityManager.TryGetComponent(owner, out BodyPartComponent? part)) + return; + + system.BodySystem.GibPart(owner, part); + } +} + diff --git a/Content.Server/Destructible/Thresholds/Behaviors/PopupBehavior.cs b/Content.Server/Destructible/Thresholds/Behaviors/PopupBehavior.cs index 59589c19aa..1d7ce24f60 100644 --- a/Content.Server/Destructible/Thresholds/Behaviors/PopupBehavior.cs +++ b/Content.Server/Destructible/Thresholds/Behaviors/PopupBehavior.cs @@ -1,6 +1,6 @@ using Content.Shared.Popups; -namespace Content.Server.Destructible.Thresholds.Behaviors; +namespace Content.Server.Destructible.Thresholds.Behaviors; /// /// Shows a popup for everyone. diff --git a/Content.Server/Destructible/Thresholds/Behaviors/SpawnGasBehavior.cs b/Content.Server/Destructible/Thresholds/Behaviors/SpawnGasBehavior.cs index fec93a87f4..81fb07ecfc 100644 --- a/Content.Server/Destructible/Thresholds/Behaviors/SpawnGasBehavior.cs +++ b/Content.Server/Destructible/Thresholds/Behaviors/SpawnGasBehavior.cs @@ -1,4 +1,5 @@ using Content.Server.Atmos; +using Content.Shared.Atmos; using JetBrains.Annotations; namespace Content.Server.Destructible.Thresholds.Behaviors; diff --git a/Content.Server/Destructible/Thresholds/MinMax.cs b/Content.Server/Destructible/Thresholds/MinMax.cs index b438e7c0e8..c44864183a 100644 --- a/Content.Server/Destructible/Thresholds/MinMax.cs +++ b/Content.Server/Destructible/Thresholds/MinMax.cs @@ -1,4 +1,6 @@ -namespace Content.Server.Destructible.Thresholds +using Robust.Shared.Random; + +namespace Content.Server.Destructible.Thresholds { [Serializable] [DataDefinition] @@ -9,5 +11,16 @@ public partial struct MinMax [DataField("max")] public int Max; + + public MinMax(int min, int max) + { + Min = min; + Max = max; + } + + public int Next(IRobustRandom random) + { + return random.Next(Min, Max + 1); + } } } diff --git a/Content.Server/DeviceLinking/Components/PowerSensorComponent.cs b/Content.Server/DeviceLinking/Components/PowerSensorComponent.cs index d9599546ae..b67a2fcd95 100644 --- a/Content.Server/DeviceLinking/Components/PowerSensorComponent.cs +++ b/Content.Server/DeviceLinking/Components/PowerSensorComponent.cs @@ -1,5 +1,6 @@ using Content.Server.DeviceLinking.Systems; using Content.Shared.DeviceLinking; +using Content.Shared.Power.Generator; using Content.Shared.Tools; using Robust.Shared.Audio; using Robust.Shared.Prototypes; diff --git a/Content.Server/DeviceLinking/Systems/SignalTimerSystem.cs b/Content.Server/DeviceLinking/Systems/SignalTimerSystem.cs index 0e214ee865..14e0c75d96 100644 --- a/Content.Server/DeviceLinking/Systems/SignalTimerSystem.cs +++ b/Content.Server/DeviceLinking/Systems/SignalTimerSystem.cs @@ -48,9 +48,9 @@ private void OnAfterActivatableUIOpen(EntityUid uid, SignalTimerComponent compon { var time = TryComp(uid, out var active) ? active.TriggerTime : TimeSpan.Zero; - if (_ui.TryGetUi(uid, SignalTimerUiKey.Key, out var bui)) + if (_ui.HasUi(uid, SignalTimerUiKey.Key)) { - _ui.SetUiState(bui, new SignalTimerBoundUserInterfaceState(component.Label, + _ui.SetUiState(uid, SignalTimerUiKey.Key, new SignalTimerBoundUserInterfaceState(component.Label, TimeSpan.FromSeconds(component.Delay).Minutes.ToString("D2"), TimeSpan.FromSeconds(component.Delay).Seconds.ToString("D2"), component.CanEditLabel, @@ -70,9 +70,9 @@ public void Trigger(EntityUid uid, SignalTimerComponent signalTimer) _audio.PlayPvs(signalTimer.DoneSound, uid); _signalSystem.InvokePort(uid, signalTimer.TriggerPort); - if (_ui.TryGetUi(uid, SignalTimerUiKey.Key, out var bui)) + if (_ui.HasUi(uid, SignalTimerUiKey.Key)) { - _ui.SetUiState(bui, new SignalTimerBoundUserInterfaceState(signalTimer.Label, + _ui.SetUiState(uid, SignalTimerUiKey.Key, new SignalTimerBoundUserInterfaceState(signalTimer.Label, TimeSpan.FromSeconds(signalTimer.Delay).Minutes.ToString("D2"), TimeSpan.FromSeconds(signalTimer.Delay).Seconds.ToString("D2"), signalTimer.CanEditLabel, @@ -117,10 +117,7 @@ private void UpdateTimer() /// The entity that is interacted with. private bool IsMessageValid(EntityUid uid, BoundUserInterfaceMessage message) { - if (message.Session.AttachedEntity is not { Valid: true } mob) - return false; - - if (!_accessReader.IsAllowed(mob, uid)) + if (!_accessReader.IsAllowed(message.Actor, uid)) return false; return true; diff --git a/Content.Server/DeviceNetwork/Systems/NetworkConfiguratorSystem.cs b/Content.Server/DeviceNetwork/Systems/NetworkConfiguratorSystem.cs index 9a038f1c78..dc1bf499df 100644 --- a/Content.Server/DeviceNetwork/Systems/NetworkConfiguratorSystem.cs +++ b/Content.Server/DeviceNetwork/Systems/NetworkConfiguratorSystem.cs @@ -62,7 +62,6 @@ public override void Initialize() SubscribeLocalEvent(OnClearLinks); SubscribeLocalEvent(OnToggleLinks); SubscribeLocalEvent(OnConfigButtonPressed); - SubscribeLocalEvent(OnUiOpenAttempt); SubscribeLocalEvent(OnComponentRemoved); } @@ -89,7 +88,7 @@ public override void Update(float frameTime) continue; //The network configurator is a handheld device. There can only ever be an ui session open for the player holding the device. - _uiSystem.TryCloseAll(uid, NetworkConfiguratorUiKey.Configure); + _uiSystem.CloseUi(uid, NetworkConfiguratorUiKey.Configure); } } @@ -215,7 +214,7 @@ private bool AccessCheck(EntityUid target, EntityUid? user, NetworkConfiguratorC private void OnComponentRemoved(EntityUid uid, DeviceListComponent component, ComponentRemove args) { - _uiSystem.TryCloseAll(uid, NetworkConfiguratorUiKey.Configure); + _uiSystem.CloseUi(uid, NetworkConfiguratorUiKey.Configure); } /// @@ -433,7 +432,7 @@ private void OpenDeviceLinkUi(EntityUid configuratorUid, EntityUid? targetUid, E return; - _uiSystem.TryOpen(configuratorUid, NetworkConfiguratorUiKey.Link, actor.PlayerSession); + _uiSystem.OpenUi(configuratorUid, NetworkConfiguratorUiKey.Link, actor.PlayerSession); configurator.DeviceLinkTarget = targetUid; @@ -464,7 +463,7 @@ private void UpdateLinkUiState(EntityUid configuratorUid, EntityUid sourceUid, E var sinkAddress = Resolve(sinkUid, ref sinkNetworkComponent, false) ? sinkNetworkComponent.Address : ""; var state = new DeviceLinkUserInterfaceState(sources, sinks, links, sourceAddress, sinkAddress, defaults); - _uiSystem.TrySetUiState(configuratorUid, NetworkConfiguratorUiKey.Link, state); + _uiSystem.SetUiState(configuratorUid, NetworkConfiguratorUiKey.Link, state); } /// @@ -478,7 +477,7 @@ private void OpenDeviceListUi(EntityUid configuratorUid, EntityUid? targetUid, E if (Delay(configurator)) return; - if (!targetUid.HasValue || !TryComp(userUid, out ActorComponent? actor) || !AccessCheck(targetUid.Value, userUid, configurator)) + if (!targetUid.HasValue || !AccessCheck(targetUid.Value, userUid, configurator)) return; if (!TryComp(targetUid, out DeviceListComponent? list)) @@ -488,14 +487,13 @@ private void OpenDeviceListUi(EntityUid configuratorUid, EntityUid? targetUid, E configurator.ActiveDeviceList = targetUid; Dirty(configuratorUid, configurator); - if (!_uiSystem.TryGetUi(configuratorUid, NetworkConfiguratorUiKey.Configure, out var bui)) - return; - - if (_uiSystem.OpenUi(bui, actor.PlayerSession)) - _uiSystem.SetUiState(bui, new DeviceListUserInterfaceState( + if (_uiSystem.TryOpenUi(configuratorUid, NetworkConfiguratorUiKey.Configure, userUid)) + { + _uiSystem.SetUiState(configuratorUid, NetworkConfiguratorUiKey.Configure, new DeviceListUserInterfaceState( _deviceListSystem.GetDeviceList(configurator.ActiveDeviceList.Value) .Select(v => (v.Key, MetaData(v.Value).EntityName)).ToHashSet() )); + } } /// @@ -523,8 +521,7 @@ private void UpdateListUiState(EntityUid uid, NetworkConfiguratorComponent compo component.Devices.Remove(invalidDevice); } - if (_uiSystem.TryGetUi(uid, NetworkConfiguratorUiKey.List, out var bui)) - _uiSystem.SetUiState(bui, new NetworkConfiguratorUserInterfaceState(devices)); + _uiSystem.SetUiState(uid, NetworkConfiguratorUiKey.List, new NetworkConfiguratorUserInterfaceState(devices)); } /// @@ -565,10 +562,10 @@ public void OnDeviceListShutdown(Entity conf, Ent /// private void OnRemoveDevice(EntityUid uid, NetworkConfiguratorComponent component, NetworkConfiguratorRemoveDeviceMessage args) { - if (component.Devices.TryGetValue(args.Address, out var removedDevice) && args.Session.AttachedEntity != null) + if (component.Devices.TryGetValue(args.Address, out var removedDevice)) { _adminLogger.Add(LogType.DeviceLinking, LogImpact.Low, - $"{ToPrettyString(args.Session.AttachedEntity.Value):actor} removed buffered device {ToPrettyString(removedDevice):subject} from {ToPrettyString(uid):tool}"); + $"{ToPrettyString(args.Actor):actor} removed buffered device {ToPrettyString(removedDevice):subject} from {ToPrettyString(uid):tool}"); } component.Devices.Remove(args.Address); @@ -583,10 +580,8 @@ private void OnRemoveDevice(EntityUid uid, NetworkConfiguratorComponent componen /// private void OnClearDevice(EntityUid uid, NetworkConfiguratorComponent component, NetworkConfiguratorClearDevicesMessage args) { - if (args.Session.AttachedEntity != null) - _adminLogger.Add(LogType.DeviceLinking, LogImpact.Low, - $"{ToPrettyString(args.Session.AttachedEntity.Value):actor} cleared buffered devices from {ToPrettyString(uid):tool}"); - + _adminLogger.Add(LogType.DeviceLinking, LogImpact.Low, + $"{ToPrettyString(args.Actor):actor} cleared buffered devices from {ToPrettyString(uid):tool}"); ClearDevices(uid, component); UpdateListUiState(uid, component); @@ -609,9 +604,8 @@ private void OnClearLinks(EntityUid uid, NetworkConfiguratorComponent configurat if (!configurator.ActiveDeviceLink.HasValue || !configurator.DeviceLinkTarget.HasValue) return; - if (args.Session.AttachedEntity != null) - _adminLogger.Add(LogType.DeviceLinking, LogImpact.Low, - $"{ToPrettyString(args.Session.AttachedEntity.Value):actor} cleared links between {ToPrettyString(configurator.ActiveDeviceLink.Value):subject} and {ToPrettyString(configurator.DeviceLinkTarget.Value):subject2} with {ToPrettyString(uid):tool}"); + _adminLogger.Add(LogType.DeviceLinking, LogImpact.Low, + $"{ToPrettyString(args.Actor):actor} cleared links between {ToPrettyString(configurator.ActiveDeviceLink.Value):subject} and {ToPrettyString(configurator.DeviceLinkTarget.Value):subject2} with {ToPrettyString(uid):tool}"); if (HasComp(configurator.ActiveDeviceLink) && HasComp(configurator.DeviceLinkTarget)) { @@ -649,7 +643,7 @@ private void OnToggleLinks(EntityUid uid, NetworkConfiguratorComponent configura if (TryComp(configurator.ActiveDeviceLink, out DeviceLinkSourceComponent? activeSource) && TryComp(configurator.DeviceLinkTarget, out DeviceLinkSinkComponent? targetSink)) { _deviceLinkSystem.ToggleLink( - args.Session.AttachedEntity, + args.Actor, configurator.ActiveDeviceLink.Value, configurator.DeviceLinkTarget.Value, args.Source, args.Sink, @@ -660,7 +654,7 @@ private void OnToggleLinks(EntityUid uid, NetworkConfiguratorComponent configura else if (TryComp(configurator.DeviceLinkTarget, out DeviceLinkSourceComponent? targetSource) && TryComp(configurator.ActiveDeviceLink, out DeviceLinkSinkComponent? activeSink)) { _deviceLinkSystem.ToggleLink( - args.Session.AttachedEntity, + args.Actor, configurator.DeviceLinkTarget.Value, configurator.ActiveDeviceLink.Value, args.Source, args.Sink, @@ -687,7 +681,7 @@ private void OnSaveLinks(EntityUid uid, NetworkConfiguratorComponent configurato if (TryComp(configurator.ActiveDeviceLink, out DeviceLinkSourceComponent? activeSource) && TryComp(configurator.DeviceLinkTarget, out DeviceLinkSinkComponent? targetSink)) { _deviceLinkSystem.SaveLinks( - args.Session.AttachedEntity, + args.Actor, configurator.ActiveDeviceLink.Value, configurator.DeviceLinkTarget.Value, args.Links, @@ -705,7 +699,7 @@ private void OnSaveLinks(EntityUid uid, NetworkConfiguratorComponent configurato else if (TryComp(configurator.DeviceLinkTarget, out DeviceLinkSourceComponent? targetSource) && TryComp(configurator.ActiveDeviceLink, out DeviceLinkSinkComponent? activeSink)) { _deviceLinkSystem.SaveLinks( - args.Session.AttachedEntity, + args.Actor, configurator.DeviceLinkTarget.Value, configurator.ActiveDeviceLink.Value, args.Links, @@ -735,29 +729,25 @@ private void OnConfigButtonPressed(EntityUid uid, NetworkConfiguratorComponent c switch (args.ButtonKey) { case NetworkConfiguratorButtonKey.Set: - if (args.Session.AttachedEntity != null) - _adminLogger.Add(LogType.DeviceLinking, LogImpact.Low, - $"{ToPrettyString(args.Session.AttachedEntity.Value):actor} set device links to {ToPrettyString(component.ActiveDeviceList.Value):subject} with {ToPrettyString(uid):tool}"); + _adminLogger.Add(LogType.DeviceLinking, LogImpact.Low, + $"{ToPrettyString(args.Actor):actor} set device links to {ToPrettyString(component.ActiveDeviceList.Value):subject} with {ToPrettyString(uid):tool}"); result = _deviceListSystem.UpdateDeviceList(component.ActiveDeviceList.Value, new HashSet(component.Devices.Values)); break; case NetworkConfiguratorButtonKey.Add: - if (args.Session.AttachedEntity != null) - _adminLogger.Add(LogType.DeviceLinking, LogImpact.Low, - $"{ToPrettyString(args.Session.AttachedEntity.Value):actor} added device links to {ToPrettyString(component.ActiveDeviceList.Value):subject} with {ToPrettyString(uid):tool}"); + _adminLogger.Add(LogType.DeviceLinking, LogImpact.Low, + $"{ToPrettyString(args.Actor):actor} added device links to {ToPrettyString(component.ActiveDeviceList.Value):subject} with {ToPrettyString(uid):tool}"); result = _deviceListSystem.UpdateDeviceList(component.ActiveDeviceList.Value, new HashSet(component.Devices.Values), true); break; case NetworkConfiguratorButtonKey.Clear: - if (args.Session.AttachedEntity != null) - _adminLogger.Add(LogType.DeviceLinking, LogImpact.Low, - $"{ToPrettyString(args.Session.AttachedEntity.Value):actor} cleared device links from {ToPrettyString(component.ActiveDeviceList.Value):subject} with {ToPrettyString(uid):tool}"); + _adminLogger.Add(LogType.DeviceLinking, LogImpact.Low, + $"{ToPrettyString(args.Actor):actor} cleared device links from {ToPrettyString(component.ActiveDeviceList.Value):subject} with {ToPrettyString(uid):tool}"); result = _deviceListSystem.UpdateDeviceList(component.ActiveDeviceList.Value, new HashSet()); break; case NetworkConfiguratorButtonKey.Copy: - if (args.Session.AttachedEntity != null) - _adminLogger.Add(LogType.DeviceLinking, LogImpact.Low, - $"{ToPrettyString(args.Session.AttachedEntity.Value):actor} copied devices from {ToPrettyString(component.ActiveDeviceList.Value):subject} to {ToPrettyString(uid):tool}"); + _adminLogger.Add(LogType.DeviceLinking, LogImpact.Low, + $"{ToPrettyString(args.Actor):actor} copied devices from {ToPrettyString(component.ActiveDeviceList.Value):subject} to {ToPrettyString(uid):tool}"); ClearDevices(uid, component); @@ -783,8 +773,8 @@ private void OnConfigButtonPressed(EntityUid uid, NetworkConfiguratorComponent c _ => "error" }; - _popupSystem.PopupCursor(Loc.GetString(resultText), args.Session, PopupType.Medium); - _uiSystem.TrySetUiState( + _popupSystem.PopupCursor(Loc.GetString(resultText), args.Actor, PopupType.Medium); + _uiSystem.SetUiState( uid, NetworkConfiguratorUiKey.Configure, new DeviceListUserInterfaceState( diff --git a/Content.Server/Devour/DevourSystem.cs b/Content.Server/Devour/DevourSystem.cs index febbd093a6..d9c50f260a 100644 --- a/Content.Server/Devour/DevourSystem.cs +++ b/Content.Server/Devour/DevourSystem.cs @@ -1,3 +1,4 @@ +using Content.Server.Body.Components; using Content.Server.Body.Systems; using Content.Shared.Chemistry.Components; using Content.Shared.Devour; @@ -15,6 +16,7 @@ public override void Initialize() base.Initialize(); SubscribeLocalEvent(OnDoAfter); + SubscribeLocalEvent(OnGibContents); } private void OnDoAfter(EntityUid uid, DevourerComponent component, DevourDoAfterEvent args) @@ -45,5 +47,15 @@ private void OnDoAfter(EntityUid uid, DevourerComponent component, DevourDoAfter _audioSystem.PlayPvs(component.SoundDevour, uid); } + + private void OnGibContents(EntityUid uid, DevourerComponent component, ref BeingGibbedEvent args) + { + if (!component.ShouldStoreDevoured) + return; + + // For some reason we have two different systems that should handle gibbing, + // and for some another reason GibbingSystem, which should empty all containers, doesn't get involved in this process + ContainerSystem.EmptyContainer(component.Stomach); + } } diff --git a/Content.Server/Disposal/Mailing/MailingUnitSystem.cs b/Content.Server/Disposal/Mailing/MailingUnitSystem.cs index 001abad3dc..8e9c9e4ba7 100644 --- a/Content.Server/Disposal/Mailing/MailingUnitSystem.cs +++ b/Content.Server/Disposal/Mailing/MailingUnitSystem.cs @@ -159,8 +159,7 @@ private void HandleActivate(EntityUid uid, MailingUnitComponent component, Activ args.Handled = true; UpdateTargetList(uid, component); - if (_userInterfaceSystem.TryGetUi(uid, MailingUnitUiKey.Key, out var bui)) - _userInterfaceSystem.OpenUi(bui, actor.PlayerSession); + _userInterfaceSystem.OpenUi(uid, MailingUnitUiKey.Key, actor.PlayerSession); } /// @@ -178,8 +177,7 @@ private void UpdateUserInterface(EntityUid uid, MailingUnitComponent component) return; var state = new MailingUnitBoundUserInterfaceState(component.DisposalUnitInterfaceState, component.Target, component.TargetList, component.Tag); - if (_userInterfaceSystem.TryGetUi(uid, MailingUnitUiKey.Key, out var bui)) - _userInterfaceSystem.SetUiState(bui, state); + _userInterfaceSystem.SetUiState(uid, MailingUnitUiKey.Key, state); } private void OnTargetSelected(EntityUid uid, MailingUnitComponent component, TargetSelectedMessage args) diff --git a/Content.Server/Disposal/Tube/DisposalTubeSystem.cs b/Content.Server/Disposal/Tube/DisposalTubeSystem.cs index 6c0bced53e..f0f6e9142c 100644 --- a/Content.Server/Disposal/Tube/DisposalTubeSystem.cs +++ b/Content.Server/Disposal/Tube/DisposalTubeSystem.cs @@ -101,8 +101,9 @@ private void OnUiAction(EntityUid uid, DisposalTaggerComponent tagger, SharedDis /// A user interface message from the client. private void OnUiAction(EntityUid uid, DisposalRouterComponent router, SharedDisposalRouterComponent.UiActionMessage msg) { - if (!EntityManager.EntityExists(msg.Session.AttachedEntity)) + if (!EntityManager.EntityExists(msg.Actor)) return; + if (TryComp(uid, out var physBody) && physBody.BodyType != BodyType.Static) return; @@ -279,9 +280,9 @@ private void OnOpenRouterUI(EntityUid uid, DisposalRouterComponent router, Bound private void OnOpenTaggerUI(EntityUid uid, DisposalTaggerComponent tagger, BoundUIOpenedEvent args) { - if (_uiSystem.TryGetUi(uid, DisposalTaggerUiKey.Key, out var bui)) + if (_uiSystem.HasUi(uid, DisposalTaggerUiKey.Key)) { - _uiSystem.SetUiState(bui, + _uiSystem.SetUiState(uid, DisposalTaggerUiKey.Key, new DisposalTaggerUserInterfaceState(tagger.Tag)); } } @@ -292,13 +293,9 @@ private void OnOpenTaggerUI(EntityUid uid, DisposalTaggerComponent tagger, Bound /// Returns a private void UpdateRouterUserInterface(EntityUid uid, DisposalRouterComponent router) { - var bui = _uiSystem.GetUiOrNull(uid, DisposalRouterUiKey.Key); - if (bui == null) - return; - if (router.Tags.Count <= 0) { - _uiSystem.SetUiState(bui, new DisposalRouterUserInterfaceState("")); + _uiSystem.SetUiState(uid, DisposalRouterUiKey.Key, new DisposalRouterUserInterfaceState("")); return; } @@ -312,7 +309,7 @@ private void UpdateRouterUserInterface(EntityUid uid, DisposalRouterComponent ro taglist.Remove(taglist.Length - 2, 2); - _uiSystem.SetUiState(bui, new DisposalRouterUserInterfaceState(taglist.ToString())); + _uiSystem.SetUiState(uid, DisposalRouterUiKey.Key, new DisposalRouterUserInterfaceState(taglist.ToString())); } private void OnAnchorChange(EntityUid uid, DisposalTubeComponent component, ref AnchorStateChangedEvent args) diff --git a/Content.Server/Disposal/Unit/Components/DisposalHolderComponent.cs b/Content.Server/Disposal/Unit/Components/DisposalHolderComponent.cs index 3df2182b41..690b33968b 100644 --- a/Content.Server/Disposal/Unit/Components/DisposalHolderComponent.cs +++ b/Content.Server/Disposal/Unit/Components/DisposalHolderComponent.cs @@ -1,5 +1,6 @@ using Content.Server.Atmos; using Content.Server.Disposal.Tube.Components; +using Content.Shared.Atmos; using Robust.Shared.Containers; namespace Content.Server.Disposal.Unit.Components diff --git a/Content.Server/Disposal/Unit/EntitySystems/DisposalUnitSystem.cs b/Content.Server/Disposal/Unit/EntitySystems/DisposalUnitSystem.cs index d6647bbf2e..3e81ebfb79 100644 --- a/Content.Server/Disposal/Unit/EntitySystems/DisposalUnitSystem.cs +++ b/Content.Server/Disposal/Unit/EntitySystems/DisposalUnitSystem.cs @@ -42,7 +42,6 @@ namespace Content.Server.Disposal.Unit.EntitySystems; public sealed class DisposalUnitSystem : SharedDisposalUnitSystem { [Dependency] private readonly IAdminLogManager _adminLogger = default!; - [Dependency] private readonly IRobustRandom _robustRandom = default!; [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; [Dependency] private readonly AppearanceSystem _appearance = default!; [Dependency] private readonly AtmosphereSystem _atmosSystem = default!; @@ -73,8 +72,6 @@ public override void Initialize() SubscribeLocalEvent(OnPowerChange); SubscribeLocalEvent(OnDisposalInit); - SubscribeLocalEvent(OnThrowCollide); - SubscribeLocalEvent(OnActivate); SubscribeLocalEvent(OnAfterInteractUsing); SubscribeLocalEvent(OnDragDropOn); @@ -143,6 +140,9 @@ private void AddClimbInsideVerb(EntityUid uid, SharedDisposalUnitComponent compo return; } + if (!CanInsert(uid, component, args.User)) + return; + // Add verb to climb inside of the unit, Verb verb = new() { @@ -219,7 +219,7 @@ public override void Update(float frameTime) #region UI Handlers private void OnUiButtonPressed(EntityUid uid, SharedDisposalUnitComponent component, SharedDisposalUnitComponent.UiButtonPressedMessage args) { - if (args.Session.AttachedEntity is not { Valid: true } player) + if (args.Actor is not { Valid: true } player) { return; } @@ -235,7 +235,7 @@ private void OnUiButtonPressed(EntityUid uid, SharedDisposalUnitComponent compon _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(player):player} hit flush button on {ToPrettyString(uid)}, it's now {(component.Engaged ? "on" : "off")}"); break; case SharedDisposalUnitComponent.UiButton.Power: - _power.TryTogglePower(uid, user: args.Session.AttachedEntity); + _power.TryTogglePower(uid, user: args.Actor); break; default: throw new ArgumentOutOfRangeException($"{ToPrettyString(player):player} attempted to hit a nonexistant button on {ToPrettyString(uid)}"); @@ -268,7 +268,7 @@ private void OnActivate(EntityUid uid, SharedDisposalUnitComponent component, Ac } args.Handled = true; - _ui.TryOpen(uid, SharedDisposalUnitComponent.DisposalUnitUiKey.Key, actor.PlayerSession); + _ui.OpenUi(uid, SharedDisposalUnitComponent.DisposalUnitUiKey.Key, actor.PlayerSession); } private void OnAfterInteractUsing(EntityUid uid, SharedDisposalUnitComponent component, AfterInteractUsingEvent args) @@ -291,40 +291,6 @@ private void OnAfterInteractUsing(EntityUid uid, SharedDisposalUnitComponent com args.Handled = true; } - /// - /// Thrown items have a chance of bouncing off the unit and not going in. - /// - private void OnThrowCollide(EntityUid uid, SharedDisposalUnitComponent component, ThrowHitByEvent args) - { - var canInsert = CanInsert(uid, component, args.Thrown); - var randDouble = _robustRandom.NextDouble(); - - if (!canInsert) - { - return; - } - - if (randDouble > 0.75) - { - _audioSystem.PlayPvs(component.MissSound, uid); - - _popupSystem.PopupEntity(Loc.GetString("disposal-unit-thrown-missed"), uid); - return; - } - - var inserted = _containerSystem.Insert(args.Thrown, component.Container); - - if (!inserted) - { - throw new InvalidOperationException("Container insertion failed but CanInsert returned true"); - } - - if (args.Component.Thrower != null) - _adminLogger.Add(LogType.Landed, LogImpact.Low, $"{ToPrettyString(args.Thrown)} thrown by {ToPrettyString(args.Component.Thrower.Value):player} landed in {ToPrettyString(uid)}"); - - AfterInsert(uid, component, args.Thrown); - } - private void OnDisposalInit(EntityUid uid, SharedDisposalUnitComponent component, ComponentInit args) { component.Container = _containerSystem.EnsureContainer(uid, SharedDisposalUnitComponent.ContainerId); @@ -598,7 +564,7 @@ public void UpdateInterface(EntityUid uid, SharedDisposalUnitComponent component var compState = GetState(uid, component); var stateString = Loc.GetString($"disposal-unit-state-{compState}"); var state = new SharedDisposalUnitComponent.DisposalUnitBoundUserInterfaceState(Name(uid), stateString, EstimatedFullPressure(uid, component), powered, component.Engaged); - _ui.TrySetUiState(uid, SharedDisposalUnitComponent.DisposalUnitUiKey.Key, state); + _ui.SetUiState(uid, SharedDisposalUnitComponent.DisposalUnitUiKey.Key, state); var stateUpdatedEvent = new DisposalUnitUIStateUpdatedEvent(state); RaiseLocalEvent(uid, stateUpdatedEvent); @@ -803,10 +769,7 @@ public void AfterInsert(EntityUid uid, SharedDisposalUnitComponent component, En QueueAutomaticEngage(uid, component); - if (TryComp(inserted, out ActorComponent? actor)) - { - _ui.TryClose(uid, SharedDisposalUnitComponent.DisposalUnitUiKey.Key, actor.PlayerSession); - } + _ui.CloseUi(uid, SharedDisposalUnitComponent.DisposalUnitUiKey.Key, inserted); // Maybe do pullable instead? Eh still fine. Joints.RecursiveClearJoints(inserted); diff --git a/Content.Server/Doors/Electronics/Systems/DoorElectronicsSystem.cs b/Content.Server/Doors/Electronics/Systems/DoorElectronicsSystem.cs index 56e8bd50b3..af9ccadd91 100644 --- a/Content.Server/Doors/Electronics/Systems/DoorElectronicsSystem.cs +++ b/Content.Server/Doors/Electronics/Systems/DoorElectronicsSystem.cs @@ -39,7 +39,7 @@ public void UpdateUserInterface(EntityUid uid, DoorElectronicsComponent componen } var state = new DoorElectronicsConfigurationState(accesses); - _uiSystem.TrySetUiState(uid, DoorElectronicsConfigurationUiKey.Key, state); + _uiSystem.SetUiState(uid, DoorElectronicsConfigurationUiKey.Key, state); } private void OnChangeConfiguration( diff --git a/Content.Server/Dragon/DragonRiftSystem.cs b/Content.Server/Dragon/DragonRiftSystem.cs index 776aa841d8..c0a81d0d24 100644 --- a/Content.Server/Dragon/DragonRiftSystem.cs +++ b/Content.Server/Dragon/DragonRiftSystem.cs @@ -72,9 +72,8 @@ public override void Update(float frameTime) comp.State = DragonRiftState.AlmostFinished; Dirty(comp); - var location = xform.LocalPosition; _announcer.SendAnnouncement(_announcer.GetAnnouncementId("CarpRift"), Filter.Broadcast(), - "carp-rift-warning", colorOverride: Color.Red, localeArgs: ("location", location)); + "carp-rift-warning", colorOverride: Color.Red, localeArgs: ("location", FormattedMessage.RemoveMarkupPermissive(_navMap.GetNearestBeaconString((uid, xform))))); _navMap.SetBeaconEnabled(uid, true); } diff --git a/Content.Server/Electrocution/Components/ElectrifiedComponent.cs b/Content.Server/Electrocution/Components/ElectrifiedComponent.cs index 65a539eb08..4ef07a0cca 100644 --- a/Content.Server/Electrocution/Components/ElectrifiedComponent.cs +++ b/Content.Server/Electrocution/Components/ElectrifiedComponent.cs @@ -85,4 +85,7 @@ public sealed partial class ElectrifiedComponent : Component [DataField("shockVolume")] public float ShockVolume = 20; + + [DataField] + public float Probability = 1f; } diff --git a/Content.Server/Electrocution/ElectrocutionSystem.cs b/Content.Server/Electrocution/ElectrocutionSystem.cs index 1163306282..4db815a394 100644 --- a/Content.Server/Electrocution/ElectrocutionSystem.cs +++ b/Content.Server/Electrocution/ElectrocutionSystem.cs @@ -62,7 +62,9 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem [ValidatePrototypeId] private const string DamageType = "Shock"; - // Multiply and shift the log scale for shock damage. + // Yes, this is absurdly small for a reason. + public const float ElectrifiedDamagePerWatt = 0.0015f; // This information is allowed to be public, and was needed in BatteryElectrocuteChargeSystem.cs + private const float RecursiveDamageMultiplier = 0.75f; private const float RecursiveTimeMultiplier = 0.8f; @@ -213,6 +215,9 @@ public bool TryDoElectrifiedAct(EntityUid uid, EntityUid targetUid, if (!IsPowered(uid, electrified, transform)) return false; + if (!_random.Prob(electrified.Probability)) + return false; + EnsureComp(uid); _appearance.SetData(uid, ElectrifiedVisuals.IsPowered, true); @@ -297,9 +302,9 @@ public override bool TryDoElectrocution( || !DoCommonElectrocution(uid, sourceUid, shockDamage, time, refresh, siemensCoefficient, statusEffects)) return false; - RaiseLocalEvent(uid, new ElectrocutedEvent(uid, sourceUid, siemensCoefficient), true); - return true; - } + RaiseLocalEvent(uid, new ElectrocutedEvent(uid, sourceUid, siemensCoefficient, shockDamage), true); + return true; + } private bool TryDoElectrocutionPowered( EntityUid uid, @@ -347,7 +352,7 @@ private bool TryDoElectrocutionPowered( electrocutionComponent.Electrocuting = uid; electrocutionComponent.Source = sourceUid; - RaiseLocalEvent(uid, new ElectrocutedEvent(uid, sourceUid, siemensCoefficient), true); + RaiseLocalEvent(uid, new ElectrocutedEvent(uid, sourceUid, siemensCoefficient, shockDamage), true); return true; } diff --git a/Content.Server/Ensnaring/EnsnareableSystem.cs b/Content.Server/Ensnaring/EnsnareableSystem.cs index f939e087e0..7b810b4f49 100644 --- a/Content.Server/Ensnaring/EnsnareableSystem.cs +++ b/Content.Server/Ensnaring/EnsnareableSystem.cs @@ -14,7 +14,7 @@ public sealed partial class EnsnareableSystem : SharedEnsnareableSystem [Dependency] private readonly ContainerSystem _container = default!; [Dependency] private readonly SharedHandsSystem _hands = default!; [Dependency] private readonly PopupSystem _popup = default!; - + public override void Initialize() { base.Initialize(); @@ -48,8 +48,11 @@ private void OnDoAfter(EntityUid uid, EnsnareableComponent component, DoAfterEve Dirty(component); ensnaring.Ensnared = null; - _hands.PickupOrDrop(args.Args.User, args.Args.Used.Value); - + if (ensnaring.DestroyOnRemove) + QueueDel(args.Args.Used); + else + _hands.PickupOrDrop(args.Args.User, args.Args.Used.Value); + _popup.PopupEntity(Loc.GetString("ensnare-component-try-free-complete", ("ensnare", args.Args.Used)), uid, uid, PopupType.Medium); UpdateAlert(args.Args.Target.Value, component); diff --git a/Content.Server/Entry/EntryPoint.cs b/Content.Server/Entry/EntryPoint.cs index a04f274491..dda783c432 100644 --- a/Content.Server/Entry/EntryPoint.cs +++ b/Content.Server/Entry/EntryPoint.cs @@ -5,15 +5,16 @@ using Content.Server.Afk; using Content.Server.Chat.Managers; using Content.Server.Connection; -using Content.Server.DiscordAuth; using Content.Server.JoinQueue; using Content.Server.Database; +using Content.Server.DiscordAuth; using Content.Server.EUI; using Content.Server.GameTicking; using Content.Server.GhostKick; using Content.Server.GuideGenerator; using Content.Server.Info; using Content.Server.IoC; +using Content.Server.Players.JobWhitelist; using Content.Server.Maps; using Content.Server.NodeContainer.NodeGroups; using Content.Server.Players.PlayTimeTracking; @@ -111,6 +112,7 @@ public override void Init() _voteManager.Initialize(); _updateManager.Initialize(); _playTimeTracking.Initialize(); + IoCManager.Resolve().Initialize(); } } diff --git a/Content.Server/Entry/IgnoredComponents.cs b/Content.Server/Entry/IgnoredComponents.cs index fe073da7a4..d9b81c8e5a 100644 --- a/Content.Server/Entry/IgnoredComponents.cs +++ b/Content.Server/Entry/IgnoredComponents.cs @@ -14,12 +14,13 @@ public static class IgnoredComponents "Icon", "HandheldGPS", "CableVisualizer", + "SolutionItemStatus", "UIFragment", "PdaBorderColor", "InventorySlots", "LightFade", "HolidayRsiSwap", - "OptionsVisualizer", + "OptionsVisualizer" }; } } diff --git a/Content.Server/Execution/ExecutionSystem.cs b/Content.Server/Execution/ExecutionSystem.cs index 326aa1d6a4..453a05e803 100644 --- a/Content.Server/Execution/ExecutionSystem.cs +++ b/Content.Server/Execution/ExecutionSystem.cs @@ -250,7 +250,7 @@ private void OnDoafterMelee(EntityUid uid, SharpComponent component, DoAfterEven if (!TryComp(weapon, out var melee) && melee!.Damage.GetTotal() > 0.0f) return; - _damageableSystem.TryChangeDamage(victim, melee.Damage * DamageModifier, true); + _damageableSystem.TryChangeDamage(victim, melee.Damage * DamageModifier, true, origin: attacker); _audioSystem.PlayEntity(melee.SoundHit, Filter.Pvs(weapon), weapon, true, AudioParams.Default); if (attacker == victim) @@ -281,7 +281,7 @@ private void OnDoafterGun(EntityUid uid, GunComponent component, DoAfterEvent ar var prevention = new ShotAttemptedEvent { User = attacker, - Used = weapon + Used = new Entity(uid, component) }; RaiseLocalEvent(weapon, ref prevention); @@ -374,7 +374,7 @@ private void OnDoafterGun(EntityUid uid, GunComponent component, DoAfterEvent ar } // Gun successfully fired, deal damage - _damageableSystem.TryChangeDamage(victim, damage * DamageModifier, true); + _damageableSystem.TryChangeDamage(victim, damage * DamageModifier, true, origin: attacker); _audioSystem.PlayEntity(component.SoundGunshot, Filter.Pvs(weapon), weapon, false, AudioParams.Default); // Popups diff --git a/Content.Server/Explosion/Components/ExplosiveComponent.cs b/Content.Server/Explosion/Components/ExplosiveComponent.cs index 04a08955a3..2b27a89d9d 100644 --- a/Content.Server/Explosion/Components/ExplosiveComponent.cs +++ b/Content.Server/Explosion/Components/ExplosiveComponent.cs @@ -81,6 +81,13 @@ public sealed partial class ExplosiveComponent : Component [DataField("deleteAfterExplosion")] public bool? DeleteAfterExplosion; + /// + /// Whether to not set to true, allowing it to explode multiple times. + /// This should never be used if it is damageable. + /// + [DataField] + public bool Repeatable; + /// /// Avoid somehow double-triggering this explosion (e.g. by damaging this entity from its own explosion. /// diff --git a/Content.Server/Explosion/Components/RepeatingTriggerComponent.cs b/Content.Server/Explosion/Components/RepeatingTriggerComponent.cs new file mode 100644 index 0000000000..cc08de53f9 --- /dev/null +++ b/Content.Server/Explosion/Components/RepeatingTriggerComponent.cs @@ -0,0 +1,25 @@ +using Content.Server.Explosion.EntitySystems; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; + +namespace Content.Server.Explosion.Components; + +/// +/// Constantly triggers after being added to an entity. +/// +[RegisterComponent, Access(typeof(TriggerSystem))] +[AutoGenerateComponentPause] +public sealed partial class RepeatingTriggerComponent : Component +{ + /// + /// How long to wait between triggers. + /// The first trigger starts this long after the component is added. + /// + [DataField] + public TimeSpan Delay = TimeSpan.FromSeconds(1); + + /// + /// When the next trigger will be. + /// + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField] + public TimeSpan NextTrigger; +} diff --git a/Content.Server/Explosion/EntitySystems/ExplosionGridTileFlood.cs b/Content.Server/Explosion/EntitySystems/ExplosionGridTileFlood.cs index 7db1f513f7..2ddcc052d8 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionGridTileFlood.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionGridTileFlood.cs @@ -14,7 +14,7 @@ public sealed class ExplosionGridTileFlood : ExplosionTileFlood public MapGridComponent Grid; private bool _needToTransform = false; - private Matrix3 _matrix = Matrix3.Identity; + private Matrix3x2 _matrix = Matrix3x2.Identity; private Vector2 _offset; // Tiles which neighbor an exploding tile, but have not yet had the explosion spread to them due to an @@ -44,7 +44,7 @@ public ExplosionGridTileFlood( int typeIndex, Dictionary edgeTiles, EntityUid? referenceGrid, - Matrix3 spaceMatrix, + Matrix3x2 spaceMatrix, Angle spaceAngle) { Grid = grid; @@ -72,9 +72,10 @@ public ExplosionGridTileFlood( var transform = IoCManager.Resolve().GetComponent(Grid.Owner); var size = (float) Grid.TileSize; - _matrix.R0C2 = size / 2; - _matrix.R1C2 = size / 2; - _matrix *= transform.WorldMatrix * Matrix3.Invert(spaceMatrix); + _matrix.M31 = size / 2; + _matrix.M32 = size / 2; + Matrix3x2.Invert(spaceMatrix, out var invSpace); + _matrix *= transform.WorldMatrix * invSpace; var relativeAngle = transform.WorldRotation - spaceAngle; _offset = relativeAngle.RotateVec(new Vector2(size / 4, size / 4)); } @@ -228,7 +229,7 @@ private void JumpToSpace(Vector2i tile) return; } - var center = _matrix.Transform(tile); + var center = Vector2.Transform(tile, _matrix); SpaceJump.Add(new((int) MathF.Floor(center.X + _offset.X), (int) MathF.Floor(center.Y + _offset.Y))); SpaceJump.Add(new((int) MathF.Floor(center.X - _offset.Y), (int) MathF.Floor(center.Y + _offset.X))); SpaceJump.Add(new((int) MathF.Floor(center.X - _offset.X), (int) MathF.Floor(center.Y - _offset.Y))); diff --git a/Content.Server/Explosion/EntitySystems/ExplosionSystem.CVars.cs b/Content.Server/Explosion/EntitySystems/ExplosionSystem.CVars.cs index 1f03109333..3332d3a4f5 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionSystem.CVars.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionSystem.CVars.cs @@ -11,7 +11,8 @@ public sealed partial class ExplosionSystem : EntitySystem public int ThrowLimit { get; private set; } public bool SleepNodeSys { get; private set; } public bool IncrementalTileBreaking { get; private set; } - public int SingleTickAreaLimit {get; private set; } + public int SingleTickAreaLimit { get; private set; } + public bool CanCreateVacuum { get; private set; } private void SubscribeCvars() { @@ -23,5 +24,6 @@ private void SubscribeCvars() Subs.CVar(_cfg, CCVars.ExplosionMaxProcessingTime, value => MaxProcessingTime = value, true); Subs.CVar(_cfg, CCVars.ExplosionIncrementalTileBreaking, value => IncrementalTileBreaking = value, true); Subs.CVar(_cfg, CCVars.ExplosionSingleTickAreaLimit, value => SingleTickAreaLimit = value, true); + Subs.CVar(_cfg, CCVars.ExplosionCanCreateVacuum, value => CanCreateVacuum = value, true); } } diff --git a/Content.Server/Explosion/EntitySystems/ExplosionSystem.GridMap.cs b/Content.Server/Explosion/EntitySystems/ExplosionSystem.GridMap.cs index 719a2eca79..556fe7c141 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionSystem.GridMap.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionSystem.GridMap.cs @@ -60,7 +60,7 @@ private void OnGridRemoved(GridRemovalEvent ev) { Dictionary transformedEdges = new(); - var targetMatrix = Matrix3.Identity; + var targetMatrix = Matrix3x2.Identity; Angle targetAngle = new(); var tileSize = DefaultTileSize; var maxDistanceSq = (int) (maxDistance * maxDistance); @@ -75,9 +75,9 @@ private void OnGridRemoved(GridRemovalEvent ev) tileSize = targetGrid.TileSize; } - var offsetMatrix = Matrix3.Identity; - offsetMatrix.R0C2 = tileSize / 2f; - offsetMatrix.R1C2 = tileSize / 2f; + var offsetMatrix = Matrix3x2.Identity; + offsetMatrix.M31 = tileSize / 2f; + offsetMatrix.M32 = tileSize / 2f; // Here we can end up with a triple nested for loop: // foreach other grid @@ -106,7 +106,7 @@ private void OnGridRemoved(GridRemovalEvent ev) var xform = xforms.GetComponent(gridToTransform); var (_, gridWorldRotation, gridWorldMatrix, invGridWorldMatrid) = xform.GetWorldPositionRotationMatrixWithInv(xforms); - var localEpicentre = (Vector2i) invGridWorldMatrid.Transform(epicentre.Position); + var localEpicentre = (Vector2i) Vector2.Transform(epicentre.Position, invGridWorldMatrid); var matrix = offsetMatrix * gridWorldMatrix * targetMatrix; var angle = gridWorldRotation - targetAngle; @@ -119,7 +119,7 @@ private void OnGridRemoved(GridRemovalEvent ev) if (delta.X * delta.X + delta.Y * delta.Y > maxDistanceSq) // no Vector2.Length??? continue; - var center = matrix.Transform(tile); + var center = Vector2.Transform(tile, matrix); if ((dir & NeighborFlag.Cardinal) == 0) { diff --git a/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs b/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs index 14a149074d..19f33e3fdd 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionSystem.Processing.cs @@ -1,4 +1,5 @@ using System.Numerics; +using Content.Server.Atmos.EntitySystems; using Content.Shared.CCVar; using Content.Shared.Damage; using Content.Shared.Explosion; @@ -16,7 +17,6 @@ using Robust.Shared.Timing; using Robust.Shared.Utility; using TimedDespawnComponent = Robust.Shared.Spawners.TimedDespawnComponent; -using Content.Server.Atmos.EntitySystems; namespace Content.Server.Explosion.EntitySystems; @@ -295,8 +295,8 @@ private static bool GridQueryCallback( /// Same as , but for SPAAAAAAACE. /// internal void ExplodeSpace(BroadphaseComponent lookup, - Matrix3 spaceMatrix, - Matrix3 invSpaceMatrix, + Matrix3x2 spaceMatrix, + Matrix3x2 invSpaceMatrix, Vector2i tile, float throwForce, DamageSpecifier damage, @@ -337,7 +337,7 @@ internal void ExplodeSpace(BroadphaseComponent lookup, } private static bool SpaceQueryCallback( - ref (List<(EntityUid, TransformComponent)> List, HashSet Processed, Matrix3 InvSpaceMatrix, EntityUid LookupOwner, EntityQuery XformQuery, Box2 GridBox, SharedTransformSystem System) state, + ref (List<(EntityUid, TransformComponent)> List, HashSet Processed, Matrix3x2 InvSpaceMatrix, EntityUid LookupOwner, EntityQuery XformQuery, Box2 GridBox, SharedTransformSystem System) state, in EntityUid uid) { if (state.Processed.Contains(uid)) @@ -348,7 +348,7 @@ private static bool SpaceQueryCallback( if (xform.ParentUid == state.LookupOwner) { // parented directly to the map, use local position - if (state.GridBox.Contains(state.InvSpaceMatrix.Transform(xform.LocalPosition))) + if (state.GridBox.Contains(Vector2.Transform(xform.LocalPosition, state.InvSpaceMatrix))) state.List.Add((uid, xform)); return true; @@ -356,14 +356,14 @@ private static bool SpaceQueryCallback( // finally check if it intersects our tile var wpos = state.System.GetWorldPosition(xform); - if (state.GridBox.Contains(state.InvSpaceMatrix.Transform(wpos))) + if (state.GridBox.Contains(Vector2.Transform(wpos, state.InvSpaceMatrix))) state.List.Add((uid, xform)); return true; } private static bool SpaceQueryCallback( - ref (List<(EntityUid, TransformComponent)> List, HashSet Processed, Matrix3 InvSpaceMatrix, EntityUid LookupOwner, EntityQuery XformQuery, Box2 GridBox, SharedTransformSystem System) state, + ref (List<(EntityUid, TransformComponent)> List, HashSet Processed, Matrix3x2 InvSpaceMatrix, EntityUid LookupOwner, EntityQuery XformQuery, Box2 GridBox, SharedTransformSystem System) state, in FixtureProxy proxy) { var uid = proxy.Entity; @@ -474,7 +474,9 @@ public void DamageFloorTile(TileRef tileRef, if (_tileDefinitionManager[tileRef.Tile.TypeId] is not ContentTileDefinition tileDef) return; - if (tileDef.MapAtmosphere) + if (!CanCreateVacuum) + canCreateVacuum = false; + else if (tileDef.MapAtmosphere) canCreateVacuum = true; // is already a vacuum. int tileBreakages = 0; @@ -567,12 +569,12 @@ struct ExplosionData /// /// The matrix that defines the reference frame for the explosion in space. /// - private readonly Matrix3 _spaceMatrix; + private readonly Matrix3x2 _spaceMatrix; /// /// Inverse of /// - private readonly Matrix3 _invSpaceMatrix; + private readonly Matrix3x2 _invSpaceMatrix; /// /// Have all the tiles on all the grids been processed? @@ -638,7 +640,7 @@ public Explosion(ExplosionSystem system, List gridData, List tileSetIntensity, MapCoordinates epicenter, - Matrix3 spaceMatrix, + Matrix3x2 spaceMatrix, int area, float tileBreakScale, int maxTileBreak, @@ -677,7 +679,7 @@ public Explosion(ExplosionSystem system, }); _spaceMatrix = spaceMatrix; - _invSpaceMatrix = Matrix3.Invert(spaceMatrix); + Matrix3x2.Invert(spaceMatrix, out _invSpaceMatrix); } foreach (var grid in gridData) diff --git a/Content.Server/Explosion/EntitySystems/ExplosionSystem.TileFill.cs b/Content.Server/Explosion/EntitySystems/ExplosionSystem.TileFill.cs index a42dd11083..8b4c0e14c5 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionSystem.TileFill.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionSystem.TileFill.cs @@ -26,7 +26,7 @@ public sealed partial class ExplosionSystem : EntitySystem /// The maximum intensity that the explosion can have at any given tile. This /// effectively caps the damage that this explosion can do. /// A list of tile-sets and a list of intensity values which describe the explosion. - private (int, List, ExplosionSpaceTileFlood?, Dictionary, Matrix3)? GetExplosionTiles( + private (int, List, ExplosionSpaceTileFlood?, Dictionary, Matrix3x2)? GetExplosionTiles( MapCoordinates epicenter, string typeID, float totalIntensity, @@ -84,7 +84,7 @@ public sealed partial class ExplosionSystem : EntitySystem Dictionary>? previousGridJump; // variables for transforming between grid and space-coordinates - var spaceMatrix = Matrix3.Identity; + var spaceMatrix = Matrix3x2.Identity; var spaceAngle = Angle.Zero; if (referenceGrid != null) { diff --git a/Content.Server/Explosion/EntitySystems/ExplosionSystem.Visuals.cs b/Content.Server/Explosion/EntitySystems/ExplosionSystem.Visuals.cs index d332531502..5db8b55bf9 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionSystem.Visuals.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionSystem.Visuals.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Content.Shared.Explosion; using Content.Shared.Explosion.Components; using Robust.Server.GameObjects; @@ -35,7 +36,7 @@ private void OnGetState(EntityUid uid, ExplosionVisualsComponent component, ref /// /// Constructor for the shared using the server-exclusive explosion classes. /// - private EntityUid CreateExplosionVisualEntity(MapCoordinates epicenter, string prototype, Matrix3 spaceMatrix, ExplosionSpaceTileFlood? spaceData, IEnumerable gridData, List iterationIntensity) + private EntityUid CreateExplosionVisualEntity(MapCoordinates epicenter, string prototype, Matrix3x2 spaceMatrix, ExplosionSpaceTileFlood? spaceData, IEnumerable gridData, List iterationIntensity) { var explosionEntity = Spawn(null, MapCoordinates.Nullspace); var comp = AddComp(explosionEntity); diff --git a/Content.Server/Explosion/EntitySystems/ExplosionSystem.cs b/Content.Server/Explosion/EntitySystems/ExplosionSystem.cs index 607a514e63..71f3025748 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionSystem.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionSystem.cs @@ -160,7 +160,7 @@ public void TriggerExplosive(EntityUid uid, ExplosiveComponent? explosive = null if (explosive.Exploded) return; - explosive.Exploded = true; + explosive.Exploded = !explosive.Repeatable; // Override the explosion intensity if optional arguments were provided. if (radius != null) diff --git a/Content.Server/Explosion/EntitySystems/TriggerSystem.cs b/Content.Server/Explosion/EntitySystems/TriggerSystem.cs index 94f5585536..8e0a75ea24 100644 --- a/Content.Server/Explosion/EntitySystems/TriggerSystem.cs +++ b/Content.Server/Explosion/EntitySystems/TriggerSystem.cs @@ -94,6 +94,7 @@ public override void Initialize() SubscribeLocalEvent(OnStepTriggered); SubscribeLocalEvent(OnSlipTriggered); SubscribeLocalEvent(OnEmptyTriggered); + SubscribeLocalEvent(OnRepeatInit); SubscribeLocalEvent(OnSpawnTrigger); SubscribeLocalEvent(HandleDeleteTrigger); @@ -153,7 +154,7 @@ private void HandleExplodeTrigger(EntityUid uid, ExplodeOnTriggerComponent compo private void HandleFlashTrigger(EntityUid uid, FlashOnTriggerComponent component, TriggerEvent args) { // TODO Make flash durations sane ffs. - _flashSystem.FlashArea(uid, args.User, component.Range, component.Duration * 1000f); + _flashSystem.FlashArea(uid, args.User, component.Range, component.Duration * 1000f, probability: component.Probability); args.Handled = true; } @@ -241,6 +242,11 @@ private void OnEmptyTriggered(EntityUid uid, TriggerWhenEmptyComponent component Trigger(uid, args.EmptyGun); } + private void OnRepeatInit(Entity ent, ref MapInitEvent args) + { + ent.Comp.NextTrigger = _timing.CurTime + ent.Comp.Delay; + } + public bool Trigger(EntityUid trigger, EntityUid? user = null) { var triggerEvent = new TriggerEvent(trigger, user); @@ -323,6 +329,7 @@ public override void Update(float frameTime) UpdateProximity(); UpdateTimer(frameTime); UpdateTimedCollide(frameTime); + UpdateRepeat(); } private void UpdateTimer(float frameTime) @@ -357,5 +364,19 @@ private void UpdateTimer(float frameTime) _appearance.SetData(uid, TriggerVisuals.VisualState, TriggerVisualState.Unprimed, appearance); } } + + private void UpdateRepeat() + { + var now = _timing.CurTime; + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var comp)) + { + if (comp.NextTrigger > now) + continue; + + comp.NextTrigger = now + comp.Delay; + Trigger(uid); + } + } } } diff --git a/Content.Server/Extinguisher/FireExtinguisherComponent.cs b/Content.Server/Extinguisher/FireExtinguisherComponent.cs index fe10b4a574..991fc76c62 100644 --- a/Content.Server/Extinguisher/FireExtinguisherComponent.cs +++ b/Content.Server/Extinguisher/FireExtinguisherComponent.cs @@ -3,7 +3,7 @@ namespace Content.Server.Extinguisher; -[NetworkedComponent, RegisterComponent] +[RegisterComponent] [Access(typeof(FireExtinguisherSystem))] public sealed partial class FireExtinguisherComponent : SharedFireExtinguisherComponent { diff --git a/Content.Server/Eye/Blinding/ActivatableUIRequiresVisionSystem.cs b/Content.Server/Eye/Blinding/ActivatableUIRequiresVisionSystem.cs index b51efc2f5e..7b937cf0d8 100644 --- a/Content.Server/Eye/Blinding/ActivatableUIRequiresVisionSystem.cs +++ b/Content.Server/Eye/Blinding/ActivatableUIRequiresVisionSystem.cs @@ -5,6 +5,7 @@ using Content.Shared.Eye.Blinding.Systems; using Robust.Shared.Player; using Robust.Server.GameObjects; +using Robust.Shared.Collections; namespace Content.Server.Eye.Blinding; @@ -37,24 +38,19 @@ private void OnBlindnessChanged(EntityUid uid, BlindableComponent component, ref if (!args.Blind) return; - if (!TryComp(uid, out var actor)) - return; - - var uiList = _userInterfaceSystem.GetAllUIsForSession(actor.PlayerSession); - if (uiList == null) - return; - - Queue closeList = new(); // foreach collection modified moment + var toClose = new ValueList<(EntityUid Entity, Enum Key)>(); - foreach (var ui in uiList) + foreach (var bui in _userInterfaceSystem.GetActorUis(uid)) { - if (HasComp(ui.Owner)) - closeList.Enqueue(ui); + if (HasComp(bui.Entity)) + { + toClose.Add(bui); + } } - foreach (var ui in closeList) + foreach (var bui in toClose) { - _userInterfaceSystem.CloseUi(ui, actor.PlayerSession); + _userInterfaceSystem.CloseUi(bui.Entity, bui.Key, uid); } } } diff --git a/Content.Server/Fax/AdminUI/AdminFaxEui.cs b/Content.Server/Fax/AdminUI/AdminFaxEui.cs index c8be6618e4..452fc593d9 100644 --- a/Content.Server/Fax/AdminUI/AdminFaxEui.cs +++ b/Content.Server/Fax/AdminUI/AdminFaxEui.cs @@ -1,6 +1,7 @@ using Content.Server.DeviceNetwork.Components; using Content.Server.EUI; using Content.Shared.Eui; +using Content.Shared.Fax.Components; using Content.Shared.Fax; using Content.Shared.Follower; using Content.Shared.Ghost; @@ -54,7 +55,7 @@ public override void HandleMessage(EuiMessageBase msg) } case AdminFaxEuiMsg.Send sendData: { - var printout = new FaxPrintout(sendData.Content, sendData.Title, null, sendData.StampState, + var printout = new FaxPrintout(sendData.Content, sendData.Title, null, null, sendData.StampState, new() { new StampDisplayInfo { StampedName = sendData.From, StampedColor = sendData.StampColor } }); _faxSystem.Receive(_entityManager.GetEntity(sendData.Target), printout); break; diff --git a/Content.Server/Fax/FaxConstants.cs b/Content.Server/Fax/FaxConstants.cs index 102510bd46..24ed7cbcf3 100644 --- a/Content.Server/Fax/FaxConstants.cs +++ b/Content.Server/Fax/FaxConstants.cs @@ -23,6 +23,7 @@ public static class FaxConstants public const string FaxNameData = "fax_data_name"; public const string FaxPaperNameData = "fax_data_title"; + public const string FaxPaperLabelData = "fax_data_label"; public const string FaxPaperPrototypeData = "fax_data_prototype"; public const string FaxPaperContentData = "fax_data_content"; public const string FaxPaperStampStateData = "fax_data_stamp_state"; diff --git a/Content.Server/Fax/FaxSystem.cs b/Content.Server/Fax/FaxSystem.cs index 3ff139466f..e86dbca4a1 100644 --- a/Content.Server/Fax/FaxSystem.cs +++ b/Content.Server/Fax/FaxSystem.cs @@ -4,6 +4,7 @@ using Content.Server.DeviceNetwork; using Content.Server.DeviceNetwork.Components; using Content.Server.DeviceNetwork.Systems; +using Content.Server.Labels; using Content.Server.Paper; using Content.Server.Popups; using Content.Server.Power.Components; @@ -16,7 +17,11 @@ using Content.Shared.Emag.Components; using Content.Shared.Emag.Systems; using Content.Shared.Fax; +using Content.Shared.Fax.Systems; +using Content.Shared.Fax.Components; using Content.Shared.Interaction; +using Content.Shared.Labels.Components; +using Content.Shared.Mobs.Components; using Content.Shared.Paper; using Robust.Server.GameObjects; using Robust.Shared.Audio; @@ -36,12 +41,14 @@ public sealed class FaxSystem : EntitySystem [Dependency] private readonly PopupSystem _popupSystem = default!; [Dependency] private readonly DeviceNetworkSystem _deviceNetworkSystem = default!; [Dependency] private readonly PaperSystem _paperSystem = default!; + [Dependency] private readonly LabelSystem _labelSystem = default!; [Dependency] private readonly SharedAudioSystem _audioSystem = default!; [Dependency] private readonly ToolSystem _toolSystem = default!; [Dependency] private readonly QuickDialogSystem _quickDialog = default!; [Dependency] private readonly UserInterfaceSystem _userInterface = default!; [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly MetaDataSystem _metaData = default!; + [Dependency] private readonly FaxecuteSystem _faxecute = default!; private const string PaperSlotId = "Paper"; @@ -51,7 +58,7 @@ public sealed class FaxSystem : EntitySystem /// [ValidatePrototypeId] private const string DefaultPaperPrototypeId = "Paper"; - + [ValidatePrototypeId] private const string OfficePaperPrototypeId = "PaperOffice"; @@ -236,7 +243,7 @@ private void OnInteractUsing(EntityUid uid, FaxMachineComponent component, Inter } _adminLogger.Add(LogType.Action, LogImpact.Low, - $"{ToPrettyString(args.User):user} renamed {ToPrettyString(uid)} from \"{component.FaxName}\" to \"{newName}\""); + $"{ToPrettyString(args.User):user} renamed {ToPrettyString(uid):tool} from \"{component.FaxName}\" to \"{newName}\""); component.FaxName = newName; _popupSystem.PopupEntity(Loc.GetString("fax-machine-popup-name-set"), uid); UpdateUserInterface(uid, component); @@ -288,11 +295,12 @@ private void OnPacketReceived(EntityUid uid, FaxMachineComponent component, Devi !args.Data.TryGetValue(FaxConstants.FaxPaperContentData, out string? content)) return; + args.Data.TryGetValue(FaxConstants.FaxPaperLabelData, out string? label); args.Data.TryGetValue(FaxConstants.FaxPaperStampStateData, out string? stampState); args.Data.TryGetValue(FaxConstants.FaxPaperStampedByData, out List? stampedBy); args.Data.TryGetValue(FaxConstants.FaxPaperPrototypeData, out string? prototypeId); - var printout = new FaxPrintout(content, name, prototypeId, stampState, stampedBy); + var printout = new FaxPrintout(content, name, label, prototypeId, stampState, stampedBy); Receive(uid, printout, args.SenderAddress); break; @@ -307,18 +315,25 @@ private void OnToggleInterface(EntityUid uid, FaxMachineComponent component, Aft private void OnFileButtonPressed(EntityUid uid, FaxMachineComponent component, FaxFileMessage args) { + args.Label = args.Label?[..Math.Min(args.Label.Length, FaxFileMessageValidation.MaxLabelSize)]; args.Content = args.Content[..Math.Min(args.Content.Length, FaxFileMessageValidation.MaxContentSize)]; PrintFile(uid, component, args); } private void OnCopyButtonPressed(EntityUid uid, FaxMachineComponent component, FaxCopyMessage args) { - Copy(uid, component, args); + if (HasComp(component.PaperSlot.Item)) + _faxecute.Faxecute(uid, component); /// when button pressed it will hurt the mob. + else + Copy(uid, component, args); } private void OnSendButtonPressed(EntityUid uid, FaxMachineComponent component, FaxSendMessage args) { - Send(uid, component, args.Session.AttachedEntity); + if (HasComp(component.PaperSlot.Item)) + _faxecute.Faxecute(uid, component); /// when button pressed it will hurt the mob. + else + Send(uid, component, args); } private void OnRefreshButtonPressed(EntityUid uid, FaxMachineComponent component, FaxRefreshMessage args) @@ -336,14 +351,20 @@ private void UpdateAppearance(EntityUid uid, FaxMachineComponent? component = nu if (!Resolve(uid, ref component)) return; + if (TryComp(component.PaperSlot.Item, out var faxable)) + component.InsertingState = faxable.InsertingState; + + if (component.InsertingTimeRemaining > 0) + { _appearanceSystem.SetData(uid, FaxMachineVisuals.VisualState, FaxMachineVisualState.Inserting); + Dirty(uid, component); + } else if (component.PrintingTimeRemaining > 0) _appearanceSystem.SetData(uid, FaxMachineVisuals.VisualState, FaxMachineVisualState.Printing); else _appearanceSystem.SetData(uid, FaxMachineVisuals.VisualState, FaxMachineVisualState.Normal); } - private void UpdateUserInterface(EntityUid uid, FaxMachineComponent? component = null) { if (!Resolve(uid, ref component)) @@ -358,7 +379,7 @@ private void UpdateUserInterface(EntityUid uid, FaxMachineComponent? component = component.SendTimeoutRemaining <= 0 && component.InsertingTimeRemaining <= 0; var state = new FaxUiState(component.FaxName, component.KnownFaxes, canSend, canCopy, isPaperInserted, component.DestinationFaxAddress); - _userInterface.TrySetUiState(uid, FaxUiKey.Key, state); + _userInterface.SetUiState(uid, FaxUiKey.Key, state); } /// @@ -409,20 +430,20 @@ public void PrintFile(EntityUid uid, FaxMachineComponent component, FaxFileMessa else prototype = DefaultPaperPrototypeId; - var name = Loc.GetString("fax-machine-printed-paper-name"); - - var printout = new FaxPrintout(args.Content, name, prototype); + var name = Loc.GetString("fax-machine-printed-paper-name"); + + var printout = new FaxPrintout(args.Content, name, args.Label, prototype); component.PrintingQueue.Enqueue(printout); component.SendTimeoutRemaining += component.SendTimeout; UpdateUserInterface(uid, component); - if (args.Session.AttachedEntity != null) - _adminLogger.Add(LogType.Action, LogImpact.Low, - $"{ToPrettyString(args.Session.AttachedEntity.Value):actor} added print job to {ToPrettyString(uid):tool} with text: {args.Content}"); - else - _adminLogger.Add(LogType.Action, LogImpact.Low, - $"Someone added print job to {ToPrettyString(uid):tool} with text: {args.Content}"); + // Unfortunately, since a paper entity does not yet exist, we have to emulate what LabelSystem will do. + var nameWithLabel = (args.Label is { } label) ? $"{name} ({label})" : name; + _adminLogger.Add(LogType.Action, LogImpact.Low, + $"{ToPrettyString(args.Actor):actor} " + + $"added print job to \"{component.FaxName}\" {ToPrettyString(uid):tool} " + + $"of {nameWithLabel}: {args.Content}"); } /// @@ -442,9 +463,12 @@ public void Copy(EntityUid uid, FaxMachineComponent? component, FaxCopyMessage a !TryComp(sendEntity, out var paper)) return; + TryComp(sendEntity, out var labelComponent); + // TODO: See comment in 'Send()' about not being able to copy whole entities var printout = new FaxPrintout(paper.Content, - metadata.EntityName, + labelComponent?.OriginalName ?? metadata.EntityName, + labelComponent?.CurrentLabel, metadata.EntityPrototype?.ID ?? DefaultPaperPrototypeId, paper.StampState, paper.StampedBy); @@ -457,16 +481,17 @@ public void Copy(EntityUid uid, FaxMachineComponent? component, FaxCopyMessage a UpdateUserInterface(uid, component); - if (args.Session.AttachedEntity != null) - _adminLogger.Add(LogType.Action, LogImpact.Low, - $"{ToPrettyString(args.Session.AttachedEntity.Value):actor} added copy job to {ToPrettyString(uid):tool} with text: {ToPrettyString(component.PaperSlot.Item):subject}"); + _adminLogger.Add(LogType.Action, LogImpact.Low, + $"{ToPrettyString(args.Actor):actor} " + + $"added copy job to \"{component.FaxName}\" {ToPrettyString(uid):tool} " + + $"of {ToPrettyString(sendEntity):subject}: {printout.Content}"); } /// /// Sends message to addressee if paper is set and a known fax is selected /// A timeout is set after sending, which is shared by the copy button. /// - public void Send(EntityUid uid, FaxMachineComponent? component = null, EntityUid? sender = null) + public void Send(EntityUid uid, FaxMachineComponent? component, FaxSendMessage args) { if (!Resolve(uid, ref component)) return; @@ -482,13 +507,16 @@ public void Send(EntityUid uid, FaxMachineComponent? component = null, EntityUid return; if (!TryComp(sendEntity, out var metadata) || - !TryComp(sendEntity, out var paper)) + !TryComp(sendEntity, out var paper)) return; + TryComp(sendEntity, out var labelComponent); + var payload = new NetworkPayload() { { DeviceNetworkConstants.Command, FaxConstants.FaxPrintCommand }, - { FaxConstants.FaxPaperNameData, metadata.EntityName }, + { FaxConstants.FaxPaperNameData, labelComponent?.OriginalName ?? metadata.EntityName }, + { FaxConstants.FaxPaperLabelData, labelComponent?.CurrentLabel }, { FaxConstants.FaxPaperContentData, paper.Content }, }; @@ -509,7 +537,11 @@ public void Send(EntityUid uid, FaxMachineComponent? component = null, EntityUid _deviceNetworkSystem.QueuePacket(uid, component.DestinationFaxAddress, payload); - _adminLogger.Add(LogType.Action, LogImpact.Low, $"{(sender != null ? ToPrettyString(sender.Value) : "Unknown"):user} sent fax from \"{component.FaxName}\" {ToPrettyString(uid)} to {faxName} ({component.DestinationFaxAddress}): {paper.Content}"); + _adminLogger.Add(LogType.Action, LogImpact.Low, + $"{ToPrettyString(args.Actor):actor} " + + $"sent fax from \"{component.FaxName}\" {ToPrettyString(uid):tool} " + + $"to \"{faxName}\" ({component.DestinationFaxAddress}) " + + $"of {ToPrettyString(sendEntity):subject}: {paper.Content}"); component.SendTimeoutRemaining += component.SendTimeout; @@ -565,7 +597,13 @@ private void SpawnPaperFromQueue(EntityUid uid, FaxMachineComponent? component = } _metaData.SetEntityName(printed, printout.Name); - _adminLogger.Add(LogType.Action, LogImpact.Low, $"\"{component.FaxName}\" {ToPrettyString(uid)} printed {ToPrettyString(printed)}: {printout.Content}"); + + if (printout.Label is { } label) + { + _labelSystem.Label(printed, label); + } + + _adminLogger.Add(LogType.Action, LogImpact.Low, $"\"{component.FaxName}\" {ToPrettyString(uid):tool} printed {ToPrettyString(printed):subject}: {printout.Content}"); } private void NotifyAdmins(string faxName) diff --git a/Content.Server/Flash/FlashSystem.cs b/Content.Server/Flash/FlashSystem.cs index 7706bfec4e..bc91a307b0 100644 --- a/Content.Server/Flash/FlashSystem.cs +++ b/Content.Server/Flash/FlashSystem.cs @@ -18,6 +18,7 @@ using Robust.Server.Audio; using Robust.Server.GameObjects; using Robust.Shared.Audio; +using Robust.Shared.Random; using Robust.Shared.Timing; using InventoryComponent = Content.Shared.Inventory.InventoryComponent; using Content.Shared.Traits.Assorted.Components; @@ -67,7 +68,7 @@ private void OnFlashMeleeHit(EntityUid uid, FlashComponent comp, MeleeHitEvent a args.Handled = true; foreach (var e in args.HitEntities) { - Flash(e, args.User, uid, comp.FlashDuration, comp.SlowTo, melee: true); + Flash(e, args.User, uid, comp.FlashDuration, comp.SlowTo, melee: true, stunDuration: comp.MeleeStunDuration); } } @@ -77,7 +78,7 @@ private void OnFlashUseInHand(EntityUid uid, FlashComponent comp, UseInHandEvent return; args.Handled = true; - FlashArea(uid, args.User, comp.Range, comp.AoeFlashDuration, comp.SlowTo, true); + FlashArea(uid, args.User, comp.Range, comp.AoeFlashDuration, comp.SlowTo, true, comp.Probability); } private bool UseFlash(EntityUid uid, FlashComponent comp, EntityUid user) @@ -117,7 +118,8 @@ public void Flash(EntityUid target, float slowTo, bool displayPopup = true, FlashableComponent? flashable = null, - bool melee = false) + bool melee = false, + TimeSpan? stunDuration = null) { if (!Resolve(target, ref flashable, false)) return; @@ -148,41 +150,46 @@ public void Flash(EntityUid target, && _random.Prob(flashable.EyeDamageChance)) _blindingSystem.AdjustEyeDamage((target, blindable), flashable.EyeDamage); - _stun.TrySlowdown(target, TimeSpan.FromSeconds(flashDuration/1000f), true, + if (stunDuration != null) + { + _stun.TryParalyze(target, stunDuration.Value, true); + } + else + { + _stun.TrySlowdown(target, TimeSpan.FromSeconds(flashDuration/1000f), true, slowTo, slowTo); + } if (displayPopup && user != null && target != user && Exists(user.Value)) { _popup.PopupEntity(Loc.GetString("flash-component-user-blinds-you", ("user", Identity.Entity(user.Value, EntityManager))), target, target); } - } - public void FlashArea(EntityUid source, EntityUid? user, float range, float duration, float slowTo = 0.8f, bool displayPopup = false, SoundSpecifier? sound = null) + public void FlashArea(Entity source, EntityUid? user, float range, float duration, float slowTo = 0.8f, bool displayPopup = false, float probability = 1f, SoundSpecifier? sound = null) { - var transform = EntityManager.GetComponent(source); + var transform = Transform(source); var mapPosition = _transform.GetMapCoordinates(transform); var flashableQuery = GetEntityQuery(); foreach (var entity in _entityLookup.GetEntitiesInRange(transform.Coordinates, range)) { - if (!flashableQuery.TryGetComponent(entity, out var flashable)) + if (!_random.Prob(probability)) continue; + if (!flashableQuery.TryGetComponent(entity, out var flashable)) + continue; // Check for unobstructed entities while ignoring the mobs with flashable components. - if (!_interaction.InRangeUnobstructed(entity, mapPosition, range, flashable.CollisionGroup, (e) => e == source)) + if (!_interaction.InRangeUnobstructed(entity, mapPosition, range, flashable.CollisionGroup, predicate: (e) => flashableQuery.HasComponent(e) || e == source.Owner)) continue; // They shouldn't have flash removed in between right? Flash(entity, user, source, duration, slowTo, displayPopup, flashableQuery.GetComponent(entity)); } - if (sound != null) - { - _audio.PlayPvs(sound, source, AudioParams.Default.WithVolume(1f).WithMaxDistance(3f)); - } + _audio.PlayPvs(sound, source, AudioParams.Default.WithVolume(1f).WithMaxDistance(3f)); } private void OnInventoryFlashAttempt(EntityUid uid, InventoryComponent component, FlashAttemptEvent args) diff --git a/Content.Server/Flight/FlightSystem.cs b/Content.Server/Flight/FlightSystem.cs new file mode 100644 index 0000000000..39321b1e66 --- /dev/null +++ b/Content.Server/Flight/FlightSystem.cs @@ -0,0 +1,176 @@ + +using Content.Shared.Cuffs.Components; +using Content.Shared.Damage.Components; +using Content.Shared.DoAfter; +using Content.Shared.Flight; +using Content.Shared.Flight.Events; +using Content.Shared.Mobs; +using Content.Shared.Popups; +using Content.Shared.Standing; +using Content.Shared.Stunnable; +using Content.Shared.Zombies; +using Robust.Shared.Audio.Systems; + +namespace Content.Server.Flight; +public sealed class FlightSystem : SharedFlightSystem +{ + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + [Dependency] private readonly SharedPopupSystem _popupSystem = default!; + [Dependency] private readonly StandingStateSystem _standing = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnToggleFlight); + SubscribeLocalEvent(OnFlightDoAfter); + SubscribeLocalEvent(OnMobStateChangedEvent); + SubscribeLocalEvent(OnZombified); + SubscribeLocalEvent(OnKnockedDown); + SubscribeLocalEvent(OnStunned); + SubscribeLocalEvent(OnDowned); + SubscribeLocalEvent(OnSleep); + } + public override void Update(float frameTime) + { + base.Update(frameTime); + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var component)) + { + if (!component.On) + continue; + + component.TimeUntilFlap -= frameTime; + + if (component.TimeUntilFlap > 0f) + continue; + + _audio.PlayPvs(component.FlapSound, uid); + component.TimeUntilFlap = component.FlapInterval; + + } + } + + #region Core Functions + private void OnToggleFlight(EntityUid uid, FlightComponent component, ToggleFlightEvent args) + { + // If the user isnt flying, we check for conditionals and initiate a doafter. + if (!component.On) + { + if (!CanFly(uid, component)) + return; + + var doAfterArgs = new DoAfterArgs(EntityManager, + uid, component.ActivationDelay, + new FlightDoAfterEvent(), uid, target: uid) + { + BlockDuplicate = true, + BreakOnTargetMove = true, + BreakOnUserMove = true, + BreakOnDamage = true, + NeedHand = true + }; + + if (!_doAfter.TryStartDoAfter(doAfterArgs)) + return; + } + else + ToggleActive(uid, false, component); + } + + private void OnFlightDoAfter(EntityUid uid, FlightComponent component, FlightDoAfterEvent args) + { + if (args.Handled || args.Cancelled) + return; + + ToggleActive(uid, true, component); + args.Handled = true; + } + + #endregion + + #region Conditionals + + private bool CanFly(EntityUid uid, FlightComponent component) + { + if (TryComp(uid, out var cuffableComp) && !cuffableComp.CanStillInteract) + { + _popupSystem.PopupEntity(Loc.GetString("no-flight-while-restrained"), uid, uid, PopupType.Medium); + return false; + } + + if (HasComp(uid)) + { + _popupSystem.PopupEntity(Loc.GetString("no-flight-while-zombified"), uid, uid, PopupType.Medium); + return false; + } + + if (HasComp(uid) && _standing.IsDown(uid)) + { + _popupSystem.PopupEntity(Loc.GetString("no-flight-while-lying"), uid, uid, PopupType.Medium); + return false; + } + + return true; + } + + private void OnMobStateChangedEvent(EntityUid uid, FlightComponent component, MobStateChangedEvent args) + { + if (!component.On + || args.NewMobState is MobState.Critical or MobState.Dead) + return; + + ToggleActive(args.Target, false, component); + } + + private void OnZombified(EntityUid uid, FlightComponent component, ref EntityZombifiedEvent args) + { + if (!component.On) + return; + + ToggleActive(args.Target, false, component); + if (!TryComp(uid, out var stamina)) + return; + Dirty(uid, stamina); + } + + private void OnKnockedDown(EntityUid uid, FlightComponent component, ref KnockedDownEvent args) + { + if (!component.On) + return; + + ToggleActive(uid, false, component); + } + + private void OnStunned(EntityUid uid, FlightComponent component, ref StunnedEvent args) + { + if (!component.On) + return; + + ToggleActive(uid, false, component); + } + + private void OnDowned(EntityUid uid, FlightComponent component, ref DownedEvent args) + { + if (!component.On) + return; + + ToggleActive(uid, false, component); + } + + private void OnSleep(EntityUid uid, FlightComponent component, ref SleepStateChangedEvent args) + { + if (!component.On + || !args.FellAsleep) + return; + + ToggleActive(uid, false, component); + if (!TryComp(uid, out var stamina)) + return; + + Dirty(uid, stamina); + } + #endregion +} \ No newline at end of file diff --git a/Content.Server/Fluids/EntitySystems/AbsorbentSystem.cs b/Content.Server/Fluids/EntitySystems/AbsorbentSystem.cs index d88f46968a..3d0996a380 100644 --- a/Content.Server/Fluids/EntitySystems/AbsorbentSystem.cs +++ b/Content.Server/Fluids/EntitySystems/AbsorbentSystem.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Content.Server.Chemistry.Containers.EntitySystems; using Content.Server.Popups; using Content.Shared.Chemistry.Components; @@ -317,7 +318,7 @@ private bool TryPuddleInteract(EntityUid user, EntityUid used, EntityUid target, var userXform = Transform(user); var targetPos = _transform.GetWorldPosition(target); - var localPos = _transform.GetInvWorldMatrix(userXform).Transform(targetPos); + var localPos = Vector2.Transform(targetPos, _transform.GetInvWorldMatrix(userXform)); localPos = userXform.LocalRotation.RotateVec(localPos); _melee.DoLunge(user, used, Angle.Zero, localPos, null, false); diff --git a/Content.Server/Fluids/EntitySystems/DrainSystem.cs b/Content.Server/Fluids/EntitySystems/DrainSystem.cs index 19cb650db7..27ad2178f9 100644 --- a/Content.Server/Fluids/EntitySystems/DrainSystem.cs +++ b/Content.Server/Fluids/EntitySystems/DrainSystem.cs @@ -87,19 +87,25 @@ private void Empty(EntityUid container, SpillableComponent spillable, EntityUid // Try to transfer as much solution as possible to the drain - var transferSolution = _solutionContainerSystem.SplitSolution(containerSoln.Value, - FixedPoint2.Min(containerSolution.Volume, drainSolution.AvailableVolume)); + var amountToPutInDrain = drainSolution.AvailableVolume; + var amountToSpillOnGround = containerSolution.Volume - drainSolution.AvailableVolume; - _solutionContainerSystem.TryAddSolution(drain.Solution.Value, transferSolution); + if (amountToPutInDrain > 0) + { + var solutionToPutInDrain = _solutionContainerSystem.SplitSolution(containerSoln.Value, amountToPutInDrain); + _solutionContainerSystem.TryAddSolution(drain.Solution.Value, solutionToPutInDrain); + + _audioSystem.PlayPvs(drain.ManualDrainSound, target); + _ambientSoundSystem.SetAmbience(target, true); + } - _audioSystem.PlayPvs(drain.ManualDrainSound, target); - _ambientSoundSystem.SetAmbience(target, true); - // If drain is full, spill + // Spill the remainder. - if (drainSolution.MaxVolume == drainSolution.Volume) + if (amountToSpillOnGround > 0) { - _puddleSystem.TrySpillAt(Transform(target).Coordinates, containerSolution, out _); + var solutionToSpill = _solutionContainerSystem.SplitSolution(containerSoln.Value, amountToSpillOnGround); + _puddleSystem.TrySpillAt(Transform(target).Coordinates, solutionToSpill, out _); _popupSystem.PopupEntity( Loc.GetString("drain-component-empty-verb-target-is-full-message", ("object", target)), container); diff --git a/Content.Server/Fluids/EntitySystems/PuddleSystem.Spillable.cs b/Content.Server/Fluids/EntitySystems/PuddleSystem.Spillable.cs index 7780e5d467..d02dd44e81 100644 --- a/Content.Server/Fluids/EntitySystems/PuddleSystem.Spillable.cs +++ b/Content.Server/Fluids/EntitySystems/PuddleSystem.Spillable.cs @@ -1,16 +1,14 @@ using Content.Server.Chemistry.Containers.EntitySystems; -using Content.Server.Fluids.Components; using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Chemistry.Reaction; using Content.Shared.Chemistry.Reagent; -using Content.Shared.Clothing.Components; +using Content.Shared.Clothing; using Content.Shared.CombatMode.Pacification; using Content.Shared.Database; using Content.Shared.FixedPoint; using Content.Shared.Fluids.Components; using Content.Shared.IdentityManagement; -using Content.Shared.Inventory.Events; using Content.Shared.Nutrition.EntitySystems; using Content.Shared.Popups; using Content.Shared.Spillable; @@ -29,8 +27,8 @@ protected override void InitializeSpillable() SubscribeLocalEvent(SpillOnLand); // Openable handles the event if it's closed SubscribeLocalEvent(SplashOnMeleeHit, after: [typeof(OpenableSystem)]); - SubscribeLocalEvent(OnGotEquipped); - SubscribeLocalEvent(OnGotUnequipped); + SubscribeLocalEvent(OnGotEquipped); + SubscribeLocalEvent(OnGotUnequipped); SubscribeLocalEvent(OnOverflow); SubscribeLocalEvent(OnDoAfter); SubscribeLocalEvent(OnAttemptPacifiedThrow); @@ -99,20 +97,11 @@ private void SplashOnMeleeHit(Entity entity, ref MeleeHitEve } } - private void OnGotEquipped(Entity entity, ref GotEquippedEvent args) + private void OnGotEquipped(Entity entity, ref ClothingGotEquippedEvent args) { if (!entity.Comp.SpillWorn) return; - if (!TryComp(entity, out ClothingComponent? clothing)) - return; - - // check if entity was actually used as clothing - // not just taken in pockets or something - var isCorrectSlot = clothing.Slots.HasFlag(args.SlotFlags); - if (!isCorrectSlot) - return; - if (!_solutionContainerSystem.TryGetSolution(entity.Owner, entity.Comp.SolutionName, out var soln, out var solution)) return; @@ -124,10 +113,10 @@ private void OnGotEquipped(Entity entity, ref GotEquippedEve // spill all solution on the player var drainedSolution = _solutionContainerSystem.Drain(entity.Owner, soln.Value, solution.Volume); - TrySplashSpillAt(entity.Owner, Transform(args.Equipee).Coordinates, drainedSolution, out _); + TrySplashSpillAt(entity.Owner, Transform(args.Wearer).Coordinates, drainedSolution, out _); } - private void OnGotUnequipped(Entity entity, ref GotUnequippedEvent args) + private void OnGotUnequipped(Entity entity, ref ClothingGotUnequippedEvent args) { if (!entity.Comp.SpillWorn) return; diff --git a/Content.Server/Fluids/EntitySystems/SpraySystem.cs b/Content.Server/Fluids/EntitySystems/SpraySystem.cs index f7621aec62..5499070738 100644 --- a/Content.Server/Fluids/EntitySystems/SpraySystem.cs +++ b/Content.Server/Fluids/EntitySystems/SpraySystem.cs @@ -144,7 +144,7 @@ private void OnAfterInteract(Entity entity, ref AfterInteractEve _audio.PlayPvs(entity.Comp.SpraySound, entity, entity.Comp.SpraySound.Params.WithVariation(0.125f)); - _useDelay.SetDelay((entity, useDelay), TimeSpan.FromSeconds(cooldownTime)); + _useDelay.SetLength(entity.Owner, TimeSpan.FromSeconds(cooldownTime)); _useDelay.TryResetDelay((entity, useDelay)); } } diff --git a/Content.Server/FootPrint/FootPrintsSystem.cs b/Content.Server/FootPrint/FootPrintsSystem.cs new file mode 100644 index 0000000000..0e45acff5c --- /dev/null +++ b/Content.Server/FootPrint/FootPrintsSystem.cs @@ -0,0 +1,122 @@ +using Content.Server.Atmos.Components; +using Content.Shared.Inventory; +using Content.Shared.Mobs; +using Content.Shared.Mobs.Components; +using Content.Shared.FootPrint; +using Content.Shared.Standing; +using Content.Shared.Chemistry.Components.SolutionManager; +using Content.Shared.Chemistry.EntitySystems; +using Robust.Shared.Map; +using Robust.Shared.Random; + +namespace Content.Server.FootPrint; + +public sealed class FootPrintsSystem : EntitySystem +{ + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly InventorySystem _inventory = default!; + [Dependency] private readonly IMapManager _map = default!; + + [Dependency] private readonly SharedSolutionContainerSystem _solution = default!; + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + + private EntityQuery _transformQuery; + private EntityQuery _mobThresholdQuery; + private EntityQuery _appearanceQuery; + private EntityQuery _layingQuery; + + public override void Initialize() + { + base.Initialize(); + + _transformQuery = GetEntityQuery(); + _mobThresholdQuery = GetEntityQuery(); + _appearanceQuery = GetEntityQuery(); + _layingQuery = GetEntityQuery(); + + SubscribeLocalEvent(OnStartupComponent); + SubscribeLocalEvent(OnMove); + } + + private void OnStartupComponent(EntityUid uid, FootPrintsComponent component, ComponentStartup args) + { + component.StepSize = Math.Max(0f, component.StepSize + _random.NextFloat(-0.05f, 0.05f)); + } + + private void OnMove(EntityUid uid, FootPrintsComponent component, ref MoveEvent args) + { + if (component.PrintsColor.A <= 0f + || !_transformQuery.TryComp(uid, out var transform) + || !_mobThresholdQuery.TryComp(uid, out var mobThreshHolds) + || !_map.TryFindGridAt(_transform.GetMapCoordinates((uid, transform)), out var gridUid, out _)) + return; + + var dragging = mobThreshHolds.CurrentThresholdState is MobState.Critical or MobState.Dead + || _layingQuery.TryComp(uid, out var laying) && laying.IsCrawlingUnder; + var distance = (transform.LocalPosition - component.StepPos).Length(); + var stepSize = dragging ? component.DragSize : component.StepSize; + + if (!(distance > stepSize)) + return; + + component.RightStep = !component.RightStep; + + var entity = Spawn(component.StepProtoId, CalcCoords(gridUid, component, transform, dragging)); + var footPrintComponent = EnsureComp(entity); + + footPrintComponent.PrintOwner = uid; + Dirty(entity, footPrintComponent); + + if (_appearanceQuery.TryComp(entity, out var appearance)) + { + _appearance.SetData(entity, FootPrintVisualState.State, PickState(uid, dragging), appearance); + _appearance.SetData(entity, FootPrintVisualState.Color, component.PrintsColor, appearance); + } + + if (!_transformQuery.TryComp(entity, out var stepTransform)) + return; + + stepTransform.LocalRotation = dragging + ? (transform.LocalPosition - component.StepPos).ToAngle() + Angle.FromDegrees(-90f) + : transform.LocalRotation + Angle.FromDegrees(180f); + + component.PrintsColor = component.PrintsColor.WithAlpha(Math.Max(0f, component.PrintsColor.A - component.ColorReduceAlpha)); + component.StepPos = transform.LocalPosition; + + if (!TryComp(entity, out var solutionContainer) + || !_solution.ResolveSolution((entity, solutionContainer), footPrintComponent.SolutionName, ref footPrintComponent.Solution, out var solution) + || string.IsNullOrWhiteSpace(component.ReagentToTransfer) || solution.Volume >= 1) + return; + + _solution.TryAddReagent(footPrintComponent.Solution.Value, component.ReagentToTransfer, 1, out _); + } + + private EntityCoordinates CalcCoords(EntityUid uid, FootPrintsComponent component, TransformComponent transform, bool state) + { + if (state) + return new EntityCoordinates(uid, transform.LocalPosition); + + var offset = component.RightStep + ? new Angle(Angle.FromDegrees(180f) + transform.LocalRotation).RotateVec(component.OffsetPrint) + : new Angle(transform.LocalRotation).RotateVec(component.OffsetPrint); + + return new EntityCoordinates(uid, transform.LocalPosition + offset); + } + + private FootPrintVisuals PickState(EntityUid uid, bool dragging) + { + var state = FootPrintVisuals.BareFootPrint; + + if (_inventory.TryGetSlotEntity(uid, "shoes", out _)) + state = FootPrintVisuals.ShoesPrint; + + if (_inventory.TryGetSlotEntity(uid, "outerClothing", out var suit) && TryComp(suit, out _)) + state = FootPrintVisuals.SuitPrint; + + if (dragging) + state = FootPrintVisuals.Dragging; + + return state; + } +} diff --git a/Content.Server/FootPrint/PuddleFootPrintsSystem.cs b/Content.Server/FootPrint/PuddleFootPrintsSystem.cs new file mode 100644 index 0000000000..706ba25359 --- /dev/null +++ b/Content.Server/FootPrint/PuddleFootPrintsSystem.cs @@ -0,0 +1,52 @@ +using System.Linq; +using Content.Shared.FootPrint; +using Content.Shared.Chemistry.Components.SolutionManager; +using Content.Shared.Chemistry.EntitySystems; +using Content.Shared.Fluids; +using Content.Shared.Fluids.Components; +using Robust.Shared.Physics.Events; + +namespace Content.Server.FootPrint; + +public sealed class PuddleFootPrintsSystem : EntitySystem +{ + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnStepTrigger); + } + + private void OnStepTrigger(EntityUid uid, PuddleFootPrintsComponent component, ref EndCollideEvent args) + { + if (!TryComp(uid, out var appearance) + || !TryComp(uid, out var puddle) + || !TryComp(args.OtherEntity, out var tripper) + || !TryComp(uid, out var solutionManager) + || !_solutionContainer.ResolveSolution((uid, solutionManager), puddle.SolutionName, ref puddle.Solution, out var solutions)) + return; + + var totalSolutionQuantity = solutions.Contents.Sum(sol => (float) sol.Quantity); + var waterQuantity = (from sol in solutions.Contents where sol.Reagent.Prototype == "Water" select (float) sol.Quantity).FirstOrDefault(); + + if (waterQuantity / (totalSolutionQuantity / 100f) > component.OffPercent || solutions.Contents.Count <= 0) + return; + + tripper.ReagentToTransfer = + solutions.Contents.Aggregate((l, r) => l.Quantity > r.Quantity ? l : r).Reagent.Prototype; + + if (_appearance.TryGetData(uid, PuddleVisuals.SolutionColor, out var color, appearance) + && _appearance.TryGetData(uid, PuddleVisuals.CurrentVolume, out var volume, appearance)) + AddColor((Color) color, (float) volume * component.SizeRatio, tripper); + + _solutionContainer.RemoveEachReagent(puddle.Solution.Value, 1); + } + + private void AddColor(Color col, float quantity, FootPrintsComponent component) + { + component.PrintsColor = component.ColorQuantity == 0f ? col : Color.InterpolateBetween(component.PrintsColor, col, component.ColorInterpolationFactor); + component.ColorQuantity += quantity; + } +} diff --git a/Content.Server/Forensics/Components/ForensicScannerComponent.cs b/Content.Server/Forensics/Components/ForensicScannerComponent.cs index ad26213848..3ad805cbbf 100644 --- a/Content.Server/Forensics/Components/ForensicScannerComponent.cs +++ b/Content.Server/Forensics/Components/ForensicScannerComponent.cs @@ -1,4 +1,5 @@ using System.Threading; +using Content.Shared.Forensics; using Robust.Shared.Audio; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; diff --git a/Content.Server/Forensics/Systems/ForensicScannerSystem.cs b/Content.Server/Forensics/Systems/ForensicScannerSystem.cs index 0d3168fd4e..e83cde7456 100644 --- a/Content.Server/Forensics/Systems/ForensicScannerSystem.cs +++ b/Content.Server/Forensics/Systems/ForensicScannerSystem.cs @@ -52,8 +52,7 @@ private void UpdateUserInterface(EntityUid uid, ForensicScannerComponent compone component.PrintCooldown, component.PrintReadyAt); - if (!_uiSystem.TrySetUiState(uid, ForensicScannerUiKey.Key, state)) - Log.Warning($"{ToPrettyString(uid)} was unable to set UI state."); + _uiSystem.SetUiState(uid, ForensicScannerUiKey.Key, state); } private void OnDoAfter(EntityUid uid, ForensicScannerComponent component, DoAfterEvent args) @@ -164,23 +163,14 @@ private void OnBeforeActivatableUIOpen(EntityUid uid, ForensicScannerComponent c private void OpenUserInterface(EntityUid user, Entity scanner) { - if (!TryComp(user, out var actor)) - return; - UpdateUserInterface(scanner, scanner.Comp); - _uiSystem.TryOpen(scanner, ForensicScannerUiKey.Key, actor.PlayerSession); + _uiSystem.OpenUi(scanner.Owner, ForensicScannerUiKey.Key, user); } private void OnPrint(EntityUid uid, ForensicScannerComponent component, ForensicScannerPrintMessage args) { - if (!args.Session.AttachedEntity.HasValue) - { - Log.Warning($"{ToPrettyString(uid)} got OnPrint without Session.AttachedEntity"); - return; - } - - var user = args.Session.AttachedEntity.Value; + var user = args.Actor; if (_gameTiming.CurTime < component.PrintReadyAt) { @@ -192,7 +182,7 @@ private void OnPrint(EntityUid uid, ForensicScannerComponent component, Forensic // Spawn a piece of paper. var printed = EntityManager.SpawnEntity(component.MachineOutput, Transform(uid).Coordinates); - _handsSystem.PickupOrDrop(args.Session.AttachedEntity, printed, checkActionBlocker: false); + _handsSystem.PickupOrDrop(args.Actor, printed, checkActionBlocker: false); if (!HasComp(printed)) { @@ -241,9 +231,6 @@ private void OnPrint(EntityUid uid, ForensicScannerComponent component, Forensic private void OnClear(EntityUid uid, ForensicScannerComponent component, ForensicScannerClearMessage args) { - if (!args.Session.AttachedEntity.HasValue) - return; - component.Fingerprints = new(); component.Fibers = new(); component.DNAs = new(); diff --git a/Content.Server/Forensics/Systems/ForensicsSystem.cs b/Content.Server/Forensics/Systems/ForensicsSystem.cs index dff134a511..8f91ec41e8 100644 --- a/Content.Server/Forensics/Systems/ForensicsSystem.cs +++ b/Content.Server/Forensics/Systems/ForensicsSystem.cs @@ -131,53 +131,32 @@ public void CopyForensicsFrom(ForensicsComponent src, EntityUid target) private void OnAfterInteract(EntityUid uid, CleansForensicsComponent component, AfterInteractEvent args) { - if (args.Handled) + if (args.Handled || !args.CanReach || !TryComp(args.Target, out var forensicsComp)) return; - if (TryComp(args.Target, out var forensicsComp) - && forensicsComp.DNAs.Count > 0 && forensicsComp.CanDnaBeCleaned - && forensicsComp.Fingerprints.Count + forensicsComp.Fibers.Count > 0 - && forensicsComp.Scent != string.Empty) - { - var cleanDelay = component.CleanDelay; - if (HasComp(args.Target)) - cleanDelay += 30; + if ((forensicsComp.DNAs.Count <= 0 || !forensicsComp.CanDnaBeCleaned) + && forensicsComp.Fingerprints.Count + forensicsComp.Fibers.Count <= 0 + && forensicsComp.Scent == string.Empty) + return; // Nothing to do if there is no DNAs, fibers, and scent - var doAfterArgs = new DoAfterArgs(EntityManager, args.User, cleanDelay, new CleanForensicsDoAfterEvent(), uid, target: args.Target, used: args.Used) - { - BreakOnHandChange = true, - NeedHand = true, - BreakOnDamage = true, - BreakOnTargetMove = true, - MovementThreshold = 0.01f, - DistanceThreshold = forensicsComp.CleanDistance, - }; - - _doAfterSystem.TryStartDoAfter(doAfterArgs); - _popupSystem.PopupEntity(Loc.GetString("forensics-cleaning", ("target", args.Target)), args.User, args.User); - - args.Handled = true; - return; - } + var cleanDelay = component.CleanDelay; + if (HasComp(args.Target)) + cleanDelay += 30; - if (TryComp(args.Target, out var scentComp)) + var doAfterArgs = new DoAfterArgs(EntityManager, args.User, cleanDelay, new CleanForensicsDoAfterEvent(), uid, target: args.Target, used: args.Used) { - var cleanDelay = component.CleanDelay + 30; - var doAfterArgs = new DoAfterArgs(EntityManager, args.User, cleanDelay, new CleanForensicsDoAfterEvent(), uid, target: args.Target, used: args.Used) - { - BreakOnHandChange = true, - NeedHand = true, - BreakOnDamage = true, - BreakOnTargetMove = true, - MovementThreshold = 0.01f, - DistanceThreshold = 1.5f, - }; - - _doAfterSystem.TryStartDoAfter(doAfterArgs); - _popupSystem.PopupEntity(Loc.GetString("forensics-cleaning", ("target", args.Target)), args.User, args.User); - - args.Handled = true; - } + BreakOnHandChange = true, + NeedHand = true, + BreakOnDamage = true, + BreakOnTargetMove = true, + MovementThreshold = 0.01f, + DistanceThreshold = forensicsComp.CleanDistance, + }; + + _doAfterSystem.TryStartDoAfter(doAfterArgs); + _popupSystem.PopupEntity(Loc.GetString("forensics-cleaning", ("target", args.Target)), args.User, args.User); + + args.Handled = true; } private void OnCleanForensicsDoAfter(EntityUid uid, ForensicsComponent component, CleanForensicsDoAfterEvent args) @@ -202,7 +181,8 @@ private void OnCleanForensicsDoAfter(EntityUid uid, ForensicsComponent component if (TryComp(args.Used, out var residue)) targetComp.Residues.Add(string.IsNullOrEmpty(residue.ResidueColor) ? Loc.GetString("forensic-residue", ("adjective", residue.ResidueAdjective)) : Loc.GetString("forensic-residue-colored", ("color", residue.ResidueColor), ("adjective", residue.ResidueAdjective))); - // If the ent has a Scent Component, we compleatly generate a new one and apply the new scent to all currently weared items. + // If the ent has a Scent Component, we completely generate a new one and apply the new scent to all currently worn items. + // TODO this is never gonna work unless you like, wash yourself with the soap??? if (TryComp(args.Target, out var scentComp)) { var generatedscent = GenerateFingerprint(length: 5); diff --git a/Content.Server/GameTicking/Rules/Components/ActiveGameRuleComponent.cs b/Content.Server/GameTicking/Components/ActiveGameRuleComponent.cs similarity index 84% rename from Content.Server/GameTicking/Rules/Components/ActiveGameRuleComponent.cs rename to Content.Server/GameTicking/Components/ActiveGameRuleComponent.cs index 956768bdd9..b9e6fa5d4b 100644 --- a/Content.Server/GameTicking/Rules/Components/ActiveGameRuleComponent.cs +++ b/Content.Server/GameTicking/Components/ActiveGameRuleComponent.cs @@ -1,4 +1,4 @@ -namespace Content.Server.GameTicking.Rules.Components; +namespace Content.Server.GameTicking.Components; /// /// Added to game rules before and removed before . diff --git a/Content.Server/GameTicking/Components/DelayedStartRuleComponent.cs b/Content.Server/GameTicking/Components/DelayedStartRuleComponent.cs new file mode 100644 index 0000000000..de4be83627 --- /dev/null +++ b/Content.Server/GameTicking/Components/DelayedStartRuleComponent.cs @@ -0,0 +1,16 @@ +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; + +namespace Content.Server.GameTicking.Components; + +/// +/// Generic component used to track a gamerule that's start has been delayed. +/// +[RegisterComponent, AutoGenerateComponentPause] +public sealed partial class DelayedStartRuleComponent : Component +{ + /// + /// The time at which the rule will start properly. + /// + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField] + public TimeSpan RuleStartTime; +} diff --git a/Content.Server/GameTicking/Rules/Components/EndedGameRuleComponent.cs b/Content.Server/GameTicking/Components/EndedGameRuleComponent.cs similarity index 81% rename from Content.Server/GameTicking/Rules/Components/EndedGameRuleComponent.cs rename to Content.Server/GameTicking/Components/EndedGameRuleComponent.cs index 4484abd4d0..3234bfff3a 100644 --- a/Content.Server/GameTicking/Rules/Components/EndedGameRuleComponent.cs +++ b/Content.Server/GameTicking/Components/EndedGameRuleComponent.cs @@ -1,4 +1,4 @@ -namespace Content.Server.GameTicking.Rules.Components; +namespace Content.Server.GameTicking.Components; /// /// Added to game rules before . diff --git a/Content.Server/GameTicking/Rules/Components/GameRuleComponent.cs b/Content.Server/GameTicking/Components/GameRuleComponent.cs similarity index 83% rename from Content.Server/GameTicking/Rules/Components/GameRuleComponent.cs rename to Content.Server/GameTicking/Components/GameRuleComponent.cs index 6309b97402..1e6c3f0ab1 100644 --- a/Content.Server/GameTicking/Rules/Components/GameRuleComponent.cs +++ b/Content.Server/GameTicking/Components/GameRuleComponent.cs @@ -1,6 +1,7 @@ +using Content.Server.Destructible.Thresholds; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; -namespace Content.Server.GameTicking.Rules.Components; +namespace Content.Server.GameTicking.Components; /// /// Component attached to all gamerule entities. @@ -20,6 +21,12 @@ public sealed partial class GameRuleComponent : Component /// [DataField] public int MinPlayers; + + /// + /// A delay for when the rule the is started and when the starting logic actually runs. + /// + [DataField] + public MinMax? Delay; } /// diff --git a/Content.Server/GameTicking/Events/GetDisallowedJobsEvent.cs b/Content.Server/GameTicking/Events/GetDisallowedJobsEvent.cs new file mode 100644 index 0000000000..cd15cfb8f8 --- /dev/null +++ b/Content.Server/GameTicking/Events/GetDisallowedJobsEvent.cs @@ -0,0 +1,8 @@ +using Content.Shared.Roles; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; + +namespace Content.Server.GameTicking.Events; + +[ByRefEvent] +public readonly record struct GetDisallowedJobsEvent(ICommonSession Player, HashSet> Jobs); diff --git a/Content.Server/GameTicking/Events/IsJobAllowedEvent.cs b/Content.Server/GameTicking/Events/IsJobAllowedEvent.cs new file mode 100644 index 0000000000..51969d61ea --- /dev/null +++ b/Content.Server/GameTicking/Events/IsJobAllowedEvent.cs @@ -0,0 +1,13 @@ +using Content.Shared.Roles; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; + +namespace Content.Server.GameTicking.Events; + +[ByRefEvent] +public struct IsJobAllowedEvent(ICommonSession player, ProtoId jobId, bool cancelled = false) +{ + public readonly ICommonSession Player = player; + public readonly ProtoId JobId = jobId; + public bool Cancelled = cancelled; +} diff --git a/Content.Server/GameTicking/GameTicker.GamePreset.cs b/Content.Server/GameTicking/GameTicker.GamePreset.cs index b97a16ab99..4c454fb189 100644 --- a/Content.Server/GameTicking/GameTicker.GamePreset.cs +++ b/Content.Server/GameTicking/GameTicker.GamePreset.cs @@ -1,6 +1,7 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading.Tasks; +using Content.Server.Ghost; using Content.Server.GameTicking.Presets; using Content.Server.Maps; using Content.Shared.CCVar; @@ -21,7 +22,7 @@ namespace Content.Server.GameTicking public sealed partial class GameTicker { [Dependency] private readonly MobThresholdSystem _mobThresholdSystem = default!; - + [Dependency] private readonly GhostReturnToRoundSystem _ghostReturnToRound = default!; public const float PresetFailedCooldownIncrease = 30f; /// @@ -100,7 +101,7 @@ private void InitializeGamePreset() SetGamePreset(LobbyEnabled ? _configurationManager.GetCVar(CCVars.GameLobbyDefaultPreset) : "sandbox"); } - public void SetGamePreset(GamePresetPrototype preset, bool force = false) + public void SetGamePreset(GamePresetPrototype? preset, bool force = false) { // Do nothing if this game ticker is a dummy! if (DummyTicker) @@ -274,35 +275,13 @@ public bool OnGhostAttempt(EntityUid mindId, bool canReturnGlobal, bool viaComma } } - var xformQuery = GetEntityQuery(); - var coords = _transform.GetMoverCoordinates(position, xformQuery); - - var ghost = Spawn(ObserverPrototypeName, coords); - - // Try setting the ghost entity name to either the character name or the player name. - // If all else fails, it'll default to the default entity prototype name, "observer". - // However, that should rarely happen. - if (!string.IsNullOrWhiteSpace(mind.CharacterName)) - _metaData.SetEntityName(ghost, mind.CharacterName); - else if (!string.IsNullOrWhiteSpace(mind.Session?.Name)) - _metaData.SetEntityName(ghost, mind.Session.Name); - - var ghostComponent = Comp(ghost); - - if (mind.TimeOfDeath.HasValue) - { - _ghost.SetTimeOfDeath(ghost, mind.TimeOfDeath!.Value, ghostComponent); - } + var ghost = _ghost.SpawnGhost((mindId, mind), position, canReturn); + if (ghost == null) + return false; if (playerEntity != null) _adminLogger.Add(LogType.Mind, $"{EntityManager.ToPrettyString(playerEntity.Value):player} ghosted{(!canReturn ? " (non-returnable)" : "")}"); - _ghost.SetCanReturnToBody(ghostComponent, canReturn); - - if (canReturn) - _mind.Visit(mindId, ghost, mind); - else - _mind.TransferTo(mindId, ghost, mind: mind); return true; } diff --git a/Content.Server/GameTicking/GameTicker.GameRule.cs b/Content.Server/GameTicking/GameTicker.GameRule.cs index 4ebe946af4..f52a3cb296 100644 --- a/Content.Server/GameTicking/GameTicker.GameRule.cs +++ b/Content.Server/GameTicking/GameTicker.GameRule.cs @@ -1,6 +1,6 @@ using System.Linq; using Content.Server.Administration; -using Content.Server.GameTicking.Rules.Components; +using Content.Server.GameTicking.Components; using Content.Shared.Administration; using Content.Shared.Database; using Content.Shared.Prototypes; @@ -102,6 +102,22 @@ public bool StartGameRule(EntityUid ruleEntity, GameRuleComponent? ruleData = nu if (MetaData(ruleEntity).EntityPrototype?.ID is not { } id) // you really fucked up return false; + // If we already have it, then we just skip the delay as it has already happened. + if (!RemComp(ruleEntity) && ruleData.Delay != null) + { + var delayTime = TimeSpan.FromSeconds(ruleData.Delay.Value.Next(_robustRandom)); + + if (delayTime > TimeSpan.Zero) + { + _sawmill.Info($"Queued start for game rule {ToPrettyString(ruleEntity)} with delay {delayTime}"); + _adminLogger.Add(LogType.EventStarted, $"Queued start for game rule {ToPrettyString(ruleEntity)} with delay {delayTime}"); + + var delayed = EnsureComp(ruleEntity); + delayed.RuleStartTime = _gameTiming.CurTime + (delayTime); + return true; + } + } + _allPreviousGameRules.Add((RoundDuration(), id)); _sawmill.Info($"Started game rule {ToPrettyString(ruleEntity)}"); _adminLogger.Add(LogType.EventStarted, $"Started game rule {ToPrettyString(ruleEntity)}"); @@ -255,6 +271,18 @@ public IEnumerable GetAllGameRulePrototypes() } } + private void UpdateGameRules() + { + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var delay, out var rule)) + { + if (_gameTiming.CurTime < delay.RuleStartTime) + continue; + + StartGameRule(uid, rule); + } + } + #region Command Implementations [AdminCommand(AdminFlags.Fun)] @@ -323,38 +351,3 @@ private void ClearGameRulesCommand(IConsoleShell shell, string argstr, string[] #endregion } - -/* -/// -/// Raised broadcast when a game rule is selected, but not started yet. -/// -public sealed class GameRuleAddedEvent -{ - public GameRulePrototype Rule { get; } - - public GameRuleAddedEvent(GameRulePrototype rule) - { - Rule = rule; - } -} - -public sealed class GameRuleStartedEvent -{ - public GameRulePrototype Rule { get; } - - public GameRuleStartedEvent(GameRulePrototype rule) - { - Rule = rule; - } -} - -public sealed class GameRuleEndedEvent -{ - public GameRulePrototype Rule { get; } - - public GameRuleEndedEvent(GameRulePrototype rule) - { - Rule = rule; - } -} -*/ diff --git a/Content.Server/GameTicking/GameTicker.Player.cs b/Content.Server/GameTicking/GameTicker.Player.cs index be52137d4f..2e00374205 100644 --- a/Content.Server/GameTicking/GameTicker.Player.cs +++ b/Content.Server/GameTicking/GameTicker.Player.cs @@ -142,13 +142,30 @@ private async void PlayerStatusChanged(object? sender, SessionStatusEventArgs ar async void SpawnWaitDb() { - await _userDb.WaitLoadComplete(session); + try + {await _userDb.WaitLoadComplete(session);} + catch (OperationCanceledException) + { + // Bail, user must've disconnected or something. + Log.Debug($"Database load cancelled while waiting to spawn {session}"); + return; + } SpawnPlayer(session, EntityUid.Invalid); } async void SpawnObserverWaitDb() { - await _userDb.WaitLoadComplete(session); + try + { + await _userDb.WaitLoadComplete(session); + } + catch (OperationCanceledException) + { + // Bail, user must've disconnected or something. + Log.Debug($"Database load cancelled while waiting to spawn {session}"); + return; + } + JoinAsObserver(session); } diff --git a/Content.Server/GameTicking/GameTicker.RoundFlow.cs b/Content.Server/GameTicking/GameTicker.RoundFlow.cs index 154244ace1..889a8a5a2b 100644 --- a/Content.Server/GameTicking/GameTicker.RoundFlow.cs +++ b/Content.Server/GameTicking/GameTicker.RoundFlow.cs @@ -167,7 +167,7 @@ public IReadOnlyList LoadGameMap(GameMapPrototype map, MapId targetMa var gridIds = _map.LoadMap(targetMapId, ev.GameMap.MapPath.ToString(), ev.Options); - _metaData.SetEntityName(_mapManager.GetMapEntityId(targetMapId), "Station map"); + _metaData.SetEntityName(_mapManager.GetMapEntityId(targetMapId), $"station map - {map.MapName}"); var gridUids = gridIds.ToList(); RaiseLocalEvent(new PostGameMapLoad(map, targetMapId, gridUids, stationName)); @@ -175,6 +175,26 @@ public IReadOnlyList LoadGameMap(GameMapPrototype map, MapId targetMa return gridUids; } + public int ReadyPlayerCount() + { + var total = 0; + foreach (var (userId, status) in _playerGameStatuses) + { + if (LobbyEnabled && status == PlayerGameStatus.NotReadyToPlay) + continue; + + if (!_playerManager.TryGetSessionById(userId, out _)) + continue; + + if (_banManager.GetRoleBans(userId) == null) + continue; + + total++; + } + + return total; + } + public void StartRound(bool force = false) { #if EXCEPTION_TOLERANCE @@ -230,6 +250,8 @@ public void StartRound(bool force = false) readyPlayerProfiles.Add(userId, profile); } + DebugTools.AssertEqual(readyPlayers.Count, ReadyPlayerCount()); + // Just in case it hasn't been loaded previously we'll try loading it. LoadMaps(); @@ -247,7 +269,10 @@ public void StartRound(bool force = false) var origReadyPlayers = readyPlayers.ToArray(); if (!StartPreset(origReadyPlayers, force)) + { + _startingRound = false; return; + } // MapInitialize *before* spawning players, our codebase is too shit to do it afterwards... _mapManager.DoMapInitialize(DefaultMap); @@ -771,7 +796,7 @@ public RulePlayerSpawningEvent(List playerPool, IReadOnlyDiction } /// - /// Event raised after players were assigned jobs by the GameTicker. + /// Event raised after players were assigned jobs by the GameTicker and have been spawned in. /// You can give on-station people special roles by listening to this event. /// public sealed class RulePlayerJobsAssignedEvent diff --git a/Content.Server/GameTicking/GameTicker.Spawning.cs b/Content.Server/GameTicking/GameTicker.Spawning.cs index bd7d9b5393..3016195f4b 100644 --- a/Content.Server/GameTicking/GameTicker.Spawning.cs +++ b/Content.Server/GameTicking/GameTicker.Spawning.cs @@ -2,12 +2,15 @@ using System.Linq; using System.Numerics; using Content.Server.Administration.Managers; +using Content.Server.GameTicking.Events; using Content.Server.Ghost; using Content.Server.Spawners.Components; using Content.Server.Speech.Components; using Content.Server.Station.Components; using Content.Shared.CCVar; +using Content.Shared.Chat; using Content.Shared.Database; +using Content.Shared.Mind; using Content.Shared.Players; using Content.Shared.Preferences; using Content.Shared.Roles; @@ -55,7 +58,9 @@ public List GetSpawnableStations() return spawnableStations; } - private void SpawnPlayers(List readyPlayers, Dictionary profiles, bool force) + private void SpawnPlayers(List readyPlayers, + Dictionary profiles, + bool force) { // Allow game rules to spawn players by themselves if needed. (For example, nuke ops or wizard) RaiseLocalEvent(new RulePlayerSpawningEvent(readyPlayers, profiles, force)); @@ -116,10 +121,17 @@ private void SpawnPlayers(List readyPlayers, Dictionary _playerManager.GetSessionById(x)).ToArray(), profiles, force)); + RaiseLocalEvent(new RulePlayerJobsAssignedEvent( + assignedJobs.Keys.Select(x => _playerManager.GetSessionById(x)).ToArray(), + profiles, + force)); } - private void SpawnPlayer(ICommonSession player, EntityUid station, string? jobId = null, bool lateJoin = true, bool silent = false) + private void SpawnPlayer(ICommonSession player, + EntityUid station, + string? jobId = null, + bool lateJoin = true, + bool silent = false) { var character = GetPlayerProfile(player); @@ -127,12 +139,23 @@ private void SpawnPlayer(ICommonSession player, EntityUid station, string? jobId if (jobBans == null || jobId != null && jobBans.Contains(jobId)) return; - if (jobId != null && !_playTimeTrackings.IsAllowed(player, jobId)) - return; + if (jobId != null) + { + var ev = new IsJobAllowedEvent(player, new ProtoId(jobId)); + RaiseLocalEvent(ref ev); + if (ev.Cancelled) + return; + } + SpawnPlayer(player, character, station, jobId, lateJoin, silent); } - private void SpawnPlayer(ICommonSession player, HumanoidCharacterProfile character, EntityUid station, string? jobId = null, bool lateJoin = true, bool silent = false) + private void SpawnPlayer(ICommonSession player, + HumanoidCharacterProfile character, + EntityUid station, + string? jobId = null, + bool lateJoin = true, + bool silent = false) { // Can't spawn players with a dummy ticker! if (DummyTicker) @@ -154,6 +177,20 @@ private void SpawnPlayer(ICommonSession player, HumanoidCharacterProfile charact return; } + //Ghost system return to round, check for whether the character isn't the same. + if (!_cfg.GetCVar(CCVars.GhostAllowSameCharacter) && lateJoin && !_adminManager.IsAdmin(player) && !CheckGhostReturnToRound(player, character, out var checkAvoid)) + { + var message = checkAvoid + ? Loc.GetString("ghost-respawn-same-character-slightly-changed-name") + : Loc.GetString("ghost-respawn-same-character"); + var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", message)); + + _chatManager.ChatMessageToOne(ChatChannel.Server, message, wrappedMessage, + default, false, player.Channel, Color.Red); + + return; + } + // We raise this event to allow other systems to handle spawning this player themselves. (e.g. late-join wizard, etc) var bev = new PlayerBeforeSpawnEvent(player, character, jobId, lateJoin, station); RaiseLocalEvent(bev); @@ -166,17 +203,18 @@ private void SpawnPlayer(ICommonSession player, HumanoidCharacterProfile charact } // Figure out job restrictions - var restrictedRoles = new HashSet(); - - var getDisallowed = _playTimeTrackings.GetDisallowedJobs(player); - restrictedRoles.UnionWith(getDisallowed); + var restrictedRoles = new HashSet>(); + var ev = new GetDisallowedJobsEvent(player, restrictedRoles); + RaiseLocalEvent(ref ev); var jobBans = _banManager.GetJobBans(player.UserId); if (jobBans != null) restrictedRoles.UnionWith(jobBans); // Pick best job best on prefs. - jobId ??= _stationJobs.PickBestAvailableJobWithPriority(station, character.JobPriorities, true, + jobId ??= _stationJobs.PickBestAvailableJobWithPriority(station, + character.JobPriorities, + true, restrictedRoles); // If no job available, stay in lobby, or if no lobby spawn as observer if (jobId is null) @@ -185,7 +223,9 @@ private void SpawnPlayer(ICommonSession player, HumanoidCharacterProfile charact { JoinAsObserver(player); } - _chatManager.DispatchServerMessage(player, Loc.GetString("game-ticker-player-no-jobs-available-when-joining")); + + _chatManager.DispatchServerMessage(player, + Loc.GetString("game-ticker-player-no-jobs-available-when-joining")); return; } @@ -222,11 +262,10 @@ private void SpawnPlayer(ICommonSession player, HumanoidCharacterProfile charact if (lateJoin && !silent) { _chatSystem.DispatchStationAnnouncement(station, - Loc.GetString( - "latejoin-arrival-announcement", - ("character", MetaData(mob).EntityName), - ("job", CultureInfo.CurrentCulture.TextInfo.ToTitleCase(jobName)) - ), Loc.GetString("latejoin-arrival-sender"), + Loc.GetString("latejoin-arrival-announcement", + ("character", MetaData(mob).EntityName), + ("job", CultureInfo.CurrentCulture.TextInfo.ToTitleCase(jobName))), + Loc.GetString("latejoin-arrival-sender"), playDefaultSound: false); } @@ -238,14 +277,17 @@ private void SpawnPlayer(ICommonSession player, HumanoidCharacterProfile charact _stationJobs.TryAssignJob(station, jobPrototype, player.UserId); if (lateJoin) - _adminLogger.Add(LogType.LateJoin, LogImpact.Medium, $"Player {player.Name} late joined as {character.Name:characterName} on station {Name(station):stationName} with {ToPrettyString(mob):entity} as a {jobName:jobName}."); + _adminLogger.Add(LogType.LateJoin, + LogImpact.Medium, + $"Player {player.Name} late joined as {character.Name:characterName} on station {Name(station):stationName} with {ToPrettyString(mob):entity} as a {jobName:jobName}."); else - _adminLogger.Add(LogType.RoundStartJoin, LogImpact.Medium, $"Player {player.Name} joined as {character.Name:characterName} on station {Name(station):stationName} with {ToPrettyString(mob):entity} as a {jobName:jobName}."); + _adminLogger.Add(LogType.RoundStartJoin, + LogImpact.Medium, + $"Player {player.Name} joined as {character.Name:characterName} on station {Name(station):stationName} with {ToPrettyString(mob):entity} as a {jobName:jobName}."); // Make sure they're aware of extended access. if (Comp(station).ExtendedAccess - && (jobPrototype.ExtendedAccess.Count > 0 - || jobPrototype.ExtendedAccessGroups.Count > 0)) + && (jobPrototype.ExtendedAccess.Count > 0 || jobPrototype.ExtendedAccessGroups.Count > 0)) { _chatManager.DispatchServerMessage(player, Loc.GetString("job-greet-crew-shortages")); } @@ -267,14 +309,20 @@ private void SpawnPlayer(ICommonSession player, HumanoidCharacterProfile charact } else { - _chatManager.DispatchServerMessage(player, Loc.GetString("latejoin-arrivals-direction-time", - ("time", $"{arrival:mm\\:ss}"))); + _chatManager.DispatchServerMessage(player, + Loc.GetString("latejoin-arrivals-direction-time", ("time", $"{arrival:mm\\:ss}"))); } } // We raise this event directed to the mob, but also broadcast it so game rules can do something now. PlayersJoinedRoundNormally++; - var aev = new PlayerSpawnCompleteEvent(mob, player, jobId, lateJoin, PlayersJoinedRoundNormally, station, character); + var aev = new PlayerSpawnCompleteEvent(mob, + player, + jobId, + lateJoin, + PlayersJoinedRoundNormally, + station, + character); RaiseLocalEvent(mob, aev, true); } @@ -329,20 +377,74 @@ public void SpawnObserver(ICommonSession player) if (DummyTicker) return; - var mind = player.GetMind(); + Entity? mind = player.GetMind(); if (mind == null) { - mind = _mind.CreateMind(player.UserId); + var name = GetPlayerProfile(player).Name; + var (mindId, mindComp) = _mind.CreateMind(player.UserId, name); + mind = (mindId, mindComp); _mind.SetUserId(mind.Value, player.UserId); _roles.MindAddRole(mind.Value, new ObserverRoleComponent()); } - var name = GetPlayerProfile(player).Name; - var ghost = SpawnObserverMob(); - _metaData.SetEntityName(ghost, name); - _ghost.SetCanReturnToBody(ghost, false); - _mind.TransferTo(mind.Value, ghost); - _adminLogger.Add(LogType.LateJoin, LogImpact.Low, $"{player.Name} late joined the round as an Observer with {ToPrettyString(ghost):entity}."); + var ghost = _ghost.SpawnGhost(mind.Value); + _adminLogger.Add(LogType.LateJoin, + LogImpact.Low, + $"{player.Name} late joined the round as an Observer with {ToPrettyString(ghost):entity}."); + } + + private bool CheckGhostReturnToRound(ICommonSession player, HumanoidCharacterProfile character, out bool checkAvoid) + { + checkAvoid = false; + + var allPlayerMinds = EntityQuery() + .Where(mind => mind.OriginalOwnerUserId == player.UserId); + + foreach (var mind in allPlayerMinds) + { + if (mind.CharacterName == character.Name) + return false; + + if (mind.CharacterName == null) + continue; + + var similarity = CalculateStringSimilarity(mind.CharacterName, character.Name); + switch (similarity) + { + case >= 85f: + _chatManager.SendAdminAlert(Loc.GetString("ghost-respawn-log-character-almost-same", + ("player", player.Name), ("try", false), ("oldName", mind.CharacterName), + ("newName", character.Name))); + checkAvoid = true; + + return false; + case >= 50f: + _chatManager.SendAdminAlert(Loc.GetString("ghost-respawn-log-character-almost-same", + ("player", player.Name), ("try", true), ("oldName", mind.CharacterName), + ("newName", character.Name))); + + break; + } + } + + return true; + } + + private float CalculateStringSimilarity(string str1, string str2) + { + var minLength = Math.Min(str1.Length, str2.Length); + var matchingCharacters = 0; + + for (var i = 0; i < minLength; i++) + { + if (str1[i] == str2[i]) + matchingCharacters++; + } + + float maxLength = Math.Max(str1.Length, str2.Length); + var similarityPercentage = (matchingCharacters / maxLength) * 100; + + return similarityPercentage; } #region Mob Spawning Helpers @@ -354,6 +456,7 @@ private EntityUid SpawnObserverMob() #endregion #region Spawn Points + public EntityCoordinates GetObserverSpawnPoint() { _possiblePositions.Clear(); @@ -374,8 +477,7 @@ public EntityCoordinates GetObserverSpawnPoint() var query = AllEntityQuery(); while (query.MoveNext(out var uid, out var grid)) { - if (!metaQuery.TryGetComponent(uid, out var meta) || - meta.EntityPaused) + if (!metaQuery.TryGetComponent(uid, out var meta) || meta.EntityPaused || TerminatingOrDeleted(uid)) { continue; } @@ -396,8 +498,7 @@ public EntityCoordinates GetObserverSpawnPoint() { var gridXform = Transform(gridUid); - return new EntityCoordinates(gridUid, - gridXform.InvWorldMatrix.Transform(toMap.Position)); + return new EntityCoordinates(gridUid, Vector2.Transform(toMap.Position, gridXform.InvWorldMatrix)); } return spawn; @@ -413,8 +514,9 @@ public EntityCoordinates GetObserverSpawnPoint() { var mapUid = _mapManager.GetMapEntityId(map); - if (!metaQuery.TryGetComponent(mapUid, out var meta) || - meta.EntityPaused) + if (!metaQuery.TryGetComponent(mapUid, out var meta) + || meta.EntityPaused + || TerminatingOrDeleted(mapUid)) { continue; } @@ -427,6 +529,7 @@ public EntityCoordinates GetObserverSpawnPoint() _sawmill.Warning("Found no observer spawn points!"); return EntityCoordinates.Invalid; } + #endregion } @@ -444,7 +547,11 @@ public sealed class PlayerBeforeSpawnEvent : HandledEntityEventArgs public bool LateJoin { get; } public EntityUid Station { get; } - public PlayerBeforeSpawnEvent(ICommonSession player, HumanoidCharacterProfile profile, string? jobId, bool lateJoin, EntityUid station) + public PlayerBeforeSpawnEvent(ICommonSession player, + HumanoidCharacterProfile profile, + string? jobId, + bool lateJoin, + EntityUid station) { Player = player; Profile = profile; @@ -472,7 +579,13 @@ public sealed class PlayerSpawnCompleteEvent : EntityEventArgs // Ex. If this is the 27th person to join, this will be 27. public int JoinOrder { get; } - public PlayerSpawnCompleteEvent(EntityUid mob, ICommonSession player, string? jobId, bool lateJoin, int joinOrder, EntityUid station, HumanoidCharacterProfile profile) + public PlayerSpawnCompleteEvent(EntityUid mob, + ICommonSession player, + string? jobId, + bool lateJoin, + int joinOrder, + EntityUid station, + HumanoidCharacterProfile profile) { Mob = mob; Player = player; diff --git a/Content.Server/GameTicking/GameTicker.cs b/Content.Server/GameTicking/GameTicker.cs index efda3df0ca..fa23312268 100644 --- a/Content.Server/GameTicking/GameTicker.cs +++ b/Content.Server/GameTicking/GameTicker.cs @@ -133,6 +133,7 @@ public override void Update(float frameTime) return; base.Update(frameTime); UpdateRoundFlow(frameTime); + UpdateGameRules(); } } } diff --git a/Content.Server/GameTicking/Rules/Components/LoadMapRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/LoadMapRuleComponent.cs new file mode 100644 index 0000000000..b7aef0c61d --- /dev/null +++ b/Content.Server/GameTicking/Rules/Components/LoadMapRuleComponent.cs @@ -0,0 +1,34 @@ +using Content.Server.Maps; +using Content.Shared.GridPreloader.Prototypes; +using Content.Shared.Storage; +using Content.Shared.Whitelist; +using Robust.Shared.Map; +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; + +namespace Content.Server.GameTicking.Rules.Components; + +/// +/// This is used for a game rule that loads a map when activated. +/// +[RegisterComponent] +public sealed partial class LoadMapRuleComponent : Component +{ + [DataField] + public MapId? Map; + + [DataField] + public ProtoId? GameMap; + + [DataField] + public ResPath? MapPath; + + [DataField] + public ProtoId? PreloadedGrid; + + [DataField] + public List MapGrids = new(); + + [DataField] + public EntityWhitelist? SpawnerWhitelist; +} diff --git a/Content.Server/GameTicking/Rules/Components/NinjaRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/NinjaRuleComponent.cs index e6966c1e37..fa352eb320 100644 --- a/Content.Server/GameTicking/Rules/Components/NinjaRuleComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/NinjaRuleComponent.cs @@ -8,7 +8,7 @@ namespace Content.Server.GameTicking.Rules.Components; /// /// Stores some configuration used by the ninja system. -/// Objectives and roundend summary are handled by . +/// Objectives and roundend summary are handled by . /// [RegisterComponent, Access(typeof(SpaceNinjaSystem))] public sealed partial class NinjaRuleComponent : Component diff --git a/Content.Server/GameTicking/Rules/Components/NukeOperativeSpawnerComponent.cs b/Content.Server/GameTicking/Rules/Components/NukeOperativeSpawnerComponent.cs index e02d90c18b..54eaa6e32e 100644 --- a/Content.Server/GameTicking/Rules/Components/NukeOperativeSpawnerComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/NukeOperativeSpawnerComponent.cs @@ -1,5 +1,4 @@ -using Content.Shared.Roles; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; +using Robust.Shared.Prototypes; namespace Content.Server.GameTicking.Rules.Components; @@ -8,12 +7,5 @@ namespace Content.Server.GameTicking.Rules.Components; /// and providing loadout + name for the operative on spawn. /// TODO: Remove once systems can request spawns from the ghost role system directly. /// -[RegisterComponent] -public sealed partial class NukeOperativeSpawnerComponent : Component -{ - [DataField("name", required:true)] - public string OperativeName = default!; - - [DataField] - public NukeopSpawnPreset SpawnDetails = default!; -} +[RegisterComponent, EntityCategory("Spawner")] +public sealed partial class NukeOperativeSpawnerComponent : Component; diff --git a/Content.Server/GameTicking/Rules/Components/NukeOpsShuttleComponent.cs b/Content.Server/GameTicking/Rules/Components/NukeOpsShuttleComponent.cs index 358b157cdf..3d097cd7c7 100644 --- a/Content.Server/GameTicking/Rules/Components/NukeOpsShuttleComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/NukeOpsShuttleComponent.cs @@ -6,4 +6,6 @@ [RegisterComponent] public sealed partial class NukeOpsShuttleComponent : Component { + [DataField] + public EntityUid AssociatedRule; } diff --git a/Content.Server/GameTicking/Rules/Components/NukeopsRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/NukeopsRuleComponent.cs index 8efd61b469..7862b38e59 100644 --- a/Content.Server/GameTicking/Rules/Components/NukeopsRuleComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/NukeopsRuleComponent.cs @@ -1,31 +1,17 @@ using Content.Server.Maps; using Content.Server.NPC.Components; using Content.Server.RoundEnd; -using Content.Server.StationEvents.Events; using Content.Shared.Dataset; using Content.Shared.Roles; -using Robust.Shared.Map; +using Robust.Shared.Audio; using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -using Robust.Shared.Utility; - namespace Content.Server.GameTicking.Rules.Components; -[RegisterComponent, Access(typeof(NukeopsRuleSystem), typeof(LoneOpsSpawnRule))] +[RegisterComponent, Access(typeof(NukeopsRuleSystem))] public sealed partial class NukeopsRuleComponent : Component { - /// - /// This INCLUDES the operatives. So a value of 3 is satisfied by 2 players & 1 operative - /// - [DataField] - public int PlayersPerOperative = 10; - - [DataField] - public int MaxOps = 5; - /// /// What will happen if all of the nuclear operatives will die. Used by LoneOpsSpawn event. /// @@ -56,12 +42,6 @@ public sealed partial class NukeopsRuleComponent : Component [DataField] public TimeSpan EvacShuttleTime = TimeSpan.FromMinutes(3); - /// - /// Whether or not to spawn the nuclear operative outpost. Used by LoneOpsSpawn event. - /// - [DataField] - public bool SpawnOutpost = true; - /// /// Whether or not nukie left their outpost /// @@ -84,7 +64,7 @@ public sealed partial class NukeopsRuleComponent : Component /// This amount of TC will be given to each nukie /// [DataField] - public int WarTCAmountPerNukie = 40; + public int WarTcAmountPerNukie = 40; /// /// Delay between war declaration and nuke ops arrival on station map. Gives crew time to prepare @@ -98,49 +78,23 @@ public sealed partial class NukeopsRuleComponent : Component [DataField] public int WarDeclarationMinOps = 4; - [DataField] - public EntProtoId SpawnPointProto = "SpawnPointNukies"; - - [DataField] - public EntProtoId GhostSpawnPointProto = "SpawnPointGhostNukeOperative"; - - [DataField] - public string OperationName = "Test Operation"; - - [DataField] - public ProtoId OutpostMapPrototype = "NukieOutpost"; - [DataField] public WinType WinType = WinType.Neutral; [DataField] public List WinConditions = new (); - public MapId? NukiePlanet; - - // TODO: use components, don't just cache entity UIDs - // There have been (and probably still are) bugs where these refer to deleted entities from old rounds. - public EntityUid? NukieOutpost; - public EntityUid? NukieShuttle; - public EntityUid? TargetStation; - - /// - /// Data to be used in for an operative once the Mind has been added. - /// - [DataField] - public Dictionary OperativeMindPendingData = new(); - - [DataField(required: true)] - public ProtoId Faction = default!; - [DataField] - public NukeopSpawnPreset CommanderSpawnDetails = new() { AntagRoleProto = "NukeopsCommander", GearProto = "SyndicateCommanderGearFull", NamePrefix = "nukeops-role-commander", NameList = "SyndicateNamesElite" }; + public EntityUid? TargetStation; [DataField] - public NukeopSpawnPreset AgentSpawnDetails = new() { AntagRoleProto = "NukeopsMedic", GearProto = "SyndicateOperativeMedicFull", NamePrefix = "nukeops-role-agent", NameList = "SyndicateNamesNormal" }; + public ProtoId Faction = "Syndicate"; + /// + /// Path to antagonist alert sound. + /// [DataField] - public NukeopSpawnPreset OperativeSpawnDetails = new(); + public SoundSpecifier GreetSoundNotification = new SoundPathSpecifier("/Audio/Ambience/Antag/nukeops_start.ogg"); } /// diff --git a/Content.Server/GameTicking/Rules/Components/PiratesRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/PiratesRuleComponent.cs deleted file mode 100644 index 1d03b41d77..0000000000 --- a/Content.Server/GameTicking/Rules/Components/PiratesRuleComponent.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Robust.Shared.Audio; - -namespace Content.Server.GameTicking.Rules.Components; - -[RegisterComponent, Access(typeof(PiratesRuleSystem))] -public sealed partial class PiratesRuleComponent : Component -{ - [ViewVariables] - public List Pirates = new(); - [ViewVariables] - public EntityUid PirateShip = EntityUid.Invalid; - [ViewVariables] - public HashSet InitialItems = new(); - [ViewVariables] - public double InitialShipValue; - - /// - /// Path to antagonist alert sound. - /// - [DataField("pirateAlertSound")] - public SoundSpecifier PirateAlertSound = new SoundPathSpecifier( - "/Audio/Ambience/Antag/pirate_start.ogg", - AudioParams.Default.WithVolume(4)); -} diff --git a/Content.Server/GameTicking/Rules/Components/RevolutionaryRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/RevolutionaryRuleComponent.cs index 2ce3f1f9a6..3b19bbffb6 100644 --- a/Content.Server/GameTicking/Rules/Components/RevolutionaryRuleComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/RevolutionaryRuleComponent.cs @@ -22,43 +22,6 @@ public sealed partial class RevolutionaryRuleComponent : Component [DataField] public TimeSpan TimerWait = TimeSpan.FromSeconds(20); - /// - /// Stores players minds - /// - [DataField] - public Dictionary HeadRevs = new(); - - [DataField] - public ProtoId HeadRevPrototypeId = "HeadRev"; - - /// - /// Min players needed for Revolutionary gamemode to start. - /// - [DataField, ViewVariables(VVAccess.ReadWrite)] - public int MinPlayers = 15; - - /// - /// Max Head Revs allowed during selection. - /// - [DataField, ViewVariables(VVAccess.ReadWrite)] - public int MaxHeadRevs = 3; - - /// - /// The amount of Head Revs that will spawn per this amount of players. - /// - [DataField, ViewVariables(VVAccess.ReadWrite)] - public int PlayersPerHeadRev = 15; - - /// - /// The gear head revolutionaries are given on spawn. - /// - [DataField] - public List StartingGear = new() - { - "Flash", - "ClothingEyesGlassesSunglasses" - }; - /// /// The time it takes after the last head is killed for the shuttle to arrive. /// diff --git a/Content.Server/GameTicking/Rules/Components/ThiefRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/ThiefRuleComponent.cs index 9dfd6e6627..01a078625a 100644 --- a/Content.Server/GameTicking/Rules/Components/ThiefRuleComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/ThiefRuleComponent.cs @@ -1,12 +1,11 @@ using Content.Shared.Random; -using Content.Shared.Roles; using Robust.Shared.Audio; using Robust.Shared.Prototypes; namespace Content.Server.GameTicking.Rules.Components; /// -/// Stores data for . +/// Stores data for . /// [RegisterComponent, Access(typeof(ThiefRuleSystem))] public sealed partial class ThiefRuleComponent : Component @@ -23,42 +22,9 @@ public sealed partial class ThiefRuleComponent : Component [DataField] public float BigObjectiveChance = 0.7f; - /// - /// Add a Pacified comp to thieves - /// - [DataField] - public bool PacifistThieves = true; - - [DataField] - public ProtoId ThiefPrototypeId = "Thief"; - [DataField] public float MaxObjectiveDifficulty = 2.5f; [DataField] public int MaxStealObjectives = 10; - - /// - /// Things that will be given to thieves - /// - [DataField] - public List StarterItems = new() { "ToolboxThief", "ClothingHandsChameleonThief" }; - - /// - /// All Thieves created by this rule - /// - [DataField] - public List ThievesMinds = new(); - - /// - /// Max Thiefs created by rule on roundstart - /// - [DataField] - public int MaxAllowThief = 3; - - /// - /// Sound played when making the player a thief via antag control or ghost role - /// - [DataField] - public SoundSpecifier? GreetingSound = new SoundPathSpecifier("/Audio/Misc/thief_greeting.ogg"); } diff --git a/Content.Server/GameTicking/Rules/Components/TraitorRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/TraitorRuleComponent.cs index 62619db76a..47a4adeaf3 100644 --- a/Content.Server/GameTicking/Rules/Components/TraitorRuleComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/TraitorRuleComponent.cs @@ -31,6 +31,9 @@ public sealed partial class TraitorRuleComponent : Component [DataField] public ProtoId CodewordVerbs = "verbs"; + [DataField] + public ProtoId ObjectiveIssuers = "TraitorCorporations"; + public int TotalTraitors => TraitorMinds.Count; public string[] Codewords = new string[3]; @@ -57,4 +60,19 @@ public enum SelectionState /// [DataField] public SoundSpecifier GreetSoundNotification = new SoundPathSpecifier("/Audio/Ambience/Antag/traitor_start.ogg"); + + /// + /// The amount of codewords that are selected. + /// + [DataField] + public int CodewordCount = 4; + + /// + /// The amount of TC traitors start with. + /// + [DataField] + public int StartingBalance = 20; + + [DataField] + public int MaxDifficulty = 5; } diff --git a/Content.Server/GameTicking/Rules/Components/ZombieRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/ZombieRuleComponent.cs index 4fe91e3a5f..59d1940eaf 100644 --- a/Content.Server/GameTicking/Rules/Components/ZombieRuleComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/ZombieRuleComponent.cs @@ -8,12 +8,6 @@ namespace Content.Server.GameTicking.Rules.Components; [RegisterComponent, Access(typeof(ZombieRuleSystem))] public sealed partial class ZombieRuleComponent : Component { - [DataField] - public Dictionary InitialInfectedNames = new(); - - [DataField] - public ProtoId PatientZeroPrototypeId = "InitialInfected"; - /// /// When the round will next check for round end. /// @@ -26,61 +20,9 @@ public sealed partial class ZombieRuleComponent : Component [DataField] public TimeSpan EndCheckDelay = TimeSpan.FromSeconds(30); - /// - /// The time at which the initial infected will be chosen. - /// - [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)] - public TimeSpan? StartTime; - - /// - /// The minimum amount of time after the round starts that the initial infected will be chosen. - /// - [DataField] - public TimeSpan MinStartDelay = TimeSpan.FromMinutes(10); - - /// - /// The maximum amount of time after the round starts that the initial infected will be chosen. - /// - [DataField] - public TimeSpan MaxStartDelay = TimeSpan.FromMinutes(15); - - /// - /// The sound that plays when someone becomes an initial infected. - /// todo: this should have a unique sound instead of reusing the zombie one. - /// - [DataField] - public SoundSpecifier InitialInfectedSound = new SoundPathSpecifier("/Audio/Ambience/Antag/zombie_start.ogg"); - - /// - /// The minimum amount of time initial infected have before they start taking infection damage. - /// - [DataField] - public TimeSpan MinInitialInfectedGrace = TimeSpan.FromMinutes(12.5f); - - /// - /// The maximum amount of time initial infected have before they start taking damage. - /// - [DataField] - public TimeSpan MaxInitialInfectedGrace = TimeSpan.FromMinutes(15f); - - /// - /// How many players for each initial infected. - /// - [DataField] - public int PlayersPerInfected = 10; - - /// - /// The maximum number of initial infected. - /// - [DataField] - public int MaxInitialInfected = 6; - /// /// After this amount of the crew become zombies, the shuttle will be automatically called. /// [DataField] public float ZombieShuttleCallPercentage = 0.7f; - - [DataField] - public EntProtoId ZombifySelfActionPrototype = "ActionTurnUndead"; } diff --git a/Content.Server/GameTicking/Rules/DeathMatchRuleSystem.cs b/Content.Server/GameTicking/Rules/DeathMatchRuleSystem.cs index 82ac755592..78b8a8a85c 100644 --- a/Content.Server/GameTicking/Rules/DeathMatchRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/DeathMatchRuleSystem.cs @@ -1,5 +1,6 @@ using System.Linq; using Content.Server.Administration.Commands; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.KillTracking; using Content.Server.Mind; @@ -33,7 +34,6 @@ public override void Initialize() SubscribeLocalEvent(OnSpawnComplete); SubscribeLocalEvent(OnKillReported); SubscribeLocalEvent(OnPointChanged); - SubscribeLocalEvent(OnRoundEndTextAppend); } private void OnBeforeSpawn(PlayerBeforeSpawnEvent ev) @@ -113,21 +113,17 @@ private void OnPointChanged(EntityUid uid, DeathMatchRuleComponent component, re _roundEnd.EndRound(component.RestartDelay); } - private void OnRoundEndTextAppend(RoundEndTextAppendEvent ev) + protected override void AppendRoundEndText(EntityUid uid, DeathMatchRuleComponent component, GameRuleComponent gameRule, ref RoundEndTextAppendEvent args) { - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out var dm, out var point, out var rule)) - { - if (!GameTicker.IsGameRuleAdded(uid, rule)) - continue; + if (!TryComp(uid, out var point)) + return; - if (dm.Victor != null && _player.TryGetPlayerData(dm.Victor.Value, out var data)) - { - ev.AddLine(Loc.GetString("point-scoreboard-winner", ("player", data.UserName))); - ev.AddLine(""); - } - ev.AddLine(Loc.GetString("point-scoreboard-header")); - ev.AddLine(new FormattedMessage(point.Scoreboard).ToMarkup()); + if (component.Victor != null && _player.TryGetPlayerData(component.Victor.Value, out var data)) + { + args.AddLine(Loc.GetString("point-scoreboard-winner", ("player", data.UserName))); + args.AddLine(""); } + args.AddLine(Loc.GetString("point-scoreboard-header")); + args.AddLine(new FormattedMessage(point.Scoreboard).ToMarkup()); } } diff --git a/Content.Server/GameTicking/Rules/GameRuleSystem.Utility.cs b/Content.Server/GameTicking/Rules/GameRuleSystem.Utility.cs index a60a2bfe22..cbd981e99e 100644 --- a/Content.Server/GameTicking/Rules/GameRuleSystem.Utility.cs +++ b/Content.Server/GameTicking/Rules/GameRuleSystem.Utility.cs @@ -1,6 +1,10 @@ using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.Station.Components; +using Content.Shared.Random.Helpers; +using Robust.Server.GameObjects; using Robust.Shared.Collections; using Robust.Shared.Map; using Robust.Shared.Map.Components; @@ -15,29 +19,12 @@ protected EntityQueryEnumerator Q return EntityQueryEnumerator(); } - protected bool TryRoundStartAttempt(RoundStartAttemptEvent ev, string localizedPresetName) + /// + /// Queries all gamerules, regardless of if they're active or not. + /// + protected EntityQueryEnumerator QueryAllRules() { - var query = EntityQueryEnumerator(); - while (query.MoveNext(out _, out _, out _, out var gameRule)) - { - var minPlayers = gameRule.MinPlayers; - if (!ev.Forced && ev.Players.Length < minPlayers) - { - ChatManager.SendAdminAnnouncement(Loc.GetString("preset-not-enough-ready-players", - ("readyPlayersCount", ev.Players.Length), ("minimumPlayers", minPlayers), - ("presetName", localizedPresetName))); - ev.Cancel(); - continue; - } - - if (ev.Players.Length == 0) - { - ChatManager.DispatchServerAnnouncement(Loc.GetString("preset-no-one-ready")); - ev.Cancel(); - } - } - - return !ev.Cancelled; + return EntityQueryEnumerator(); } /// @@ -98,17 +85,23 @@ protected bool TryFindRandomTileOnStation(Entity station, targetCoords = EntityCoordinates.Invalid; targetGrid = EntityUid.Invalid; - var possibleTargets = station.Comp.Grids; - if (possibleTargets.Count == 0) + // Weight grid choice by tilecount + var weights = new Dictionary, float>(); + foreach (var possibleTarget in station.Comp.Grids) + { + if (!TryComp(possibleTarget, out var comp)) + continue; + + weights.Add((possibleTarget, comp), _map.GetAllTiles(possibleTarget, comp).Count()); + } + + if (weights.Count == 0) { targetGrid = EntityUid.Invalid; return false; } - targetGrid = RobustRandom.Pick(possibleTargets); - - if (!TryComp(targetGrid, out var gridComp)) - return false; + (targetGrid, var gridComp) = RobustRandom.Pick(weights); var found = false; var aabb = gridComp.LocalAABB; diff --git a/Content.Server/GameTicking/Rules/GameRuleSystem.cs b/Content.Server/GameTicking/Rules/GameRuleSystem.cs index 363c2ad7f7..05374aa139 100644 --- a/Content.Server/GameTicking/Rules/GameRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/GameRuleSystem.cs @@ -1,6 +1,6 @@ using Content.Server.Atmos.EntitySystems; using Content.Server.Chat.Managers; -using Content.Server.GameTicking.Rules.Components; +using Content.Server.GameTicking.Components; using Robust.Server.GameObjects; using Robust.Shared.Random; using Robust.Shared.Timing; @@ -22,9 +22,31 @@ public override void Initialize() { base.Initialize(); + SubscribeLocalEvent(OnStartAttempt); SubscribeLocalEvent(OnGameRuleAdded); SubscribeLocalEvent(OnGameRuleStarted); SubscribeLocalEvent(OnGameRuleEnded); + SubscribeLocalEvent(OnRoundEndTextAppend); + } + + private void OnStartAttempt(RoundStartAttemptEvent args) + { + if (args.Forced || args.Cancelled) + return; + + var query = QueryAllRules(); + while (query.MoveNext(out var uid, out _, out var gameRule)) + { + var minPlayers = gameRule.MinPlayers; + if (args.Players.Length >= minPlayers) + continue; + + ChatManager.SendAdminAnnouncement(Loc.GetString("preset-not-enough-ready-players", + ("readyPlayersCount", args.Players.Length), + ("minimumPlayers", minPlayers), + ("presetName", ToPrettyString(uid)))); + args.Cancel(); + } } private void OnGameRuleAdded(EntityUid uid, T component, ref GameRuleAddedEvent args) @@ -48,6 +70,17 @@ private void OnGameRuleEnded(EntityUid uid, T component, ref GameRuleEndedEvent Ended(uid, component, ruleData, args); } + private void OnRoundEndTextAppend(RoundEndTextAppendEvent ev) + { + var query = AllEntityQuery(); + while (query.MoveNext(out var uid, out var comp)) + { + if (!TryComp(uid, out var ruleData)) + continue; + + AppendRoundEndText(uid, comp, ruleData, ref ev); + } + } /// /// Called when the gamerule is added @@ -73,6 +106,14 @@ protected virtual void Ended(EntityUid uid, T component, GameRuleComponent gameR } + /// + /// Called at the end of a round when text needs to be added for a game rule. + /// + protected virtual void AppendRoundEndText(EntityUid uid, T component, GameRuleComponent gameRule, ref RoundEndTextAppendEvent args) + { + + } + /// /// Called on an active gamerule entity in the Update function /// diff --git a/Content.Server/GameTicking/Rules/InactivityTimeRestartRuleSystem.cs b/Content.Server/GameTicking/Rules/InactivityTimeRestartRuleSystem.cs index b775b7af56..01fa387595 100644 --- a/Content.Server/GameTicking/Rules/InactivityTimeRestartRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/InactivityTimeRestartRuleSystem.cs @@ -1,5 +1,6 @@ using System.Threading; using Content.Server.Chat.Managers; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Robust.Server.Player; using Robust.Shared.Player; diff --git a/Content.Server/GameTicking/Rules/KillCalloutRuleSystem.cs b/Content.Server/GameTicking/Rules/KillCalloutRuleSystem.cs index 01fd97d9a7..3da55e30c9 100644 --- a/Content.Server/GameTicking/Rules/KillCalloutRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/KillCalloutRuleSystem.cs @@ -1,4 +1,5 @@ using Content.Server.Chat.Managers; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.KillTracking; using Content.Shared.Chat; diff --git a/Content.Server/GameTicking/Rules/LoadMapRuleSystem.cs b/Content.Server/GameTicking/Rules/LoadMapRuleSystem.cs new file mode 100644 index 0000000000..3a80d82fd9 --- /dev/null +++ b/Content.Server/GameTicking/Rules/LoadMapRuleSystem.cs @@ -0,0 +1,108 @@ +using Content.Server.Antag; +using Content.Server.GameTicking.Components; +using Content.Server.GameTicking.Rules.Components; +using Content.Server.GridPreloader; +using Content.Server.Spawners.Components; +using Robust.Server.GameObjects; +using Robust.Server.Maps; +using Robust.Shared.Map; +using Robust.Shared.Prototypes; + +namespace Content.Server.GameTicking.Rules; + +public sealed class LoadMapRuleSystem : GameRuleSystem +{ + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly MapSystem _map = default!; + [Dependency] private readonly MapLoaderSystem _mapLoader = default!; + [Dependency] private readonly MetaDataSystem _metaData = default!; + [Dependency] private readonly TransformSystem _transform = default!; + [Dependency] private readonly GridPreloaderSystem _gridPreloader = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnSelectLocation); + SubscribeLocalEvent(OnGridSplit); + } + + private void OnGridSplit(ref GridSplitEvent args) + { + var rule = QueryActiveRules(); + while (rule.MoveNext(out _, out var mapComp, out _)) + { + if (!mapComp.MapGrids.Contains(args.Grid)) + continue; + + mapComp.MapGrids.AddRange(args.NewGrids); + break; + } + } + + protected override void Added(EntityUid uid, LoadMapRuleComponent comp, GameRuleComponent rule, GameRuleAddedEvent args) + { + if (comp.Map != null) + return; + + // grid preloading needs map to init after moving it + var mapUid = comp.PreloadedGrid != null ? _map.CreateMap(out var mapId, false) : _map.CreateMap(out mapId); + _metaData.SetEntityName(mapUid, $"LoadMapRule destination for rule {ToPrettyString(uid)}"); + comp.Map = mapId; + + if (comp.GameMap != null) + { + var gameMap = _prototypeManager.Index(comp.GameMap.Value); + comp.MapGrids.AddRange(GameTicker.LoadGameMap(gameMap, comp.Map.Value, new MapLoadOptions())); + } + else if (comp.MapPath != null) + { + if (!_mapLoader.TryLoad(comp.Map.Value, + comp.MapPath.Value.ToString(), + out var roots, + new MapLoadOptions { LoadMap = true })) + { + _mapManager.DeleteMap(mapId); + return; + } + + comp.MapGrids.AddRange(roots); + } + else if (comp.PreloadedGrid != null) + { + // TODO: If there are no preloaded grids left, any rule announcements will still go off! + if (!_gridPreloader.TryGetPreloadedGrid(comp.PreloadedGrid.Value, out var loadedShuttle)) + { + _mapManager.DeleteMap(mapId); + return; + } + + _transform.SetParent(loadedShuttle.Value, mapUid); + comp.MapGrids.Add(loadedShuttle.Value); + _map.InitializeMap(mapId); + } + else + { + Log.Error($"No valid map prototype or map path associated with the rule {ToPrettyString(uid)}"); + } + } + + private void OnSelectLocation(Entity ent, ref AntagSelectLocationEvent args) + { + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out _, out var xform)) + { + if (xform.MapID != ent.Comp.Map) + continue; + + if (xform.GridUid == null || !ent.Comp.MapGrids.Contains(xform.GridUid.Value)) + continue; + + if (ent.Comp.SpawnerWhitelist != null && !ent.Comp.SpawnerWhitelist.IsValid(uid, EntityManager)) + continue; + + args.Coordinates.Add(_transform.GetMapCoordinates(xform)); + } + } +} diff --git a/Content.Server/GameTicking/Rules/MaxTimeRestartRuleSystem.cs b/Content.Server/GameTicking/Rules/MaxTimeRestartRuleSystem.cs index e792a004df..cae99fee9f 100644 --- a/Content.Server/GameTicking/Rules/MaxTimeRestartRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/MaxTimeRestartRuleSystem.cs @@ -1,5 +1,6 @@ using System.Threading; using Content.Server.Chat.Managers; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Timer = Robust.Shared.Timing.Timer; @@ -33,6 +34,7 @@ protected override void Ended(EntityUid uid, MaxTimeRestartRuleComponent compone public void RestartTimer(MaxTimeRestartRuleComponent component) { + // TODO FULL GAME SAVE component.TimerCancel.Cancel(); component.TimerCancel = new CancellationTokenSource(); Timer.Spawn(component.RoundMaxTime, () => TimerFired(component), component.TimerCancel.Token); @@ -49,6 +51,7 @@ private void TimerFired(MaxTimeRestartRuleComponent component) _chatManager.DispatchServerAnnouncement(Loc.GetString("rule-restarting-in-seconds",("seconds", (int) component.RoundEndDelay.TotalSeconds))); + // TODO FULL GAME SAVE Timer.Spawn(component.RoundEndDelay, () => GameTicker.RestartRound()); } diff --git a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs index 46040e2945..3e61fda874 100644 --- a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs @@ -1,77 +1,51 @@ -using Content.Server.Administration.Commands; -using Content.Server.Administration.Managers; using Content.Server.Antag; using Content.Server.Communications; using Content.Server.GameTicking.Rules.Components; -using Content.Server.Ghost.Roles.Components; -using Content.Server.Ghost.Roles.Events; using Content.Server.Humanoid; -using Content.Server.Mind; -using Content.Server.NPC.Components; -using Content.Server.NPC.Systems; using Content.Server.Nuke; using Content.Server.NukeOps; using Content.Server.Popups; using Content.Server.Preferences.Managers; -using Content.Server.RandomMetadata; using Content.Server.Roles; using Content.Server.RoundEnd; using Content.Server.Shuttles.Events; using Content.Server.Shuttles.Systems; -using Content.Server.Spawners.Components; using Content.Server.Station.Components; -using Content.Server.Station.Systems; using Content.Server.Store.Components; using Content.Server.Store.Systems; -using Content.Shared.CCVar; -using Content.Shared.Dataset; using Content.Shared.Humanoid; using Content.Shared.Humanoid.Prototypes; -using Content.Shared.Mind.Components; using Content.Shared.Mobs; using Content.Shared.Mobs.Components; using Content.Shared.Nuke; using Content.Shared.NukeOps; using Content.Shared.Preferences; -using Content.Shared.Roles; using Content.Shared.Store; using Content.Shared.Tag; using Content.Shared.Zombies; -using Robust.Server.Player; -using Robust.Shared.Configuration; using Robust.Shared.Map; -using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Utility; using System.Linq; +using Content.Server.GameTicking.Components; +using Content.Server.NPC.Components; +using Content.Server.NPC.Systems; namespace Content.Server.GameTicking.Rules; public sealed class NukeopsRuleSystem : GameRuleSystem { - [Dependency] private readonly IMapManager _mapManager = default!; - [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IServerPreferencesManager _prefs = default!; - [Dependency] private readonly IAdminManager _adminManager = default!; - [Dependency] private readonly IConfigurationManager _cfg = default!; - [Dependency] private readonly ILogManager _logManager = default!; [Dependency] private readonly EmergencyShuttleSystem _emergency = default!; [Dependency] private readonly HumanoidAppearanceSystem _humanoid = default!; - [Dependency] private readonly MetaDataSystem _metaData = default!; - [Dependency] private readonly RandomMetadataSystem _randomMetadata = default!; - [Dependency] private readonly MindSystem _mind = default!; [Dependency] private readonly NpcFactionSystem _npcFaction = default!; + [Dependency] private readonly AntagSelectionSystem _antag = default!; [Dependency] private readonly PopupSystem _popupSystem = default!; [Dependency] private readonly RoundEndSystem _roundEndSystem = default!; - [Dependency] private readonly SharedRoleSystem _roles = default!; - [Dependency] private readonly StationSpawningSystem _stationSpawning = default!; [Dependency] private readonly StoreSystem _store = default!; [Dependency] private readonly TagSystem _tag = default!; - [Dependency] private readonly AntagSelectionSystem _antagSelection = default!; - - private ISawmill _sawmill = default!; [ValidatePrototypeId] private const string TelecrystalCurrencyPrototype = "Telecrystal"; @@ -79,141 +53,67 @@ public sealed class NukeopsRuleSystem : GameRuleSystem [ValidatePrototypeId] private const string NukeOpsUplinkTagPrototype = "NukeOpsUplink"; - [ValidatePrototypeId] - public const string NukeopsId = "Nukeops"; - - [ValidatePrototypeId] - private const string OperationPrefixDataset = "operationPrefix"; - - [ValidatePrototypeId] - private const string OperationSuffixDataset = "operationSuffix"; - public override void Initialize() { base.Initialize(); - _sawmill = _logManager.GetSawmill("NukeOps"); - - SubscribeLocalEvent(OnStartAttempt); - SubscribeLocalEvent(OnPlayersSpawning); - SubscribeLocalEvent(OnRoundEndText); SubscribeLocalEvent(OnNukeExploded); SubscribeLocalEvent(OnRunLevelChanged); SubscribeLocalEvent(OnNukeDisarm); SubscribeLocalEvent(OnComponentRemove); SubscribeLocalEvent(OnMobStateChanged); - SubscribeLocalEvent(OnPlayersGhostSpawning); - SubscribeLocalEvent(OnMindAdded); SubscribeLocalEvent(OnOperativeZombified); + SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnShuttleFTLAttempt); SubscribeLocalEvent(OnWarDeclared); SubscribeLocalEvent(OnShuttleCallAttempt); + + SubscribeLocalEvent(OnAntagSelectEntity); + SubscribeLocalEvent(OnAfterAntagEntSelected); } protected override void Started(EntityUid uid, NukeopsRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args) { - base.Started(uid, component, gameRule, args); - - if (GameTicker.RunLevel == GameRunLevel.InRound) - SpawnOperativesForGhostRoles(uid, component); - } - - #region Event Handlers - - private void OnStartAttempt(RoundStartAttemptEvent ev) - { - TryRoundStartAttempt(ev, Loc.GetString("nukeops-title")); - } - - private void OnPlayersSpawning(RulePlayerSpawningEvent ev) - { - var query = QueryActiveRules(); - while (query.MoveNext(out var uid, out _, out var nukeops, out _)) + var eligible = new List>(); + var eligibleQuery = EntityQueryEnumerator(); + while (eligibleQuery.MoveNext(out var eligibleUid, out var eligibleComp, out var member)) { - if (!SpawnMap((uid, nukeops))) - { - _sawmill.Info("Failed to load map for nukeops"); - continue; - } - - //Handle there being nobody readied up - if (ev.PlayerPool.Count == 0) + if (!_npcFaction.IsFactionHostile(component.Faction, eligibleUid, member)) continue; - var commanderEligible = _antagSelection.GetEligibleSessions(ev.PlayerPool, nukeops.CommanderSpawnDetails.AntagRoleProto); - var agentEligible = _antagSelection.GetEligibleSessions(ev.PlayerPool, nukeops.AgentSpawnDetails.AntagRoleProto); - var operativeEligible = _antagSelection.GetEligibleSessions(ev.PlayerPool, nukeops.OperativeSpawnDetails.AntagRoleProto); - //Calculate how large the nukeops team needs to be - var nukiesToSelect = _antagSelection.CalculateAntagCount(_playerManager.PlayerCount, nukeops.PlayersPerOperative, nukeops.MaxOps); - - //Select Nukies - //Select Commander, priority : commanderEligible, agentEligible, operativeEligible, all players - var selectedCommander = _antagSelection.ChooseAntags(1, commanderEligible, agentEligible, operativeEligible, ev.PlayerPool).FirstOrDefault(); - //Select Agent, priority : agentEligible, operativeEligible, all players - var selectedAgent = _antagSelection.ChooseAntags(1, agentEligible, operativeEligible, ev.PlayerPool).FirstOrDefault(); - //Select Operatives, priority : operativeEligible, all players - var selectedOperatives = _antagSelection.ChooseAntags(nukiesToSelect - 2, operativeEligible, ev.PlayerPool); - - //Create the team! - //If the session is null, they will be spawned as ghost roles (provided the cvar is set) - var operatives = new List { new NukieSpawn(selectedCommander, nukeops.CommanderSpawnDetails) }; - if (nukiesToSelect > 1) - operatives.Add(new NukieSpawn(selectedAgent, nukeops.AgentSpawnDetails)); - - for (var i = 0; i < nukiesToSelect - 2; i++) - { - //Use up all available sessions first, then spawn the rest as ghost roles (if enabled) - if (selectedOperatives.Count > i) - { - operatives.Add(new NukieSpawn(selectedOperatives[i], nukeops.OperativeSpawnDetails)); - } - else - { - operatives.Add(new NukieSpawn(null, nukeops.OperativeSpawnDetails)); - } - } - - SpawnOperatives(operatives, _cfg.GetCVar(CCVars.NukeopsSpawnGhostRoles), nukeops); + eligible.Add((eligibleUid, eligibleComp, member)); + } - foreach (var nukieSpawn in operatives) - { - if (nukieSpawn.Session == null) - continue; + if (eligible.Count == 0) + return; - GameTicker.PlayerJoinGame(nukieSpawn.Session); - } - } + component.TargetStation = RobustRandom.Pick(eligible); } - private void OnRoundEndText(RoundEndTextAppendEvent ev) + #region Event Handlers + protected override void AppendRoundEndText(EntityUid uid, NukeopsRuleComponent component, GameRuleComponent gameRule, + ref RoundEndTextAppendEvent args) { - var ruleQuery = QueryActiveRules(); - while (ruleQuery.MoveNext(out _, out _, out var nukeops, out _)) - { - var winText = Loc.GetString($"nukeops-{nukeops.WinType.ToString().ToLower()}"); - ev.AddLine(winText); + var winText = Loc.GetString($"nukeops-{component.WinType.ToString().ToLower()}"); + args.AddLine(winText); - foreach (var cond in nukeops.WinConditions) - { - var text = Loc.GetString($"nukeops-cond-{cond.ToString().ToLower()}"); - ev.AddLine(text); - } + foreach (var cond in component.WinConditions) + { + var text = Loc.GetString($"nukeops-cond-{cond.ToString().ToLower()}"); + args.AddLine(text); } - ev.AddLine(Loc.GetString("nukeops-list-start")); + args.AddLine(Loc.GetString("nukeops-list-start")); - var nukiesQuery = EntityQueryEnumerator(); - while (nukiesQuery.MoveNext(out var nukeopsUid, out _, out var mindContainer)) - { - if (!_mind.TryGetMind(nukeopsUid, out _, out var mind, mindContainer)) - continue; + var antags =_antag.GetAntagIdentifiers(uid); - ev.AddLine(mind.Session != null - ? Loc.GetString("nukeops-list-name-user", ("name", Name(nukeopsUid)), ("user", mind.Session.Name)) - : Loc.GetString("nukeops-list-name", ("name", Name(nukeopsUid)))); + foreach (var (_, sessionData, name) in antags) + { + args.AddLine(Loc.GetString("nukeops-list-name-user", ("name", name), ("user", sessionData.UserName))); } } @@ -224,10 +124,10 @@ private void OnNukeExploded(NukeExplodedEvent ev) { if (ev.OwningStation != null) { - if (ev.OwningStation == nukeops.NukieOutpost) + if (ev.OwningStation == GetOutpost(uid)) { nukeops.WinConditions.Add(WinCondition.NukeExplodedOnNukieOutpost); - SetWinType(uid, WinType.CrewMajor, nukeops); + SetWinType((uid, nukeops), WinType.CrewMajor); continue; } @@ -242,7 +142,7 @@ private void OnNukeExploded(NukeExplodedEvent ev) } nukeops.WinConditions.Add(WinCondition.NukeExplodedOnCorrectStation); - SetWinType(uid, WinType.OpsMajor, nukeops); + SetWinType((uid, nukeops), WinType.OpsMajor); correctStation = true; } @@ -263,19 +163,85 @@ private void OnNukeExploded(NukeExplodedEvent ev) private void OnRunLevelChanged(GameRunLevelChangedEvent ev) { + if (ev.New is not GameRunLevel.PostRound) + return; + var query = QueryActiveRules(); while (query.MoveNext(out var uid, out _, out var nukeops, out _)) { - switch (ev.New) + OnRoundEnd((uid, nukeops)); + } + } + + private void OnRoundEnd(Entity ent) + { + // If the win condition was set to operative/crew major win, ignore. + if (ent.Comp.WinType == WinType.OpsMajor || ent.Comp.WinType == WinType.CrewMajor) + return; + + var nukeQuery = AllEntityQuery(); + var centcomms = _emergency.GetCentcommMaps(); + + while (nukeQuery.MoveNext(out var nuke, out var nukeTransform)) + { + if (nuke.Status != NukeStatus.ARMED) + continue; + + // UH OH + if (nukeTransform.MapUid != null && centcomms.Contains(nukeTransform.MapUid.Value)) + { + ent.Comp.WinConditions.Add(WinCondition.NukeActiveAtCentCom); + SetWinType((ent, ent), WinType.OpsMajor); + return; + } + + if (nukeTransform.GridUid == null || ent.Comp.TargetStation == null) + continue; + + if (!TryComp(ent.Comp.TargetStation.Value, out StationDataComponent? data)) + continue; + + foreach (var grid in data.Grids) { - case GameRunLevel.InRound: - OnRoundStart(uid, nukeops); - break; - case GameRunLevel.PostRound: - OnRoundEnd(uid, nukeops); - break; + if (grid != nukeTransform.GridUid) + continue; + + ent.Comp.WinConditions.Add(WinCondition.NukeActiveInStation); + SetWinType(ent, WinType.OpsMajor); + return; } } + + if (_antag.AllAntagsAlive(ent.Owner)) + { + SetWinType(ent, WinType.OpsMinor); + ent.Comp.WinConditions.Add(WinCondition.AllNukiesAlive); + return; + } + + ent.Comp.WinConditions.Add(_antag.AnyAliveAntags(ent.Owner) + ? WinCondition.SomeNukiesAlive + : WinCondition.AllNukiesDead); + + var diskAtCentCom = false; + var diskQuery = AllEntityQuery(); + while (diskQuery.MoveNext(out _, out var transform)) + { + diskAtCentCom = transform.MapUid != null && centcomms.Contains(transform.MapUid.Value); + + // TODO: The target station should be stored, and the nuke disk should store its original station. + // This is fine for now, because we can assume a single station in base SS14. + break; + } + + // If the disk is currently at Central Command, the crew wins - just slightly. + // This also implies that some nuclear operatives have died. + SetWinType(ent, diskAtCentCom + ? WinType.CrewMinor + : WinType.OpsMinor); + ent.Comp.WinConditions.Add(diskAtCentCom + ? WinCondition.NukeDiskOnCentCom + : WinCondition.NukeDiskNotOnCentCom); } private void OnNukeDisarm(NukeDisarmSuccessEvent ev) @@ -294,66 +260,31 @@ private void OnMobStateChanged(EntityUid uid, NukeOperativeComponent component, CheckRoundShouldEnd(); } - private void OnPlayersGhostSpawning(EntityUid uid, NukeOperativeComponent component, GhostRoleSpawnerUsedEvent args) + private void OnOperativeZombified(EntityUid uid, NukeOperativeComponent component, ref EntityZombifiedEvent args) { - var spawner = args.Spawner; - - if (!TryComp(spawner, out var nukeOpSpawner)) - return; - - HumanoidCharacterProfile? profile = null; - if (TryComp(args.Spawned, out ActorComponent? actor)) - profile = _prefs.GetPreferences(actor.PlayerSession.UserId).SelectedCharacter as HumanoidCharacterProfile; - - // TODO: this is kinda awful for multi-nukies - foreach (var nukeops in EntityQuery()) - { - SetupOperativeEntity(uid, nukeOpSpawner.OperativeName, nukeOpSpawner.SpawnDetails, profile); - - nukeops.OperativeMindPendingData.Add(uid, nukeOpSpawner.SpawnDetails.AntagRoleProto); - } + RemCompDeferred(uid, component); } - private void OnMindAdded(EntityUid uid, NukeOperativeComponent component, MindAddedMessage args) + private void OnMapInit(Entity ent, ref MapInitEvent args) { - if (!_mind.TryGetMind(uid, out var mindId, out var mind)) - return; + var map = Transform(ent).MapID; - var query = QueryActiveRules(); - while (query.MoveNext(out _, out _, out var nukeops, out _)) + var rules = EntityQueryEnumerator(); + while (rules.MoveNext(out var uid, out _, out var mapRule)) { - if (nukeops.OperativeMindPendingData.TryGetValue(uid, out var role) || !nukeops.SpawnOutpost || - nukeops.RoundEndBehavior == RoundEndBehavior.Nothing) - { - role ??= nukeops.OperativeSpawnDetails.AntagRoleProto; - _roles.MindAddRole(mindId, new NukeopsRoleComponent { PrototypeId = role }); - nukeops.OperativeMindPendingData.Remove(uid); - } - - if (mind.Session is not { } playerSession) - return; - - if (GameTicker.RunLevel != GameRunLevel.InRound) - return; - - if (nukeops.TargetStation != null && !string.IsNullOrEmpty(Name(nukeops.TargetStation.Value))) - { - NotifyNukie(playerSession, component, nukeops); - } + if (map != mapRule.Map) + continue; + ent.Comp.AssociatedRule = uid; + break; } } - private void OnOperativeZombified(EntityUid uid, NukeOperativeComponent component, ref EntityZombifiedEvent args) - { - RemCompDeferred(uid, component); - } - private void OnShuttleFTLAttempt(ref ConsoleFTLAttemptEvent ev) { var query = QueryActiveRules(); - while (query.MoveNext(out _, out _, out var nukeops, out _)) + while (query.MoveNext(out var uid, out _, out var nukeops, out _)) { - if (ev.Uid != nukeops.NukieShuttle) + if (ev.Uid != GetShuttle((uid, nukeops))) continue; if (nukeops.WarDeclaredTime != null) @@ -397,12 +328,12 @@ private void OnWarDeclared(ref WarDeclaredEvent ev) { // TODO: this is VERY awful for multi-nukies var query = QueryActiveRules(); - while (query.MoveNext(out _, out _, out var nukeops, out _)) + while (query.MoveNext(out var uid, out _, out var nukeops, out _)) { if (nukeops.WarDeclaredTime != null) continue; - if (Transform(ev.DeclaratorEntity).MapID != nukeops.NukiePlanet) + if (TryComp(uid, out var mapComp) && Transform(ev.DeclaratorEntity).MapID != mapComp.Map) continue; var newStatus = GetWarCondition(nukeops, ev.Status); @@ -413,7 +344,7 @@ private void OnWarDeclared(ref WarDeclaredEvent ev) var timeRemain = nukeops.WarNukieArriveDelay + Timing.CurTime; ev.DeclaratorEntity.Comp.ShuttleDisabledTime = timeRemain; - DistributeExtraTc(nukeops); + DistributeExtraTc((uid, nukeops)); } } } @@ -440,7 +371,7 @@ public WarConditionStatus GetWarCondition(NukeopsRuleComponent nukieRule, WarCon return WarConditionStatus.YesWar; } - private void DistributeExtraTc(NukeopsRuleComponent nukieRule) + private void DistributeExtraTc(Entity nukieRule) { var enumerator = EntityQueryEnumerator(); while (enumerator.MoveNext(out var uid, out var component)) @@ -448,161 +379,22 @@ private void DistributeExtraTc(NukeopsRuleComponent nukieRule) if (!_tag.HasTag(uid, NukeOpsUplinkTagPrototype)) continue; - if (!nukieRule.NukieOutpost.HasValue) + if (GetOutpost(nukieRule.Owner) is not { } outpost) continue; - if (Transform(uid).MapID != Transform(nukieRule.NukieOutpost.Value).MapID) // Will receive bonus TC only on their start outpost + if (Transform(uid).MapID != Transform(outpost).MapID) // Will receive bonus TC only on their start outpost continue; - _store.TryAddCurrency(new () { { TelecrystalCurrencyPrototype, nukieRule.WarTCAmountPerNukie } }, uid, component); + _store.TryAddCurrency(new () { { TelecrystalCurrencyPrototype, nukieRule.Comp.WarTcAmountPerNukie } }, uid, component); var msg = Loc.GetString("store-currency-war-boost-given", ("target", uid)); _popupSystem.PopupEntity(msg, uid); } } - private void OnRoundStart(EntityUid uid, NukeopsRuleComponent? component = null) + private void SetWinType(Entity ent, WinType type, bool endRound = true) { - if (!Resolve(uid, ref component)) - return; - - // TODO: This needs to try and target a Nanotrasen station. At the very least, - // we can only currently guarantee that NT stations are the only station to - // exist in the base game. - - var eligible = new List>(); - var eligibleQuery = EntityQueryEnumerator(); - while (eligibleQuery.MoveNext(out var eligibleUid, out var eligibleComp, out var member)) - { - if (!_npcFaction.IsFactionHostile(component.Faction, eligibleUid, member)) - continue; - - eligible.Add((eligibleUid, eligibleComp, member)); - } - - if (eligible.Count == 0) - return; - - component.TargetStation = RobustRandom.Pick(eligible); - component.OperationName = _randomMetadata.GetRandomFromSegments([OperationPrefixDataset, OperationSuffixDataset], " "); - - var filter = Filter.Empty(); - var query = EntityQueryEnumerator(); - while (query.MoveNext(out _, out var nukeops, out var actor)) - { - NotifyNukie(actor.PlayerSession, nukeops, component); - filter.AddPlayer(actor.PlayerSession); - } - } - - private void OnRoundEnd(EntityUid uid, NukeopsRuleComponent? component = null) - { - if (!Resolve(uid, ref component)) - return; - - // If the win condition was set to operative/crew major win, ignore. - if (component.WinType == WinType.OpsMajor || component.WinType == WinType.CrewMajor) - return; - - var nukeQuery = AllEntityQuery(); - var centcomms = _emergency.GetCentcommMaps(); - - while (nukeQuery.MoveNext(out var nuke, out var nukeTransform)) - { - if (nuke.Status != NukeStatus.ARMED) - continue; - - // UH OH - if (nukeTransform.MapUid != null && centcomms.Contains(nukeTransform.MapUid.Value)) - { - component.WinConditions.Add(WinCondition.NukeActiveAtCentCom); - SetWinType(uid, WinType.OpsMajor, component); - return; - } - - if (nukeTransform.GridUid == null || component.TargetStation == null) - continue; - - if (!TryComp(component.TargetStation.Value, out StationDataComponent? data)) - continue; - - foreach (var grid in data.Grids) - { - if (grid != nukeTransform.GridUid) - continue; - - component.WinConditions.Add(WinCondition.NukeActiveInStation); - SetWinType(uid, WinType.OpsMajor, component); - return; - } - } - - var allAlive = true; - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var nukeopsUid, out _, out var mindContainer, out var mobState)) - { - // mind got deleted somehow so ignore it - if (!_mind.TryGetMind(nukeopsUid, out _, out var mind, mindContainer)) - continue; - - // check if player got gibbed or ghosted or something - count as dead - if (mind.OwnedEntity != null && - // if the player somehow isn't a mob anymore that also counts as dead - // have to be alive, not crit or dead - mobState.CurrentState is MobState.Alive) - { - continue; - } - - allAlive = false; - break; - } - - // If all nuke ops were alive at the end of the round, - // the nuke ops win. This is to prevent people from - // running away the moment nuke ops appear. - if (allAlive) - { - SetWinType(uid, WinType.OpsMinor, component); - component.WinConditions.Add(WinCondition.AllNukiesAlive); - return; - } - - component.WinConditions.Add(WinCondition.SomeNukiesAlive); - - var diskAtCentCom = false; - var diskQuery = AllEntityQuery(); - - while (diskQuery.MoveNext(out _, out var transform)) - { - diskAtCentCom = transform.MapUid != null && centcomms.Contains(transform.MapUid.Value); - - // TODO: The target station should be stored, and the nuke disk should store its original station. - // This is fine for now, because we can assume a single station in base SS14. - break; - } - - // If the disk is currently at Central Command, the crew wins - just slightly. - // This also implies that some nuclear operatives have died. - if (diskAtCentCom) - { - SetWinType(uid, WinType.CrewMinor, component); - component.WinConditions.Add(WinCondition.NukeDiskOnCentCom); - } - // Otherwise, the nuke ops win. - else - { - SetWinType(uid, WinType.OpsMinor, component); - component.WinConditions.Add(WinCondition.NukeDiskNotOnCentCom); - } - } - - private void SetWinType(EntityUid uid, WinType type, NukeopsRuleComponent? component = null, bool endRound = true) - { - if (!Resolve(uid, ref component)) - return; - - component.WinType = type; + ent.Comp.WinType = type; if (endRound && (type == WinType.CrewMajor || type == WinType.OpsMajor)) _roundEndSystem.EndRound(); @@ -613,243 +405,128 @@ private void CheckRoundShouldEnd() var query = QueryActiveRules(); while (query.MoveNext(out var uid, out _, out var nukeops, out _)) { - if (nukeops.RoundEndBehavior == RoundEndBehavior.Nothing || nukeops.WinType == WinType.CrewMajor || nukeops.WinType == WinType.OpsMajor) - continue; - - // If there are any nuclear bombs that are active, immediately return. We're not over yet. - var armed = false; - foreach (var nuke in EntityQuery()) - { - if (nuke.Status == NukeStatus.ARMED) - { - armed = true; - break; - } - } - if (armed) - continue; - - MapId? shuttleMapId = Exists(nukeops.NukieShuttle) - ? Transform(nukeops.NukieShuttle.Value).MapID - : null; - - MapId? targetStationMap = null; - if (nukeops.TargetStation != null && TryComp(nukeops.TargetStation, out StationDataComponent? data)) - { - var grid = data.Grids.FirstOrNull(); - targetStationMap = grid != null - ? Transform(grid.Value).MapID - : null; - } - - // Check if there are nuke operatives still alive on the same map as the shuttle, - // or on the same map as the station. - // If there are, the round can continue. - var operatives = EntityQuery(true); - var operativesAlive = operatives - .Where(ent => - ent.Item3.MapID == shuttleMapId - || ent.Item3.MapID == targetStationMap) - .Any(ent => ent.Item2.CurrentState == MobState.Alive && ent.Item1.Running); - - if (operativesAlive) - continue; // There are living operatives than can access the shuttle, or are still on the station's map. - - // Check that there are spawns available and that they can access the shuttle. - var spawnsAvailable = EntityQuery(true).Any(); - if (spawnsAvailable && shuttleMapId == nukeops.NukiePlanet) - continue; // Ghost spawns can still access the shuttle. Continue the round. - - // The shuttle is inaccessible to both living nuke operatives and yet to spawn nuke operatives, - // and there are no nuclear operatives on the target station's map. - nukeops.WinConditions.Add(spawnsAvailable - ? WinCondition.NukiesAbandoned - : WinCondition.AllNukiesDead); - - SetWinType(uid, WinType.CrewMajor, nukeops, false); - _roundEndSystem.DoRoundEndBehavior( - nukeops.RoundEndBehavior, nukeops.EvacShuttleTime, nukeops.RoundEndTextSender, nukeops.RoundEndTextShuttleCall, nukeops.RoundEndTextAnnouncement); - - // prevent it called multiple times - nukeops.RoundEndBehavior = RoundEndBehavior.Nothing; + CheckRoundShouldEnd((uid, nukeops)); } } - private bool SpawnMap(Entity ent) + private void CheckRoundShouldEnd(Entity ent) { - if (!ent.Comp.SpawnOutpost - || ent.Comp.NukiePlanet != null) - return true; - - ent.Comp.NukiePlanet = _mapManager.CreateMap(); - var gameMap = _prototypeManager.Index(ent.Comp.OutpostMapPrototype); - ent.Comp.NukieOutpost = GameTicker.LoadGameMap(gameMap, ent.Comp.NukiePlanet.Value, null)[0]; - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var grid, out _, out var shuttleTransform)) - { - if (shuttleTransform.MapID != ent.Comp.NukiePlanet) - continue; - - ent.Comp.NukieShuttle = grid; - break; - } - - return true; - } - - /// - /// Adds missing nuke operative components, equips starting gear and renames the entity. - /// - private void SetupOperativeEntity(EntityUid mob, string name, NukeopSpawnPreset spawnDetails, HumanoidCharacterProfile? profile) - { - _metaData.SetEntityName(mob, name); - EnsureComp(mob); - - if (profile != null) - _humanoid.LoadProfile(mob, profile); - - var gear = _prototypeManager.Index(spawnDetails.GearProto); - _stationSpawning.EquipStartingGear(mob, gear, profile); - - _npcFaction.RemoveFaction(mob, "NanoTrasen", false); - _npcFaction.AddFaction(mob, "Syndicate"); - } + var nukeops = ent.Comp; - private void SpawnOperatives(List sessions, bool spawnGhostRoles, NukeopsRuleComponent component) - { - if (component.NukieOutpost is not { Valid: true } outpostUid) + if (nukeops.RoundEndBehavior == RoundEndBehavior.Nothing || nukeops.WinType == WinType.CrewMajor || nukeops.WinType == WinType.OpsMajor) return; - var spawns = new List(); - foreach (var (_, meta, xform) in EntityQuery(true)) - { - if (meta.EntityPrototype?.ID != component.SpawnPointProto.Id) - continue; - - if (xform.ParentUid != component.NukieOutpost) - continue; - spawns.Add(xform.Coordinates); - break; - } - - //Fallback, spawn at the centre of the map - if (spawns.Count == 0) + // If there are any nuclear bombs that are active, immediately return. We're not over yet. + foreach (var nuke in EntityQuery()) { - spawns.Add(Transform(outpostUid).Coordinates); - _sawmill.Warning($"Fell back to default spawn for nukies!"); + if (nuke.Status == NukeStatus.ARMED) + return; } - //Spawn the team - foreach (var nukieSession in sessions) - { - var name = $"{Loc.GetString(nukieSession.Type.NamePrefix)} {RobustRandom.PickAndTake(_prototypeManager.Index(nukieSession.Type.NameList).Values.ToList())}"; - - var nukeOpsAntag = _prototypeManager.Index(nukieSession.Type.AntagRoleProto); + var shuttle = GetShuttle((ent, ent)); - //If a session is available, spawn mob and transfer mind into it - if (nukieSession.Session != null) - { - var profile = _prefs.GetPreferences(nukieSession.Session.UserId).SelectedCharacter as HumanoidCharacterProfile; - if (!_prototypeManager.TryIndex(profile?.Species ?? SharedHumanoidAppearanceSystem.DefaultSpecies, out SpeciesPrototype? species)) - { - species = _prototypeManager.Index(SharedHumanoidAppearanceSystem.DefaultSpecies); - } + MapId? shuttleMapId = Exists(shuttle) + ? Transform(shuttle.Value).MapID + : null; - var mob = Spawn(species.Prototype, RobustRandom.Pick(spawns)); - SetupOperativeEntity(mob, name, nukieSession.Type, profile); - - var newMind = _mind.CreateMind(nukieSession.Session.UserId, name); - _mind.SetUserId(newMind, nukieSession.Session.UserId); - _roles.MindAddRole(newMind, new NukeopsRoleComponent { PrototypeId = nukieSession.Type.AntagRoleProto }); - - _mind.TransferTo(newMind, mob); - } - //Otherwise, spawn as a ghost role - else if (spawnGhostRoles) - { - var spawnPoint = Spawn(component.GhostSpawnPointProto, RobustRandom.Pick(spawns)); - var ghostRole = EnsureComp(spawnPoint); - EnsureComp(spawnPoint); - ghostRole.RoleName = Loc.GetString(nukeOpsAntag.Name); - ghostRole.RoleDescription = Loc.GetString(nukeOpsAntag.Objective); - - var nukeOpSpawner = EnsureComp(spawnPoint); - nukeOpSpawner.OperativeName = name; - nukeOpSpawner.SpawnDetails = nukieSession.Type; - } + MapId? targetStationMap = null; + if (nukeops.TargetStation != null && TryComp(nukeops.TargetStation, out StationDataComponent? data)) + { + var grid = data.Grids.FirstOrNull(); + targetStationMap = grid != null + ? Transform(grid.Value).MapID + : null; } - } - /// - /// Display a greeting message and play a sound for a nukie - /// - private void NotifyNukie(ICommonSession session, NukeOperativeComponent nukeop, NukeopsRuleComponent nukeopsRule) - { - if (nukeopsRule.TargetStation is not { } station) - return; - - _antagSelection.SendBriefing(session, Loc.GetString("nukeops-welcome", ("station", station), ("name", nukeopsRule.OperationName)), Color.Red, nukeop.GreetSoundNotification); + // Check if there are nuke operatives still alive on the same map as the shuttle, + // or on the same map as the station. + // If there are, the round can continue. + var operatives = EntityQuery(true); + var operativesAlive = operatives + .Where(op => + op.Item3.MapID == shuttleMapId + || op.Item3.MapID == targetStationMap) + .Any(op => op.Item2.CurrentState == MobState.Alive && op.Item1.Running); + + if (operativesAlive) + return; // There are living operatives than can access the shuttle, or are still on the station's map. + + // Check that there are spawns available and that they can access the shuttle. + var spawnsAvailable = EntityQuery(true).Any(); + if (spawnsAvailable && CompOrNull(ent)?.Map == shuttleMapId) + return; // Ghost spawns can still access the shuttle. Continue the round. + + // The shuttle is inaccessible to both living nuke operatives and yet to spawn nuke operatives, + // and there are no nuclear operatives on the target station's map. + nukeops.WinConditions.Add(spawnsAvailable + ? WinCondition.NukiesAbandoned + : WinCondition.AllNukiesDead); + + SetWinType(ent, WinType.CrewMajor, false); + _roundEndSystem.DoRoundEndBehavior( + nukeops.RoundEndBehavior, nukeops.EvacShuttleTime, nukeops.RoundEndTextSender, nukeops.RoundEndTextShuttleCall, nukeops.RoundEndTextAnnouncement); + + // prevent it called multiple times + nukeops.RoundEndBehavior = RoundEndBehavior.Nothing; } - /// - /// Spawn nukie ghost roles if this gamerule was started mid round - /// - private void SpawnOperativesForGhostRoles(EntityUid uid, NukeopsRuleComponent? component = null) + // this should really go anywhere else but im tired. + private void OnAntagSelectEntity(Entity ent, ref AntagSelectEntityEvent args) { - if (!Resolve(uid, ref component)) + if (args.Handled) return; - if (!SpawnMap((uid, component))) - { - _sawmill.Info("Failed to load map for nukeops"); - return; - } + var profile = args.Session != null + ? _prefs.GetPreferences(args.Session.UserId).SelectedCharacter as HumanoidCharacterProfile + : HumanoidCharacterProfile.RandomWithSpecies(); + if (!_prototypeManager.TryIndex(profile?.Species ?? SharedHumanoidAppearanceSystem.DefaultSpecies, out SpeciesPrototype? species)) + species = _prototypeManager.Index(SharedHumanoidAppearanceSystem.DefaultSpecies); - var numNukies = _antagSelection.CalculateAntagCount(_playerManager.PlayerCount, component.PlayersPerOperative, component.MaxOps); + args.Entity = Spawn(species.Prototype); + _humanoid.LoadProfile(args.Entity.Value, profile!); + } - //Dont continue if we have no nukies to spawn - if (numNukies == 0) + private void OnAfterAntagEntSelected(Entity ent, ref AfterAntagEntitySelectedEvent args) + { + if (ent.Comp.TargetStation is not { } station) return; - //Fill the ranks, commander first, then agent, then operatives - //TODO: Possible alternative team compositions? Like multiple commanders or agents - var operatives = new List(); - if (numNukies >= 1) - operatives.Add(new NukieSpawn(null, component.CommanderSpawnDetails)); - if (numNukies >= 2) - operatives.Add(new NukieSpawn(null, component.AgentSpawnDetails)); - if (numNukies >= 3) - { - for (var i = 2; i < numNukies; i++) - { - operatives.Add(new NukieSpawn(null, component.OperativeSpawnDetails)); - } - } - - SpawnOperatives(operatives, true, component); + _antag.SendBriefing(args.Session, Loc.GetString("nukeops-welcome", + ("station", station), + ("name", Name(ent))), + Color.Red, + ent.Comp.GreetSoundNotification); } - //For admins forcing someone to nukeOps. - public void MakeLoneNukie(EntityUid entity) + /// + /// Is this method the shitty glue holding together the last of my sanity? yes. + /// Do i have a better solution? not presently. + /// + private EntityUid? GetOutpost(Entity ent) { - if (!_mind.TryGetMind(entity, out var mindId, out var mindComponent)) - return; + if (!Resolve(ent, ref ent.Comp, false)) + return null; - //ok hardcoded value bad but so is everything else here - _roles.MindAddRole(mindId, new NukeopsRoleComponent { PrototypeId = NukeopsId }, mindComponent); - SetOutfitCommand.SetOutfit(entity, "SyndicateOperativeGearFull", EntityManager); + return ent.Comp.MapGrids.Where(e => HasComp(e) && !HasComp(e)).FirstOrNull(); } - private sealed class NukieSpawn + /// + /// Is this method the shitty glue holding together the last of my sanity? yes. + /// Do i have a better solution? not presently. + /// + private EntityUid? GetShuttle(Entity ent) { - public ICommonSession? Session { get; private set; } - public NukeopSpawnPreset Type { get; private set; } + if (!Resolve(ent, ref ent.Comp, false)) + return null; - public NukieSpawn(ICommonSession? session, NukeopSpawnPreset type) + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var comp)) { - Session = session; - Type = type; + if (comp.AssociatedRule == ent.Owner) + return uid; } + + return null; } } diff --git a/Content.Server/GameTicking/Rules/PiratesRuleSystem.cs b/Content.Server/GameTicking/Rules/PiratesRuleSystem.cs index 128f112304..e69de29bb2 100644 --- a/Content.Server/GameTicking/Rules/PiratesRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/PiratesRuleSystem.cs @@ -1,321 +0,0 @@ -using System.Linq; -using System.Numerics; -using Content.Server.Administration.Commands; -using Content.Server.Cargo.Systems; -using Content.Server.Chat.Managers; -using Content.Server.GameTicking.Rules.Components; -using Content.Server.NPC.Components; -using Content.Server.NPC.Systems; -using Content.Server.Preferences.Managers; -using Content.Server.Spawners.Components; -using Content.Server.Station.Components; -using Content.Server.Station.Systems; -using Content.Shared.CCVar; -using Content.Shared.Humanoid; -using Content.Shared.Humanoid.Prototypes; -using Content.Shared.Mind; -using Content.Shared.Preferences; -using Content.Shared.Roles; -using Robust.Server.GameObjects; -using Robust.Server.Maps; -using Robust.Server.Player; -using Robust.Shared.Audio; -using Robust.Shared.Audio.Systems; -using Robust.Shared.Configuration; -using Robust.Shared.Enums; -using Robust.Shared.Map; -using Robust.Shared.Map.Components; -using Robust.Shared.Player; -using Robust.Shared.Prototypes; -using Robust.Shared.Random; -using Robust.Shared.Utility; - -namespace Content.Server.GameTicking.Rules; - -/// -/// This handles the Pirates minor antag, which is designed to coincide with other modes on occasion. -/// -public sealed class PiratesRuleSystem : GameRuleSystem -{ - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly IConfigurationManager _cfg = default!; - [Dependency] private readonly IChatManager _chatManager = default!; - [Dependency] private readonly IMapManager _mapManager = default!; - [Dependency] private readonly IServerPreferencesManager _prefs = default!; - [Dependency] private readonly StationSpawningSystem _stationSpawningSystem = default!; - [Dependency] private readonly PricingSystem _pricingSystem = default!; - [Dependency] private readonly MapLoaderSystem _map = default!; - [Dependency] private readonly NamingSystem _namingSystem = default!; - [Dependency] private readonly NpcFactionSystem _npcFaction = default!; - [Dependency] private readonly SharedMindSystem _mindSystem = default!; - [Dependency] private readonly SharedAudioSystem _audioSystem = default!; - [Dependency] private readonly MetaDataSystem _metaData = default!; - - [ValidatePrototypeId] - private const string GameRuleId = "Pirates"; - - [ValidatePrototypeId] - private const string MobId = "MobHuman"; - - [ValidatePrototypeId] - private const string SpeciesId = "Human"; - - [ValidatePrototypeId] - private const string PirateFactionId = "Syndicate"; - - [ValidatePrototypeId] - private const string EnemyFactionId = "NanoTrasen"; - - [ValidatePrototypeId] - private const string GearId = "PirateGear"; - - [ValidatePrototypeId] - private const string SpawnPointId = "SpawnPointPirates"; - - /// - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnPlayerSpawningEvent); - SubscribeLocalEvent(OnRoundEndTextEvent); - SubscribeLocalEvent(OnStartAttempt); - } - - private void OnRoundEndTextEvent(RoundEndTextAppendEvent ev) - { - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out var pirates, out var gameRule)) - { - if (Deleted(pirates.PirateShip)) - { - // Major loss, the ship somehow got annihilated. - ev.AddLine(Loc.GetString("pirates-no-ship")); - } - else - { - List<(double, EntityUid)> mostValuableThefts = new(); - - var comp1 = pirates; - var finalValue = _pricingSystem.AppraiseGrid(pirates.PirateShip, uid => - { - foreach (var mindId in comp1.Pirates) - { - if (TryComp(mindId, out MindComponent? mind) && mind.CurrentEntity == uid) - return false; // Don't appraise the pirates twice, we count them in separately. - } - - return true; - }, (uid, price) => - { - if (comp1.InitialItems.Contains(uid)) - return; - - mostValuableThefts.Add((price, uid)); - mostValuableThefts.Sort((i1, i2) => i2.Item1.CompareTo(i1.Item1)); - if (mostValuableThefts.Count > 5) - mostValuableThefts.Pop(); - }); - - foreach (var mindId in pirates.Pirates) - { - if (TryComp(mindId, out MindComponent? mind) && mind.CurrentEntity is not null) - finalValue += _pricingSystem.GetPrice(mind.CurrentEntity.Value); - } - - var score = finalValue - pirates.InitialShipValue; - - ev.AddLine(Loc.GetString("pirates-final-score", ("score", $"{score:F2}"))); - ev.AddLine(Loc.GetString("pirates-final-score-2", ("finalPrice", $"{finalValue:F2}"))); - - ev.AddLine(""); - ev.AddLine(Loc.GetString("pirates-most-valuable")); - - foreach (var (price, obj) in mostValuableThefts) - { - ev.AddLine(Loc.GetString("pirates-stolen-item-entry", ("entity", obj), ("credits", $"{price:F2}"))); - } - - if (mostValuableThefts.Count == 0) - ev.AddLine(Loc.GetString("pirates-stole-nothing")); - } - - ev.AddLine(""); - ev.AddLine(Loc.GetString("pirates-list-start")); - foreach (var pirate in pirates.Pirates) - { - if (TryComp(pirate, out MindComponent? mind)) - { - ev.AddLine($"- {mind.CharacterName} ({mind.Session?.Name})"); - } - } - } - } - - private void OnPlayerSpawningEvent(RulePlayerSpawningEvent ev) - { - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out var pirates, out var gameRule)) - { - // Forgive me for copy-pasting nukies. - if (!GameTicker.IsGameRuleAdded(uid, gameRule)) - return; - - pirates.Pirates.Clear(); - pirates.InitialItems.Clear(); - - // Between 1 and : needs at least n players per op. - var numOps = Math.Max(1, - (int) Math.Min( - Math.Floor((double) ev.PlayerPool.Count / _cfg.GetCVar(CCVars.PiratesPlayersPerOp)), - _cfg.GetCVar(CCVars.PiratesMaxOps))); - var ops = new ICommonSession[numOps]; - for (var i = 0; i < numOps; i++) - { - ops[i] = _random.PickAndTake(ev.PlayerPool); - } - - var map = "/Maps/Shuttles/pirate.yml"; - var xformQuery = GetEntityQuery(); - - var aabbs = EntityQuery().SelectMany(x => - x.Grids.Select(x => - xformQuery.GetComponent(x).WorldMatrix.TransformBox(Comp(x).LocalAABB))) - .ToArray(); - - var aabb = aabbs[0]; - - for (var i = 1; i < aabbs.Length; i++) - { - aabb.Union(aabbs[i]); - } - - // (Not commented?) - var a = MathF.Max(aabb.Height / 2f, aabb.Width / 2f) * 2.5f; - - var gridId = _map.LoadGrid(GameTicker.DefaultMap, map, new MapLoadOptions - { - Offset = aabb.Center + new Vector2(a, a), - LoadMap = false, - }); - - if (!gridId.HasValue) - { - Log.Error($"Gridid was null when loading \"{map}\", aborting."); - foreach (var session in ops) - { - ev.PlayerPool.Add(session); - } - - return; - } - - pirates.PirateShip = gridId.Value; - - // TODO: Loot table or something - var pirateGear = _prototypeManager.Index(GearId); // YARRR - - var spawns = new List(); - - // Forgive me for hardcoding prototypes - foreach (var (_, meta, xform) in - EntityQuery(true)) - { - if (meta.EntityPrototype?.ID != SpawnPointId || xform.ParentUid != pirates.PirateShip) - continue; - - spawns.Add(xform.Coordinates); - } - - if (spawns.Count == 0) - { - spawns.Add(Transform(pirates.PirateShip).Coordinates); - Log.Warning($"Fell back to default spawn for pirates!"); - } - - for (var i = 0; i < ops.Length; i++) - { - var sex = _random.Prob(0.5f) ? Sex.Male : Sex.Female; - var gender = sex == Sex.Male ? Gender.Male : Gender.Female; - - var name = _namingSystem.GetName(SpeciesId, gender); - - var session = ops[i]; - var newMind = _mindSystem.CreateMind(session.UserId, name); - _mindSystem.SetUserId(newMind, session.UserId); - - var mob = Spawn(MobId, _random.Pick(spawns)); - _metaData.SetEntityName(mob, name); - - _mindSystem.TransferTo(newMind, mob); - var profile = _prefs.GetPreferences(session.UserId).SelectedCharacter as HumanoidCharacterProfile; - _stationSpawningSystem.EquipStartingGear(mob, pirateGear, profile); - - _npcFaction.RemoveFaction(mob, EnemyFactionId, false); - _npcFaction.AddFaction(mob, PirateFactionId); - - pirates.Pirates.Add(newMind); - - // Notificate every player about a pirate antagonist role with sound - _audioSystem.PlayGlobal(pirates.PirateAlertSound, session); - - GameTicker.PlayerJoinGame(session); - } - - pirates.InitialShipValue = _pricingSystem.AppraiseGrid(pirates.PirateShip, uid => - { - pirates.InitialItems.Add(uid); - return true; - }); // Include the players in the appraisal. - } - } - - //Forcing one player to be a pirate. - public void MakePirate(EntityUid entity) - { - if (!_mindSystem.TryGetMind(entity, out var mindId, out var mind)) - return; - - SetOutfitCommand.SetOutfit(entity, GearId, EntityManager); - - var pirateRule = EntityQuery().FirstOrDefault(); - if (pirateRule == null) - { - //todo fuck me this shit is awful - GameTicker.StartGameRule(GameRuleId, out var ruleEntity); - pirateRule = Comp(ruleEntity); - } - - // Notificate every player about a pirate antagonist role with sound - if (mind.Session != null) - { - _audioSystem.PlayGlobal(pirateRule.PirateAlertSound, mind.Session); - } - } - - private void OnStartAttempt(RoundStartAttemptEvent ev) - { - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out var pirates, out var gameRule)) - { - if (!GameTicker.IsGameRuleActive(uid, gameRule)) - return; - - var minPlayers = _cfg.GetCVar(CCVars.PiratesMinPlayers); - if (!ev.Forced && ev.Players.Length < minPlayers) - { - _chatManager.SendAdminAnnouncement(Loc.GetString("nukeops-not-enough-ready-players", - ("readyPlayersCount", ev.Players.Length), ("minimumPlayers", minPlayers))); - ev.Cancel(); - return; - } - - if (ev.Players.Length == 0) - { - _chatManager.DispatchServerAnnouncement(Loc.GetString("nukeops-no-one-ready")); - ev.Cancel(); - } - } - } -} diff --git a/Content.Server/GameTicking/Rules/RespawnRuleSystem.cs b/Content.Server/GameTicking/Rules/RespawnRuleSystem.cs index b11c28fb2b..5215da96aa 100644 --- a/Content.Server/GameTicking/Rules/RespawnRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/RespawnRuleSystem.cs @@ -1,4 +1,5 @@ using Content.Server.Chat.Managers; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.Station.Systems; using Content.Shared.Chat; diff --git a/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs b/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs index d20775c734..577b0c3030 100644 --- a/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs @@ -16,7 +16,6 @@ using Content.Shared.Database; using Content.Shared.Humanoid; using Content.Shared.IdentityManagement; -using Content.Shared.Inventory; using Content.Shared.Mind; using Content.Shared.Mind.Components; using Content.Shared.Mindshield.Components; @@ -24,12 +23,12 @@ using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; using Content.Shared.Revolutionary.Components; -using Content.Shared.Roles; using Content.Shared.Stunnable; using Content.Shared.Zombies; using Robust.Shared.Prototypes; using Robust.Shared.Timing; -using System.Linq; +using Content.Server.GameTicking.Components; +using Content.Shared.Cuffs.Components; namespace Content.Server.GameTicking.Rules; @@ -40,7 +39,7 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem RevolutionaryNpcFaction = "Revolutionary"; @@ -60,23 +58,12 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem(OnStartAttempt); - SubscribeLocalEvent(OnPlayerJobAssigned); SubscribeLocalEvent(OnCommandMobStateChanged); SubscribeLocalEvent(OnHeadRevMobStateChanged); - SubscribeLocalEvent(OnRoundEndText); SubscribeLocalEvent(OnGetBriefing); SubscribeLocalEvent(OnPostFlash); } - //Set miniumum players - protected override void Added(EntityUid uid, RevolutionaryRuleComponent component, GameRuleComponent gameRule, GameRuleAddedEvent args) - { - base.Added(uid, component, gameRule, args); - - gameRule.MinPlayers = component.MinPlayers; - } - protected override void Started(EntityUid uid, RevolutionaryRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args) { base.Started(uid, component, gameRule, args); @@ -98,40 +85,29 @@ protected override void ActiveTick(EntityUid uid, RevolutionaryRuleComponent com } } - private void OnRoundEndText(RoundEndTextAppendEvent ev) + protected override void AppendRoundEndText(EntityUid uid, RevolutionaryRuleComponent component, GameRuleComponent gameRule, + ref RoundEndTextAppendEvent args) { + base.AppendRoundEndText(uid, component, gameRule, ref args); + var revsLost = CheckRevsLose(); var commandLost = CheckCommandLose(); - var query = AllEntityQuery(); - while (query.MoveNext(out var headrev)) + // This is (revsLost, commandsLost) concatted together + // (moony wrote this comment idk what it means) + var index = (commandLost ? 1 : 0) | (revsLost ? 2 : 0); + args.AddLine(Loc.GetString(Outcomes[index])); + + var sessionData = _antag.GetAntagIdentifiers(uid); + args.AddLine(Loc.GetString("rev-headrev-count", ("initialCount", sessionData.Count))); + foreach (var (mind, data, name) in sessionData) { - // This is (revsLost, commandsLost) concatted together - // (moony wrote this comment idk what it means) - var index = (commandLost ? 1 : 0) | (revsLost ? 2 : 0); - ev.AddLine(Loc.GetString(Outcomes[index])); - - ev.AddLine(Loc.GetString("rev-headrev-count", ("initialCount", headrev.HeadRevs.Count))); - foreach (var player in headrev.HeadRevs) - { - // TODO: when role entities are a thing this has to change - var count = CompOrNull(player.Value)?.ConvertedCount ?? 0; + var count = CompOrNull(mind)?.ConvertedCount ?? 0; + args.AddLine(Loc.GetString("rev-headrev-name-user", + ("name", name), + ("username", data.UserName), + ("count", count))); - _mind.TryGetSession(player.Value, out var session); - var username = session?.Name; - if (username != null) - { - ev.AddLine(Loc.GetString("rev-headrev-name-user", - ("name", player.Key), - ("username", username), ("count", count))); - } - else - { - ev.AddLine(Loc.GetString("rev-headrev-name", - ("name", player.Key), ("count", count))); - } - - // TODO: someone suggested listing all alive? revs maybe implement at some point - } + // TODO: someone suggested listing all alive? revs maybe implement at some point } } @@ -144,57 +120,6 @@ private void OnGetBriefing(EntityUid uid, RevolutionaryRoleComponent comp, ref G args.Append(Loc.GetString(head ? "head-rev-briefing" : "rev-briefing")); } - //Check for enough players to start rule - private void OnStartAttempt(RoundStartAttemptEvent ev) - { - TryRoundStartAttempt(ev, Loc.GetString("roles-antag-rev-name")); - } - - private void OnPlayerJobAssigned(RulePlayerJobsAssignedEvent ev) - { - var query = QueryActiveRules(); - while (query.MoveNext(out var uid, out var activeGameRule, out var comp, out var gameRule)) - { - var eligiblePlayers = _antagSelection.GetEligiblePlayers(ev.Players, comp.HeadRevPrototypeId); - - if (eligiblePlayers.Count == 0) - continue; - - var headRevCount = _antagSelection.CalculateAntagCount(ev.Players.Length, comp.PlayersPerHeadRev, comp.MaxHeadRevs); - - var headRevs = _antagSelection.ChooseAntags(headRevCount, eligiblePlayers); - - GiveHeadRev(headRevs, comp.HeadRevPrototypeId, comp); - } - } - - private void GiveHeadRev(IEnumerable chosen, ProtoId antagProto, RevolutionaryRuleComponent comp) - { - foreach (var headRev in chosen) - GiveHeadRev(headRev, antagProto, comp); - } - private void GiveHeadRev(EntityUid chosen, ProtoId antagProto, RevolutionaryRuleComponent comp) - { - RemComp(chosen); - - var inCharacterName = MetaData(chosen).EntityName; - - if (!_mind.TryGetMind(chosen, out var mind, out _)) - return; - - if (!_role.MindHasRole(mind)) - { - _role.MindAddRole(mind, new RevolutionaryRoleComponent { PrototypeId = antagProto }, silent: true); - } - - comp.HeadRevs.Add(inCharacterName, mind); - _inventory.SpawnItemsOnEntity(chosen, comp.StartingGear); - var revComp = EnsureComp(chosen); - EnsureComp(chosen); - - _antagSelection.SendBriefing(chosen, Loc.GetString("head-rev-role-greeting"), Color.CornflowerBlue, revComp.RevStartSound); - } - /// /// Called when a Head Rev uses a flash in melee to convert somebody else. /// @@ -217,7 +142,6 @@ private void OnPostFlash(EntityUid uid, HeadRevolutionaryComponent comp, ref Aft _npcFaction.AddFaction(ev.Target, RevolutionaryNpcFaction); var revComp = EnsureComp(ev.Target); - _stun.TryParalyze(ev.Target, comp.StunTime, true); if (ev.User != null) { @@ -233,22 +157,7 @@ private void OnPostFlash(EntityUid uid, HeadRevolutionaryComponent comp, ref Aft } if (mind?.Session != null) - _antagSelection.SendBriefing(mind.Session, Loc.GetString("rev-role-greeting"), Color.Red, revComp.RevStartSound); - } - - public void OnHeadRevAdmin(EntityUid entity) - { - if (HasComp(entity)) - return; - - var revRule = EntityQuery().FirstOrDefault(); - if (revRule == null) - { - GameTicker.StartGameRule("Revolutionary", out var ruleEnt); - revRule = Comp(ruleEnt); - } - - GiveHeadRev(entity, revRule.HeadRevPrototypeId, revRule); + _antag.SendBriefing(mind.Session, Loc.GetString("rev-role-greeting"), Color.Red, revComp.RevStartSound); } //TODO: Enemies of the revolution @@ -271,7 +180,7 @@ private bool CheckCommandLose() commandList.Add(id); } - return IsGroupDead(commandList, true); + return IsGroupDetainedOrDead(commandList, true, true); } private void OnHeadRevMobStateChanged(EntityUid uid, HeadRevolutionaryComponent comp, MobStateChangedEvent ev) @@ -295,7 +204,8 @@ private bool CheckRevsLose() } // If no Head Revs are alive all normal Revs will lose their Rev status and rejoin Nanotrasen - if (IsGroupDead(headRevList, false)) + // Cuffing Head Revs is not enough - they must be killed. + if (IsGroupDetainedOrDead(headRevList, false, false)) { var rev = AllEntityQuery(); while (rev.MoveNext(out var uid, out _, out var mc)) @@ -309,7 +219,7 @@ private bool CheckRevsLose() _popup.PopupEntity(Loc.GetString("rev-break-control", ("name", Identity.Entity(uid, EntityManager))), uid); _adminLogManager.Add(LogType.Mind, LogImpact.Medium, $"{ToPrettyString(uid)} was deconverted due to all Head Revolutionaries dying."); - if (!_mind.TryGetMind(uid, out var mindId, out var mind, mc)) + if (!_mind.TryGetMind(uid, out var mindId, out _, mc)) continue; // remove their antag role @@ -327,35 +237,43 @@ private bool CheckRevsLose() } /// - /// Will take a group of entities and check if they are all alive or dead + /// Will take a group of entities and check if these entities are alive, dead or cuffed. /// /// The list of the entities - /// Bool for if you want to check if someone is in space and consider them dead. (Won't check when emergency shuttle arrives just in case) + /// Bool for if you want to check if someone is in space and consider them missing in action. (Won't check when emergency shuttle arrives just in case) + /// Bool for if you don't want to count cuffed entities. /// - private bool IsGroupDead(List list, bool checkOffStation) + private bool IsGroupDetainedOrDead(List list, bool checkOffStation, bool countCuffed) { - var dead = 0; + var gone = 0; foreach (var entity in list) { - if (TryComp(entity, out var state)) + if (TryComp(entity, out var cuffed) && cuffed.CuffedHandCount > 0 && countCuffed) { - if (state.CurrentState == MobState.Dead || state.CurrentState == MobState.Invalid) + gone++; + } + else + { + if (TryComp(entity, out var state)) { - dead++; + if (state.CurrentState == MobState.Dead || state.CurrentState == MobState.Invalid) + { + gone++; + } + else if (checkOffStation && _stationSystem.GetOwningStation(entity) == null && !_emergencyShuttle.EmergencyShuttleArrived) + { + gone++; + } } - else if (checkOffStation && _stationSystem.GetOwningStation(entity) == null && !_emergencyShuttle.EmergencyShuttleArrived) + //If they don't have the MobStateComponent they might as well be dead. + else { - dead++; + gone++; } } - //If they don't have the MobStateComponent they might as well be dead. - else - { - dead++; - } } - return dead == list.Count || list.Count == 0; + return gone == list.Count || list.Count == 0; } private static readonly string[] Outcomes = diff --git a/Content.Server/GameTicking/Rules/RoundstartStationVariationRuleSystem.cs b/Content.Server/GameTicking/Rules/RoundstartStationVariationRuleSystem.cs index 7755f684be..f09ed3ebc3 100644 --- a/Content.Server/GameTicking/Rules/RoundstartStationVariationRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/RoundstartStationVariationRuleSystem.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.Shuttles.Systems; using Content.Server.Station.Components; diff --git a/Content.Server/GameTicking/Rules/SandboxRuleSystem.cs b/Content.Server/GameTicking/Rules/SandboxRuleSystem.cs index a26a2d783c..c60670a3ad 100644 --- a/Content.Server/GameTicking/Rules/SandboxRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/SandboxRuleSystem.cs @@ -1,3 +1,4 @@ +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.Sandbox; diff --git a/Content.Server/GameTicking/Rules/SecretRuleSystem.cs b/Content.Server/GameTicking/Rules/SecretRuleSystem.cs index fa5f17b4f3..95bf5986a5 100644 --- a/Content.Server/GameTicking/Rules/SecretRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/SecretRuleSystem.cs @@ -1,14 +1,17 @@ +using System.Diagnostics.CodeAnalysis; +using System.Linq; using Content.Server.Administration.Logs; +using Content.Server.GameTicking.Components; using Content.Server.Chat.Managers; using Content.Server.GameTicking.Presets; using Content.Server.GameTicking.Rules.Components; using Content.Shared.Random; -using Content.Shared.Random.Helpers; using Content.Shared.CCVar; using Content.Shared.Database; using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Configuration; +using Robust.Shared.Utility; namespace Content.Server.GameTicking.Rules; @@ -19,11 +22,46 @@ public sealed class SecretRuleSystem : GameRuleSystem [Dependency] private readonly IConfigurationManager _configurationManager = default!; [Dependency] private readonly IAdminLogManager _adminLogger = default!; [Dependency] private readonly IChatManager _chatManager = default!; + [Dependency] private readonly IComponentFactory _compFact = default!; + + private string _ruleCompName = default!; + + public override void Initialize() + { + base.Initialize(); + _ruleCompName = _compFact.GetComponentName(typeof(GameRuleComponent)); + } protected override void Added(EntityUid uid, SecretRuleComponent component, GameRuleComponent gameRule, GameRuleAddedEvent args) { base.Added(uid, component, gameRule, args); - PickRule(component); + var weights = _configurationManager.GetCVar(CCVars.SecretWeightPrototype); + + if (!TryPickPreset(weights, out var preset)) + { + Log.Error($"{ToPrettyString(uid)} failed to pick any preset. Removing rule."); + Del(uid); + return; + } + + Log.Info($"Selected {preset.ID} as the secret preset."); + _adminLogger.Add(LogType.EventStarted, $"Selected {preset.ID} as the secret preset."); + _chatManager.SendAdminAnnouncement(Loc.GetString("rule-secret-selected-preset", ("preset", preset.ID))); + + foreach (var rule in preset.Rules) + { + EntityUid ruleEnt; + + // if we're pre-round (i.e. will only be added) + // then just add rules. if we're added in the middle of the round (or at any other point really) + // then we want to start them as well + if (GameTicker.RunLevel <= GameRunLevel.InRound) + ruleEnt = GameTicker.AddGameRule(rule); + else + GameTicker.StartGameRule(rule, out ruleEnt); + + component.AdditionalGameRules.Add(ruleEnt); + } } protected override void Ended(EntityUid uid, SecretRuleComponent component, GameRuleComponent gameRule, GameRuleEndedEvent args) @@ -36,32 +74,101 @@ protected override void Ended(EntityUid uid, SecretRuleComponent component, Game } } - private void PickRule(SecretRuleComponent component) + private bool TryPickPreset(ProtoId weights, [NotNullWhen(true)] out GamePresetPrototype? preset) { - // TODO: This doesn't consider what can't start due to minimum player count, - // but currently there's no way to know anyway as they use cvars. - var presetString = _configurationManager.GetCVar(CCVars.SecretWeightPrototype); - var preset = _prototypeManager.Index(presetString).Pick(_random); - Log.Info($"Selected {preset} for secret."); - _adminLogger.Add(LogType.EventStarted, $"Selected {preset} for secret."); - _chatManager.SendAdminAnnouncement(Loc.GetString("rule-secret-selected-preset", ("preset", preset))); - - var rules = _prototypeManager.Index(preset).Rules; - foreach (var rule in rules) + var options = _prototypeManager.Index(weights).Weights.ShallowClone(); + var players = GameTicker.ReadyPlayerCount(); + + GamePresetPrototype? selectedPreset = null; + var sum = options.Values.Sum(); + while (options.Count > 0) { - EntityUid ruleEnt; + var accumulated = 0f; + var rand = _random.NextFloat(sum); + foreach (var (key, weight) in options) + { + accumulated += weight; + if (accumulated < rand) + continue; - // if we're pre-round (i.e. will only be added) - // then just add rules. if we're added in the middle of the round (or at any other point really) - // then we want to start them as well - if (GameTicker.RunLevel <= GameRunLevel.InRound) - ruleEnt = GameTicker.AddGameRule(rule); - else + if (!_prototypeManager.TryIndex(key, out selectedPreset)) + Log.Error($"Invalid preset {selectedPreset} in secret rule weights: {weights}"); + + options.Remove(key); + sum -= weight; + break; + } + + if (CanPick(selectedPreset, players)) { - GameTicker.StartGameRule(rule, out ruleEnt); + preset = selectedPreset; + return true; } - component.AdditionalGameRules.Add(ruleEnt); + if (selectedPreset != null) + Log.Info($"Excluding {selectedPreset.ID} from secret preset selection."); + } + + preset = null; + return false; + } + + public bool CanPickAny() + { + var secretPresetId = _configurationManager.GetCVar(CCVars.SecretWeightPrototype); + return CanPickAny(secretPresetId); + } + + /// + /// Can any of the given presets be picked, taking into account the currently available player count? + /// + public bool CanPickAny(ProtoId weightedPresets) + { + var ids = _prototypeManager.Index(weightedPresets).Weights.Keys + .Select(x => new ProtoId(x)); + + return CanPickAny(ids); + } + + /// + /// Can any of the given presets be picked, taking into account the currently available player count? + /// + public bool CanPickAny(IEnumerable> protos) + { + var players = GameTicker.ReadyPlayerCount(); + foreach (var id in protos) + { + if (!_prototypeManager.TryIndex(id, out var selectedPreset)) + Log.Error($"Invalid preset {selectedPreset} in secret rule weights: {id}"); + + if (CanPick(selectedPreset, players)) + return true; } + + return false; + } + + /// + /// Can the given preset be picked, taking into account the currently available player count? + /// + private bool CanPick([NotNullWhen(true)] GamePresetPrototype? selected, int players) + { + if (selected == null) + return false; + + foreach (var ruleId in selected.Rules) + { + if (!_prototypeManager.TryIndex(ruleId, out EntityPrototype? rule) + || !rule.TryGetComponent(_ruleCompName, out GameRuleComponent? ruleComp)) + { + Log.Error($"Encountered invalid rule {ruleId} in preset {selected.ID}"); + return false; + } + + if (ruleComp.MinPlayers > players) + return false; + } + + return true; } } diff --git a/Content.Server/GameTicking/Rules/SubGamemodesSystem.cs b/Content.Server/GameTicking/Rules/SubGamemodesSystem.cs index 42e7e82335..4486ee40fb 100644 --- a/Content.Server/GameTicking/Rules/SubGamemodesSystem.cs +++ b/Content.Server/GameTicking/Rules/SubGamemodesSystem.cs @@ -1,3 +1,4 @@ +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Shared.Storage; diff --git a/Content.Server/GameTicking/Rules/ThiefRuleSystem.cs b/Content.Server/GameTicking/Rules/ThiefRuleSystem.cs index 32f9040f89..083085fa0d 100644 --- a/Content.Server/GameTicking/Rules/ThiefRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/ThiefRuleSystem.cs @@ -3,118 +3,38 @@ using Content.Server.Mind; using Content.Server.Objectives; using Content.Server.Roles; -using Content.Shared.Antag; -using Content.Shared.CombatMode.Pacification; using Content.Shared.Humanoid; -using Content.Shared.Inventory; using Content.Shared.Mind; using Content.Shared.Objectives.Components; -using Content.Shared.Roles; using Robust.Shared.Random; -using System.Linq; namespace Content.Server.GameTicking.Rules; public sealed class ThiefRuleSystem : GameRuleSystem { [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly AntagSelectionSystem _antagSelection = default!; [Dependency] private readonly MindSystem _mindSystem = default!; - [Dependency] private readonly SharedRoleSystem _roleSystem = default!; + [Dependency] private readonly AntagSelectionSystem _antag = default!; [Dependency] private readonly ObjectivesSystem _objectives = default!; - [Dependency] private readonly InventorySystem _inventory = default!; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnPlayersSpawned); + SubscribeLocalEvent(AfterAntagSelected); SubscribeLocalEvent(OnGetBriefing); SubscribeLocalEvent(OnObjectivesTextGetInfo); } - private void OnPlayersSpawned(RulePlayerJobsAssignedEvent ev) + private void AfterAntagSelected(Entity ent, ref AfterAntagEntitySelectedEvent args) { - var query = QueryActiveRules(); - while (query.MoveNext(out var uid, out _, out var comp, out var gameRule)) - { - //Get all players eligible for this role, allow selecting existing antags - //TO DO: When voxes specifies are added, increase their chance of becoming a thief by 4 times >:) - var eligiblePlayers = _antagSelection.GetEligiblePlayers(ev.Players, comp.ThiefPrototypeId, acceptableAntags: AntagAcceptability.All, allowNonHumanoids: true); - - //Abort if there are none - if (eligiblePlayers.Count == 0) - { - Log.Warning($"No eligible thieves found, ending game rule {ToPrettyString(uid):rule}"); - GameTicker.EndGameRule(uid, gameRule); - continue; - } - - //Calculate number of thieves to choose - var thiefCount = _random.Next(1, comp.MaxAllowThief + 1); - - //Select our theives - var thieves = _antagSelection.ChooseAntags(thiefCount, eligiblePlayers); - - MakeThief(thieves, comp, comp.PacifistThieves); - } - } - - public void MakeThief(List players, ThiefRuleComponent thiefRule, bool addPacified) - { - foreach (var thief in players) - { - MakeThief(thief, thiefRule, addPacified); - } - } - - public void MakeThief(EntityUid thief, ThiefRuleComponent thiefRule, bool addPacified) - { - if (!_mindSystem.TryGetMind(thief, out var mindId, out var mind)) + if (!_mindSystem.TryGetMind(args.EntityUid, out var mindId, out var mind)) return; - if (HasComp(mindId)) - return; - - // Assign thief roles - _roleSystem.MindAddRole(mindId, new ThiefRoleComponent - { - PrototypeId = thiefRule.ThiefPrototypeId, - }, silent: true); - - //Add Pacified - //To Do: Long-term this should just be using the antag code to add components. - if (addPacified) //This check is important because some servers may want to disable the thief's pacifism. Do not remove. - { - EnsureComp(thief); - } - //Generate objectives - GenerateObjectives(mindId, mind, thiefRule); - - //Send briefing here to account for humanoid/animal - _antagSelection.SendBriefing(thief, MakeBriefing(thief), null, thiefRule.GreetingSound); - - // Give starting items - _inventory.SpawnItemsOnEntity(thief, thiefRule.StarterItems); - - thiefRule.ThievesMinds.Add(mindId); - } - - public void AdminMakeThief(EntityUid entity, bool addPacified) - { - var thiefRule = EntityQuery().FirstOrDefault(); - if (thiefRule == null) - { - GameTicker.StartGameRule("Thief", out var ruleEntity); - thiefRule = Comp(ruleEntity); - } - - if (HasComp(entity)) - return; - - MakeThief(entity, thiefRule, addPacified); + GenerateObjectives(mindId, mind, ent); + _antag.SendBriefing(args.EntityUid, MakeBriefing(args.EntityUid), null, null); } private void GenerateObjectives(EntityUid mindId, MindComponent mind, ThiefRuleComponent thiefRule) @@ -160,8 +80,7 @@ private void OnGetBriefing(Entity thief, ref GetBriefingEven private string MakeBriefing(EntityUid thief) { var isHuman = HasComp(thief); - var briefing = "\n"; - briefing = isHuman + var briefing = isHuman ? Loc.GetString("thief-role-greeting-human") : Loc.GetString("thief-role-greeting-animal"); @@ -169,9 +88,9 @@ private string MakeBriefing(EntityUid thief) return briefing; } - private void OnObjectivesTextGetInfo(Entity thiefs, ref ObjectivesTextGetInfoEvent args) + private void OnObjectivesTextGetInfo(Entity ent, ref ObjectivesTextGetInfoEvent args) { - args.Minds = thiefs.Comp.ThievesMinds; + args.Minds = _antag.GetAntagMindEntityUids(ent.Owner); args.AgentName = Loc.GetString("thief-round-end-agent-name"); } } diff --git a/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs b/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs index fc9f0a9a9f..1db288799f 100644 --- a/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs @@ -6,96 +6,62 @@ using Content.Server.PDA.Ringer; using Content.Server.Roles; using Content.Server.Traitor.Uplink; -using Content.Shared.CCVar; -using Content.Shared.Dataset; using Content.Shared.Mind; -using Content.Shared.Mobs.Systems; using Content.Shared.Objectives.Components; using Content.Shared.PDA; using Content.Shared.Roles; using Content.Shared.Roles.Jobs; -using Robust.Server.Player; -using Robust.Shared.Configuration; using Robust.Shared.Prototypes; using Robust.Shared.Random; -using Robust.Shared.Timing; using System.Linq; using System.Text; +using Content.Shared.Mood; +using Content.Server.GameTicking.Components; +using Content.Server.Traitor.Components; namespace Content.Server.GameTicking.Rules; public sealed class TraitorRuleSystem : GameRuleSystem { - [Dependency] private readonly AntagSelectionSystem _antagSelection = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly IConfigurationManager _cfg = default!; - [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly NpcFactionSystem _npcFaction = default!; - [Dependency] private readonly MobStateSystem _mobStateSystem = default!; + [Dependency] private readonly AntagSelectionSystem _antag = default!; [Dependency] private readonly UplinkSystem _uplink = default!; [Dependency] private readonly MindSystem _mindSystem = default!; [Dependency] private readonly SharedRoleSystem _roleSystem = default!; [Dependency] private readonly SharedJobSystem _jobs = default!; [Dependency] private readonly ObjectivesSystem _objectives = default!; - [Dependency] private readonly IGameTiming _timing = default!; - private int PlayersPerTraitor => _cfg.GetCVar(CCVars.TraitorPlayersPerTraitor); - private int MaxTraitors => _cfg.GetCVar(CCVars.TraitorMaxTraitors); + public const int MaxPicks = 20; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnStartAttempt); - SubscribeLocalEvent(OnPlayersSpawned); - SubscribeLocalEvent(HandleLatejoin); + SubscribeLocalEvent(AfterEntitySelected); SubscribeLocalEvent(OnObjectivesTextGetInfo); SubscribeLocalEvent(OnObjectivesTextPrepend); } - //Set min players on game rule protected override void Added(EntityUid uid, TraitorRuleComponent component, GameRuleComponent gameRule, GameRuleAddedEvent args) { base.Added(uid, component, gameRule, args); - - gameRule.MinPlayers = _cfg.GetCVar(CCVars.TraitorMinPlayers); - } - - protected override void Started(EntityUid uid, TraitorRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args) - { - base.Started(uid, component, gameRule, args); MakeCodewords(component); } - protected override void ActiveTick(EntityUid uid, TraitorRuleComponent component, GameRuleComponent gameRule, float frameTime) - { - base.ActiveTick(uid, component, gameRule, frameTime); - - if (component.SelectionStatus < TraitorRuleComponent.SelectionState.Started && component.AnnounceAt < _timing.CurTime) - { - DoTraitorStart(component); - component.SelectionStatus = TraitorRuleComponent.SelectionState.Started; - } - } - - /// - /// Check for enough players - /// - /// - private void OnStartAttempt(RoundStartAttemptEvent ev) + private void AfterEntitySelected(Entity ent, ref AfterAntagEntitySelectedEvent args) { - TryRoundStartAttempt(ev, Loc.GetString("traitor-title")); + MakeTraitor(args.EntityUid, ent); } private void MakeCodewords(TraitorRuleComponent component) { - var codewordCount = _cfg.GetCVar(CCVars.TraitorCodewordCount); - var adjectives = _prototypeManager.Index(component.CodewordAdjectives).Values; - var verbs = _prototypeManager.Index(component.CodewordVerbs).Values; + var adjectives = _prototypeManager.Index(component.CodewordAdjectives).Values; + var verbs = _prototypeManager.Index(component.CodewordVerbs).Values; var codewordPool = adjectives.Concat(verbs).ToList(); - var finalCodewordCount = Math.Min(codewordCount, codewordPool.Count); + var finalCodewordCount = Math.Min(component.CodewordCount, codewordPool.Count); component.Codewords = new string[finalCodewordCount]; for (var i = 0; i < finalCodewordCount; i++) { @@ -103,66 +69,26 @@ private void MakeCodewords(TraitorRuleComponent component) } } - private void DoTraitorStart(TraitorRuleComponent component) - { - var eligiblePlayers = _antagSelection.GetEligiblePlayers(_playerManager.Sessions, component.TraitorPrototypeId); - - if (eligiblePlayers.Count == 0) - return; - - var traitorsToSelect = _antagSelection.CalculateAntagCount(_playerManager.PlayerCount, PlayersPerTraitor, MaxTraitors); - - var selectedTraitors = _antagSelection.ChooseAntags(traitorsToSelect, eligiblePlayers); - - MakeTraitor(selectedTraitors, component); - } - - private void OnPlayersSpawned(RulePlayerJobsAssignedEvent ev) - { - //Start the timer - var query = QueryActiveRules(); - while (query.MoveNext(out _, out var comp, out var gameRuleComponent)) - { - var delay = TimeSpan.FromSeconds( - _cfg.GetCVar(CCVars.TraitorStartDelay) + - _random.NextFloat(0f, _cfg.GetCVar(CCVars.TraitorStartDelayVariance))); - - //Set the delay for choosing traitors - comp.AnnounceAt = _timing.CurTime + delay; - - comp.SelectionStatus = TraitorRuleComponent.SelectionState.ReadyToStart; - } - } - - public bool MakeTraitor(List traitors, TraitorRuleComponent component, bool giveUplink = true, bool giveObjectives = true) - { - foreach (var traitor in traitors) - { - MakeTraitor(traitor, component, giveUplink, giveObjectives); - } - - return true; - } - public bool MakeTraitor(EntityUid traitor, TraitorRuleComponent component, bool giveUplink = true, bool giveObjectives = true) { //Grab the mind if it wasnt provided if (!_mindSystem.TryGetMind(traitor, out var mindId, out var mind)) return false; - if (HasComp(mindId)) + var briefing = Loc.GetString("traitor-role-codewords-short", ("codewords", string.Join(", ", component.Codewords))); + var issuer = _random.Pick(_prototypeManager.Index(component.ObjectiveIssuers).Values); + + if (TryComp(traitor, out var autoTraitorComponent)) { - Log.Error($"Player {mind.CharacterName} is already a traitor."); - return false; + giveUplink = autoTraitorComponent.GiveUplink; + giveObjectives = autoTraitorComponent.GiveObjectives; } - var briefing = Loc.GetString("traitor-role-codewords-short", ("codewords", string.Join(", ", component.Codewords))); - Note[]? code = null; if (giveUplink) { // Calculate the amount of currency on the uplink. - var startingBalance = _cfg.GetCVar(CCVars.TraitorStartingBalance); + var startingBalance = component.StartingBalance; if (_jobs.MindTryGetJob(mindId, out _, out var prototype)) startingBalance = Math.Max(startingBalance - prototype.AntagAdvantage, 0); @@ -180,33 +106,27 @@ public bool MakeTraitor(EntityUid traitor, TraitorRuleComponent component, bool Loc.GetString("traitor-role-uplink-code-short", ("code", string.Join("-", code).Replace("sharp", "#")))); } - _antagSelection.SendBriefing(traitor, GenerateBriefing(component.Codewords, code), null, component.GreetSoundNotification); + _antag.SendBriefing(traitor, GenerateBriefing(component.Codewords, code, issuer), null, component.GreetSoundNotification); component.TraitorMinds.Add(mindId); - // Assign traitor roles - _roleSystem.MindAddRole(mindId, new TraitorRoleComponent - { - PrototypeId = component.TraitorPrototypeId - }, mind, true); // Assign briefing _roleSystem.MindAddRole(mindId, new RoleBriefingComponent { - Briefing = briefing.ToString() + Briefing = briefing }, mind, true); - // Change the faction - _npcFaction.RemoveFaction(traitor, component.NanoTrasenFaction, false); - _npcFaction.AddFaction(traitor, component.SyndicateFaction); + // Don't Change the faction, this was stupid. + //_npcFaction.RemoveFaction(traitor, component.NanoTrasenFaction, false); + //_npcFaction.AddFaction(traitor, component.SyndicateFaction); + + RaiseLocalEvent(traitor, new MoodEffectEvent("TraitorFocused")); // Give traitors their objectives if (giveObjectives) { - var maxDifficulty = _cfg.GetCVar(CCVars.TraitorMaxDifficulty); - var maxPicks = _cfg.GetCVar(CCVars.TraitorMaxPicks); var difficulty = 0f; - Log.Debug($"Attempting {maxPicks} objective picks with {maxDifficulty} difficulty"); - for (var pick = 0; pick < maxPicks && maxDifficulty > difficulty; pick++) + for (var pick = 0; pick < MaxPicks && component.MaxDifficulty > difficulty; pick++) { var objective = _objectives.GetRandomObjective(mindId, mind, component.ObjectiveGroup); if (objective == null) @@ -222,53 +142,9 @@ public bool MakeTraitor(EntityUid traitor, TraitorRuleComponent component, bool return true; } - private void HandleLatejoin(PlayerSpawnCompleteEvent ev) - { - var query = QueryActiveRules(); - while (query.MoveNext(out _, out var comp, out _)) - { - if (comp.TotalTraitors >= MaxTraitors) - continue; - - if (!ev.LateJoin) - continue; - - if (!_antagSelection.IsPlayerEligible(ev.Player, comp.TraitorPrototypeId)) - continue; - - //If its before we have selected traitors, continue - if (comp.SelectionStatus < TraitorRuleComponent.SelectionState.Started) - continue; - - // the nth player we adjust our probabilities around - var target = PlayersPerTraitor * comp.TotalTraitors + 1; - var chance = 1f / PlayersPerTraitor; - - // If we have too many traitors, divide by how many players below target for next traitor we are. - if (ev.JoinOrder < target) - { - chance /= (target - ev.JoinOrder); - } - else // Tick up towards 100% chance. - { - chance *= ((ev.JoinOrder + 1) - target); - } - - if (chance > 1) - chance = 1; - - // Now that we've calculated our chance, roll and make them a traitor if we roll under. - // You get one shot. - if (_random.Prob(chance)) - { - MakeTraitor(ev.Mob, comp); - } - } - } - private void OnObjectivesTextGetInfo(EntityUid uid, TraitorRuleComponent comp, ref ObjectivesTextGetInfoEvent args) { - args.Minds = comp.TraitorMinds; + args.Minds = _antag.GetAntagMindEntityUids(uid); args.AgentName = Loc.GetString("traitor-round-end-agent-name"); } @@ -277,31 +153,10 @@ private void OnObjectivesTextPrepend(EntityUid uid, TraitorRuleComponent comp, r args.Text += "\n" + Loc.GetString("traitor-round-end-codewords", ("codewords", string.Join(", ", comp.Codewords))); } - /// - /// Start this game rule manually - /// - public TraitorRuleComponent StartGameRule() - { - var comp = EntityQuery().FirstOrDefault(); - if (comp == null) - { - GameTicker.StartGameRule("Traitor", out var ruleEntity); - comp = Comp(ruleEntity); - } - - return comp; - } - - public void MakeTraitorAdmin(EntityUid entity, bool giveUplink, bool giveObjectives) - { - var traitorRule = StartGameRule(); - MakeTraitor(entity, traitorRule, giveUplink, giveObjectives); - } - - private string GenerateBriefing(string[] codewords, Note[]? uplinkCode) + private string GenerateBriefing(string[] codewords, Note[]? uplinkCode, string? objectiveIssuer = null) { var sb = new StringBuilder(); - sb.AppendLine(Loc.GetString("traitor-role-greeting")); + sb.AppendLine(Loc.GetString("traitor-role-greeting", ("corporation", objectiveIssuer ?? Loc.GetString("objective-issuer-unknown")))); sb.AppendLine(Loc.GetString("traitor-role-codewords-short", ("codewords", string.Join(", ", codewords)))); if (uplinkCode != null) sb.AppendLine(Loc.GetString("traitor-role-uplink-code-short", ("code", string.Join("-", uplinkCode).Replace("sharp", "#")))); @@ -312,9 +167,11 @@ private string GenerateBriefing(string[] codewords, Note[]? uplinkCode) public List<(EntityUid Id, MindComponent Mind)> GetOtherTraitorMindsAliveAndConnected(MindComponent ourMind) { List<(EntityUid Id, MindComponent Mind)> allTraitors = new(); - foreach (var traitor in EntityQuery()) + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var traitor)) { - foreach (var role in GetOtherTraitorMindsAliveAndConnected(ourMind, traitor)) + foreach (var role in GetOtherTraitorMindsAliveAndConnected(ourMind, (uid, traitor))) { if (!allTraitors.Contains(role)) allTraitors.Add(role); @@ -324,20 +181,15 @@ private string GenerateBriefing(string[] codewords, Note[]? uplinkCode) return allTraitors; } - private List<(EntityUid Id, MindComponent Mind)> GetOtherTraitorMindsAliveAndConnected(MindComponent ourMind, TraitorRuleComponent component) + private List<(EntityUid Id, MindComponent Mind)> GetOtherTraitorMindsAliveAndConnected(MindComponent ourMind, Entity rule) { var traitors = new List<(EntityUid Id, MindComponent Mind)>(); - foreach (var traitor in component.TraitorMinds) + foreach (var mind in _antag.GetAntagMinds(rule.Owner)) { - if (TryComp(traitor, out MindComponent? mind) && - mind.OwnedEntity != null && - mind.Session != null && - mind != ourMind && - _mobStateSystem.IsAlive(mind.OwnedEntity.Value) && - mind.CurrentEntity == mind.OwnedEntity) - { - traitors.Add((traitor, mind)); - } + if (mind.Comp == ourMind) + continue; + + traitors.Add((mind, mind)); } return traitors; diff --git a/Content.Server/GameTicking/Rules/ZombieRuleSystem.cs b/Content.Server/GameTicking/Rules/ZombieRuleSystem.cs index 5714337d4d..1361ab3733 100644 --- a/Content.Server/GameTicking/Rules/ZombieRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/ZombieRuleSystem.cs @@ -1,46 +1,35 @@ -using Content.Server.Actions; using Content.Server.Antag; using Content.Server.Chat.Systems; using Content.Server.GameTicking.Rules.Components; using Content.Server.Popups; -using Content.Server.Roles; using Content.Server.RoundEnd; using Content.Server.Station.Components; using Content.Server.Station.Systems; using Content.Server.Zombies; -using Content.Shared.CCVar; using Content.Shared.Humanoid; using Content.Shared.Mind; using Content.Shared.Mobs; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; -using Content.Shared.Roles; using Content.Shared.Zombies; -using Robust.Server.Player; -using Robust.Shared.Configuration; using Robust.Shared.Player; -using Robust.Shared.Random; using Robust.Shared.Timing; using System.Globalization; using Content.Server.Announcements.Systems; +using Content.Server.GameTicking.Components; namespace Content.Server.GameTicking.Rules; public sealed class ZombieRuleSystem : GameRuleSystem { - [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly IConfigurationManager _cfg = default!; - [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly ChatSystem _chat = default!; [Dependency] private readonly RoundEndSystem _roundEnd = default!; [Dependency] private readonly PopupSystem _popup = default!; - [Dependency] private readonly ActionsSystem _action = default!; [Dependency] private readonly MobStateSystem _mobState = default!; [Dependency] private readonly ZombieSystem _zombie = default!; [Dependency] private readonly SharedMindSystem _mindSystem = default!; - [Dependency] private readonly SharedRoleSystem _roles = default!; [Dependency] private readonly StationSystem _station = default!; - [Dependency] private readonly AntagSelectionSystem _antagSelection = default!; + [Dependency] private readonly AntagSelectionSystem _antag = default!; [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly AnnouncerSystem _announcer = default!; [Dependency] private readonly GameTicker _gameTicker = default!; @@ -49,67 +38,56 @@ public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnStartAttempt); - SubscribeLocalEvent(OnRoundEndText); - SubscribeLocalEvent(OnZombifySelf); + SubscribeLocalEvent(OnZombifySelf); } - /// - /// Set the required minimum players for this gamemode to start - /// - protected override void Added(EntityUid uid, ZombieRuleComponent component, GameRuleComponent gameRule, GameRuleAddedEvent args) + protected override void AppendRoundEndText(EntityUid uid, ZombieRuleComponent component, GameRuleComponent gameRule, + ref RoundEndTextAppendEvent args) { - base.Added(uid, component, gameRule, args); - - gameRule.MinPlayers = _cfg.GetCVar(CCVars.ZombieMinPlayers); - } - - private void OnRoundEndText(RoundEndTextAppendEvent ev) - { - foreach (var zombie in EntityQuery()) + base.AppendRoundEndText(uid, component, gameRule, ref args); + + // This is just the general condition thing used for determining the win/lose text + var fraction = GetInfectedFraction(true, true); + + if (fraction <= 0) + args.AddLine(Loc.GetString("zombie-round-end-amount-none")); + else if (fraction <= 0.25) + args.AddLine(Loc.GetString("zombie-round-end-amount-low")); + else if (fraction <= 0.5) + args.AddLine(Loc.GetString("zombie-round-end-amount-medium", ("percent", Math.Round((fraction * 100), 2).ToString(CultureInfo.InvariantCulture)))); + else if (fraction < 1) + args.AddLine(Loc.GetString("zombie-round-end-amount-high", ("percent", Math.Round((fraction * 100), 2).ToString(CultureInfo.InvariantCulture)))); + else + args.AddLine(Loc.GetString("zombie-round-end-amount-all")); + + var antags = _antag.GetAntagIdentifiers(uid); + args.AddLine(Loc.GetString("zombie-round-end-initial-count", ("initialCount", antags.Count))); + foreach (var (_, data, entName) in antags) { - // This is just the general condition thing used for determining the win/lose text - var fraction = GetInfectedFraction(true, true); - - if (fraction <= 0) - ev.AddLine(Loc.GetString("zombie-round-end-amount-none")); - else if (fraction <= 0.25) - ev.AddLine(Loc.GetString("zombie-round-end-amount-low")); - else if (fraction <= 0.5) - ev.AddLine(Loc.GetString("zombie-round-end-amount-medium", ("percent", Math.Round((fraction * 100), 2).ToString(CultureInfo.InvariantCulture)))); - else if (fraction < 1) - ev.AddLine(Loc.GetString("zombie-round-end-amount-high", ("percent", Math.Round((fraction * 100), 2).ToString(CultureInfo.InvariantCulture)))); - else - ev.AddLine(Loc.GetString("zombie-round-end-amount-all")); + args.AddLine(Loc.GetString("zombie-round-end-user-was-initial", + ("name", entName), + ("username", data.UserName))); + } - ev.AddLine(Loc.GetString("zombie-round-end-initial-count", ("initialCount", zombie.InitialInfectedNames.Count))); - foreach (var player in zombie.InitialInfectedNames) + var healthy = GetHealthyHumans(); + // Gets a bunch of the living players and displays them if they're under a threshold. + // InitialInfected is used for the threshold because it scales with the player count well. + if (healthy.Count <= 0 || healthy.Count > 2 * antags.Count) + return; + args.AddLine(""); + args.AddLine(Loc.GetString("zombie-round-end-survivor-count", ("count", healthy.Count))); + foreach (var survivor in healthy) + { + var meta = MetaData(survivor); + var username = string.Empty; + if (_mindSystem.TryGetMind(survivor, out _, out var mind) && mind.Session != null) { - ev.AddLine(Loc.GetString("zombie-round-end-user-was-initial", - ("name", player.Key), - ("username", player.Value))); + username = mind.Session.Name; } - var healthy = GetHealthyHumans(true); - // Gets a bunch of the living players and displays them if they're under a threshold. - // InitialInfected is used for the threshold because it scales with the player count well. - if (healthy.Count <= 0 || healthy.Count > 2 * zombie.InitialInfectedNames.Count) - continue; - ev.AddLine(""); - ev.AddLine(Loc.GetString("zombie-round-end-survivor-count", ("count", healthy.Count))); - foreach (var survivor in healthy) - { - var meta = MetaData(survivor); - var username = string.Empty; - if (_mindSystem.TryGetMind(survivor, out _, out var mind) && mind.Session != null) - { - username = mind.Session.Name; - } - - ev.AddLine(Loc.GetString("zombie-round-end-user-was-survivor", - ("name", meta.EntityName), - ("username", username))); - } + args.AddLine(Loc.GetString("zombie-round-end-user-was-survivor", + ("name", meta.EntityName), + ("username", username))); } } @@ -139,41 +117,23 @@ private void CheckRoundEnd(ZombieRuleComponent zombieRuleComponent) _roundEnd.EndRound(); } - /// - /// Check we have enough players to start this game mode, if not - cancel and announce - /// - private void OnStartAttempt(RoundStartAttemptEvent ev) - { - TryRoundStartAttempt(ev, Loc.GetString("zombie-title")); - } - protected override void Started(EntityUid uid, ZombieRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args) { base.Started(uid, component, gameRule, args); - var delay = _random.Next(component.MinStartDelay, component.MaxStartDelay); - component.StartTime = _timing.CurTime + delay; + component.NextRoundEndCheck = _timing.CurTime + component.EndCheckDelay; } protected override void ActiveTick(EntityUid uid, ZombieRuleComponent component, GameRuleComponent gameRule, float frameTime) { base.ActiveTick(uid, component, gameRule, frameTime); - - if (component.StartTime.HasValue && component.StartTime < _timing.CurTime) - { - InfectInitialPlayers(component); - component.StartTime = null; - component.NextRoundEndCheck = _timing.CurTime + component.EndCheckDelay; - } - - if (component.NextRoundEndCheck.HasValue && component.NextRoundEndCheck < _timing.CurTime) - { - CheckRoundEnd(component); - component.NextRoundEndCheck = _timing.CurTime + component.EndCheckDelay; - } + if (!component.NextRoundEndCheck.HasValue || component.NextRoundEndCheck > _timing.CurTime) + return; + CheckRoundEnd(component); + component.NextRoundEndCheck = _timing.CurTime + component.EndCheckDelay; } - private void OnZombifySelf(EntityUid uid, PendingZombieComponent component, ZombifySelfActionEvent args) + private void OnZombifySelf(EntityUid uid, IncurableZombieComponent component, ZombifySelfActionEvent args) { _zombie.ZombifyEntity(uid); if (component.Action != null) @@ -235,81 +195,4 @@ private List GetHealthyHumans(bool includeOffStation = false) } return healthy; } - - /// - /// Infects the first players with the passive zombie virus. - /// Also records their names for the end of round screen. - /// - /// - /// The reason this code is written separately is to facilitate - /// allowing this gamemode to be started midround. As such, it doesn't need - /// any information besides just running. - /// - private void InfectInitialPlayers(ZombieRuleComponent component) - { - //Get all players with initial infected enabled, and exclude those with the ZombieImmuneComponent and roles with CanBeAntag = False - var eligiblePlayers = _antagSelection.GetEligiblePlayers( - _playerManager.Sessions, - component.PatientZeroPrototypeId, - includeAllJobs: false, - customExcludeCondition: player => HasComp(player) || HasComp(player) - ); - - //And get all players, excluding ZombieImmune and roles with CanBeAntag = False - to fill any leftover initial infected slots - var allPlayers = _antagSelection.GetEligiblePlayers( - _playerManager.Sessions, - component.PatientZeroPrototypeId, - acceptableAntags: Shared.Antag.AntagAcceptability.All, - includeAllJobs: false , - ignorePreferences: true, - customExcludeCondition: HasComp - ); - - //If there are no players to choose, abort - if (allPlayers.Count == 0) - return; - - //How many initial infected should we select - var initialInfectedCount = _antagSelection.CalculateAntagCount(_playerManager.PlayerCount, component.PlayersPerInfected, component.MaxInitialInfected); - - //Choose the required number of initial infected from the eligible players, making up any shortfall by choosing from all players - var initialInfected = _antagSelection.ChooseAntags(initialInfectedCount, eligiblePlayers, allPlayers); - - //Make brain craving - MakeZombie(initialInfected, component); - - //Send the briefing, play greeting sound - _antagSelection.SendBriefing(initialInfected, Loc.GetString("zombie-patientzero-role-greeting"), Color.Plum, component.InitialInfectedSound); - } - - private void MakeZombie(List entities, ZombieRuleComponent component) - { - foreach (var entity in entities) - { - MakeZombie(entity, component); - } - } - private void MakeZombie(EntityUid entity, ZombieRuleComponent component) - { - if (!_mindSystem.TryGetMind(entity, out var mind, out var mindComponent)) - return; - - //Add the role to the mind silently (to avoid repeating job assignment) - _roles.MindAddRole(mind, new InitialInfectedRoleComponent { PrototypeId = component.PatientZeroPrototypeId }, silent: true); - EnsureComp(entity); - - //Add the zombie components and grace period - var pending = EnsureComp(entity); - pending.GracePeriod = _random.Next(component.MinInitialInfectedGrace, component.MaxInitialInfectedGrace); - EnsureComp(entity); - EnsureComp(entity); - - //Add the zombify action - _action.AddAction(entity, ref pending.Action, component.ZombifySelfActionPrototype, entity); - - //Get names for the round end screen, incase they leave mid-round - var inCharacterName = MetaData(entity).EntityName; - var accountName = mindComponent.Session == null ? string.Empty : mindComponent.Session.Name; - component.InitialInfectedNames.Add(inCharacterName, accountName); - } } diff --git a/Content.Server/Gateway/Components/GatewayGeneratorComponent.cs b/Content.Server/Gateway/Components/GatewayGeneratorComponent.cs index d65760e270..0f948f1b8f 100644 --- a/Content.Server/Gateway/Components/GatewayGeneratorComponent.cs +++ b/Content.Server/Gateway/Components/GatewayGeneratorComponent.cs @@ -64,6 +64,8 @@ public sealed partial class GatewayGeneratorComponent : Component "OreUranium", "OreBananium", "OreArtifactFragment", + "OreBluespace", + "OreNormality", }; } diff --git a/Content.Server/Gateway/Systems/GatewaySystem.cs b/Content.Server/Gateway/Systems/GatewaySystem.cs index 7ebc751dd2..6ed28d71a7 100644 --- a/Content.Server/Gateway/Systems/GatewaySystem.cs +++ b/Content.Server/Gateway/Systems/GatewaySystem.cs @@ -129,7 +129,7 @@ private void UpdateUserInterface(EntityUid uid, GatewayComponent comp, Transform unlockTime ); - _ui.TrySetUiState(uid, GatewayUiKey.Key, state); + _ui.SetUiState(uid, GatewayUiKey.Key, state); } private void UpdateAppearance(EntityUid uid) @@ -139,12 +139,14 @@ private void UpdateAppearance(EntityUid uid) private void OnOpenPortal(EntityUid uid, GatewayComponent comp, GatewayOpenPortalMessage args) { - if (args.Session.AttachedEntity == null || GetNetEntity(uid) == args.Destination || + if (GetNetEntity(uid) == args.Destination || !comp.Enabled || !comp.Interactable) + { return; + } // if the gateway has an access reader check it before allowing opening - var user = args.Session.AttachedEntity.Value; + var user = args.Actor; if (CheckAccess(user, uid, comp)) return; diff --git a/Content.Server/Geras/GerasComponent.cs b/Content.Server/Geras/GerasComponent.cs new file mode 100644 index 0000000000..eaf792502f --- /dev/null +++ b/Content.Server/Geras/GerasComponent.cs @@ -0,0 +1,18 @@ +using Content.Shared.Actions; +using Content.Shared.Polymorph; +using Robust.Shared.Prototypes; + +namespace Content.Server.Geras; + +/// +/// This component assigns the entity with a polymorph action. +/// +[RegisterComponent] +public sealed partial class GerasComponent : Component +{ + [DataField] public ProtoId GerasPolymorphId = "SlimeMorphGeras"; + + [DataField] public ProtoId GerasAction = "ActionMorphGeras"; + + [DataField] public EntityUid? GerasActionEntity; +} diff --git a/Content.Server/Geras/GerasSystem.cs b/Content.Server/Geras/GerasSystem.cs new file mode 100644 index 0000000000..f83f7c1999 --- /dev/null +++ b/Content.Server/Geras/GerasSystem.cs @@ -0,0 +1,51 @@ +using Content.Server.Polymorph.Systems; +using Content.Shared.Zombies; +using Content.Server.Actions; +using Content.Server.Popups; +using Content.Shared.Geras; +using Robust.Shared.Player; + +namespace Content.Server.Geras; + +/// +public sealed class GerasSystem : SharedGerasSystem +{ + [Dependency] private readonly PolymorphSystem _polymorphSystem = default!; + [Dependency] private readonly ActionsSystem _actionsSystem = default!; + [Dependency] private readonly PopupSystem _popupSystem = default!; + + /// + public override void Initialize() + { + SubscribeLocalEvent(OnMorphIntoGeras); + SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnZombification); + } + + private void OnZombification(EntityUid uid, GerasComponent component, EntityZombifiedEvent args) + { + _actionsSystem.RemoveAction(uid, component.GerasActionEntity); + } + + private void OnMapInit(EntityUid uid, GerasComponent component, MapInitEvent args) + { + // try to add geras action + _actionsSystem.AddAction(uid, ref component.GerasActionEntity, component.GerasAction); + } + + private void OnMorphIntoGeras(EntityUid uid, GerasComponent component, MorphIntoGeras args) + { + if (HasComp(uid)) + return; // i hate zomber. + + var ent = _polymorphSystem.PolymorphEntity(uid, component.GerasPolymorphId); + + if (!ent.HasValue) + return; + + _popupSystem.PopupEntity(Loc.GetString("geras-popup-morph-message-others", ("entity", ent.Value)), ent.Value, Filter.PvsExcept(ent.Value), true); + _popupSystem.PopupEntity(Loc.GetString("geras-popup-morph-message-user"), ent.Value, ent.Value); + + args.Handled = true; + } +} diff --git a/Content.Server/Ghost/GhostReturnToRoundSystem.cs b/Content.Server/Ghost/GhostReturnToRoundSystem.cs new file mode 100644 index 0000000000..f6ed44ca58 --- /dev/null +++ b/Content.Server/Ghost/GhostReturnToRoundSystem.cs @@ -0,0 +1,80 @@ +using Content.Server.Administration.Logs; +using Content.Server.Chat.Managers; +using Content.Server.GameTicking; +using Content.Shared.Database; +using Content.Shared.CCVar; +using Content.Shared.Ghost; +using Robust.Server.Player; +using Robust.Shared.Configuration; +using Robust.Shared.Network; +using Robust.Shared.Timing; + +namespace Content.Server.Ghost; + +public sealed class GhostReturnToRoundSystem : EntitySystem +{ + [Dependency] private readonly IChatManager _chatManager = default!; + [Dependency] private readonly IAdminLogManager _adminLogger = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly GameTicker _ticker = default!; + + public override void Initialize() + { + SubscribeNetworkEvent(OnGhostReturnToRoundRequest); + } + + private void OnGhostReturnToRoundRequest(GhostReturnToRoundRequest msg, EntitySessionEventArgs args) + { + var uid = args.SenderSession.AttachedEntity; + + if (uid == null) + return; + + var connectedClient = args.SenderSession.Channel; + var userId = args.SenderSession.UserId; + + TryGhostReturnToRound(uid.Value, connectedClient, userId, out var message, out var wrappedMessage); + + _chatManager.ChatMessageToOne(Shared.Chat.ChatChannel.Server, + message, + wrappedMessage, + default, + false, + connectedClient, + Color.Red); + } + + private void TryGhostReturnToRound(EntityUid uid, INetChannel connectedClient, NetUserId userId, out string message, out string wrappedMessage) + { + var maxPlayers = _cfg.GetCVar(CCVars.GhostRespawnMaxPlayers); + if (_playerManager.PlayerCount >= maxPlayers) + { + message = Loc.GetString("ghost-respawn-max-players", ("players", maxPlayers)); + wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", message)); + return; + } + + var deathTime = EnsureComp(uid).TimeOfDeath; + var timeUntilRespawn = _cfg.GetCVar(CCVars.GhostRespawnTime); + var timePast = (_gameTiming.CurTime - deathTime).TotalMinutes; + if (timePast >= timeUntilRespawn) + { + _playerManager.TryGetSessionById(userId, out var targetPlayer); + + if (targetPlayer != null) + _ticker.Respawn(targetPlayer); + + _adminLogger.Add(LogType.Mind, LogImpact.Medium, $"{Loc.GetString("ghost-respawn-log-return-to-lobby", ("userName", connectedClient.UserName))}"); + + message = Loc.GetString("ghost-respawn-window-rules-footer"); + wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", message)); + + return; + } + + message = Loc.GetString("ghost-respawn-time-left", ("time", (int) (timeUntilRespawn - timePast))); + wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", message)); + } +} diff --git a/Content.Server/Ghost/GhostSystem.cs b/Content.Server/Ghost/GhostSystem.cs index c0e753c4c5..254d478bb0 100644 --- a/Content.Server/Ghost/GhostSystem.cs +++ b/Content.Server/Ghost/GhostSystem.cs @@ -19,6 +19,7 @@ using Content.Shared.Storage.Components; using Robust.Server.GameObjects; using Robust.Server.Player; +using Robust.Shared.Map; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Systems; using Robust.Shared.Player; @@ -42,11 +43,18 @@ public sealed class GhostSystem : SharedGhostSystem [Dependency] private readonly GameTicker _ticker = default!; [Dependency] private readonly TransformSystem _transformSystem = default!; [Dependency] private readonly VisibilitySystem _visibilitySystem = default!; + [Dependency] private readonly MetaDataSystem _metaData = default!; + + private EntityQuery _ghostQuery; + private EntityQuery _physicsQuery; public override void Initialize() { base.Initialize(); + _ghostQuery = GetEntityQuery(); + _physicsQuery = GetEntityQuery(); + SubscribeLocalEvent(OnGhostStartup); SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(OnGhostShutdown); @@ -62,12 +70,14 @@ public override void Initialize() SubscribeNetworkEvent(OnGhostWarpsRequest); SubscribeNetworkEvent(OnGhostReturnToBodyRequest); SubscribeNetworkEvent(OnGhostWarpToTargetRequest); + SubscribeNetworkEvent(OnGhostnadoRequest); SubscribeLocalEvent(OnActionPerform); SubscribeLocalEvent(OnGhostHearingAction); SubscribeLocalEvent(OnEntityStorageInsertAttempt); SubscribeLocalEvent(_ => MakeVisible(true)); + SubscribeLocalEvent(OnToggleGhostVisibilityToAll); } private void OnGhostHearingAction(EntityUid uid, GhostComponent component, ToggleGhostHearingActionEvent args) @@ -144,8 +154,8 @@ private void OnGhostStartup(EntityUid uid, GhostComponent component, ComponentSt if (_ticker.RunLevel != GameRunLevel.PostRound) { - _visibilitySystem.AddLayer(uid, visibility, (int) VisibilityFlags.Ghost, false); - _visibilitySystem.RemoveLayer(uid, visibility, (int) VisibilityFlags.Normal, false); + _visibilitySystem.AddLayer((uid, visibility), (int) VisibilityFlags.Ghost, false); + _visibilitySystem.RemoveLayer((uid, visibility), (int) VisibilityFlags.Normal, false); _visibilitySystem.RefreshVisibility(uid, visibilityComponent: visibility); } @@ -164,8 +174,8 @@ private void OnGhostShutdown(EntityUid uid, GhostComponent component, ComponentS // Entity can't be seen by ghosts anymore. if (TryComp(uid, out VisibilityComponent? visibility)) { - _visibilitySystem.RemoveLayer(uid, visibility, (int) VisibilityFlags.Ghost, false); - _visibilitySystem.AddLayer(uid, visibility, (int) VisibilityFlags.Normal, false); + _visibilitySystem.RemoveLayer((uid, visibility), (int) VisibilityFlags.Ghost, false); + _visibilitySystem.AddLayer((uid, visibility), (int) VisibilityFlags.Normal, false); _visibilitySystem.RefreshVisibility(uid, visibilityComponent: visibility); } @@ -241,7 +251,7 @@ private void DeleteEntity(EntityUid uid) private void OnGhostReturnToBodyRequest(GhostReturnToBodyRequest msg, EntitySessionEventArgs args) { if (args.SenderSession.AttachedEntity is not {Valid: true} attached - || !TryComp(attached, out GhostComponent? ghost) + || !_ghostQuery.TryComp(attached, out var ghost) || !ghost.CanReturnToBody || !TryComp(attached, out ActorComponent? actor)) { @@ -257,7 +267,7 @@ private void OnGhostReturnToBodyRequest(GhostReturnToBodyRequest msg, EntitySess private void OnGhostWarpsRequest(GhostWarpsRequestEvent msg, EntitySessionEventArgs args) { if (args.SenderSession.AttachedEntity is not {Valid: true} entity - || !HasComp(entity)) + || !_ghostQuery.HasComp(entity)) { Log.Warning($"User {args.SenderSession.Name} sent a {nameof(GhostWarpsRequestEvent)} without being a ghost."); return; @@ -270,7 +280,7 @@ private void OnGhostWarpsRequest(GhostWarpsRequestEvent msg, EntitySessionEventA private void OnGhostWarpToTargetRequest(GhostWarpToTargetRequestEvent msg, EntitySessionEventArgs args) { if (args.SenderSession.AttachedEntity is not {Valid: true} attached - || !TryComp(attached, out GhostComponent? _)) + || !_ghostQuery.HasComp(attached)) { Log.Warning($"User {args.SenderSession.Name} tried to warp to {msg.Target} without being a ghost."); return; @@ -284,17 +294,37 @@ private void OnGhostWarpToTargetRequest(GhostWarpToTargetRequestEvent msg, Entit return; } + WarpTo(attached, target); + } + + private void OnGhostnadoRequest(GhostnadoRequestEvent msg, EntitySessionEventArgs args) + { + if (args.SenderSession.AttachedEntity is not {} uid + || !_ghostQuery.HasComp(uid)) + { + Log.Warning($"User {args.SenderSession.Name} tried to ghostnado without being a ghost."); + return; + } + + if (_followerSystem.GetMostFollowed() is not {} target) + return; + + WarpTo(uid, target); + } + + private void WarpTo(EntityUid uid, EntityUid target) + { if ((TryComp(target, out WarpPointComponent? warp) && warp.Follow) || HasComp(target)) { - _followerSystem.StartFollowingEntity(attached, target); + _followerSystem.StartFollowingEntity(uid, target); return; } - var xform = Transform(attached); - _transformSystem.SetCoordinates(attached, xform, Transform(target).Coordinates); - _transformSystem.AttachToGridOrMap(attached, xform); - if (TryComp(attached, out PhysicsComponent? physics)) - _physics.SetLinearVelocity(attached, Vector2.Zero, body: physics); + var xform = Transform(uid); + _transformSystem.SetCoordinates(uid, xform, Transform(target).Coordinates); + _transformSystem.AttachToGridOrMap(uid, xform); + if (_physicsQuery.TryComp(uid, out var physics)) + _physics.SetLinearVelocity(uid, Vector2.Zero, body: physics); } private IEnumerable GetLocationWarps() @@ -333,6 +363,15 @@ private void OnEntityStorageInsertAttempt(EntityUid uid, GhostComponent comp, re args.Cancelled = true; } + private void OnToggleGhostVisibilityToAll(ToggleGhostVisibilityToAllEvent ev) + { + if (ev.Handled) + return; + + ev.Handled = true; + MakeVisible(true); + } + /// /// When the round ends, make all players able to see ghosts. /// @@ -343,13 +382,13 @@ public void MakeVisible(bool visible) { if (visible) { - _visibilitySystem.AddLayer(uid, vis, (int) VisibilityFlags.Normal, false); - _visibilitySystem.RemoveLayer(uid, vis, (int) VisibilityFlags.Ghost, false); + _visibilitySystem.AddLayer((uid, vis), (int) VisibilityFlags.Normal, false); + _visibilitySystem.RemoveLayer((uid, vis), (int) VisibilityFlags.Ghost, false); } else { - _visibilitySystem.AddLayer(uid, vis, (int) VisibilityFlags.Ghost, false); - _visibilitySystem.RemoveLayer(uid, vis, (int) VisibilityFlags.Normal, false); + _visibilitySystem.AddLayer((uid, vis), (int) VisibilityFlags.Ghost, false); + _visibilitySystem.RemoveLayer((uid, vis), (int) VisibilityFlags.Normal, false); } _visibilitySystem.RefreshVisibility(uid, visibilityComponent: vis); } @@ -362,5 +401,59 @@ public bool DoGhostBooEvent(EntityUid target) return ghostBoo.Handled; } + + public EntityUid? SpawnGhost(Entity mind, EntityUid targetEntity, + bool canReturn = false) + { + _transformSystem.TryGetMapOrGridCoordinates(targetEntity, out var spawnPosition); + return SpawnGhost(mind, spawnPosition, canReturn); + } + + public EntityUid? SpawnGhost(Entity mind, EntityCoordinates? spawnPosition = null, + bool canReturn = false) + { + if (!Resolve(mind, ref mind.Comp)) + return null; + + // Test if the map is being deleted + var mapUid = spawnPosition?.GetMapUid(EntityManager); + if (mapUid == null || TerminatingOrDeleted(mapUid.Value)) + spawnPosition = null; + + spawnPosition ??= _ticker.GetObserverSpawnPoint(); + + if (!spawnPosition.Value.IsValid(EntityManager)) + { + Log.Warning($"No spawn valid ghost spawn position found for {mind.Comp.CharacterName}" + + " \"{ToPrettyString(mind)}\""); + _minds.TransferTo(mind.Owner, null, createGhost: false, mind: mind.Comp); + return null; + } + + var ghost = SpawnAtPosition(GameTicker.ObserverPrototypeName, spawnPosition.Value); + var ghostComponent = Comp(ghost); + + // Try setting the ghost entity name to either the character name or the player name. + // If all else fails, it'll default to the default entity prototype name, "observer". + // However, that should rarely happen. + if (!string.IsNullOrWhiteSpace(mind.Comp.CharacterName)) + _metaData.SetEntityName(ghost, mind.Comp.CharacterName); + else if (!string.IsNullOrWhiteSpace(mind.Comp.Session?.Name)) + _metaData.SetEntityName(ghost, mind.Comp.Session.Name); + + if (mind.Comp.TimeOfDeath.HasValue) + { + SetTimeOfDeath(ghost, mind.Comp.TimeOfDeath!.Value, ghostComponent); + } + + SetCanReturnToBody(ghostComponent, canReturn); + + if (canReturn) + _minds.Visit(mind.Owner, ghost, mind.Comp); + else + _minds.TransferTo(mind.Owner, ghost, mind: mind.Comp); + Log.Debug($"Spawned ghost \"{ToPrettyString(ghost)}\" for {mind.Comp.CharacterName}."); + return ghost; + } } } diff --git a/Content.Server/Ghost/Roles/Components/GhostRoleComponent.cs b/Content.Server/Ghost/Roles/Components/GhostRoleComponent.cs index 997961ae67..35b8ad7556 100644 --- a/Content.Server/Ghost/Roles/Components/GhostRoleComponent.cs +++ b/Content.Server/Ghost/Roles/Components/GhostRoleComponent.cs @@ -1,4 +1,5 @@ -using Content.Server.Mind.Commands; +using Content.Server.Ghost.Roles.Raffles; +using Content.Server.Mind.Commands; using Content.Shared.Customization.Systems; using Content.Shared.Roles; @@ -88,5 +89,12 @@ public string RoleRules [ViewVariables(VVAccess.ReadWrite)] [DataField("reregister")] public bool ReregisterOnGhost { get; set; } = true; + + /// + /// If set, ghost role is raffled, otherwise it is first-come-first-serve. + /// + [DataField("raffle")] + [Access(typeof(GhostRoleSystem), Other = AccessPermissions.ReadWriteExecute)] // FIXME Friends + public GhostRoleRaffleConfig? RaffleConfig { get; set; } } } diff --git a/Content.Server/Ghost/Roles/Components/GhostRoleMobSpawnerComponent.cs b/Content.Server/Ghost/Roles/Components/GhostRoleMobSpawnerComponent.cs index 4cdab6ce07..6116173f90 100644 --- a/Content.Server/Ghost/Roles/Components/GhostRoleMobSpawnerComponent.cs +++ b/Content.Server/Ghost/Roles/Components/GhostRoleMobSpawnerComponent.cs @@ -1,26 +1,30 @@ using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; namespace Content.Server.Ghost.Roles.Components { /// /// Allows a ghost to take this role, spawning a new entity. /// - [RegisterComponent] + [RegisterComponent, EntityCategory("Spawner")] [Access(typeof(GhostRoleSystem))] public sealed partial class GhostRoleMobSpawnerComponent : Component { - [ViewVariables(VVAccess.ReadWrite)] [DataField("deleteOnSpawn")] + [DataField] public bool DeleteOnSpawn = true; - [ViewVariables(VVAccess.ReadWrite)] [DataField("availableTakeovers")] + [DataField] public int AvailableTakeovers = 1; [ViewVariables] public int CurrentTakeovers = 0; - [ViewVariables(VVAccess.ReadWrite)] - [DataField("prototype", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? Prototype { get; private set; } + [DataField] + public EntProtoId? Prototype; + + /// + /// If this ghostrole spawner has multiple selectable ghostrole prototypes. + /// + [DataField] + public List SelectablePrototypes = []; } } diff --git a/Content.Server/Ghost/Roles/Components/GhostRoleRaffleComponent.cs b/Content.Server/Ghost/Roles/Components/GhostRoleRaffleComponent.cs new file mode 100644 index 0000000000..ac518685e9 --- /dev/null +++ b/Content.Server/Ghost/Roles/Components/GhostRoleRaffleComponent.cs @@ -0,0 +1,58 @@ +using Content.Server.Ghost.Roles.Raffles; +using Robust.Shared.Player; + +namespace Content.Server.Ghost.Roles.Components; + +/// +/// Indicates that a ghost role is currently being raffled, and stores data about the raffle in progress. +/// Raffles start when the first player joins a raffle. +/// +[RegisterComponent] +[Access(typeof(GhostRoleSystem))] +public sealed partial class GhostRoleRaffleComponent : Component +{ + /// + /// Identifier of the Ghost Role this raffle is for. + /// + [ViewVariables(VVAccess.ReadOnly)] + [DataField] + public uint Identifier { get; set; } + + /// + /// List of sessions that are currently in the raffle. + /// + [ViewVariables(VVAccess.ReadOnly)] + public HashSet CurrentMembers = []; + + /// + /// List of sessions that are currently or were previously in the raffle. + /// + [ViewVariables(VVAccess.ReadOnly)] + public HashSet AllMembers = []; + + /// + /// Time left in the raffle in seconds. This must be initialized to a positive value. + /// + [ViewVariables(VVAccess.ReadOnly)] + [DataField] + public TimeSpan Countdown = TimeSpan.MaxValue; + + /// + /// The cumulative time, i.e. how much time the raffle will take in total. Added to when the time is extended + /// by someone joining the raffle. + /// Must be set to the same value as on initialization. + /// + [ViewVariables(VVAccess.ReadOnly)] + [DataField("cumulativeTime")] + public TimeSpan CumulativeTime = TimeSpan.MaxValue; + + /// + [ViewVariables(VVAccess.ReadOnly)] + [DataField("joinExtendsDurationBy")] + public TimeSpan JoinExtendsDurationBy { get; set; } + + /// + [ViewVariables(VVAccess.ReadOnly)] + [DataField("maxDuration")] + public TimeSpan MaxDuration { get; set; } +} diff --git a/Content.Server/Ghost/Roles/GhostRoleSystem.cs b/Content.Server/Ghost/Roles/GhostRoleSystem.cs index f603416d00..346cb64ea8 100644 --- a/Content.Server/Ghost/Roles/GhostRoleSystem.cs +++ b/Content.Server/Ghost/Roles/GhostRoleSystem.cs @@ -1,7 +1,10 @@ +using System.Linq; using Content.Server.Administration.Logs; using Content.Server.EUI; using Content.Server.Ghost.Roles.Components; using Content.Server.Ghost.Roles.Events; +using Content.Server.Ghost.Roles.Raffles; +using Content.Shared.Ghost.Roles.Raffles; using Content.Server.Ghost.Roles.UI; using Content.Server.Mind.Commands; using Content.Shared.Administration; @@ -21,8 +24,13 @@ using Robust.Shared.Console; using Robust.Shared.Enums; using Robust.Shared.Player; +using Robust.Shared.Prototypes; using Robust.Shared.Random; +using Robust.Shared.Timing; using Robust.Shared.Utility; +using Content.Server.Popups; +using Content.Shared.Verbs; +using Robust.Shared.Collections; namespace Content.Server.Ghost.Roles { @@ -37,10 +45,16 @@ namespace Content.Server.Ghost.Roles [Dependency] private readonly TransformSystem _transform = default!; [Dependency] private readonly SharedMindSystem _mindSystem = default!; [Dependency] private readonly SharedRoleSystem _roleSystem = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly PopupSystem _popupSystem = default!; + [Dependency] private readonly IPrototypeManager _prototype = default!; private uint _nextRoleIdentifier; private bool _needsUpdateGhostRoleCount = true; + private readonly Dictionary> _ghostRoles = new(); + private readonly Dictionary> _ghostRoleRaffles = new(); + private readonly Dictionary _openUis = new(); private readonly Dictionary _openMakeGhostRoleUis = new(); @@ -57,13 +71,16 @@ public override void Initialize() SubscribeLocalEvent(OnMindRemoved); SubscribeLocalEvent(OnMobStateChanged); SubscribeLocalEvent(OnMapInit); - SubscribeLocalEvent(OnStartup); - SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnRoleStartup); + SubscribeLocalEvent(OnRoleShutdown); SubscribeLocalEvent(OnPaused); SubscribeLocalEvent(OnUnpaused); + SubscribeLocalEvent(OnRaffleInit); + SubscribeLocalEvent(OnRaffleShutdown); SubscribeLocalEvent(OnSpawnerTakeRole); SubscribeLocalEvent(OnTakeoverTakeRole); - SubscribeLocalEvent(OnSpawnerTakeCharacter); // DeltaV - Character ghost roles, see Content.Server/DeltaV/Ghost/Roles/GhostRoleSystem.Character.cs + SubscribeLocalEvent>(OnVerb); + SubscribeLocalEvent(OnSpawnerTakeCharacter); _playerManager.PlayerStatusChanged += PlayerStatusChanged; } @@ -75,11 +92,11 @@ private void OnMobStateChanged(Entity component switch (args.NewMobState) { case MobState.Alive: - { - if (!ghostRole.Taken) - RegisterGhostRole((component, ghostRole)); - break; - } + { + if (!ghostRole.Taken) + RegisterGhostRole((component, ghostRole)); + break; + } case MobState.Critical: case MobState.Dead: UnregisterGhostRole((component, ghostRole)); @@ -101,11 +118,11 @@ private uint GetNextRoleIdentifier() public void OpenEui(ICommonSession session) { - if (session.AttachedEntity is not {Valid: true} attached || + if (session.AttachedEntity is not { Valid: true } attached || !EntityManager.HasComponent(attached)) return; - if(_openUis.ContainsKey(session)) + if (_openUis.ContainsKey(session)) CloseEui(session); var eui = _openUis[session] = new GhostRolesEui(); @@ -159,17 +176,118 @@ public void UpdateAllEui() public override void Update(float frameTime) { base.Update(frameTime); - if (_needsUpdateGhostRoleCount) + + UpdateGhostRoleCount(); + UpdateRaffles(frameTime); + } + + /// + /// Handles sending count update for the ghost role button in ghost UI, if ghost role count changed. + /// + private void UpdateGhostRoleCount() + { + if (!_needsUpdateGhostRoleCount) + return; + + _needsUpdateGhostRoleCount = false; + var response = new GhostUpdateGhostRoleCountEvent(GetGhostRoleCount()); + foreach (var player in _playerManager.Sessions) + { + RaiseNetworkEvent(response, player.Channel); + } + } + + /// + /// Handles ghost role raffle logic. + /// + private void UpdateRaffles(float frameTime) + { + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var entityUid, out var raffle, out var meta)) { - _needsUpdateGhostRoleCount = false; - var response = new GhostUpdateGhostRoleCountEvent(GetGhostRolesInfo().Length); - foreach (var player in _playerManager.Sessions) + if (meta.EntityPaused) + continue; + + // if all participants leave/were removed from the raffle, the raffle is canceled. + if (raffle.CurrentMembers.Count == 0) { - RaiseNetworkEvent(response, player.Channel); + RemoveRaffleAndUpdateEui(entityUid, raffle); + continue; } + + raffle.Countdown = raffle.Countdown.Subtract(TimeSpan.FromSeconds(frameTime)); + if (raffle.Countdown.Ticks > 0) + continue; + + // the raffle is over! find someone to take over the ghost role + if (!TryComp(entityUid, out GhostRoleComponent? ghostRole)) + { + Log.Warning($"Ghost role raffle finished on {entityUid} but {nameof(GhostRoleComponent)} is missing"); + RemoveRaffleAndUpdateEui(entityUid, raffle); + continue; + } + + if (ghostRole.RaffleConfig is null) + { + Log.Warning($"Ghost role raffle finished on {entityUid} but RaffleConfig became null"); + RemoveRaffleAndUpdateEui(entityUid, raffle); + continue; + } + + var foundWinner = false; + var deciderPrototype = _prototype.Index(ghostRole.RaffleConfig.Decider); + + // use the ghost role's chosen winner picker to find a winner + deciderPrototype.Decider.PickWinner( + raffle.CurrentMembers.AsEnumerable(), + session => + { + var success = TryTakeover(session, raffle.Identifier); + foundWinner |= success; + return success; + } + ); + + if (!foundWinner) + { + Log.Warning($"Ghost role raffle for {entityUid} ({ghostRole.RoleName}) finished without " + + $"{ghostRole.RaffleConfig?.Decider} finding a winner"); + } + + // raffle over + RemoveRaffleAndUpdateEui(entityUid, raffle); } } + private bool TryTakeover(ICommonSession player, uint identifier) + { + // TODO: the following two checks are kind of redundant since they should already be removed + // from the raffle + // can't win if you are disconnected (although you shouldn't be a candidate anyway) + if (player.Status != SessionStatus.InGame) + return false; + + // can't win if you are no longer a ghost (e.g. if you returned to your body) + if (player.AttachedEntity == null || !HasComp(player.AttachedEntity)) + return false; + + if (Takeover(player, identifier)) + { + // takeover successful, we have a winner! remove the winner from other raffles they might be in + LeaveAllRaffles(player); + return true; + } + + return false; + } + + private void RemoveRaffleAndUpdateEui(EntityUid entityUid, GhostRoleRaffleComponent raffle) + { + _ghostRoleRaffles.Remove(raffle.Identifier); + RemComp(entityUid, raffle); + UpdateAllEui(); + } + private void PlayerStatusChanged(object? blah, SessionStatusEventArgs args) { if (args.NewStatus == SessionStatus.InGame) @@ -177,6 +295,11 @@ private void PlayerStatusChanged(object? blah, SessionStatusEventArgs args) var response = new GhostUpdateGhostRoleCountEvent(_ghostRoles.Count); RaiseNetworkEvent(response, args.Session.Channel); } + else + { + // people who disconnect are removed from ghost role raffles + LeaveAllRaffles(args.Session); + } } public void RegisterGhostRole(Entity role) @@ -195,24 +318,170 @@ public void UnregisterGhostRole(Entity role) return; _ghostRoles.Remove(comp.Identifier); + if (TryComp(role.Owner, out GhostRoleRaffleComponent? raffle)) + { + // if a raffle is still running, get rid of it + RemoveRaffleAndUpdateEui(role.Owner, raffle); + } + else + { + UpdateAllEui(); + } + } + + // probably fine to be init because it's never added during entity initialization, but much later + private void OnRaffleInit(Entity ent, ref ComponentInit args) + { + if (!TryComp(ent, out GhostRoleComponent? ghostRole)) + { + // can't have a raffle for a ghost role that doesn't exist + RemComp(ent); + return; + } + + var config = ghostRole.RaffleConfig; + if (config is null) + return; // should, realistically, never be reached but you never know + + var settings = config.SettingsOverride + ?? _prototype.Index(config.Settings).Settings; + + if (settings.MaxDuration < settings.InitialDuration) + { + Log.Error($"Ghost role on {ent} has invalid raffle settings (max duration shorter than initial)"); + ghostRole.RaffleConfig = null; // make it a non-raffle role so stuff isn't entirely broken + RemComp(ent); + return; + } + + var raffle = ent.Comp; + raffle.Identifier = ghostRole.Identifier; + raffle.Countdown = TimeSpan.FromSeconds(settings.InitialDuration); + raffle.CumulativeTime = TimeSpan.FromSeconds(settings.InitialDuration); + // we copy these settings into the component because they would be cumbersome to access otherwise + raffle.JoinExtendsDurationBy = TimeSpan.FromSeconds(settings.JoinExtendsDurationBy); + raffle.MaxDuration = TimeSpan.FromSeconds(settings.MaxDuration); + } + + private void OnRaffleShutdown(Entity ent, ref ComponentShutdown args) + { + _ghostRoleRaffles.Remove(ent.Comp.Identifier); + } + + /// + /// Joins the given player onto a ghost role raffle, or creates it if it doesn't exist. + /// + /// The player. + /// The ID that represents the ghost role or ghost role raffle. + /// (A raffle will have the same ID as the ghost role it's for.) + private void JoinRaffle(ICommonSession player, uint identifier) + { + if (!_ghostRoles.TryGetValue(identifier, out var roleEnt)) + return; + + // get raffle or create a new one if it doesn't exist + var raffle = _ghostRoleRaffles.TryGetValue(identifier, out var raffleEnt) + ? raffleEnt.Comp + : EnsureComp(roleEnt.Owner); + + _ghostRoleRaffles.TryAdd(identifier, (roleEnt.Owner, raffle)); + + if (!raffle.CurrentMembers.Add(player)) + { + Log.Warning($"{player.Name} tried to join raffle for ghost role {identifier} but they are already in the raffle"); + return; + } + + // if this is the first time the player joins this raffle, and the player wasn't the starter of the raffle: + // extend the countdown, but only if doing so will not make the raffle take longer than the maximum + // duration + if (raffle.AllMembers.Add(player) && raffle.AllMembers.Count > 1 + && raffle.CumulativeTime.Add(raffle.JoinExtendsDurationBy) <= raffle.MaxDuration) + { + raffle.Countdown += raffle.JoinExtendsDurationBy; + raffle.CumulativeTime += raffle.JoinExtendsDurationBy; + } + UpdateAllEui(); } - public void Takeover(ICommonSession player, uint identifier) + /// + /// Makes the given player leave the raffle corresponding to the given ID. + /// + public void LeaveRaffle(ICommonSession player, uint identifier) { - if (!_ghostRoles.TryGetValue(identifier, out var role)) + if (!_ghostRoleRaffles.TryGetValue(identifier, out var raffleEnt)) return; + if (raffleEnt.Comp.CurrentMembers.Remove(player)) + { + UpdateAllEui(); + } + else + { + Log.Warning($"{player.Name} tried to leave raffle for ghost role {identifier} but they are not in the raffle"); + } + + // (raffle ending because all players left is handled in update()) + } + + /// + /// Makes the given player leave all ghost role raffles. + /// + public void LeaveAllRaffles(ICommonSession player) + { + var shouldUpdateEui = false; + + foreach (var raffleEnt in _ghostRoleRaffles.Values) + { + shouldUpdateEui |= raffleEnt.Comp.CurrentMembers.Remove(player); + } + + if (shouldUpdateEui) + UpdateAllEui(); + } + + /// + /// Request a ghost role. If it's a raffled role starts or joins a raffle, otherwise the player immediately + /// takes over the ghost role if possible. + /// + /// The player. + /// ID of the ghost role. + public void Request(ICommonSession player, uint identifier) + { + if (!_ghostRoles.TryGetValue(identifier, out var roleEnt)) + return; + + if (roleEnt.Comp.RaffleConfig is not null) + { + JoinRaffle(player, identifier); + } + else + { + Takeover(player, identifier); + } + } + + /// + /// Attempts having the player take over the ghost role with the corresponding ID. Does not start a raffle. + /// + /// True if takeover was successful, otherwise false. + public bool Takeover(ICommonSession player, uint identifier) + { + if (!_ghostRoles.TryGetValue(identifier, out var role)) + return false; + var ev = new TakeGhostRoleEvent(player); RaiseLocalEvent(role, ref ev); if (!ev.TookRole) - return; + return false; if (player.AttachedEntity != null) _adminLogger.Add(LogType.GhostRoleTaken, LogImpact.Low, $"{player:player} took the {role.Comp.RoleName:roleName} ghost role {ToPrettyString(player.AttachedEntity.Value):entity}"); CloseEui(player); + return true; } public void Follow(ICommonSession player, uint identifier) @@ -241,7 +510,22 @@ public void GhostRoleInternalCreateMindAndTransfer(ICommonSession player, Entity _mindSystem.TransferTo(newMind, mob); } - public GhostRoleInfo[] GetGhostRolesInfo() + /// + /// Returns the number of available ghost roles. + /// + public int GetGhostRoleCount() + { + var metaQuery = GetEntityQuery(); + return _ghostRoles.Count(pair => metaQuery.GetComponent(pair.Value.Owner).EntityPaused == false); + } + + /// + /// Returns information about all available ghost roles. + /// + /// + /// If not null, the s will show if the given player is in a raffle. + /// + public GhostRoleInfo[] GetGhostRolesInfo(ICommonSession? player) { var roles = new List(); var metaQuery = GetEntityQuery(); @@ -251,7 +535,40 @@ public GhostRoleInfo[] GetGhostRolesInfo() if (metaQuery.GetComponent(uid).EntityPaused) continue; - roles.Add(new GhostRoleInfo {Identifier = id, Name = role.RoleName, Description = role.RoleDescription, Rules = role.RoleRules, Requirements = role.Requirements}); + + var kind = GhostRoleKind.FirstComeFirstServe; + GhostRoleRaffleComponent? raffle = null; + + if (role.RaffleConfig is not null) + { + kind = GhostRoleKind.RaffleReady; + + if (_ghostRoleRaffles.TryGetValue(id, out var raffleEnt)) + { + kind = GhostRoleKind.RaffleInProgress; + raffle = raffleEnt.Comp; + + if (player is not null && raffle.CurrentMembers.Contains(player)) + kind = GhostRoleKind.RaffleJoined; + } + } + + var rafflePlayerCount = (uint?) raffle?.CurrentMembers.Count ?? 0; + var raffleEndTime = raffle is not null + ? _timing.CurTime.Add(raffle.Countdown) + : TimeSpan.MinValue; + + roles.Add(new GhostRoleInfo + { + Identifier = id, + Name = role.RoleName, + Description = role.RoleDescription, + Rules = role.RoleRules, + Requirements = role.Requirements, + Kind = kind, + RafflePlayerCount = rafflePlayerCount, + RaffleEndTime = raffleEndTime + }); } return roles.ToArray(); @@ -266,6 +583,10 @@ private void OnPlayerAttached(PlayerAttachedEvent message) if (HasComp(message.Entity)) return; + // The player is not a ghost (anymore), so they should not be in any raffles. Remove them. + // This ensures player doesn't win a raffle after returning to their (revived) body and ends up being + // forced into a ghost role. + LeaveAllRaffles(message.Player); CloseEui(message.Player); } @@ -300,6 +621,7 @@ public void Reset(RoundRestartCleanupEvent ev) _openUis.Clear(); _ghostRoles.Clear(); + _ghostRoleRaffles.Clear(); _nextRoleIdentifier = 0; } @@ -325,12 +647,12 @@ private void OnMapInit(Entity ent, ref MapInitEvent args) RemCompDeferred(ent); } - private void OnStartup(Entity ent, ref ComponentStartup args) + private void OnRoleStartup(Entity ent, ref ComponentStartup args) { RegisterGhostRole(ent); } - private void OnShutdown(Entity role, ref ComponentShutdown args) + private void OnRoleShutdown(Entity role, ref ComponentShutdown args) { UnregisterGhostRole(role); } @@ -408,6 +730,63 @@ private void OnTakeoverTakeRole(EntityUid uid, GhostTakeoverAvailableComponent c args.TookRole = true; } + + private void OnVerb(EntityUid uid, GhostRoleMobSpawnerComponent component, GetVerbsEvent args) + { + var prototypes = component.SelectablePrototypes; + if (prototypes.Count < 1) + return; + + if (!args.CanAccess || !args.CanInteract || args.Hands == null) + return; + + var verbs = new ValueList(); + + foreach (var prototypeID in prototypes) + { + if (_prototype.TryIndex(prototypeID, out var prototype)) + { + var verb = CreateVerb(uid, component, args.User, prototype); + verbs.Add(verb); + } + } + + args.Verbs.UnionWith(verbs); + } + + private Verb CreateVerb(EntityUid uid, GhostRoleMobSpawnerComponent component, EntityUid userUid, GhostRolePrototype prototype) + { + var verbText = Loc.GetString(prototype.Name); + + return new Verb() + { + Text = verbText, + Disabled = component.Prototype == prototype.EntityPrototype, + Category = VerbCategory.SelectType, + Act = () => SetMode(uid, prototype, verbText, component, userUid) + }; + } + + public void SetMode(EntityUid uid, GhostRolePrototype prototype, string verbText, GhostRoleMobSpawnerComponent? component, EntityUid? userUid = null) + { + if (!Resolve(uid, ref component)) + return; + + var ghostrolecomp = EnsureComp(uid); + + component.Prototype = prototype.EntityPrototype; + ghostrolecomp.RoleName = verbText; + ghostrolecomp.RoleDescription = prototype.Description; + ghostrolecomp.RoleRules = prototype.Rules; + + // Dirty(ghostrolecomp); + + if (userUid != null) + { + var msg = Loc.GetString("ghostrole-spawner-select", ("mode", verbText)); + _popupSystem.PopupEntity(msg, uid, userUid.Value); + } + } } [AnyCommand] @@ -418,7 +797,7 @@ public sealed class GhostRoles : IConsoleCommand public string Help => $"{Command}"; public void Execute(IConsoleShell shell, string argStr, string[] args) { - if(shell.Player != null) + if (shell.Player != null) EntitySystem.Get().OpenEui(shell.Player); else shell.WriteLine("You can only open the ghost roles UI on a client."); diff --git a/Content.Server/Ghost/Roles/MakeRaffledGhostRoleCommand.cs b/Content.Server/Ghost/Roles/MakeRaffledGhostRoleCommand.cs new file mode 100644 index 0000000000..5f5eabdad4 --- /dev/null +++ b/Content.Server/Ghost/Roles/MakeRaffledGhostRoleCommand.cs @@ -0,0 +1,127 @@ +using System.Linq; +using Content.Server.Administration; +using Content.Server.Ghost.Roles.Components; +using Content.Server.Ghost.Roles.Raffles; +using Content.Shared.Administration; +using Content.Shared.Ghost.Roles.Raffles; +using Content.Shared.Mind.Components; +using Robust.Shared.Console; +using Robust.Shared.Prototypes; + +namespace Content.Server.Ghost.Roles +{ + [AdminCommand(AdminFlags.Admin)] + public sealed class MakeRaffledGhostRoleCommand : IConsoleCommand + { + [Dependency] private readonly IPrototypeManager _protoManager = default!; + [Dependency] private readonly IEntityManager _entManager = default!; + + public string Command => "makeghostroleraffled"; + public string Description => "Turns an entity into a raffled ghost role."; + public string Help => $"Usage: {Command} ( | ) []\n" + + $"Durations are in seconds."; + + public void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (args.Length is < 4 or > 7) + { + shell.WriteLine($"Invalid amount of arguments.\n{Help}"); + return; + } + + if (!NetEntity.TryParse(args[0], out var uidNet) || !_entManager.TryGetEntity(uidNet, out var uid)) + { + shell.WriteLine($"{args[0]} is not a valid entity uid."); + return; + } + + if (!_entManager.TryGetComponent(uid, out MetaDataComponent? metaData)) + { + shell.WriteLine($"No entity found with uid {uid}"); + return; + } + + if (_entManager.TryGetComponent(uid, out MindContainerComponent? mind) && + mind.HasMind) + { + shell.WriteLine($"Entity {metaData.EntityName} with id {uid} already has a mind."); + return; + } + + if (_entManager.TryGetComponent(uid, out GhostRoleComponent? ghostRole)) + { + shell.WriteLine($"Entity {metaData.EntityName} with id {uid} already has a {nameof(GhostRoleComponent)}"); + return; + } + + if (_entManager.HasComponent(uid)) + { + shell.WriteLine($"Entity {metaData.EntityName} with id {uid} already has a {nameof(GhostTakeoverAvailableComponent)}"); + return; + } + + var name = args[1]; + var description = args[2]; + + // if the rules are specified then use those, otherwise use the default + var rules = args.Length switch + { + 5 => args[4], + 7 => args[6], + _ => Loc.GetString("ghost-role-component-default-rules"), + }; + + // is it an invocation with a prototype ID and optional rules? + var isProto = args.Length is 4 or 5; + GhostRoleRaffleSettings settings; + + if (isProto) + { + if (!_protoManager.TryIndex(args[4], out var proto)) + { + var validProtos = string.Join(", ", + _protoManager.EnumeratePrototypes().Select(p => p.ID) + ); + + shell.WriteLine($"{args[4]} is not a valid raffle settings prototype. Valid options: {validProtos}"); + return; + } + + settings = proto.Settings; + } + else + { + if (!uint.TryParse(args[3], out var initial) + || !uint.TryParse(args[4], out var extends) + || !uint.TryParse(args[5], out var max) + || initial == 0 || max == 0) + { + shell.WriteLine($"The raffle initial/extends/max settings must be positive numbers."); + return; + } + + if (initial > max) + { + shell.WriteLine("The initial duration must be smaller than or equal to the maximum duration."); + return; + } + + settings = new GhostRoleRaffleSettings() + { + InitialDuration = initial, + JoinExtendsDurationBy = extends, + MaxDuration = max + }; + } + + ghostRole = _entManager.AddComponent(uid.Value); + _entManager.AddComponent(uid.Value); + ghostRole.RoleName = name; + ghostRole.RoleDescription = description; + ghostRole.RoleRules = rules; + ghostRole.RaffleConfig = new GhostRoleRaffleConfig(settings); + + shell.WriteLine($"Made entity {metaData.EntityName} a raffled ghost role."); + } + } +} diff --git a/Content.Server/Ghost/Roles/Raffles/GhostRoleRaffleConfig.cs b/Content.Server/Ghost/Roles/Raffles/GhostRoleRaffleConfig.cs new file mode 100644 index 0000000000..052704df25 --- /dev/null +++ b/Content.Server/Ghost/Roles/Raffles/GhostRoleRaffleConfig.cs @@ -0,0 +1,35 @@ +using Content.Shared.Ghost.Roles.Raffles; +using Robust.Shared.Prototypes; + +namespace Content.Server.Ghost.Roles.Raffles; + +/// +/// Raffle configuration. +/// +[DataDefinition] +public sealed partial class GhostRoleRaffleConfig +{ + public GhostRoleRaffleConfig(GhostRoleRaffleSettings settings) + { + SettingsOverride = settings; + } + + /// + /// Specifies the raffle settings to use. + /// + [DataField("settings", required: true)] + public ProtoId Settings { get; set; } = "default"; + + /// + /// If not null, the settings from are ignored and these settings are used instead. + /// Intended for allowing admins to set custom raffle settings for admeme ghost roles. + /// + [ViewVariables(VVAccess.ReadOnly)] + public GhostRoleRaffleSettings? SettingsOverride { get; set; } + + /// + /// Sets which is used. + /// + [DataField("decider")] + public ProtoId Decider { get; set; } = "default"; +} diff --git a/Content.Server/Ghost/Roles/Raffles/GhostRoleRaffleDeciderPrototype.cs b/Content.Server/Ghost/Roles/Raffles/GhostRoleRaffleDeciderPrototype.cs new file mode 100644 index 0000000000..b2ebf6ca5f --- /dev/null +++ b/Content.Server/Ghost/Roles/Raffles/GhostRoleRaffleDeciderPrototype.cs @@ -0,0 +1,20 @@ +using Robust.Shared.Prototypes; + +namespace Content.Server.Ghost.Roles.Raffles; + +/// +/// Allows getting a as prototype. +/// +[Prototype("ghostRoleRaffleDecider")] +public sealed class GhostRoleRaffleDeciderPrototype : IPrototype +{ + /// + [IdDataField] + public string ID { get; private set; } = default!; + + /// + /// The instance that chooses the winner of a raffle. + /// + [DataField("decider", required: true)] + public IGhostRoleRaffleDecider Decider { get; private set; } = new RngGhostRoleRaffleDecider(); +} diff --git a/Content.Server/Ghost/Roles/Raffles/IGhostRoleRaffleDecider.cs b/Content.Server/Ghost/Roles/Raffles/IGhostRoleRaffleDecider.cs new file mode 100644 index 0000000000..bdd2bf046d --- /dev/null +++ b/Content.Server/Ghost/Roles/Raffles/IGhostRoleRaffleDecider.cs @@ -0,0 +1,28 @@ +using Robust.Shared.Player; + +namespace Content.Server.Ghost.Roles.Raffles; + +/// +/// Chooses a winner of a ghost role raffle. +/// +[ImplicitDataDefinitionForInheritors] +public partial interface IGhostRoleRaffleDecider +{ + /// + /// Chooses a winner of a ghost role raffle draw from the given pool of candidates. + /// + /// The players in the session at the time of drawing. + /// + /// Call this with the chosen winner as argument. + ///
  • If true is returned, your winner was able to take over the ghost role, and the drawing is complete. + /// Do not call again after true is returned.
  • + ///
  • If false is returned, your winner was not able to take over the ghost role, + /// and you must choose another winner, and call with the new winner as argument.
  • + ///
+ /// + /// If is not called, or only returns false, the raffle will end without a winner. + /// Do not call with the same player several times. + /// + void PickWinner(IEnumerable candidates, Func tryTakeover); +} + diff --git a/Content.Server/Ghost/Roles/Raffles/RngGhostRoleRaffleDecider.cs b/Content.Server/Ghost/Roles/Raffles/RngGhostRoleRaffleDecider.cs new file mode 100644 index 0000000000..b91d359935 --- /dev/null +++ b/Content.Server/Ghost/Roles/Raffles/RngGhostRoleRaffleDecider.cs @@ -0,0 +1,27 @@ +using System.Linq; +using JetBrains.Annotations; +using Robust.Shared.Player; +using Robust.Shared.Random; + +namespace Content.Server.Ghost.Roles.Raffles; + +/// +/// Chooses the winner of a ghost role raffle entirely randomly, without any weighting. +/// +[UsedImplicitly(ImplicitUseTargetFlags.WithMembers)] +public sealed partial class RngGhostRoleRaffleDecider : IGhostRoleRaffleDecider +{ + public void PickWinner(IEnumerable candidates, Func tryTakeover) + { + var random = IoCManager.Resolve(); + + var choices = candidates.ToList(); + random.Shuffle(choices); // shuffle the list so we can pick a lucky winner! + + foreach (var candidate in choices) + { + if (tryTakeover(candidate)) + return; + } + } +} diff --git a/Content.Server/Ghost/Roles/UI/GhostRolesEui.cs b/Content.Server/Ghost/Roles/UI/GhostRolesEui.cs index 627231db9e..c1e39919a2 100644 --- a/Content.Server/Ghost/Roles/UI/GhostRolesEui.cs +++ b/Content.Server/Ghost/Roles/UI/GhostRolesEui.cs @@ -6,9 +6,16 @@ namespace Content.Server.Ghost.Roles.UI { public sealed class GhostRolesEui : BaseEui { + private readonly GhostRoleSystem _ghostRoleSystem; + + public GhostRolesEui() + { + _ghostRoleSystem = IoCManager.Resolve().GetEntitySystem(); + } + public override GhostRolesEuiState GetNewState() { - return new(EntitySystem.Get().GetGhostRolesInfo()); + return new(_ghostRoleSystem.GetGhostRolesInfo(Player)); } public override void HandleMessage(EuiMessageBase msg) @@ -17,11 +24,14 @@ public override void HandleMessage(EuiMessageBase msg) switch (msg) { - case GhostRoleTakeoverRequestMessage req: - EntitySystem.Get().Takeover(Player, req.Identifier); + case RequestGhostRoleMessage req: + _ghostRoleSystem.Request(Player, req.Identifier); + break; + case FollowGhostRoleMessage req: + _ghostRoleSystem.Follow(Player, req.Identifier); break; - case GhostRoleFollowRequestMessage req: - EntitySystem.Get().Follow(Player, req.Identifier); + case LeaveGhostRoleRaffleMessage req: + _ghostRoleSystem.LeaveRaffle(Player, req.Identifier); break; } } diff --git a/Content.Server/Gravity/GravityGeneratorComponent.cs b/Content.Server/Gravity/GravityGeneratorComponent.cs index f946292038..f47d397939 100644 --- a/Content.Server/Gravity/GravityGeneratorComponent.cs +++ b/Content.Server/Gravity/GravityGeneratorComponent.cs @@ -37,6 +37,9 @@ public sealed partial class GravityGeneratorComponent : SharedGravityGeneratorCo // 0 -> 1 [ViewVariables(VVAccess.ReadWrite)] [DataField("charge")] public float Charge { get; set; } = 1; + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string MachinePartMaxChargeMultiplier = "Capacitor"; + /// /// Is the gravity generator currently "producing" gravity? /// diff --git a/Content.Server/Gravity/GravityGeneratorSystem.cs b/Content.Server/Gravity/GravityGeneratorSystem.cs index ec5646457e..dc31c004c6 100644 --- a/Content.Server/Gravity/GravityGeneratorSystem.cs +++ b/Content.Server/Gravity/GravityGeneratorSystem.cs @@ -1,5 +1,6 @@ using Content.Server.Administration.Logs; using Content.Server.Audio; +using Content.Server.Construction; using Content.Server.Power.Components; using Content.Server.Emp; using Content.Shared.Database; @@ -27,6 +28,7 @@ public override void Initialize() SubscribeLocalEvent(OnComponentShutdown); SubscribeLocalEvent(OnParentChanged); // Or just anchor changed? SubscribeLocalEvent(OnInteractHand); + SubscribeLocalEvent(OnRefreshParts); SubscribeLocalEvent( OnSwitchGenerator); @@ -134,13 +136,13 @@ public override void Update(float frameTime) } private void SetSwitchedOn(EntityUid uid, GravityGeneratorComponent component, bool on, - ApcPowerReceiverComponent? powerReceiver = null, ICommonSession? session = null) + ApcPowerReceiverComponent? powerReceiver = null, EntityUid? user = null) { if (!Resolve(uid, ref powerReceiver)) return; - if (session is { AttachedEntity: { } }) - _adminLogger.Add(LogType.Action, on ? LogImpact.Medium : LogImpact.High, $"{session:player} set ${ToPrettyString(uid):target} to {(on ? "on" : "off")}"); + if (user != null) + _adminLogger.Add(LogType.Action, on ? LogImpact.Medium : LogImpact.High, $"{ToPrettyString(user)} set ${ToPrettyString(uid):target} to {(on ? "on" : "off")}"); component.SwitchedOn = on; UpdatePowerState(component, powerReceiver); @@ -157,7 +159,7 @@ private static void UpdatePowerState( private void UpdateUI(Entity ent, float chargeRate) { var (_, component, powerReceiver) = ent; - if (!_uiSystem.IsUiOpen(ent, SharedGravityGeneratorComponent.GravityGeneratorUiKey.Key)) + if (!_uiSystem.IsUiOpen(ent.Owner, SharedGravityGeneratorComponent.GravityGeneratorUiKey.Key)) return; var chargeTarget = chargeRate < 0 ? 0 : component.MaxCharge; @@ -192,8 +194,8 @@ private void UpdateUI(Entity ent, ref ComponentInit private void OnInteractHand(EntityUid uid, GravityGeneratorComponent component, InteractHandEvent args) { - if (!EntityManager.TryGetComponent(args.User, out ActorComponent? actor)) - return; - ApcPowerReceiverComponent? powerReceiver = default!; if (!Resolve(uid, ref powerReceiver)) return; @@ -223,7 +222,7 @@ private void OnInteractHand(EntityUid uid, GravityGeneratorComponent component, if (!component.Intact || powerReceiver.PowerReceived < component.IdlePowerUse) return; - _uiSystem.TryOpen(uid, SharedGravityGeneratorComponent.GravityGeneratorUiKey.Key, actor.PlayerSession); + _uiSystem.OpenUi(uid, SharedGravityGeneratorComponent.GravityGeneratorUiKey.Key, args.User); component.NeedUIUpdate = true; } @@ -257,6 +256,12 @@ public void UpdateState(Entity ent, AppearanceComponent? appearance) { _ambientSoundSystem.SetAmbience(ent, false); @@ -290,7 +295,7 @@ private void OnSwitchGenerator( GravityGeneratorComponent component, SharedGravityGeneratorComponent.SwitchGeneratorMessage args) { - SetSwitchedOn(uid, component, args.On, session:args.Session); + SetSwitchedOn(uid, component, args.On, user: args.Actor); } private void OnEmpPulse(EntityUid uid, GravityGeneratorComponent component, EmpPulseEvent args) diff --git a/Content.Server/GridPreloader/GridPreloaderComponent.cs b/Content.Server/GridPreloader/GridPreloaderComponent.cs new file mode 100644 index 0000000000..9ff31927d0 --- /dev/null +++ b/Content.Server/GridPreloader/GridPreloaderComponent.cs @@ -0,0 +1,16 @@ +using Content.Shared.GridPreloader.Prototypes; +using Robust.Shared.Map; +using Robust.Shared.Prototypes; + +namespace Content.Server.GridPreloader; + +/// +/// Component storing data about preloaded grids and their location +/// Goes on the map entity +/// +[RegisterComponent, Access(typeof(GridPreloaderSystem))] +public sealed partial class GridPreloaderComponent : Component +{ + [DataField] + public Dictionary, List> PreloadedGrids = new(); +} diff --git a/Content.Server/GridPreloader/GridPreloaderSystem.cs b/Content.Server/GridPreloader/GridPreloaderSystem.cs new file mode 100644 index 0000000000..569fe54141 --- /dev/null +++ b/Content.Server/GridPreloader/GridPreloaderSystem.cs @@ -0,0 +1,147 @@ +using System.Diagnostics.CodeAnalysis; +using Content.Shared.CCVar; +using Content.Shared.GridPreloader.Prototypes; +using Content.Shared.GridPreloader.Systems; +using Robust.Server.GameObjects; +using Robust.Server.Maps; +using Robust.Shared.Configuration; +using Robust.Shared.Map; +using Robust.Shared.Map.Components; +using Robust.Shared.Physics.Components; +using Robust.Shared.Prototypes; +using System.Numerics; +using Content.Server.GameTicking; +using Content.Shared.GameTicking; +using JetBrains.Annotations; + +namespace Content.Server.GridPreloader; +public sealed class GridPreloaderSystem : SharedGridPreloaderSystem +{ + [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly MapSystem _map = default!; + [Dependency] private readonly MapLoaderSystem _mapLoader = default!; + [Dependency] private readonly MetaDataSystem _meta = default!; + [Dependency] private readonly IPrototypeManager _prototype = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnRoundRestart); + SubscribeLocalEvent(OnPostGameMapLoad); + } + + private void OnRoundRestart(RoundRestartCleanupEvent ev) + { + var ent = GetPreloaderEntity(); + if (ent == null) + return; + + Del(ent.Value.Owner); + } + + private void OnPostGameMapLoad(PostGameMapLoad ev) + { + EnsurePreloadedGridMap(); + } + + private void EnsurePreloadedGridMap() + { + // Already have a preloader? + if (GetPreloaderEntity() != null) + return; + + if (!_cfg.GetCVar(CCVars.PreloadGrids)) + return; + + var mapUid = _map.CreateMap(out var mapId, false); + var preloader = EnsureComp(mapUid); + _meta.SetEntityName(mapUid, "GridPreloader Map"); + _map.SetPaused(mapId, true); + + var globalXOffset = 0f; + foreach (var proto in _prototype.EnumeratePrototypes()) + { + for (var i = 0; i < proto.Copies; i++) + { + var options = new MapLoadOptions + { + LoadMap = false, + }; + + if (!_mapLoader.TryLoad(mapId, proto.Path.ToString(), out var roots, options)) + continue; + + // only supports loading maps with one grid. + if (roots.Count != 1) + continue; + + var gridUid = roots[0]; + + // gets grid + also confirms that the root we loaded is actually a grid + if (!TryComp(gridUid, out var mapGrid)) + continue; + + if (!TryComp(gridUid, out var physics)) + continue; + + // Position Calculating + globalXOffset += mapGrid.LocalAABB.Width / 2; + + var coords = new Vector2(-physics.LocalCenter.X + globalXOffset, -physics.LocalCenter.Y); + _transform.SetCoordinates(gridUid, new EntityCoordinates(mapUid, coords)); + + globalXOffset += (mapGrid.LocalAABB.Width / 2) + 1; + + // Add to list + if (!preloader.PreloadedGrids.ContainsKey(proto.ID)) + preloader.PreloadedGrids[proto.ID] = new(); + preloader.PreloadedGrids[proto.ID].Add(gridUid); + } + } + } + + /// + /// Should be a singleton no matter station count, so we can assume 1 + /// (better support for singleton component in engine at some point i guess) + /// + /// + public Entity? GetPreloaderEntity() + { + var query = AllEntityQuery(); + while (query.MoveNext(out var uid, out var comp)) + { + return (uid, comp); + } + + return null; + } + + /// + /// An attempt to get a certain preloaded shuttle. If there are no more such shuttles left, returns null + /// + [PublicAPI] + public bool TryGetPreloadedGrid(ProtoId proto, [NotNullWhen(true)] out EntityUid? preloadedGrid, GridPreloaderComponent? preloader = null) + { + preloadedGrid = null; + + if (preloader == null) + { + preloader = GetPreloaderEntity(); + if (preloader == null) + return false; + } + + if (!preloader.PreloadedGrids.TryGetValue(proto, out var list) || list.Count <= 0) + return false; + + preloadedGrid = list[0]; + + list.RemoveAt(0); + if (list.Count == 0) + preloader.PreloadedGrids.Remove(proto); + + return true; + } +} diff --git a/Content.Server/Hands/Systems/HandsSystem.cs b/Content.Server/Hands/Systems/HandsSystem.cs index bd24ddab5d..8f0519b24c 100644 --- a/Content.Server/Hands/Systems/HandsSystem.cs +++ b/Content.Server/Hands/Systems/HandsSystem.cs @@ -2,6 +2,8 @@ using Content.Server.Inventory; using Content.Server.Stack; using Content.Server.Stunnable; +using Content.Shared.Body.Systems; +using Content.Shared.Body.Events; using Content.Shared.ActionBlocker; using Content.Shared.Body.Part; using Content.Shared.CombatMode; @@ -19,6 +21,7 @@ using Robust.Shared.Input.Binding; using Robust.Shared.Map; using Robust.Shared.Player; +using Robust.Shared.Random; using Robust.Shared.Timing; using Robust.Shared.Utility; @@ -27,19 +30,19 @@ namespace Content.Server.Hands.Systems public sealed class HandsSystem : SharedHandsSystem { [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly StackSystem _stackSystem = default!; [Dependency] private readonly VirtualItemSystem _virtualItemSystem = default!; [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; - [Dependency] private readonly SharedHandsSystem _handsSystem = default!; [Dependency] private readonly SharedTransformSystem _transformSystem = default!; [Dependency] private readonly PullingSystem _pullingSystem = default!; [Dependency] private readonly ThrowingSystem _throwingSystem = default!; - + [Dependency] private readonly SharedBodySystem _bodySystem = default!; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnDisarmed, before: new[] {typeof(StunSystem)}); + SubscribeLocalEvent(OnDisarmed, before: new[] { typeof(StunSystem) }); SubscribeLocalEvent(HandlePullStarted); SubscribeLocalEvent(HandlePullStopped); @@ -50,6 +53,8 @@ public override void Initialize() SubscribeLocalEvent(GetComponentState); SubscribeLocalEvent(OnExploded); + SubscribeLocalEvent(HandleBodyPartEnabled); + SubscribeLocalEvent(HandleBodyPartDisabled); CommandBinds.Builder .Bind(ContentKeyFunctions.ThrowItemInHand, new PointerInputCmdHandler(HandleThrowItem)) @@ -90,38 +95,62 @@ private void OnDisarmed(EntityUid uid, HandsComponent component, DisarmedEvent a if (TryComp(uid, out PullerComponent? puller) && TryComp(puller.Pulling, out PullableComponent? pullable)) _pullingSystem.TryStopPull(puller.Pulling.Value, pullable); - if (!_handsSystem.TryDrop(uid, component.ActiveHand!, null, checkActionBlocker: false)) + var offsetRandomCoordinates = _transformSystem.GetMoverCoordinates(args.Target).Offset(_random.NextVector2(1f, 1.5f)); + if (!ThrowHeldItem(args.Target, offsetRandomCoordinates)) return; args.Handled = true; // no shove/stun. } - private void HandleBodyPartAdded(EntityUid uid, HandsComponent component, ref BodyPartAddedEvent args) + private void TryAddHand(EntityUid uid, HandsComponent component, Entity part, string slot) { - if (args.Part.Comp.PartType != BodyPartType.Hand) + if (part.Comp is null + || part.Comp.PartType != BodyPartType.Hand) return; // If this annoys you, which it should. // Ping Smugleaf. - var location = args.Part.Comp.Symmetry switch + var location = part.Comp.Symmetry switch { BodyPartSymmetry.None => HandLocation.Middle, BodyPartSymmetry.Left => HandLocation.Left, BodyPartSymmetry.Right => HandLocation.Right, - _ => throw new ArgumentOutOfRangeException(nameof(args.Part.Comp.Symmetry)) + _ => throw new ArgumentOutOfRangeException(nameof(part.Comp.Symmetry)) }; - AddHand(uid, args.Slot, location); + if (part.Comp.Enabled + && _bodySystem.TryGetParentBodyPart(part, out var _, out var parentPartComp) + && parentPartComp.Enabled) + AddHand(uid, slot, location); + } + + private void HandleBodyPartAdded(EntityUid uid, HandsComponent component, ref BodyPartAddedEvent args) + { + TryAddHand(uid, component, args.Part, args.Slot); } private void HandleBodyPartRemoved(EntityUid uid, HandsComponent component, ref BodyPartRemovedEvent args) { - if (args.Part.Comp.PartType != BodyPartType.Hand) + if (args.Part.Comp is null + || args.Part.Comp.PartType != BodyPartType.Hand) return; - RemoveHand(uid, args.Slot); } + private void HandleBodyPartEnabled(EntityUid uid, HandsComponent component, ref BodyPartEnabledEvent args) => + TryAddHand(uid, component, args.Part, SharedBodySystem.GetPartSlotContainerId(args.Part.Comp.ParentSlot?.Id ?? string.Empty)); + + private void HandleBodyPartDisabled(EntityUid uid, HandsComponent component, ref BodyPartDisabledEvent args) + { + if (TerminatingOrDeleted(uid) + || args.Part.Comp is null + || args.Part.Comp.PartType != BodyPartType.Hand) + return; + + RemoveHand(uid, SharedBodySystem.GetPartSlotContainerId(args.Part.Comp.ParentSlot?.Id ?? string.Empty)); + } + + #region pulling private void HandlePullStarted(EntityUid uid, HandsComponent component, PullStartedMessage args) @@ -165,7 +194,7 @@ private void HandlePullStopped(EntityUid uid, HandsComponent component, PullStop private bool HandleThrowItem(ICommonSession? playerSession, EntityCoordinates coordinates, EntityUid entity) { - if (playerSession?.AttachedEntity is not {Valid: true} player || !Exists(player)) + if (playerSession?.AttachedEntity is not { Valid: true } player || !Exists(player)) return false; return ThrowHeldItem(player, coordinates); @@ -190,7 +219,7 @@ hands.ActiveHandEntity is not { } throwEnt || { var splitStack = _stackSystem.Split(throwEnt, 1, EntityManager.GetComponent(player).Coordinates, stack); - if (splitStack is not {Valid: true}) + if (splitStack is not { Valid: true }) return false; throwEnt = splitStack.Value; @@ -202,7 +231,7 @@ hands.ActiveHandEntity is not { } throwEnt || var length = direction.Length(); var distance = Math.Clamp(length, minDistance, hands.ThrowRange); - direction *= distance/length; + direction *= distance / length; var throwStrength = hands.ThrowForceMultiplier; diff --git a/Content.Server/HealthExaminable/HealthExaminableComponent.cs b/Content.Server/HealthExaminable/HealthExaminableComponent.cs deleted file mode 100644 index 04053aed70..0000000000 --- a/Content.Server/HealthExaminable/HealthExaminableComponent.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Content.Shared.Damage.Prototypes; -using Content.Shared.FixedPoint; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set; - -namespace Content.Server.HealthExaminable; - -[RegisterComponent, Access(typeof(HealthExaminableSystem))] -public sealed partial class HealthExaminableComponent : Component -{ - // - // The thresholds for determining the examine text for certain amounts of damage. - // These are calculated as a percentage of the entity's critical threshold. - // - public List Thresholds = new() - { FixedPoint2.New(0.10), FixedPoint2.New(0.25), FixedPoint2.New(0.50), FixedPoint2.New(0.75) }; - - [DataField("examinableTypes", required: true, customTypeSerializer:typeof(PrototypeIdHashSetSerializer))] - public HashSet ExaminableTypes = default!; - - /// - /// Health examine text is automatically generated through creating loc string IDs, in the form: - /// `health-examine-[prefix]-[type]-[threshold]` - /// This part determines the prefix. - /// - [DataField("locPrefix")] - public string LocPrefix = "carbon"; -} diff --git a/Content.Server/HeightAdjust/BloodstreamAdjustSystem.cs b/Content.Server/HeightAdjust/BloodstreamAdjustSystem.cs new file mode 100644 index 0000000000..9ba0ee4b00 --- /dev/null +++ b/Content.Server/HeightAdjust/BloodstreamAdjustSystem.cs @@ -0,0 +1,49 @@ +using Content.Server.Body.Components; +using Content.Server.Body.Systems; +using Content.Server.Chemistry.Containers.EntitySystems; +using Content.Shared.CCVar; +using Content.Shared.Chemistry.Reagent; +using Content.Shared.Contests; +using Content.Shared.HeightAdjust; +using Robust.Shared.Configuration; + +namespace Content.Server.HeightAdjust; + +public sealed class BloodstreamAdjustSystem : EntitySystem +{ + [Dependency] private readonly BloodstreamSystem _bloodstream = default!; + [Dependency] private readonly IConfigurationManager _config = default!; + [Dependency] private readonly ContestsSystem _contests = default!; + [Dependency] private readonly SolutionContainerSystem _solutionContainer = default!; + + public override void Initialize() + { + SubscribeLocalEvent((uid, comp, _) => TryAdjustBloodstream((uid, comp))); + SubscribeLocalEvent((uid, comp, _) => TryAdjustBloodstream((uid, comp))); + } + + /// + /// Adjusts the bloodstream of the specified entity based on the settings provided by the component. + /// + public bool TryAdjustBloodstream(Entity ent) + { + if (!TryComp(ent, out var bloodstream) + || !_solutionContainer.TryGetSolution(ent.Owner, bloodstream.BloodSolutionName, out var bloodSolutionEnt) + || !_config.GetCVar(CCVars.HeightAdjustModifiesBloodstream)) + return false; + + var bloodSolution = bloodSolutionEnt.Value.Comp.Solution; + + var factor = Math.Pow(_contests.MassContest(ent, bypassClamp: true, rangeFactor: 4f), ent.Comp.Power); + factor = Math.Clamp(factor, ent.Comp.Min, ent.Comp.Max); + + var newVolume = bloodstream.BloodMaxVolume * factor; + var newBloodLevel = bloodSolution.FillFraction * newVolume; + bloodSolution.MaxVolume = newVolume; + bloodSolution.SetContents([new ReagentQuantity(bloodstream.BloodReagent, newBloodLevel, null)], false); + + _bloodstream.SetBloodMaxVolume(ent.Owner, newVolume, bloodstream); + + return true; + } +} diff --git a/Content.Server/HeightAdjust/BloodstreamAffectedByMassComponent.cs b/Content.Server/HeightAdjust/BloodstreamAffectedByMassComponent.cs new file mode 100644 index 0000000000..f6c3a0e250 --- /dev/null +++ b/Content.Server/HeightAdjust/BloodstreamAffectedByMassComponent.cs @@ -0,0 +1,26 @@ +using Content.Server.Body.Components; + +namespace Content.Server.HeightAdjust; + +/// +/// When applied to a humanoid or any mob, adjusts their blood level based on the mass contest between them +/// and an average humanoid. +///
+/// The formula for the resulting bloodstream volume is V = BloodMaxVolume * MassContest^Power +/// clamped between the specified Min and Max values. +///
+[RegisterComponent] +public sealed partial class BloodstreamAffectedByMassComponent : Component +{ + /// + /// Minimum and maximum resulting volume factors. A minimum value of 0.5 means that the resulting volume will be at least 50% of the original. + /// + [DataField] + public float Min = 1 / 3f, Max = 3f; + + /// + /// The power to which the outcome of the mass contest will be risen. + /// + [DataField] + public float Power = 1f; +} diff --git a/Content.Server/Humanoid/Components/RandomHumanoidSpawnerComponent.cs b/Content.Server/Humanoid/Components/RandomHumanoidSpawnerComponent.cs index b56664fe19..bb38e94e04 100644 --- a/Content.Server/Humanoid/Components/RandomHumanoidSpawnerComponent.cs +++ b/Content.Server/Humanoid/Components/RandomHumanoidSpawnerComponent.cs @@ -8,7 +8,7 @@ namespace Content.Server.Humanoid.Components; /// This is added to a marker entity in order to spawn a randomized /// humanoid ingame. ///
-[RegisterComponent] +[RegisterComponent, EntityCategory("Spawner")] public sealed partial class RandomHumanoidSpawnerComponent : Component { [DataField("settings", customTypeSerializer: typeof(PrototypeIdSerializer))] diff --git a/Content.Server/Humanoid/Systems/HumanoidAppearanceSystem.Modifier.cs b/Content.Server/Humanoid/Systems/HumanoidAppearanceSystem.Modifier.cs index 05a8b06222..06225c9d57 100644 --- a/Content.Server/Humanoid/Systems/HumanoidAppearanceSystem.Modifier.cs +++ b/Content.Server/Humanoid/Systems/HumanoidAppearanceSystem.Modifier.cs @@ -32,8 +32,8 @@ private void OnVerbsRequest(EntityUid uid, HumanoidAppearanceComponent component Icon = new SpriteSpecifier.Rsi(new ("/Textures/Mobs/Customization/reptilian_parts.rsi"), "tail_smooth"), Act = () => { - _uiSystem.TryOpen(uid, HumanoidMarkingModifierKey.Key, actor.PlayerSession); - _uiSystem.TrySetUiState( + _uiSystem.OpenUi(uid, HumanoidMarkingModifierKey.Key, actor.PlayerSession); + _uiSystem.SetUiState( uid, HumanoidMarkingModifierKey.Key, new HumanoidMarkingModifierState(component.MarkingSet, component.Species, @@ -48,8 +48,7 @@ private void OnVerbsRequest(EntityUid uid, HumanoidAppearanceComponent component private void OnBaseLayersSet(EntityUid uid, HumanoidAppearanceComponent component, HumanoidMarkingModifierBaseLayersSetMessage message) { - if (message.Session is not { } player - || !_adminManager.HasAdminFlag(player, AdminFlags.Fun)) + if (!_adminManager.HasAdminFlag(message.Actor, AdminFlags.Fun)) { return; } @@ -67,7 +66,7 @@ private void OnBaseLayersSet(EntityUid uid, HumanoidAppearanceComponent componen if (message.ResendState) { - _uiSystem.TrySetUiState( + _uiSystem.SetUiState( uid, HumanoidMarkingModifierKey.Key, new HumanoidMarkingModifierState(component.MarkingSet, component.Species, @@ -81,8 +80,7 @@ private void OnBaseLayersSet(EntityUid uid, HumanoidAppearanceComponent componen private void OnMarkingsSet(EntityUid uid, HumanoidAppearanceComponent component, HumanoidMarkingModifierMarkingSetMessage message) { - if (message.Session is not { } player - || !_adminManager.HasAdminFlag(player, AdminFlags.Fun)) + if (!_adminManager.HasAdminFlag(message.Actor, AdminFlags.Fun)) { return; } @@ -92,7 +90,7 @@ private void OnMarkingsSet(EntityUid uid, HumanoidAppearanceComponent component, if (message.ResendState) { - _uiSystem.TrySetUiState( + _uiSystem.SetUiState( uid, HumanoidMarkingModifierKey.Key, new HumanoidMarkingModifierState(component.MarkingSet, component.Species, diff --git a/Content.Server/IdentityManagement/IdentitySystem.cs b/Content.Server/IdentityManagement/IdentitySystem.cs index a959b96ef9..4766b89172 100644 --- a/Content.Server/IdentityManagement/IdentitySystem.cs +++ b/Content.Server/IdentityManagement/IdentitySystem.cs @@ -19,7 +19,7 @@ namespace Content.Server.IdentityManagement; /// /// Responsible for updating the identity of an entity on init or clothing equip/unequip. /// -public class IdentitySystem : SharedIdentitySystem +public sealed class IdentitySystem : SharedIdentitySystem { [Dependency] private readonly IdCardSystem _idCard = default!; [Dependency] private readonly IAdminLogManager _adminLog = default!; diff --git a/Content.Server/ImmovableRod/ImmovableRodComponent.cs b/Content.Server/ImmovableRod/ImmovableRodComponent.cs index 05fa3d9d9b..8b57a71696 100644 --- a/Content.Server/ImmovableRod/ImmovableRodComponent.cs +++ b/Content.Server/ImmovableRod/ImmovableRodComponent.cs @@ -42,7 +42,7 @@ public sealed partial class ImmovableRodComponent : Component /// If true, this will gib & delete bodies ///
[DataField] - public bool ShouldGib = true; + public bool ShouldGib; /// /// Damage done, if not gibbing diff --git a/Content.Server/ImmovableRod/ImmovableRodSystem.cs b/Content.Server/ImmovableRod/ImmovableRodSystem.cs index 4553dda095..f9873b0d6a 100644 --- a/Content.Server/ImmovableRod/ImmovableRodSystem.cs +++ b/Content.Server/ImmovableRod/ImmovableRodSystem.cs @@ -60,18 +60,21 @@ private void OnMapInit(EntityUid uid, ImmovableRodComponent component, MapInitEv _physics.SetFriction(uid, phys, 0f); _physics.SetBodyStatus(uid, phys, BodyStatus.InAir); - if (!component.RandomizeVelocity) - return; - var xform = Transform(uid); - var vel = component.DirectionOverride.Degrees switch + var (worldPos, worldRot) = _transform.GetWorldPositionRotation(uid); + var vel = worldRot.ToWorldVec() * component.MaxSpeed; + + if (component.RandomizeVelocity) { - 0f => _random.NextVector2(component.MinSpeed, component.MaxSpeed), - _ => _transform.GetWorldRotation(uid).RotateVec(component.DirectionOverride.ToVec()) * _random.NextFloat(component.MinSpeed, component.MaxSpeed) - }; + vel = component.DirectionOverride.Degrees switch + { + 0f => _random.NextVector2(component.MinSpeed, component.MaxSpeed), + _ => worldRot.RotateVec(component.DirectionOverride.ToVec()) * _random.NextFloat(component.MinSpeed, component.MaxSpeed) + }; + } _physics.ApplyLinearImpulse(uid, vel, body: phys); - xform.LocalRotation = (vel - _transform.GetWorldPosition(uid)).ToWorldAngle() + MathHelper.PiOver2; + xform.LocalRotation = (vel - worldPos).ToWorldAngle() + MathHelper.PiOver2; } } diff --git a/Content.Server/Instruments/InstrumentComponent.cs b/Content.Server/Instruments/InstrumentComponent.cs index 1b7913386d..db9dbb375b 100644 --- a/Content.Server/Instruments/InstrumentComponent.cs +++ b/Content.Server/Instruments/InstrumentComponent.cs @@ -1,6 +1,7 @@ using Content.Server.UserInterface; using Content.Shared.Instruments; using Robust.Shared.Player; +using ActivatableUIComponent = Content.Shared.UserInterface.ActivatableUIComponent; namespace Content.Server.Instruments; @@ -16,9 +17,9 @@ public sealed partial class InstrumentComponent : SharedInstrumentComponent [ViewVariables] public uint LastSequencerTick = 0; // TODO Instruments: Make this ECS - public ICommonSession? InstrumentPlayer => + public EntityUid? InstrumentPlayer => _entMan.GetComponentOrNull(Owner)?.CurrentSingleUser - ?? _entMan.GetComponentOrNull(Owner)?.PlayerSession; + ?? _entMan.GetComponentOrNull(Owner)?.PlayerSession.AttachedEntity; } [RegisterComponent] diff --git a/Content.Server/Instruments/InstrumentSystem.cs b/Content.Server/Instruments/InstrumentSystem.cs index 8dd9644e3c..f5a6713886 100644 --- a/Content.Server/Instruments/InstrumentSystem.cs +++ b/Content.Server/Instruments/InstrumentSystem.cs @@ -111,7 +111,7 @@ private void OnMidiStart(InstrumentStartMidiEvent msg, EntitySessionEventArgs ar if (!TryComp(uid, out InstrumentComponent? instrument)) return; - if (args.SenderSession != instrument.InstrumentPlayer) + if (args.SenderSession.AttachedEntity != instrument.InstrumentPlayer) return; instrument.Playing = true; @@ -125,7 +125,7 @@ private void OnMidiStop(InstrumentStopMidiEvent msg, EntitySessionEventArgs args if (!TryComp(uid, out InstrumentComponent? instrument)) return; - if (args.SenderSession != instrument.InstrumentPlayer) + if (args.SenderSession.AttachedEntity != instrument.InstrumentPlayer) return; Clean(uid, instrument); @@ -142,7 +142,7 @@ private void OnMidiSetMaster(InstrumentSetMasterEvent msg, EntitySessionEventArg if (!TryComp(uid, out InstrumentComponent? instrument)) return; - if (args.SenderSession != instrument.InstrumentPlayer) + if (args.SenderSession.AttachedEntity != instrument.InstrumentPlayer) return; if (master != null) @@ -174,7 +174,7 @@ private void OnMidiSetFilteredChannel(InstrumentSetFilteredChannelEvent msg, Ent if (!TryComp(uid, out InstrumentComponent? instrument)) return; - if (args.SenderSession != instrument.InstrumentPlayer) + if (args.SenderSession.AttachedEntity != instrument.InstrumentPlayer) return; if (msg.Channel == RobustMidiEvent.PercussionChannel && !instrument.AllowPercussion) @@ -194,8 +194,7 @@ private void OnMidiSetFilteredChannel(InstrumentSetFilteredChannelEvent msg, Ent private void OnBoundUIClosed(EntityUid uid, InstrumentComponent component, BoundUIClosedEvent args) { if (HasComp(uid) - && _bui.TryGetUi(uid, args.UiKey, out var bui) - && bui.SubscribedSessions.Count == 0) + && !_bui.IsUiOpen(uid, args.UiKey)) { RemComp(uid); } @@ -232,7 +231,7 @@ private void OnBoundUIRequestBands(EntityUid uid, InstrumentComponent component, var instrumentQuery = EntityManager.GetEntityQuery(); if (!TryComp(uid, out InstrumentComponent? originInstrument) - || originInstrument.InstrumentPlayer?.AttachedEntity is not {} originPlayer) + || originInstrument.InstrumentPlayer is not {} originPlayer) return Array.Empty<(NetEntity, string)>(); // It's probably faster to get all possible active instruments than all entities in range @@ -247,7 +246,7 @@ private void OnBoundUIRequestBands(EntityUid uid, InstrumentComponent component, continue; // We want to use the instrument player's name. - if (instrument.InstrumentPlayer?.AttachedEntity is not {} playerUid) + if (instrument.InstrumentPlayer is not {} playerUid) continue; // Maybe a bit expensive but oh well GetBands is queued and has a timer anyway. @@ -298,7 +297,7 @@ private void OnMidiEventRx(InstrumentMidiEventEvent msg, EntitySessionEventArgs return; if (!instrument.Playing - || args.SenderSession != instrument.InstrumentPlayer + || args.SenderSession.AttachedEntity != instrument.InstrumentPlayer || instrument.InstrumentPlayer == null || args.SenderSession.AttachedEntity is not { } attached) { @@ -374,8 +373,7 @@ public override void Update(float frameTime) var entity = GetEntity(request.Entity); var nearby = GetBands(entity); - _bui.TrySendUiMessage(entity, request.UiKey, new InstrumentBandResponseBuiMessage(nearby), - request.Session); + _bui.ServerSendUiMessage(entity, request.UiKey, new InstrumentBandResponseBuiMessage(nearby), request.Actor); } _bandRequestQueue.Clear(); @@ -413,7 +411,7 @@ public override void Update(float frameTime) (instrument.BatchesDropped >= MaxMidiBatchesDropped || instrument.LaggedBatches >= MaxMidiLaggedBatches)) { - if (instrument.InstrumentPlayer?.AttachedEntity is {Valid: true} mob) + if (instrument.InstrumentPlayer is {Valid: true} mob) { _stuns.TryParalyze(mob, TimeSpan.FromSeconds(1), true); @@ -423,7 +421,7 @@ public override void Update(float frameTime) // Just in case Clean(uid); - _bui.TryCloseAll(uid, InstrumentUiKey.Key); + _bui.CloseUi(uid, InstrumentUiKey.Key); } instrument.Timer += frameTime; @@ -437,13 +435,12 @@ public override void Update(float frameTime) } } - public void ToggleInstrumentUi(EntityUid uid, ICommonSession session, InstrumentComponent? component = null) + public void ToggleInstrumentUi(EntityUid uid, EntityUid actor, InstrumentComponent? component = null) { if (!Resolve(uid, ref component)) return; - if (_bui.TryGetUi(uid, InstrumentUiKey.Key, out var bui)) - _bui.ToggleUi(bui, session); + _bui.TryToggleUi(uid, InstrumentUiKey.Key, actor); } public override bool ResolveInstrument(EntityUid uid, ref SharedInstrumentComponent? component) diff --git a/Content.Server/Interaction/InteractionPopupSystem.cs b/Content.Server/Interaction/InteractionPopupSystem.cs index 77b76f898a..a028598df0 100644 --- a/Content.Server/Interaction/InteractionPopupSystem.cs +++ b/Content.Server/Interaction/InteractionPopupSystem.cs @@ -5,6 +5,7 @@ using Content.Shared.Interaction; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; +using Content.Shared.Mood; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Player; @@ -78,7 +79,19 @@ private void SharedInteract( if (_random.Prob(component.SuccessChance)) { if (component.InteractSuccessString != null) + { msg = Loc.GetString(component.InteractSuccessString, ("target", Identity.Entity(uid, EntityManager))); // Success message (localized). + if (component.InteractSuccessString == "hugging-success-generic") + { + var ev = new MoodEffectEvent("BeingHugged"); + RaiseLocalEvent(target, ev); + } + else if (component.InteractSuccessString.Contains("petting-success-")) + { + var ev = new MoodEffectEvent("PetAnimal"); + RaiseLocalEvent(user, ev); + } + } if (component.InteractSuccessSound != null) sfx = component.InteractSuccessSound; diff --git a/Content.Server/Interaction/InteractionSystem.cs b/Content.Server/Interaction/InteractionSystem.cs index 203781bcda..9ac82b2185 100644 --- a/Content.Server/Interaction/InteractionSystem.cs +++ b/Content.Server/Interaction/InteractionSystem.cs @@ -7,56 +7,6 @@ namespace Content.Server.Interaction { - /// - /// Governs interactions during clicking on entities - /// - [UsedImplicitly] - public sealed partial class InteractionSystem : SharedInteractionSystem - { - [Dependency] private readonly SharedContainerSystem _container = default!; - [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(HandleUserInterfaceRangeCheck); - } - - public override bool CanAccessViaStorage(EntityUid user, EntityUid target) - { - if (Deleted(target)) - return false; - - if (!_container.TryGetContainingContainer(target, out var container)) - return false; - - if (!TryComp(container.Owner, out StorageComponent? storage)) - return false; - - if (storage.Container?.ID != container.ID) - return false; - - if (!TryComp(user, out ActorComponent? actor)) - return false; - - // we don't check if the user can access the storage entity itself. This should be handed by the UI system. - return _uiSystem.SessionHasOpenUi(container.Owner, StorageComponent.StorageUiKey.Key, actor.PlayerSession); - } - - private void HandleUserInterfaceRangeCheck(ref BoundUserInterfaceCheckRangeEvent ev) - { - if (ev.Player.AttachedEntity is not { } user || ev.Result == BoundUserInterfaceRangeResult.Fail) - return; - - if (InRangeUnobstructed(user, ev.Target, ev.UserInterface.InteractionRange)) - { - ev.Result = BoundUserInterfaceRangeResult.Pass; - } - else - { - ev.Result = BoundUserInterfaceRangeResult.Fail; - } - } - } + // TODO Remove Shared prefix + public sealed class InteractionSystem : SharedInteractionSystem; } diff --git a/Content.Server/InteractionVerbs/Actions/ChangeStandingStateAction.cs b/Content.Server/InteractionVerbs/Actions/ChangeStandingStateAction.cs new file mode 100644 index 0000000000..59340df58f --- /dev/null +++ b/Content.Server/InteractionVerbs/Actions/ChangeStandingStateAction.cs @@ -0,0 +1,40 @@ +using Content.Shared.InteractionVerbs; +using Content.Shared.Standing; + +namespace Content.Server.InteractionVerbs.Actions; + +[Serializable] +public sealed partial class ChangeStandingStateAction : InteractionAction +{ + [DataField] + public bool MakeStanding, MakeLaying; + + public override bool CanPerform(InteractionArgs args, InteractionVerbPrototype proto, bool isBefore, VerbDependencies deps) + { + if (!deps.EntMan.TryGetComponent(args.Target, out var state)) + return false; + + if (isBefore) + args.Blackboard["standing"] = state.CurrentState; + + return state.CurrentState == StandingState.Standing && MakeLaying + || state.CurrentState == StandingState.Lying && MakeStanding; + } + + public override bool Perform(InteractionArgs args, InteractionVerbPrototype proto, VerbDependencies deps) + { + var stateSystem = deps.EntMan.System(); + + if (!deps.EntMan.TryGetComponent(args.Target, out var state) + || args.TryGetBlackboard("standing", out StandingState oldStanding) && oldStanding != state.CurrentState) + return false; + + // Note: these will get cancelled if the target is forced to stand/lay, e.g. due to a buckle or stun or something else. + if (state.CurrentState == StandingState.Lying && MakeStanding) + return stateSystem.Stand(args.Target); + else if (state.CurrentState == StandingState.Standing && MakeLaying) + return stateSystem.Down(args.Target, setDrawDepth: true); + + return false; + } +} diff --git a/Content.Server/InteractionVerbs/Actions/ChatMessageAction.cs b/Content.Server/InteractionVerbs/Actions/ChatMessageAction.cs new file mode 100644 index 0000000000..4c0aaaee97 --- /dev/null +++ b/Content.Server/InteractionVerbs/Actions/ChatMessageAction.cs @@ -0,0 +1,62 @@ +using Content.Server.Chat.Systems; +using Content.Shared.ActionBlocker; +using Content.Shared.Chat; +using Content.Shared.InteractionVerbs; +using Robust.Shared.Serialization; + +namespace Content.Server.InteractionVerbs.Actions; + +/// +/// Makes the target or the user to send a chat message.

+/// +/// Messages are locale-based, their keys follow the form of "interaction-[verb id]-[message loc prefix]-[index]". +/// The index parameter is a random integer from 1 to .

+/// +/// Similarly to interaction verb locales, {$user}, {$target} amd {$used} arguments are passed to the locales retrieved by this action. +///
+[Serializable] +public sealed partial class ChatMessageAction : InteractionAction +{ + [DataField] + public string MessageLocPrefix = "message"; + + /// + /// Number of messages in the dataset. + /// + [DataField] + public int NumMessages = 1; + + [DataField] + public InGameICChatType ChatType = InGameICChatType.Speak; + + /// + /// If true, makes the target speak. Otherwise, makes the user speak. + /// + [DataField] + public bool TargetIsSource = true; + + private EntityUid GetSpeaker(InteractionArgs args) => TargetIsSource ? args.Target : args.User; + + public override bool CanPerform(InteractionArgs args, InteractionVerbPrototype proto, bool beforeDelay, VerbDependencies deps) + { + return deps.EntMan.System().CanSpeak(GetSpeaker(args)); + } + + public override bool Perform(InteractionArgs args, InteractionVerbPrototype proto, VerbDependencies deps) + { + var index = NumMessages <= 1 ? 1 : deps.Random.Next(1, NumMessages + 1); + var locString = $"interaction-{proto.ID}-{MessageLocPrefix}-{index}"; + + var used = args.Used ?? EntityUid.Invalid; + if (!Loc.TryGetString(locString, out var message, ("user", args.User), ("target", args.Target), ("used", used))) + { + Logger.GetSawmill("action.chat_message").Error($"No chat message found for interaction {proto.ID}! Loc string: {locString}."); + return false; + } + + var speaker = GetSpeaker(args); + deps.EntMan.System().TrySendInGameICMessage(speaker, message, ChatType, false); + + return true; + } +} diff --git a/Content.Server/InteractionVerbs/Actions/ComplexAction.cs b/Content.Server/InteractionVerbs/Actions/ComplexAction.cs new file mode 100644 index 0000000000..5e1fb89eab --- /dev/null +++ b/Content.Server/InteractionVerbs/Actions/ComplexAction.cs @@ -0,0 +1,63 @@ +using System.Linq; +using Content.Shared.InteractionVerbs; +using Robust.Shared.Serialization; + +namespace Content.Server.InteractionVerbs.Actions; + +/// +/// An action that combines multiple other actions. +/// +[Serializable] +public sealed partial class ComplexAction : InteractionAction +{ + [DataField] + public List Actions = new(); + + /// + /// If true, all actions must pass the IsAllowed and CanPerform checks, + /// and all must successfully perform for this action to succeed (boolean and). + /// Otherwise, at least one must pass the checks and successfully perform (boolean or). + /// + /// If this is false, all actions will be performed if at least one of their CanPerform checks succeeds. + [DataField] + public bool RequireAll = false; + + /// + /// If true, when it comes to execution of this action, the entire action will exit early if:
+ /// * The action has RequireAll = false and at least one action succeeds;
+ /// * Or if the action has RequireAll = true and at least one action fails. + ///
+ [DataField] + public bool Lazy = false; + + private bool Delegate(Func delegatedAction) + { + if (Lazy) + return RequireAll ? Actions.All(delegatedAction) : Actions.Any(delegatedAction); + + var result = RequireAll; + if (RequireAll) + foreach (var action in Actions) + result &= delegatedAction(action); + else + foreach (var action in Actions) + result |= delegatedAction(action); + + return result; + } + + public override bool IsAllowed(InteractionArgs args, InteractionVerbPrototype proto, VerbDependencies deps) + { + return Delegate(act => act.IsAllowed(args, proto, deps)); + } + + public override bool CanPerform(InteractionArgs args, InteractionVerbPrototype proto, bool beforeDelay, VerbDependencies deps) + { + return Delegate(act => act.CanPerform(args, proto, beforeDelay, deps)); + } + + public override bool Perform(InteractionArgs args, InteractionVerbPrototype proto, VerbDependencies deps) + { + return Delegate(act => act.Perform(args, proto, deps)); + } +} diff --git a/Content.Server/InteractionVerbs/Actions/ConditionalAction.cs b/Content.Server/InteractionVerbs/Actions/ConditionalAction.cs new file mode 100644 index 0000000000..d274828d1e --- /dev/null +++ b/Content.Server/InteractionVerbs/Actions/ConditionalAction.cs @@ -0,0 +1,64 @@ +using Content.Shared.InteractionVerbs; +using Robust.Shared.Serialization; + +namespace Content.Server.InteractionVerbs.Actions; + +/// +/// An action that performs one of the two (or just one) actions based on a condition. +/// +[Serializable] +public sealed partial class ConditionalAction : InteractionAction +{ + [DataField(required: true)] + public InteractionRequirement Condition; + + [DataField("true")] + public InteractionAction? TrueBranch; + + [DataField("false")] + public InteractionAction? FalseBranch; + + /// + /// If true, CanPerform and Perform will fail when the condition results in a null branch. + /// Otherwise, null branch is equivalent to a no-op action. + /// + [DataField("failWhenNull")] + public bool FailWhenNoBranch = false; + + /// + /// If true, the IsValid check will be delegated to the respective branch. + /// If the respective branch is null, the decision will be made based on + /// + [DataField("delegateValid")] + public bool DelegateValidation = false; + + /// + /// If true, the CanPerform check will be performed before the do-after, interrupting the verb early. + /// + [DataField] + public bool BeforeDelay = false; + + public override bool IsAllowed(InteractionArgs args, InteractionVerbPrototype proto, VerbDependencies deps) + { + if (!DelegateValidation) + return true; + + var branch = Condition.IsMet(args, proto, deps) ? TrueBranch : FalseBranch; + return branch?.IsAllowed(args, proto, deps) ?? !FailWhenNoBranch; + } + + public override bool CanPerform(InteractionArgs args, InteractionVerbPrototype proto, bool beforeDelay, VerbDependencies deps) + { + if (beforeDelay && !BeforeDelay) + return true; + + var branch = Condition.IsMet(args, proto, deps) ? TrueBranch : FalseBranch; + return branch?.CanPerform(args, proto, beforeDelay, deps) ?? !FailWhenNoBranch; + } + + public override bool Perform(InteractionArgs args, InteractionVerbPrototype proto, VerbDependencies deps) + { + var branch = Condition.IsMet(args, proto, deps) ? TrueBranch : FalseBranch; + return branch?.Perform(args, proto, deps) ?? !FailWhenNoBranch; + } +} diff --git a/Content.Server/InteractionVerbs/Actions/JitterAction.cs b/Content.Server/InteractionVerbs/Actions/JitterAction.cs new file mode 100644 index 0000000000..63a55652df --- /dev/null +++ b/Content.Server/InteractionVerbs/Actions/JitterAction.cs @@ -0,0 +1,28 @@ +using Content.Shared.InteractionVerbs; +using Content.Shared.Jittering; + +namespace Content.Server.InteractionVerbs.Actions; + +[Serializable] +public sealed partial class JitterAction : InteractionAction +{ + [DataField] + public TimeSpan Time = TimeSpan.FromSeconds(1); + + [DataField] + public float Amplitude = 10f, Frequency = 4f; + + [DataField] + public bool Refresh = false; + + public override bool CanPerform(InteractionArgs args, InteractionVerbPrototype proto, bool beforeDelay, VerbDependencies deps) + { + return true; + } + + public override bool Perform(InteractionArgs args, InteractionVerbPrototype proto, VerbDependencies deps) + { + deps.EntMan.System().DoJitter(args.Target, Time, Refresh, Amplitude, Frequency); + return true; + } +} diff --git a/Content.Server/InteractionVerbs/Actions/ModifyHealthAction.cs b/Content.Server/InteractionVerbs/Actions/ModifyHealthAction.cs new file mode 100644 index 0000000000..b90492da6f --- /dev/null +++ b/Content.Server/InteractionVerbs/Actions/ModifyHealthAction.cs @@ -0,0 +1,29 @@ +using Content.Shared.Damage; +using Content.Shared.InteractionVerbs; +using Robust.Shared.Serialization; + +namespace Content.Server.InteractionVerbs.Actions; + +[Serializable] +public sealed partial class ModifyHealthAction : InteractionAction +{ + [DataField(required: true)] public DamageSpecifier Damage = default!; + [DataField] public bool IgnoreResistance = false; + + public override bool IsAllowed(InteractionArgs args, InteractionVerbPrototype proto, VerbDependencies deps) + { + return deps.EntMan.HasComponent(args.Target); + } + + public override bool CanPerform(InteractionArgs args, InteractionVerbPrototype proto, bool beforeDelay, VerbDependencies deps) + { + // TODO: check if container supports this kind of damage? + return true; + } + + public override bool Perform(InteractionArgs args, InteractionVerbPrototype proto, VerbDependencies deps) + { + return deps.EntMan.System() + .TryChangeDamage(args.Target, Damage, IgnoreResistance, origin: args.User) is not null; + } +} diff --git a/Content.Server/InteractionVerbs/Actions/ModifyStatusEffectAction.cs b/Content.Server/InteractionVerbs/Actions/ModifyStatusEffectAction.cs new file mode 100644 index 0000000000..23a5f92c3e --- /dev/null +++ b/Content.Server/InteractionVerbs/Actions/ModifyStatusEffectAction.cs @@ -0,0 +1,47 @@ +using Content.Shared.InteractionVerbs; +using Content.Shared.StatusEffect; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; + +namespace Content.Server.InteractionVerbs.Actions; + +[Serializable] +public sealed partial class ModifyStatusEffectAction : InteractionAction +{ + [DataField(required: true)] + public ProtoId Effect; + + /// + /// If true, the action will ensure that the system already has the status effect when removing time, + /// or will ensure the entity gets the status effect when adding it. + /// + [DataField] + public bool EnsureEffect = true; + + /// + /// Amount of time added by this action. Can be negative, but then should be false. + /// + [DataField] + public TimeSpan TimeAdded = TimeSpan.FromSeconds(1); + + public override bool CanPerform(InteractionArgs args, InteractionVerbPrototype proto, bool isBefore, VerbDependencies deps) + { + var statusEffects = deps.EntMan.System(); + if (!statusEffects.CanApplyEffect(args.Target, Effect)) + return false; + + return !EnsureEffect || TimeAdded >= TimeSpan.Zero || statusEffects.HasStatusEffect(args.Target, Effect); + } + + public override bool Perform(InteractionArgs args, InteractionVerbPrototype proto, VerbDependencies deps) + { + var statusEffects = deps.EntMan.System(); + + if (statusEffects.HasStatusEffect(args.Target, Effect)) + return statusEffects.TryAddTime(args.Target, Effect, TimeAdded); + else if (EnsureEffect) + return statusEffects.TryAddStatusEffect(args.Target, Effect, TimeAdded, true); + + return false; + } +} diff --git a/Content.Server/InteractionVerbs/Actions/MoodAction.cs b/Content.Server/InteractionVerbs/Actions/MoodAction.cs new file mode 100644 index 0000000000..5b53c6ca18 --- /dev/null +++ b/Content.Server/InteractionVerbs/Actions/MoodAction.cs @@ -0,0 +1,42 @@ +using Content.Shared.InteractionVerbs; +using Content.Shared.Mood; +using Robust.Shared.Prototypes; + +namespace Content.Server.InteractionVerbs.Actions; + +/// +/// An action that adds a moodlet to the target, or removes one. +/// +[Serializable] +public sealed partial class MoodAction : InteractionAction +{ + [DataField(required: true)] + public ProtoId Effect; + + /// + /// Parameters for the . Only used if is false. + /// + [DataField] + public float Modifier = 1f, Offset = 0f; + + /// + /// If true, the moodlet will be removed. Otherwise, it will be added. + /// + [DataField] + public bool Remove = false; + + public override bool CanPerform(InteractionArgs args, InteractionVerbPrototype proto, bool isBefore, VerbDependencies deps) + { + return true; + } + + public override bool Perform(InteractionArgs args, InteractionVerbPrototype proto, VerbDependencies deps) + { + if (Remove) + deps.EntMan.EventBus.RaiseLocalEvent(args.Target, new MoodRemoveEffectEvent(Effect)); + else + deps.EntMan.EventBus.RaiseLocalEvent(args.Target, new MoodEffectEvent(Effect, Modifier, Offset)); + + return true; // Mood system is shitcode so we can't even know if the effect was added or anything + } +} diff --git a/Content.Server/InteractionVerbs/Actions/NoOpAction.cs b/Content.Server/InteractionVerbs/Actions/NoOpAction.cs new file mode 100644 index 0000000000..3d999f9ab0 --- /dev/null +++ b/Content.Server/InteractionVerbs/Actions/NoOpAction.cs @@ -0,0 +1,28 @@ +using Content.Shared.InteractionVerbs; +using Robust.Shared.Random; + +namespace Content.Server.InteractionVerbs.Actions; + +/// +/// An action that does nothing on its own, made just to mimic the old "chance to show a popup" interactions. +/// +[Serializable] +public sealed partial class NoOpAction : InteractionAction +{ + [DataField] + public float SuccessChance = 1f; + + public override bool CanPerform(InteractionArgs args, InteractionVerbPrototype proto, bool isBefore, VerbDependencies deps) + { + if (isBefore) + return true; // so the do-after can happen if there's one + + // Return true if chance >= 1f, false if <= 0f, or a random result if anywhere in-between. + return SuccessChance > 0f && (SuccessChance >= 1f || deps.Random.Prob(SuccessChance)); + } + + public override bool Perform(InteractionArgs args, InteractionVerbPrototype proto, VerbDependencies deps) + { + return true; + } +} diff --git a/Content.Server/InteractionVerbs/Actions/OnUserAction.cs b/Content.Server/InteractionVerbs/Actions/OnUserAction.cs new file mode 100644 index 0000000000..99015ec0f9 --- /dev/null +++ b/Content.Server/InteractionVerbs/Actions/OnUserAction.cs @@ -0,0 +1,38 @@ +using Content.Shared.InteractionVerbs; + +namespace Content.Server.InteractionVerbs.Actions; + +/// +/// A special proxy action that swaps the target and the user for the proxied action. +/// This effectively means that in most cases the proxied action will be applied to the user even if it's meant for target. +/// +[Serializable] +public sealed partial class OnUserAction : InteractionAction +{ + [DataField(required: true)] + public InteractionAction Action = default!; + + private InteractionArgs Swap(InteractionArgs args) + { + return new InteractionArgs(args) + { + Target = args.User, + User = args.Target + }; + } + + public override bool IsAllowed(InteractionArgs args, InteractionVerbPrototype proto, VerbDependencies deps) + { + return Action.IsAllowed(Swap(args), proto, deps); + } + + public override bool CanPerform(InteractionArgs args, InteractionVerbPrototype proto, bool beforeDelay, VerbDependencies deps) + { + return Action.CanPerform(Swap(args), proto, beforeDelay, deps); + } + + public override bool Perform(InteractionArgs args, InteractionVerbPrototype proto, VerbDependencies deps) + { + return Action.Perform(Swap(args), proto, deps); + } +} diff --git a/Content.Server/InteractionVerbs/Actions/RaiseEventAction.cs b/Content.Server/InteractionVerbs/Actions/RaiseEventAction.cs new file mode 100644 index 0000000000..68b59bc5cc --- /dev/null +++ b/Content.Server/InteractionVerbs/Actions/RaiseEventAction.cs @@ -0,0 +1,50 @@ +using Content.Shared.InteractionVerbs; + +namespace Content.Server.InteractionVerbs.Actions; + +/// +/// An action that raises an event on the target or the user. Made for interop with systems that rely on events. +/// +[Serializable] +public sealed partial class RaiseEventAction : InteractionAction +{ + /// + /// The event to raise. Must be serializable because it will be copied before being raised. + /// + /// + /// If this is a handled event, the result of the action is whether the event was handled. + /// Likewise, if it's cancellable, the result is whether it was not cancelled. + /// + [DataField("event", required: true)] + public EntityEventArgs? EventData; + + /// + /// If true, the event will be raised on the user. Otherwise, it will be raised on the target. + /// + [DataField] + public bool OnUser = false; + + [DataField] + public bool Broadcast = false; + + public override bool CanPerform(InteractionArgs args, InteractionVerbPrototype proto, bool beforeDelay, VerbDependencies deps) + { + return true; + } + + public override bool Perform(InteractionArgs args, InteractionVerbPrototype proto, VerbDependencies deps) + { + if (EventData is null) + return false; + + var ev = deps.Serialization.CreateCopy(EventData, notNullableOverride: true); + deps.EntMan.EventBus.RaiseLocalEvent(OnUser ? args.User : args.Target, ev, Broadcast); + + if (ev is HandledEntityEventArgs handled) + return handled.Handled; + if (ev is CancellableEntityEventArgs cancellable) + return !cancellable.Cancelled; + + return true; + } +} diff --git a/Content.Server/InteractionVerbs/Actions/ToggleSleepingAction.cs b/Content.Server/InteractionVerbs/Actions/ToggleSleepingAction.cs new file mode 100644 index 0000000000..588853a8d4 --- /dev/null +++ b/Content.Server/InteractionVerbs/Actions/ToggleSleepingAction.cs @@ -0,0 +1,44 @@ +using Content.Server.Bed.Sleep; +using Content.Shared.Bed.Sleep; +using Content.Shared.InteractionVerbs; +using Content.Shared.Mobs.Components; + +namespace Content.Server.InteractionVerbs.Actions; + +[Serializable] +public sealed partial class ToggleSleepingAction : InteractionAction +{ + [DataField] + public bool WakeUp = false, Sleep = false; + + public override bool IsAllowed(InteractionArgs args, InteractionVerbPrototype proto, VerbDependencies deps) + { + var isSleeping = deps.EntMan.HasComponent(args.Target); + if (!isSleeping) + return Sleep && deps.EntMan.HasComponent(args.Target); // Non-mobs cannot sleep + + return WakeUp; + } + + public override bool CanPerform(InteractionArgs args, InteractionVerbPrototype proto, bool isBefore, VerbDependencies deps) + { + if (isBefore) + args.Blackboard["sleeping"] = deps.EntMan.HasComponent(args.Target); + + return true; // We already checked the rest in IsAllowed + } + + public override bool Perform(InteractionArgs args, InteractionVerbPrototype proto, VerbDependencies deps) + { + var isSleeping = deps.EntMan.HasComponent(args.Target); + if (args.TryGetBlackboard("sleeping", out bool wasSleeping) && wasSleeping != isSleeping) + return false; // The target woke up/went to sleep during the do-after - sus + + if (isSleeping && WakeUp) + return deps.EntMan.System().TryWaking(args.Target, user: args.User); + else if (Sleep) + return deps.EntMan.System().TrySleeping(args.Target); + + return false; + } +} diff --git a/Content.Server/InteractionVerbs/InteractionVerbsSystem.cs b/Content.Server/InteractionVerbs/InteractionVerbsSystem.cs new file mode 100644 index 0000000000..ba51bbe39c --- /dev/null +++ b/Content.Server/InteractionVerbs/InteractionVerbsSystem.cs @@ -0,0 +1,61 @@ +using System.Linq; +using Content.Server.Chat.Managers; +using Content.Shared.Interaction; +using Content.Shared.InteractionVerbs; +using Content.Shared.Physics; +using Content.Shared.Popups; +using Robust.Shared.Player; + +namespace Content.Server.InteractionVerbs; + +public sealed class InteractionVerbsSystem : SharedInteractionVerbsSystem +{ + [Dependency] private readonly IChatManager _chatManager = default!; + [Dependency] private readonly SharedInteractionSystem _interactions = default!; + + private EntityQuery _occluderQuery; + + public override void Initialize() + { + base.Initialize(); + _occluderQuery = GetEntityQuery(); + } + + protected override void SendChatLog(string message, EntityUid source, Filter filter, InteractionPopupPrototype popup, bool clip) + { + if (filter.Count <= 0) + return; + + var color = popup.LogColor ?? InferColor(popup.PopupType); + var wrappedMessage = message; // TODO: custom chat wraps maybe? + + // Exclude entities who cannot directly see the target of the popup. TODO this may have a high performance cost - although whispers do the same. + // We only do this if the popup has to be logged into chat since that has some gameplay implications. + if (clip && popup.DoClipping) + filter.RemoveWhereAttachedEntity(ent => !CanSee(ent, source, popup.VisibilityRange)); + + if (filter.Count == 1) + _chatManager.ChatMessageToOne(popup.LogChannel, message, wrappedMessage, source, false, filter.Recipients.First().Channel, color); + else + _chatManager.ChatMessageToManyFiltered(filter, popup.LogChannel, message, wrappedMessage, source, false, false, color); + } + + private Color InferColor(PopupType popup) => popup switch + { + // These are all hardcoded on client-side, so we have to improvise + PopupType.LargeCaution or PopupType.MediumCaution or PopupType.SmallCaution => Color.Red, + PopupType.Medium or PopupType.Small => Color.LightGray, + _ => Color.White + }; + + private bool CanSee(EntityUid source, EntityUid target, float maxRange) + { + // TODO: InRangeUnobstructed has a pretty high performance cost and is not intended to be used like that. + // We should see if we can move this to client side later, aka make the client check if the target is visible for it. + return _interactions.InRangeUnobstructed( + source, target, maxRange, + CollisionGroup.Opaque, + uid => !_occluderQuery.TryComp(uid, out var occluder) || !occluder.Enabled, // We ignore all entities that do not occlude light + false); + } +} diff --git a/Content.Server/Inventory/ServerInventorySystem.cs b/Content.Server/Inventory/ServerInventorySystem.cs index 29d39f3723..e3783753bb 100644 --- a/Content.Server/Inventory/ServerInventorySystem.cs +++ b/Content.Server/Inventory/ServerInventorySystem.cs @@ -1,21 +1,15 @@ -using Content.Server.Storage.EntitySystems; using Content.Shared.Explosion; using Content.Shared.Inventory; -using Content.Shared.Inventory.Events; -using Content.Shared.Storage; namespace Content.Server.Inventory { public sealed class ServerInventorySystem : InventorySystem { - [Dependency] private readonly StorageSystem _storageSystem = default!; - public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnExploded); - SubscribeNetworkEvent(OnOpenSlotStorage); } private void OnExploded(Entity ent, ref BeforeExplodeEvent args) @@ -29,17 +23,6 @@ private void OnExploded(Entity ent, ref BeforeExplodeEvent a } } - private void OnOpenSlotStorage(OpenSlotStorageNetworkMessage ev, EntitySessionEventArgs args) - { - if (args.SenderSession.AttachedEntity is not { Valid: true } uid) - return; - - if (TryGetSlotEntity(uid, ev.Slot, out var entityUid) && TryComp(entityUid, out var storageComponent)) - { - _storageSystem.OpenStorageUI(entityUid.Value, uid, storageComponent); - } - } - public void TransferEntityInventories(Entity source, Entity target) { if (!Resolve(source.Owner, ref source.Comp) || !Resolve(target.Owner, ref target.Comp)) diff --git a/Content.Server/IoC/ServerContentIoC.cs b/Content.Server/IoC/ServerContentIoC.cs index fa263e059d..7c15014133 100644 --- a/Content.Server/IoC/ServerContentIoC.cs +++ b/Content.Server/IoC/ServerContentIoC.cs @@ -5,14 +5,15 @@ using Content.Server.Afk; using Content.Server.Chat.Managers; using Content.Server.Connection; -using Content.Server.DiscordAuth; using Content.Server.JoinQueue; using Content.Server.Database; using Content.Server.Discord; +using Content.Server.DiscordAuth; using Content.Server.EUI; using Content.Server.GhostKick; using Content.Server.Info; using Content.Server.Maps; +using Content.Server.Players.JobWhitelist; using Content.Server.MoMMI; using Content.Server.NodeContainer.NodeGroups; using Content.Server.Players.PlayTimeTracking; @@ -61,6 +62,7 @@ public static void Register() IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); + IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); diff --git a/Content.Server/Jobs/AddComponentSpecial.cs b/Content.Server/Jobs/AddComponentSpecial.cs index c57d734354..3264c54cd5 100644 --- a/Content.Server/Jobs/AddComponentSpecial.cs +++ b/Content.Server/Jobs/AddComponentSpecial.cs @@ -8,8 +8,7 @@ namespace Content.Server.Jobs [UsedImplicitly] public sealed partial class AddComponentSpecial : JobSpecial { - [DataField("components")] - [AlwaysPushInheritance] + [DataField, AlwaysPushInheritance] public ComponentRegistry Components { get; private set; } = new(); public override void AfterEquip(EntityUid mob) diff --git a/Content.Server/Kitchen/Components/MicrowaveComponent.cs b/Content.Server/Kitchen/Components/MicrowaveComponent.cs index 815ba8f521..1e343e5e33 100644 --- a/Content.Server/Kitchen/Components/MicrowaveComponent.cs +++ b/Content.Server/Kitchen/Components/MicrowaveComponent.cs @@ -11,95 +11,99 @@ namespace Content.Server.Kitchen.Components [RegisterComponent] public sealed partial class MicrowaveComponent : Component { - [DataField("cookTimeMultiplier"), ViewVariables(VVAccess.ReadWrite)] + [DataField] public float CookTimeMultiplier = 1; - - [DataField("baseHeatMultiplier"), ViewVariables(VVAccess.ReadWrite)] + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string MachinePartCookTimeMultiplier = "Capacitor"; + [DataField] + public float CookTimeScalingConstant = 0.5f; + [DataField] public float BaseHeatMultiplier = 100; - [DataField("objectHeatMultiplier"), ViewVariables(VVAccess.ReadWrite)] + [DataField] public float ObjectHeatMultiplier = 100; - [DataField("failureResult", customTypeSerializer: typeof(PrototypeIdSerializer))] + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] public string BadRecipeEntityId = "FoodBadRecipe"; #region audio - [DataField("beginCookingSound")] + [DataField] public SoundSpecifier StartCookingSound = new SoundPathSpecifier("/Audio/Machines/microwave_start_beep.ogg"); - [DataField("foodDoneSound")] + [DataField] public SoundSpecifier FoodDoneSound = new SoundPathSpecifier("/Audio/Machines/microwave_done_beep.ogg"); - [DataField("clickSound")] + [DataField] public SoundSpecifier ClickSound = new SoundPathSpecifier("/Audio/Machines/machine_switch.ogg"); - [DataField("ItemBreakSound")] + [DataField] public SoundSpecifier ItemBreakSound = new SoundPathSpecifier("/Audio/Effects/clang.ogg"); public EntityUid? PlayingStream; - [DataField("loopingSound")] + [DataField] public SoundSpecifier LoopingSound = new SoundPathSpecifier("/Audio/Machines/microwave_loop.ogg"); #endregion [ViewVariables] public bool Broken; - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public ProtoId OnPort = "On"; /// - /// This is a fixed offset of 5. - /// The cook times for all recipes should be divisible by 5,with a minimum of 1 second. - /// For right now, I don't think any recipe cook time should be greater than 60 seconds. + /// This is a fixed offset of 5. + /// The cook times for all recipes should be divisible by 5,with a minimum of 1 second. + /// For right now, I don't think any recipe cook time should be greater than 60 seconds. /// - [DataField("currentCookTimerTime"), ViewVariables(VVAccess.ReadWrite)] + [DataField] public uint CurrentCookTimerTime = 0; /// - /// Tracks the elapsed time of the current cook timer. + /// Tracks the elapsed time of the current cook timer. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public TimeSpan CurrentCookTimeEnd = TimeSpan.Zero; /// - /// The maximum number of seconds a microwave can be set to. - /// This is currently only used for validation and the client does not check this. + /// The maximum number of seconds a microwave can be set to. + /// This is currently only used for validation and the client does not check this. /// - [DataField("maxCookTime"), ViewVariables(VVAccess.ReadWrite)] + [DataField] public uint MaxCookTime = 30; /// /// The max temperature that this microwave can heat objects to. /// - [DataField("temperatureUpperThreshold")] + [DataField] public float TemperatureUpperThreshold = 373.15f; public int CurrentCookTimeButtonIndex; public Container Storage = default!; - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public int Capacity = 10; - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public ProtoId MaxItemSize = "Normal"; /// - /// How frequently the microwave can malfunction. + /// How frequently the microwave can malfunction. /// [DataField] public float MalfunctionInterval = 1.0f; /// - /// Chance of an explosion occurring when we microwave a metallic object + /// Chance of an explosion occurring when we microwave a metallic object /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public float ExplosionChance = .1f; /// - /// Chance of lightning occurring when we microwave a metallic object - [DataField, ViewVariables(VVAccess.ReadWrite)] + /// Chance of lightning occurring when we microwave a metallic object + /// + [DataField] public float LightningChance = .75f; } diff --git a/Content.Server/Kitchen/Components/ReagentGrinderComponent.cs b/Content.Server/Kitchen/Components/ReagentGrinderComponent.cs index 5bbbe2dc8d..4f4531206c 100644 --- a/Content.Server/Kitchen/Components/ReagentGrinderComponent.cs +++ b/Content.Server/Kitchen/Components/ReagentGrinderComponent.cs @@ -1,6 +1,8 @@ using Content.Shared.Kitchen; using Content.Server.Kitchen.EntitySystems; +using Content.Shared.Construction.Prototypes; using Robust.Shared.Audio; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; namespace Content.Server.Kitchen.Components { @@ -13,15 +15,30 @@ namespace Content.Server.Kitchen.Components [Access(typeof(ReagentGrinderSystem)), RegisterComponent] public sealed partial class ReagentGrinderComponent : Component { - [DataField] + [ViewVariables(VVAccess.ReadWrite)] public int StorageMaxEntities = 6; [DataField] - public TimeSpan WorkTime = TimeSpan.FromSeconds(3.5); // Roughly matches the grind/juice sounds. + public int BaseStorageMaxEntities = 4; + + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string MachinePartStorageMax = "MatterBin"; + + [DataField] + public int StoragePerPartRating = 4; [DataField] + public TimeSpan WorkTime = TimeSpan.FromSeconds(3.5); // Roughly matches the grind/juice sounds. + + [ViewVariables(VVAccess.ReadWrite)] public float WorkTimeMultiplier = 1; + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string MachinePartWorkTime = "Manipulator"; + + [DataField] + public float PartRatingWorkTimerMulitplier = 0.6f; + [DataField] public SoundSpecifier ClickSound { get; set; } = new SoundPathSpecifier("/Audio/Machines/machine_switch.ogg"); diff --git a/Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs b/Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs index 212383c463..f9e1ae2241 100644 --- a/Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs +++ b/Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs @@ -76,10 +76,12 @@ public override void Initialize() SubscribeLocalEvent(OnPowerChanged); SubscribeLocalEvent(OnAnchorChanged); SubscribeLocalEvent(OnSuicide); + SubscribeLocalEvent(OnRefreshParts); + SubscribeLocalEvent(OnUpgradeExamine); SubscribeLocalEvent(OnSignalReceived); - SubscribeLocalEvent((u, c, m) => Wzhzhzh(u, c, m.Session.AttachedEntity)); + SubscribeLocalEvent((u, c, m) => Wzhzhzh(u, c, m.Actor)); SubscribeLocalEvent(OnEjectMessage); SubscribeLocalEvent(OnEjectIndex); SubscribeLocalEvent(OnSelectTime); @@ -271,6 +273,9 @@ private void OnSolutionChange(Entity ent, ref SolutionContai private void OnContentUpdate(EntityUid uid, MicrowaveComponent component, ContainerModifiedMessage args) // For some reason ContainerModifiedMessage just can't be used at all with Entity. TODO: replace with Entity syntax once that's possible { + if (component.Storage != args.Container) + return; + UpdateUserInterfaceState(uid, component); } @@ -342,6 +347,17 @@ private void OnAnchorChanged(EntityUid uid, MicrowaveComponent component, ref An _container.EmptyContainer(component.Storage); } + private void OnRefreshParts(Entity ent, ref RefreshPartsEvent args) + { + var cookRating = args.PartRatings[ent.Comp.MachinePartCookTimeMultiplier]; + ent.Comp.CookTimeMultiplier = MathF.Pow(ent.Comp.CookTimeScalingConstant, cookRating - 1); + } + + private void OnUpgradeExamine(Entity ent, ref UpgradeExamineEvent args) + { + args.AddPercentageUpgrade("microwave-component-upgrade-cook-time", ent.Comp.CookTimeMultiplier); + } + private void OnSignalReceived(Entity ent, ref SignalReceivedEvent args) { if (args.Port != ent.Comp.OnPort) @@ -355,11 +371,7 @@ private void OnSignalReceived(Entity ent, ref SignalReceived public void UpdateUserInterfaceState(EntityUid uid, MicrowaveComponent component) { - var ui = _userInterface.GetUiOrNull(uid, MicrowaveUiKey.Key); - if (ui == null) - return; - - _userInterface.SetUiState(ui, new MicrowaveUpdateUserInterfaceState( + _userInterface.SetUiState(uid, MicrowaveUiKey.Key, new MicrowaveUpdateUserInterfaceState( GetNetEntityArray(component.Storage.ContainedEntities.ToArray()), HasComp(uid), component.CurrentCookTimeButtonIndex, diff --git a/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs b/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs index e8ee453986..93a15f319d 100644 --- a/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs +++ b/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs @@ -1,4 +1,5 @@ using Content.Server.Chemistry.Containers.EntitySystems; +using Content.Server.Construction; using Content.Server.Kitchen.Components; using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; @@ -48,6 +49,8 @@ public override void Initialize() SubscribeLocalEvent((uid, _, _) => UpdateUiState(uid)); SubscribeLocalEvent((EntityUid uid, ReagentGrinderComponent _, ref PowerChangedEvent _) => UpdateUiState(uid)); SubscribeLocalEvent(OnInteractUsing); + SubscribeLocalEvent(OnRefreshParts); + SubscribeLocalEvent(OnUpgradeExamine); SubscribeLocalEvent(OnContainerModified); SubscribeLocalEvent(OnContainerModified); @@ -127,7 +130,7 @@ public override void Update(float frameTime) _solutionContainersSystem.TryAddSolution(containerSoln.Value, solution); } - _userInterfaceSystem.TrySendUiMessage(uid, ReagentGrinderUiKey.Key, + _userInterfaceSystem.ServerSendUiMessage(uid, ReagentGrinderUiKey.Key, new ReagentGrinderWorkCompleteMessage()); UpdateUiState(uid); @@ -197,6 +200,24 @@ private void OnInteractUsing(Entity entity, ref Interac args.Handled = true; } + /// + /// Gotta be efficient, you know? you're saving a whole extra second here and everything. + /// + private void OnRefreshParts(Entity entity, ref RefreshPartsEvent args) + { + var ratingWorkTime = args.PartRatings[entity.Comp.MachinePartWorkTime]; + var ratingStorage = args.PartRatings[entity.Comp.MachinePartStorageMax]; + + entity.Comp.WorkTimeMultiplier = MathF.Pow(entity.Comp.PartRatingWorkTimerMulitplier, ratingWorkTime - 1); + entity.Comp.StorageMaxEntities = entity.Comp.BaseStorageMaxEntities + (int) (entity.Comp.StoragePerPartRating * (ratingStorage - 1)); + } + + private void OnUpgradeExamine(Entity entity, ref UpgradeExamineEvent args) + { + args.AddPercentageUpgrade("reagent-grinder-component-upgrade-work-time", entity.Comp.WorkTimeMultiplier); + args.AddNumberUpgrade("reagent-grinder-component-upgrade-storage", entity.Comp.StorageMaxEntities - entity.Comp.BaseStorageMaxEntities); + } + private void UpdateUiState(EntityUid uid) { ReagentGrinderComponent? grinderComp = null; @@ -228,7 +249,7 @@ private void UpdateUiState(EntityUid uid) GetNetEntityArray(inputContainer.ContainedEntities.ToArray()), containerSolution?.Contents.ToArray() ); - _userInterfaceSystem.TrySetUiState(uid, ReagentGrinderUiKey.Key, state); + _userInterfaceSystem.SetUiState(uid, ReagentGrinderUiKey.Key, state); } private void OnStartMessage(Entity entity, ref ReagentGrinderStartMessage message) @@ -305,7 +326,7 @@ private void DoWork(EntityUid uid, ReagentGrinderComponent reagentGrinder, Grind reagentGrinder.AudioStream = _audioSystem.PlayPvs(sound, uid, AudioParams.Default.WithPitchScale(1 / reagentGrinder.WorkTimeMultiplier)).Value.Entity; //slightly higher pitched - _userInterfaceSystem.TrySendUiMessage(uid, ReagentGrinderUiKey.Key, + _userInterfaceSystem.ServerSendUiMessage(uid, ReagentGrinderUiKey.Key, new ReagentGrinderWorkStartedMessage(program)); } diff --git a/Content.Server/Kitchen/EntitySystems/SharpSystem.cs b/Content.Server/Kitchen/EntitySystems/SharpSystem.cs index 35adf6f4b5..81806a0c81 100644 --- a/Content.Server/Kitchen/EntitySystems/SharpSystem.cs +++ b/Content.Server/Kitchen/EntitySystems/SharpSystem.cs @@ -3,24 +3,17 @@ using Content.Server.Nutrition.EntitySystems; using Content.Shared.Body.Components; using Content.Shared.Administration.Logs; -using Content.Shared.Body.Components; using Content.Shared.Database; using Content.Shared.Interaction; using Content.Shared.Nutrition.Components; -using Content.Server.Nutrition.EntitySystems; using Content.Shared.Popups; using Content.Shared.Storage; using Content.Shared.Verbs; using Content.Shared.Destructible; using Content.Shared.DoAfter; -using Content.Shared.Interaction; using Content.Shared.Kitchen; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; -using Content.Shared.Nutrition.Components; -using Content.Shared.Popups; -using Content.Shared.Storage; -using Content.Shared.Verbs; using Robust.Server.Containers; using Robust.Shared.Random; using Robust.Shared.Utility; diff --git a/Content.Server/Labels/Label/Components/HandLabelerComponent.cs b/Content.Server/Labels/Label/Components/HandLabelerComponent.cs deleted file mode 100644 index 6c96cada9e..0000000000 --- a/Content.Server/Labels/Label/Components/HandLabelerComponent.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Content.Shared.Whitelist; - -namespace Content.Server.Labels.Components -{ - [RegisterComponent] - public sealed partial class HandLabelerComponent : Component - { - [ViewVariables(VVAccess.ReadWrite)] - [DataField("assignedLabel")] - public string AssignedLabel { get; set; } = string.Empty; - - [ViewVariables(VVAccess.ReadWrite)] - [DataField("maxLabelChars")] - public int MaxLabelChars { get; set; } = 50; - - [DataField("whitelist")] - public EntityWhitelist Whitelist = new(); - } -} diff --git a/Content.Server/Labels/Label/HandLabelerSystem.cs b/Content.Server/Labels/Label/HandLabelerSystem.cs index dec7d69759..d52bf26046 100644 --- a/Content.Server/Labels/Label/HandLabelerSystem.cs +++ b/Content.Server/Labels/Label/HandLabelerSystem.cs @@ -1,123 +1,8 @@ -using Content.Server.Labels.Components; -using Content.Server.UserInterface; -using Content.Server.Popups; -using Content.Shared.Administration.Logs; -using Content.Shared.Database; -using Content.Shared.Interaction; -using Content.Shared.Labels; -using Content.Shared.Tag; -using Content.Shared.Verbs; -using JetBrains.Annotations; -using Robust.Server.GameObjects; -using Robust.Shared.Player; +using Content.Shared.Labels.EntitySystems; -namespace Content.Server.Labels -{ - /// - /// A hand labeler system that lets an object apply labels to objects with the . - /// - [UsedImplicitly] - public sealed class HandLabelerSystem : EntitySystem - { - [Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!; - [Dependency] private readonly PopupSystem _popupSystem = default!; - [Dependency] private readonly LabelSystem _labelSystem = default!; - [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; - [Dependency] private readonly TagSystem _tagSystem = default!; - - [ValidatePrototypeId] - private const string PreventTag = "PreventLabel"; - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(AfterInteractOn); - SubscribeLocalEvent>(OnUtilityVerb); - // Bound UI subscriptions - SubscribeLocalEvent(OnHandLabelerLabelChanged); - } - - private void OnUtilityVerb(EntityUid uid, HandLabelerComponent handLabeler, GetVerbsEvent args) - { - if (args.Target is not { Valid: true } target || !handLabeler.Whitelist.IsValid(target) || !args.CanAccess - || _tagSystem.HasTag(target, PreventTag)) // DeltaV - Prevent labels on certain items - return; - - string labelerText = handLabeler.AssignedLabel == string.Empty ? Loc.GetString("hand-labeler-remove-label-text") : Loc.GetString("hand-labeler-add-label-text"); - - var verb = new UtilityVerb() - { - Act = () => - { - AddLabelTo(uid, handLabeler, target, out var result); - if (result != null) - _popupSystem.PopupEntity(result, args.User, args.User); - }, - Text = labelerText - }; - - args.Verbs.Add(verb); - } - - private void AfterInteractOn(EntityUid uid, HandLabelerComponent handLabeler, AfterInteractEvent args) - { - if (args.Target is not {Valid: true} target || !handLabeler.Whitelist.IsValid(target) || !args.CanReach - || _tagSystem.HasTag(target, PreventTag)) // DeltaV - Prevent labels on certain items - return; - - AddLabelTo(uid, handLabeler, target, out string? result); - if (result == null) - return; - _popupSystem.PopupEntity(result, args.User, args.User); +namespace Content.Server.Labels.Label; - // Log labeling - _adminLogger.Add(LogType.Action, LogImpact.Low, - $"{ToPrettyString(args.User):user} labeled {ToPrettyString(target):target} with {ToPrettyString(uid):labeler}"); - } - - private void AddLabelTo(EntityUid uid, HandLabelerComponent? handLabeler, EntityUid target, out string? result) - { - if (!Resolve(uid, ref handLabeler)) - { - result = null; - return; - } - - if (handLabeler.AssignedLabel == string.Empty) - { - _labelSystem.Label(target, null); - result = Loc.GetString("hand-labeler-successfully-removed"); - return; - } - - _labelSystem.Label(target, handLabeler.AssignedLabel); - result = Loc.GetString("hand-labeler-successfully-applied"); - } - - private void OnHandLabelerLabelChanged(EntityUid uid, HandLabelerComponent handLabeler, HandLabelerLabelChangedMessage args) - { - if (args.Session.AttachedEntity is not {Valid: true} player) - return; - - var label = args.Label.Trim(); - handLabeler.AssignedLabel = label.Substring(0, Math.Min(handLabeler.MaxLabelChars, label.Length)); - DirtyUI(uid, handLabeler); - - // Log label change - _adminLogger.Add(LogType.Action, LogImpact.Low, - $"{ToPrettyString(player):user} set {ToPrettyString(uid):labeler} to apply label \"{handLabeler.AssignedLabel}\""); - - } - - private void DirtyUI(EntityUid uid, - HandLabelerComponent? handLabeler = null) - { - if (!Resolve(uid, ref handLabeler)) - return; +public sealed class HandLabelerSystem : SharedHandLabelerSystem +{ - _userInterfaceSystem.TrySetUiState(uid, HandLabelerUiKey.Key, - new HandLabelerBoundUserInterfaceState(handLabeler.AssignedLabel)); - } - } } diff --git a/Content.Server/Labels/Label/LabelSystem.cs b/Content.Server/Labels/Label/LabelSystem.cs index 52184fb58f..9d235b488b 100644 --- a/Content.Server/Labels/Label/LabelSystem.cs +++ b/Content.Server/Labels/Label/LabelSystem.cs @@ -54,7 +54,7 @@ private void OnLabelCompMapInit(EntityUid uid, LabelComponent component, MapInit /// intended label text (null to remove) /// label component for resolve /// metadata component for resolve - public void Label(EntityUid uid, string? text, MetaDataComponent? metadata = null, LabelComponent? label = null) + public override void Label(EntityUid uid, string? text, MetaDataComponent? metadata = null, LabelComponent? label = null) { if (!Resolve(uid, ref metadata)) return; diff --git a/Content.Server/Language/Commands/AdminLanguageCommand.cs b/Content.Server/Language/Commands/AdminLanguageCommand.cs index f02d9c7f40..2e7a0b193a 100644 --- a/Content.Server/Language/Commands/AdminLanguageCommand.cs +++ b/Content.Server/Language/Commands/AdminLanguageCommand.cs @@ -3,6 +3,7 @@ using Content.Shared.Language; using Content.Shared.Language.Components; using Content.Shared.Language.Systems; +using Robust.Shared.Prototypes; using Robust.Shared.Toolshed; using Robust.Shared.Toolshed.Syntax; using Robust.Shared.Toolshed.TypeParsers; @@ -62,13 +63,13 @@ public EntityUid RemoveLanguage( } [CommandImplementation("lsspoken")] - public IEnumerable ListSpoken([PipedArgument] EntityUid input) + public IEnumerable> ListSpoken([PipedArgument] EntityUid input) { return Languages.GetSpokenLanguages(input); } [CommandImplementation("lsunderstood")] - public IEnumerable ListUnderstood([PipedArgument] EntityUid input) + public IEnumerable> ListUnderstood([PipedArgument] EntityUid input) { return Languages.GetUnderstoodLanguages(input); } diff --git a/Content.Server/Language/Commands/AdminTranslatorCommand.cs b/Content.Server/Language/Commands/AdminTranslatorCommand.cs index 8a7984bc36..fc8ee02e38 100644 --- a/Content.Server/Language/Commands/AdminTranslatorCommand.cs +++ b/Content.Server/Language/Commands/AdminTranslatorCommand.cs @@ -6,6 +6,7 @@ using Content.Shared.Language.Components.Translators; using Content.Shared.Language.Systems; using Robust.Server.Containers; +using Robust.Shared.Prototypes; using Robust.Shared.Toolshed; using Robust.Shared.Toolshed.Syntax; using Robust.Shared.Toolshed.TypeParsers; @@ -107,7 +108,7 @@ public EntityUid RemoveRequiredLanguage( } [CommandImplementation("lsspoken")] - public IEnumerable ListSpoken([PipedArgument] EntityUid input) + public IEnumerable> ListSpoken([PipedArgument] EntityUid input) { if (!TryGetTranslatorComp(input, out var translator)) return []; @@ -115,7 +116,7 @@ public IEnumerable ListSpoken([PipedArgument] EntityUid input) } [CommandImplementation("lsunderstood")] - public IEnumerable ListUnderstood([PipedArgument] EntityUid input) + public IEnumerable> ListUnderstood([PipedArgument] EntityUid input) { if (!TryGetTranslatorComp(input, out var translator)) return []; @@ -123,7 +124,7 @@ public IEnumerable ListUnderstood([PipedArgument] EntityUid input) } [CommandImplementation("lsrequired")] - public IEnumerable ListRequired([PipedArgument] EntityUid input) + public IEnumerable> ListRequired([PipedArgument] EntityUid input) { if (!TryGetTranslatorComp(input, out var translator)) return []; diff --git a/Content.Server/Language/LanguageKnowledgeComponent.cs b/Content.Server/Language/LanguageKnowledgeComponent.cs new file mode 100644 index 0000000000..da8376f762 --- /dev/null +++ b/Content.Server/Language/LanguageKnowledgeComponent.cs @@ -0,0 +1,23 @@ +using Content.Shared.Language; +using Robust.Shared.Prototypes; + +namespace Content.Server.Language; + +/// +/// Stores data about entities' intrinsic language knowledge. +/// +[RegisterComponent] +public sealed partial class LanguageKnowledgeComponent : Component +{ + /// + /// List of languages this entity can speak without any external tools. + /// + [DataField("speaks", required: true)] + public List> SpokenLanguages = new(); + + /// + /// List of languages this entity can understand without any external tools. + /// + [DataField("understands", required: true)] + public List> UnderstoodLanguages = new(); +} diff --git a/Content.Server/Language/LanguageSystem.Networking.cs b/Content.Server/Language/LanguageSystem.Networking.cs deleted file mode 100644 index 572e2961fd..0000000000 --- a/Content.Server/Language/LanguageSystem.Networking.cs +++ /dev/null @@ -1,78 +0,0 @@ -using Content.Server.Language.Events; -using Content.Server.Mind; -using Content.Shared.Language; -using Content.Shared.Language.Components; -using Content.Shared.Language.Events; -using Content.Shared.Mind; -using Content.Shared.Mind.Components; -using Robust.Shared.Player; - -namespace Content.Server.Language; - -public sealed partial class LanguageSystem -{ - [Dependency] private readonly MindSystem _mind = default!; - - - public void InitializeNet() - { - SubscribeNetworkEvent(OnClientSetLanguage); - SubscribeNetworkEvent((_, session) => SendLanguageStateToClient(session.SenderSession)); - - SubscribeLocalEvent((uid, comp, _) => SendLanguageStateToClient(uid, comp)); - - // Refresh the client's state when its mind hops to a different entity - SubscribeLocalEvent((uid, _, _) => SendLanguageStateToClient(uid)); - SubscribeLocalEvent((_, _, args) => - { - if (args.Mind.Comp.Session != null) - SendLanguageStateToClient(args.Mind.Comp.Session); - }); - } - - - private void OnClientSetLanguage(LanguagesSetMessage message, EntitySessionEventArgs args) - { - if (args.SenderSession.AttachedEntity is not { Valid: true } uid) - return; - - var language = GetLanguagePrototype(message.CurrentLanguage); - if (language == null || !CanSpeak(uid, language.ID)) - return; - - SetLanguage(uid, language.ID); - } - - private void SendLanguageStateToClient(EntityUid uid, LanguageSpeakerComponent? comp = null) - { - // Try to find a mind inside the entity and notify its session - if (!_mind.TryGetMind(uid, out _, out var mindComp) || mindComp.Session == null) - return; - - SendLanguageStateToClient(uid, mindComp.Session, comp); - } - - private void SendLanguageStateToClient(ICommonSession session, LanguageSpeakerComponent? comp = null) - { - // Try to find an entity associated with the session and resolve the languages from it - if (session.AttachedEntity is not { Valid: true } entity) - return; - - SendLanguageStateToClient(entity, session, comp); - } - - // TODO this is really stupid and can be avoided if we just make everything shared... - private void SendLanguageStateToClient(EntityUid uid, ICommonSession session, LanguageSpeakerComponent? component = null) - { - var isUniversal = HasComp(uid); - if (!isUniversal) - Resolve(uid, ref component, logMissing: false); - - // I really don't want to call 3 getter methods here, so we'll just have this slightly hardcoded solution - var message = isUniversal || component == null - ? new LanguagesUpdatedMessage(UniversalPrototype, [UniversalPrototype], [UniversalPrototype]) - : new LanguagesUpdatedMessage(component.CurrentLanguage, component.SpokenLanguages, component.UnderstoodLanguages); - - RaiseNetworkEvent(message, session); - } -} diff --git a/Content.Server/Language/LanguageSystem.cs b/Content.Server/Language/LanguageSystem.cs index e68489e9e2..3ef4caa84d 100644 --- a/Content.Server/Language/LanguageSystem.cs +++ b/Content.Server/Language/LanguageSystem.cs @@ -1,10 +1,10 @@ using System.Linq; -using Content.Server.Language.Events; using Content.Shared.Language; using Content.Shared.Language.Components; using Content.Shared.Language.Events; using Content.Shared.Language.Systems; -using UniversalLanguageSpeakerComponent = Content.Shared.Language.Components.UniversalLanguageSpeakerComponent; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; namespace Content.Server.Language; @@ -13,45 +13,85 @@ public sealed partial class LanguageSystem : SharedLanguageSystem public override void Initialize() { base.Initialize(); - InitializeNet(); - SubscribeLocalEvent(OnInitLanguageSpeaker); + SubscribeLocalEvent(OnInitLanguageSpeaker); + SubscribeLocalEvent(OnGetLanguageState); + SubscribeLocalEvent(OnDetermineUniversalLanguages); + SubscribeNetworkEvent(OnClientSetLanguage); + + SubscribeLocalEvent((uid, _, _) => UpdateEntityLanguages(uid)); + SubscribeLocalEvent((uid, _, _) => UpdateEntityLanguages(uid)); } + #region event handling - #region public api + private void OnInitLanguageSpeaker(Entity ent, ref MapInitEvent args) + { + if (string.IsNullOrEmpty(ent.Comp.CurrentLanguage)) + ent.Comp.CurrentLanguage = ent.Comp.SpokenLanguages.FirstOrDefault(UniversalPrototype); + + UpdateEntityLanguages(ent!); + } - public bool CanUnderstand(EntityUid listener, string language, LanguageSpeakerComponent? component = null) + private void OnGetLanguageState(Entity entity, ref ComponentGetState args) { - if (language == UniversalPrototype || HasComp(listener)) - return true; + args.State = new LanguageSpeakerComponent.State + { + CurrentLanguage = entity.Comp.CurrentLanguage, + SpokenLanguages = entity.Comp.SpokenLanguages, + UnderstoodLanguages = entity.Comp.UnderstoodLanguages + }; + } - if (!Resolve(listener, ref component, logMissing: false)) - return false; + private void OnDetermineUniversalLanguages(Entity entity, ref DetermineEntityLanguagesEvent ev) + { + // We only add it as a spoken language; CanUnderstand checks for ULSC itself. + if (entity.Comp.Enabled) + ev.SpokenLanguages.Add(UniversalPrototype); + } + + + private void OnClientSetLanguage(LanguagesSetMessage message, EntitySessionEventArgs args) + { + if (args.SenderSession.AttachedEntity is not { Valid: true } uid) + return; - return component.UnderstoodLanguages.Contains(language); + var language = GetLanguagePrototype(message.CurrentLanguage); + if (language == null || !CanSpeak(uid, language.ID)) + return; + + SetLanguage(uid, language.ID); } - public bool CanSpeak(EntityUid speaker, string language, LanguageSpeakerComponent? component = null) + #endregion + + #region public api + + public bool CanUnderstand(Entity ent, ProtoId language) { - if (HasComp(speaker)) + if (language == UniversalPrototype || TryComp(ent, out var uni) && uni.Enabled) return true; - if (!Resolve(speaker, ref component, logMissing: false)) + return Resolve(ent, ref ent.Comp, logMissing: false) && ent.Comp.UnderstoodLanguages.Contains(language); + } + + public bool CanSpeak(Entity ent, ProtoId language) + { + if (!Resolve(ent, ref ent.Comp, logMissing: false)) return false; - return component.SpokenLanguages.Contains(language); + return ent.Comp.SpokenLanguages.Contains(language); } /// /// Returns the current language of the given entity, assumes Universal if it's not a language speaker. /// - public LanguagePrototype GetLanguage(EntityUid speaker, LanguageSpeakerComponent? component = null) + public LanguagePrototype GetLanguage(Entity ent) { - if (HasComp(speaker) || !Resolve(speaker, ref component, logMissing: false)) - return Universal; // Serves both as a fallback and uhhh something (TODO: fix this comment) - - if (string.IsNullOrEmpty(component.CurrentLanguage) || !_prototype.TryIndex(component.CurrentLanguage, out var proto)) + if (!Resolve(ent, ref ent.Comp, logMissing: false) + || string.IsNullOrEmpty(ent.Comp.CurrentLanguage) + || !_prototype.TryIndex(ent.Comp.CurrentLanguage, out var proto) + ) return Universal; return proto; @@ -60,43 +100,31 @@ public LanguagePrototype GetLanguage(EntityUid speaker, LanguageSpeakerComponent /// /// Returns the list of languages this entity can speak. /// - /// Typically, checking is sufficient. - public List GetSpokenLanguages(EntityUid uid) + /// This simply returns the value of . + public List> GetSpokenLanguages(EntityUid uid) { - if (HasComp(uid)) - return [UniversalPrototype]; - - if (TryComp(uid, out var component)) - return component.SpokenLanguages; - - return []; + return TryComp(uid, out var component) ? component.SpokenLanguages : []; } /// /// Returns the list of languages this entity can understand. - /// - /// Typically, checking is sufficient. - public List GetUnderstoodLanguages(EntityUid uid) + ///
This simply returns the value of . + public List> GetUnderstoodLanguages(EntityUid uid) { - if (HasComp(uid)) - return [UniversalPrototype]; // This one is tricky because... well, they understand all of them, not just one. - - if (TryComp(uid, out var component)) - return component.UnderstoodLanguages; - - return []; + return TryComp(uid, out var component) ? component.UnderstoodLanguages : []; } - public void SetLanguage(EntityUid speaker, string language, LanguageSpeakerComponent? component = null) + public void SetLanguage(Entity ent, ProtoId language) { - if (!CanSpeak(speaker, language) || (HasComp(speaker) && language != UniversalPrototype)) + if (!CanSpeak(ent, language) + || !Resolve(ent, ref ent.Comp) + || ent.Comp.CurrentLanguage == language) return; - if (!Resolve(speaker, ref component) || component.CurrentLanguage == language) - return; - - component.CurrentLanguage = language; - RaiseLocalEvent(speaker, new LanguagesUpdateEvent(), true); + ent.Comp.CurrentLanguage = language; + RaiseLocalEvent(ent, new LanguagesUpdateEvent(), true); + Dirty(ent); } /// @@ -104,14 +132,12 @@ public void SetLanguage(EntityUid speaker, string language, LanguageSpeakerCompo /// public void AddLanguage( EntityUid uid, - string language, + ProtoId language, bool addSpoken = true, - bool addUnderstood = true, - LanguageKnowledgeComponent? knowledge = null, - LanguageSpeakerComponent? speaker = null) + bool addUnderstood = true) { - if (knowledge == null) - knowledge = EnsureComp(uid); + EnsureComp(uid, out var knowledge); + EnsureComp(uid, out var speaker); if (addSpoken && !knowledge.SpokenLanguages.Contains(language)) knowledge.SpokenLanguages.Add(language); @@ -119,30 +145,29 @@ public void AddLanguage( if (addUnderstood && !knowledge.UnderstoodLanguages.Contains(language)) knowledge.UnderstoodLanguages.Add(language); - UpdateEntityLanguages(uid, speaker); + UpdateEntityLanguages((uid, speaker)); } /// /// Removes a language from the respective lists of intrinsically known languages of the given entity. /// public void RemoveLanguage( - EntityUid uid, - string language, + Entity ent, + ProtoId language, bool removeSpoken = true, - bool removeUnderstood = true, - LanguageKnowledgeComponent? knowledge = null, - LanguageSpeakerComponent? speaker = null) + bool removeUnderstood = true) { - if (knowledge == null) - knowledge = EnsureComp(uid); + if (!Resolve(ent, ref ent.Comp, false)) + return; if (removeSpoken) - knowledge.SpokenLanguages.Remove(language); + ent.Comp.SpokenLanguages.Remove(language); if (removeUnderstood) - knowledge.UnderstoodLanguages.Remove(language); + ent.Comp.UnderstoodLanguages.Remove(language); - UpdateEntityLanguages(uid, speaker); + // We don't ensure that the entity has a speaker comp. If it doesn't... Well, woe be the caller of this method. + UpdateEntityLanguages(ent.Owner); } /// @@ -150,15 +175,16 @@ public void RemoveLanguage( /// If not, sets it to the first entry of its SpokenLanguages list, or universal if it's empty. /// /// True if the current language was modified, false otherwise. - public bool EnsureValidLanguage(EntityUid entity, LanguageSpeakerComponent? comp = null) + public bool EnsureValidLanguage(Entity ent) { - if (!Resolve(entity, ref comp)) + if (!Resolve(ent, ref ent.Comp, false)) return false; - if (!comp.SpokenLanguages.Contains(comp.CurrentLanguage)) + if (!ent.Comp.SpokenLanguages.Contains(ent.Comp.CurrentLanguage)) { - comp.CurrentLanguage = comp.SpokenLanguages.FirstOrDefault(UniversalPrototype); - RaiseLocalEvent(entity, new LanguagesUpdateEvent()); + ent.Comp.CurrentLanguage = ent.Comp.SpokenLanguages.FirstOrDefault(UniversalPrototype); + RaiseLocalEvent(ent, new LanguagesUpdateEvent()); + Dirty(ent); return true; } @@ -168,14 +194,14 @@ public bool EnsureValidLanguage(EntityUid entity, LanguageSpeakerComponent? comp /// /// Immediately refreshes the cached lists of spoken and understood languages for the given entity. /// - public void UpdateEntityLanguages(EntityUid entity, LanguageSpeakerComponent? languages = null) + public void UpdateEntityLanguages(Entity ent) { - if (!Resolve(entity, ref languages)) + if (!Resolve(ent, ref ent.Comp, false)) return; var ev = new DetermineEntityLanguagesEvent(); // We add the intrinsically known languages first so other systems can manipulate them easily - if (TryComp(entity, out var knowledge)) + if (TryComp(ent, out var knowledge)) { foreach (var spoken in knowledge.SpokenLanguages) ev.SpokenLanguages.Add(spoken); @@ -184,28 +210,19 @@ public void UpdateEntityLanguages(EntityUid entity, LanguageSpeakerComponent? la ev.UnderstoodLanguages.Add(understood); } - RaiseLocalEvent(entity, ref ev); - - languages.SpokenLanguages.Clear(); - languages.UnderstoodLanguages.Clear(); - - languages.SpokenLanguages.AddRange(ev.SpokenLanguages); - languages.UnderstoodLanguages.AddRange(ev.UnderstoodLanguages); + RaiseLocalEvent(ent, ref ev); - if (!EnsureValidLanguage(entity)) - RaiseLocalEvent(entity, new LanguagesUpdateEvent()); - } + ent.Comp.SpokenLanguages.Clear(); + ent.Comp.UnderstoodLanguages.Clear(); - #endregion + ent.Comp.SpokenLanguages.AddRange(ev.SpokenLanguages); + ent.Comp.UnderstoodLanguages.AddRange(ev.UnderstoodLanguages); - #region event handling - - private void OnInitLanguageSpeaker(EntityUid uid, LanguageSpeakerComponent component, ComponentInit args) - { - if (string.IsNullOrEmpty(component.CurrentLanguage)) - component.CurrentLanguage = component.SpokenLanguages.FirstOrDefault(UniversalPrototype); + // If EnsureValidLanguage returns true, it also raises a LanguagesUpdateEvent, so we try to avoid raising it twice in that case. + if (!EnsureValidLanguage(ent)) + RaiseLocalEvent(ent, new LanguagesUpdateEvent()); - UpdateEntityLanguages(uid, component); + Dirty(ent); } #endregion diff --git a/Content.Server/Language/LanguagesUpdateEvent.cs b/Content.Server/Language/LanguagesUpdateEvent.cs deleted file mode 100644 index 88ea09916b..0000000000 --- a/Content.Server/Language/LanguagesUpdateEvent.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Content.Server.Language.Events; - -/// -/// Raised on an entity when its list of languages changes. -/// -public sealed class LanguagesUpdateEvent : EntityEventArgs -{ -} diff --git a/Content.Server/Language/TranslatorImplantSystem.cs b/Content.Server/Language/TranslatorImplantSystem.cs index 4d58144481..439389477c 100644 --- a/Content.Server/Language/TranslatorImplantSystem.cs +++ b/Content.Server/Language/TranslatorImplantSystem.cs @@ -1,6 +1,7 @@ using Content.Shared.Implants.Components; using Content.Shared.Language; using Content.Shared.Language.Components; +using Content.Shared.Language.Events; using Robust.Shared.Containers; namespace Content.Server.Language; diff --git a/Content.Server/Language/TranslatorSystem.cs b/Content.Server/Language/TranslatorSystem.cs index 24f4cb1729..5cb5c8cd2e 100644 --- a/Content.Server/Language/TranslatorSystem.cs +++ b/Content.Server/Language/TranslatorSystem.cs @@ -8,13 +8,16 @@ using Content.Shared.Language.Systems; using Content.Shared.PowerCell; using Content.Shared.Language.Components.Translators; +using Content.Shared.Language.Events; +using Robust.Shared.Containers; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; namespace Content.Server.Language; -// This does not support holding multiple translators at once. -// That shouldn't be an issue for now, but it needs to be fixed later. public sealed class TranslatorSystem : SharedTranslatorSystem { + [Dependency] private readonly SharedContainerSystem _containers = default!; [Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly LanguageSystem _language = default!; [Dependency] private readonly PowerCellSystem _powerCell = default!; @@ -24,66 +27,69 @@ public override void Initialize() base.Initialize(); SubscribeLocalEvent(OnDetermineLanguages); - SubscribeLocalEvent(OnDetermineLanguages); - SubscribeLocalEvent(OnDetermineLanguages); + SubscribeLocalEvent(OnProxyDetermineLanguages); + SubscribeLocalEvent(OnTranslatorInserted); + SubscribeLocalEvent(OnTranslatorParentChanged); SubscribeLocalEvent(OnTranslatorToggle); SubscribeLocalEvent(OnPowerCellSlotEmpty); - - SubscribeLocalEvent(OnTranslatorInteract); - SubscribeLocalEvent(OnTranslatorDropped); } private void OnDetermineLanguages(EntityUid uid, IntrinsicTranslatorComponent component, DetermineEntityLanguagesEvent ev) { - if (!component.Enabled || !TryComp(uid, out var speaker)) + if (!component.Enabled + || component.LifeStage >= ComponentLifeStage.Removing + || !TryComp(uid, out var knowledge) + || !_powerCell.HasActivatableCharge(uid)) return; - if (!_powerCell.HasActivatableCharge(uid)) + CopyLanguages(component, ev, knowledge); + } + + private void OnProxyDetermineLanguages(EntityUid uid, HoldsTranslatorComponent component, DetermineEntityLanguagesEvent ev) + { + if (!TryComp(uid, out var knowledge)) return; - // The idea here is as follows: - // Required languages are languages that are required to operate the translator. - // The translator has a limited number of languages it can translate to and translate from. - // If the wielder understands the language of the translator, they will be able to understand translations provided by it - // If the wielder also speaks that language, they will be able to use it to translate their own speech by "speaking" in that language - var addSpoken = CheckLanguagesMatch(component.RequiredLanguages, speaker.SpokenLanguages, component.RequiresAllLanguages); - var addUnderstood = CheckLanguagesMatch(component.RequiredLanguages, speaker.UnderstoodLanguages, component.RequiresAllLanguages); + foreach (var (translator, translatorComp) in component.Translators.ToArray()) + { + if (!translatorComp.Enabled || !_powerCell.HasActivatableCharge(uid)) + continue; - if (addSpoken) - foreach (var language in component.SpokenLanguages) - ev.SpokenLanguages.Add(language); + if (!_containers.TryGetContainingContainer(translator, out var container) || container.Owner != uid) + { + component.Translators.RemoveWhere(it => it.Owner == translator); + continue; + } - if (addUnderstood) - foreach (var language in component.UnderstoodLanguages) - ev.UnderstoodLanguages.Add(language); + CopyLanguages(translatorComp, ev, knowledge); + } } - private void OnTranslatorInteract(EntityUid translator, HandheldTranslatorComponent component, InteractHandEvent args) + private void OnTranslatorInserted(EntityUid translator, HandheldTranslatorComponent component, EntGotInsertedIntoContainerMessage args) { - var holder = args.User; - if (!EntityManager.HasComponent(holder)) + if (args.Container.Owner is not {Valid: true} holder || !HasComp(holder)) return; var intrinsic = EnsureComp(holder); - UpdateBoundIntrinsicComp(component, intrinsic, component.Enabled); + intrinsic.Translators.Add((translator, component)); _language.UpdateEntityLanguages(holder); } - private void OnTranslatorDropped(EntityUid translator, HandheldTranslatorComponent component, DroppedEvent args) + private void OnTranslatorParentChanged(EntityUid translator, HandheldTranslatorComponent component, EntParentChangedMessage args) { - var holder = args.User; - if (!EntityManager.TryGetComponent(holder, out var intrinsic)) + if (!HasComp(args.OldParent)) return; - if (intrinsic.Issuer == component) + // Update the translator on the next tick - this is necessary because there's a good chance the removal from a container + // Was caused by the player moving the translator within their inventory rather than removing it. + // If that is not the case, then OnProxyDetermineLanguages will remove this translator from HoldsTranslatorComponent.Translators. + Timer.Spawn(0, () => { - intrinsic.Enabled = false; - RemCompDeferred(holder, intrinsic); - } - - _language.UpdateEntityLanguages(holder); + if (Exists(args.OldParent) && HasComp(args.OldParent)) + _language.UpdateEntityLanguages(args.OldParent.Value); + }); } private void OnTranslatorToggle(EntityUid translator, HandheldTranslatorComponent translatorComp, ActivateInWorldEvent args) @@ -93,53 +99,31 @@ private void OnTranslatorToggle(EntityUid translator, HandheldTranslatorComponen // This will show a popup if false var hasPower = _powerCell.HasDrawCharge(translator); + var isEnabled = !translatorComp.Enabled && hasPower; - if (Transform(args.Target).ParentUid is { Valid: true } holder + translatorComp.Enabled = isEnabled; + _powerCell.SetPowerCellDrawEnabled(translator, isEnabled); + + if (_containers.TryGetContainingContainer(translator, out var holderCont) + && holderCont.Owner is var holder && TryComp(holder, out var languageComp)) { - // This translator is held by a language speaker and thus has an intrinsic counterpart bound to it. - // Make sure it's up-to-date. - var intrinsic = EnsureComp(holder); - var isEnabled = !translatorComp.Enabled; - if (intrinsic.Issuer != translatorComp) - { - // The intrinsic comp wasn't owned by this handheld translator, so this wasn't the active translator. - // Thus, the intrinsic comp needs to be turned on regardless of its previous state. - intrinsic.Issuer = translatorComp; - isEnabled = true; - } - isEnabled &= hasPower; - - UpdateBoundIntrinsicComp(translatorComp, intrinsic, isEnabled); - translatorComp.Enabled = isEnabled; - _powerCell.SetPowerCellDrawEnabled(translator, isEnabled); - // The first new spoken language added by this translator, or null var firstNewLanguage = translatorComp.SpokenLanguages.FirstOrDefault(it => !languageComp.SpokenLanguages.Contains(it)); - - _language.UpdateEntityLanguages(holder, languageComp); + _language.UpdateEntityLanguages(holder); // Update the current language of the entity if necessary if (isEnabled && translatorComp.SetLanguageOnInteract && firstNewLanguage is {}) - _language.SetLanguage(holder, firstNewLanguage, languageComp); - } - else - { - // This is a standalone translator (e.g. lying on the ground), toggle its state. - translatorComp.Enabled = !translatorComp.Enabled && hasPower; - _powerCell.SetPowerCellDrawEnabled(translator, !translatorComp.Enabled && hasPower); + _language.SetLanguage((holder, languageComp), firstNewLanguage); } OnAppearanceChange(translator, translatorComp); if (hasPower) { - var message = Loc.GetString( - translatorComp.Enabled - ? "translator-component-turnon" - : "translator-component-shutoff", - ("translator", translatorComp.Owner)); - _popup.PopupEntity(message, translatorComp.Owner, args.User); + var loc = isEnabled ? "translator-component-turnon" : "translator-component-shutoff"; + var message = Loc.GetString(loc, ("translator", translator)); + _popup.PopupEntity(message, translator, args.User); } } @@ -149,48 +133,28 @@ private void OnPowerCellSlotEmpty(EntityUid translator, HandheldTranslatorCompon _powerCell.SetPowerCellDrawEnabled(translator, false); OnAppearanceChange(translator, component); - if (Transform(translator).ParentUid is { Valid: true } holder - && TryComp(holder, out var languageComp)) - { - if (!EntityManager.TryGetComponent(holder, out var intrinsic)) - return; - - if (intrinsic.Issuer == component) - { - intrinsic.Enabled = false; - RemComp(holder, intrinsic); - } - - _language.UpdateEntityLanguages(holder, languageComp); - } + if (_containers.TryGetContainingContainer(translator, out var holderCont) && HasComp(holderCont.Owner)) + _language.UpdateEntityLanguages(holderCont.Owner); } - /// - /// Copies the state from the handheld to the intrinsic component - /// - private void UpdateBoundIntrinsicComp(HandheldTranslatorComponent comp, HoldsTranslatorComponent intrinsic, bool isEnabled) + private void CopyLanguages(BaseTranslatorComponent from, DetermineEntityLanguagesEvent to, LanguageKnowledgeComponent knowledge) { - if (isEnabled) - { - intrinsic.SpokenLanguages = [..comp.SpokenLanguages]; - intrinsic.UnderstoodLanguages = [..comp.UnderstoodLanguages]; - intrinsic.RequiredLanguages = [..comp.RequiredLanguages]; - } - else - { - intrinsic.SpokenLanguages.Clear(); - intrinsic.UnderstoodLanguages.Clear(); - intrinsic.RequiredLanguages.Clear(); - } + var addSpoken = CheckLanguagesMatch(from.RequiredLanguages, knowledge.SpokenLanguages, from.RequiresAllLanguages); + var addUnderstood = CheckLanguagesMatch(from.RequiredLanguages, knowledge.UnderstoodLanguages, from.RequiresAllLanguages); + + if (addSpoken) + foreach (var language in from.SpokenLanguages) + to.SpokenLanguages.Add(language); - intrinsic.Enabled = isEnabled; - intrinsic.Issuer = comp; + if (addUnderstood) + foreach (var language in from.UnderstoodLanguages) + to.UnderstoodLanguages.Add(language); } /// /// Checks whether any OR all required languages are provided. Used for utility purposes. /// - public static bool CheckLanguagesMatch(ICollection required, ICollection provided, bool requireAll) + public static bool CheckLanguagesMatch(ICollection> required, ICollection> provided, bool requireAll) { if (required.Count == 0) return true; diff --git a/Content.Server/Language/UniversalLanguageSpeakerComponent.cs b/Content.Server/Language/UniversalLanguageSpeakerComponent.cs new file mode 100644 index 0000000000..ee43450335 --- /dev/null +++ b/Content.Server/Language/UniversalLanguageSpeakerComponent.cs @@ -0,0 +1,12 @@ +namespace Content.Server.Language; + +// +// Signifies that this entity can speak and understand any language. +// Applies to such entities as ghosts. +// +[RegisterComponent] +public sealed partial class UniversalLanguageSpeakerComponent : Component +{ + [DataField] + public bool Enabled = true; +} diff --git a/Content.Server/Lathe/LatheSystem.cs b/Content.Server/Lathe/LatheSystem.cs index 06d1b463ec..7448a9b84d 100644 --- a/Content.Server/Lathe/LatheSystem.cs +++ b/Content.Server/Lathe/LatheSystem.cs @@ -8,11 +8,13 @@ using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; using Content.Server.Stack; +using Content.Shared.Atmos; using Content.Shared.UserInterface; using Content.Shared.Database; using Content.Shared.Emag.Components; using Content.Shared.Lathe; using Content.Shared.Materials; +using Content.Shared.ReagentSpeed; using Content.Shared.Research.Components; using Content.Shared.Research.Prototypes; using JetBrains.Annotations; @@ -34,6 +36,7 @@ public sealed class LatheSystem : SharedLatheSystem [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly UserInterfaceSystem _uiSys = default!; [Dependency] private readonly MaterialStorageSystem _materialStorage = default!; + [Dependency] private readonly ReagentSpeedSystem _reagentSpeed = default!; [Dependency] private readonly StackSystem _stack = default!; [Dependency] private readonly TransformSystem _transform = default!; @@ -185,9 +188,11 @@ public bool TryStartProducing(EntityUid uid, LatheComponent? component = null) var recipe = component.Queue.First(); component.Queue.RemoveAt(0); + var time = _reagentSpeed.ApplySpeed(uid, recipe.CompleteTime); + var lathe = EnsureComp(uid); lathe.StartTime = _timing.CurTime; - lathe.ProductionLength = recipe.CompleteTime * component.TimeMultiplier; + lathe.ProductionLength = time * component.TimeMultiplier; component.CurrentRecipe = recipe; var ev = new LatheStartPrintingEvent(recipe); @@ -226,11 +231,10 @@ public void UpdateUserInterfaceState(EntityUid uid, LatheComponent? component = if (!Resolve(uid, ref component)) return; - var ui = _uiSys.GetUi(uid, LatheUiKey.Key); var producing = component.CurrentRecipe ?? component.Queue.FirstOrDefault(); var state = new LatheUpdateState(GetAvailableRecipes(uid, component), component.Queue, producing); - _uiSys.SetUiState(ui, state); + _uiSys.SetUiState(uid, LatheUiKey.Key, state); } private void OnGetRecipes(EntityUid uid, TechnologyDatabaseComponent component, LatheGetRecipesEvent args) @@ -337,10 +341,10 @@ private void OnLatheQueueRecipeMessage(EntityUid uid, LatheComponent component, else break; } - if (count > 0 && args.Session.AttachedEntity != null) + if (count > 0) { _adminLogger.Add(LogType.Action, LogImpact.Low, - $"{ToPrettyString(args.Session.AttachedEntity.Value):player} queued {count} {recipe.Name} at {ToPrettyString(uid):lathe}"); + $"{ToPrettyString(args.Actor):player} queued {count} {recipe.Name} at {ToPrettyString(uid):lathe}"); } } TryStartProducing(uid, component); diff --git a/Content.Server/LifeDrainer/LifeDrainerComponent.cs b/Content.Server/LifeDrainer/LifeDrainerComponent.cs new file mode 100644 index 0000000000..c5d09266c7 --- /dev/null +++ b/Content.Server/LifeDrainer/LifeDrainerComponent.cs @@ -0,0 +1,60 @@ +using Content.Shared.Damage; +using Content.Shared.DoAfter; +using Content.Shared.Whitelist; +using Robust.Shared.Audio; + +namespace Content.Server.LifeDrainer; + +/// +/// Adds a verb to drain life from a crit mob that matches a whitelist. +/// Successfully draining a mob rejuvenates you completely. +/// +[RegisterComponent, Access(typeof(LifeDrainerSystem))] +public sealed partial class LifeDrainerComponent : Component +{ + /// + /// Damage to give to the target when draining is complete + /// + [DataField(required: true)] + public DamageSpecifier Damage = new(); + + /// + /// Mobs have to match this whitelist to be drained. + /// + [DataField] + public EntityWhitelist? Whitelist; + + /// + /// The time that it takes to drain an entity. + /// + [DataField] + public TimeSpan Delay = TimeSpan.FromSeconds(8.35f); + + /// + /// Sound played while draining a mob. + /// + [DataField] + public SoundSpecifier DrainSound = new SoundPathSpecifier("/Audio/DeltaV/Effects/clang2.ogg"); + + /// + /// Sound played after draining is complete. + /// + [DataField] + public SoundSpecifier FinishSound = new SoundPathSpecifier("/Audio/Effects/guardian_inject.ogg"); + + [DataField] + public EntityUid? DrainStream; + + /// + /// A current drain doafter in progress. + /// + [DataField] + public DoAfterId? DoAfter; + + /// + /// What mob is being targeted for draining. + /// When draining stops the AI will try to drain this target again until successful. + /// + [DataField] + public EntityUid? Target; +} diff --git a/Content.Server/LifeDrainer/LifeDrainerSystem.cs b/Content.Server/LifeDrainer/LifeDrainerSystem.cs new file mode 100644 index 0000000000..439a5dcf45 --- /dev/null +++ b/Content.Server/LifeDrainer/LifeDrainerSystem.cs @@ -0,0 +1,143 @@ +using Content.Server.Carrying; +using Content.Server.NPC.Systems; +using Content.Shared.ActionBlocker; +using Content.Shared.Damage; +using Content.Shared.DoAfter; +using Content.Shared.Interaction; +using Content.Shared.Mobs.Systems; +using Content.Shared.Movement.Pulling.Components; +using Content.Shared.Popups; +using Content.Shared.Rejuvenate; +using Content.Shared.Verbs; +using Content.Shared.Whitelist; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Player; +using Robust.Shared.Utility; + +namespace Content.Server.LifeDrainer; + +public sealed class LifeDrainerSystem : EntitySystem +{ + [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; + [Dependency] private readonly DamageableSystem _damageable = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelist = default!; + [Dependency] private readonly MobStateSystem _mob = default!; + [Dependency] private readonly NpcFactionSystem _faction = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + [Dependency] private readonly SharedInteractionSystem _interaction = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent>(OnGetVerbs); + SubscribeLocalEvent(OnDrain); + } + + private void OnGetVerbs(Entity ent, ref GetVerbsEvent args) + { + var target = args.Target; + if (!args.CanAccess || !args.CanInteract || !CanDrain(ent, target)) + return; + + args.Verbs.Add(new InnateVerb() + { + Act = () => + { + TryDrain(ent, target); + }, + Text = Loc.GetString("verb-life-drain"), + Icon = new SpriteSpecifier.Texture(new ("/Textures/Nyanotrasen/Icons/verbiconfangs.png")), + Priority = 2 + }); + } + + private void OnDrain(Entity ent, ref LifeDrainDoAfterEvent args) + { + var (uid, comp) = ent; + CancelDrain(comp); + if (args.Handled || args.Args.Target is not {} target) + return; + + // attack whoever interrupted the draining + if (args.Cancelled) + { + // someone pulled the psionic away + if (TryComp(target, out var pullable) && pullable.Puller is {} puller) + _faction.AggroEntity(uid, puller); + + // someone pulled me away + if (TryComp(ent, out pullable) && pullable.Puller is {} selfPuller) + _faction.AggroEntity(uid, selfPuller); + + // someone carried the psionic away + if (TryComp(target, out var carried)) + _faction.AggroEntity(uid, carried.Carrier); + + return; + } + + _popup.PopupEntity(Loc.GetString("life-drain-second-end", ("drainer", uid)), target, target, PopupType.LargeCaution); + _popup.PopupEntity(Loc.GetString("life-drain-third-end", ("drainer", uid), ("target", target)), target, Filter.PvsExcept(target), true, PopupType.LargeCaution); + + var rejuv = new RejuvenateEvent(); + RaiseLocalEvent(uid, rejuv); + + _audio.PlayPvs(comp.FinishSound, uid); + + _damageable.TryChangeDamage(target, comp.Damage, true, origin: uid); + } + + public bool CanDrain(Entity ent, EntityUid target) + { + var (uid, comp) = ent; + return !IsDraining(comp) + && uid != target + && (comp.Whitelist is null || _whitelist.IsValid(comp.Whitelist, target)) + && _mob.IsCritical(target); + } + + public bool IsDraining(LifeDrainerComponent comp) + { + return _doAfter.GetStatus(comp.DoAfter) == DoAfterStatus.Running; + } + + public bool TryDrain(Entity ent, EntityUid target) + { + var (uid, comp) = ent; + if (!CanDrain(ent, target) || !_actionBlocker.CanInteract(uid, target) || !_interaction.InRangeUnobstructed(ent.Owner, target, popup: true)) + return false; + + _popup.PopupEntity(Loc.GetString("life-drain-second-start", ("drainer", uid)), target, target, PopupType.LargeCaution); + _popup.PopupEntity(Loc.GetString("life-drain-third-start", ("drainer", uid), ("target", target)), target, Filter.PvsExcept(target), true, PopupType.LargeCaution); + + if (_audio.PlayPvs(comp.DrainSound, target) is {} stream) + comp.DrainStream = stream.Item1; + + var ev = new LifeDrainDoAfterEvent(); + var args = new DoAfterArgs(EntityManager, uid, comp.Delay, ev, target: target, eventTarget: uid) + { + BreakOnTargetMove = true, + BreakOnUserMove = true, + MovementThreshold = 2f, + NeedHand = false + }; + + if (!_doAfter.TryStartDoAfter(args, out var id)) + return false; + + comp.DoAfter = id; + comp.Target = target; + return true; + } + + public void CancelDrain(LifeDrainerComponent comp) + { + comp.DrainStream = _audio.Stop(comp.DrainStream); + _doAfter.Cancel(comp.DoAfter); + comp.DoAfter = null; + comp.Target = null; + } +} diff --git a/Content.Server/Light/Components/EmergencyLightComponent.cs b/Content.Server/Light/Components/EmergencyLightComponent.cs index 20de7f1c03..b49a8c3868 100644 --- a/Content.Server/Light/Components/EmergencyLightComponent.cs +++ b/Content.Server/Light/Components/EmergencyLightComponent.cs @@ -14,7 +14,7 @@ public sealed partial class EmergencyLightComponent : SharedEmergencyLightCompon /// /// Is this emergency light forced on for some reason and cannot be disabled through normal means - /// (i.e. delta alert level?) + /// (i.e. blue alert or higher?) /// public bool ForciblyEnabled = false; diff --git a/Content.Server/Light/EntitySystems/EmergencyLightSystem.cs b/Content.Server/Light/EntitySystems/EmergencyLightSystem.cs index 3fa5237948..f2c4c7dcc5 100644 --- a/Content.Server/Light/EntitySystems/EmergencyLightSystem.cs +++ b/Content.Server/Light/EntitySystems/EmergencyLightSystem.cs @@ -31,9 +31,9 @@ public override void Initialize() SubscribeLocalEvent(OnEmergencyPower); } - private void OnEmergencyPower(EntityUid uid, EmergencyLightComponent component, ref PowerChangedEvent args) + private void OnEmergencyPower(Entity entity, ref PowerChangedEvent args) { - var meta = MetaData(uid); + var meta = MetaData(entity.Owner); // TODO: PowerChangedEvent shouldn't be issued for paused ents but this is the world we live in. if (meta.EntityLifeStage >= EntityLifeStage.Terminating || @@ -42,7 +42,7 @@ private void OnEmergencyPower(EntityUid uid, EmergencyLightComponent component, return; } - UpdateState(uid, component); + UpdateState(entity); } private void OnEmergencyExamine(EntityUid uid, EmergencyLightComponent component, ExaminedEvent args) @@ -111,13 +111,13 @@ private void OnAlertLevelChanged(AlertLevelChangedEvent ev) if (details.ForceEnableEmergencyLights && !light.ForciblyEnabled) { light.ForciblyEnabled = true; - TurnOn(uid, light); + TurnOn((uid, light)); } else if (!details.ForceEnableEmergencyLights && light.ForciblyEnabled) { // Previously forcibly enabled, and we went down an alert level. light.ForciblyEnabled = false; - UpdateState(uid, light); + UpdateState((uid, light)); } } } @@ -135,31 +135,31 @@ public override void Update(float frameTime) var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out _, out var emergencyLight, out var battery)) { - Update(uid, emergencyLight, battery, frameTime); + Update((uid, emergencyLight), battery, frameTime); } } - private void Update(EntityUid uid, EmergencyLightComponent component, BatteryComponent battery, float frameTime) + private void Update(Entity entity, BatteryComponent battery, float frameTime) { - if (component.State == EmergencyLightState.On) + if (entity.Comp.State == EmergencyLightState.On) { - if (!_battery.TryUseCharge(uid, component.Wattage * frameTime, battery)) + if (!_battery.TryUseCharge(entity.Owner, entity.Comp.Wattage * frameTime, battery)) { - SetState(uid, component, EmergencyLightState.Empty); - TurnOff(uid, component); + SetState(entity.Owner, entity.Comp, EmergencyLightState.Empty); + TurnOff(entity); } } else { - _battery.SetCharge(uid, battery.CurrentCharge + component.ChargingWattage * frameTime * component.ChargingEfficiency, battery); + _battery.SetCharge(entity.Owner, battery.CurrentCharge + entity.Comp.ChargingWattage * frameTime * entity.Comp.ChargingEfficiency, battery); if (battery.IsFullyCharged) { - if (TryComp(uid, out var receiver)) + if (TryComp(entity.Owner, out var receiver)) { receiver.Load = 1; } - SetState(uid, component, EmergencyLightState.Full); + SetState(entity.Owner, entity.Comp, EmergencyLightState.Full); } } } @@ -167,35 +167,73 @@ private void Update(EntityUid uid, EmergencyLightComponent component, BatteryCom /// /// Updates the light's power drain, battery drain, sprite and actual light state. /// - public void UpdateState(EntityUid uid, EmergencyLightComponent component) + public void UpdateState(Entity entity) { - if (!TryComp(uid, out var receiver)) + if (!TryComp(entity.Owner, out var receiver)) return; - if (receiver.Powered && !component.ForciblyEnabled) + if (!TryComp(_station.GetOwningStation(entity.Owner), out var alerts)) + return; + + if (alerts.AlertLevels == null || !alerts.AlertLevels.Levels.TryGetValue(alerts.CurrentLevel, out var details)) { - receiver.Load = (int) Math.Abs(component.Wattage); - TurnOff(uid, component); - SetState(uid, component, EmergencyLightState.Charging); + TurnOff(entity, Color.Red); // if no alert, default to off red state + return; } - else + + if (receiver.Powered && !entity.Comp.ForciblyEnabled) // Green alert { - TurnOn(uid, component); - SetState(uid, component, EmergencyLightState.On); + receiver.Load = (int) Math.Abs(entity.Comp.Wattage); + TurnOff(entity, details.Color); + SetState(entity.Owner, entity.Comp, EmergencyLightState.Charging); + } + else if (!receiver.Powered) // If internal battery runs out it will end in off red state + { + TurnOn(entity, Color.Red); + SetState(entity.Owner, entity.Comp, EmergencyLightState.On); + } + else // Powered and enabled + { + TurnOn(entity, details.Color); + SetState(entity.Owner, entity.Comp, EmergencyLightState.On); } } - private void TurnOff(EntityUid uid, EmergencyLightComponent component) + private void TurnOff(Entity entity) { - _pointLight.SetEnabled(uid, false); - _appearance.SetData(uid, EmergencyLightVisuals.On, false); - _ambient.SetAmbience(uid, false); + _pointLight.SetEnabled(entity.Owner, false); + _appearance.SetData(entity.Owner, EmergencyLightVisuals.On, false); + _ambient.SetAmbience(entity.Owner, false); } - private void TurnOn(EntityUid uid, EmergencyLightComponent component) + /// + /// Turn off emergency light and set color. + /// + private void TurnOff(Entity entity, Color color) + { + _pointLight.SetEnabled(entity.Owner, false); + _pointLight.SetColor(entity.Owner, color); + _appearance.SetData(entity.Owner, EmergencyLightVisuals.Color, color); + _appearance.SetData(entity.Owner, EmergencyLightVisuals.On, false); + _ambient.SetAmbience(entity.Owner, false); + } + + private void TurnOn(Entity entity) + { + _pointLight.SetEnabled(entity.Owner, true); + _appearance.SetData(entity.Owner, EmergencyLightVisuals.On, true); + _ambient.SetAmbience(entity.Owner, true); + } + + /// + /// Turn on emergency light and set color. + /// + private void TurnOn(Entity entity, Color color) { - _pointLight.SetEnabled(uid, true); - _appearance.SetData(uid, EmergencyLightVisuals.On, true); - _ambient.SetAmbience(uid, true); + _pointLight.SetEnabled(entity.Owner, true); + _pointLight.SetColor(entity.Owner, color); + _appearance.SetData(entity.Owner, EmergencyLightVisuals.Color, color); + _appearance.SetData(entity.Owner, EmergencyLightVisuals.On, true); + _ambient.SetAmbience(entity.Owner, true); } } diff --git a/Content.Server/Light/EntitySystems/UnpoweredFlashlightSystem.cs b/Content.Server/Light/EntitySystems/UnpoweredFlashlightSystem.cs deleted file mode 100644 index a1ed71ee4c..0000000000 --- a/Content.Server/Light/EntitySystems/UnpoweredFlashlightSystem.cs +++ /dev/null @@ -1,113 +0,0 @@ -using Content.Server.Light.Events; -using Content.Shared.Actions; -using Content.Shared.Decals; -using Content.Shared.Emag.Systems; -using Content.Shared.Light; -using Content.Shared.Light.Components; -using Content.Shared.Mind.Components; -using Content.Shared.Toggleable; -using Content.Shared.Verbs; -using Robust.Shared.Audio; -using Robust.Shared.Audio.Systems; -using Robust.Shared.Prototypes; -using Robust.Shared.Random; -using Robust.Shared.Utility; - -namespace Content.Server.Light.EntitySystems -{ - public sealed class UnpoweredFlashlightSystem : EntitySystem - { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly SharedActionsSystem _actionsSystem = default!; - [Dependency] private readonly ActionContainerSystem _actionContainer = default!; - [Dependency] private readonly SharedAppearanceSystem _appearance = default!; - [Dependency] private readonly SharedAudioSystem _audioSystem = default!; - [Dependency] private readonly SharedPointLightSystem _light = default!; - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent>(AddToggleLightVerbs); - SubscribeLocalEvent(OnGetActions); - SubscribeLocalEvent(OnToggleAction); - SubscribeLocalEvent(OnMindAdded); - SubscribeLocalEvent(OnGotEmagged); - SubscribeLocalEvent(OnMapInit); - } - - private void OnMapInit(EntityUid uid, UnpoweredFlashlightComponent component, MapInitEvent args) - { - _actionContainer.EnsureAction(uid, ref component.ToggleActionEntity, component.ToggleAction); - Dirty(uid, component); - } - - private void OnToggleAction(EntityUid uid, UnpoweredFlashlightComponent component, ToggleActionEvent args) - { - if (args.Handled) - return; - - ToggleLight(uid, component); - - args.Handled = true; - } - - private void OnGetActions(EntityUid uid, UnpoweredFlashlightComponent component, GetItemActionsEvent args) - { - args.AddAction(ref component.ToggleActionEntity, component.ToggleAction); - } - - private void AddToggleLightVerbs(EntityUid uid, UnpoweredFlashlightComponent component, GetVerbsEvent args) - { - if (!args.CanAccess || !args.CanInteract) - return; - - ActivationVerb verb = new() - { - Text = Loc.GetString("toggle-flashlight-verb-get-data-text"), - Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/light.svg.192dpi.png")), - Act = () => ToggleLight(uid, component), - Priority = -1 // For things like PDA's, Open-UI and other verbs that should be higher priority. - }; - - args.Verbs.Add(verb); - } - - private void OnMindAdded(EntityUid uid, UnpoweredFlashlightComponent component, MindAddedMessage args) - { - _actionsSystem.AddAction(uid, ref component.ToggleActionEntity, component.ToggleAction); - } - - private void OnGotEmagged(EntityUid uid, UnpoweredFlashlightComponent component, ref GotEmaggedEvent args) - { - if (!_light.TryGetLight(uid, out var light)) - return; - - if (_prototypeManager.TryIndex(component.EmaggedColorsPrototype, out var possibleColors)) - { - var pick = _random.Pick(possibleColors.Colors.Values); - _light.SetColor(uid, pick, light); - } - - args.Repeatable = true; - args.Handled = true; - } - - public void ToggleLight(EntityUid uid, UnpoweredFlashlightComponent flashlight) - { - if (!_light.TryGetLight(uid, out var light)) - return; - - flashlight.LightOn = !flashlight.LightOn; - _light.SetEnabled(uid, flashlight.LightOn, light); - - _appearance.SetData(uid, UnpoweredFlashlightVisuals.LightOn, flashlight.LightOn); - - _audioSystem.PlayPvs(flashlight.ToggleSound, uid); - - RaiseLocalEvent(uid, new LightToggleEvent(flashlight.LightOn), true); - _actionsSystem.SetToggled(flashlight.ToggleActionEntity, flashlight.LightOn); - } - } -} diff --git a/Content.Server/Light/Events/LightToggleEvent.cs b/Content.Server/Light/Events/LightToggleEvent.cs deleted file mode 100644 index c7e82ce754..0000000000 --- a/Content.Server/Light/Events/LightToggleEvent.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Content.Server.Light.Events -{ - public sealed class LightToggleEvent : EntityEventArgs - { - public bool IsOn; - - public LightToggleEvent(bool isOn) - { - IsOn = isOn; - } - } -} diff --git a/Content.Server/Lock/EntitySystems/ActivatableUIRequiresLockSystem.cs b/Content.Server/Lock/EntitySystems/ActivatableUIRequiresLockSystem.cs index dfe398ebaf..04f8e2eb54 100644 --- a/Content.Server/Lock/EntitySystems/ActivatableUIRequiresLockSystem.cs +++ b/Content.Server/Lock/EntitySystems/ActivatableUIRequiresLockSystem.cs @@ -3,6 +3,7 @@ using Content.Shared.UserInterface; using Content.Shared.Lock; using Content.Server.UserInterface; +using ActivatableUISystem = Content.Shared.UserInterface.ActivatableUISystem; namespace Content.Server.Lock.EntitySystems; public sealed class ActivatableUIRequiresLockSystem : EntitySystem diff --git a/Content.Server/Magic/Components/SpellbookComponent.cs b/Content.Server/Magic/Components/SpellbookComponent.cs deleted file mode 100644 index ebc3c88043..0000000000 --- a/Content.Server/Magic/Components/SpellbookComponent.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary; - -namespace Content.Server.Magic.Components; - -/// -/// Spellbooks for having an entity learn spells as long as they've read the book and it's in their hand. -/// -[RegisterComponent] -public sealed partial class SpellbookComponent : Component -{ - /// - /// List of spells that this book has. This is a combination of the WorldSpells, EntitySpells, and InstantSpells. - /// - [ViewVariables] - public readonly List Spells = new(); - - /// - /// The three fields below is just used for initialization. - /// - [DataField("spells", customTypeSerializer: typeof(PrototypeIdDictionarySerializer))] - [ViewVariables(VVAccess.ReadWrite)] - public Dictionary SpellActions = new(); - - [DataField("learnTime")] - [ViewVariables(VVAccess.ReadWrite)] - public float LearnTime = .75f; - - /// - /// If true, the spell action stays even after the book is removed - /// - [DataField("learnPermanently")] - [ViewVariables(VVAccess.ReadWrite)] - public bool LearnPermanently; -} diff --git a/Content.Server/Magic/MagicSystem.cs b/Content.Server/Magic/MagicSystem.cs index 18602c3de8..c25aada3a0 100644 --- a/Content.Server/Magic/MagicSystem.cs +++ b/Content.Server/Magic/MagicSystem.cs @@ -1,409 +1,23 @@ -using System.Numerics; -using Content.Server.Body.Components; -using Content.Server.Body.Systems; using Content.Server.Chat.Systems; -using Content.Server.Doors.Systems; -using Content.Server.Magic.Components; -using Content.Server.Weapons.Ranged.Systems; -using Content.Shared.Actions; -using Content.Shared.Body.Components; using Content.Shared.Chat; -using Content.Shared.Coordinates.Helpers; -using Content.Shared.DoAfter; -using Content.Shared.Doors.Components; -using Content.Shared.Doors.Systems; -using Content.Shared.Interaction.Events; using Content.Shared.Magic; using Content.Shared.Magic.Events; -using Content.Shared.Maps; -using Content.Shared.Physics; -using Content.Shared.Storage; -using Robust.Server.GameObjects; -using Robust.Shared.Audio; -using Robust.Shared.Audio.Systems; -using Robust.Shared.Map; -using Robust.Shared.Map.Components; -using Robust.Shared.Random; -using Robust.Shared.Serialization.Manager; -using Robust.Shared.Spawners; namespace Content.Server.Magic; -/// -/// Handles learning and using spells (actions) -/// -public sealed class MagicSystem : EntitySystem +public sealed class MagicSystem : SharedMagicSystem { - [Dependency] private readonly ISerializationManager _seriMan = default!; - [Dependency] private readonly IComponentFactory _compFact = default!; - [Dependency] private readonly IMapManager _mapManager = default!; - [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly BodySystem _bodySystem = default!; - [Dependency] private readonly EntityLookupSystem _lookup = default!; - [Dependency] private readonly SharedDoorSystem _doorSystem = default!; - [Dependency] private readonly SharedActionsSystem _actionsSystem = default!; - [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; - [Dependency] private readonly GunSystem _gunSystem = default!; - [Dependency] private readonly PhysicsSystem _physics = default!; - [Dependency] private readonly SharedTransformSystem _transformSystem = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly ChatSystem _chat = default!; - [Dependency] private readonly ActionContainerSystem _actionContainer = default!; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnUse); - SubscribeLocalEvent(OnDoAfter); - - SubscribeLocalEvent(OnInstantSpawn); - SubscribeLocalEvent(OnTeleportSpell); - SubscribeLocalEvent(OnKnockSpell); - SubscribeLocalEvent(OnSmiteSpell); - SubscribeLocalEvent(OnWorldSpawn); - SubscribeLocalEvent(OnProjectileSpell); - SubscribeLocalEvent(OnChangeComponentsSpell); - } - - private void OnDoAfter(EntityUid uid, SpellbookComponent component, DoAfterEvent args) - { - if (args.Handled || args.Cancelled) - return; - - args.Handled = true; - if (!component.LearnPermanently) - { - _actionsSystem.GrantActions(args.Args.User, component.Spells, uid); - return; - } - - foreach (var (id, charges) in component.SpellActions) - { - // TOOD store spells entity ids on some sort of innate magic user component or something like that. - EntityUid? actionId = null; - if (_actionsSystem.AddAction(args.Args.User, ref actionId, id)) - _actionsSystem.SetCharges(actionId, charges < 0 ? null : charges); - } - - component.SpellActions.Clear(); - } - - private void OnInit(EntityUid uid, SpellbookComponent component, MapInitEvent args) - { - if (component.LearnPermanently) - return; - - foreach (var (id, charges) in component.SpellActions) - { - var spell = _actionContainer.AddAction(uid, id); - if (spell == null) - continue; - - _actionsSystem.SetCharges(spell, charges < 0 ? null : charges); - component.Spells.Add(spell.Value); - } - } - - private void OnUse(EntityUid uid, SpellbookComponent component, UseInHandEvent args) - { - if (args.Handled) - return; - - AttemptLearn(uid, component, args); - - args.Handled = true; - } - - private void AttemptLearn(EntityUid uid, SpellbookComponent component, UseInHandEvent args) - { - var doAfterEventArgs = new DoAfterArgs(EntityManager, args.User, component.LearnTime, new SpellbookDoAfterEvent(), uid, target: uid) - { - BreakOnTargetMove = true, - BreakOnUserMove = true, - BreakOnDamage = true, - NeedHand = true //What, are you going to read with your eyes only?? - }; - - _doAfter.TryStartDoAfter(doAfterEventArgs); - } - - #region Spells - - /// - /// Handles the instant action (i.e. on the caster) attempting to spawn an entity. - /// - private void OnInstantSpawn(InstantSpawnSpellEvent args) - { - if (args.Handled) - return; - - var transform = Transform(args.Performer); - - foreach (var position in GetSpawnPositions(transform, args.Pos)) - { - var ent = Spawn(args.Prototype, position.SnapToGrid(EntityManager, _mapManager)); - - if (args.PreventCollideWithCaster) - { - var comp = EnsureComp(ent); - comp.Uid = args.Performer; - } - } - - Speak(args); - args.Handled = true; - } - - private void OnProjectileSpell(ProjectileSpellEvent ev) - { - if (ev.Handled) - return; - - ev.Handled = true; - Speak(ev); - - var xform = Transform(ev.Performer); - var userVelocity = _physics.GetMapLinearVelocity(ev.Performer); - - foreach (var pos in GetSpawnPositions(xform, ev.Pos)) - { - // If applicable, this ensures the projectile is parented to grid on spawn, instead of the map. - var mapPos = pos.ToMap(EntityManager, _transformSystem); - var spawnCoords = _mapManager.TryFindGridAt(mapPos, out var gridUid, out _) - ? pos.WithEntityId(gridUid, EntityManager) - : new(_mapManager.GetMapEntityId(mapPos.MapId), mapPos.Position); - - var ent = Spawn(ev.Prototype, spawnCoords); - var direction = ev.Target.ToMapPos(EntityManager, _transformSystem) - - spawnCoords.ToMapPos(EntityManager, _transformSystem); - _gunSystem.ShootProjectile(ent, direction, userVelocity, ev.Performer, ev.Performer); - } - } - - private void OnChangeComponentsSpell(ChangeComponentsSpellEvent ev) - { - if (ev.Handled) - return; - ev.Handled = true; - Speak(ev); - - foreach (var toRemove in ev.ToRemove) - { - if (_compFact.TryGetRegistration(toRemove, out var registration)) - RemComp(ev.Target, registration.Type); - } - - foreach (var (name, data) in ev.ToAdd) - { - if (HasComp(ev.Target, data.Component.GetType())) - continue; - - var component = (Component) _compFact.GetComponent(name); - component.Owner = ev.Target; - var temp = (object) component; - _seriMan.CopyTo(data.Component, ref temp); - EntityManager.AddComponent(ev.Target, (Component) temp!); - } - } - - private List GetSpawnPositions(TransformComponent casterXform, MagicSpawnData data) - { - switch (data) - { - case TargetCasterPos: - return new List(1) {casterXform.Coordinates}; - case TargetInFront: - { - // This is shit but you get the idea. - var directionPos = casterXform.Coordinates.Offset(casterXform.LocalRotation.ToWorldVec().Normalized()); - - if (!TryComp(casterXform.GridUid, out var mapGrid)) - return new List(); - - if (!directionPos.TryGetTileRef(out var tileReference, EntityManager, _mapManager)) - return new List(); - - var tileIndex = tileReference.Value.GridIndices; - var coords = mapGrid.GridTileToLocal(tileIndex); - EntityCoordinates coordsPlus; - EntityCoordinates coordsMinus; - - var dir = casterXform.LocalRotation.GetCardinalDir(); - switch (dir) - { - case Direction.North: - case Direction.South: - { - coordsPlus = mapGrid.GridTileToLocal(tileIndex + (1, 0)); - coordsMinus = mapGrid.GridTileToLocal(tileIndex + (-1, 0)); - return new List(3) - { - coords, - coordsPlus, - coordsMinus, - }; - } - case Direction.East: - case Direction.West: - { - coordsPlus = mapGrid.GridTileToLocal(tileIndex + (0, 1)); - coordsMinus = mapGrid.GridTileToLocal(tileIndex + (0, -1)); - return new List(3) - { - coords, - coordsPlus, - coordsMinus, - }; - } - } - - return new List(); - } - default: - throw new ArgumentOutOfRangeException(); - } - } - - /// - /// Teleports the user to the clicked location - /// - /// - private void OnTeleportSpell(TeleportSpellEvent args) - { - if (args.Handled) - return; - - var transform = Transform(args.Performer); - - if (transform.MapID != args.Target.GetMapId(EntityManager)) return; - - _transformSystem.SetCoordinates(args.Performer, args.Target); - transform.AttachToGridOrMap(); - _audio.PlayPvs(args.BlinkSound, args.Performer, AudioParams.Default.WithVolume(args.BlinkVolume)); - Speak(args); - args.Handled = true; + SubscribeLocalEvent(OnSpellSpoken); } - /// - /// Opens all doors within range - /// - /// - private void OnKnockSpell(KnockSpellEvent args) + private void OnSpellSpoken(ref SpeakSpellEvent args) { - if (args.Handled) - return; - - args.Handled = true; - Speak(args); - - //Get the position of the player - var transform = Transform(args.Performer); - var coords = transform.Coordinates; - - _audio.PlayPvs(args.KnockSound, args.Performer, AudioParams.Default.WithVolume(args.KnockVolume)); - - //Look for doors and don't open them if they're already open. - foreach (var entity in _lookup.GetEntitiesInRange(coords, args.Range)) - { - if (TryComp(entity, out var bolts)) - _doorSystem.SetBoltsDown((entity, bolts), false); - - if (TryComp(entity, out var doorComp) && doorComp.State is not DoorState.Open) - _doorSystem.StartOpening(entity); - } - } - - private void OnSmiteSpell(SmiteSpellEvent ev) - { - if (ev.Handled) - return; - - ev.Handled = true; - Speak(ev); - - var direction = Transform(ev.Target).MapPosition.Position - Transform(ev.Performer).MapPosition.Position; - var impulseVector = direction * 10000; - - _physics.ApplyLinearImpulse(ev.Target, impulseVector); - - if (!TryComp(ev.Target, out var body)) - return; - - var ents = _bodySystem.GibBody(ev.Target, true, body); - - if (!ev.DeleteNonBrainParts) - return; - - foreach (var part in ents) - { - // just leaves a brain and clothes - if (HasComp(part) && !HasComp(part)) - { - QueueDel(part); - } - } - } - - /// - /// Spawns entity prototypes from a list within range of click. - /// - /// - /// It will offset mobs after the first mob based on the OffsetVector2 property supplied. - /// - /// The Spawn Spell Event args. - private void OnWorldSpawn(WorldSpawnSpellEvent args) - { - if (args.Handled) - return; - - var targetMapCoords = args.Target; - - SpawnSpellHelper(args.Contents, targetMapCoords, args.Lifetime, args.Offset); - Speak(args); - args.Handled = true; - } - - /// - /// Loops through a supplied list of entity prototypes and spawns them - /// - /// - /// If an offset of 0, 0 is supplied then the entities will all spawn on the same tile. - /// Any other offset will spawn entities starting from the source Map Coordinates and will increment the supplied - /// offset - /// - /// The list of Entities to spawn in - /// Map Coordinates where the entities will spawn - /// Check to see if the entities should self delete - /// A Vector2 offset that the entities will spawn in - private void SpawnSpellHelper(List entityEntries, EntityCoordinates entityCoords, float? lifetime, Vector2 offsetVector2) - { - var getProtos = EntitySpawnCollection.GetSpawns(entityEntries, _random); - - var offsetCoords = entityCoords; - foreach (var proto in getProtos) - { - // TODO: Share this code with instant because they're both doing similar things for positioning. - var entity = Spawn(proto, offsetCoords); - offsetCoords = offsetCoords.Offset(offsetVector2); - - if (lifetime != null) - { - var comp = EnsureComp(entity); - comp.Lifetime = lifetime.Value; - } - } - } - - #endregion - - private void Speak(BaseActionEvent args) - { - if (args is not ISpeakSpell speak || string.IsNullOrWhiteSpace(speak.Speech)) - return; - - _chat.TrySendInGameICMessage(args.Performer, Loc.GetString(speak.Speech), - InGameICChatType.Speak, false); + _chat.TrySendInGameICMessage(args.Performer, Loc.GetString(args.Speech), InGameICChatType.Speak, false); } } diff --git a/Content.Server/MagicMirror/MagicMirrorSystem.cs b/Content.Server/MagicMirror/MagicMirrorSystem.cs index aef6e11d71..6cbfc55cac 100644 --- a/Content.Server/MagicMirror/MagicMirrorSystem.cs +++ b/Content.Server/MagicMirror/MagicMirrorSystem.cs @@ -16,13 +16,12 @@ namespace Content.Server.MagicMirror; /// /// Allows humanoids to change their appearance mid-round. /// -public sealed class MagicMirrorSystem : EntitySystem +public sealed class MagicMirrorSystem : SharedMagicMirrorSystem { [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; [Dependency] private readonly MarkingManager _markings = default!; [Dependency] private readonly HumanoidAppearanceSystem _humanoid = default!; - [Dependency] private readonly SharedInteractionSystem _interaction = default!; [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; public override void Initialize() @@ -32,7 +31,7 @@ public override void Initialize() Subs.BuiEvents(MagicMirrorUiKey.Key, subs => { - subs.Event(OnUIClosed); + subs.Event(OnUiClosed); subs.Event(OnMagicMirrorSelect); subs.Event(OnTryMagicMirrorChangeColor); subs.Event(OnTryMagicMirrorAddSlot); @@ -45,16 +44,6 @@ public override void Initialize() SubscribeLocalEvent(OnChangeColorDoAfter); SubscribeLocalEvent(OnRemoveSlotDoAfter); SubscribeLocalEvent(OnAddSlotDoAfter); - - SubscribeLocalEvent(OnMirrorRangeCheck); - } - - private void OnMirrorRangeCheck(EntityUid uid, MagicMirrorComponent component, ref BoundUserInterfaceCheckRangeEvent args) - { - if (!Exists(component.Target) || !_interaction.InRangeUnobstructed(uid, component.Target.Value)) - { - args.Result = BoundUserInterfaceRangeResult.Fail; - } } private void OnMagicMirrorInteract(Entity mirror, ref AfterInteractEvent args) @@ -62,10 +51,7 @@ private void OnMagicMirrorInteract(Entity mirror, ref Afte if (!args.CanReach || args.Target == null) return; - if (!TryComp(args.User, out var actor)) - return; - - if (!_uiSystem.TryOpen(mirror.Owner, MagicMirrorUiKey.Key, actor.PlayerSession)) + if (!_uiSystem.TryOpenUi(mirror.Owner, MagicMirrorUiKey.Key, args.User)) return; UpdateInterface(mirror.Owner, args.Target.Value, mirror.Comp); @@ -79,7 +65,7 @@ private void OnOpenUIAttempt(EntityUid uid, MagicMirrorComponent mirror, Activat private void OnMagicMirrorSelect(EntityUid uid, MagicMirrorComponent component, MagicMirrorSelectMessage message) { - if (component.Target is not { } target || message.Session.AttachedEntity is not { } user) + if (component.Target is not { } target) return; _doAfterSystem.Cancel(component.DoAfter); @@ -92,7 +78,7 @@ private void OnMagicMirrorSelect(EntityUid uid, MagicMirrorComponent component, Marking = message.Marking, }; - _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, user, component.SelectSlotTime, doAfter, uid, target: target, used: uid) + _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, message.Actor, component.SelectSlotTime, doAfter, uid, target: target, used: uid) { DistanceThreshold = SharedInteractionSystem.InteractionRange, BreakOnTargetMove = true, @@ -136,7 +122,7 @@ private void OnSelectSlotDoAfter(EntityUid uid, MagicMirrorComponent component, private void OnTryMagicMirrorChangeColor(EntityUid uid, MagicMirrorComponent component, MagicMirrorChangeColorMessage message) { - if (component.Target is not { } target || message.Session.AttachedEntity is not { } user) + if (component.Target is not { } target) return; _doAfterSystem.Cancel(component.DoAfter); @@ -149,7 +135,7 @@ private void OnTryMagicMirrorChangeColor(EntityUid uid, MagicMirrorComponent com Colors = message.Colors, }; - _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, user, component.ChangeSlotTime, doAfter, uid, target: target, used: uid) + _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, message.Actor, component.ChangeSlotTime, doAfter, uid, target: target, used: uid) { BreakOnTargetMove = true, BreakOnDamage = true, @@ -191,7 +177,7 @@ private void OnChangeColorDoAfter(EntityUid uid, MagicMirrorComponent component, private void OnTryMagicMirrorRemoveSlot(EntityUid uid, MagicMirrorComponent component, MagicMirrorRemoveSlotMessage message) { - if (component.Target is not { } target || message.Session.AttachedEntity is not { } user) + if (component.Target is not { } target) return; _doAfterSystem.Cancel(component.DoAfter); @@ -203,7 +189,7 @@ private void OnTryMagicMirrorRemoveSlot(EntityUid uid, MagicMirrorComponent comp Slot = message.Slot, }; - _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, user, component.RemoveSlotTime, doAfter, uid, target: target, used: uid) + _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, message.Actor, component.RemoveSlotTime, doAfter, uid, target: target, used: uid) { DistanceThreshold = SharedInteractionSystem.InteractionRange, BreakOnTargetMove = true, @@ -250,9 +236,6 @@ private void OnTryMagicMirrorAddSlot(EntityUid uid, MagicMirrorComponent compone if (component.Target == null) return; - if (message.Session.AttachedEntity == null) - return; - _doAfterSystem.Cancel(component.DoAfter); component.DoAfter = null; @@ -261,7 +244,7 @@ private void OnTryMagicMirrorAddSlot(EntityUid uid, MagicMirrorComponent compone Category = message.Category, }; - _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, message.Session.AttachedEntity.Value, component.AddSlotTime, doAfter, uid, target: component.Target.Value, used: uid) + _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, message.Actor, component.AddSlotTime, doAfter, uid, target: component.Target.Value, used: uid) { BreakOnTargetMove = true, BreakOnDamage = true, @@ -324,12 +307,15 @@ private void UpdateInterface(EntityUid mirrorUid, EntityUid targetUid, MagicMirr facialHair, humanoid.MarkingSet.PointsLeft(MarkingCategories.FacialHair) + facialHair.Count); + // TODO: Component states component.Target = targetUid; - _uiSystem.TrySetUiState(mirrorUid, MagicMirrorUiKey.Key, state); + _uiSystem.SetUiState(mirrorUid, MagicMirrorUiKey.Key, state); + Dirty(mirrorUid, component); } - private void OnUIClosed(Entity ent, ref BoundUIClosedEvent args) + private void OnUiClosed(Entity ent, ref BoundUIClosedEvent args) { ent.Comp.Target = null; + Dirty(ent); } } diff --git a/Content.Server/Mail/Components/DelayedItemComponent.cs b/Content.Server/Mail/Components/DelayedItemComponent.cs new file mode 100644 index 0000000000..f73aef85b0 --- /dev/null +++ b/Content.Server/Mail/Components/DelayedItemComponent.cs @@ -0,0 +1,15 @@ +namespace Content.Server.Mail.Components; + +/// +/// A placeholder for another entity, spawned when dropped or placed in someone's hands. +/// Useful for storing instant effect entities, e.g. smoke, in the mail. +/// +[RegisterComponent] +public sealed partial class DelayedItemComponent : Component +{ + /// + /// The entity to replace this when opened or dropped. + /// + [DataField] + public string Item = "None"; +} diff --git a/Content.Server/Mail/Components/MailComponent.cs b/Content.Server/Mail/Components/MailComponent.cs new file mode 100644 index 0000000000..af0bdaa87f --- /dev/null +++ b/Content.Server/Mail/Components/MailComponent.cs @@ -0,0 +1,111 @@ +using System.Threading; +using Robust.Shared.Audio; +using Content.Shared.Storage; +using Content.Shared.Mail; + +namespace Content.Server.Mail.Components; + +[RegisterComponent] +public sealed partial class MailComponent : SharedMailComponent +{ + [DataField] + public string Recipient = "None"; + + [DataField] + public string RecipientJob = "None"; + + /// + /// Why do we not use LockComponent? + /// Because this can't be locked again, + /// and we have special conditions for unlocking, + /// and we don't want to add a verb. + /// + [DataField] + public bool IsLocked = true; + + /// + /// Is this parcel profitable to deliver for the station? + /// + /// + /// The station won't receive any award on delivery if this is false. + /// This is useful for broken fragile packages and packages that were + /// not delivered in time. + /// + [DataField] + public bool IsProfitable = true; + + /// + /// Is this package considered fragile? + /// + /// + /// This can be set to true in the YAML files for a mail delivery to + /// always be Fragile, despite its contents. + /// + [DataField] + public bool IsFragile = false; + + /// + /// Is this package considered priority mail? + /// + /// + /// There will be a timer set for its successful delivery. The + /// station's bank account will be penalized if it is not delivered on + /// time.
+ ///
+ /// This is set to false on successful delivery.
+ ///
+ /// This can be set to true in the YAML files for a mail delivery to always be Priority. + ///
+ [DataField] + public bool IsPriority = false; + + /// + /// Whether this parcel is large. + /// + [DataField] + public bool IsLarge = false; + + /// + /// What will be packaged when the mail is spawned. + /// + [DataField] + public List Contents = new(); + + /// + /// The amount that cargo will be awarded for delivering this mail. + /// + [DataField] + public int Bounty = 750; + + /// + /// Penalty if the mail is destroyed. + /// + [DataField] + public int Penalty = -250; + + /// + /// The sound that's played when the mail's lock is broken. + /// + [DataField] + public SoundSpecifier PenaltySound = new SoundPathSpecifier("/Audio/Machines/Nuke/angry_beep.ogg"); + + /// + /// The sound that's played when the mail's opened. + /// + [DataField] + public SoundSpecifier OpenSound = new SoundPathSpecifier("/Audio/Effects/packetrip.ogg"); + + /// + /// The sound that's played when the mail's lock has been emagged. + /// + [DataField] + public SoundSpecifier EmagSound = new SoundCollectionSpecifier("sparks"); + + /// + /// Whether this component is enabled. + /// Removed when it becomes trash. + /// + public bool IsEnabled = true; + + public CancellationTokenSource? PriorityCancelToken; +} diff --git a/Content.Server/Mail/Components/MailReceiverComponent.cs b/Content.Server/Mail/Components/MailReceiverComponent.cs new file mode 100644 index 0000000000..281a27ea79 --- /dev/null +++ b/Content.Server/Mail/Components/MailReceiverComponent.cs @@ -0,0 +1,4 @@ +namespace Content.Server.Mail.Components; + +[RegisterComponent] +public sealed partial class MailReceiverComponent : Component {} diff --git a/Content.Server/Mail/Components/MailTeleporterComponent.cs b/Content.Server/Mail/Components/MailTeleporterComponent.cs new file mode 100644 index 0000000000..81f58bc7a5 --- /dev/null +++ b/Content.Server/Mail/Components/MailTeleporterComponent.cs @@ -0,0 +1,118 @@ +using Robust.Shared.Audio; + +namespace Content.Server.Mail.Components; + +/// +/// This is for the mail teleporter. +/// Random mail will be teleported to this every few minutes. +/// +[RegisterComponent] +public sealed partial class MailTeleporterComponent : Component +{ + // Not starting accumulator at 0 so mail carriers have some deliveries to make shortly after roundstart. + [DataField] + public float Accumulator = 285f; + + [DataField] + public TimeSpan TeleportInterval = TimeSpan.FromMinutes(5); + + /// + /// The sound that's played when new mail arrives. + /// + [DataField] + public SoundSpecifier TeleportSound = new SoundPathSpecifier("/Audio/Effects/teleport_arrival.ogg"); + + /// + /// The MailDeliveryPoolPrototype that's used to select what mail this + /// teleporter can deliver. + /// + [DataField] + public string MailPool = "RandomDeltaVMailDeliveryPool"; + + /// + /// How many mail candidates do we need per actual delivery sent when + /// the mail goes out? The number of candidates is divided by this number + /// to determine how many deliveries will be teleported in. + /// It does not determine unique recipients. That is random. + /// + [DataField] + public int CandidatesPerDelivery = 8; + + [DataField] + public int MinimumDeliveriesPerTeleport = 1; + + /// + /// Do not teleport any more mail in, if there are at least this many + /// undelivered parcels. + /// + /// + /// Currently this works by checking how many MailComponent entities + /// are sitting on the teleporter's tile.

+ /// + /// It should be noted that if the number of actual deliveries to be + /// made based on the number of candidates divided by candidates per + /// delivery exceeds this number, the teleporter will spawn more mail + /// than this number.

+ /// + /// This is just a simple check to see if anyone's been picking up the + /// mail lately to prevent entity bloat for the sake of performance. + ///
+ [DataField] + public int MaximumUndeliveredParcels = 5; + + /// + /// Any item that breaks or is destroyed in less than this amount of + /// damage is one of the types of items considered fragile. + /// + [DataField] + public int FragileDamageThreshold = 10; + + /// + /// What's the bonus for delivering a fragile package intact? + /// + [DataField] + public int FragileBonus = 100; + + /// + /// What's the malus for failing to deliver a fragile package? + /// + [DataField] + public int FragileMalus = -100; + + /// + /// What's the chance for any one delivery to be marked as priority mail? + /// + [DataField] + public float PriorityChance = 0.1f; + + /// + /// How long until a priority delivery is considered as having failed + /// if not delivered? + /// + [DataField] + public TimeSpan PriorityDuration = TimeSpan.FromMinutes(5); + + /// + /// What's the bonus for delivering a priority package on time? + /// + [DataField] + public int PriorityBonus = 250; + + /// + /// What's the malus for failing to deliver a priority package? + /// + [DataField] + public int PriorityMalus = -250; + + /// + /// What's the bonus for delivering a large package intact? + /// + [DataField] + public int LargeBonus = 1500; + + /// + /// What's the malus for failing to deliver a large package? + /// + [DataField] + public int LargeMalus = -500; +} diff --git a/Content.Server/Mail/Components/StationMailRouterComponent.cs b/Content.Server/Mail/Components/StationMailRouterComponent.cs new file mode 100644 index 0000000000..6f6df1e10b --- /dev/null +++ b/Content.Server/Mail/Components/StationMailRouterComponent.cs @@ -0,0 +1,9 @@ +namespace Content.Server.Mail.Components; + +/// +/// Designates a station as a place for sending and receiving mail. +/// +[RegisterComponent] +public sealed partial class StationMailRouterComponent : Component +{ +} diff --git a/Content.Server/Mail/MailCommands.cs b/Content.Server/Mail/MailCommands.cs new file mode 100644 index 0000000000..390de93fb3 --- /dev/null +++ b/Content.Server/Mail/MailCommands.cs @@ -0,0 +1,142 @@ +using System.Linq; +using Robust.Shared.Console; +using Robust.Shared.Containers; +using Robust.Shared.Prototypes; +using Content.Shared.Administration; +using Content.Server.Administration; +using Content.Server.Mail.Components; +using Content.Server.Mail.Systems; + +namespace Content.Server.Mail; + +[AdminCommand(AdminFlags.Fun)] +public sealed class MailToCommand : IConsoleCommand +{ + public string Command => "mailto"; + public string Description => Loc.GetString("command-mailto-description", ("requiredComponent", nameof(MailReceiverComponent))); + public string Help => Loc.GetString("command-mailto-help", ("command", Command)); + + [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; + + private readonly string _blankMailPrototype = "MailAdminFun"; + private readonly string _blankLargeMailPrototype = "MailLargeAdminFun"; // Frontier: large mail + private readonly string _container = "storagebase"; + private readonly string _mailContainer = "contents"; + + + public async void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (args.Length < 4) + { + shell.WriteError(Loc.GetString("shell-wrong-arguments-number")); + return; + } + + if (!EntityUid.TryParse(args[0], out var recipientUid)) + { + shell.WriteError(Loc.GetString("shell-entity-uid-must-be-number")); + return; + } + + if (!EntityUid.TryParse(args[1], out var containerUid)) + { + shell.WriteError(Loc.GetString("shell-entity-uid-must-be-number")); + return; + } + + if (!bool.TryParse(args[2], out var isFragile) || !bool.TryParse(args[3], out var isPriority)) + { + shell.WriteError(Loc.GetString("shell-invalid-bool")); + return; + } + + var isLarge = false; + if (args.Length > 4 && !bool.TryParse(args[4], out isLarge)) + { + shell.WriteError(Loc.GetString("shell-invalid-bool")); + return; + } + var mailPrototype = isLarge ? _blankLargeMailPrototype : _blankMailPrototype; + + + var mailSystem = _entitySystemManager.GetEntitySystem(); + var containerSystem = _entitySystemManager.GetEntitySystem(); + + if (!_entityManager.TryGetComponent(recipientUid, out MailReceiverComponent? mailReceiver)) + { + shell.WriteLine(Loc.GetString("command-mailto-no-mailreceiver", ("requiredComponent", nameof(MailReceiverComponent)))); + return; + } + + if (!_prototypeManager.HasIndex(mailPrototype)) + { + shell.WriteLine(Loc.GetString("command-mailto-no-blankmail", ("blankMail", mailPrototype))); + return; + } + + if (!containerSystem.TryGetContainer(containerUid, _container, out var targetContainer)) + { + shell.WriteLine(Loc.GetString("command-mailto-invalid-container", ("requiredContainer", _container))); + return; + } + + if (!mailSystem.TryGetMailRecipientForReceiver(mailReceiver, out MailRecipient? recipient)) + { + shell.WriteLine(Loc.GetString("command-mailto-unable-to-receive")); + return; + } + + if (!mailSystem.TryGetMailTeleporterForReceiver(mailReceiver, out MailTeleporterComponent? teleporterComponent)) + { + shell.WriteLine(Loc.GetString("command-mailto-no-teleporter-found")); + return; + } + + var mailUid = _entityManager.SpawnEntity(mailPrototype, _entityManager.GetComponent(containerUid).Coordinates); + var mailContents = containerSystem.EnsureContainer(mailUid, _mailContainer); + + if (!_entityManager.TryGetComponent(mailUid, out MailComponent? mailComponent)) + { + shell.WriteLine(Loc.GetString("command-mailto-bogus-mail", ("blankMail", mailPrototype), ("requiredMailComponent", nameof(MailComponent)))); + return; + } + + foreach (var entity in targetContainer.ContainedEntities.ToArray()) + containerSystem.Insert(entity, mailContents); + + mailComponent.IsFragile = isFragile; + mailComponent.IsPriority = isPriority; + mailComponent.IsLarge = isLarge; + + mailSystem.SetupMail(mailUid, teleporterComponent, recipient.Value); + + var teleporterQueue = containerSystem.EnsureContainer(teleporterComponent.Owner, "queued"); + containerSystem.Insert(mailUid, teleporterQueue); + shell.WriteLine(Loc.GetString("command-mailto-success", ("timeToTeleport", teleporterComponent.TeleportInterval.TotalSeconds - teleporterComponent.Accumulator))); + } +} + +[AdminCommand(AdminFlags.Fun)] +public sealed class MailNowCommand : IConsoleCommand +{ + public string Command => "mailnow"; + public string Description => Loc.GetString("command-mailnow"); + public string Help => Loc.GetString("command-mailnow-help", ("command", Command)); + + [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; + + public async void Execute(IConsoleShell shell, string argStr, string[] args) + { + var entitySystem = _entitySystemManager.GetEntitySystem(); + + foreach (var mailTeleporter in _entityManager.EntityQuery()) + { + mailTeleporter.Accumulator += (float) mailTeleporter.TeleportInterval.TotalSeconds - mailTeleporter.Accumulator; + } + + shell.WriteLine(Loc.GetString("command-mailnow-success")); + } +} diff --git a/Content.Server/Mail/MailConstants.cs b/Content.Server/Mail/MailConstants.cs new file mode 100644 index 0000000000..38d37a1326 --- /dev/null +++ b/Content.Server/Mail/MailConstants.cs @@ -0,0 +1,37 @@ +namespace Content.Server.Mail; + +/// +/// A set of localized strings related to mail entities +/// +public struct MailEntityStrings +{ + public string NameAddressed; + public string DescClose; + public string DescFar; +} + +/// +/// Constants related to mail. +/// +public static class MailConstants +{ + /// + /// Locale strings related to small parcels. + /// + public static readonly MailEntityStrings Mail = new() + { + NameAddressed = "mail-item-name-addressed", + DescClose = "mail-desc-close", + DescFar = "mail-desc-far" + }; + + /// + /// Locale strings related to large packages. + /// + public static readonly MailEntityStrings MailLarge = new() + { + NameAddressed = "mail-large-item-name-addressed", + DescClose = "mail-large-desc-close", + DescFar = "mail-large-desc-far" + }; +} diff --git a/Content.Server/Mail/Systems/DelayedItemSystem.cs b/Content.Server/Mail/Systems/DelayedItemSystem.cs new file mode 100644 index 0000000000..59aaa3aff6 --- /dev/null +++ b/Content.Server/Mail/Systems/DelayedItemSystem.cs @@ -0,0 +1,43 @@ +using Content.Shared.Damage; +using Content.Shared.Hands; +using Robust.Shared.Containers; + +namespace Content.Server.Mail.Systems; + +/// +/// A placeholder for another entity, spawned when taken out of a container, with the placeholder deleted shortly after. +/// Useful for storing instant effect entities, e.g. smoke, in the mail. +/// +public sealed class DelayedItemSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnDropAttempt); + SubscribeLocalEvent(OnHandEquipped); + SubscribeLocalEvent(OnDamageChanged); + SubscribeLocalEvent(OnRemovedFromContainer); + } + + private void OnRemovedFromContainer(EntityUid uid, Components.DelayedItemComponent component, ContainerModifiedMessage args) + { + Spawn(component.Item, Transform(uid).Coordinates); + } + + private void OnHandEquipped(EntityUid uid, Components.DelayedItemComponent component, EquippedHandEvent args) + { + EntityManager.DeleteEntity(uid); + } + + private void OnDropAttempt(EntityUid uid, Components.DelayedItemComponent component, DropAttemptEvent args) + { + EntityManager.DeleteEntity(uid); + } + + private void OnDamageChanged(EntityUid uid, Components.DelayedItemComponent component, DamageChangedEvent args) + { + Spawn(component.Item, Transform(uid).Coordinates); + EntityManager.DeleteEntity(uid); + } +} diff --git a/Content.Server/Mail/Systems/MailSystem.cs b/Content.Server/Mail/Systems/MailSystem.cs new file mode 100644 index 0000000000..e80febd230 --- /dev/null +++ b/Content.Server/Mail/Systems/MailSystem.cs @@ -0,0 +1,756 @@ +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading; +using Content.Server.Access.Systems; +using Content.Server.Cargo.Components; +using Content.Server.Cargo.Systems; +using Content.Server.Chat.Systems; +using Content.Server.Chemistry.Containers.EntitySystems; +using Content.Server.Damage.Components; +using Content.Server.Destructible; +using Content.Server.Destructible.Thresholds; +using Content.Server.Destructible.Thresholds.Behaviors; +using Content.Server.Destructible.Thresholds.Triggers; +using Content.Server.Item; +using Content.Server.Mail.Components; +using Content.Server.Mind; +using Content.Server.Popups; +using Content.Server.Power.Components; +using Content.Server.Spawners.EntitySystems; +using Content.Server.Station.Systems; +using Content.Shared.Access; +using Content.Shared.Access.Components; +using Content.Shared.Access.Systems; +using Content.Shared.Chat; +using Content.Shared.Damage; +using Content.Shared.Destructible; +using Content.Shared.Emag.Components; +using Content.Shared.Emag.Systems; +using Content.Shared.Examine; +using Content.Shared.Fluids.Components; +using Content.Shared.Hands.EntitySystems; +using Content.Shared.Interaction; +using Content.Shared.Interaction.Events; +using Content.Shared.Mail; +using Content.Shared.Maps; +using Content.Shared.Nutrition.Components; +using Content.Shared.Nutrition.EntitySystems; +using Content.Shared.PDA; +using Content.Shared.Roles; +using Content.Shared.Storage; +using Content.Shared.Tag; +using Robust.Shared.Audio; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Containers; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; +using Timer = Robust.Shared.Timing.Timer; + +namespace Content.Server.Mail.Systems; + +public sealed class MailSystem : EntitySystem +{ + [Dependency] private readonly PopupSystem _popupSystem = default!; + [Dependency] private readonly AccessReaderSystem _accessSystem = default!; + [Dependency] private readonly SharedHandsSystem _handsSystem = default!; + [Dependency] private readonly IdCardSystem _idCardSystem = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly TagSystem _tagSystem = default!; + [Dependency] private readonly CargoSystem _cargoSystem = default!; + [Dependency] private readonly StationSystem _stationSystem = default!; + [Dependency] private readonly ChatSystem _chatSystem = default!; + [Dependency] private readonly OpenableSystem _openable = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly SharedContainerSystem _containerSystem = default!; + [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!; + [Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!; + [Dependency] private readonly SharedAudioSystem _audioSystem = default!; + [Dependency] private readonly DamageableSystem _damageableSystem = default!; + [Dependency] private readonly MindSystem _mindSystem = default!; + [Dependency] private readonly MetaDataSystem _metaDataSystem = default!; + + // DeltaV - system that keeps track of mail and cargo stats + [Dependency] private readonly LogisticStatsSystem _logisticsStatsSystem = default!; + + private ISawmill _sawmill = default!; + + public override void Initialize() + { + base.Initialize(); + + _sawmill = Logger.GetSawmill("mail"); + + SubscribeLocalEvent(OnSpawnPlayer, after: new[] { typeof(SpawnPointSystem) }); + + SubscribeLocalEvent(OnRemove); + SubscribeLocalEvent(OnUseInHand); + SubscribeLocalEvent(OnAfterInteractUsing); + SubscribeLocalEvent(OnExamined); + SubscribeLocalEvent(OnDestruction); + SubscribeLocalEvent(OnDamage); + SubscribeLocalEvent(OnBreak); + SubscribeLocalEvent(OnMailEmagged); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + foreach (var mailTeleporter in EntityQuery()) + { + if (TryComp(mailTeleporter.Owner, out var power) && !power.Powered) + continue; + + mailTeleporter.Accumulator += frameTime; + if (mailTeleporter.Accumulator < mailTeleporter.TeleportInterval.TotalSeconds) + continue; + + mailTeleporter.Accumulator -= (float) mailTeleporter.TeleportInterval.TotalSeconds; + + SpawnMail(mailTeleporter.Owner, mailTeleporter); + } + } + + /// + /// Dynamically add the MailReceiver component to appropriate entities. + /// + private void OnSpawnPlayer(PlayerSpawningEvent args) + { + if (args.SpawnResult == null + || args.Job == null + || args.Station is not {} station + || !HasComp(station)) + return; + + AddComp(args.SpawnResult.Value); + } + + private void OnRemove(EntityUid uid, MailComponent component, ComponentRemove args) + { + // Make sure the priority timer doesn't run. + if (component.PriorityCancelToken != null) + component.PriorityCancelToken.Cancel(); + } + + /// + /// Try to open the mail. + /// + private void OnUseInHand(EntityUid uid, MailComponent component, UseInHandEvent args) + { + if (!component.IsEnabled) + return; + if (component.IsLocked) + { + _popupSystem.PopupEntity(Loc.GetString("mail-locked"), uid, args.User); + return; + } + OpenMail(uid, component, args.User); + } + + /// + /// Handle logic similar between a normal mail unlock and an emag + /// frying out the lock. + /// + private void UnlockMail(EntityUid uid, MailComponent component) + { + component.IsLocked = false; + UpdateAntiTamperVisuals(uid, false); + + if (component.IsPriority) + { + // This is a successful delivery. Keep the failure timer from triggering. + if (component.PriorityCancelToken != null) + component.PriorityCancelToken.Cancel(); + + // The priority tape is visually considered to be a part of the + // anti-tamper lock, so remove that too. + _appearanceSystem.SetData(uid, MailVisuals.IsPriority, false); + + // The examination code depends on this being false to not show + // the priority tape description anymore. + component.IsPriority = false; + } + } + + /// + /// Check the ID against the mail's lock + /// + private void OnAfterInteractUsing(EntityUid uid, MailComponent component, AfterInteractUsingEvent args) + { + if (!args.CanReach || !component.IsLocked || !TryComp(uid, out var access)) + return; + + IdCardComponent? idCard = null; // We need an ID card. + if (HasComp(args.Used)) // Can we find it in a PDA if the user is using that? + { + _idCardSystem.TryGetIdCard(args.Used, out var pdaID); + idCard = pdaID; + } + + if (HasComp(args.Used)) // Or are they using an id card directly? + idCard = Comp(args.Used); + + if (idCard == null) // Return if we still haven't found an id card. + return; + + if (!HasComp(uid)) + { + if (idCard.FullName != component.Recipient || idCard.JobTitle != component.RecipientJob) + { + _popupSystem.PopupEntity(Loc.GetString("mail-recipient-mismatch"), uid, args.User); + return; + } + + if (!_accessSystem.IsAllowed(uid, args.User)) + { + _popupSystem.PopupEntity(Loc.GetString("mail-invalid-access"), uid, args.User); + return; + } + } + + ExecuteForEachLogisticsStats(uid, (station, logisticStats) => + { + _logisticsStatsSystem.AddOpenedMailEarnings(station, + logisticStats, + component.IsProfitable ? component.Bounty : 0); + }); + UnlockMail(uid, component); + + if (!component.IsProfitable) + { + _popupSystem.PopupEntity(Loc.GetString("mail-unlocked"), uid, args.User); + return; + } + + _popupSystem.PopupEntity(Loc.GetString("mail-unlocked-reward", ("bounty", component.Bounty)), uid, args.User); + + component.IsProfitable = false; + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var station, out var account)) + { + if (_stationSystem.GetOwningStation(uid) != station) + continue; + + _cargoSystem.UpdateBankAccount(station, account, component.Bounty); + } + } + + private void OnExamined(EntityUid uid, MailComponent component, ExaminedEvent args) + { + MailEntityStrings mailEntityStrings = component.IsLarge ? MailConstants.MailLarge : MailConstants.Mail; //Frontier: mail types stored per type (large mail) + if (!args.IsInDetailsRange) + { + args.PushMarkup(Loc.GetString(mailEntityStrings.DescFar)); // Frontier: mail constants struct + return; + } + + args.PushMarkup(Loc.GetString(mailEntityStrings.DescClose, ("name", component.Recipient), ("job", component.RecipientJob))); // Frontier: mail constants struct + + if (component.IsFragile) + args.PushMarkup(Loc.GetString("mail-desc-fragile")); + + if (component.IsPriority) + { + if (component.IsProfitable) + args.PushMarkup(Loc.GetString("mail-desc-priority")); + else + args.PushMarkup(Loc.GetString("mail-desc-priority-inactive")); + } + } + + /// + /// Penalize a station for a failed delivery. + /// + /// + /// This will mark a parcel as no longer being profitable, which will + /// prevent multiple failures on different conditions for the same + /// delivery.

+ /// + /// The standard penalization is breaking the anti-tamper lock, + /// but this allows a delivery to fail for other reasons too + /// while having a generic function to handle different messages. + ///
+ public void PenalizeStationFailedDelivery(EntityUid uid, MailComponent component, string localizationString) + { + if (!component.IsProfitable) + return; + + _chatSystem.TrySendInGameICMessage(uid, Loc.GetString(localizationString, ("credits", component.Penalty)), InGameICChatType.Speak, false); + _audioSystem.PlayPvs(component.PenaltySound, uid); + + component.IsProfitable = false; + + if (component.IsPriority) + _appearanceSystem.SetData(uid, MailVisuals.IsPriorityInactive, true); + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var station, out var account)) + { + if (_stationSystem.GetOwningStation(uid) != station) + continue; + + _cargoSystem.UpdateBankAccount(station, account, component.Penalty); + return; + } + } + + private void OnDestruction(EntityUid uid, MailComponent component, DestructionEventArgs args) + { + if (component.IsLocked) + { + // DeltaV - Tampered mail recorded to logistic stats + ExecuteForEachLogisticsStats(uid, (station, logisticStats) => + { + _logisticsStatsSystem.AddTamperedMailLosses(station, + logisticStats, + component.IsProfitable ? component.Penalty : 0); + }); + + PenalizeStationFailedDelivery(uid, component, "mail-penalty-lock"); + } + + if (component.IsEnabled) + OpenMail(uid, component); + + UpdateAntiTamperVisuals(uid, false); + } + + private void OnDamage(EntityUid uid, MailComponent component, DamageChangedEvent args) + { + if (args.DamageDelta == null || !_containerSystem.TryGetContainer(uid, "contents", out var contents)) + return; + + // Transfer damage to the contents. + // This should be a general-purpose feature for all containers in the future. + foreach (var entity in contents.ContainedEntities.ToArray()) + _damageableSystem.TryChangeDamage(entity, args.DamageDelta); + } + + private void OnBreak(EntityUid uid, MailComponent component, BreakageEventArgs args) + { + _appearanceSystem.SetData(uid, MailVisuals.IsBroken, true); + + if (component.IsFragile) + { + // DeltaV - Broken mail recorded to logistic stats + ExecuteForEachLogisticsStats(uid, (station, logisticStats) => + { + _logisticsStatsSystem.AddDamagedMailLosses(station, + logisticStats, + component.IsProfitable ? component.Penalty : 0); + }); + + PenalizeStationFailedDelivery(uid, component, "mail-penalty-fragile"); + } + } + + private void OnMailEmagged(EntityUid uid, MailComponent component, ref GotEmaggedEvent args) + { + if (!component.IsLocked) + return; + + UnlockMail(uid, component); + + _popupSystem.PopupEntity(Loc.GetString("mail-unlocked-by-emag"), uid, args.UserUid); + + _audioSystem.PlayPvs(component.EmagSound, uid, AudioParams.Default.WithVolume(4)); + component.IsProfitable = false; + args.Handled = true; + } + + /// + /// Returns true if the given entity is considered fragile for delivery. + /// + public bool IsEntityFragile(EntityUid uid, int fragileDamageThreshold) + { + // It takes damage on falling. + if (HasComp(uid)) + return true; + + // It can be spilled easily and has something to spill. + if (HasComp(uid) + && TryComp(uid, out var openable) + && !_openable.IsClosed(uid, null, openable) + && _solutionContainerSystem.PercentFull(uid) > 0) + return true; + + // It might be made of non-reinforced glass. + if (TryComp(uid, out DamageableComponent? damageableComponent) + && damageableComponent.DamageModifierSetId == "Glass") + return true; + + if (!TryComp(uid, out DestructibleComponent? destructibleComp)) + return false; + + // Fallback: It breaks or is destroyed in less than a damage + // threshold dictated by the teleporter. + foreach (var threshold in destructibleComp.Thresholds) + { + if (threshold.Trigger is not DamageTrigger trigger || trigger.Damage >= fragileDamageThreshold) + continue; + + foreach (var behavior in threshold.Behaviors) + { + if (behavior is not DoActsBehavior doActs) + continue; + + if (doActs.Acts.HasFlag(ThresholdActs.Breakage) || doActs.Acts.HasFlag(ThresholdActs.Destruction)) + return true; + } + } + + return false; + } + + public bool TryMatchJobTitleToDepartment(string jobTitle, [NotNullWhen(true)] out string? jobDepartment) + { + foreach (var department in _prototypeManager.EnumeratePrototypes()) + { + foreach (var role in department.Roles) + { + if (!_prototypeManager.TryIndex(role, out JobPrototype? jobPrototype) || jobPrototype.LocalizedName != jobTitle) + continue; + + jobDepartment = department.ID; + return true; + } + } + + jobDepartment = null; + return false; + } + + public bool TryMatchJobTitleToPrototype(string jobTitle, [NotNullWhen(true)] out JobPrototype? jobPrototype) + { + foreach (var job in _prototypeManager.EnumeratePrototypes()) + { + if (job.LocalizedName == jobTitle) + { + jobPrototype = job; + return true; + } + } + + jobPrototype = null; + return false; + } + + /// + /// Handle all the gritty details particular to a new mail entity. + /// + /// + /// This is separate mostly so the unit tests can get to it. + /// + public void SetupMail(EntityUid uid, MailTeleporterComponent component, MailRecipient recipient) + { + var mailComp = EnsureComp(uid); + + var container = _containerSystem.EnsureContainer(uid, "contents"); + foreach (var item in EntitySpawnCollection.GetSpawns(mailComp.Contents, _random)) + { + var entity = EntityManager.SpawnEntity(item, Transform(uid).Coordinates); + if (!_containerSystem.Insert(entity, container)) + { + _sawmill.Error($"Can't insert {ToPrettyString(entity)} into new mail delivery {ToPrettyString(uid)}! Deleting it."); + QueueDel(entity); + } + else if (!mailComp.IsFragile && IsEntityFragile(entity, component.FragileDamageThreshold)) + { + mailComp.IsFragile = true; + } + } + + mailComp.IsPriority = recipient.MayReceivePriorityMail && _random.Prob(component.PriorityChance); + mailComp.RecipientJob = recipient.Job; + mailComp.Recipient = recipient.Name; + + var mailEntityStrings = mailComp.IsLarge ? MailConstants.MailLarge : MailConstants.Mail; + if (mailComp.IsLarge) + { + mailComp.Bounty += component.LargeBonus; + mailComp.Penalty += component.LargeMalus; + } + + if (mailComp.IsFragile) + { + mailComp.Bounty += component.FragileBonus; + mailComp.Penalty += component.FragileMalus; + _appearanceSystem.SetData(uid, MailVisuals.IsFragile, true); + } + + if (mailComp.IsPriority) + { + mailComp.Bounty += component.PriorityBonus; + mailComp.Penalty += component.PriorityMalus; + _appearanceSystem.SetData(uid, MailVisuals.IsPriority, true); + mailComp.PriorityCancelToken = new CancellationTokenSource(); + + Timer.Spawn((int) component.PriorityDuration.TotalMilliseconds, () => + { + // DeltaV - Expired mail recorded to logistic stats + ExecuteForEachLogisticsStats(uid, (station, logisticStats) => + { + _logisticsStatsSystem.AddExpiredMailLosses(station, + logisticStats, + mailComp.IsProfitable ? mailComp.Penalty : 0); + }); + + PenalizeStationFailedDelivery(uid, mailComp, "mail-penalty-expired"); + }, mailComp.PriorityCancelToken.Token); + } + + _appearanceSystem.SetData(uid, MailVisuals.JobIcon, recipient.JobIcon); + + _metaDataSystem.SetEntityName(uid, Loc.GetString(mailEntityStrings.NameAddressed, // Frontier: move constant to MailEntityString + ("recipient", recipient.Name))); + + var accessReader = EnsureComp(uid); + accessReader.AccessLists.Add(recipient.AccessTags); + } + + /// + /// Return the parcels waiting for delivery. + /// + /// The mail teleporter to check. + public List GetUndeliveredParcels(EntityUid uid) + { + // An alternative solution would be to keep a list of the unopened + // parcels spawned by the teleporter and see if they're not carried + // by someone, but this is simple, and simple is good. + List undeliveredParcels = new(); + foreach (var entityInTile in TurfHelpers.GetEntitiesInTile(Transform(uid).Coordinates, LookupFlags.Dynamic | LookupFlags.Sundries)) + { + if (HasComp(entityInTile)) + undeliveredParcels.Add(entityInTile); + } + return undeliveredParcels; + } + + /// + /// Return how many parcels are waiting for delivery. + /// + /// The mail teleporter to check. + public uint GetUndeliveredParcelCount(EntityUid uid) + { + return (uint) GetUndeliveredParcels(uid).Count(); + } + + /// + /// Try to match a mail receiver to a mail teleporter. + /// + public bool TryGetMailTeleporterForReceiver(MailReceiverComponent receiver, [NotNullWhen(true)] out MailTeleporterComponent? teleporterComponent) + { + foreach (var mailTeleporter in EntityQuery()) + { + if (_stationSystem.GetOwningStation(receiver.Owner) != _stationSystem.GetOwningStation(mailTeleporter.Owner)) + continue; + + teleporterComponent = mailTeleporter; + return true; + } + + teleporterComponent = null; + return false; + } + + /// + /// Try to construct a recipient struct for a mail parcel based on a receiver. + /// + public bool TryGetMailRecipientForReceiver(MailReceiverComponent receiver, [NotNullWhen(true)] out MailRecipient? recipient) + { + // Because of the way this works, people are not considered + // candidates for mail if there is no valid PDA or ID in their slot + // or active hand. A better future solution might be checking the + // station records, possibly cross-referenced with the medical crew + // scanner to look for living recipients. TODO + + if (_idCardSystem.TryFindIdCard(receiver.Owner, out var idCard) + && TryComp(idCard.Owner, out var access) + && idCard.Comp.FullName != null + && idCard.Comp.JobTitle != null) + { + var accessTags = access.Tags; + + var mayReceivePriorityMail = !(_mindSystem.GetMind(receiver.Owner) == null); + + recipient = new MailRecipient(idCard.Comp.FullName, + idCard.Comp.JobTitle, + idCard.Comp.JobIcon, + accessTags, + mayReceivePriorityMail); + + return true; + } + + recipient = null; + return false; + } + + /// + /// Get the list of valid mail recipients for a mail teleporter. + /// + public List GetMailRecipientCandidates(EntityUid uid) + { + List candidateList = new(); + + foreach (var receiver in EntityQuery()) + { + if (_stationSystem.GetOwningStation(receiver.Owner) != _stationSystem.GetOwningStation(uid)) + continue; + + if (TryGetMailRecipientForReceiver(receiver, out MailRecipient? recipient)) + candidateList.Add(recipient.Value); + } + + return candidateList; + } + + /// + /// Handle the spawning of all the mail for a mail teleporter. + /// + public void SpawnMail(EntityUid uid, MailTeleporterComponent? component = null) + { + if (!Resolve(uid, ref component)) + { + _sawmill.Error($"Tried to SpawnMail on {ToPrettyString(uid)} without a valid MailTeleporterComponent!"); + return; + } + + if (GetUndeliveredParcelCount(uid) >= component.MaximumUndeliveredParcels) + return; + + var candidateList = GetMailRecipientCandidates(uid); + + if (candidateList.Count <= 0) + { + _sawmill.Error("List of mail candidates was empty!"); + return; + } + + if (!_prototypeManager.TryIndex(component.MailPool, out var pool)) + { + _sawmill.Error($"Can't index {ToPrettyString(uid)}'s MailPool {component.MailPool}!"); + return; + } + + for (int i = 0; + i < component.MinimumDeliveriesPerTeleport + candidateList.Count / component.CandidatesPerDelivery; + i++) + { + var candidate = _random.Pick(candidateList); + var possibleParcels = new Dictionary(pool.Everyone); + + if (TryMatchJobTitleToPrototype(candidate.Job, out JobPrototype? jobPrototype) + && pool.Jobs.TryGetValue(jobPrototype.ID, out Dictionary? jobParcels)) + { + possibleParcels = possibleParcels.Union(jobParcels) + .GroupBy(g => g.Key) + .ToDictionary(pair => pair.Key, pair => pair.First().Value); + } + + if (TryMatchJobTitleToDepartment(candidate.Job, out string? department) + && pool.Departments.TryGetValue(department, out Dictionary? departmentParcels)) + { + possibleParcels = possibleParcels.Union(departmentParcels) + .GroupBy(g => g.Key) + .ToDictionary(pair => pair.Key, pair => pair.First().Value); + } + + var accumulated = 0f; + var randomPoint = _random.NextFloat(possibleParcels.Values.Sum()); + string? chosenParcel = null; + foreach (var (key, weight) in possibleParcels) + { + accumulated += weight; + if (accumulated >= randomPoint) + { + chosenParcel = key; + break; + } + } + + if (chosenParcel == null) + { + _sawmill.Error($"MailSystem wasn't able to find a deliverable parcel for {candidate.Name}, {candidate.Job}!"); + return; + } + + var mail = EntityManager.SpawnEntity(chosenParcel, Transform(uid).Coordinates); + SetupMail(mail, component, candidate); + + _tagSystem.AddTag(mail, "Mail"); // Frontier + } + + if (_containerSystem.TryGetContainer(uid, "queued", out var queued)) + _containerSystem.EmptyContainer(queued); + + _audioSystem.PlayPvs(component.TeleportSound, uid); + } + + public void OpenMail(EntityUid uid, MailComponent? component = null, EntityUid? user = null) + { + if (!Resolve(uid, ref component)) + return; + + _audioSystem.PlayPvs(component.OpenSound, uid); + + if (user != null) + _handsSystem.TryDrop((EntityUid) user); + + if (!_containerSystem.TryGetContainer(uid, "contents", out var contents)) + { + // I silenced this error because it fails non deterministically in tests and doesn't seem to effect anything else. + // _sawmill.Error($"Mail {ToPrettyString(uid)} was missing contents container!"); + return; + } + + foreach (var entity in contents.ContainedEntities.ToArray()) + { + _handsSystem.PickupOrDrop(user, entity); + } + + _tagSystem.AddTag(uid, "Trash"); + _tagSystem.AddTag(uid, "Recyclable"); + component.IsEnabled = false; + UpdateMailTrashState(uid, true); + } + + private void UpdateAntiTamperVisuals(EntityUid uid, bool isLocked) + { + _appearanceSystem.SetData(uid, MailVisuals.IsLocked, isLocked); + } + + private void UpdateMailTrashState(EntityUid uid, bool isTrash) + { + _appearanceSystem.SetData(uid, MailVisuals.IsTrash, isTrash); + } + + // DeltaV - Helper function that executes for each StationLogisticsStatsComponent + // For updating MailMetrics stats + private void ExecuteForEachLogisticsStats(EntityUid uid, + Action action) + { + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var station, out var logisticStats)) + { + if (_stationSystem.GetOwningStation(uid) != station) + continue; + action(station, logisticStats); + } + } +} + +public struct MailRecipient( + string name, + string job, + string jobIcon, + HashSet> accessTags, + bool mayReceivePriorityMail) +{ + public string Name = name; + public string Job = job; + public string JobIcon = jobIcon; + public HashSet> AccessTags = accessTags; + public bool MayReceivePriorityMail = mayReceivePriorityMail; +} diff --git a/Content.Server/Mapping/MappingCommand.cs b/Content.Server/Mapping/MappingCommand.cs index 08f3dcccf9..46534f7059 100644 --- a/Content.Server/Mapping/MappingCommand.cs +++ b/Content.Server/Mapping/MappingCommand.cs @@ -53,7 +53,7 @@ public void Execute(IConsoleShell shell, string argStr, string[] args) } #if DEBUG - shell.WriteError(Loc.GetString("cmd-mapping-warning")); + shell.WriteLine(Loc.GetString("cmd-mapping-warning")); #endif MapId mapId; diff --git a/Content.Server/MassMedia/Systems/NewsSystem.cs b/Content.Server/MassMedia/Systems/NewsSystem.cs index c313b0d4cc..3246de5316 100644 --- a/Content.Server/MassMedia/Systems/NewsSystem.cs +++ b/Content.Server/MassMedia/Systems/NewsSystem.cs @@ -92,15 +92,12 @@ private void OnWriteUiDeleteMessage(Entity ent, ref NewsWri if (msg.ArticleNum >= articles.Count) return; - if (msg.Session.AttachedEntity is not { } actor) - return; - var article = articles[msg.ArticleNum]; - if (CheckDeleteAccess(article, ent, actor)) + if (CheckDeleteAccess(article, ent, msg.Actor)) { _adminLogger.Add( LogType.Chat, LogImpact.Medium, - $"{ToPrettyString(actor):actor} deleted news article {article.Title} by {article.Author}: {article.Content}" + $"{ToPrettyString(msg.Actor):actor} deleted news article {article.Title} by {article.Author}: {article.Content}" ); articles.RemoveAt(msg.ArticleNum); @@ -138,11 +135,8 @@ private void OnWriteUiPublishMessage(Entity ent, ref NewsWr if (!TryGetArticles(ent, out var articles)) return; - if (msg.Session.AttachedEntity is not { } author) - return; - string? authorName = null; - if (_idCardSystem.TryFindIdCard(author, out var idCard)) + if (_idCardSystem.TryFindIdCard(msg.Actor, out var idCard)) authorName = idCard.Comp.FullName; var title = msg.Title.Trim(); @@ -161,7 +155,7 @@ private void OnWriteUiPublishMessage(Entity ent, ref NewsWr _adminLogger.Add( LogType.Chat, LogImpact.Medium, - $"{ToPrettyString(author):actor} created news article {article.Title} by {article.Author}: {article.Content}" + $"{ToPrettyString(msg.Actor):actor} created news article {article.Title} by {article.Author}: {article.Content}" ); articles.Add(article); @@ -245,14 +239,14 @@ private bool TryGetArticles(EntityUid uid, [NotNullWhen(true)] out List ent) { - if (!_ui.TryGetUi(ent, NewsWriterUiKey.Key, out var ui)) + if (!_ui.HasUi(ent, NewsWriterUiKey.Key)) return; if (!TryGetArticles(ent, out var articles)) return; var state = new NewsWriterBoundUserInterfaceState(articles.ToArray(), ent.Comp.PublishEnabled, ent.Comp.NextPublish); - _ui.SetUiState(ui, state); + _ui.SetUiState(ent.Owner, NewsWriterUiKey.Key, state); } private void UpdateReaderUi(Entity ent, EntityUid loaderUid) diff --git a/Content.Server/Materials/MaterialReclaimerSystem.cs b/Content.Server/Materials/MaterialReclaimerSystem.cs index aa24fde44b..de82f12598 100644 --- a/Content.Server/Materials/MaterialReclaimerSystem.cs +++ b/Content.Server/Materials/MaterialReclaimerSystem.cs @@ -1,4 +1,6 @@ using Content.Server.Chemistry.Containers.EntitySystems; +using Content.Server.Chemistry.EntitySystems; +using Content.Server.Construction; using Content.Server.Fluids.EntitySystems; using Content.Server.GameTicking; using Content.Server.Popups; @@ -45,6 +47,8 @@ public override void Initialize() base.Initialize(); SubscribeLocalEvent(OnStartup); + SubscribeLocalEvent(OnRefreshParts); + SubscribeLocalEvent(OnUpgradeExamine); SubscribeLocalEvent(OnPowerChanged); SubscribeLocalEvent(OnInteractUsing, before: new []{typeof(WiresSystem), typeof(SolutionTransferSystem)}); @@ -56,6 +60,18 @@ private void OnStartup(Entity entity, ref ComponentS _solutionContainer.EnsureSolution(entity.Owner, entity.Comp.SolutionContainerId); } + private void OnUpgradeExamine(Entity entity, ref UpgradeExamineEvent args) + { + args.AddPercentageUpgrade(Loc.GetString("material-reclaimer-upgrade-process-rate"), entity.Comp.MaterialProcessRate / entity.Comp.BaseMaterialProcessRate); + } + + private void OnRefreshParts(Entity entity, ref RefreshPartsEvent args) + { + var rating = args.PartRatings[entity.Comp.MachinePartProcessRate] - 1; + entity.Comp.MaterialProcessRate = entity.Comp.BaseMaterialProcessRate * MathF.Pow(entity.Comp.PartRatingProcessRateMultiplier, rating); + Dirty(entity); + } + private void OnPowerChanged(Entity entity, ref PowerChangedEvent args) { AmbientSound.SetAmbience(entity.Owner, entity.Comp.Enabled && args.Powered); diff --git a/Content.Server/Mech/Components/MechAirComponent.cs b/Content.Server/Mech/Components/MechAirComponent.cs index c533b3d834..d312e1b739 100644 --- a/Content.Server/Mech/Components/MechAirComponent.cs +++ b/Content.Server/Mech/Components/MechAirComponent.cs @@ -1,4 +1,5 @@ using Content.Server.Atmos; +using Content.Shared.Atmos; namespace Content.Server.Mech.Components; diff --git a/Content.Server/Mech/Systems/MechSystem.cs b/Content.Server/Mech/Systems/MechSystem.cs index 976c3867a9..36dce2c9bc 100644 --- a/Content.Server/Mech/Systems/MechSystem.cs +++ b/Content.Server/Mech/Systems/MechSystem.cs @@ -306,15 +306,14 @@ public override void UpdateUserInterface(EntityUid uid, MechComponent? component { EquipmentStates = ev.States }; - var ui = _ui.GetUi(uid, MechUiKey.Key); - _ui.SetUiState(ui, state); + _ui.SetUiState(uid, MechUiKey.Key, state); } public override void BreakMech(EntityUid uid, MechComponent? component = null) { base.BreakMech(uid, component); - _ui.TryCloseAll(uid, MechUiKey.Key); + _ui.CloseUi(uid, MechUiKey.Key); _actionBlocker.UpdateCanMove(uid); } diff --git a/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerComponent.cs b/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerComponent.cs index 61d36f98b9..1358bfbcbb 100644 --- a/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerComponent.cs +++ b/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerComponent.cs @@ -1,4 +1,8 @@ +using System.Threading; +using Content.Shared.Construction.Prototypes; using Content.Shared.Storage; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; namespace Content.Server.Medical.BiomassReclaimer { @@ -6,72 +10,111 @@ namespace Content.Server.Medical.BiomassReclaimer public sealed partial class BiomassReclaimerComponent : Component { /// - /// This gets set for each mob it processes. - /// When it hits 0, there is a chance for the reclaimer to either spill blood or throw an item. + /// This gets set for each mob it processes. + /// When it hits 0, there is a chance for the reclaimer to either spill blood or throw an item. /// [ViewVariables] public float RandomMessTimer = 0f; /// - /// The interval for . + /// The interval for . /// - [ViewVariables(VVAccess.ReadWrite), DataField] + [DataField] public TimeSpan RandomMessInterval = TimeSpan.FromSeconds(5); /// - /// This gets set for each mob it processes. - /// When it hits 0, spit out biomass. + /// This gets set for each mob it processes. + /// When it hits 0, spit out biomass. /// [ViewVariables] - public float ProcessingTimer = default; + public float ProcessingTimer; /// - /// Amount of biomass that the mob being processed will yield. - /// This is calculated from the YieldPerUnitMass. - /// Also stores non-integer leftovers. + /// Amount of biomass that the mob being processed will yield. + /// This is calculated from the YieldPerUnitMass. + /// Also stores non-integer leftovers. /// [ViewVariables] - public float CurrentExpectedYield = 0f; + public float CurrentExpectedYield; /// - /// The reagent that will be spilled while processing a mob. + /// The reagent that will be spilled while processing a mob. /// [ViewVariables] public string? BloodReagent; /// - /// Entities that can be randomly spawned while processing a mob. + /// Entities that can be randomly spawned while processing a mob. /// public List SpawnedEntities = new(); /// - /// How many units of biomass it produces for each unit of mass. + /// How many units of biomass it produces for each unit of mass. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] - public float YieldPerUnitMass = 0.4f; + [ViewVariables(VVAccess.ReadWrite)] + public float YieldPerUnitMass = default; /// - /// How many seconds to take to insert an entity per unit of its mass. + /// The base yield per mass unit when no components are upgraded. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] + public float BaseYieldPerUnitMass = 0.4f; + + /// + /// Machine part whose rating modifies the yield per mass. + /// + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string MachinePartYieldAmount = "MatterBin"; + + /// + /// How much the machine part quality affects the yield. + /// Going up a tier will multiply the yield by this amount. + /// + [DataField] + public float PartRatingYieldAmountMultiplier = 1.25f; + + /// + /// How many seconds to take to insert an entity per unit of its mass. + /// + [DataField] public float BaseInsertionDelay = 0.1f; /// - /// How much to multiply biomass yield from botany produce. + /// How much to multiply biomass yield from botany produce. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public float ProduceYieldMultiplier = 0.25f; /// - /// The time it takes to process a mob, per mass. + /// The time it takes to process a mob, per mass. + /// + [ViewVariables(VVAccess.ReadWrite)] + public float ProcessingTimePerUnitMass; + + /// + /// The base time per mass unit that it takes to process a mob + /// when no components are upgraded. + /// + [DataField] + public float BaseProcessingTimePerUnitMass = 0.5f; + + /// + /// The machine part that increses the processing speed. + /// + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string MachinePartProcessingSpeed = "Manipulator"; + + /// + /// How much the machine part quality affects the yield. + /// Going up a tier will multiply the speed by this amount. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] - public float ProcessingTimePerUnitMass = 0.5f; + [DataField] + public float PartRatingSpeedMultiplier = 1.35f; /// - /// Will this refuse to gib a living mob? + /// Will this refuse to gib a living mob? /// - [ViewVariables(VVAccess.ReadWrite), DataField] + [DataField] public bool SafetyEnabled = true; } } diff --git a/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs b/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs index d07858aec5..97a758a5ed 100644 --- a/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs +++ b/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs @@ -1,6 +1,7 @@ using System.Numerics; using Content.Server.Body.Components; using Content.Server.Botany.Components; +using Content.Server.Construction; using Content.Server.Fluids.EntitySystems; using Content.Server.Materials; using Content.Server.Power.Components; @@ -28,7 +29,6 @@ using Robust.Shared.Audio.Systems; using Robust.Shared.Configuration; using Robust.Shared.Physics.Components; -using Robust.Shared.Prototypes; using Robust.Shared.Random; namespace Content.Server.Medical.BiomassReclaimer @@ -81,11 +81,9 @@ public override void Update(float frameTime) } if (reclaimer.ProcessingTimer > 0) - { continue; - } - var actualYield = (int) (reclaimer.CurrentExpectedYield); // can only have integer biomass + var actualYield = (int) reclaimer.CurrentExpectedYield; // Can only have integer biomass physically reclaimer.CurrentExpectedYield = reclaimer.CurrentExpectedYield - actualYield; // store non-integer leftovers _material.SpawnMultipleFromMaterial(actualYield, BiomassPrototype, Transform(uid).Coordinates); @@ -102,6 +100,8 @@ public override void Initialize() SubscribeLocalEvent(OnUnanchorAttempt); SubscribeLocalEvent(OnAfterInteractUsing); SubscribeLocalEvent(OnClimbedOn); + SubscribeLocalEvent(OnRefreshParts); + SubscribeLocalEvent(OnUpgradeExamine); SubscribeLocalEvent(OnPowerChanged); SubscribeLocalEvent(OnSuicide); SubscribeLocalEvent(OnDoAfter); @@ -109,13 +109,9 @@ public override void Initialize() private void OnSuicide(Entity ent, ref SuicideEvent args) { - if (args.Handled) - return; - - if (HasComp(ent)) - return; - - if (TryComp(ent, out var power) && !power.Powered) + if (args.Handled + || HasComp(ent) + || TryComp(ent, out var power) && !power.Powered) return; _popup.PopupEntity(Loc.GetString("biomass-reclaimer-suicide-others", ("victim", args.Victim)), ent, PopupType.LargeCaution); @@ -138,11 +134,9 @@ private void OnShutdown(EntityUid uid, ActiveBiomassReclaimerComponent component private void OnPowerChanged(EntityUid uid, BiomassReclaimerComponent component, ref PowerChangedEvent args) { - if (args.Powered) - { - if (component.ProcessingTimer > 0) - EnsureComp(uid); - } + if (args.Powered + && component.ProcessingTimer > 0) + EnsureComp(uid); else RemComp(uid); } @@ -153,16 +147,14 @@ private void OnUnanchorAttempt(EntityUid uid, ActiveBiomassReclaimerComponent co } private void OnAfterInteractUsing(Entity reclaimer, ref AfterInteractUsingEvent args) { - if (!args.CanReach || args.Target == null) + if (!args.CanReach + || args.Target == null + || !CanGib(reclaimer, args.Used)) return; - if (!CanGib(reclaimer, args.Used)) - return; - - if (!TryComp(args.Used, out var physics)) - return; - - var delay = reclaimer.Comp.BaseInsertionDelay * physics.FixturesMass; + var delay = reclaimer.Comp.BaseInsertionDelay * (TryComp(args.Used, out var physics) + ? physics.FixturesMass + : 1); _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, args.User, delay, new ReclaimerDoAfterEvent(), reclaimer, target: args.Target, used: args.Used) { BreakOnTargetMove = true, @@ -184,12 +176,33 @@ private void OnClimbedOn(Entity reclaimer, ref Climbe StartProcessing(args.Climber, reclaimer); } - private void OnDoAfter(Entity reclaimer, ref ReclaimerDoAfterEvent args) + private void OnRefreshParts(EntityUid uid, BiomassReclaimerComponent component, RefreshPartsEvent args) { - if (args.Handled || args.Cancelled) - return; + var laserRating = args.PartRatings[component.MachinePartProcessingSpeed]; + var manipRating = args.PartRatings[component.MachinePartYieldAmount]; + + // Processing time slopes downwards with part rating. + component.ProcessingTimePerUnitMass = + component.BaseProcessingTimePerUnitMass / MathF.Pow(component.PartRatingSpeedMultiplier, laserRating - 1); + + // Yield slopes upwards with part rating. + component.YieldPerUnitMass = + component.BaseYieldPerUnitMass * MathF.Pow(component.PartRatingYieldAmountMultiplier, manipRating - 1); + } - if (args.Args.Used == null || args.Args.Target == null || !HasComp(args.Args.Target.Value)) + private void OnUpgradeExamine(EntityUid uid, BiomassReclaimerComponent component, UpgradeExamineEvent args) + { + args.AddPercentageUpgrade("biomass-reclaimer-component-upgrade-speed", component.BaseProcessingTimePerUnitMass / component.ProcessingTimePerUnitMass); + args.AddPercentageUpgrade("biomass-reclaimer-component-upgrade-biomass-yield", component.YieldPerUnitMass / component.BaseYieldPerUnitMass); + } + + private void OnDoAfter(Entity reclaimer, ref ReclaimerDoAfterEvent args) + { + if (args.Handled + || args.Cancelled + || args.Args.Used == null + || args.Args.Target == null + || !HasComp(args.Args.Target.Value)) return; _adminLogger.Add(LogType.Action, LogImpact.Extreme, $"{ToPrettyString(args.Args.User):player} used a biomass reclaimer to gib {ToPrettyString(args.Args.Target.Value):target} in {ToPrettyString(reclaimer):reclaimer}"); @@ -207,18 +220,13 @@ private void StartProcessing(EntityUid toProcess, Entity(ent); if (TryComp(toProcess, out var stream)) - { component.BloodReagent = stream.BloodReagent; - } if (TryComp(toProcess, out var butcherableComponent)) - { component.SpawnedEntities = butcherableComponent.SpawnedEntities; - } - var expectedYield = physics.FixturesMass * component.YieldPerUnitMass; - if (HasComp(toProcess)) - expectedYield *= component.ProduceYieldMultiplier; - component.CurrentExpectedYield += expectedYield; + component.CurrentExpectedYield += HasComp(toProcess) + ? physics.FixturesMass * component.YieldPerUnitMass * component.ProduceYieldMultiplier + : physics.FixturesMass * component.YieldPerUnitMass; component.ProcessingTimer = physics.FixturesMass * component.ProcessingTimePerUnitMass; @@ -227,31 +235,22 @@ private void StartProcessing(EntityUid toProcess, Entity reclaimer, EntityUid dragged) { - if (HasComp(reclaimer)) + if (HasComp(reclaimer) + || !Transform(reclaimer).Anchored + || TryComp(reclaimer, out var power) && !power.Powered) return false; bool isPlant = HasComp(dragged); - if (!isPlant && !HasComp(dragged)) - return false; - - if (!Transform(reclaimer).Anchored) - return false; - - if (TryComp(reclaimer, out var power) && !power.Powered) + if (!HasComp(dragged) && (!HasComp(dragged) || reclaimer.Comp.SafetyEnabled && !_mobState.IsDead(dragged))) return false; - if (!isPlant && reclaimer.Comp.SafetyEnabled && !_mobState.IsDead(dragged)) + if (_configManager.GetCVar(CCVars.CloningReclaimSouledBodies) + && HasComp(dragged) + && _minds.TryGetMind(dragged, out _, out var mind) + && mind.UserId != null + && _playerManager.TryGetSessionById(mind.UserId.Value, out _)) return false; - // Reject souled bodies in easy mode. - if (_configManager.GetCVar(CCVars.BiomassEasyMode) && - HasComp(dragged) && - _minds.TryGetMind(dragged, out _, out var mind)) - { - if (mind.UserId != null && _playerManager.TryGetSessionById(mind.UserId.Value, out _)) - return false; - } - return true; } } diff --git a/Content.Server/Medical/Components/CryoPodAirComponent.cs b/Content.Server/Medical/Components/CryoPodAirComponent.cs index baaa3bcda0..72a10b391e 100644 --- a/Content.Server/Medical/Components/CryoPodAirComponent.cs +++ b/Content.Server/Medical/Components/CryoPodAirComponent.cs @@ -11,5 +11,5 @@ public sealed partial class CryoPodAirComponent : Component ///
[ViewVariables(VVAccess.ReadWrite)] [DataField("gasMixture")] - public GasMixture Air { get; set; } = new(Atmospherics.OneAtmosphere); + public GasMixture Air { get; set; } = new GasMixture(1000f); } diff --git a/Content.Server/Medical/Components/HealthAnalyzerComponent.cs b/Content.Server/Medical/Components/HealthAnalyzerComponent.cs index 6380b71c8a..f0b56cbd19 100644 --- a/Content.Server/Medical/Components/HealthAnalyzerComponent.cs +++ b/Content.Server/Medical/Components/HealthAnalyzerComponent.cs @@ -35,6 +35,12 @@ public sealed partial class HealthAnalyzerComponent : Component [DataField] public EntityUid? ScannedEntity; + /// + /// The body part that is currently being scanned. + /// + [DataField] + public EntityUid? CurrentBodyPart; + /// /// The maximum range in tiles at which the analyzer can receive continuous updates /// diff --git a/Content.Server/Medical/Components/MedicalScannerComponent.cs b/Content.Server/Medical/Components/MedicalScannerComponent.cs index 96de649987..15ca6cd2bd 100644 --- a/Content.Server/Medical/Components/MedicalScannerComponent.cs +++ b/Content.Server/Medical/Components/MedicalScannerComponent.cs @@ -1,5 +1,4 @@ using Content.Shared.Construction.Prototypes; -using Content.Shared.DragDrop; using Content.Shared.MedicalScanner; using Robust.Shared.Containers; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; @@ -13,10 +12,15 @@ public sealed partial class MedicalScannerComponent : SharedMedicalScannerCompon public ContainerSlot BodyContainer = default!; public EntityUid? ConnectedConsole; - [DataField, ViewVariables(VVAccess.ReadWrite)] + [ViewVariables(VVAccess.ReadWrite)] public float CloningFailChanceMultiplier = 1f; - - // Nyano, needed for Metem Machine. + public float MetemKarmaBonus = 0.25f; + + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string MachinePartCloningFailChance = "Capacitor"; + + [DataField] + public float PartRatingFailMultiplier = 0.75f; } } diff --git a/Content.Server/Medical/CrewMonitoring/CrewMonitoringConsoleSystem.cs b/Content.Server/Medical/CrewMonitoring/CrewMonitoringConsoleSystem.cs index ff02b9cbdf..a53df6dbae 100644 --- a/Content.Server/Medical/CrewMonitoring/CrewMonitoringConsoleSystem.cs +++ b/Content.Server/Medical/CrewMonitoring/CrewMonitoringConsoleSystem.cs @@ -58,7 +58,7 @@ private void UpdateUserInterface(EntityUid uid, CrewMonitoringConsoleComponent? if (!Resolve(uid, ref component)) return; - if (!_uiSystem.TryGetUi(uid, CrewMonitoringUIKey.Key, out var bui)) + if (!_uiSystem.IsUiOpen(uid, CrewMonitoringUIKey.Key)) return; // The grid must have a NavMapComponent to visualize the map in the UI @@ -69,6 +69,6 @@ private void UpdateUserInterface(EntityUid uid, CrewMonitoringConsoleComponent? // Update all sensors info var allSensors = component.ConnectedSensors.Values.ToList(); - _uiSystem.SetUiState(bui, new CrewMonitoringState(allSensors)); + _uiSystem.SetUiState(uid, CrewMonitoringUIKey.Key, new CrewMonitoringState(allSensors)); } } diff --git a/Content.Server/Medical/CryoPodSystem.cs b/Content.Server/Medical/CryoPodSystem.cs index bcacd2f505..caae6c5f17 100644 --- a/Content.Server/Medical/CryoPodSystem.cs +++ b/Content.Server/Medical/CryoPodSystem.cs @@ -13,6 +13,7 @@ using Content.Server.NodeContainer.Nodes; using Content.Server.Power.Components; using Content.Server.Temperature.Components; +using Content.Shared.Atmos; using Content.Shared.UserInterface; using Content.Shared.Chemistry; using Content.Shared.Chemistry.Components; @@ -194,7 +195,8 @@ private void OnActivateUI(Entity entity, ref AfterActivatableU healthAnalyzer.ScannedEntity = entity.Comp.BodyContainer.ContainedEntity; } - _userInterfaceSystem.TrySendUiMessage( + // TODO: This should be a state my dude + _userInterfaceSystem.ServerSendUiMessage( entity.Owner, HealthAnalyzerUiKey.Key, new HealthAnalyzerScannedUserMessage(GetNetEntity(entity.Comp.BodyContainer.ContainedEntity), @@ -204,6 +206,8 @@ private void OnActivateUI(Entity entity, ref AfterActivatableU ? bloodSolution.FillFraction : 0, null, + null, + null, null )); } @@ -247,7 +251,7 @@ private void OnPowerChanged(Entity entity, ref PowerChangedEve else { RemComp(entity); - _uiSystem.TryCloseAll(entity.Owner, HealthAnalyzerUiKey.Key); + _uiSystem.CloseUi(entity.Owner, HealthAnalyzerUiKey.Key); } UpdateAppearance(entity.Owner, entity.Comp); } @@ -277,10 +281,17 @@ private void OnGasAnalyzed(Entity entity, ref GasAnalyzerScanE if (!TryComp(entity, out CryoPodAirComponent? cryoPodAir)) return; - args.GasMixtures ??= new Dictionary { { Name(entity.Owner), cryoPodAir.Air } }; + args.GasMixtures ??= new List<(string, GasMixture?)>(); + args.GasMixtures.Add((Name(entity.Owner), cryoPodAir.Air)); // If it's connected to a port, include the port side - if (_nodeContainer.TryGetNode(entity.Owner, entity.Comp.PortName, out PipeNode? port)) - args.GasMixtures.Add(entity.Comp.PortName, port.Air); + // multiply by volume fraction to make sure to send only the gas inside the analyzed pipe element, not the whole pipe system + if (_nodeContainer.TryGetNode(entity.Owner, entity.Comp.PortName, out PipeNode? port) && port.Air.Volume != 0f) + { + var portAirLocal = port.Air.Clone(); + portAirLocal.Multiply(port.Volume / port.Air.Volume); + portAirLocal.Volume = port.Volume; + args.GasMixtures.Add((entity.Comp.PortName, portAirLocal)); + } } private void OnEjected(Entity cryoPod, ref EntRemovedFromContainerMessage args) @@ -291,7 +302,7 @@ private void OnEjected(Entity cryoPod, ref EntRemovedFromConta } // if body is ejected - no need to display health-analyzer - _uiSystem.TryCloseAll(cryoPod.Owner, HealthAnalyzerUiKey.Key); + _uiSystem.CloseUi(cryoPod.Owner, HealthAnalyzerUiKey.Key); } #endregion diff --git a/Content.Server/Medical/HealingSystem.cs b/Content.Server/Medical/HealingSystem.cs index e7362d481e..c64bb2a485 100644 --- a/Content.Server/Medical/HealingSystem.cs +++ b/Content.Server/Medical/HealingSystem.cs @@ -1,6 +1,7 @@ using Content.Server.Administration.Logs; using Content.Server.Body.Components; using Content.Server.Body.Systems; +using Content.Shared.Body.Part; using Content.Server.Chemistry.Containers.EntitySystems; using Content.Server.Medical.Components; using Content.Server.Popups; @@ -9,6 +10,8 @@ using Content.Shared.Damage; using Content.Shared.Database; using Content.Shared.DoAfter; +using Content.Shared.Targeting; +using Content.Shared.Body.Components; using Content.Shared.FixedPoint; using Content.Shared.Interaction; using Content.Shared.Interaction.Events; @@ -16,9 +19,11 @@ using Content.Shared.Mobs; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; +using Content.Shared.Targeting; using Content.Shared.Stacks; using Robust.Shared.Audio.Systems; using Robust.Shared.Random; +using System.Linq; namespace Content.Server.Medical; @@ -27,6 +32,8 @@ public sealed class HealingSystem : EntitySystem [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly IAdminLogManager _adminLogger = default!; [Dependency] private readonly DamageableSystem _damageable = default!; + + [Dependency] private readonly BodySystem _bodySystem = default!; [Dependency] private readonly BloodstreamSystem _bloodstreamSystem = default!; [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; [Dependency] private readonly IRobustRandom _random = default!; @@ -79,7 +86,7 @@ entity.Comp.DamageContainerID is not null && if (healing.ModifyBloodLevel != 0) _bloodstreamSystem.TryModifyBloodLevel(entity.Owner, healing.ModifyBloodLevel); - var healed = _damageable.TryChangeDamage(entity.Owner, healing.Damage, true, origin: args.Args.User); + var healed = _damageable.TryChangeDamage(entity.Owner, healing.Damage, true, origin: args.User, canSever: false); if (healed == null && healing.BloodlossModifier != 0) return; @@ -113,8 +120,8 @@ entity.Comp.DamageContainerID is not null && _audio.PlayPvs(healing.HealingEndSound, entity.Owner, AudioHelpers.WithVariation(0.125f, _random).WithVolume(-5f)); - // Logic to determine the whether or not to repeat the healing action - args.Repeat = (HasDamage(entity.Comp, healing) && !dontRepeat); + // Logic to determine whether or not to repeat the healing action + args.Repeat = HasDamage(entity.Comp, healing) && !dontRepeat || IsPartDamaged(args.User, entity); if (!args.Repeat && !dontRepeat) _popupSystem.PopupEntity(Loc.GetString("medical-item-finished-using", ("item", args.Used)), entity.Owner, args.User); args.Handled = true; @@ -135,6 +142,20 @@ private bool HasDamage(DamageableComponent component, HealingComponent healing) return false; } + private bool IsPartDamaged(EntityUid user, EntityUid target) + { + if (!TryComp(user, out TargetingComponent? targeting)) + return false; + + var (targetType, targetSymmetry) = _bodySystem.ConvertTargetBodyPart(targeting.Target); + foreach (var part in _bodySystem.GetBodyChildrenOfType(target, targetType, symmetry: targetSymmetry)) + if (TryComp(part.Id, out var damageable) + && damageable.TotalDamage > part.Component.MinIntegrity) + return true; + + return false; + } + private void OnHealingUse(Entity entity, ref UseInHandEvent args) { if (args.Handled) @@ -173,6 +194,7 @@ targetDamage.DamageContainerID is not null && var anythingToDo = HasDamage(targetDamage, component) || + IsPartDamaged(user, target) || component.ModifyBloodLevel > 0 // Special case if healing item can restore lost blood... && TryComp(target, out var bloodstream) && _solutionContainerSystem.ResolveSolution(target, bloodstream.BloodSolutionName, ref bloodstream.BloodSolution, out var bloodSolution) diff --git a/Content.Server/Medical/HealthAnalyzerSystem.cs b/Content.Server/Medical/HealthAnalyzerSystem.cs index 489ba9ac81..11d2758cdd 100644 --- a/Content.Server/Medical/HealthAnalyzerSystem.cs +++ b/Content.Server/Medical/HealthAnalyzerSystem.cs @@ -3,6 +3,13 @@ using Content.Server.Medical.Components; using Content.Server.PowerCell; using Content.Server.Temperature.Components; +using Content.Server.Traits.Assorted; +using Content.Shared.Chemistry.EntitySystems; +// Shitmed Start +using Content.Shared.Body.Part; +using Content.Shared.Body.Systems; +using Content.Shared.Targeting; +// Shitmed End using Content.Shared.Damage; using Content.Shared.DoAfter; using Content.Shared.Interaction; @@ -15,6 +22,7 @@ using Robust.Shared.Containers; using Robust.Shared.Player; using Robust.Shared.Timing; +using System.Linq; namespace Content.Server.Medical; @@ -24,6 +32,7 @@ public sealed class HealthAnalyzerSystem : EntitySystem [Dependency] private readonly PowerCellSystem _cell = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; + [Dependency] private readonly SharedBodySystem _bodySystem = default!; [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!; [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; [Dependency] private readonly TransformSystem _transformSystem = default!; @@ -35,6 +44,12 @@ public override void Initialize() SubscribeLocalEvent(OnInsertedIntoContainer); SubscribeLocalEvent(OnPowerCellSlotEmpty); SubscribeLocalEvent(OnDropped); + // Start-Shitmed + Subs.BuiEvents(HealthAnalyzerUiKey.Key, subs => + { + subs.Event(OnHealthAnalyzerPartSelected); + }); + // End-Shitmed } public override void Update(float frameTime) @@ -55,6 +70,17 @@ public override void Update(float frameTime) continue; } + // Shitmed Change Start + if (component.CurrentBodyPart != null + && (Deleted(component.CurrentBodyPart) + || TryComp(component.CurrentBodyPart, out BodyPartComponent? bodyPartComponent) + && bodyPartComponent.Body is null)) + { + BeginAnalyzingEntity((uid, component), patient, null); + continue; + } + // Shitmed Change End + component.NextUpdate = _timing.CurTime + component.UpdateInterval; //Get distance between health analyzer and the scanned entity @@ -66,7 +92,7 @@ public override void Update(float frameTime) continue; } - UpdateScannedUser(uid, patient, true); + UpdateScannedUser(uid, patient, true, component.CurrentBodyPart); } } @@ -129,10 +155,10 @@ private void OnDropped(Entity uid, ref DroppedEvent arg private void OpenUserInterface(EntityUid user, EntityUid analyzer) { - if (!TryComp(user, out var actor) || !_uiSystem.TryGetUi(analyzer, HealthAnalyzerUiKey.Key, out var ui)) + if (!_uiSystem.HasUi(analyzer, HealthAnalyzerUiKey.Key)) return; - _uiSystem.OpenUi(ui, actor.PlayerSession); + _uiSystem.OpenUi(analyzer, HealthAnalyzerUiKey.Key, user); } /// @@ -140,14 +166,14 @@ private void OpenUserInterface(EntityUid user, EntityUid analyzer) /// /// The health analyzer that should receive the updates /// The entity to start analyzing - private void BeginAnalyzingEntity(Entity healthAnalyzer, EntityUid target) + private void BeginAnalyzingEntity(Entity healthAnalyzer, EntityUid target, EntityUid? part = null) { //Link the health analyzer to the scanned entity healthAnalyzer.Comp.ScannedEntity = target; - + healthAnalyzer.Comp.CurrentBodyPart = part; _cell.SetPowerCellDrawEnabled(healthAnalyzer, true); - UpdateScannedUser(healthAnalyzer, target, true); + UpdateScannedUser(healthAnalyzer, target, true, part); } /// @@ -159,26 +185,50 @@ private void StopAnalyzingEntity(Entity healthAnalyzer, { //Unlink the analyzer healthAnalyzer.Comp.ScannedEntity = null; + healthAnalyzer.Comp.CurrentBodyPart = null; _cell.SetPowerCellDrawEnabled(target, false); UpdateScannedUser(healthAnalyzer, target, false); } + // Start-Shitmed + /// + /// Handle the selection of a body part on the health analyzer + /// + /// The health analyzer that's receiving the updates + /// The message containing the selected part + private void OnHealthAnalyzerPartSelected(Entity healthAnalyzer, ref HealthAnalyzerPartMessage args) + { + if (!TryGetEntity(args.Owner, out var owner)) + return; + + if (args.BodyPart == null) + { + BeginAnalyzingEntity(healthAnalyzer, owner.Value, null); + } + else + { + var (targetType, targetSymmetry) = _bodySystem.ConvertTargetBodyPart(args.BodyPart.Value); + if (_bodySystem.GetBodyChildrenOfType(owner.Value, targetType, symmetry: targetSymmetry) is { } part) + BeginAnalyzingEntity(healthAnalyzer, owner.Value, part.FirstOrDefault().Id); + } + } +// End-Shitmed + /// /// Send an update for the target to the healthAnalyzer /// /// The health analyzer /// The entity being scanned /// True makes the UI show ACTIVE, False makes the UI show INACTIVE - public void UpdateScannedUser(EntityUid healthAnalyzer, EntityUid target, bool scanMode) + public void UpdateScannedUser(EntityUid healthAnalyzer, EntityUid target, bool scanMode, EntityUid? part = null) { - if (!_uiSystem.TryGetUi(healthAnalyzer, HealthAnalyzerUiKey.Key, out var ui)) + if (!_uiSystem.HasUi(healthAnalyzer, HealthAnalyzerUiKey.Key)) return; if (!HasComp(target)) return; - var bodyTemperature = float.NaN; if (TryComp(target, out var temp)) @@ -186,6 +236,7 @@ public void UpdateScannedUser(EntityUid healthAnalyzer, EntityUid target, bool s var bloodAmount = float.NaN; var bleeding = false; + var unrevivable = false; if (TryComp(target, out var bloodstream) && _solutionContainerSystem.ResolveSolution(target, bloodstream.BloodSolutionName, @@ -195,14 +246,25 @@ public void UpdateScannedUser(EntityUid healthAnalyzer, EntityUid target, bool s bleeding = bloodstream.BleedAmount > 0; } + /*if (HasComp(target)) Somehow we dont have unrevivable??? + unrevivable = true; + */ + // Start-Shitmed + Dictionary? body = null; + if (HasComp(target)) + body = _bodySystem.GetBodyPartStatus(target); + // End-Shitmed - _uiSystem.SendUiMessage(ui, new HealthAnalyzerScannedUserMessage( + _uiSystem.ServerSendUiMessage(healthAnalyzer, HealthAnalyzerUiKey.Key, new HealthAnalyzerScannedUserMessage( GetNetEntity(target), bodyTemperature, bloodAmount, scanMode, - bleeding + bleeding, + unrevivable, + body, // Shitmed + part != null ? GetNetEntity(part) : null // Shitmed )); } } diff --git a/Content.Server/Medical/MedicalScannerSystem.cs b/Content.Server/Medical/MedicalScannerSystem.cs index 91184ddc16..ab6918e373 100644 --- a/Content.Server/Medical/MedicalScannerSystem.cs +++ b/Content.Server/Medical/MedicalScannerSystem.cs @@ -7,6 +7,7 @@ using Content.Shared.Verbs; using Robust.Shared.Containers; using Content.Server.Cloning.Components; +using Content.Server.Construction; using Content.Server.DeviceLinking.Systems; using Content.Shared.DeviceLinking.Events; using Content.Server.Power.EntitySystems; @@ -15,7 +16,7 @@ using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; using Robust.Server.Containers; -using static Content.Shared.MedicalScanner.SharedMedicalScannerComponent; // Hmm... +using static Content.Shared.MedicalScanner.SharedMedicalScannerComponent; namespace Content.Server.Medical { @@ -44,6 +45,8 @@ public override void Initialize() SubscribeLocalEvent(OnDragDropOn); SubscribeLocalEvent(OnPortDisconnected); SubscribeLocalEvent(OnAnchorChanged); + SubscribeLocalEvent(OnRefreshParts); + SubscribeLocalEvent(OnUpgradeExamine); SubscribeLocalEvent(OnCanDragDropOn); } @@ -78,22 +81,18 @@ private void OnRelayMovement(EntityUid uid, MedicalScannerComponent scannerCompo private void AddInsertOtherVerb(EntityUid uid, MedicalScannerComponent component, GetVerbsEvent args) { - if (args.Using == null || - !args.CanAccess || - !args.CanInteract || - IsOccupied(component) || - !CanScannerInsert(uid, args.Using.Value, component)) + if (args.Using == null + || !args.CanAccess + || !args.CanInteract + || IsOccupied(component) + || !CanScannerInsert(uid, args.Using.Value, component)) return; - var name = "Unknown"; - if (TryComp(args.Using.Value, out var metadata)) - name = metadata.EntityName; - InteractionVerb verb = new() { Act = () => InsertBody(uid, args.Target, component), Category = VerbCategory.Insert, - Text = name + Text = MetaData(args.Using.Value).EntityName }; args.Verbs.Add(verb); } @@ -115,11 +114,8 @@ private void AddAlternativeVerbs(EntityUid uid, MedicalScannerComponent componen }; args.Verbs.Add(verb); } - - // Self-insert verb - if (!IsOccupied(component) && - CanScannerInsert(uid, args.User, component) && - _blocker.CanMove(args.User)) + else if (CanScannerInsert(uid, args.User, component) + && _blocker.CanMove(args.User)) { AlternativeVerb verb = new() { @@ -147,59 +143,48 @@ private void OnPortDisconnected(EntityUid uid, MedicalScannerComponent component private void OnAnchorChanged(EntityUid uid, MedicalScannerComponent component, ref AnchorStateChangedEvent args) { - if (component.ConnectedConsole == null || !TryComp(component.ConnectedConsole, out var console)) + if (component.ConnectedConsole == null + || !args.Anchored + || !TryComp(component.ConnectedConsole, out var console) + || !_cloningConsoleSystem.RecheckConnections(component.ConnectedConsole.Value, console.CloningPod, uid, console)) return; - if (args.Anchored) - { - _cloningConsoleSystem.RecheckConnections(component.ConnectedConsole.Value, console.CloningPod, uid, console); - return; - } _cloningConsoleSystem.UpdateUserInterface(component.ConnectedConsole.Value, console); } + private MedicalScannerStatus GetStatus(EntityUid uid, MedicalScannerComponent scannerComponent) { - if (this.IsPowered(uid, EntityManager)) - { - var body = scannerComponent.BodyContainer.ContainedEntity; - if (body == null) - return MedicalScannerStatus.Open; + if (!this.IsPowered(uid, EntityManager)) + return MedicalScannerStatus.Off; - if (!TryComp(body.Value, out var state)) - { // Is not alive or dead or critical - return MedicalScannerStatus.Yellow; - } + var body = scannerComponent.BodyContainer.ContainedEntity; + if (body == null) + return MedicalScannerStatus.Open; - return GetStatusFromDamageState(body.Value, state); - } - return MedicalScannerStatus.Off; - } + if (!TryComp(body.Value, out var state)) + return MedicalScannerStatus.Yellow; - public static bool IsOccupied(MedicalScannerComponent scannerComponent) - { - return scannerComponent.BodyContainer.ContainedEntity != null; - } - - private MedicalScannerStatus GetStatusFromDamageState(EntityUid uid, MobStateComponent state) - { - if (_mobStateSystem.IsAlive(uid, state)) + if (_mobStateSystem.IsAlive(body.Value, state)) return MedicalScannerStatus.Green; - if (_mobStateSystem.IsCritical(uid, state)) + if (_mobStateSystem.IsCritical(body.Value, state)) return MedicalScannerStatus.Red; - if (_mobStateSystem.IsDead(uid, state)) + if (_mobStateSystem.IsDead(body.Value, state)) return MedicalScannerStatus.Death; return MedicalScannerStatus.Yellow; } + public static bool IsOccupied(MedicalScannerComponent scannerComponent) + { + return scannerComponent.BodyContainer.ContainedEntity != null; + } + private void UpdateAppearance(EntityUid uid, MedicalScannerComponent scannerComponent) { if (TryComp(uid, out var appearance)) - { _appearance.SetData(uid, MedicalScannerVisuals.Status, GetStatus(uid, scannerComponent), appearance); - } } public override void Update(float frameTime) @@ -214,20 +199,14 @@ public override void Update(float frameTime) var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var scanner)) - { UpdateAppearance(uid, scanner); - } } public void InsertBody(EntityUid uid, EntityUid to_insert, MedicalScannerComponent? scannerComponent) { - if (!Resolve(uid, ref scannerComponent)) - return; - - if (scannerComponent.BodyContainer.ContainedEntity != null) - return; - - if (!HasComp(to_insert)) + if (!Resolve(uid, ref scannerComponent) + || scannerComponent.BodyContainer.ContainedEntity != null + || !HasComp(to_insert)) return; _containerSystem.Insert(to_insert, scannerComponent.BodyContainer); @@ -236,15 +215,25 @@ public void InsertBody(EntityUid uid, EntityUid to_insert, MedicalScannerCompone public void EjectBody(EntityUid uid, MedicalScannerComponent? scannerComponent) { - if (!Resolve(uid, ref scannerComponent)) - return; - - if (scannerComponent.BodyContainer.ContainedEntity is not { Valid: true } contained) + if (!Resolve(uid, ref scannerComponent) + || scannerComponent.BodyContainer.ContainedEntity is not { Valid: true } contained) return; _containerSystem.Remove(contained, scannerComponent.BodyContainer); _climbSystem.ForciblySetClimbing(contained, uid); UpdateAppearance(uid, scannerComponent); } + + private void OnRefreshParts(EntityUid uid, MedicalScannerComponent component, RefreshPartsEvent args) + { + var ratingFail = args.PartRatings[component.MachinePartCloningFailChance]; + + component.CloningFailChanceMultiplier = MathF.Pow(component.PartRatingFailMultiplier, ratingFail - 1); + } + + private void OnUpgradeExamine(EntityUid uid, MedicalScannerComponent component, UpgradeExamineEvent args) + { + args.AddPercentageUpgrade("medical-scanner-upgrade-cloning", component.CloningFailChanceMultiplier); + } } } diff --git a/Content.Server/Medical/PenLightSystem.cs b/Content.Server/Medical/PenLightSystem.cs index f48a84d047..f1df356cab 100644 --- a/Content.Server/Medical/PenLightSystem.cs +++ b/Content.Server/Medical/PenLightSystem.cs @@ -1,4 +1,5 @@ using Content.Server.DoAfter; +using Content.Server.Popups; using Content.Server.PowerCell; using Content.Shared.Damage; using Content.Shared.DoAfter; @@ -7,6 +8,7 @@ using Content.Shared.Eye.Blinding.Components; using Content.Shared.Interaction; using Content.Shared.Medical; +using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; using Content.Shared.Traits.Assorted.Components; using Robust.Server.GameObjects; @@ -22,7 +24,9 @@ public sealed class PenLightSystem : EntitySystem [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly DoAfterSystem _doAfter = default!; [Dependency] private readonly PowerCellSystem _powerCell = default!; + [Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; + /// public override void Initialize() { @@ -30,20 +34,23 @@ public override void Initialize() SubscribeLocalEvent(OnDoAfter); } - private void OnAfterInteract(EntityUid uid, PenLightComponent component, AfterInteractEvent args) + private void OnAfterInteract(EntityUid uid, PenLightComponent component, ref AfterInteractEvent args) { - if (args.Handled - || args.Target is not { } target) + if (args.Handled + || args.Target is not {} target + || target == null + || !args.CanReach + || !HasComp(target) + || !_powerCell.HasDrawCharge(uid, user: args.User)) return; - args.Handled = TryStartExam(uid, target, args.User, component); } private void OnDoAfter(Entity uid, ref PenLightDoAfterEvent args) { - if (args.Handled - || args.Cancelled - || args.Target == null + if (args.Handled + || args.Cancelled + || args.Target == null || !_powerCell.HasDrawCharge(uid, user: args.User)) return; @@ -52,6 +59,13 @@ private void OnDoAfter(Entity uid, ref PenLightDoAfterEvent a args.Handled = true; } + /// + /// Checks if the PointLight component is enabled. + /// + private bool IsLightEnabled(EntityUid uid) + { + return TryComp(uid, out var pointLight) && pointLight.Enabled; + } /// /// Actually handles the exam interaction. @@ -61,6 +75,18 @@ public bool TryStartExam(EntityUid uid, EntityUid target, EntityUid user, PenLig if (!Resolve(uid, ref component)) return false; + if (!IsLightEnabled(uid)) + { + if (user != null) + _popup.PopupEntity(Loc.GetString("penlight-off"), uid, user); + return false; + } + // can't examine your own eyes, dingus + if (user == target) + { + _popup.PopupEntity(Loc.GetString("penlight-cannot-examine-self"), uid, user); + return false; + } return _doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, user, component.ExamSpeed, new PenLightDoAfterEvent(), uid, target, uid) { @@ -73,11 +99,10 @@ public bool TryStartExam(EntityUid uid, EntityUid target, EntityUid user, PenLig } private void OpenUserInterface(EntityUid user, EntityUid penlight) { - if (!TryComp(user, out var actor) - || !_uiSystem.TryGetUi(penlight, PenLightUiKey.Key, out var ui)) + if (!_uiSystem.HasUi(penlight, PenLightUiKey.Key)) return; - _uiSystem.OpenUi(ui, actor.PlayerSession); + _uiSystem.OpenUi(penlight, PenLightUiKey.Key, user); } /// @@ -85,9 +110,11 @@ private void OpenUserInterface(EntityUid user, EntityUid penlight) /// private void Diagnose(EntityUid penlight, EntityUid target) { - if (!_uiSystem.TryGetUi(penlight, PenLightUiKey.Key, out var ui) - || !HasComp(target)) + if (!_uiSystem.HasUi(penlight, PenLightUiKey.Key) + || !HasComp(target) + || !HasComp(target)) return; + // Blind var blind = _entityManager.HasComponent(target); @@ -107,12 +134,16 @@ private void Diagnose(EntityUid penlight, EntityUid target) // Healthy var healthy = !(blind || drunk || eyeDamage || seeingRainbows); - _uiSystem.SendUiMessage(ui, new PenLightUserMessage(GetNetEntity(target), - blind, - drunk, - eyeDamage, - healthy, - seeingRainbows - )); + _uiSystem.ServerSendUiMessage( + penlight, + PenLightUiKey.Key, + new PenLightUserMessage(GetNetEntity(target), + blind, + drunk, + eyeDamage, + healthy, + seeingRainbows + ) + ); } } diff --git a/Content.Server/Medical/Stethoscope/StethoscopeSystem.cs b/Content.Server/Medical/Stethoscope/StethoscopeSystem.cs index 9521c14f2e..96bfc7c904 100644 --- a/Content.Server/Medical/Stethoscope/StethoscopeSystem.cs +++ b/Content.Server/Medical/Stethoscope/StethoscopeSystem.cs @@ -3,11 +3,10 @@ using Content.Server.Medical.Stethoscope.Components; using Content.Server.Popups; using Content.Shared.Actions; -using Content.Shared.Clothing.Components; +using Content.Shared.Clothing; using Content.Shared.Damage; using Content.Shared.DoAfter; using Content.Shared.FixedPoint; -using Content.Shared.Inventory.Events; using Content.Shared.Medical; using Content.Shared.Medical.Stethoscope; using Content.Shared.Mobs.Components; @@ -26,8 +25,8 @@ public sealed class StethoscopeSystem : EntitySystem public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnEquipped); - SubscribeLocalEvent(OnUnequipped); + SubscribeLocalEvent(OnEquipped); + SubscribeLocalEvent(OnUnequipped); SubscribeLocalEvent>(AddStethoscopeVerb); SubscribeLocalEvent(OnGetActions); SubscribeLocalEvent(OnStethoscopeAction); @@ -37,26 +36,20 @@ public override void Initialize() /// /// Add the component the verb event subs to if the equippee is wearing the stethoscope. /// - private void OnEquipped(EntityUid uid, StethoscopeComponent component, GotEquippedEvent args) + private void OnEquipped(EntityUid uid, StethoscopeComponent component, ref ClothingGotEquippedEvent args) { - if (!TryComp(uid, out var clothing)) - return; - // Is the clothing in its actual slot? - if (!clothing.Slots.HasFlag(args.SlotFlags)) - return; - component.IsActive = true; - var wearingComp = EnsureComp(args.Equipee); + var wearingComp = EnsureComp(args.Wearer); wearingComp.Stethoscope = uid; } - private void OnUnequipped(EntityUid uid, StethoscopeComponent component, GotUnequippedEvent args) + private void OnUnequipped(EntityUid uid, StethoscopeComponent component, ref ClothingGotUnequippedEvent args) { if (!component.IsActive) return; - RemComp(args.Equipee); + RemComp(args.Wearer); component.IsActive = false; } diff --git a/Content.Server/Medical/SuitSensors/SuitSensorSystem.cs b/Content.Server/Medical/SuitSensors/SuitSensorSystem.cs index 0c4f1ec546..bd904a03b0 100644 --- a/Content.Server/Medical/SuitSensors/SuitSensorSystem.cs +++ b/Content.Server/Medical/SuitSensors/SuitSensorSystem.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Content.Server.Access.Systems; using Content.Server.DeviceNetwork; using Content.Server.DeviceNetwork.Components; @@ -7,10 +8,10 @@ using Content.Server.Medical.CrewMonitoring; using Content.Server.Popups; using Content.Server.Station.Systems; +using Content.Shared.Clothing; using Content.Shared.Damage; using Content.Shared.DeviceNetwork; using Content.Shared.Examine; -using Content.Shared.Inventory.Events; using Content.Shared.Medical.SuitSensor; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; @@ -40,8 +41,8 @@ public override void Initialize() base.Initialize(); SubscribeLocalEvent(OnPlayerSpawn); SubscribeLocalEvent(OnMapInit); - SubscribeLocalEvent(OnEquipped); - SubscribeLocalEvent(OnUnequipped); + SubscribeLocalEvent(OnEquipped); + SubscribeLocalEvent(OnUnequipped); SubscribeLocalEvent(OnExamine); SubscribeLocalEvent>(OnVerb); SubscribeLocalEvent(OnInsert); @@ -165,19 +166,13 @@ private void OnMapInit(EntityUid uid, SuitSensorComponent component, MapInitEven } } - private void OnEquipped(EntityUid uid, SuitSensorComponent component, GotEquippedEvent args) + private void OnEquipped(EntityUid uid, SuitSensorComponent component, ref ClothingGotEquippedEvent args) { - if (args.Slot != component.ActivationSlot) - return; - - component.User = args.Equipee; + component.User = args.Wearer; } - private void OnUnequipped(EntityUid uid, SuitSensorComponent component, GotUnequippedEvent args) + private void OnUnequipped(EntityUid uid, SuitSensorComponent component, ref ClothingGotUnequippedEvent args) { - if (args.Slot != component.ActivationSlot) - return; - component.User = null; } @@ -377,8 +372,8 @@ public void SetSensor(EntityUid uid, SuitSensorMode mode, EntityUid? userUid = n if (transform.GridUid != null) { coordinates = new EntityCoordinates(transform.GridUid.Value, - _transform.GetInvWorldMatrix(xformQuery.GetComponent(transform.GridUid.Value), xformQuery) - .Transform(_transform.GetWorldPosition(transform, xformQuery))); + Vector2.Transform(_transform.GetWorldPosition(transform, xformQuery), + _transform.GetInvWorldMatrix(xformQuery.GetComponent(transform.GridUid.Value), xformQuery))); } else if (transform.MapUid != null) { diff --git a/Content.Server/Medical/Surgery/SurgerySystem.cs b/Content.Server/Medical/Surgery/SurgerySystem.cs new file mode 100644 index 0000000000..615166390a --- /dev/null +++ b/Content.Server/Medical/Surgery/SurgerySystem.cs @@ -0,0 +1,189 @@ +using Content.Server.Atmos.Rotting; +using Content.Server.Body.Systems; +using Content.Server.Chat.Systems; +using Content.Shared.Body.Organ; +using Content.Shared.Body.Part; +using Content.Server.Popups; +using Content.Shared.Bed.Sleep; +using Content.Shared.CCVar; +using Content.Shared.Damage; +using Content.Shared.Eye.Blinding.Components; +using Content.Shared.Eye.Blinding.Systems; +using Content.Shared.Interaction; +using Content.Shared.Inventory; +using Content.Shared.Medical.Surgery; +using Content.Shared.Medical.Surgery.Conditions; +using Content.Shared.Medical.Surgery.Effects.Step; +using Content.Shared.Medical.Surgery.Steps; +using Content.Shared.Medical.Surgery.Steps.Parts; +using Content.Shared.Medical.Surgery.Tools; +using Content.Shared.Mood; +using Content.Shared.Prototypes; +using Robust.Server.GameObjects; +using Robust.Shared.Configuration; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; +using System.Linq; + +namespace Content.Server.Medical.Surgery; + +public sealed class SurgerySystem : SharedSurgerySystem +{ + [Dependency] private readonly BodySystem _body = default!; + [Dependency] private readonly ChatSystem _chat = default!; + [Dependency] private readonly IConfigurationManager _config = default!; + [Dependency] private readonly DamageableSystem _damageable = default!; + [Dependency] private readonly IPrototypeManager _prototypes = default!; + [Dependency] private readonly PopupSystem _popup = default!; + [Dependency] private readonly UserInterfaceSystem _ui = default!; + [Dependency] private readonly RottingSystem _rot = default!; + [Dependency] private readonly BlindableSystem _blindableSystem = default!; + + private readonly List _surgeries = new(); + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnToolAfterInteract); + SubscribeLocalEvent(OnSurgeryStepDamage); + SubscribeLocalEvent(OnSurgeryDamageChange); + SubscribeLocalEvent(OnSurgerySpecialDamageChange); + SubscribeLocalEvent(OnStepScreamComplete); + SubscribeLocalEvent(OnStepSpawnComplete); + SubscribeLocalEvent(OnPrototypesReloaded); + LoadPrototypes(); + } + + protected override void RefreshUI(EntityUid body) + { + var surgeries = new Dictionary>(); + foreach (var surgery in _surgeries) + { + if (GetSingleton(surgery) is not { } surgeryEnt) + continue; + + foreach (var part in _body.GetBodyChildren(body)) + { + var ev = new SurgeryValidEvent(body, part.Id); + RaiseLocalEvent(surgeryEnt, ref ev); + + if (ev.Cancelled) + continue; + + surgeries.GetOrNew(GetNetEntity(part.Id)).Add(surgery); + } + + } + _ui.SetUiState(body, SurgeryUIKey.Key, new SurgeryBuiState(surgeries)); + /* + Reason we do this is because when applying a BUI State, it rolls back the state on the entity temporarily, + which just so happens to occur right as we're checking for step completion, so we end up with the UI + not updating at all until you change tools or reopen the window. I love shitcode. + */ + _ui.ServerSendUiMessage(body, SurgeryUIKey.Key, new SurgeryBuiRefreshMessage()); + } + private void SetDamage(EntityUid body, + DamageSpecifier damage, + float partMultiplier, + EntityUid user, + EntityUid part) + { + if (!TryComp(part, out var partComp)) + return; + + _damageable.TryChangeDamage(body, + damage, + true, + origin: user, + canSever: false, + partMultiplier: partMultiplier, + targetPart: _body.GetTargetBodyPart(partComp)); + } + + private void OnToolAfterInteract(Entity ent, ref AfterInteractEvent args) + { + var user = args.User; + if (args.Handled + || !args.CanReach + || args.Target == null + || !HasComp(args.Target) + || !TryComp(args.User, out var surgery) + || !surgery.CanOperate + || !IsLyingDown(args.Target.Value, args.User)) + { + return; + } + + if (user == args.Target && !_config.GetCVar(CCVars.CanOperateOnSelf)) + { + _popup.PopupEntity(Loc.GetString("surgery-error-self-surgery"), user, user); + return; + } + + args.Handled = true; + _ui.OpenUi(args.Target.Value, SurgeryUIKey.Key, user); + RefreshUI(args.Target.Value); + } + + private void OnSurgeryStepDamage(Entity ent, ref SurgeryStepDamageEvent args) => + SetDamage(args.Body, args.Damage, args.PartMultiplier, args.User, args.Part); + + private void OnSurgeryDamageChange(Entity ent, ref SurgeryStepEvent args) + { + // This unintentionally punishes the user if they have an organ in another hand that is already used. + // Imo surgery shouldn't let you automatically pick tools on both hands anyway, it should only use the one you've got in your selected hand. + if (ent.Comp.IsConsumable + && args.Tools.Where(tool => TryComp(tool, out var organComp) + && !_body.TrySetOrganUsed(tool, true, organComp)).Any()) + return; + + var damageChange = ent.Comp.Damage; + if (HasComp(args.Body)) + damageChange = damageChange * ent.Comp.SleepModifier; + + SetDamage(args.Body, damageChange, 0.5f, args.User, args.Part); + } + + private void OnSurgerySpecialDamageChange(Entity ent, ref SurgeryStepEvent args) + { + if (ent.Comp.IsConsumable + && args.Tools.Where(tool => TryComp(tool, out var organComp) + && !_body.TrySetOrganUsed(tool, true, organComp)).Any()) + return; + + if (ent.Comp.DamageType == "Rot") + _rot.ReduceAccumulator(args.Body, TimeSpan.FromSeconds(2147483648)); // BEHOLD, SHITCODE THAT I JUST COPY PASTED. I'll redo it at some point, pinky swear :) + else if (ent.Comp.DamageType == "Eye" + && TryComp(args.Body, out BlindableComponent? blindComp) + && blindComp.EyeDamage > 0) + _blindableSystem.AdjustEyeDamage((args.Body, blindComp), -blindComp!.EyeDamage); + } + + private void OnStepScreamComplete(Entity ent, ref SurgeryStepEvent args) + { + if (HasComp(args.Body)) + return; + + _chat.TryEmoteWithChat(args.Body, ent.Comp.Emote); + } + private void OnStepSpawnComplete(Entity ent, ref SurgeryStepEvent args) => + SpawnAtPosition(ent.Comp.Entity, Transform(args.Body).Coordinates); + + private void OnPrototypesReloaded(PrototypesReloadedEventArgs args) + { + if (!args.WasModified()) + return; + + LoadPrototypes(); + } + + private void LoadPrototypes() + { + _surgeries.Clear(); + foreach (var entity in _prototypes.EnumeratePrototypes()) + if (entity.HasComponent()) + _surgeries.Add(new EntProtoId(entity.ID)); + } +} diff --git a/Content.Server/Medical/VomitSystem.cs b/Content.Server/Medical/VomitSystem.cs index 8c3b15aed3..dc049b2a1d 100644 --- a/Content.Server/Medical/VomitSystem.cs +++ b/Content.Server/Medical/VomitSystem.cs @@ -11,6 +11,7 @@ using Content.Shared.Nutrition.EntitySystems; using Content.Shared.StatusEffect; using Robust.Server.Audio; +using Content.Shared.Mood; using Robust.Shared.Audio; using Robust.Shared.Prototypes; @@ -94,6 +95,8 @@ public void Vomit(EntityUid uid, float thirstAdded = -40f, float hungerAdded = - // Force sound to play as spill doesn't work if solution is empty. _audio.PlayPvs("/Audio/Effects/Fluids/splat.ogg", uid, AudioParams.Default.WithVariation(0.2f).WithVolume(-4f)); _popup.PopupEntity(Loc.GetString("disease-vomit", ("person", Identity.Entity(uid, EntityManager))), uid); + + RaiseLocalEvent(uid, new MoodEffectEvent("MobVomit")); } } } diff --git a/Content.Server/Mind/Commands/MakeSentientCommand.cs b/Content.Server/Mind/Commands/MakeSentientCommand.cs index b58d782d9c..450f0712a1 100644 --- a/Content.Server/Mind/Commands/MakeSentientCommand.cs +++ b/Content.Server/Mind/Commands/MakeSentientCommand.cs @@ -3,7 +3,7 @@ using Content.Shared.Administration; using Content.Shared.Emoting; using Content.Shared.Examine; -using Content.Shared.Language; +using Content.Shared.Language.Components; using Content.Shared.Language.Systems; using Content.Shared.Mind.Components; using Content.Shared.Movement.Components; diff --git a/Content.Server/Mind/MindSystem.cs b/Content.Server/Mind/MindSystem.cs index dc12836d90..4271d76b44 100644 --- a/Content.Server/Mind/MindSystem.cs +++ b/Content.Server/Mind/MindSystem.cs @@ -1,6 +1,7 @@ using System.Diagnostics.CodeAnalysis; using Content.Server.Administration.Logs; using Content.Server.GameTicking; +using Content.Server.Ghost; using Content.Server.Mind.Commands; using Content.Shared.Database; using Content.Shared.Ghost; @@ -9,10 +10,8 @@ using Content.Shared.Players; using Robust.Server.GameStates; using Robust.Server.Player; -using Robust.Shared.Map.Components; using Robust.Shared.Network; using Robust.Shared.Player; -using Robust.Shared.Timing; using Robust.Shared.Utility; namespace Content.Server.Mind; @@ -22,8 +21,7 @@ public sealed class MindSystem : SharedMindSystem [Dependency] private readonly GameTicker _gameTicker = default!; [Dependency] private readonly IAdminLogManager _adminLogger = default!; [Dependency] private readonly IPlayerManager _players = default!; - [Dependency] private readonly MetaDataSystem _metaData = default!; - [Dependency] private readonly SharedGhostSystem _ghosts = default!; + [Dependency] private readonly GhostSystem _ghosts = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly PvsOverrideSystem _pvsOverride = default!; @@ -63,8 +61,8 @@ private void OnMindContainerTerminating(EntityUid uid, MindContainerComponent co && !Terminating(visiting)) { TransferTo(mindId, visiting, mind: mind); - if (TryComp(visiting, out GhostComponent? ghost)) - _ghosts.SetCanReturnToBody(ghost, false); + if (TryComp(visiting, out GhostComponent? ghostComp)) + _ghosts.SetCanReturnToBody(ghostComp, false); return; } @@ -74,40 +72,13 @@ private void OnMindContainerTerminating(EntityUid uid, MindContainerComponent co if (!component.GhostOnShutdown || mind.Session == null || _gameTicker.RunLevel == GameRunLevel.PreRoundLobby) return; - var xform = Transform(uid); - var gridId = xform.GridUid; - var spawnPosition = Transform(uid).Coordinates; - - // Use a regular timer here because the entity has probably been deleted. - Timer.Spawn(0, () => - { - // Make extra sure the round didn't end between spawning the timer and it being executed. - if (_gameTicker.RunLevel == GameRunLevel.PreRoundLobby) - return; - - // Async this so that we don't throw if the grid we're on is being deleted. - if (!HasComp(gridId)) - spawnPosition = _gameTicker.GetObserverSpawnPoint(); - - // TODO refactor observer spawning. - // please. - if (!spawnPosition.IsValid(EntityManager)) - { - // This should be an error, if it didn't cause tests to start erroring when they delete a player. - Log.Warning($"Entity \"{ToPrettyString(uid)}\" for {mind.CharacterName} was deleted, and no applicable spawn location is available."); - TransferTo(mindId, null, createGhost: false, mind: mind); - return; - } - - var ghost = Spawn(GameTicker.ObserverPrototypeName, spawnPosition); - var ghostComponent = Comp(ghost); - _ghosts.SetCanReturnToBody(ghostComponent, false); - + var ghost = _ghosts.SpawnGhost((mindId, mind), uid); + if (ghost != null) // Log these to make sure they're not causing the GameTicker round restart bugs... Log.Debug($"Entity \"{ToPrettyString(uid)}\" for {mind.CharacterName} was deleted, spawned \"{ToPrettyString(ghost)}\"."); - _metaData.SetEntityName(ghost, mind.CharacterName ?? string.Empty); - TransferTo(mindId, ghost, mind: mind); - }); + else + // This should be an error, if it didn't cause tests to start erroring when they delete a player. + Log.Warning($"Entity \"{ToPrettyString(uid)}\" for {mind.CharacterName} was deleted, and no applicable spawn location is available."); } public override bool TryGetMind(NetUserId user, [NotNullWhen(true)] out EntityUid? mindId, [NotNullWhen(true)] out MindComponent? mind) diff --git a/Content.Server/Mobs/DeathgaspComponent.cs b/Content.Server/Mobs/DeathgaspComponent.cs index cb1f02f0d9..7afcaa1965 100644 --- a/Content.Server/Mobs/DeathgaspComponent.cs +++ b/Content.Server/Mobs/DeathgaspComponent.cs @@ -1,4 +1,4 @@ -using Content.Shared.Chat.Prototypes; +using Content.Shared.Chat.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; namespace Content.Server.Mobs; @@ -13,6 +13,12 @@ public sealed partial class DeathgaspComponent : Component /// /// The emote prototype to use. /// - [DataField("prototype", customTypeSerializer:typeof(PrototypeIdSerializer))] + [DataField(customTypeSerializer:typeof(PrototypeIdSerializer))] public string Prototype = "DefaultDeathgasp"; + + /// + /// Makes sure that the deathgasp is only displayed if the entity went critical before dying + /// + [DataField] + public bool NeedsCritical = true; } diff --git a/Content.Server/Mobs/DeathgaspSystem.cs b/Content.Server/Mobs/DeathgaspSystem.cs index c531784ea6..32be7bfe32 100644 --- a/Content.Server/Mobs/DeathgaspSystem.cs +++ b/Content.Server/Mobs/DeathgaspSystem.cs @@ -1,4 +1,4 @@ -using Content.Server.Chat.Systems; +using Content.Server.Chat.Systems; using Content.Server.Speech.Muting; using Content.Shared.Mobs; using Content.Shared.Speech.Muting; @@ -21,7 +21,8 @@ public override void Initialize() private void OnMobStateChanged(EntityUid uid, DeathgaspComponent component, MobStateChangedEvent args) { // don't deathgasp if they arent going straight from crit to dead - if (args.NewMobState != MobState.Dead || args.OldMobState != MobState.Critical) + if (component.NeedsCritical && args.OldMobState != MobState.Critical + || args.NewMobState != MobState.Dead) return; Deathgasp(uid, component); diff --git a/Content.Server/Mood/MoodComponent.cs b/Content.Server/Mood/MoodComponent.cs new file mode 100644 index 0000000000..7fd4a7136f --- /dev/null +++ b/Content.Server/Mood/MoodComponent.cs @@ -0,0 +1,111 @@ +using Content.Shared.Alert; +using Content.Shared.FixedPoint; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Generic; + +namespace Content.Server.Mood; + +[RegisterComponent] +public sealed partial class MoodComponent : Component +{ + [DataField] + public float CurrentMoodLevel; + + [DataField] + public MoodThreshold CurrentMoodThreshold; + + [DataField] + public MoodThreshold LastThreshold; + + [ViewVariables(VVAccess.ReadOnly)] + public readonly Dictionary CategorisedEffects = new(); + + [ViewVariables(VVAccess.ReadOnly)] + public readonly Dictionary UncategorisedEffects = new(); + + /// + /// The formula for the movement speed modifier is SpeedBonusGrowth ^ (MoodLevel - MoodThreshold.Neutral). + /// Change this ONLY BY 0.001 AT A TIME. + /// + [DataField] + public float SpeedBonusGrowth = 1.003f; + + /// + /// The lowest point that low morale can multiply our movement speed by. Lowering speed follows a linear curve, rather than geometric. + /// + [DataField] + public float MinimumSpeedModifier = 0.75f; + + /// + /// The maximum amount that high morale can multiply our movement speed by. This follows a significantly slower geometric sequence. + /// + [DataField] + public float MaximumSpeedModifier = 1.15f; + + [DataField] + public float IncreaseCritThreshold = 1.2f; + + [DataField] + public float DecreaseCritThreshold = 0.9f; + + [ViewVariables(VVAccess.ReadOnly)] + public FixedPoint2 CritThresholdBeforeModify; + + [DataField(customTypeSerializer: typeof(DictionarySerializer))] + public Dictionary MoodThresholds = new() + { + { MoodThreshold.Perfect, 100f }, + { MoodThreshold.Exceptional, 80f }, + { MoodThreshold.Great, 70f }, + { MoodThreshold.Good, 60f }, + { MoodThreshold.Neutral, 50f }, + { MoodThreshold.Meh, 40f }, + { MoodThreshold.Bad, 30f }, + { MoodThreshold.Terrible, 20f }, + { MoodThreshold.Horrible, 10f }, + { MoodThreshold.Dead, 0f } + }; + + [DataField(customTypeSerializer: typeof(DictionarySerializer))] + public Dictionary MoodThresholdsAlerts = new() + { + { MoodThreshold.Dead, AlertType.MoodDead }, + { MoodThreshold.Horrible, AlertType.Horrible }, + { MoodThreshold.Terrible, AlertType.Terrible }, + { MoodThreshold.Bad, AlertType.Bad }, + { MoodThreshold.Meh, AlertType.Meh }, + { MoodThreshold.Neutral, AlertType.Neutral }, + { MoodThreshold.Good, AlertType.Good }, + { MoodThreshold.Great, AlertType.Great }, + { MoodThreshold.Exceptional, AlertType.Exceptional }, + { MoodThreshold.Perfect, AlertType.Perfect }, + { MoodThreshold.Insane, AlertType.Insane } + }; + + /// + /// These thresholds represent a percentage of Crit-Threshold, 0.8 corresponding with 80%. + /// + [DataField(customTypeSerializer: typeof(DictionarySerializer))] + public Dictionary HealthMoodEffectsThresholds = new() + { + { "HealthHeavyDamage", 0.8f }, + { "HealthSevereDamage", 0.5f }, + { "HealthLightDamage", 0.1f }, + { "HealthNoDamage", 0.05f } + }; +} + +[Serializable] +public enum MoodThreshold : ushort +{ + Insane = 1, + Horrible = 2, + Terrible = 3, + Bad = 4, + Meh = 5, + Neutral = 6, + Good = 7, + Great = 8, + Exceptional = 9, + Perfect = 10, + Dead = 0 +} diff --git a/Content.Server/Mood/MoodSystem.cs b/Content.Server/Mood/MoodSystem.cs new file mode 100644 index 0000000000..4ec4709ea7 --- /dev/null +++ b/Content.Server/Mood/MoodSystem.cs @@ -0,0 +1,464 @@ +using Content.Server.Chat.Managers; +using Content.Server.Popups; +using Content.Shared.Alert; +using Content.Shared.Chat; +using Content.Shared.Damage; +using Content.Shared.FixedPoint; +using Content.Shared.Mobs; +using Content.Shared.Mobs.Components; +using Content.Shared.Mobs.Systems; +using Content.Shared.Movement.Systems; +using Content.Shared.Mood; +using Content.Shared.Overlays; +using Content.Shared.Popups; +using Content.Shared.Traits.Assorted.Components; +using JetBrains.Annotations; +using Robust.Shared.Prototypes; +using Timer = Robust.Shared.Timing.Timer; +using Robust.Server.Player; +using Robust.Shared.Player; +using Robust.Shared.Configuration; +using Content.Shared.CCVar; + +namespace Content.Server.Mood; + +public sealed class MoodSystem : EntitySystem +{ + [Dependency] private readonly AlertsSystem _alerts = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!; + [Dependency] private readonly SharedJetpackSystem _jetpack = default!; + [Dependency] private readonly MobThresholdSystem _mobThreshold = default!; + [Dependency] private readonly PopupSystem _popup = default!; + [Dependency] private readonly IConfigurationManager _config = default!; + +#if RELEASE + // Disable Mood for tests, because of a stupid race condition where if it spawns an Urist McHarpy, + // the Harpy will choke during the test, creating a mood alert. + // And then cause a debug assert. + private bool _debugMode; +#else + private bool _debugMode = true; +#endif + + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnMobStateChanged); + SubscribeLocalEvent(OnMoodEffect); + SubscribeLocalEvent(OnDamageChange); + SubscribeLocalEvent(OnRefreshMoveSpeed); + SubscribeLocalEvent(OnRemoveEffect); + } + + private void OnShutdown(EntityUid uid, MoodComponent component, ComponentShutdown args) + { + _alerts.ClearAlertCategory(uid, AlertCategory.Mood); + } + + private void OnRemoveEffect(EntityUid uid, MoodComponent component, MoodRemoveEffectEvent args) + { + if (_debugMode) + return; + + if (component.UncategorisedEffects.TryGetValue(args.EffectId, out _)) + RemoveTimedOutEffect(uid, args.EffectId); + else + foreach (var (category, id) in component.CategorisedEffects) + if (id == args.EffectId) + { + RemoveTimedOutEffect(uid, args.EffectId, category); + return; + } + } + + private void OnRefreshMoveSpeed(EntityUid uid, MoodComponent component, RefreshMovementSpeedModifiersEvent args) + { + if (_debugMode + || component.CurrentMoodThreshold is > MoodThreshold.Meh and < MoodThreshold.Good or MoodThreshold.Dead + || _jetpack.IsUserFlying(uid)) + return; + + // This ridiculous math serves a purpose making high mood less impactful on movement speed than low mood + var modifier = + Math.Clamp( + (component.CurrentMoodLevel >= component.MoodThresholds[MoodThreshold.Neutral]) + ? _config.GetCVar(CCVars.MoodIncreasesSpeed) + ? MathF.Pow(1.003f, component.CurrentMoodLevel - component.MoodThresholds[MoodThreshold.Neutral]) + : 1 + : _config.GetCVar(CCVars.MoodDecreasesSpeed) + ? 2 - component.MoodThresholds[MoodThreshold.Neutral] / component.CurrentMoodLevel + : 1, + component.MinimumSpeedModifier, + component.MaximumSpeedModifier); + + args.ModifySpeed(1, modifier); + } + + private void OnMoodEffect(EntityUid uid, MoodComponent component, MoodEffectEvent args) + { + if (_debugMode + || !_config.GetCVar(CCVars.MoodEnabled) + || !_prototypeManager.TryIndex(args.EffectId, out var prototype) ) + return; + + var ev = new OnMoodEffect(uid, args.EffectId, args.EffectModifier, args.EffectOffset); + RaiseLocalEvent(uid, ref ev); + + ApplyEffect(uid, component, prototype, ev.EffectModifier, ev.EffectOffset); + } + + private void ApplyEffect(EntityUid uid, MoodComponent component, MoodEffectPrototype prototype, float eventModifier = 1, float eventOffset = 0) + { + // Apply categorised effect + if (prototype.Category != null) + { + if (component.CategorisedEffects.TryGetValue(prototype.Category, out var oldPrototypeId)) + { + if (!_prototypeManager.TryIndex(oldPrototypeId, out var oldPrototype)) + return; + + // Don't send the moodlet popup if we already have the moodlet. + if (!component.CategorisedEffects.ContainsValue(prototype.ID)) + SendEffectText(uid, prototype); + + if (prototype.ID != oldPrototype.ID) + component.CategorisedEffects[prototype.Category] = prototype.ID; + } + else + component.CategorisedEffects.Add(prototype.Category, prototype.ID); + + if (prototype.Timeout != 0) + Timer.Spawn(TimeSpan.FromSeconds(prototype.Timeout), () => RemoveTimedOutEffect(uid, prototype.ID, prototype.Category)); + } + // Apply uncategorised effect + else + { + if (component.UncategorisedEffects.TryGetValue(prototype.ID, out _)) + return; + + var moodChange = prototype.MoodChange * eventModifier + eventOffset; + if (moodChange == 0) + return; + + // Don't send the moodlet popup if we already have the moodlet. + if (!component.UncategorisedEffects.ContainsKey(prototype.ID)) + SendEffectText(uid, prototype); + + component.UncategorisedEffects.Add(prototype.ID, moodChange); + + if (prototype.Timeout != 0) + Timer.Spawn(TimeSpan.FromSeconds(prototype.Timeout), () => RemoveTimedOutEffect(uid, prototype.ID)); + } + + RefreshMood(uid, component); + } + + private void SendEffectText(EntityUid uid, MoodEffectPrototype prototype) + { + if (prototype.Hidden) + return; + + _popup.PopupEntity(prototype.Description, uid, uid, (prototype.MoodChange > 0) ? PopupType.Medium : PopupType.MediumCaution); + } + + private void RemoveTimedOutEffect(EntityUid uid, string prototypeId, string? category = null) + { + if (!TryComp(uid, out var comp)) + return; + + if (category == null) + { + if (!comp.UncategorisedEffects.ContainsKey(prototypeId)) + return; + comp.UncategorisedEffects.Remove(prototypeId); + } + else + { + if (!comp.CategorisedEffects.TryGetValue(category, out var currentProtoId) + || currentProtoId != prototypeId + || !_prototypeManager.HasIndex(currentProtoId)) + return; + comp.CategorisedEffects.Remove(category); + } + + ReplaceMood(uid, prototypeId); + RefreshMood(uid, comp); + } + + /// + /// Some moods specifically create a moodlet upon expiration. This is normally used for "Addiction" type moodlets, + /// such as a positive moodlet from an addictive substance that becomes a negative moodlet when a timer ends. + /// + /// + /// Moodlets that use this should probably also share a category with each other, but this isn't necessarily required. + /// Only if you intend that "Re-using the drug" should also remove the negative moodlet. + /// + private void ReplaceMood(EntityUid uid, string prototypeId) + { + if (!_prototypeManager.TryIndex(prototypeId, out var proto) + || proto.MoodletOnEnd is null) + return; + + var ev = new MoodEffectEvent(proto.MoodletOnEnd); + EntityManager.EventBus.RaiseLocalEvent(uid, ev); + } + + private void OnMobStateChanged(EntityUid uid, MoodComponent component, MobStateChangedEvent args) + { + if (_debugMode) + return; + + if (args.NewMobState == MobState.Dead && args.OldMobState != MobState.Dead) + { + var ev = new MoodEffectEvent("Dead"); + RaiseLocalEvent(uid, ev); + } + else if (args.OldMobState == MobState.Dead && args.NewMobState != MobState.Dead) + { + var ev = new MoodRemoveEffectEvent("Dead"); + RaiseLocalEvent(uid, ev); + } + RefreshMood(uid, component); + } + + // + // Recalculate the mood level of an entity by summing up all moodlets. + // + private void RefreshMood(EntityUid uid, MoodComponent component) + { + var amount = 0f; + + foreach (var (_, protoId) in component.CategorisedEffects) + { + if (!_prototypeManager.TryIndex(protoId, out var prototype)) + continue; + + amount += prototype.MoodChange; + } + + foreach (var (_, value) in component.UncategorisedEffects) + amount += value; + + SetMood(uid, amount, component, refresh: true); + } + + private void OnInit(EntityUid uid, MoodComponent component, ComponentStartup args) + { + if (_debugMode) + return; + + if (_config.GetCVar(CCVars.MoodModifiesThresholds) + && TryComp(uid, out var mobThresholdsComponent) + && _mobThreshold.TryGetThresholdForState(uid, MobState.Critical, out var critThreshold, mobThresholdsComponent)) + component.CritThresholdBeforeModify = critThreshold.Value; + + EnsureComp(uid); + RefreshMood(uid, component); + } + + private void SetMood(EntityUid uid, float amount, MoodComponent? component = null, bool force = false, bool refresh = false) + { + if (!_config.GetCVar(CCVars.MoodEnabled) + || !Resolve(uid, ref component) + || component.CurrentMoodThreshold == MoodThreshold.Dead && !refresh) + return; + + var neutral = component.MoodThresholds[MoodThreshold.Neutral]; + var ev = new OnSetMoodEvent(uid, amount, false); + RaiseLocalEvent(uid, ref ev); + + if (ev.Cancelled) + return; + else + { + uid = ev.Receiver; + amount = ev.MoodChangedAmount; + } + + var newMoodLevel = amount + neutral; + if (!force) + newMoodLevel = Math.Clamp(amount + neutral, + component.MoodThresholds[MoodThreshold.Dead], + component.MoodThresholds[MoodThreshold.Perfect]); + + component.CurrentMoodLevel = newMoodLevel; + + if (TryComp(uid, out var mood)) + { + mood.CurrentMoodLevel = component.CurrentMoodLevel; + mood.NeutralMoodThreshold = component.MoodThresholds.GetValueOrDefault(MoodThreshold.Neutral); + } + + UpdateCurrentThreshold(uid, component); + } + + private void UpdateCurrentThreshold(EntityUid uid, MoodComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + + var calculatedThreshold = GetMoodThreshold(component); + if (calculatedThreshold == component.CurrentMoodThreshold) + return; + + component.CurrentMoodThreshold = calculatedThreshold; + + DoMoodThresholdsEffects(uid, component); + } + + private void DoMoodThresholdsEffects(EntityUid uid, MoodComponent? component = null, bool force = false) + { + if (!Resolve(uid, ref component) + || component.CurrentMoodThreshold == component.LastThreshold && !force) + return; + + var modifier = GetMovementThreshold(component.CurrentMoodThreshold); + + // Modify mob stats + if (modifier != GetMovementThreshold(component.LastThreshold)) + { + _movementSpeedModifier.RefreshMovementSpeedModifiers(uid); + SetCritThreshold(uid, component, modifier); + RefreshShaders(uid, modifier); + } + + // Modify interface + if (component.MoodThresholdsAlerts.TryGetValue(component.CurrentMoodThreshold, out var alertId)) + _alerts.ShowAlert(uid, alertId); + else + _alerts.ClearAlertCategory(uid, AlertCategory.Mood); + + component.LastThreshold = component.CurrentMoodThreshold; + } + + private void RefreshShaders(EntityUid uid, int modifier) + { + if (modifier == -1) + EnsureComp(uid); + else + RemComp(uid); + } + + private void SetCritThreshold(EntityUid uid, MoodComponent component, int modifier) + { + if (!_config.GetCVar(CCVars.MoodModifiesThresholds) + || !TryComp(uid, out var mobThresholds) + || !_mobThreshold.TryGetThresholdForState(uid, MobState.Critical, out var key)) + return; + + var newKey = modifier switch + { + 1 => FixedPoint2.New(key.Value.Float() * component.IncreaseCritThreshold), + -1 => FixedPoint2.New(key.Value.Float() * component.DecreaseCritThreshold), + _ => component.CritThresholdBeforeModify + }; + + component.CritThresholdBeforeModify = key.Value; + _mobThreshold.SetMobStateThreshold(uid, newKey, MobState.Critical, mobThresholds); + } + + private MoodThreshold GetMoodThreshold(MoodComponent component, float? moodLevel = null) + { + moodLevel ??= component.CurrentMoodLevel; + var result = MoodThreshold.Dead; + var value = component.MoodThresholds[MoodThreshold.Perfect]; + + foreach (var threshold in component.MoodThresholds) + if (threshold.Value <= value && threshold.Value >= moodLevel) + { + result = threshold.Key; + value = threshold.Value; + } + + return result; + } + + private int GetMovementThreshold(MoodThreshold threshold) + { + return threshold switch + { + >= MoodThreshold.Good => 1, + <= MoodThreshold.Meh => -1, + _ => 0 + }; + } + + private void OnDamageChange(EntityUid uid, MoodComponent component, DamageChangedEvent args) + { + if (!_mobThreshold.TryGetPercentageForState(uid, MobState.Critical, args.Damageable.TotalDamage, out var damage)) + return; + + var protoId = "HealthNoDamage"; + var value = component.HealthMoodEffectsThresholds["HealthNoDamage"]; + + foreach (var threshold in component.HealthMoodEffectsThresholds) + if (threshold.Value <= damage && threshold.Value >= value) + { + protoId = threshold.Key; + value = threshold.Value; + } + + var ev = new MoodEffectEvent(protoId); + RaiseLocalEvent(uid, ev); + } +} + +[UsedImplicitly] +[DataDefinition] +public sealed partial class ShowMoodEffects : IAlertClick +{ + public void AlertClicked(EntityUid uid) + { + var entityManager = IoCManager.Resolve(); + var prototypeManager = IoCManager.Resolve(); + var chatManager = IoCManager.Resolve(); + var playerManager = IoCManager.Resolve(); + + if (!entityManager.TryGetComponent(uid, out var comp) + || comp.CurrentMoodThreshold == MoodThreshold.Dead + || !playerManager.TryGetSessionByEntity(uid, out var session) + || session == null) + return; + + var msgStart = Loc.GetString("mood-show-effects-start"); + chatManager.ChatMessageToOne(ChatChannel.Emotes, msgStart, msgStart, EntityUid.Invalid, false, + session.Channel); + + foreach (var (_, protoId) in comp.CategorisedEffects) + { + if (!prototypeManager.TryIndex(protoId, out var proto) + || proto.Hidden) + continue; + + SendDescToChat(proto, session); + } + + foreach (var (protoId, _) in comp.UncategorisedEffects) + { + if (!prototypeManager.TryIndex(protoId, out var proto) + || proto.Hidden) + continue; + + SendDescToChat(proto, session); + } + } + + private void SendDescToChat(MoodEffectPrototype proto, ICommonSession session) + { + if (session == null) + return; + + var chatManager = IoCManager.Resolve(); + + var color = (proto.MoodChange > 0) ? "#008000" : "#BA0000"; + var msg = $"[font size=10][color={color}]{proto.Description}[/color][/font]"; + + chatManager.ChatMessageToOne(ChatChannel.Emotes, msg, msg, EntityUid.Invalid, false, + session.Channel); + } +} diff --git a/Content.Server/Mousetrap/MousetrapSystem.cs b/Content.Server/Mousetrap/MousetrapSystem.cs index e3aaab364d..2d4fed2799 100644 --- a/Content.Server/Mousetrap/MousetrapSystem.cs +++ b/Content.Server/Mousetrap/MousetrapSystem.cs @@ -2,13 +2,9 @@ using Content.Server.Explosion.EntitySystems; using Content.Server.Popups; using Content.Shared.Interaction.Events; -using Content.Shared.Inventory; using Content.Shared.Mousetrap; -using Content.Shared.StepTrigger; using Content.Shared.StepTrigger.Systems; -using Robust.Server.GameObjects; using Robust.Shared.Physics.Components; -using Robust.Shared.Player; namespace Content.Server.Mousetrap; @@ -44,15 +40,16 @@ private void OnStepTriggerAttempt(EntityUid uid, MousetrapComponent component, r private void BeforeDamageOnTrigger(EntityUid uid, MousetrapComponent component, BeforeDamageUserOnTriggerEvent args) { - if (TryComp(args.Tripper, out PhysicsComponent? physics) && physics.Mass != 0) - { - // The idea here is inverse, - // Small - big damage, - // Large - small damage - // yes i punched numbers into a calculator until the graph looked right - var scaledDamage = -50 * Math.Atan(physics.Mass - component.MassBalance) + (25 * Math.PI); - args.Damage *= scaledDamage; - } + if (!TryComp(args.Tripper, out var physics) + || physics.Mass is 0) + return; + + // The idea here is inverse, + // Small - big damage, + // Large - small damage + // Yes, I punched numbers into a calculator until the graph looked right + var scaledDamage = -50 * MathF.Atan(physics.Mass - component.MassBalance) + 25 * MathF.PI; + args.Damage *= scaledDamage; } private void OnTrigger(EntityUid uid, MousetrapComponent component, TriggerEvent args) @@ -64,9 +61,7 @@ private void OnTrigger(EntityUid uid, MousetrapComponent component, TriggerEvent private void UpdateVisuals(EntityUid uid, MousetrapComponent? mousetrap = null, AppearanceComponent? appearance = null) { if (!Resolve(uid, ref mousetrap, ref appearance, false)) - { return; - } _appearance.SetData(uid, MousetrapVisuals.Visual, mousetrap.IsActive ? MousetrapVisuals.Armed : MousetrapVisuals.Unarmed, appearance); diff --git a/Content.Server/NPC/Components/NPCJukeComponent.cs b/Content.Server/NPC/Components/NPCJukeComponent.cs index 2c4136c24b..768feeca6f 100644 --- a/Content.Server/NPC/Components/NPCJukeComponent.cs +++ b/Content.Server/NPC/Components/NPCJukeComponent.cs @@ -1,4 +1,3 @@ -using Content.Server.NPC.HTN.PrimitiveTasks.Operators.Combat; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; namespace Content.Server.NPC.Components; @@ -6,17 +5,20 @@ namespace Content.Server.NPC.Components; [RegisterComponent, AutoGenerateComponentPause] public sealed partial class NPCJukeComponent : Component { - [DataField("jukeType")] + [DataField] public JukeType JukeType = JukeType.Away; - [DataField("jukeDuration")] + [DataField] public float JukeDuration = 0.5f; - [DataField("nextJuke", customTypeSerializer:typeof(TimeOffsetSerializer))] + [DataField] + public float JukeCooldown = 3f; + + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] [AutoPausedField] public TimeSpan NextJuke; - [DataField("targetTile")] + [DataField] public Vector2i? TargetTile; } diff --git a/Content.Server/NPC/Components/NPCRetaliationComponent.cs b/Content.Server/NPC/Components/NPCRetaliationComponent.cs index c0bf54d76e..21b806c448 100644 --- a/Content.Server/NPC/Components/NPCRetaliationComponent.cs +++ b/Content.Server/NPC/Components/NPCRetaliationComponent.cs @@ -21,4 +21,10 @@ public sealed partial class NPCRetaliationComponent : Component /// todo: this needs to support timeoffsetserializer at some point [DataField("attackMemories")] public Dictionary AttackMemories = new(); + + /// + /// Whether this NPC will retaliate against a "Friendly" NPC. + /// + [DataField] + public bool RetaliateFriendlies; } diff --git a/Content.Server/NPC/Components/NpcFactionMemberComponent.cs b/Content.Server/NPC/Components/NpcFactionMemberComponent.cs index ce7e59ea2c..f38d8cca0f 100644 --- a/Content.Server/NPC/Components/NpcFactionMemberComponent.cs +++ b/Content.Server/NPC/Components/NpcFactionMemberComponent.cs @@ -4,7 +4,6 @@ namespace Content.Server.NPC.Components { [RegisterComponent] - [Access(typeof(NpcFactionSystem))] public sealed partial class NpcFactionMemberComponent : Component { /// diff --git a/Content.Server/NPC/HTN/Preconditions/GunAmmoPrecondition.cs b/Content.Server/NPC/HTN/Preconditions/GunAmmoPrecondition.cs index fe3b844ae3..58647d8874 100644 --- a/Content.Server/NPC/HTN/Preconditions/GunAmmoPrecondition.cs +++ b/Content.Server/NPC/HTN/Preconditions/GunAmmoPrecondition.cs @@ -35,7 +35,7 @@ public override bool IsMet(NPCBlackboard blackboard) else percent = ammoEv.Count / (float) ammoEv.Capacity; - percent = Math.Clamp(percent, 0f, 1f); + percent = System.Math.Clamp(percent, 0f, 1f); if (MaxPercent < percent) return false; diff --git a/Content.Server/NPC/HTN/Preconditions/InContainerPrecondition.cs b/Content.Server/NPC/HTN/Preconditions/InContainerPrecondition.cs new file mode 100644 index 0000000000..aa0ad98ede --- /dev/null +++ b/Content.Server/NPC/HTN/Preconditions/InContainerPrecondition.cs @@ -0,0 +1,27 @@ +using Robust.Server.Containers; + +namespace Content.Server.NPC.HTN.Preconditions; + +/// +/// Checks if the owner in container or not +/// +public sealed partial class InContainerPrecondition : HTNPrecondition +{ + private ContainerSystem _container = default!; + + [ViewVariables(VVAccess.ReadWrite)] [DataField("isInContainer")] public bool IsInContainer = true; + + public override void Initialize(IEntitySystemManager sysManager) + { + base.Initialize(sysManager); + _container = sysManager.GetEntitySystem(); + } + + public override bool IsMet(NPCBlackboard blackboard) + { + var owner = blackboard.GetValue(NPCBlackboard.Owner); + + return IsInContainer && _container.IsEntityInContainer(owner) || + !IsInContainer && !_container.IsEntityInContainer(owner); + } +} diff --git a/Content.Server/NPC/HTN/Preconditions/KeyNotExistsPrecondition.cs b/Content.Server/NPC/HTN/Preconditions/KeyNotExistsPrecondition.cs new file mode 100644 index 0000000000..c12663901c --- /dev/null +++ b/Content.Server/NPC/HTN/Preconditions/KeyNotExistsPrecondition.cs @@ -0,0 +1,12 @@ +namespace Content.Server.NPC.HTN.Preconditions; + +public sealed partial class KeyNotExistsPrecondition : HTNPrecondition +{ + [DataField(required: true)] + public string Key = string.Empty; + + public override bool IsMet(NPCBlackboard blackboard) + { + return !blackboard.ContainsKey(Key); + } +} diff --git a/Content.Server/NPC/HTN/Preconditions/Math/KeyBoolEqualsPrecondition.cs b/Content.Server/NPC/HTN/Preconditions/Math/KeyBoolEqualsPrecondition.cs new file mode 100644 index 0000000000..8c7920e8be --- /dev/null +++ b/Content.Server/NPC/HTN/Preconditions/Math/KeyBoolEqualsPrecondition.cs @@ -0,0 +1,23 @@ +namespace Content.Server.NPC.HTN.Preconditions.Math; + +/// +/// Checks for the presence of data in the blackboard and makes a comparison with the specified boolean +/// +public sealed partial class KeyBoolEqualsPrecondition : HTNPrecondition +{ + [Dependency] private readonly IEntityManager _entManager = default!; + + [DataField(required: true)] + public string Key = string.Empty; + + [DataField(required: true)] + public bool Value; + + public override bool IsMet(NPCBlackboard blackboard) + { + if (!blackboard.TryGetValue(Key, out var value, _entManager)) + return false; + + return Value == value; + } +} diff --git a/Content.Server/NPC/HTN/Preconditions/Math/KeyFloatEqualsPrecondition.cs b/Content.Server/NPC/HTN/Preconditions/Math/KeyFloatEqualsPrecondition.cs new file mode 100644 index 0000000000..802fdaf2b9 --- /dev/null +++ b/Content.Server/NPC/HTN/Preconditions/Math/KeyFloatEqualsPrecondition.cs @@ -0,0 +1,18 @@ +namespace Content.Server.NPC.HTN.Preconditions.Math; + +public sealed partial class KeyFloatEqualsPrecondition : HTNPrecondition +{ + [Dependency] private readonly IEntityManager _entManager = default!; + + [DataField(required: true)] + public string Key = string.Empty; + + [DataField(required: true)] + public float Value; + + public override bool IsMet(NPCBlackboard blackboard) + { + return blackboard.TryGetValue(Key, out var value, _entManager) && + MathHelper.CloseTo(value, value); + } +} diff --git a/Content.Server/NPC/HTN/Preconditions/Math/KeyFloatGreaterPrecondition.cs b/Content.Server/NPC/HTN/Preconditions/Math/KeyFloatGreaterPrecondition.cs new file mode 100644 index 0000000000..3a9ac36698 --- /dev/null +++ b/Content.Server/NPC/HTN/Preconditions/Math/KeyFloatGreaterPrecondition.cs @@ -0,0 +1,17 @@ +namespace Content.Server.NPC.HTN.Preconditions.Math; + +public sealed partial class KeyFloatGreaterPrecondition : HTNPrecondition +{ + [Dependency] private readonly IEntityManager _entManager = default!; + + [DataField(required: true)] + public string Key = string.Empty; + + [DataField(required: true)] + public float Value; + + public override bool IsMet(NPCBlackboard blackboard) + { + return blackboard.TryGetValue(Key, out var value, _entManager) && value > Value; + } +} diff --git a/Content.Server/NPC/HTN/Preconditions/Math/KeyFloatLessPrecondition.cs b/Content.Server/NPC/HTN/Preconditions/Math/KeyFloatLessPrecondition.cs new file mode 100644 index 0000000000..5cd51d7a7c --- /dev/null +++ b/Content.Server/NPC/HTN/Preconditions/Math/KeyFloatLessPrecondition.cs @@ -0,0 +1,17 @@ +namespace Content.Server.NPC.HTN.Preconditions.Math; + +public sealed partial class KeyFloatLessPrecondition : HTNPrecondition +{ + [Dependency] private readonly IEntityManager _entManager = default!; + + [DataField(required: true)] + public string Key = string.Empty; + + [DataField(required: true)] + public float Value; + + public override bool IsMet(NPCBlackboard blackboard) + { + return blackboard.TryGetValue(Key, out var value, _entManager) && value < Value; + } +} diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Combat/ContainerOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Combat/ContainerOperator.cs new file mode 100644 index 0000000000..667d0b8ec4 --- /dev/null +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Combat/ContainerOperator.cs @@ -0,0 +1,40 @@ +using Robust.Server.Containers; + +namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Combat; + +public sealed partial class ContainerOperator : HTNOperator +{ + [Dependency] private readonly IEntityManager _entManager = default!; + private ContainerSystem _container = default!; + private EntityQuery _transformQuery; + + [DataField("shutdownState")] + public HTNPlanState ShutdownState { get; private set; } = HTNPlanState.TaskFinished; + + [DataField("targetKey", required: true)] + public string TargetKey = default!; + + public override void Initialize(IEntitySystemManager sysManager) + { + base.Initialize(sysManager); + _container = sysManager.GetEntitySystem(); + _transformQuery = _entManager.GetEntityQuery(); + } + + public override void Startup(NPCBlackboard blackboard) + { + base.Startup(blackboard); + var owner = blackboard.GetValue(NPCBlackboard.Owner); + + if (!_container.TryGetOuterContainer(owner, _transformQuery.GetComponent(owner), out var outerContainer) && outerContainer == null) + return; + + var target = outerContainer.Owner; + blackboard.SetValue(TargetKey, target); + } + + public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime) + { + return HTNOperatorStatus.Finished; + } +} diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Combat/EscapeOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Combat/EscapeOperator.cs new file mode 100644 index 0000000000..a794e1e314 --- /dev/null +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Combat/EscapeOperator.cs @@ -0,0 +1,140 @@ +using System.Threading; +using System.Threading.Tasks; +using Content.Server.NPC.Components; +using Content.Server.Storage.EntitySystems; +using Content.Shared.CombatMode; +using Robust.Server.Containers; + +namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Combat.Melee; + +public sealed partial class EscapeOperator : HTNOperator, IHtnConditionalShutdown +{ + [Dependency] private readonly IEntityManager _entManager = default!; + private ContainerSystem _container = default!; + private EntityStorageSystem _entityStorage = default!; + + [DataField("shutdownState")] + public HTNPlanState ShutdownState { get; private set; } = HTNPlanState.TaskFinished; + + [DataField("targetKey", required: true)] + public string TargetKey = default!; + + public override void Initialize(IEntitySystemManager sysManager) + { + base.Initialize(sysManager); + _container = sysManager.GetEntitySystem(); + _entityStorage = sysManager.GetEntitySystem(); + } + + public override void Startup(NPCBlackboard blackboard) + { + base.Startup(blackboard); + var owner = blackboard.GetValue(NPCBlackboard.Owner); + var target = blackboard.GetValue(TargetKey); + + if (_entityStorage.TryOpenStorage(owner, target)) + { + TaskShutdown(blackboard, HTNOperatorStatus.Finished); + return; + } + + var melee = _entManager.EnsureComponent(owner); + melee.MissChance = blackboard.GetValueOrDefault(NPCBlackboard.MeleeMissChance, _entManager); + melee.Target = target; + } + + public override async Task<(bool Valid, Dictionary? Effects)> Plan(NPCBlackboard blackboard, + CancellationToken cancelToken) + { + var owner = blackboard.GetValue(NPCBlackboard.Owner); + if (!blackboard.TryGetValue(TargetKey, out var target, _entManager)) + { + return (false, null); + } + + if (!_container.IsEntityInContainer(owner)) + { + return (false, null); + } + + if (_entityStorage.TryOpenStorage(owner, target)) + { + return (false, null); + } + + return (true, null); + } + + public void ConditionalShutdown(NPCBlackboard blackboard) + { + var owner = blackboard.GetValue(NPCBlackboard.Owner); + _entManager.System().SetInCombatMode(owner, false); + _entManager.RemoveComponent(owner); + blackboard.Remove(TargetKey); + } + + public override void TaskShutdown(NPCBlackboard blackboard, HTNOperatorStatus status) + { + base.TaskShutdown(blackboard, status); + + ConditionalShutdown(blackboard); + } + + public override void PlanShutdown(NPCBlackboard blackboard) + { + base.PlanShutdown(blackboard); + + ConditionalShutdown(blackboard); + } + + public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime) + { + base.Update(blackboard, frameTime); + var owner = blackboard.GetValue(NPCBlackboard.Owner); + HTNOperatorStatus status; + + if (_entManager.TryGetComponent(owner, out var combat) && + blackboard.TryGetValue(TargetKey, out var target, _entManager)) + { + combat.Target = target; + + // Success + if (!_container.IsEntityInContainer(owner)) + { + status = HTNOperatorStatus.Finished; + } + else + { + if (_entityStorage.TryOpenStorage(owner, target)) + { + status = HTNOperatorStatus.Finished; + } + else + { + switch (combat.Status) + { + case CombatStatus.TargetOutOfRange: + case CombatStatus.Normal: + status = HTNOperatorStatus.Continuing; + break; + default: + status = HTNOperatorStatus.Failed; + break; + } + } + } + } + else + { + status = HTNOperatorStatus.Failed; + } + + // Mark it as finished to continue the plan. + if (status == HTNOperatorStatus.Continuing && ShutdownState == HTNPlanState.PlanFinished) + { + status = HTNOperatorStatus.Finished; + } + + return status; + } +} diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Combat/JukeOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Combat/JukeOperator.cs index 02a3b08510..68029f5a4c 100644 --- a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Combat/JukeOperator.cs +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Combat/JukeOperator.cs @@ -6,17 +6,31 @@ public sealed partial class JukeOperator : HTNOperator, IHtnConditionalShutdown { [Dependency] private readonly IEntityManager _entManager = default!; - [DataField("jukeType")] + [DataField] public JukeType JukeType = JukeType.AdjacentTile; - [DataField("shutdownState")] + [DataField] public HTNPlanState ShutdownState { get; private set; } = HTNPlanState.PlanFinished; + /// + /// Controls how long(in seconds) the NPC will move while juking. + /// + [DataField] + public float JukeDuration = 0.5f; + + /// + /// Controls how often (in seconds) an NPC will try to juke. + /// + [DataField] + public float JukeCooldown = 3f; + public override void Startup(NPCBlackboard blackboard) { base.Startup(blackboard); var juke = _entManager.EnsureComponent(blackboard.GetValue(NPCBlackboard.Owner)); juke.JukeType = JukeType; + juke.JukeDuration = JukeDuration; + juke.JukeCooldown = JukeCooldown; } public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime) diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Combat/UnPullOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Combat/UnPullOperator.cs new file mode 100644 index 0000000000..54f422fe67 --- /dev/null +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Combat/UnPullOperator.cs @@ -0,0 +1,35 @@ +using Content.Shared.Movement.Pulling.Components; +using Content.Shared.Movement.Pulling.Systems; + +namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Combat; + +public sealed partial class UnPullOperator : HTNOperator +{ + [Dependency] private readonly IEntityManager _entManager = default!; + private PullingSystem _pulling = default!; + + private EntityQuery _pullableQuery; + + [DataField("shutdownState")] + public HTNPlanState ShutdownState { get; private set; } = HTNPlanState.TaskFinished; + + public override void Initialize(IEntitySystemManager sysManager) + { + base.Initialize(sysManager); + _pulling = sysManager.GetEntitySystem(); + _pullableQuery = _entManager.GetEntityQuery(); + } + + public override void Startup(NPCBlackboard blackboard) + { + base.Startup(blackboard); + var owner = blackboard.GetValue(NPCBlackboard.Owner); + + _pulling.TryStopPull(owner, _pullableQuery.GetComponent(owner), owner); + } + + public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime) + { + return HTNOperatorStatus.Finished; + } +} diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Combat/UnbuckleOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Combat/UnbuckleOperator.cs new file mode 100644 index 0000000000..207665d786 --- /dev/null +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Combat/UnbuckleOperator.cs @@ -0,0 +1,34 @@ +using Content.Server.Buckle.Systems; +using Content.Shared.Buckle.Components; + +namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Combat; + +public sealed partial class UnbuckleOperator : HTNOperator +{ + [Dependency] private readonly IEntityManager _entManager = default!; + private BuckleSystem _buckle = default!; + + [DataField("shutdownState")] + public HTNPlanState ShutdownState { get; private set; } = HTNPlanState.TaskFinished; + + public override void Initialize(IEntitySystemManager sysManager) + { + base.Initialize(sysManager); + _buckle = sysManager.GetEntitySystem(); + } + + public override void Startup(NPCBlackboard blackboard) + { + base.Startup(blackboard); + var owner = blackboard.GetValue(NPCBlackboard.Owner); + if (!_entManager.TryGetComponent(owner, out var buckle) || !buckle.Buckled) + return; + + _buckle.TryUnbuckle(owner, owner, true, buckle); + } + + public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime) + { + return HTNOperatorStatus.Finished; + } +} diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Interactions/DrainOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Interactions/DrainOperator.cs new file mode 100644 index 0000000000..ecb6e16c9d --- /dev/null +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Interactions/DrainOperator.cs @@ -0,0 +1,50 @@ +using Content.Server.LifeDrainer; + +namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Interactions; + +public sealed partial class DrainOperator : HTNOperator +{ + [Dependency] private readonly IEntityManager _entMan = default!; + + private LifeDrainerSystem _drainer = default!; + private EntityQuery _drainerQuery; + + [DataField(required: true)] + public string DrainKey = string.Empty; + + public override void Initialize(IEntitySystemManager sysManager) + { + base.Initialize(sysManager); + + _drainer = sysManager.GetEntitySystem(); + _drainerQuery = _entMan.GetEntityQuery(); + } + + public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime) + { + var owner = blackboard.GetValue(NPCBlackboard.Owner); + var target = blackboard.GetValue(DrainKey); + + if (_entMan.Deleted(target)) + return HTNOperatorStatus.Failed; + + if (!_drainerQuery.TryComp(owner, out var wisp)) + return HTNOperatorStatus.Failed; + + // still draining hold your horses + if (_drainer.IsDraining(wisp)) + return HTNOperatorStatus.Continuing; + + // not draining and no target set, start to drain + if (wisp.Target == null) + { + return _drainer.TryDrain((owner, wisp), target) + ? HTNOperatorStatus.Continuing + : HTNOperatorStatus.Failed; + } + + // stopped draining, clean up and find another one after + _drainer.CancelDrain(wisp); + return HTNOperatorStatus.Finished; + } +} diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/AddFloatOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/AddFloatOperator.cs new file mode 100644 index 0000000000..00404517c9 --- /dev/null +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/AddFloatOperator.cs @@ -0,0 +1,33 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Math; + +/// +/// Gets the key, and adds the value to that float +/// +public sealed partial class AddFloatOperator : HTNOperator +{ + [Dependency] private readonly IEntityManager _entManager = default!; + + [DataField(required: true)] + public string TargetKey = string.Empty; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float Amount; + + public override async Task<(bool Valid, Dictionary? Effects)> Plan(NPCBlackboard blackboard, + CancellationToken cancelToken) + { + if (!blackboard.TryGetValue(TargetKey, out var value, _entManager)) + return (false, null); + + return ( + true, + new Dictionary + { + { TargetKey, value + Amount } + } + ); + } +} diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/SetBoolOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/SetBoolOperator.cs new file mode 100644 index 0000000000..a40b96798d --- /dev/null +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/SetBoolOperator.cs @@ -0,0 +1,28 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Math; + +/// +/// Just sets a blackboard key to a bool +/// +public sealed partial class SetBoolOperator : HTNOperator +{ + [DataField(required: true)] + public string TargetKey = string.Empty; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public bool Value; + + public override async Task<(bool Valid, Dictionary? Effects)> Plan(NPCBlackboard blackboard, + CancellationToken cancelToken) + { + return ( + true, + new Dictionary + { + { TargetKey, Value } + } + ); + } +} diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/SetFloatOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/SetFloatOperator.cs new file mode 100644 index 0000000000..76842b431f --- /dev/null +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/SetFloatOperator.cs @@ -0,0 +1,28 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Math; + +/// +/// Just sets a blackboard key to a float +/// +public sealed partial class SetFloatOperator : HTNOperator +{ + [DataField(required: true)] + public string TargetKey = string.Empty; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float Amount; + + public override async Task<(bool Valid, Dictionary? Effects)> Plan(NPCBlackboard blackboard, + CancellationToken cancelToken) + { + return ( + true, + new Dictionary + { + { TargetKey, Amount } + } + ); + } +} diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/SetRandomFloatOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/SetRandomFloatOperator.cs new file mode 100644 index 0000000000..999756f1f7 --- /dev/null +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Math/SetRandomFloatOperator.cs @@ -0,0 +1,34 @@ +using System.Threading; +using System.Threading.Tasks; +using Robust.Shared.Random; + +namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Math; + +/// +/// Sets a random float from MinAmount to MaxAmount to blackboard +/// +public sealed partial class SetRandomFloatOperator : HTNOperator +{ + [Dependency] private readonly IRobustRandom _random = default!; + + [DataField(required: true)] + public string TargetKey = string.Empty; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float MaxAmount = 1f; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float MinAmount = 0f; + + public override async Task<(bool Valid, Dictionary? Effects)> Plan(NPCBlackboard blackboard, + CancellationToken cancelToken) + { + return ( + true, + new Dictionary + { + { TargetKey, _random.NextFloat(MinAmount, MaxAmount) } + } + ); + } +} diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/PickDrainTargetOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/PickDrainTargetOperator.cs new file mode 100644 index 0000000000..52c12777aa --- /dev/null +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/PickDrainTargetOperator.cs @@ -0,0 +1,74 @@ +using System.Threading; +using System.Threading.Tasks; +using Content.Server.LifeDrainer; +using Content.Server.NPC.Pathfinding; +using Content.Server.NPC.Systems; + +namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators; + +public sealed partial class PickDrainTargetOperator : HTNOperator +{ + [Dependency] private readonly IEntityManager _entMan = default!; + + private LifeDrainerSystem _drainer = default!; + private NpcFactionSystem _faction = default!; + private PathfindingSystem _pathfinding = default!; + + private EntityQuery _drainerQuery; + private EntityQuery _xformQuery; + + [DataField(required: true)] public string + RangeKey = string.Empty, + TargetKey = string.Empty, + DrainKey = string.Empty; + + /// + /// Where the pathfinding result will be stored (if applicable). This gets removed after execution. + /// + [DataField] + public string PathfindKey = NPCBlackboard.PathfindKey; + + public override void Initialize(IEntitySystemManager sysMan) + { + base.Initialize(sysMan); + + _drainer = sysMan.GetEntitySystem(); + _faction = sysMan.GetEntitySystem(); + _pathfinding = sysMan.GetEntitySystem(); + + _drainerQuery = _entMan.GetEntityQuery(); + _xformQuery = _entMan.GetEntityQuery(); + } + + public override async Task<(bool Valid, Dictionary? Effects)> Plan(NPCBlackboard blackboard, CancellationToken cancelToken) { + var owner = blackboard.GetValue(NPCBlackboard.Owner); + if (!_drainerQuery.TryComp(owner, out var drainer)) + return (false, null); + + var ent = (owner, drainer); + if (!blackboard.TryGetValue(RangeKey, out var range, _entMan)) + return (false, null); + + // find crit psionics nearby + foreach (var target in _faction.GetNearbyHostiles(owner, range)) + { + if (!_drainer.CanDrain(ent, target) || !_xformQuery.TryComp(target, out var xform)) + continue; + + // pathfind to the first crit psionic in range to start draining + var targetCoords = xform.Coordinates; + var path = await _pathfinding.GetPath(owner, target, range, cancelToken); + if (path.Result != PathResult.Path) + continue; + + return (true, new Dictionary() + { + { TargetKey, targetCoords }, + { DrainKey, target }, + { PathfindKey, path } + }); + } + + return (false, null); + } +} diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/PlaySoundOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/PlaySoundOperator.cs new file mode 100644 index 0000000000..57cc2e91e4 --- /dev/null +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/PlaySoundOperator.cs @@ -0,0 +1,28 @@ +using Robust.Server.Audio; +using Robust.Shared.Audio; + +namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators; + +public sealed partial class PlaySoundOperator : HTNOperator +{ + private AudioSystem _audio = default!; + + [DataField(required: true)] + public SoundSpecifier? Sound; + + public override void Initialize(IEntitySystemManager sysManager) + { + base.Initialize(sysManager); + + _audio = IoCManager.Resolve().GetEntitySystem(); + } + + public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime) + { + var uid = blackboard.GetValue(NPCBlackboard.Owner); + + _audio.PlayPvs(Sound, uid); + + return base.Update(blackboard, frameTime); + } +} diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/SayKeyOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/SayKeyOperator.cs new file mode 100644 index 0000000000..3422c77249 --- /dev/null +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/SayKeyOperator.cs @@ -0,0 +1,37 @@ +using Content.Server.Chat.Systems; +using Content.Shared.Chat; + +namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators; + +public sealed partial class SayKeyOperator : HTNOperator +{ + [Dependency] private readonly IEntityManager _entManager = default!; + + private ChatSystem _chat = default!; + + [DataField(required: true)] + public string Key = string.Empty; + + /// + /// Whether to hide message from chat window and logs. + /// + [DataField] + public bool Hidden; + + public override void Initialize(IEntitySystemManager sysManager) + { + base.Initialize(sysManager); + _chat = IoCManager.Resolve().GetEntitySystem(); + } + + public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime) + { + if (!blackboard.TryGetValue(Key, out var value, _entManager)) + return HTNOperatorStatus.Failed; + + var speaker = blackboard.GetValue(NPCBlackboard.Owner); + _chat.TrySendInGameICMessage(speaker, value.ToString() ?? "Oh no...", InGameICChatType.Speak, hideChat: Hidden, hideLog: Hidden); + + return base.Update(blackboard, frameTime); + } +} diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/SetFloatOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/SetFloatOperator.cs deleted file mode 100644 index 7a460592cb..0000000000 --- a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/SetFloatOperator.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators; - -/// -/// Just sets a blackboard key to a float -/// -public sealed partial class SetFloatOperator : HTNOperator -{ - [DataField("targetKey", required: true)] public string TargetKey = string.Empty; - - [ViewVariables(VVAccess.ReadWrite), DataField("amount")] - public float Amount; - - public override async Task<(bool Valid, Dictionary? Effects)> Plan(NPCBlackboard blackboard, - CancellationToken cancelToken) - { - return (true, new Dictionary() - { - {TargetKey, Amount}, - }); - } -} diff --git a/Content.Server/NPC/Pathfinding/PathfindingSystem.Distance.cs b/Content.Server/NPC/Pathfinding/PathfindingSystem.Distance.cs index 95d5c9c465..35deb6a0ca 100644 --- a/Content.Server/NPC/Pathfinding/PathfindingSystem.Distance.cs +++ b/Content.Server/NPC/Pathfinding/PathfindingSystem.Distance.cs @@ -36,7 +36,7 @@ private Vector2 GetDiff(PathPoly start, PathPoly end) return Vector2.Zero; } - endPos = startXform.InvWorldMatrix.Transform(endXform.WorldMatrix.Transform(endPos)); + endPos = Vector2.Transform(Vector2.Transform(endPos, endXform.WorldMatrix), startXform.InvWorldMatrix); } // TODO: Numerics when we changeover. diff --git a/Content.Server/NPC/Pathfinding/PathfindingSystem.Grid.cs b/Content.Server/NPC/Pathfinding/PathfindingSystem.Grid.cs index 6462c10fe5..35122e3e62 100644 --- a/Content.Server/NPC/Pathfinding/PathfindingSystem.Grid.cs +++ b/Content.Server/NPC/Pathfinding/PathfindingSystem.Grid.cs @@ -395,7 +395,7 @@ private Vector2i GetOrigin(Vector2 localPos) private Vector2i GetOrigin(EntityCoordinates coordinates, EntityUid gridUid) { - var localPos = _transform.GetInvWorldMatrix(gridUid).Transform(coordinates.ToMapPos(EntityManager, _transform)); + var localPos = Vector2.Transform(coordinates.ToMapPos(EntityManager, _transform), _transform.GetInvWorldMatrix(gridUid)); return new Vector2i((int) Math.Floor(localPos.X / ChunkSize), (int) Math.Floor(localPos.Y / ChunkSize)); } diff --git a/Content.Server/NPC/Pathfinding/PathfindingSystem.cs b/Content.Server/NPC/Pathfinding/PathfindingSystem.cs index a59af88ff5..af9c44a1ef 100644 --- a/Content.Server/NPC/Pathfinding/PathfindingSystem.cs +++ b/Content.Server/NPC/Pathfinding/PathfindingSystem.cs @@ -405,7 +405,7 @@ public async void GetPathEvent( return null; } - var localPos = xform.InvWorldMatrix.Transform(coordinates.ToMapPos(EntityManager, _transform)); + var localPos = Vector2.Transform(coordinates.ToMapPos(EntityManager, _transform), xform.InvWorldMatrix); var origin = GetOrigin(localPos); if (!TryGetChunk(origin, comp, out var chunk)) diff --git a/Content.Server/NPC/Systems/NPCJukeSystem.cs b/Content.Server/NPC/Systems/NPCJukeSystem.cs index da9fa1f761..5a724762ef 100644 --- a/Content.Server/NPC/Systems/NPCJukeSystem.cs +++ b/Content.Server/NPC/Systems/NPCJukeSystem.cs @@ -3,6 +3,7 @@ using Content.Server.NPC.Events; using Content.Server.NPC.HTN.PrimitiveTasks.Operators.Combat; using Content.Server.Weapons.Melee; +using Content.Shared.Coordinates.Helpers; using Content.Shared.NPC; using Content.Shared.Weapons.Melee; using Robust.Shared.Collections; @@ -38,22 +39,19 @@ public override void Initialize() private void OnJukeSteering(EntityUid uid, NPCJukeComponent component, ref NPCSteeringEvent args) { - if (component.JukeType == JukeType.AdjacentTile) + if (_timing.CurTime < component.NextJuke) { - if (_npcRangedQuery.TryGetComponent(uid, out var ranged) && - ranged.Status == CombatStatus.NotInSight) - { - component.TargetTile = null; - return; - } + component.TargetTile = null; + return; + } - if (_timing.CurTime < component.NextJuke) - { - component.TargetTile = null; - return; - } + component.NextJuke = _timing.CurTime + TimeSpan.FromSeconds(component.JukeCooldown); - if (!TryComp(args.Transform.GridUid, out var grid)) + if (component.JukeType == JukeType.AdjacentTile) + { + if (_npcRangedQuery.TryGetComponent(uid, out var ranged) + && ranged.Status is CombatStatus.NotInSight + || !TryComp(args.Transform.GridUid, out var grid)) { component.TargetTile = null; return; @@ -107,12 +105,11 @@ private void OnJukeSteering(EntityUid uid, NPCJukeComponent component, ref NPCSt var elapsed = _timing.CurTime - component.NextJuke; - // Finished juke, reset timer. - if (elapsed.TotalSeconds > component.JukeDuration || - currentTile == component.TargetTile) + // Finished juke. + if (elapsed.TotalSeconds > component.JukeDuration + || currentTile == component.TargetTile) { component.TargetTile = null; - component.NextJuke = _timing.CurTime + TimeSpan.FromSeconds(component.JukeDuration); return; } @@ -155,9 +152,7 @@ private void OnJukeSteering(EntityUid uid, NPCJukeComponent component, ref NPCSt var obstacleDirection = _transform.GetWorldPosition(melee.Target) - args.WorldPosition; if (obstacleDirection == Vector2.Zero) - { obstacleDirection = _random.NextVector2(); - } // If they're moving away then pursue anyway. // If just hit then always back up a bit. diff --git a/Content.Server/NPC/Systems/NPCRetaliationSystem.cs b/Content.Server/NPC/Systems/NPCRetaliationSystem.cs index cde8decefc..a855c9915a 100644 --- a/Content.Server/NPC/Systems/NPCRetaliationSystem.cs +++ b/Content.Server/NPC/Systems/NPCRetaliationSystem.cs @@ -47,7 +47,8 @@ public bool TryRetaliate(EntityUid uid, EntityUid target, NPCRetaliationComponen if (!HasComp(target)) return false; - if (_npcFaction.IsEntityFriendly(uid, target)) + if (!component.RetaliateFriendlies + && _npcFaction.IsEntityFriendly(uid, target)) return false; _npcFaction.AggroEntity(uid, target); diff --git a/Content.Server/NPC/Systems/NPCSystem.cs b/Content.Server/NPC/Systems/NPCSystem.cs index 8abe0f7f54..24c038a3e7 100644 --- a/Content.Server/NPC/Systems/NPCSystem.cs +++ b/Content.Server/NPC/Systems/NPCSystem.cs @@ -2,6 +2,8 @@ using Content.Server.NPC.Components; using Content.Server.NPC.HTN; using Content.Shared.CCVar; +using Content.Shared.Mind; +using Content.Shared.Mind.Components; using Content.Shared.Mobs; using Content.Shared.Mobs.Systems; using Content.Shared.NPC; @@ -48,6 +50,10 @@ public void OnPlayerNPCDetach(EntityUid uid, HTNComponent component, PlayerDetac if (_mobState.IsIncapacitated(uid) || TerminatingOrDeleted(uid)) return; + // This NPC has an attached mind, so it should not wake up. + if (TryComp(uid, out var mindContainer) && mindContainer.HasMind) + return; + WakeNPC(uid, component); } diff --git a/Content.Server/Ninja/Systems/NinjaGlovesSystem.cs b/Content.Server/Ninja/Systems/NinjaGlovesSystem.cs index d84a728775..ac76ae6b77 100644 --- a/Content.Server/Ninja/Systems/NinjaGlovesSystem.cs +++ b/Content.Server/Ninja/Systems/NinjaGlovesSystem.cs @@ -1,8 +1,9 @@ using Content.Server.Communications; using Content.Server.Mind; using Content.Server.Ninja.Events; -using Content.Server.Objectives.Components; +using Content.Server.Objectives.Systems; using Content.Shared.Communications; +using Content.Shared.CriminalRecords.Components; using Content.Shared.Ninja.Components; using Content.Shared.Ninja.Systems; using Content.Shared.Research.Components; @@ -16,8 +17,8 @@ namespace Content.Server.Ninja.Systems; public sealed class NinjaGlovesSystem : SharedNinjaGlovesSystem { [Dependency] private readonly EmagProviderSystem _emagProvider = default!; + [Dependency] private readonly CodeConditionSystem _codeCondition = default!; [Dependency] private readonly CommsHackerSystem _commsHacker = default!; - [Dependency] private readonly MindSystem _mind = default!; [Dependency] private readonly SharedStunProviderSystem _stunProvider = default!; [Dependency] private readonly SpaceNinjaSystem _ninja = default!; @@ -88,12 +89,16 @@ private void EnableGloves(EntityUid uid, NinjaGlovesComponent comp, EntityUid us EnsureComp(user); // prevent calling in multiple threats by toggling gloves after - if (_mind.TryGetObjectiveComp(user, out var obj) && !obj.CalledInThreat) + if (!_codeCondition.IsCompleted(user, ninja.TerrorObjective)) { var hacker = EnsureComp(user); var rule = _ninja.NinjaRule(user); if (rule != null) _commsHacker.SetThreats(user, rule.Threats, hacker); } + if (!_codeCondition.IsCompleted(user, ninja.MassArrestObjective)) + { + EnsureComp(user); + } } } diff --git a/Content.Server/Ninja/Systems/SpaceNinjaSystem.cs b/Content.Server/Ninja/Systems/SpaceNinjaSystem.cs index 835ac7ad6c..1dfaf4f339 100644 --- a/Content.Server/Ninja/Systems/SpaceNinjaSystem.cs +++ b/Content.Server/Ninja/Systems/SpaceNinjaSystem.cs @@ -1,12 +1,15 @@ using Content.Server.Communications; using Content.Server.Chat.Managers; +using Content.Server.CriminalRecords.Systems; using Content.Server.GameTicking.Rules.Components; +using Content.Server.GenericAntag; +using Content.Server.Objectives.Components; +using Content.Server.Objectives.Systems; using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; using Content.Server.PowerCell; using Content.Server.Research.Systems; using Content.Server.Roles; -using Content.Server.GenericAntag; using Content.Shared.Alert; using Content.Shared.Clothing.EntitySystems; using Content.Shared.Doors.Components; @@ -19,7 +22,6 @@ using Robust.Shared.Audio; using Robust.Shared.Player; using System.Diagnostics.CodeAnalysis; -using Content.Server.Objectives.Components; using Robust.Shared.Audio.Systems; namespace Content.Server.Ninja.Systems; @@ -28,7 +30,6 @@ namespace Content.Server.Ninja.Systems; // engi -> saboteur // medi -> idk reskin it // other -> assault -// TODO: when criminal records is merged, hack it to set everyone to arrest /// /// Main ninja system that handles ninja setup, provides helper methods for the rest of the code to use. @@ -37,6 +38,7 @@ public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem { [Dependency] private readonly AlertsSystem _alerts = default!; [Dependency] private readonly BatterySystem _battery = default!; + [Dependency] private readonly CodeConditionSystem _codeCondition = default!; [Dependency] private readonly IChatManager _chatMan = default!; [Dependency] private readonly PowerCellSystem _powerCell = default!; [Dependency] private readonly RoleSystem _role = default!; @@ -52,6 +54,7 @@ public override void Initialize() SubscribeLocalEvent(OnDoorjack); SubscribeLocalEvent(OnResearchStolen); SubscribeLocalEvent(OnThreatCalledIn); + SubscribeLocalEvent(OnCriminalRecordsHacked); } public override void Update(float frameTime) @@ -216,11 +219,21 @@ private void OnResearchStolen(EntityUid uid, SpaceNinjaComponent comp, ref Resea Popup.PopupEntity(str, uid, uid, PopupType.Medium); } - private void OnThreatCalledIn(EntityUid uid, SpaceNinjaComponent comp, ref ThreatCalledInEvent args) + private void OnThreatCalledIn(Entity ent, ref ThreatCalledInEvent args) { - if (_mind.TryGetObjectiveComp(uid, out var obj)) - { - obj.CalledInThreat = true; - } + _codeCondition.SetCompleted(ent.Owner, ent.Comp.TerrorObjective); + } + + private void OnCriminalRecordsHacked(Entity ent, ref CriminalRecordsHackedEvent args) + { + _codeCondition.SetCompleted(ent.Owner, ent.Comp.MassArrestObjective); + } + + /// + /// Called by when it detonates. + /// + public void DetonatedSpiderCharge(Entity ent) + { + _codeCondition.SetCompleted(ent.Owner, ent.Comp.SpiderChargeObjective); } } diff --git a/Content.Server/Ninja/Systems/SpiderChargeSystem.cs b/Content.Server/Ninja/Systems/SpiderChargeSystem.cs index 948d715f0a..64c958d6f1 100644 --- a/Content.Server/Ninja/Systems/SpiderChargeSystem.cs +++ b/Content.Server/Ninja/Systems/SpiderChargeSystem.cs @@ -19,6 +19,7 @@ public sealed class SpiderChargeSystem : EntitySystem [Dependency] private readonly MindSystem _mind = default!; [Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly SpaceNinjaSystem _ninja = default!; public override void Initialize() { @@ -76,10 +77,10 @@ private void OnStuck(EntityUid uid, SpiderChargeComponent comp, EntityStuckEvent /// private void OnExplode(EntityUid uid, SpiderChargeComponent comp, TriggerEvent args) { - if (comp.Planter == null || !_mind.TryGetObjectiveComp(comp.Planter.Value, out var obj)) + if (!TryComp(comp.Planter, out var ninja)) return; // assumes the target was destroyed, that the charge wasn't moved somehow - obj.Detonated = true; + _ninja.DetonatedSpiderCharge((comp.Planter.Value, ninja)); } } diff --git a/Content.Server/Nuke/NukeCodePaperSystem.cs b/Content.Server/Nuke/NukeCodePaperSystem.cs index 36268d5648..cedae69682 100644 --- a/Content.Server/Nuke/NukeCodePaperSystem.cs +++ b/Content.Server/Nuke/NukeCodePaperSystem.cs @@ -1,6 +1,7 @@ using System.Diagnostics.CodeAnalysis; using Content.Server.Chat.Systems; using Content.Server.Fax; +using Content.Shared.Fax.Components; using Content.Server.Paper; using Content.Server.Station.Components; using Content.Server.Station.Systems; @@ -66,6 +67,7 @@ public bool SendNukeCodes(EntityUid station) paperContent, Loc.GetString("nuke-codes-fax-paper-name"), null, + null, "paper_stamp-centcom", new List { diff --git a/Content.Server/Nuke/NukeSystem.cs b/Content.Server/Nuke/NukeSystem.cs index 5a7219fdf1..9cd24b9af6 100644 --- a/Content.Server/Nuke/NukeSystem.cs +++ b/Content.Server/Nuke/NukeSystem.cs @@ -189,7 +189,7 @@ private async void OnAnchorButtonPressed(EntityUid uid, NukeComponent component, continue; var msg = Loc.GetString("nuke-component-cant-anchor-floor"); - _popups.PopupEntity(msg, uid, args.Session, PopupType.MediumCaution); + _popups.PopupEntity(msg, uid, args.Actor, PopupType.MediumCaution); return; } @@ -245,10 +245,7 @@ private void OnArmButtonPressed(EntityUid uid, NukeComponent component, NukeArme else { - if (args.Session.AttachedEntity is not { } user) - return; - - DisarmBombDoafter(uid, user, component); + DisarmBombDoafter(uid, args.Actor, component); } } @@ -368,8 +365,7 @@ private void UpdateUserInterface(EntityUid uid, NukeComponent? component = null) if (!Resolve(uid, ref component)) return; - var ui = _ui.GetUiOrNull(uid, NukeUiKey.Key); - if (ui == null) + if (!_ui.HasUi(uid, NukeUiKey.Key)) return; var anchored = Transform(uid).Anchored; @@ -390,7 +386,7 @@ private void UpdateUserInterface(EntityUid uid, NukeComponent? component = null) CooldownTime = (int) component.CooldownTime }; - _ui.SetUiState(ui, state); + _ui.SetUiState(uid, NukeUiKey.Key, state); } private void PlayNukeKeypadSound(EntityUid uid, int number, NukeComponent? component = null) @@ -456,11 +452,6 @@ public void ArmBomb(EntityUid uid, NukeComponent? component = null) if (stationUid != null) _alertLevel.SetLevel(stationUid.Value, component.AlertLevelOnActivate, true, true, true, true); - var pos = nukeXform.MapPosition; - var x = (int) pos.X; - var y = (int) pos.Y; - var posText = $"({x}, {y})"; - // We are collapsing the randomness here, otherwise we would get separate random song picks for checking duration and when actually playing the song afterwards _selectedNukeSong = _audio.GetSound(component.ArmMusic); @@ -471,7 +462,7 @@ public void ArmBomb(EntityUid uid, NukeComponent? component = null) Color.Red, stationUid ?? uid, null, - ("time", (int) component.RemainingTime), ("position", posText) + ("time", (int) component.RemainingTime), ("position", FormattedMessage.RemoveMarkupPermissive(_navMap.GetNearestBeaconString((uid, nukeXform)))) ); _sound.PlayGlobalOnStation(uid, _audio.GetSound(component.ArmSound)); diff --git a/Content.Server/NukeOps/WarDeclaratorSystem.cs b/Content.Server/NukeOps/WarDeclaratorSystem.cs index dee0c10c40..04f0b069f1 100644 --- a/Content.Server/NukeOps/WarDeclaratorSystem.cs +++ b/Content.Server/NukeOps/WarDeclaratorSystem.cs @@ -58,9 +58,6 @@ private void OnAttemptOpenUI(Entity ent, ref Activatable private void OnActivated(Entity ent, ref WarDeclaratorActivateMessage args) { - if (args.Session.AttachedEntity is not {} playerEntity) - return; - var ev = new WarDeclaredEvent(ent.Comp.CurrentStatus, ent); RaiseLocalEvent(ref ev); @@ -78,7 +75,7 @@ private void OnActivated(Entity ent, ref WarDeclaratorAc { var title = Loc.GetString(ent.Comp.SenderTitle); _announcer.SendAnnouncement("war", Filter.Broadcast(), ent.Comp.Message, title, ent.Comp.Color); - _adminLogger.Add(LogType.Chat, LogImpact.Low, $"{ToPrettyString(playerEntity):player} has declared war with this text: {ent.Comp.Message}"); + _adminLogger.Add(LogType.Chat, LogImpact.Low, $"{ToPrettyString(args.Actor):player} has declared war with this text: {ent.Comp.Message}"); } UpdateUI(ent, ev.Status); @@ -86,8 +83,8 @@ private void OnActivated(Entity ent, ref WarDeclaratorAc private void UpdateUI(Entity ent, WarConditionStatus? status = null) { - _userInterfaceSystem.TrySetUiState( - ent, + _userInterfaceSystem.SetUiState( + ent.Owner, WarDeclaratorUiKey.Key, new WarDeclaratorBoundUserInterfaceState(status, ent.Comp.DisableAt, ent.Comp.ShuttleDisabledTime)); } diff --git a/Content.Server/Nutrition/Components/DrinkComponent.cs b/Content.Server/Nutrition/Components/DrinkComponent.cs deleted file mode 100644 index 20d47cda88..0000000000 --- a/Content.Server/Nutrition/Components/DrinkComponent.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Content.Server.Nutrition.EntitySystems; -using Content.Shared.FixedPoint; -using Robust.Shared.Audio; - -namespace Content.Server.Nutrition.Components; - -[RegisterComponent, Access(typeof(DrinkSystem))] -public sealed partial class DrinkComponent : Component -{ - [DataField, ViewVariables(VVAccess.ReadWrite)] - public string Solution = "drink"; - - [DataField] - public SoundSpecifier UseSound = new SoundPathSpecifier("/Audio/Items/drink.ogg"); - - [DataField, ViewVariables(VVAccess.ReadWrite)] - public FixedPoint2 TransferAmount = FixedPoint2.New(5); - - /// - /// How long it takes to drink this yourself. - /// - [DataField, ViewVariables(VVAccess.ReadWrite)] - public float Delay = 1; - - [DataField, ViewVariables(VVAccess.ReadWrite)] - public bool Examinable = true; - - /// - /// If true, trying to drink when empty will not handle the event. - /// This means other systems such as equipping on use can run. - /// Example usecase is the bucket. - /// - [DataField, ViewVariables(VVAccess.ReadWrite)] - public bool IgnoreEmpty; - - /// - /// This is how many seconds it takes to force feed someone this drink. - /// - [DataField, ViewVariables(VVAccess.ReadWrite)] - public float ForceFeedDelay = 3; -} diff --git a/Content.Server/Nutrition/Components/FatExtractorComponent.cs b/Content.Server/Nutrition/Components/FatExtractorComponent.cs index e23c557236..fa6edc911e 100644 --- a/Content.Server/Nutrition/Components/FatExtractorComponent.cs +++ b/Content.Server/Nutrition/Components/FatExtractorComponent.cs @@ -1,4 +1,5 @@ using Content.Server.Nutrition.EntitySystems; +using Content.Shared.Construction.Prototypes; using Content.Shared.Nutrition.Components; using Robust.Shared.Audio; using Robust.Shared.Prototypes; @@ -8,67 +9,87 @@ namespace Content.Server.Nutrition.Components; /// -/// This is used for a machine that extracts hunger from entities and creates meat. Yum! +/// This is used for a machine that extracts hunger from entities and creates meat. Yum! /// [RegisterComponent, Access(typeof(FatExtractorSystem)), AutoGenerateComponentPause] public sealed partial class FatExtractorComponent : Component { /// - /// Whether or not the extractor is currently extracting fat from someone + /// Whether or not the extractor is currently extracting fat from someone /// - [DataField("processing")] + [DataField] public bool Processing = true; /// - /// How much nutrition is extracted per second. + /// How much nutrition is extracted per second. /// - [DataField("nutritionPerSecond"), ViewVariables(VVAccess.ReadWrite)] + [ViewVariables(VVAccess.ReadWrite)] public int NutritionPerSecond = 10; /// - /// An accumulator which tracks extracted nutrition to determine - /// when to spawn a meat. + /// The base rate of extraction /// - [DataField("nutrientAccumulator"), ViewVariables(VVAccess.ReadWrite)] + [DataField] + public int BaseNutritionPerSecond = 10; + + #region Machine Upgrade + /// + /// Which machine part affects the nutrition rate + /// + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string MachinePartNutritionRate = "Manipulator"; + + /// + /// The increase in rate per each rating above 1. + /// + [DataField] + public float PartRatingRateMultiplier = 10; + #endregion + + /// + /// An accumulator which tracks extracted nutrition to determine + /// when to spawn a meat. + /// + [DataField] public int NutrientAccumulator; /// - /// How high has to be to spawn meat + /// How high has to be to spawn meat /// - [DataField("nutrientPerMeat"), ViewVariables(VVAccess.ReadWrite)] - public int NutrientPerMeat = 30; + [DataField] + public int NutrientPerMeat = 60; /// - /// Meat spawned by the extractor. + /// Meat spawned by the extractor. /// - [DataField("meatPrototype", customTypeSerializer: typeof(PrototypeIdSerializer)), ViewVariables(VVAccess.ReadWrite)] + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] public string MeatPrototype = "FoodMeat"; /// - /// When the next update will occur + /// When the next update will occur /// - [DataField("nextUpdate", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)] + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] [AutoPausedField] public TimeSpan NextUpdate; /// - /// How long each update takes + /// How long each update takes /// - [DataField("updateTime"), ViewVariables(VVAccess.ReadWrite)] + [DataField] public TimeSpan UpdateTime = TimeSpan.FromSeconds(1); /// - /// The sound played when extracting + /// The sound played when extracting /// - [DataField("processSound")] + [DataField] public SoundSpecifier? ProcessSound; public EntityUid? Stream; /// - /// A minium hunger threshold for extracting nutrition. - /// Ignored when emagged. + /// A minium hunger threshold for extracting nutrition. + /// Ignored when emagged. /// - [DataField("minHungerThreshold")] + [DataField] public HungerThreshold MinHungerThreshold = HungerThreshold.Okay; } diff --git a/Content.Server/Nutrition/Components/PressurizedDrinkComponent.cs b/Content.Server/Nutrition/Components/PressurizedDrinkComponent.cs deleted file mode 100644 index aafb3bc106..0000000000 --- a/Content.Server/Nutrition/Components/PressurizedDrinkComponent.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Content.Server.Nutrition.EntitySystems; -using Robust.Shared.Audio; - -namespace Content.Server.Nutrition.Components; - -/// -/// Lets a drink burst open when thrown while closed. -/// Requires and to work. -/// -[RegisterComponent, Access(typeof(DrinkSystem))] -public sealed partial class PressurizedDrinkComponent : Component -{ - /// - /// Chance for the drink to burst when thrown while closed. - /// - [DataField, ViewVariables(VVAccess.ReadWrite)] - public float BurstChance = 0.25f; - - /// - /// Sound played when the drink bursts. - /// - [DataField] - public SoundSpecifier BurstSound = new SoundPathSpecifier("/Audio/Effects/flash_bang.ogg") - { - Params = AudioParams.Default.WithVolume(-4) - }; -} diff --git a/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs b/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs index 2249926baa..cd05adc794 100644 --- a/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs @@ -5,7 +5,6 @@ using Content.Server.Fluids.EntitySystems; using Content.Server.Forensics; using Content.Server.Inventory; -using Content.Server.Nutrition.Components; using Content.Server.Popups; using Content.Server.Traits.Assorted.Components; using Content.Shared.Administration.Logs; @@ -18,7 +17,6 @@ using Content.Shared.Chemistry.Reagent; using Content.Shared.Database; using Content.Shared.DoAfter; -using Content.Shared.Examine; using Content.Shared.FixedPoint; using Content.Shared.IdentityManagement; using Content.Shared.Interaction; @@ -27,25 +25,22 @@ using Content.Shared.Nutrition; using Content.Shared.Nutrition.Components; using Content.Shared.Nutrition.EntitySystems; -using Content.Shared.Throwing; using Content.Shared.Verbs; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Configuration; using Robust.Shared.Player; using Robust.Shared.Prototypes; -using Robust.Shared.Random; using Robust.Shared.Utility; namespace Content.Server.Nutrition.EntitySystems; -public sealed class DrinkSystem : EntitySystem +public sealed class DrinkSystem : SharedDrinkSystem { [Dependency] private readonly BodySystem _body = default!; [Dependency] private readonly FlavorProfileSystem _flavorProfile = default!; [Dependency] private readonly FoodSystem _food = default!; [Dependency] private readonly IPrototypeManager _proto = default!; - [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly MobStateSystem _mobState = default!; [Dependency] private readonly OpenableSystem _openable = default!; @@ -70,33 +65,10 @@ public override void Initialize() SubscribeLocalEvent(OnDrinkInit); // run before inventory so for bucket it always tries to drink before equipping (when empty) // run after openable so its always open -> drink - SubscribeLocalEvent(OnUse, before: new[] { typeof(ServerInventorySystem) }, after: new[] { typeof(OpenableSystem) }); + SubscribeLocalEvent(OnUse, before: [typeof(ServerInventorySystem)], after: [typeof(OpenableSystem)]); SubscribeLocalEvent(AfterInteract); SubscribeLocalEvent>(AddDrinkVerb); - // put drink amount after opened - SubscribeLocalEvent(OnExamined, after: new[] { typeof(OpenableSystem) }); SubscribeLocalEvent(OnDoAfter); - - SubscribeLocalEvent(OnPressurizedDrinkLand); - } - - private FixedPoint2 DrinkVolume(EntityUid uid, DrinkComponent? component = null) - { - if (!Resolve(uid, ref component)) - return FixedPoint2.Zero; - - if (!_solutionContainer.TryGetSolution(uid, component.Solution, out _, out var sol)) - return FixedPoint2.Zero; - - return sol.Volume; - } - - public bool IsEmpty(EntityUid uid, DrinkComponent? component = null) - { - if (!Resolve(uid, ref component)) - return true; - - return DrinkVolume(uid, component) <= 0; } /// @@ -133,38 +105,6 @@ public float TotalHydration(EntityUid uid, DrinkComponent? comp = null) return total; } - private void OnExamined(Entity entity, ref ExaminedEvent args) - { - TryComp(entity, out var openable); - if (_openable.IsClosed(entity.Owner, null, openable) || !args.IsInDetailsRange || !entity.Comp.Examinable) - return; - - var empty = IsEmpty(entity, entity.Comp); - if (empty) - { - args.PushMarkup(Loc.GetString("drink-component-on-examine-is-empty")); - return; - } - - if (HasComp(entity)) - { - //provide exact measurement for beakers - args.PushText(Loc.GetString("drink-component-on-examine-exact-volume", ("amount", DrinkVolume(entity, entity.Comp)))); - } - else - { - //general approximation - var remainingString = (int) _solutionContainer.PercentFull(entity) switch - { - 100 => "drink-component-on-examine-is-full", - > 66 => "drink-component-on-examine-is-mostly-full", - > 33 => HalfEmptyOrHalfFull(args), - _ => "drink-component-on-examine-is-mostly-empty", - }; - args.PushMarkup(Loc.GetString(remainingString)); - } - } - private void AfterInteract(Entity entity, ref AfterInteractEvent args) { if (args.Handled || args.Target == null || !args.CanReach) @@ -181,25 +121,6 @@ private void OnUse(Entity entity, ref UseInHandEvent args) args.Handled = TryDrink(args.User, args.User, entity.Comp, entity); } - private void OnPressurizedDrinkLand(Entity entity, ref LandEvent args) - { - if (!TryComp(entity, out var drink) || !TryComp(entity, out var openable)) - return; - - if (!openable.Opened && - _random.Prob(entity.Comp.BurstChance) && - _solutionContainer.TryGetSolution(entity.Owner, drink.Solution, out var soln, out var interactions)) - { - // using SetOpen instead of TryOpen to not play 2 sounds - _openable.SetOpen(entity, true, openable); - - var solution = _solutionContainer.SplitSolution(soln.Value, interactions.Volume); - _puddle.TrySpillAt(entity, solution, out _); - - _audio.PlayPvs(entity.Comp.BurstSound, entity); - } - } - private void OnDrinkInit(Entity entity, ref ComponentInit args) { if (TryComp(entity, out var existingDrainable)) @@ -442,16 +363,4 @@ private void AddDrinkVerb(Entity entity, ref GetVerbsEvent(args.Examiner, out var examiner) && examiner.EntityName.Length > 0 - && string.Compare(examiner.EntityName.Substring(0, 1), "m", StringComparison.InvariantCultureIgnoreCase) > 0) - remainingString = "drink-component-on-examine-is-half-empty"; - - return remainingString; - } } diff --git a/Content.Server/Nutrition/EntitySystems/FatExtractorSystem.cs b/Content.Server/Nutrition/EntitySystems/FatExtractorSystem.cs index 180e40d1e4..dc1f67c740 100644 --- a/Content.Server/Nutrition/EntitySystems/FatExtractorSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/FatExtractorSystem.cs @@ -1,5 +1,6 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; +using Content.Server.Construction; using Content.Server.Nutrition.Components; using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; @@ -9,6 +10,7 @@ using Content.Shared.Nutrition.Components; using Content.Shared.Nutrition.EntitySystems; using Content.Shared.Storage.Components; +using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Timing; @@ -27,12 +29,25 @@ public sealed class FatExtractorSystem : EntitySystem /// public override void Initialize() { + SubscribeLocalEvent(OnRefreshParts); + SubscribeLocalEvent(OnUpgradeExamine); SubscribeLocalEvent(OnGotEmagged); SubscribeLocalEvent(OnClosed); SubscribeLocalEvent(OnOpen); SubscribeLocalEvent(OnPowerChanged); } + private void OnRefreshParts(EntityUid uid, FatExtractorComponent component, RefreshPartsEvent args) + { + var rating = args.PartRatings[component.MachinePartNutritionRate] - 1; + component.NutritionPerSecond = component.BaseNutritionPerSecond + (int) (component.PartRatingRateMultiplier * rating); + } + + private void OnUpgradeExamine(EntityUid uid, FatExtractorComponent component, UpgradeExamineEvent args) + { + args.AddPercentageUpgrade("fat-extractor-component-rate", (float) component.NutritionPerSecond / component.BaseNutritionPerSecond); + } + private void OnGotEmagged(EntityUid uid, FatExtractorComponent component, ref GotEmaggedEvent args) { args.Handled = true; diff --git a/Content.Server/Nutrition/EntitySystems/FoodGuideDataSystem.cs b/Content.Server/Nutrition/EntitySystems/FoodGuideDataSystem.cs new file mode 100644 index 0000000000..f21c509ace --- /dev/null +++ b/Content.Server/Nutrition/EntitySystems/FoodGuideDataSystem.cs @@ -0,0 +1,138 @@ +using System.Linq; +using Content.Client.Chemistry.EntitySystems; +using Content.Server.Chemistry.ReactionEffects; +using Content.Server.Nutrition.Components; +using Content.Shared.Chemistry.Components.SolutionManager; +using Content.Shared.Chemistry.Reaction; +using Content.Shared.Chemistry.Reagent; +using Content.Shared.Kitchen; +using Content.Shared.Nutrition.Components; +using Robust.Server.Player; +using Robust.Shared.Enums; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; + +namespace Content.Server.Nutrition.EntitySystems; + +public sealed class FoodGuideDataSystem : SharedFoodGuideDataSystem +{ + public static readonly ProtoId[] ReagentWhitelist = + [ + "Nutriment", + "Vitamin", + "Protein", + "UncookedAnimalProteins", + "Fat", + "Water" + ]; + + public static readonly string[] ComponentNamesBlacklist = ["HumanoidAppearance"]; + + public static readonly string[] SuffixBlacklist = ["debug", "do not map", "admeme"]; + + [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly IPrototypeManager _protoMan = default!; + + private Dictionary> _sources = new(); + + public override void Initialize() + { + SubscribeLocalEvent(OnPrototypesReloaded); + _player.PlayerStatusChanged += OnPlayerStatusChanged; + + ReloadRecipes(); + } + + private void OnPrototypesReloaded(PrototypesReloadedEventArgs args) + { + if (!args.WasModified() + && !args.WasModified() + && !args.WasModified() + ) + return; + + ReloadRecipes(); + } + + public void ReloadRecipes() + { + // TODO: add this code to the list of known recipes because this is spaghetti + _sources.Clear(); + + // Butcherable and slicable entities + foreach (var ent in _protoMan.EnumeratePrototypes()) + { + if (ent.Abstract + || ent.Components.Any(it => ComponentNamesBlacklist.Contains(it.Key)) + || ent.SetSuffix is {} suffix && SuffixBlacklist.Any(it => suffix.Contains(it, StringComparison.OrdinalIgnoreCase)) + ) + continue; + + if (ent.TryGetComponent(out var butcherable)) + { + var butcheringSource = new FoodButcheringData(ent, butcherable); + foreach (var butchlet in butcherable.SpawnedEntities) + { + if (butchlet.PrototypeId is null) + continue; + + _sources.GetOrNew(butchlet.PrototypeId).Add(butcheringSource); + } + } + + if (ent.TryGetComponent(out var slicable) && slicable.Slice is not null) + { + _sources.GetOrNew(slicable.Slice).Add(new FoodSlicingData(ent, slicable.Slice.Value, slicable.TotalCount)); + } + } + + // Recipes + foreach (var recipe in _protoMan.EnumeratePrototypes()) + { + _sources.GetOrNew(recipe.Result).Add(new FoodRecipeData(recipe)); + } + + // Entity-spawning reactions + foreach (var reaction in _protoMan.EnumeratePrototypes()) + { + foreach (var effect in reaction.Effects) + { + if (effect is not CreateEntityReactionEffect entEffect) + continue; + + _sources.GetOrNew(entEffect.Entity).Add(new FoodReactionData(reaction, entEffect.Entity, (int) entEffect.Number)); + } + } + + Registry.Clear(); + + foreach (var (result, sources) in _sources) + { + var proto = _protoMan.Index(result); + var composition = proto.TryGetComponent(out var food) && proto.TryGetComponent(out var manager) + ? manager?.Solutions?[food.Solution]?.Contents?.ToArray() ?? [] + : []; + + // We filter out food without whitelisted reagents because well when people look for food they usually expect FOOD and not insulated gloves. + // And we get insulated and other gloves because they have ButcherableComponent and they are also moth food + if (!composition.Any(it => ReagentWhitelist.Contains(it.Reagent.Prototype))) + continue; + + // We also limit the number of sources to 10 because it's a huge performance strain to render 500 raw meat recipes. + var distinctSources = sources.DistinctBy(it => it.Identitier).Take(10); + var entry = new FoodGuideEntry(result, proto.Name, distinctSources.ToArray(), composition); + Registry.Add(entry); + } + + RaiseNetworkEvent(new FoodGuideRegistryChangedEvent(Registry)); + } + + private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs args) + { + if (args.NewStatus != SessionStatus.Connected) + return; + + RaiseNetworkEvent(new FoodGuideRegistryChangedEvent(Registry), args.Session); + } +} diff --git a/Content.Server/Nutrition/EntitySystems/SmokingSystem.Vape.cs b/Content.Server/Nutrition/EntitySystems/SmokingSystem.Vape.cs index fe0d1d0c16..d5bff967b3 100644 --- a/Content.Server/Nutrition/EntitySystems/SmokingSystem.Vape.cs +++ b/Content.Server/Nutrition/EntitySystems/SmokingSystem.Vape.cs @@ -13,6 +13,7 @@ using Content.Shared.Interaction; using Content.Shared.Nutrition; using System.Threading; +using Content.Shared.Atmos; /// /// System for vapes diff --git a/Content.Server/Nyanotrasen/Chemistry/Effects/ChemRemovePsionic.cs b/Content.Server/Nyanotrasen/Chemistry/Effects/ChemRemovePsionic.cs index a23a5b3d77..859f22cd4a 100644 --- a/Content.Server/Nyanotrasen/Chemistry/Effects/ChemRemovePsionic.cs +++ b/Content.Server/Nyanotrasen/Chemistry/Effects/ChemRemovePsionic.cs @@ -21,7 +21,7 @@ public override void Effect(ReagentEffectArgs args) var psySys = args.EntityManager.EntitySysManager.GetEntitySystem(); - psySys.RemovePsionics(args.SolutionEntity); + psySys.RemoveAllPsionicPowers(args.SolutionEntity, true); } } } diff --git a/Content.Server/Nyanotrasen/Cloning/MetempsychoticMachineComponent.cs b/Content.Server/Nyanotrasen/Cloning/MetempsychoticMachineComponent.cs deleted file mode 100644 index 0adcc9b5b2..0000000000 --- a/Content.Server/Nyanotrasen/Cloning/MetempsychoticMachineComponent.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Content.Shared.Random; - -namespace Content.Server.Nyanotrasen.Cloning -{ - [RegisterComponent] - public sealed partial class MetempsychoticMachineComponent : Component - { - /// - /// Chance you will spawn as a humanoid instead of a non humanoid. - /// - [DataField("humanoidBaseChance")] - public float HumanoidBaseChance = 0.75f; - - [ValidatePrototypeId] - [DataField("metempsychoticHumanoidPool")] - public string MetempsychoticHumanoidPool = "MetempsychoticHumanoidPool"; - - [ValidatePrototypeId] - [DataField("metempsychoticNonHumanoidPool")] - public string MetempsychoticNonHumanoidPool = "MetempsychoticNonhumanoidPool"; - } -} diff --git a/Content.Server/Nyanotrasen/Cloning/MetempsychoticMachineSystem.cs b/Content.Server/Nyanotrasen/Cloning/MetempsychoticMachineSystem.cs deleted file mode 100644 index 62dc1b078e..0000000000 --- a/Content.Server/Nyanotrasen/Cloning/MetempsychoticMachineSystem.cs +++ /dev/null @@ -1,47 +0,0 @@ -using Content.Shared.Humanoid.Prototypes; -using Content.Shared.Random; -using Content.Shared.Random.Helpers; -using Robust.Shared.Random; -using Robust.Shared.Prototypes; - -namespace Content.Server.Nyanotrasen.Cloning -{ - public sealed class MetempsychoticMachineSystem : EntitySystem - { - [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - - private ISawmill _sawmill = default!; - - public string GetSpawnEntity(EntityUid uid, float karmaBonus, MetempsychoticMachineComponent component, SpeciesPrototype oldSpecies, out SpeciesPrototype? species, int? karma = null) - { - var chance = component.HumanoidBaseChance + karmaBonus; - - if (karma != null) - chance -= ((1 - component.HumanoidBaseChance) * (float) karma); - - if (chance > 1 && _random.Prob(chance - 1)) - { - species = oldSpecies; - return oldSpecies.Prototype; - } - else - chance = 1; - - chance = Math.Clamp(chance, 0, 1); - if (_random.Prob(chance) && - _prototypeManager.TryIndex(component.MetempsychoticHumanoidPool, out var humanoidPool) && - _prototypeManager.TryIndex(humanoidPool.Pick(), out var speciesPrototype)) - { - species = speciesPrototype; - return speciesPrototype.Prototype; - } - else - { - species = null; - _sawmill.Error("Could not index species for metempsychotic machine..."); - return "MobHuman"; - } - } - } -} diff --git a/Content.Server/Nyanotrasen/Kitchen/EntitySystems/DeepFryerSystem.Storage.cs b/Content.Server/Nyanotrasen/Kitchen/EntitySystems/DeepFryerSystem.Storage.cs index 8858286dbf..a9a07c287b 100644 --- a/Content.Server/Nyanotrasen/Kitchen/EntitySystems/DeepFryerSystem.Storage.cs +++ b/Content.Server/Nyanotrasen/Kitchen/EntitySystems/DeepFryerSystem.Storage.cs @@ -80,14 +80,13 @@ private void OnInteractUsing(EntityUid uid, DeepFryerComponent component, Intera private void OnInsertItem(EntityUid uid, DeepFryerComponent component, DeepFryerInsertItemMessage args) { - var user = args.Session.AttachedEntity; + var user = EntityManager.GetEntity(args.Entity); - if (user == null || - !TryComp(user, out var handsComponent) || + if (!TryComp(user, out var handsComponent) || handsComponent.ActiveHandEntity == null) return; if (handsComponent.ActiveHandEntity != null) - TryInsertItem(uid, component, user.Value, handsComponent.ActiveHandEntity.Value); + TryInsertItem(uid, component, user, handsComponent.ActiveHandEntity.Value); } } diff --git a/Content.Server/Nyanotrasen/Kitchen/EntitySystems/DeepFryerSystem.cs b/Content.Server/Nyanotrasen/Kitchen/EntitySystems/DeepFryerSystem.cs index 80c38f4630..dc182c1edf 100644 --- a/Content.Server/Nyanotrasen/Kitchen/EntitySystems/DeepFryerSystem.cs +++ b/Content.Server/Nyanotrasen/Kitchen/EntitySystems/DeepFryerSystem.cs @@ -128,8 +128,7 @@ private void UpdateUserInterface(EntityUid uid, DeepFryerComponent component) component.FryingOilThreshold, EntityManager.GetNetEntityArray(component.Storage.ContainedEntities.ToArray())); - if (!_uiSystem.TrySetUiState(uid, DeepFryerUiKey.Key, state)) - _sawmill.Warning($"{ToPrettyString(uid)} was unable to set UI state."); + _uiSystem.SetUiState(new Entity(uid, null), DeepFryerUiKey.Key, state); } /// @@ -525,15 +524,12 @@ private void OnRemoveItem(EntityUid uid, DeepFryerComponent component, DeepFryer if (!_containerSystem.Remove(removedItem, component.Storage)) return; - var user = args.Session.AttachedEntity; + var user = EntityManager.GetEntity(args.Entity); - if (user != null) - { - _handsSystem.TryPickupAnyHand(user.Value, removedItem); + _handsSystem.TryPickupAnyHand(user, removedItem); - _adminLogManager.Add(LogType.Action, LogImpact.Low, - $"{ToPrettyString(user.Value)} took {ToPrettyString(args.Item)} out of {ToPrettyString(uid)}."); - } + _adminLogManager.Add(LogType.Action, LogImpact.Low, + $"{ToPrettyString(user)} took {ToPrettyString(args.Item)} out of {ToPrettyString(uid)}."); _audioSystem.PlayPvs(component.SoundRemoveItem, uid, AudioParamsInsertRemove); @@ -581,17 +577,16 @@ private bool TryGetActiveHandSolutionContainer( private void OnScoopVat(EntityUid uid, DeepFryerComponent component, DeepFryerScoopVatMessage args) { - var user = args.Session.AttachedEntity; + var user = EntityManager.GetEntity(args.Entity); - if (user == null || - !TryGetActiveHandSolutionContainer(uid, user.Value, out var heldItem, out var heldSolution, + if (!TryGetActiveHandSolutionContainer(uid, user, out var heldItem, out var heldSolution, out var transferAmount)) return; if (!_solutionContainerSystem.TryGetSolution(component.Owner, component.Solution.Name, out var solution)) return; - _solutionTransferSystem.Transfer(user.Value, + _solutionTransferSystem.Transfer(user, uid, solution.Value, heldItem.Value, @@ -603,10 +598,9 @@ private void OnScoopVat(EntityUid uid, DeepFryerComponent component, DeepFryerSc private void OnClearSlagStart(EntityUid uid, DeepFryerComponent component, DeepFryerClearSlagMessage args) { - var user = args.Session.AttachedEntity; + var user = EntityManager.GetEntity(args.Entity); - if (user == null || - !TryGetActiveHandSolutionContainer(uid, user.Value, out var heldItem, out var heldSolution, + if (!TryGetActiveHandSolutionContainer(uid, user, out var heldItem, out var heldSolution, out var transferAmount)) return; @@ -616,7 +610,7 @@ private void OnClearSlagStart(EntityUid uid, DeepFryerComponent component, DeepF _popupSystem.PopupEntity( Loc.GetString("deep-fryer-oil-no-slag"), uid, - user.Value); + user); return; } @@ -626,7 +620,7 @@ private void OnClearSlagStart(EntityUid uid, DeepFryerComponent component, DeepF var ev = new ClearSlagDoAfterEvent(heldSolution.Value.Comp.Solution, transferAmount); //JJ Comment - not sure I have DoAfterArgs configured correctly. - var doAfterArgs = new DoAfterArgs(EntityManager, user.Value, delay, ev, uid, uid, heldItem) + var doAfterArgs = new DoAfterArgs(EntityManager, user, delay, ev, uid, uid, heldItem) { BreakOnDamage = true, BreakOnTargetMove = true, @@ -645,13 +639,10 @@ private void OnRemoveAllItems(EntityUid uid, DeepFryerComponent component, DeepF _containerSystem.EmptyContainer(component.Storage); - var user = args.Session.AttachedEntity; + var user = EntityManager.GetEntity(args.Entity); - if (user != null) - { - _adminLogManager.Add(LogType.Action, LogImpact.Low, - $"{ToPrettyString(user.Value)} removed all items from {ToPrettyString(uid)}."); - } + _adminLogManager.Add(LogType.Action, LogImpact.Low, + $"{ToPrettyString(user)} removed all items from {ToPrettyString(uid)}."); _audioSystem.PlayPvs(component.SoundRemoveItem, uid, AudioParamsInsertRemove); diff --git a/Content.Server/Nyanotrasen/Mail/Components/MailComponent.cs b/Content.Server/Nyanotrasen/Mail/Components/MailComponent.cs deleted file mode 100644 index 61d94bbad3..0000000000 --- a/Content.Server/Nyanotrasen/Mail/Components/MailComponent.cs +++ /dev/null @@ -1,108 +0,0 @@ -using System.Threading; -using Robust.Shared.Audio; -using Content.Shared.Storage; -using Content.Shared.Mail; - -namespace Content.Server.Mail.Components -{ - [RegisterComponent] - public sealed partial class MailComponent : SharedMailComponent - { - [ViewVariables(VVAccess.ReadWrite)] - [DataField("recipient")] - public string Recipient = "None"; - - [ViewVariables(VVAccess.ReadWrite)] - [DataField("recipientJob")] - public string RecipientJob = "None"; - - // Why do we not use LockComponent? - // Because this can't be locked again, - // and we have special conditions for unlocking, - // and we don't want to add a verb. - [ViewVariables(VVAccess.ReadWrite)] - [DataField("isLocked")] - public bool IsLocked = true; - - /// - /// Is this parcel profitable to deliver for the station? - /// - /// - /// The station won't receive any award on delivery if this is false. - /// This is useful for broken fragile packages and packages that were - /// not delivered in time. - /// - [DataField("isProfitable")] - public bool IsProfitable = true; - - /// - /// Is this package considered fragile? - /// - /// - /// This can be set to true in the YAML files for a mail delivery to - /// always be Fragile, despite its contents. - /// - [DataField("isFragile")] - public bool IsFragile = false; - - /// - /// Is this package considered priority mail? - /// - /// - /// There will be a timer set for its successful delivery. The - /// station's bank account will be penalized if it is not delivered on - /// time. - /// - /// This is set to false on successful delivery. - /// - /// This can be set to true in the YAML files for a mail delivery to - /// always be Priority. - /// - [DataField("isPriority")] - public bool IsPriority = false; - - /// - /// What will be packaged when the mail is spawned. - /// - [DataField("contents")] - public List Contents = new(); - - /// - /// The amount that cargo will be awarded for delivering this mail. - /// - [DataField("bounty")] - public int Bounty = 750; - - /// - /// Penalty if the mail is destroyed. - /// - [DataField("penalty")] - public int Penalty = -250; - - /// - /// The sound that's played when the mail's lock is broken. - /// - [DataField("penaltySound")] - public SoundSpecifier PenaltySound = new SoundPathSpecifier("/Audio/Machines/Nuke/angry_beep.ogg"); - - /// - /// The sound that's played when the mail's opened. - /// - [DataField("openSound")] - public SoundSpecifier OpenSound = new SoundPathSpecifier("/Audio/Effects/packetrip.ogg"); - - /// - /// The sound that's played when the mail's lock has been emagged. - /// - [DataField("emagSound")] - public SoundSpecifier EmagSound = new SoundCollectionSpecifier("sparks"); - - /// - /// Whether this component is enabled. - /// Removed when it becomes trash. - /// - public bool IsEnabled = true; - - public CancellationTokenSource? priorityCancelToken; - } -} diff --git a/Content.Server/Nyanotrasen/Mail/Components/MailReceiverComponent.cs b/Content.Server/Nyanotrasen/Mail/Components/MailReceiverComponent.cs deleted file mode 100644 index 4224de5de4..0000000000 --- a/Content.Server/Nyanotrasen/Mail/Components/MailReceiverComponent.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Content.Server.Mail.Components -{ - [RegisterComponent] - public sealed partial class MailReceiverComponent : Component - {} -} diff --git a/Content.Server/Nyanotrasen/Mail/Components/MailTeleporterComponent.cs b/Content.Server/Nyanotrasen/Mail/Components/MailTeleporterComponent.cs deleted file mode 100644 index bc05d7307d..0000000000 --- a/Content.Server/Nyanotrasen/Mail/Components/MailTeleporterComponent.cs +++ /dev/null @@ -1,108 +0,0 @@ -using Robust.Shared.Audio; - -namespace Content.Server.Mail.Components -{ - /// - /// This is for the mail teleporter. - /// Random mail will be teleported to this every few minutes. - /// - [RegisterComponent] - public sealed partial class MailTeleporterComponent : Component - { - - // Not starting accumulator at 0 so mail carriers have some deliveries to make shortly after roundstart. - [DataField("accumulator")] - public float Accumulator = 285f; - - [DataField("teleportInterval")] - public TimeSpan TeleportInterval = TimeSpan.FromMinutes(5); - - /// - /// The sound that's played when new mail arrives. - /// - [DataField("teleportSound")] - public SoundSpecifier TeleportSound = new SoundPathSpecifier("/Audio/Effects/teleport_arrival.ogg"); - - /// - /// The MailDeliveryPoolPrototype that's used to select what mail this - /// teleporter can deliver. - /// - [DataField("mailPool")] - public string MailPool = "RandomMailDeliveryPool"; - - /// - /// How many mail candidates do we need per actual delivery sent when - /// the mail goes out? The number of candidates is divided by this number - /// to determine how many deliveries will be teleported in. - /// It does not determine unique recipients. That is random. - /// - [DataField("candidatesPerDelivery")] - public int CandidatesPerDelivery = 8; - - [DataField("minimumDeliveriesPerTeleport")] - public int MinimumDeliveriesPerTeleport = 1; - - /// - /// Do not teleport any more mail in, if there are at least this many - /// undelivered parcels. - /// - /// - /// Currently this works by checking how many MailComponent entities - /// are sitting on the teleporter's tile. - /// - /// It should be noted that if the number of actual deliveries to be - /// made based on the number of candidates divided by candidates per - /// delivery exceeds this number, the teleporter will spawn more mail - /// than this number. - /// - /// This is just a simple check to see if anyone's been picking up the - /// mail lately to prevent entity bloat for the sake of performance. - /// - [DataField("maximumUndeliveredParcels")] - public int MaximumUndeliveredParcels = 5; - - /// - /// Any item that breaks or is destroyed in less than this amount of - /// damage is one of the types of items considered fragile. - /// - [DataField("fragileDamageThreshold")] - public int FragileDamageThreshold = 10; - - /// - /// What's the bonus for delivering a fragile package intact? - /// - [DataField("fragileBonus")] - public int FragileBonus = 100; - - /// - /// What's the malus for failing to deliver a fragile package? - /// - [DataField("fragileMalus")] - public int FragileMalus = -100; - - /// - /// What's the chance for any one delivery to be marked as priority mail? - /// - [DataField("priorityChance")] - public float PriorityChance = 0.1f; - - /// - /// How long until a priority delivery is considered as having failed - /// if not delivered? - /// - [DataField("priorityDuration")] - public TimeSpan priorityDuration = TimeSpan.FromMinutes(5); - - /// - /// What's the bonus for delivering a priority package on time? - /// - [DataField("priorityBonus")] - public int PriorityBonus = 250; - - /// - /// What's the malus for failing to deliver a priority package? - /// - [DataField("priorityMalus")] - public int PriorityMalus = -250; - } -} diff --git a/Content.Server/Nyanotrasen/Mail/Components/StationMailRouterComponent.cs b/Content.Server/Nyanotrasen/Mail/Components/StationMailRouterComponent.cs deleted file mode 100644 index ce87eb131f..0000000000 --- a/Content.Server/Nyanotrasen/Mail/Components/StationMailRouterComponent.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Content.Server.Mail; - -/// -/// Designates a station as a place for sending and receiving mail. -/// -[RegisterComponent] -public sealed partial class StationMailRouterComponent : Component -{ -} diff --git a/Content.Server/Nyanotrasen/Mail/MailCommands.cs b/Content.Server/Nyanotrasen/Mail/MailCommands.cs deleted file mode 100644 index 5af873f9e8..0000000000 --- a/Content.Server/Nyanotrasen/Mail/MailCommands.cs +++ /dev/null @@ -1,137 +0,0 @@ -using System.Linq; -using Robust.Shared.Console; -using Robust.Shared.Containers; -using Robust.Shared.Prototypes; -using Content.Shared.Administration; -using Content.Server.Administration; -using Content.Server.Mail.Components; - -namespace Content.Server.Mail; - -[AdminCommand(AdminFlags.Fun)] -public sealed class MailToCommand : IConsoleCommand -{ - public string Command => "mailto"; - public string Description => Loc.GetString("command-mailto-description", ("requiredComponent", nameof(MailReceiverComponent))); - public string Help => Loc.GetString("command-mailto-help", ("command", Command)); - - [Dependency] private readonly IEntityManager _entityManager = default!; - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; - - private readonly string _blankMailPrototype = "MailAdminFun"; - private readonly string _container = "storagebase"; - private readonly string _mailContainer = "contents"; - - - public async void Execute(IConsoleShell shell, string argStr, string[] args) - { - if (args.Length < 4) - { - shell.WriteError(Loc.GetString("shell-wrong-arguments-number")); - return; - } - - if (!EntityUid.TryParse(args[0], out var recipientUid)) - { - shell.WriteError(Loc.GetString("shell-entity-uid-must-be-number")); - return; - } - - if (!EntityUid.TryParse(args[1], out var containerUid)) - { - shell.WriteError(Loc.GetString("shell-entity-uid-must-be-number")); - return; - } - - if (!Boolean.TryParse(args[2], out var isFragile)) - { - shell.WriteError(Loc.GetString("shell-invalid-bool")); - return; - } - - if (!Boolean.TryParse(args[3], out var isPriority)) - { - shell.WriteError(Loc.GetString("shell-invalid-bool")); - return; - } - - - var _mailSystem = _entitySystemManager.GetEntitySystem(); - var _containerSystem = _entitySystemManager.GetEntitySystem(); - - if (!_entityManager.TryGetComponent(recipientUid, out MailReceiverComponent? mailReceiver)) - { - shell.WriteLine(Loc.GetString("command-mailto-no-mailreceiver", ("requiredComponent", nameof(MailReceiverComponent)))); - return; - } - - if (!_prototypeManager.HasIndex(_blankMailPrototype)) - { - shell.WriteLine(Loc.GetString("command-mailto-no-blankmail", ("blankMail", _blankMailPrototype))); - return; - } - - if (!_containerSystem.TryGetContainer(containerUid, _container, out var targetContainer)) - { - shell.WriteLine(Loc.GetString("command-mailto-invalid-container", ("requiredContainer", _container))); - return; - } - - if (!_mailSystem.TryGetMailRecipientForReceiver(mailReceiver, out MailRecipient? recipient)) - { - shell.WriteLine(Loc.GetString("command-mailto-unable-to-receive")); - return; - } - - if (!_mailSystem.TryGetMailTeleporterForReceiver(mailReceiver, out MailTeleporterComponent? teleporterComponent)) - { - shell.WriteLine(Loc.GetString("command-mailto-no-teleporter-found")); - return; - } - - var mailUid = _entityManager.SpawnEntity(_blankMailPrototype, _entityManager.GetComponent(containerUid).Coordinates); - var mailContents = _containerSystem.EnsureContainer(mailUid, _mailContainer); - - if (!_entityManager.TryGetComponent(mailUid, out MailComponent? mailComponent)) - { - shell.WriteLine(Loc.GetString("command-mailto-bogus-mail", ("blankMail", _blankMailPrototype), ("requiredMailComponent", nameof(MailComponent)))); - return; - } - - foreach (var entity in targetContainer.ContainedEntities.ToArray()) - _containerSystem.Insert(entity, mailContents); - - mailComponent.IsFragile = isFragile; - mailComponent.IsPriority = isPriority; - - _mailSystem.SetupMail(mailUid, teleporterComponent, recipient.Value); - - var teleporterQueue = _containerSystem.EnsureContainer(teleporterComponent.Owner, "queued"); - _containerSystem.Insert(mailUid, teleporterQueue); - shell.WriteLine(Loc.GetString("command-mailto-success", ("timeToTeleport", teleporterComponent.TeleportInterval.TotalSeconds - teleporterComponent.Accumulator))); - } -} - -[AdminCommand(AdminFlags.Fun)] -public sealed class MailNowCommand : IConsoleCommand -{ - public string Command => "mailnow"; - public string Description => Loc.GetString("command-mailnow"); - public string Help => Loc.GetString("command-mailnow-help", ("command", Command)); - - [Dependency] private readonly IEntityManager _entityManager = default!; - [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; - - public async void Execute(IConsoleShell shell, string argStr, string[] args) - { - var _mailSystem = _entitySystemManager.GetEntitySystem(); - - foreach (var mailTeleporter in _entityManager.EntityQuery()) - { - mailTeleporter.Accumulator += (float) mailTeleporter.TeleportInterval.TotalSeconds - mailTeleporter.Accumulator; - } - - shell.WriteLine(Loc.GetString("command-mailnow-success")); - } -} diff --git a/Content.Server/Nyanotrasen/Mail/MailSystem.cs b/Content.Server/Nyanotrasen/Mail/MailSystem.cs deleted file mode 100644 index 05cd0c88a7..0000000000 --- a/Content.Server/Nyanotrasen/Mail/MailSystem.cs +++ /dev/null @@ -1,731 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Threading; -using Robust.Shared.Audio; -using Robust.Shared.Containers; -using Robust.Shared.Prototypes; -using Robust.Shared.Random; -using Content.Server.Access.Systems; -using Content.Server.Cargo.Components; -using Content.Server.Cargo.Systems; -using Content.Server.Chat.Systems; -using Content.Server.Chemistry.Containers.EntitySystems; -using Content.Server.Chemistry.EntitySystems; -using Content.Server.Damage.Components; -using Content.Server.Destructible; -using Content.Server.Destructible.Thresholds; -using Content.Server.Destructible.Thresholds.Behaviors; -using Content.Server.Destructible.Thresholds.Triggers; -using Content.Server.Fluids.Components; -using Content.Server.Item; -using Content.Server.Mail.Components; -using Content.Server.Mind; -using Content.Server.Nutrition.Components; -using Content.Server.Nutrition.EntitySystems; -using Content.Server.Popups; -using Content.Server.Power.Components; -using Content.Server.Station.Systems; -using Content.Server.Spawners.EntitySystems; -using Content.Shared.Access; -using Content.Shared.Access.Components; -using Content.Shared.Access.Systems; -using Content.Shared.Chat; -using Content.Shared.Chemistry.EntitySystems; -using Content.Shared.Damage; -using Content.Shared.Emag.Components; -using Content.Shared.Destructible; -using Content.Shared.Emag.Systems; -using Content.Shared.Examine; -using Content.Shared.Fluids.Components; -using Content.Shared.Hands.EntitySystems; -using Content.Shared.Interaction; -using Content.Shared.Interaction.Events; -using Content.Shared.Item; -using Content.Shared.Mail; -using Content.Shared.Maps; -using Content.Shared.Nutrition.Components; -using Content.Shared.Nutrition.EntitySystems; -using Content.Shared.PDA; -using Content.Shared.Random.Helpers; -using Content.Shared.Roles; -using Content.Shared.StatusIcon; -using Content.Shared.Storage; -using Content.Shared.Tag; -using Robust.Shared.Audio.Systems; -using Timer = Robust.Shared.Timing.Timer; - -namespace Content.Server.Mail -{ - public sealed class MailSystem : EntitySystem - { - [Dependency] private readonly PopupSystem _popupSystem = default!; - [Dependency] private readonly AccessReaderSystem _accessSystem = default!; - [Dependency] private readonly SharedHandsSystem _handsSystem = default!; - [Dependency] private readonly IdCardSystem _idCardSystem = default!; - [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly TagSystem _tagSystem = default!; - [Dependency] private readonly CargoSystem _cargoSystem = default!; - [Dependency] private readonly StationSystem _stationSystem = default!; - [Dependency] private readonly ChatSystem _chatSystem = default!; - [Dependency] private readonly OpenableSystem _openable = default!; - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly SharedContainerSystem _containerSystem = default!; - [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!; - [Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!; - [Dependency] private readonly SharedAudioSystem _audioSystem = default!; - [Dependency] private readonly DamageableSystem _damageableSystem = default!; - [Dependency] private readonly ItemSystem _itemSystem = default!; - [Dependency] private readonly MindSystem _mindSystem = default!; - [Dependency] private readonly MetaDataSystem _metaDataSystem = default!; - - private ISawmill _sawmill = default!; - - public override void Initialize() - { - base.Initialize(); - - _sawmill = Logger.GetSawmill("mail"); - - SubscribeLocalEvent(OnSpawnPlayer, after: new[] { typeof(SpawnPointSystem) }); - - SubscribeLocalEvent(OnRemove); - SubscribeLocalEvent(OnUseInHand); - SubscribeLocalEvent(OnAfterInteractUsing); - SubscribeLocalEvent(OnExamined); - SubscribeLocalEvent(OnDestruction); - SubscribeLocalEvent(OnDamage); - SubscribeLocalEvent(OnBreak); - SubscribeLocalEvent(OnMailEmagged); - } - - public override void Update(float frameTime) - { - base.Update(frameTime); - foreach (var mailTeleporter in EntityQuery()) - { - if (TryComp(mailTeleporter.Owner, out var power) && !power.Powered) - return; - - mailTeleporter.Accumulator += frameTime; - - if (mailTeleporter.Accumulator < mailTeleporter.TeleportInterval.TotalSeconds) - continue; - - mailTeleporter.Accumulator -= (float) mailTeleporter.TeleportInterval.TotalSeconds; - - SpawnMail(mailTeleporter.Owner, mailTeleporter); - } - } - - /// - /// Dynamically add the MailReceiver component to appropriate entities. - /// - private void OnSpawnPlayer(PlayerSpawningEvent args) - { - if (args.SpawnResult == null || - args.Job == null || - args.Station is not {} station) - { - return; - } - - if (!HasComp(station)) - return; - - AddComp(args.SpawnResult.Value); - } - - private void OnRemove(EntityUid uid, MailComponent component, ComponentRemove args) - { - // Make sure the priority timer doesn't run. - if (component.priorityCancelToken != null) - component.priorityCancelToken.Cancel(); - } - - /// - /// Try to open the mail. - /// - private void OnUseInHand(EntityUid uid, MailComponent component, UseInHandEvent args) - { - if (!component.IsEnabled) - return; - if (component.IsLocked) - { - _popupSystem.PopupEntity(Loc.GetString("mail-locked"), uid, args.User); - return; - } - OpenMail(uid, component, args.User); - } - - /// - /// Handle logic similar between a normal mail unlock and an emag - /// frying out the lock. - /// - private void UnlockMail(EntityUid uid, MailComponent component) - { - component.IsLocked = false; - UpdateAntiTamperVisuals(uid, false); - - if (component.IsPriority) - { - // This is a successful delivery. Keep the failure timer from triggering. - if (component.priorityCancelToken != null) - component.priorityCancelToken.Cancel(); - - // The priority tape is visually considered to be a part of the - // anti-tamper lock, so remove that too. - _appearanceSystem.SetData(uid, MailVisuals.IsPriority, false); - - // The examination code depends on this being false to not show - // the priority tape description anymore. - component.IsPriority = false; - } - } - - /// - /// Check the ID against the mail's lock - /// - private void OnAfterInteractUsing(EntityUid uid, MailComponent component, AfterInteractUsingEvent args) - { - if (!args.CanReach || !component.IsLocked) - return; - - if (!TryComp(uid, out var access)) - return; - - IdCardComponent? idCard = null; // We need an ID card. - - if (HasComp(args.Used)) /// Can we find it in a PDA if the user is using that? - { - _idCardSystem.TryGetIdCard(args.Used, out var pdaID); - idCard = pdaID; - } - - if (HasComp(args.Used)) /// Or are they using an id card directly? - idCard = Comp(args.Used); - - if (idCard == null) /// Return if we still haven't found an id card. - return; - - if (!HasComp(uid)) - { - if (idCard.FullName != component.Recipient || idCard.JobTitle != component.RecipientJob) - { - _popupSystem.PopupEntity(Loc.GetString("mail-recipient-mismatch"), uid, args.User); - return; - } - - if (!_accessSystem.IsAllowed(uid, args.User)) - { - _popupSystem.PopupEntity(Loc.GetString("mail-invalid-access"), uid, args.User); - return; - } - } - - UnlockMail(uid, component); - - if (!component.IsProfitable) - { - _popupSystem.PopupEntity(Loc.GetString("mail-unlocked"), uid, args.User); - return; - } - - _popupSystem.PopupEntity(Loc.GetString("mail-unlocked-reward", ("bounty", component.Bounty)), uid, args.User); - - component.IsProfitable = false; - - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var station, out var account)) - { - if (_stationSystem.GetOwningStation(uid) != station) - continue; - - _cargoSystem.UpdateBankAccount(station, account, component.Bounty); - return; - } - } - - private void OnExamined(EntityUid uid, MailComponent component, ExaminedEvent args) - { - if (!args.IsInDetailsRange) - { - args.PushMarkup(Loc.GetString("mail-desc-far")); - return; - } - - args.PushMarkup(Loc.GetString("mail-desc-close", ("name", component.Recipient), ("job", component.RecipientJob))); - - if (component.IsFragile) - args.PushMarkup(Loc.GetString("mail-desc-fragile")); - - if (component.IsPriority) - { - if (component.IsProfitable) - args.PushMarkup(Loc.GetString("mail-desc-priority")); - else - args.PushMarkup(Loc.GetString("mail-desc-priority-inactive")); - } - } - - /// - /// Penalize a station for a failed delivery. - /// - /// - /// This will mark a parcel as no longer being profitable, which will - /// prevent multiple failures on different conditions for the same - /// delivery. - /// - /// The standard penalization is breaking the anti-tamper lock, - /// but this allows a delivery to fail for other reasons too - /// while having a generic function to handle different messages. - /// - public void PenalizeStationFailedDelivery(EntityUid uid, MailComponent component, string localizationString) - { - if (!component.IsProfitable) - return; - - _chatSystem.TrySendInGameICMessage(uid, Loc.GetString(localizationString, ("credits", component.Penalty)), InGameICChatType.Speak, false); - _audioSystem.PlayPvs(component.PenaltySound, uid); - - component.IsProfitable = false; - - if (component.IsPriority) - _appearanceSystem.SetData(uid, MailVisuals.IsPriorityInactive, true); - - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var station, out var account)) - { - if (_stationSystem.GetOwningStation(uid) != station) - continue; - - _cargoSystem.UpdateBankAccount(station, account, component.Penalty); - return; - } - } - - private void OnDestruction(EntityUid uid, MailComponent component, DestructionEventArgs args) - { - if (component.IsLocked) - PenalizeStationFailedDelivery(uid, component, "mail-penalty-lock"); - - if (component.IsEnabled) - OpenMail(uid, component); - - UpdateAntiTamperVisuals(uid, false); - } - - private void OnDamage(EntityUid uid, MailComponent component, DamageChangedEvent args) - { - if (args.DamageDelta == null) - return; - - if (!_containerSystem.TryGetContainer(uid, "contents", out var contents)) - return; - - // Transfer damage to the contents. - // This should be a general-purpose feature for all containers in the future. - foreach (var entity in contents.ContainedEntities.ToArray()) - { - _damageableSystem.TryChangeDamage(entity, args.DamageDelta); - } - } - - private void OnBreak(EntityUid uid, MailComponent component, BreakageEventArgs args) - { - _appearanceSystem.SetData(uid, MailVisuals.IsBroken, true); - - if (component.IsFragile) - PenalizeStationFailedDelivery(uid, component, "mail-penalty-fragile"); - } - - private void OnMailEmagged(EntityUid uid, MailComponent component, ref GotEmaggedEvent args) - { - if (!component.IsLocked) - return; - - UnlockMail(uid, component); - - _popupSystem.PopupEntity(Loc.GetString("mail-unlocked-by-emag"), uid, args.UserUid); - - _audioSystem.PlayPvs(component.EmagSound, uid, AudioParams.Default.WithVolume(4)); - component.IsProfitable = false; - args.Handled = true; - } - - /// - /// Returns true if the given entity is considered fragile for delivery. - /// - public bool IsEntityFragile(EntityUid uid, int fragileDamageThreshold) - { - // It takes damage on falling. - if (HasComp(uid)) - return true; - - // It can be spilled easily and has something to spill. - if (HasComp(uid) - && TryComp(uid, out var openable) - && !_openable.IsClosed(uid, null, openable) - && _solutionContainerSystem.PercentFull(uid) > 0) - return true; - - // It might be made of non-reinforced glass. - if (TryComp(uid, out DamageableComponent? damageableComponent) - && damageableComponent.DamageModifierSetId == "Glass") - return true; - - // Fallback: It breaks or is destroyed in less than a damage - // threshold dictated by the teleporter. - if (TryComp(uid, out DestructibleComponent? destructibleComp)) - { - foreach (var threshold in destructibleComp.Thresholds) - { - if (threshold.Trigger is DamageTrigger trigger - && trigger.Damage < fragileDamageThreshold) - { - foreach (var behavior in threshold.Behaviors) - { - if (behavior is DoActsBehavior doActs) - { - if (doActs.Acts.HasFlag(ThresholdActs.Breakage) - || doActs.Acts.HasFlag(ThresholdActs.Destruction)) - { - return true; - } - } - } - } - } - } - - return false; - } - - public bool TryMatchJobTitleToDepartment(string jobTitle, [NotNullWhen(true)] out string? jobDepartment) - { - foreach (var department in _prototypeManager.EnumeratePrototypes()) - { - foreach (var role in department.Roles) - { - if (_prototypeManager.TryIndex(role, out JobPrototype? _jobPrototype) - && _jobPrototype.LocalizedName == jobTitle) - { - jobDepartment = department.ID; - return true; - } - } - } - - jobDepartment = null; - return false; - } - - public bool TryMatchJobTitleToPrototype(string jobTitle, [NotNullWhen(true)] out JobPrototype? jobPrototype) - { - foreach (var job in _prototypeManager.EnumeratePrototypes()) - { - if (job.LocalizedName == jobTitle) - { - jobPrototype = job; - return true; - } - } - - jobPrototype = null; - return false; - } - - /// - /// Handle all the gritty details particular to a new mail entity. - /// - /// - /// This is separate mostly so the unit tests can get to it. - /// - public void SetupMail(EntityUid uid, MailTeleporterComponent component, MailRecipient recipient) - { - var mailComp = EnsureComp(uid); - - var container = _containerSystem.EnsureContainer(uid, "contents"); - foreach (var item in EntitySpawnCollection.GetSpawns(mailComp.Contents, _random)) - { - var entity = EntityManager.SpawnEntity(item, Transform(uid).Coordinates); - if (!_containerSystem.Insert(entity, container)) - { - _sawmill.Error($"Can't insert {ToPrettyString(entity)} into new mail delivery {ToPrettyString(uid)}! Deleting it."); - QueueDel(entity); - } - else if (!mailComp.IsFragile && IsEntityFragile(entity, component.FragileDamageThreshold)) - { - mailComp.IsFragile = true; - } - } - - if (_random.Prob(component.PriorityChance)) - mailComp.IsPriority = true; - - // This needs to override both the random probability and the - // entity prototype, so this is fine. - if (!recipient.MayReceivePriorityMail) - mailComp.IsPriority = false; - - mailComp.RecipientJob = recipient.Job; - mailComp.Recipient = recipient.Name; - - if (mailComp.IsFragile) - { - mailComp.Bounty += component.FragileBonus; - mailComp.Penalty += component.FragileMalus; - _appearanceSystem.SetData(uid, MailVisuals.IsFragile, true); - } - - if (mailComp.IsPriority) - { - mailComp.Bounty += component.PriorityBonus; - mailComp.Penalty += component.PriorityMalus; - _appearanceSystem.SetData(uid, MailVisuals.IsPriority, true); - - mailComp.priorityCancelToken = new CancellationTokenSource(); - - Timer.Spawn((int) component.priorityDuration.TotalMilliseconds, - () => PenalizeStationFailedDelivery(uid, mailComp, "mail-penalty-expired"), - mailComp.priorityCancelToken.Token); - } - - _appearanceSystem.SetData(uid, MailVisuals.JobIcon, recipient.JobIcon); - - _metaDataSystem.SetEntityName(uid, Loc.GetString("mail-item-name-addressed", - ("recipient", recipient.Name))); - - var accessReader = EnsureComp(uid); - accessReader.AccessLists.Add(recipient.AccessTags); - } - - /// - /// Return the parcels waiting for delivery. - /// - /// The mail teleporter to check. - public List GetUndeliveredParcels(EntityUid uid) - { - // An alternative solution would be to keep a list of the unopened - // parcels spawned by the teleporter and see if they're not carried - // by someone, but this is simple, and simple is good. - List undeliveredParcels = new(); - foreach (var entityInTile in TurfHelpers.GetEntitiesInTile(Transform(uid).Coordinates, LookupFlags.Dynamic | LookupFlags.Sundries)) - { - if (HasComp(entityInTile)) - undeliveredParcels.Add(entityInTile); - } - return undeliveredParcels; - } - - /// - /// Return how many parcels are waiting for delivery. - /// - /// The mail teleporter to check. - public uint GetUndeliveredParcelCount(EntityUid uid) - { - return (uint) GetUndeliveredParcels(uid).Count(); - } - - /// - /// Try to match a mail receiver to a mail teleporter. - /// - public bool TryGetMailTeleporterForReceiver(MailReceiverComponent receiver, [NotNullWhen(true)] out MailTeleporterComponent? teleporterComponent) - { - foreach (var mailTeleporter in EntityQuery()) - { - if (_stationSystem.GetOwningStation(receiver.Owner) == _stationSystem.GetOwningStation(mailTeleporter.Owner)) - { - teleporterComponent = mailTeleporter; - return true; - } - } - - teleporterComponent = null; - return false; - } - - /// - /// Try to construct a recipient struct for a mail parcel based on a receiver. - /// - public bool TryGetMailRecipientForReceiver(MailReceiverComponent receiver, [NotNullWhen(true)] out MailRecipient? recipient) - { - // Because of the way this works, people are not considered - // candidates for mail if there is no valid PDA or ID in their slot - // or active hand. A better future solution might be checking the - // station records, possibly cross-referenced with the medical crew - // scanner to look for living recipients. TODO - - if (_idCardSystem.TryFindIdCard(receiver.Owner, out var idCard) - && TryComp(idCard.Owner, out var access) - && idCard.Comp.FullName != null - && idCard.Comp.JobTitle != null) - { - var accessTags = access.Tags; - - var mayReceivePriorityMail = !(_mindSystem.GetMind(receiver.Owner) == null); - - recipient = new MailRecipient(idCard.Comp.FullName, - idCard.Comp.JobTitle, - idCard.Comp.JobIcon, - accessTags, - mayReceivePriorityMail); - - return true; - } - - recipient = null; - return false; - } - - /// - /// Get the list of valid mail recipients for a mail teleporter. - /// - public List GetMailRecipientCandidates(EntityUid uid) - { - List candidateList = new(); - - foreach (var receiver in EntityQuery()) - { - if (_stationSystem.GetOwningStation(receiver.Owner) != _stationSystem.GetOwningStation(uid)) - continue; - - if (TryGetMailRecipientForReceiver(receiver, out MailRecipient? recipient)) - candidateList.Add(recipient.Value); - } - - return candidateList; - } - - /// - /// Handle the spawning of all the mail for a mail teleporter. - /// - public void SpawnMail(EntityUid uid, MailTeleporterComponent? component = null) - { - if (!Resolve(uid, ref component)) - { - _sawmill.Error($"Tried to SpawnMail on {ToPrettyString(uid)} without a valid MailTeleporterComponent!"); - return; - } - - if (GetUndeliveredParcelCount(uid) >= component.MaximumUndeliveredParcels) - return; - - var candidateList = GetMailRecipientCandidates(uid); - - if (candidateList.Count <= 0) - { - _sawmill.Error("List of mail candidates was empty!"); - return; - } - - if (!_prototypeManager.TryIndex(component.MailPool, out var pool)) - { - _sawmill.Error($"Can't index {ToPrettyString(uid)}'s MailPool {component.MailPool}!"); - return; - } - - for (int i = 0; - i < component.MinimumDeliveriesPerTeleport + candidateList.Count / component.CandidatesPerDelivery; - i++) - { - var candidate = _random.Pick(candidateList); - var possibleParcels = new Dictionary(pool.Everyone); - - if (TryMatchJobTitleToPrototype(candidate.Job, out JobPrototype? jobPrototype) - && pool.Jobs.TryGetValue(jobPrototype.ID, out Dictionary? jobParcels)) - { - possibleParcels = possibleParcels.Union(jobParcels) - .GroupBy(g => g.Key) - .ToDictionary(pair => pair.Key, pair => pair.First().Value); - } - - if (TryMatchJobTitleToDepartment(candidate.Job, out string? department) - && pool.Departments.TryGetValue(department, out Dictionary? departmentParcels)) - { - possibleParcels = possibleParcels.Union(departmentParcels) - .GroupBy(g => g.Key) - .ToDictionary(pair => pair.Key, pair => pair.First().Value); - } - - var accumulated = 0f; - var randomPoint = _random.NextFloat(possibleParcels.Values.Sum()); - string? chosenParcel = null; - foreach (var (key, weight) in possibleParcels) - { - accumulated += weight; - if (accumulated >= randomPoint) - { - chosenParcel = key; - break; - } - } - - if (chosenParcel == null) - { - _sawmill.Error($"MailSystem wasn't able to find a deliverable parcel for {candidate.Name}, {candidate.Job}!"); - return; - } - - var mail = EntityManager.SpawnEntity(chosenParcel, Transform(uid).Coordinates); - SetupMail(mail, component, candidate); - } - - if (_containerSystem.TryGetContainer(uid, "queued", out var queued)) - _containerSystem.EmptyContainer(queued); - - _audioSystem.PlayPvs(component.TeleportSound, uid); - } - - public void OpenMail(EntityUid uid, MailComponent? component = null, EntityUid? user = null) - { - if (!Resolve(uid, ref component)) - return; - - _audioSystem.PlayPvs(component.OpenSound, uid); - - if (user != null) - _handsSystem.TryDrop((EntityUid) user); - - if (!_containerSystem.TryGetContainer(uid, "contents", out var contents)) - { - // I silenced this error because it fails non deterministically in tests and doesn't seem to effect anything else. - // _sawmill.Error($"Mail {ToPrettyString(uid)} was missing contents container!"); - return; - } - - foreach (var entity in contents.ContainedEntities.ToArray()) - { - _handsSystem.PickupOrDrop(user, entity); - } - - _tagSystem.AddTag(uid, "Trash"); - _tagSystem.AddTag(uid, "Recyclable"); - component.IsEnabled = false; - UpdateMailTrashState(uid, true); - } - - private void UpdateAntiTamperVisuals(EntityUid uid, bool isLocked) - { - _appearanceSystem.SetData(uid, MailVisuals.IsLocked, isLocked); - } - - private void UpdateMailTrashState(EntityUid uid, bool isTrash) - { - _appearanceSystem.SetData(uid, MailVisuals.IsTrash, isTrash); - } - } - - public struct MailRecipient( - string name, - string job, - string jobIcon, - HashSet> accessTags, - bool mayReceivePriorityMail) - { - public string Name = name; - public string Job = job; - public string JobIcon = jobIcon; - public HashSet> AccessTags = accessTags; - public bool MayReceivePriorityMail = mayReceivePriorityMail; - } -} diff --git a/Content.Server/Nyanotrasen/Objectives/Components/BecomePsionicConditionComponent.cs b/Content.Server/Nyanotrasen/Objectives/Components/BecomePsionicConditionComponent.cs deleted file mode 100644 index 3b677bab2d..0000000000 --- a/Content.Server/Nyanotrasen/Objectives/Components/BecomePsionicConditionComponent.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Content.Server.Objectives.Systems; - -namespace Content.Server.Objectives.Components; - -/// -/// Requires that the player dies to be complete. -/// -[RegisterComponent, Access(typeof(BecomePsionicConditionSystem))] -public sealed partial class BecomePsionicConditionComponent : Component -{ -} \ No newline at end of file diff --git a/Content.Server/Nyanotrasen/Objectives/Systems/BecomePsionicConditionSystem.cs b/Content.Server/Nyanotrasen/Objectives/Systems/BecomePsionicConditionSystem.cs deleted file mode 100644 index d090c320a4..0000000000 --- a/Content.Server/Nyanotrasen/Objectives/Systems/BecomePsionicConditionSystem.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Content.Shared.Abilities.Psionics; -using Content.Server.Objectives.Components; -using Content.Shared.Mind; -using Content.Shared.Objectives.Components; - -namespace Content.Server.Objectives.Systems -{ - public sealed class BecomePsionicConditionSystem : EntitySystem - { - private EntityQuery _metaQuery; - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnGetProgress); - } - - private void OnGetProgress(EntityUid uid, BecomePsionicConditionComponent comp, ref ObjectiveGetProgressEvent args) - { - args.Progress = GetProgress(args.Mind); - } - - private float GetProgress(MindComponent mind) - { - var entMan = IoCManager.Resolve(); - if (HasComp(mind.CurrentEntity)) - return 1; - return 0; - } - } -} diff --git a/Content.Server/Nyanotrasen/Psionics/NPC/PsionicNpcCombatSystem.cs b/Content.Server/Nyanotrasen/Psionics/NPC/PsionicNpcCombatSystem.cs new file mode 100644 index 0000000000..9caef36a75 --- /dev/null +++ b/Content.Server/Nyanotrasen/Psionics/NPC/PsionicNpcCombatSystem.cs @@ -0,0 +1,51 @@ +using Content.Shared.Abilities.Psionics; +using Content.Shared.Actions; +using Content.Server.NPC.Events; +using Content.Server.NPC.Components; +using Content.Server.Abilities.Psionics; +using Content.Shared.Psionics; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; +using Robust.Shared.Utility; + +namespace Content.Server.Psionics.NPC; + +// TODO this is nyanotrasen shitcode. It works, but it needs to be refactored to be more generic. +public sealed class PsionicNpcCombatSystem : EntitySystem +{ + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly IPrototypeManager _protoMan = default!; + [Dependency] private readonly SharedActionsSystem _actions = default!; + + private static readonly ProtoId NoosphericZapProto = "NoosphericZapPower"; + private PsionicPowerPrototype NoosphericZap = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(ZapCombat); + + NoosphericZap = _protoMan.Index(NoosphericZapProto); + DebugTools.Assert(NoosphericZap.Actions.Count == 1, "I can't account for this, so it's your problem now"); + } + + private void ZapCombat(Entity ent, ref NPCSteeringEvent args) + { + PsionicComponent? psionics = null; + if (!Resolve(ent, ref psionics, logMissing: true) + || !psionics.Actions.TryGetValue(NoosphericZap.Actions[0], out var action) + || action is null) + return; + + var actionTarget = Comp(action.Value); + if (actionTarget.Cooldown is {} cooldown && cooldown.End > _timing.CurTime + || !TryComp(ent, out var combat) + || !_actions.ValidateEntityTarget(ent, combat.Target, (action.Value, actionTarget)) + || actionTarget.Event is not {} ev) + return; + + ev.Target = combat.Target; + _actions.PerformAction(ent, null, action.Value, actionTarget, ev, _timing.CurTime, predicted: false); + } +} diff --git a/Content.Server/Nyanotrasen/ReverseEngineering/ReverseEngineeringSystem.cs b/Content.Server/Nyanotrasen/ReverseEngineering/ReverseEngineeringSystem.cs index 85fa160d8e..88da3093d7 100644 --- a/Content.Server/Nyanotrasen/ReverseEngineering/ReverseEngineeringSystem.cs +++ b/Content.Server/Nyanotrasen/ReverseEngineering/ReverseEngineeringSystem.cs @@ -187,22 +187,18 @@ private void UpdateUserInterface(EntityUid uid, ReverseEngineeringMachineCompone if (!Resolve(uid, ref component)) return; - if (!_ui.TryGetUi(uid, ReverseEngineeringMachineUiKey.Key, out var bui)) - return; - EntityUid? item = component.CurrentItem; if (component.CachedMessage == null) component.CachedMessage = GetReverseEngineeringScanMessage(component); - var totalTime = TimeSpan.Zero; var scanning = TryComp(uid, out var active); - var canScan = (item != null && !scanning); + var canScan = item != null && !scanning; var remaining = active != null ? _timing.CurTime - active.StartTime : TimeSpan.Zero; EntityManager.TryGetNetEntity(item, out var netItem); var state = new ReverseEngineeringMachineScanUpdateState(netItem, canScan, component.CachedMessage, scanning, component.SafetyOn, component.AutoScan, component.Progress, remaining, component.AnalysisDuration); - _ui.SetUiState(bui, state); + _ui.SetUiState(uid, ReverseEngineeringMachineUiKey.Key, state); } private ReverseEngineeringTickResult Roll(ReverseEngineeringMachineComponent component, out int actualRoll) diff --git a/Content.Server/Nyanotrasen/StationEvents/Events/MidRoundAntagRule.cs b/Content.Server/Nyanotrasen/StationEvents/Events/MidRoundAntagRule.cs index 7abbdcdab3..94a488bd84 100644 --- a/Content.Server/Nyanotrasen/StationEvents/Events/MidRoundAntagRule.cs +++ b/Content.Server/Nyanotrasen/StationEvents/Events/MidRoundAntagRule.cs @@ -1,3 +1,4 @@ +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.StationEvents.Components; using Robust.Shared.Random; diff --git a/Content.Server/Objectives/Components/CodeConditionSystem.cs b/Content.Server/Objectives/Components/CodeConditionSystem.cs new file mode 100644 index 0000000000..581098c3f7 --- /dev/null +++ b/Content.Server/Objectives/Components/CodeConditionSystem.cs @@ -0,0 +1,17 @@ +using Content.Server.Objectives.Systems; + +namespace Content.Server.Objectives.Components; + +/// +/// An objective that is set to complete by code in another system. +/// Use to check and set this. +/// +[RegisterComponent, Access(typeof(CodeConditionSystem))] +public sealed partial class CodeConditionComponent : Component +{ + /// + /// Whether the objective is complete or not. + /// + [DataField] + public bool Completed; +} diff --git a/Content.Server/Objectives/Components/SpiderChargeConditionComponent.cs b/Content.Server/Objectives/Components/SpiderChargeConditionComponent.cs index 368c9f27ed..9983b35969 100644 --- a/Content.Server/Objectives/Components/SpiderChargeConditionComponent.cs +++ b/Content.Server/Objectives/Components/SpiderChargeConditionComponent.cs @@ -9,9 +9,6 @@ namespace Content.Server.Objectives.Components; [RegisterComponent, Access(typeof(NinjaConditionsSystem), typeof(SpiderChargeSystem), typeof(SpaceNinjaSystem))] public sealed partial class SpiderChargeConditionComponent : Component { - [DataField, ViewVariables(VVAccess.ReadWrite)] - public bool Detonated; - /// /// Warp point that the spider charge has to target /// diff --git a/Content.Server/Objectives/Components/TerminatorTargetOverrideComponent.cs b/Content.Server/Objectives/Components/TerminatorTargetOverrideComponent.cs deleted file mode 100644 index c66ff55f05..0000000000 --- a/Content.Server/Objectives/Components/TerminatorTargetOverrideComponent.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Content.Server.Objectives.Systems; - -namespace Content.Server.Objectives.Components; - -/// -/// Sets this objective's target to the exterminator's target override, if it has one. -/// If not it will be random. -/// -[RegisterComponent, Access(typeof(TerminatorTargetOverrideSystem))] -public sealed partial class TerminatorTargetOverrideComponent : Component -{ -} diff --git a/Content.Server/Objectives/Components/TerrorConditionComponent.cs b/Content.Server/Objectives/Components/TerrorConditionComponent.cs deleted file mode 100644 index acd3218ad4..0000000000 --- a/Content.Server/Objectives/Components/TerrorConditionComponent.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Content.Server.Objectives.Systems; -using Content.Shared.Ninja.Systems; - -namespace Content.Server.Objectives.Components; - -/// -/// Requires that the player is a ninja and has called in a threat. -/// -[RegisterComponent, Access(typeof(NinjaConditionsSystem), typeof(SharedSpaceNinjaSystem))] -public sealed partial class TerrorConditionComponent : Component -{ - /// - /// Whether the comms console has been hacked - /// - [DataField("calledInThreat"), ViewVariables(VVAccess.ReadWrite)] - public bool CalledInThreat; -} diff --git a/Content.Server/Objectives/ObjectivesSystem.cs b/Content.Server/Objectives/ObjectivesSystem.cs index 20205b8b72..47fe4eb5f8 100644 --- a/Content.Server/Objectives/ObjectivesSystem.cs +++ b/Content.Server/Objectives/ObjectivesSystem.cs @@ -1,10 +1,7 @@ using Content.Server.GameTicking; -using Content.Server.GameTicking.Rules.Components; -using Content.Server.Mind; using Content.Server.Shuttles.Systems; using Content.Shared.Cuffs.Components; using Content.Shared.Mind; -using Content.Shared.Mobs.Systems; using Content.Shared.Objectives.Components; using Content.Shared.Objectives.Systems; using Content.Shared.Random; @@ -12,7 +9,9 @@ using Robust.Shared.Prototypes; using Robust.Shared.Random; using System.Linq; +using Content.Server.GameTicking.Components; using System.Text; +using Robust.Server.Player; namespace Content.Server.Objectives; @@ -20,8 +19,8 @@ public sealed class ObjectivesSystem : SharedObjectivesSystem { [Dependency] private readonly GameTicker _gameTicker = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IPlayerManager _player = default!; [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly MindSystem _mind = default!; [Dependency] private readonly EmergencyShuttleSystem _emergencyShuttle = default!; public override void Initialize() @@ -179,7 +178,9 @@ private void AddSummary(StringBuilder result, string agent, List mind .ThenByDescending(x => x.completedObjectives); foreach (var (summary, _, _) in sortedAgents) + { result.AppendLine(summary); + } } public EntityUid? GetRandomObjective(EntityUid mindId, MindComponent mind, string objectiveGroupProto) @@ -244,8 +245,14 @@ private bool IsInCustody(EntityUid mindId, MindComponent? mind = null) return null; var name = mind.CharacterName; - _mind.TryGetSession(mindId, out var session); - var username = session?.Name; + var username = (string?) null; + + if (mind.OriginalOwnerUserId != null && + _player.TryGetPlayerData(mind.OriginalOwnerUserId.Value, out var sessionData)) + { + username = sessionData.UserName; + } + if (username != null) { diff --git a/Content.Server/Objectives/Systems/CodeConditionSystem.cs b/Content.Server/Objectives/Systems/CodeConditionSystem.cs new file mode 100644 index 0000000000..7ba312f4bb --- /dev/null +++ b/Content.Server/Objectives/Systems/CodeConditionSystem.cs @@ -0,0 +1,76 @@ +using Content.Server.Objectives.Components; +using Content.Shared.Objectives.Components; +using Content.Shared.Mind; +using Content.Shared.Mind.Components; + +namespace Content.Server.Objectives.Systems; + +/// +/// Handles progress and provides API for systems to use. +/// +public sealed class CodeConditionSystem : EntitySystem +{ + [Dependency] private readonly SharedMindSystem _mind = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnGetProgress); + } + + private void OnGetProgress(Entity ent, ref ObjectiveGetProgressEvent args) + { + args.Progress = ent.Comp.Completed ? 1f : 0f; + } + + /// + /// Returns whether an objective is completed. + /// + public bool IsCompleted(Entity ent) + { + if (!Resolve(ent, ref ent.Comp)) + return false; + + return ent.Comp.Completed; + } + + /// + /// Returns true if a mob's objective with a certain prototype is completed. + /// + public bool IsCompleted(Entity mob, string prototype) + { + if (_mind.GetMind(mob, mob.Comp) is not {} mindId) + return false; + + if (!_mind.TryFindObjective(mindId, prototype, out var obj)) + return false; + + return IsCompleted(obj.Value); + } + + /// + /// Sets an objective's completed field. + /// + public void SetCompleted(Entity ent, bool completed = true) + { + if (!Resolve(ent, ref ent.Comp)) + return; + + ent.Comp.Completed = completed; + } + + /// + /// Sets a mob's objective to complete. + /// + public void SetCompleted(Entity mob, string prototype, bool completed = true) + { + if (_mind.GetMind(mob, mob.Comp) is not {} mindId) + return; + + if (!_mind.TryFindObjective(mindId, prototype, out var obj)) + return; + + SetCompleted(obj.Value, completed); + } +} diff --git a/Content.Server/Objectives/Systems/NinjaConditionsSystem.cs b/Content.Server/Objectives/Systems/NinjaConditionsSystem.cs index 888a365a5d..47c54b937a 100644 --- a/Content.Server/Objectives/Systems/NinjaConditionsSystem.cs +++ b/Content.Server/Objectives/Systems/NinjaConditionsSystem.cs @@ -23,11 +23,8 @@ public override void Initialize() SubscribeLocalEvent(OnSpiderChargeRequirementCheck); SubscribeLocalEvent(OnSpiderChargeAfterAssign); - SubscribeLocalEvent(OnSpiderChargeGetProgress); SubscribeLocalEvent(OnStealResearchGetProgress); - - SubscribeLocalEvent(OnTerrorGetProgress); } // doorjack @@ -88,11 +85,6 @@ private void OnSpiderChargeAfterAssign(EntityUid uid, SpiderChargeConditionCompo _metaData.SetEntityName(uid, title, args.Meta); } - private void OnSpiderChargeGetProgress(EntityUid uid, SpiderChargeConditionComponent comp, ref ObjectiveGetProgressEvent args) - { - args.Progress = comp.Detonated ? 1f : 0f; - } - // steal research private void OnStealResearchGetProgress(EntityUid uid, StealResearchConditionComponent comp, ref ObjectiveGetProgressEvent args) @@ -108,9 +100,4 @@ private float StealResearchProgress(StealResearchConditionComponent comp, int ta return MathF.Min(comp.DownloadedNodes.Count / (float) target, 1f); } - - private void OnTerrorGetProgress(EntityUid uid, TerrorConditionComponent comp, ref ObjectiveGetProgressEvent args) - { - args.Progress = comp.CalledInThreat ? 1f : 0f; - } } diff --git a/Content.Server/Objectives/Systems/TerminatorTargetOverrideSystem.cs b/Content.Server/Objectives/Systems/TerminatorTargetOverrideSystem.cs deleted file mode 100644 index 0a81c2810e..0000000000 --- a/Content.Server/Objectives/Systems/TerminatorTargetOverrideSystem.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Content.Server.Objectives.Components; -using Content.Server.Terminator.Components; -using Content.Shared.Mind; -using Content.Shared.Objectives.Components; - -namespace Content.Server.Objectives.Systems; - -/// -/// Handles copying the exterminator's target override to this objective. -/// -public sealed class TerminatorTargetOverrideSystem : EntitySystem -{ - [Dependency] private readonly TargetObjectiveSystem _target = default!; - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnAssigned); - } - - private void OnAssigned(EntityUid uid, TerminatorTargetOverrideComponent comp, ref ObjectiveAssignedEvent args) - { - if (args.Mind.OwnedEntity == null) - { - args.Cancelled = true; - return; - } - - var user = args.Mind.OwnedEntity.Value; - if (!TryComp(user, out var terminator)) - { - args.Cancelled = true; - return; - } - - // this exterminator has a target override so set its objective target accordingly - if (terminator.Target != null) - _target.SetTarget(uid, terminator.Target.Value); - } -} diff --git a/Content.Server/PAI/PAISystem.cs b/Content.Server/PAI/PAISystem.cs index e9505b5e6f..091afb1557 100644 --- a/Content.Server/PAI/PAISystem.cs +++ b/Content.Server/PAI/PAISystem.cs @@ -102,13 +102,15 @@ public void PAITurningOff(EntityUid uid) { // Close the instrument interface if it was open // before closing - if (HasComp(uid) && TryComp(uid, out var actor)) + if (HasComp(uid)) { - _instrumentSystem.ToggleInstrumentUi(uid, actor.PlayerSession); + _instrumentSystem.ToggleInstrumentUi(uid, uid); } // Stop instrument - if (TryComp(uid, out var instrument)) _instrumentSystem.Clean(uid, instrument); + if (TryComp(uid, out var instrument)) + _instrumentSystem.Clean(uid, instrument); + if (TryComp(uid, out var metadata)) { var proto = metadata.EntityPrototype; diff --git a/Content.Server/PDA/PdaSystem.cs b/Content.Server/PDA/PdaSystem.cs index a343607196..d4934ee24e 100644 --- a/Content.Server/PDA/PdaSystem.cs +++ b/Content.Server/PDA/PdaSystem.cs @@ -4,7 +4,6 @@ using Content.Server.DeviceNetwork.Components; using Content.Server.Instruments; using Content.Server.Light.EntitySystems; -using Content.Server.Light.Events; using Content.Server.PDA.Ringer; using Content.Server.Station.Systems; using Content.Server.Store.Components; @@ -12,7 +11,9 @@ using Content.Shared.Access.Components; using Content.Shared.CartridgeLoader; using Content.Shared.Chat; +using Content.Shared.Light; using Content.Shared.Light.Components; +using Content.Shared.Light.EntitySystems; using Content.Shared.PDA; using Robust.Server.Containers; using Robust.Server.GameObjects; @@ -41,6 +42,7 @@ public override void Initialize() SubscribeLocalEvent(OnLightToggle); // UI Events: + SubscribeLocalEvent(OnPdaOpen); SubscribeLocalEvent(OnUiMessage); SubscribeLocalEvent(OnUiMessage); SubscribeLocalEvent(OnUiMessage); @@ -145,7 +147,7 @@ public void UpdatePdaUi(EntityUid uid, PdaComponent? pda = null) if (!Resolve(uid, ref pda, false)) return; - if (!_ui.TryGetUi(uid, PdaUiKey.Key, out var ui)) + if (!_ui.HasUi(uid, PdaUiKey.Key)) return; var address = GetDeviceNetAddress(uid); @@ -182,7 +184,15 @@ public void UpdatePdaUi(EntityUid uid, PdaComponent? pda = null) hasInstrument, address); - _ui.SetUiState(ui, state); + _ui.SetUiState(uid, PdaUiKey.Key, state); + } + + private void OnPdaOpen(Entity ent, ref BoundUIOpenedEvent args) + { + if (!PdaUiKey.Key.Equals(args.UiKey)) + return; + + UpdatePdaUi(ent.Owner, ent.Comp); } private void OnUiMessage(EntityUid uid, PdaComponent pda, PdaRequestUpdateInterfaceMessage msg) @@ -198,8 +208,9 @@ private void OnUiMessage(EntityUid uid, PdaComponent pda, PdaToggleFlashlightMes if (!PdaUiKey.Key.Equals(msg.UiKey)) return; - if (TryComp(uid, out var flashlight)) - _unpoweredFlashlight.ToggleLight(uid, flashlight); + // TODO PREDICTION + // When moving this to shared, fill in the user field + _unpoweredFlashlight.TryToggleLight(uid, user: null); } private void OnUiMessage(EntityUid uid, PdaComponent pda, PdaShowRingtoneMessage msg) @@ -208,7 +219,7 @@ private void OnUiMessage(EntityUid uid, PdaComponent pda, PdaShowRingtoneMessage return; if (HasComp(uid)) - _ringer.ToggleRingerUI(uid, msg.Session); + _ringer.ToggleRingerUI(uid, msg.Actor); } private void OnUiMessage(EntityUid uid, PdaComponent pda, PdaShowMusicMessage msg) @@ -217,7 +228,7 @@ private void OnUiMessage(EntityUid uid, PdaComponent pda, PdaShowMusicMessage ms return; if (TryComp(uid, out var instrument)) - _instrument.ToggleInstrumentUi(uid, msg.Session, instrument); + _instrument.ToggleInstrumentUi(uid, msg.Actor, instrument); } private void OnUiMessage(EntityUid uid, PdaComponent pda, PdaShowUplinkMessage msg) @@ -227,7 +238,7 @@ private void OnUiMessage(EntityUid uid, PdaComponent pda, PdaShowUplinkMessage m // check if its locked again to prevent malicious clients opening locked uplinks if (TryComp(uid, out var store) && IsUnlocked(uid)) - _store.ToggleUi(msg.Session.AttachedEntity!.Value, uid, store); + _store.ToggleUi(msg.Actor, uid, store); } private void OnUiMessage(EntityUid uid, PdaComponent pda, PdaLockUplinkMessage msg) diff --git a/Content.Server/PDA/Ringer/RingerSystem.cs b/Content.Server/PDA/Ringer/RingerSystem.cs index f95725873a..a10544d696 100644 --- a/Content.Server/PDA/Ringer/RingerSystem.cs +++ b/Content.Server/PDA/Ringer/RingerSystem.cs @@ -81,7 +81,10 @@ private void UpdateRingerUserInterfaceDriver(EntityUid uid, RingerComponent ring private void OnSetRingtone(EntityUid uid, RingerComponent ringer, RingerSetRingtoneMessage args) { - ref var lastSetAt = ref CollectionsMarshal.GetValueRefOrAddDefault(_lastSetRingtoneAt, args.Session.UserId, out var exists); + if (!TryComp(args.Actor, out ActorComponent? actorComp)) + return; + + ref var lastSetAt = ref CollectionsMarshal.GetValueRefOrAddDefault(_lastSetRingtoneAt, actorComp.PlayerSession.UserId, out var exists); // Delay on the client is 0.333, 0.25 is still enough and gives some leeway in case of small time differences if (exists && lastSetAt > _gameTiming.CurTime - TimeSpan.FromMilliseconds(250)) @@ -111,7 +114,7 @@ private void OnSetUplinkRingtone(EntityUid uid, RingerUplinkComponent uplink, re // can't keep store open after locking it if (!uplink.Unlocked) - _ui.TryCloseAll(uid, StoreUiKey.Key); + _ui.CloseUi(uid, StoreUiKey.Key); // no saving the code to prevent meta click set on sus guys pda -> wewlad args.Handled = true; @@ -130,7 +133,7 @@ public void LockUplink(EntityUid uid, RingerUplinkComponent? uplink) return; uplink.Unlocked = false; - _ui.TryCloseAll(uid, StoreUiKey.Key); + _ui.CloseUi(uid, StoreUiKey.Key); } public void RandomizeRingtone(EntityUid uid, RingerComponent ringer, MapInitEvent args) @@ -181,14 +184,12 @@ private bool UpdateRingerRingtone(EntityUid uid, RingerComponent ringer, Note[] private void UpdateRingerUserInterface(EntityUid uid, RingerComponent ringer, bool isPlaying) { - if (_ui.TryGetUi(uid, RingerUiKey.Key, out var bui)) - _ui.SetUiState(bui, new RingerUpdateState(isPlaying, ringer.Ringtone)); + _ui.SetUiState(uid, RingerUiKey.Key, new RingerUpdateState(isPlaying, ringer.Ringtone)); } - public bool ToggleRingerUI(EntityUid uid, ICommonSession session) + public bool ToggleRingerUI(EntityUid uid, EntityUid actor) { - if (_ui.TryGetUi(uid, RingerUiKey.Key, out var bui)) - _ui.ToggleUi(bui, session); + _ui.TryToggleUi(uid, RingerUiKey.Key, actor); return true; } diff --git a/Content.Server/Paint/PaintSystem.cs b/Content.Server/Paint/PaintSystem.cs new file mode 100644 index 0000000000..bdda9ed887 --- /dev/null +++ b/Content.Server/Paint/PaintSystem.cs @@ -0,0 +1,202 @@ +using Content.Shared.Popups; +using Content.Shared.Paint; +using Content.Shared.Sprite; +using Content.Shared.DoAfter; +using Content.Shared.Interaction; +using Content.Server.Chemistry.Containers.EntitySystems; +using Robust.Shared.Audio.Systems; +using Content.Shared.Humanoid; +using Robust.Shared.Utility; +using Content.Shared.Verbs; +using Content.Shared.SubFloor; +using Content.Server.Nutrition.Components; +using Content.Shared.Inventory; +using Content.Server.Nutrition.EntitySystems; +using Content.Shared.Nutrition.EntitySystems; +using Content.Shared.Whitelist; +using Robust.Shared.Audio; + +namespace Content.Server.Paint; + +/// +/// Colors target and consumes reagent on each color success. +/// +public sealed class PaintSystem : SharedPaintSystem +{ + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly SolutionContainerSystem _solutionContainer = default!; + [Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; + [Dependency] private readonly InventorySystem _inventory = default!; + [Dependency] private readonly OpenableSystem _openable = default!; + + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnInteract); + SubscribeLocalEvent(OnPaint); + SubscribeLocalEvent>(OnPaintVerb); + } + + + private void OnInteract(EntityUid uid, PaintComponent component, AfterInteractEvent args) + { + if (!args.CanReach + || args.Target is not { Valid: true } target) + return; + + PrepPaint(uid, component, target, args.User); + } + + private void OnPaintVerb(EntityUid uid, PaintComponent component, GetVerbsEvent args) + { + if (!args.CanInteract || !args.CanAccess) + return; + + var paintText = Loc.GetString("paint-verb"); + + var verb = new UtilityVerb() + { + Act = () => + { + PrepPaint(uid, component, args.Target, args.User); + }, + + Text = paintText, + Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/paint.svg.192dpi.png")) + }; + args.Verbs.Add(verb); + } + + private void PrepPaint(EntityUid uid, PaintComponent component, EntityUid target, EntityUid user) => + _doAfterSystem.TryStartDoAfter( + new DoAfterArgs( + EntityManager, + user, + component.Delay, + new PaintDoAfterEvent(), + uid, + target: target, + used: uid) + { + BreakOnUserMove = true, + BreakOnTargetMove = true, + NeedHand = true, + BreakOnHandChange = true, + }); + + private void OnPaint(Entity entity, ref PaintDoAfterEvent args) + { + if (args.Target == null || args.Used == null || args.Handled || args.Cancelled || args.Target is not { Valid: true } target) + return; + + Paint(entity, target, args.User); + args.Handled = true; + } + + public void Paint(Entity entity, EntityUid target, EntityUid user) + { + if (!_openable.IsOpen(entity)) + { + _popup.PopupEntity(Loc.GetString("paint-closed", ("used", entity)), user, user, PopupType.Medium); + return; + } + + if (HasComp(target) || HasComp(target)) + { + _popup.PopupEntity(Loc.GetString("paint-failure-painted", ("target", target)), user, user, PopupType.Medium); + return; + } + + if (!entity.Comp.Whitelist?.IsValid(target, EntityManager) == true + || !entity.Comp.Blacklist?.IsValid(target, EntityManager) == false + || HasComp(target) || HasComp(target)) + { + _popup.PopupEntity(Loc.GetString("paint-failure", ("target", target)), user, user, PopupType.Medium); + return; + } + + if (CanPaint(entity, target)) + { + EnsureComp(target, out var paint); + EnsureComp(target); + + paint.Color = entity.Comp.Color; + _audio.PlayPvs(entity.Comp.Spray, entity); + paint.Enabled = true; + + // Paint any clothing the target is wearing + if (HasComp(target) + && _inventory.TryGetSlots(target, out var slotDefinitions)) + foreach (var slot in slotDefinitions) + { + if (!_inventory.TryGetSlotEntity(target, slot.Name, out var slotEnt) + || HasComp(slotEnt.Value) + || !entity.Comp.Whitelist?.IsValid(slotEnt.Value, EntityManager) != true + || !entity.Comp.Blacklist?.IsValid(slotEnt.Value, EntityManager) != false + || HasComp(slotEnt.Value) + || HasComp(slotEnt.Value)) + continue; + + EnsureComp(slotEnt.Value, out var slotToPaint); + EnsureComp(slotEnt.Value); + slotToPaint.Color = entity.Comp.Color; + _appearanceSystem.SetData(slotEnt.Value, PaintVisuals.Painted, true); + Dirty(slotEnt.Value, slotToPaint); + } + + _popup.PopupEntity(Loc.GetString("paint-success", ("target", target)), user, user, PopupType.Medium); + _appearanceSystem.SetData(target, PaintVisuals.Painted, true); + Dirty(target, paint); + return; + } + + if (!CanPaint(entity, target)) + _popup.PopupEntity(Loc.GetString("paint-empty", ("used", entity)), user, user, PopupType.Medium); + } + + public void Paint(EntityWhitelist whitelist, EntityWhitelist blacklist, EntityUid target, Color color) + { + if (!whitelist.IsValid(target, EntityManager) + || blacklist.IsValid(target, EntityManager)) + return; + + EnsureComp(target, out var paint); + EnsureComp(target); + + paint.Color = color; + paint.Enabled = true; + + if (HasComp(target) + && _inventory.TryGetSlots(target, out var slotDefinitions)) + foreach (var slot in slotDefinitions) + { + if (!_inventory.TryGetSlotEntity(target, slot.Name, out var slotEnt) + || !whitelist.IsValid(slotEnt.Value, EntityManager) + || blacklist.IsValid(slotEnt.Value, EntityManager)) + continue; + + EnsureComp(slotEnt.Value, out var slotToPaint); + EnsureComp(slotEnt.Value); + slotToPaint.Color = color; + _appearanceSystem.SetData(slotEnt.Value, PaintVisuals.Painted, true); + Dirty(slotEnt.Value, slotToPaint); + } + + _appearanceSystem.SetData(target, PaintVisuals.Painted, true); + Dirty(target, paint); + } + + private bool CanPaint(Entity reagent, EntityUid target) + { + if (HasComp(target) + || HasComp(target) + || !_solutionContainer.TryGetSolution(reagent.Owner, reagent.Comp.Solution, out _, out var solution)) + return false; + var quantity = solution.RemoveReagent(reagent.Comp.Reagent, reagent.Comp.ConsumptionUnit); + return (quantity > 0); + } +} diff --git a/Content.Server/Paper/PaperComponent.cs b/Content.Server/Paper/PaperComponent.cs index 556a197fd4..6c96b67acb 100644 --- a/Content.Server/Paper/PaperComponent.cs +++ b/Content.Server/Paper/PaperComponent.cs @@ -3,7 +3,7 @@ namespace Content.Server.Paper; -[NetworkedComponent, RegisterComponent] +[RegisterComponent] public sealed partial class PaperComponent : SharedPaperComponent { public PaperAction Mode; diff --git a/Content.Server/Paper/PaperSystem.cs b/Content.Server/Paper/PaperSystem.cs index 27c94313b2..d9202341d5 100644 --- a/Content.Server/Paper/PaperSystem.cs +++ b/Content.Server/Paper/PaperSystem.cs @@ -67,11 +67,7 @@ private void OnInit(EntityUid uid, PaperComponent paperComp, ComponentInit args) private void BeforeUIOpen(EntityUid uid, PaperComponent paperComp, BeforeActivatableUIOpenEvent args) { paperComp.Mode = PaperAction.Read; - - if (!TryComp(args.User, out var actor)) - return; - - UpdateUserInterface(uid, paperComp, actor.PlayerSession); + UpdateUserInterface(uid, paperComp); } private void OnExamined(EntityUid uid, PaperComponent paperComp, ExaminedEvent args) @@ -108,12 +104,10 @@ private void OnInteractUsing(EntityUid uid, PaperComponent paperComp, InteractUs { var writeEvent = new PaperWriteEvent(uid, args.User); RaiseLocalEvent(args.Used, ref writeEvent); - if (!TryComp(args.User, out var actor)) - return; paperComp.Mode = PaperAction.Write; - _uiSystem.TryOpen(uid, PaperUiKey.Key, actor.PlayerSession); - UpdateUserInterface(uid, paperComp, actor.PlayerSession); + _uiSystem.OpenUi(uid, PaperUiKey.Key, args.User); + UpdateUserInterface(uid, paperComp); args.Handled = true; return; } @@ -157,9 +151,8 @@ private void OnInputTextMessage(EntityUid uid, PaperComponent paperComp, PaperIn if (TryComp(uid, out var meta)) _metaSystem.SetEntityDescription(uid, "", meta); - if (args.Session.AttachedEntity != null) - _adminLogger.Add(LogType.Chat, LogImpact.Low, - $"{ToPrettyString(args.Session.AttachedEntity.Value):player} has written on {ToPrettyString(uid):entity} the following text: {args.Text}"); + _adminLogger.Add(LogType.Chat, LogImpact.Low, + $"{ToPrettyString(args.Actor):player} has written on {ToPrettyString(uid):entity} the following text: {args.Text}"); _audio.PlayPvs(paperComp.Sound, uid); } @@ -213,13 +206,12 @@ public void SetContent(EntityUid uid, string content, PaperComponent? paperComp _appearance.SetData(uid, PaperVisuals.Status, status, appearance); } - public void UpdateUserInterface(EntityUid uid, PaperComponent? paperComp = null, ICommonSession? session = null) + public void UpdateUserInterface(EntityUid uid, PaperComponent? paperComp = null) { if (!Resolve(uid, ref paperComp)) return; - if (_uiSystem.TryGetUi(uid, PaperUiKey.Key, out var bui)) - _uiSystem.SetUiState(bui, new PaperBoundUserInterfaceState(paperComp.Content, paperComp.StampedBy, paperComp.Mode), session); + _uiSystem.SetUiState(uid, PaperUiKey.Key, new PaperBoundUserInterfaceState(paperComp.Content, paperComp.StampedBy, paperComp.Mode)); } } diff --git a/Content.Server/Parallax/BiomeSystem.cs b/Content.Server/Parallax/BiomeSystem.cs index 83cf9b9cb2..0543518dcc 100644 --- a/Content.Server/Parallax/BiomeSystem.cs +++ b/Content.Server/Parallax/BiomeSystem.cs @@ -973,7 +973,7 @@ private void UnloadChunk(BiomeComponent component, EntityUid gridUid, MapGridCom /// /// Creates a simple planet setup for a map. /// - public void EnsurePlanet(EntityUid mapUid, BiomeTemplatePrototype biomeTemplate, int? seed = null, MetaDataComponent? metadata = null) + public void EnsurePlanet(EntityUid mapUid, BiomeTemplatePrototype biomeTemplate, int? seed = null, MetaDataComponent? metadata = null, Color? mapLight = null) { if (!Resolve(mapUid, ref metadata)) return; @@ -998,7 +998,7 @@ public void EnsurePlanet(EntityUid mapUid, BiomeTemplatePrototype biomeTemplate, // Lava: #A34931 var light = EnsureComp(mapUid); - light.AmbientLightColor = Color.FromHex("#D8B059"); + light.AmbientLightColor = mapLight ?? Color.FromHex("#D8B059"); Dirty(mapUid, light, metadata); var moles = new float[Atmospherics.AdjustedNumberOfGases]; diff --git a/Content.Server/ParticleAccelerator/EntitySystems/ParticleAcceleratorSystem.ControlBox.cs b/Content.Server/ParticleAccelerator/EntitySystems/ParticleAcceleratorSystem.ControlBox.cs index 5d373652a9..f3cff9f2e7 100644 --- a/Content.Server/ParticleAccelerator/EntitySystems/ParticleAcceleratorSystem.ControlBox.cs +++ b/Content.Server/ParticleAccelerator/EntitySystems/ParticleAcceleratorSystem.ControlBox.cs @@ -67,7 +67,7 @@ public void Fire(EntityUid uid, TimeSpan curTime, ParticleAcceleratorControlBoxC FireEmitter(comp.StarboardEmitter!.Value, strength); } - public void SwitchOn(EntityUid uid, ICommonSession? user = null, ParticleAcceleratorControlBoxComponent? comp = null) + public void SwitchOn(EntityUid uid, EntityUid? user = null, ParticleAcceleratorControlBoxComponent? comp = null) { if (!Resolve(uid, ref comp)) return; @@ -77,7 +77,7 @@ public void SwitchOn(EntityUid uid, ICommonSession? user = null, ParticleAcceler if (comp.Enabled || !comp.CanBeEnabled) return; - if (user?.AttachedEntity is { } player) + if (user is { } player) _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(player):player} has turned {ToPrettyString(uid)} on"); comp.Enabled = true; @@ -90,14 +90,14 @@ public void SwitchOn(EntityUid uid, ICommonSession? user = null, ParticleAcceler UpdateUI(uid, comp); } - public void SwitchOff(EntityUid uid, ICommonSession? user = null, ParticleAcceleratorControlBoxComponent? comp = null) + public void SwitchOff(EntityUid uid, EntityUid? user = null, ParticleAcceleratorControlBoxComponent? comp = null) { if (!Resolve(uid, ref comp)) return; if (!comp.Enabled) return; - if (user?.AttachedEntity is { } player) + if (user is { } player) _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(player):player} has turned {ToPrettyString(uid)} off"); comp.Enabled = false; @@ -138,7 +138,7 @@ public void PowerOff(EntityUid uid, ParticleAcceleratorControlBoxComponent? comp UpdateUI(uid, comp); } - public void SetStrength(EntityUid uid, ParticleAcceleratorPowerState strength, ICommonSession? user = null, ParticleAcceleratorControlBoxComponent? comp = null) + public void SetStrength(EntityUid uid, ParticleAcceleratorPowerState strength, EntityUid? user = null, ParticleAcceleratorControlBoxComponent? comp = null) { if (!Resolve(uid, ref comp)) return; @@ -154,7 +154,7 @@ public void SetStrength(EntityUid uid, ParticleAcceleratorPowerState strength, I if (strength == comp.SelectedStrength) return; - if (user?.AttachedEntity is { } player) + if (user is { } player) { var impact = strength switch { @@ -235,7 +235,8 @@ private void UpdateUI(EntityUid uid, ParticleAcceleratorControlBoxComponent? com { if (!Resolve(uid, ref comp)) return; - if (!_uiSystem.TryGetUi(uid, ParticleAcceleratorControlBoxUiKey.Key, out var bui)) + + if (!_uiSystem.HasUi(uid, ParticleAcceleratorControlBoxUiKey.Key)) return; var draw = 0f; @@ -247,7 +248,7 @@ private void UpdateUI(EntityUid uid, ParticleAcceleratorControlBoxComponent? com receive = powerConsumer.ReceivedPower; } - _uiSystem.SetUiState(bui, new ParticleAcceleratorUIState( + _uiSystem.SetUiState(uid, ParticleAcceleratorControlBoxUiKey.Key, new ParticleAcceleratorUIState( comp.Assembled, comp.Enabled, comp.SelectedStrength, @@ -346,7 +347,7 @@ private void OnControlBoxPowerChange(EntityUid uid, ParticleAcceleratorControlBo UpdateAppearance(uid, comp); if (!args.Powered) - _uiSystem.TryCloseAll(uid, ParticleAcceleratorControlBoxUiKey.Key); + _uiSystem.CloseUi(uid, ParticleAcceleratorControlBoxUiKey.Key); } private void OnUISetEnableMessage(EntityUid uid, ParticleAcceleratorControlBoxComponent comp, ParticleAcceleratorSetEnableMessage msg) @@ -361,10 +362,10 @@ private void OnUISetEnableMessage(EntityUid uid, ParticleAcceleratorControlBoxCo if (msg.Enabled) { if (comp.Assembled) - SwitchOn(uid, msg.Session, comp); + SwitchOn(uid, msg.Actor, comp); } else - SwitchOff(uid, msg.Session, comp); + SwitchOff(uid, msg.Actor, comp); UpdateUI(uid, comp); } @@ -378,7 +379,7 @@ private void OnUISetPowerMessage(EntityUid uid, ParticleAcceleratorControlBoxCom if (TryComp(uid, out var apcPower) && !apcPower.Powered) return; - SetStrength(uid, msg.State, msg.Session, comp); + SetStrength(uid, msg.State, msg.Actor, comp); UpdateUI(uid, comp); } @@ -392,7 +393,7 @@ private void OnUIRescanMessage(EntityUid uid, ParticleAcceleratorControlBoxCompo if (TryComp(uid, out var apcPower) && !apcPower.Powered) return; - RescanParts(uid, msg.Session, comp); + RescanParts(uid, msg.Actor, comp); UpdateUI(uid, comp); } diff --git a/Content.Server/ParticleAccelerator/EntitySystems/ParticleAcceleratorSystem.Parts.cs b/Content.Server/ParticleAccelerator/EntitySystems/ParticleAcceleratorSystem.Parts.cs index bdbc7b3f5b..99bb0d5cbd 100644 --- a/Content.Server/ParticleAccelerator/EntitySystems/ParticleAcceleratorSystem.Parts.cs +++ b/Content.Server/ParticleAccelerator/EntitySystems/ParticleAcceleratorSystem.Parts.cs @@ -18,7 +18,7 @@ private void InitializePartSystem() SubscribeLocalEvent(BodyTypeChanged); } - public void RescanParts(EntityUid uid, ICommonSession? user = null, ParticleAcceleratorControlBoxComponent? controller = null) + public void RescanParts(EntityUid uid, EntityUid? user = null, ParticleAcceleratorControlBoxComponent? controller = null) { if (!Resolve(uid, ref controller)) return; diff --git a/Content.Server/ParticleAccelerator/Wires/ParticleAcceleratorLimiterWireAction.cs b/Content.Server/ParticleAccelerator/Wires/ParticleAcceleratorLimiterWireAction.cs index 0cbd47c233..0645944a2a 100644 --- a/Content.Server/ParticleAccelerator/Wires/ParticleAcceleratorLimiterWireAction.cs +++ b/Content.Server/ParticleAccelerator/Wires/ParticleAcceleratorLimiterWireAction.cs @@ -48,8 +48,7 @@ public override bool Mend(EntityUid user, Wire wire, ParticleAcceleratorControlB // Yes, it's a feature that mending this wire WON'T WORK if the strength wire is also cut. // Since that blocks SetStrength(). var paSystem = EntityManager.System(); - var userSession = EntityManager.TryGetComponent(user, out var actor) ? actor.PlayerSession : null; - paSystem.SetStrength(wire.Owner, controller.MaxStrength, userSession, controller); + paSystem.SetStrength(wire.Owner, controller.MaxStrength, user, controller); return true; } diff --git a/Content.Server/ParticleAccelerator/Wires/ParticleAcceleratorStrengthWireAction.cs b/Content.Server/ParticleAccelerator/Wires/ParticleAcceleratorStrengthWireAction.cs index 65fa76ee41..1650960bd3 100644 --- a/Content.Server/ParticleAccelerator/Wires/ParticleAcceleratorStrengthWireAction.cs +++ b/Content.Server/ParticleAccelerator/Wires/ParticleAcceleratorStrengthWireAction.cs @@ -33,7 +33,6 @@ public override bool Mend(EntityUid user, Wire wire, ParticleAcceleratorControlB public override void Pulse(EntityUid user, Wire wire, ParticleAcceleratorControlBoxComponent controller) { var paSystem = EntityManager.System(); - var userSession = EntityManager.TryGetComponent(user, out var actor) ? actor.PlayerSession : null; - paSystem.SetStrength(wire.Owner, (ParticleAcceleratorPowerState) ((int) controller.SelectedStrength + 1), userSession, controller); + paSystem.SetStrength(wire.Owner, (ParticleAcceleratorPowerState) ((int) controller.SelectedStrength + 1), user, controller); } } diff --git a/Content.Server/ParticleAccelerator/Wires/ParticleAcceleratorToggleWireAction.cs b/Content.Server/ParticleAccelerator/Wires/ParticleAcceleratorToggleWireAction.cs index c43403edd4..40a15d2bc5 100644 --- a/Content.Server/ParticleAccelerator/Wires/ParticleAcceleratorToggleWireAction.cs +++ b/Content.Server/ParticleAccelerator/Wires/ParticleAcceleratorToggleWireAction.cs @@ -23,10 +23,9 @@ public sealed partial class ParticleAcceleratorPowerWireAction : ComponentWireAc public override bool Cut(EntityUid user, Wire wire, ParticleAcceleratorControlBoxComponent controller) { var paSystem = EntityManager.System(); - var userSession = EntityManager.TryGetComponent(user, out var actor) ? actor.PlayerSession : null; controller.CanBeEnabled = false; - paSystem.SwitchOff(wire.Owner, userSession, controller); + paSystem.SwitchOff(wire.Owner, user, controller); return true; } @@ -39,11 +38,10 @@ public override bool Mend(EntityUid user, Wire wire, ParticleAcceleratorControlB public override void Pulse(EntityUid user, Wire wire, ParticleAcceleratorControlBoxComponent controller) { var paSystem = EntityManager.System(); - var userSession = EntityManager.TryGetComponent(user, out var actor) ? actor.PlayerSession : null; if (controller.Enabled) - paSystem.SwitchOff(wire.Owner, userSession, controller); + paSystem.SwitchOff(wire.Owner, user, controller); else if (controller.Assembled) - paSystem.SwitchOn(wire.Owner, userSession, controller); + paSystem.SwitchOn(wire.Owner, user, controller); } } diff --git a/Content.Server/Pinpointer/NavMapSystem.cs b/Content.Server/Pinpointer/NavMapSystem.cs index aee8b90191..5881daa068 100644 --- a/Content.Server/Pinpointer/NavMapSystem.cs +++ b/Content.Server/Pinpointer/NavMapSystem.cs @@ -1,61 +1,65 @@ -using System.Diagnostics.CodeAnalysis; using Content.Server.Administration.Logs; +using Content.Server.Atmos.Components; +using Content.Server.Atmos.EntitySystems; using Content.Server.Station.Systems; using Content.Server.Warps; using Content.Shared.Database; using Content.Shared.Examine; using Content.Shared.Localizations; +using Content.Shared.Maps; using Content.Shared.Pinpointer; -using Content.Shared.Tag; using JetBrains.Annotations; -using Robust.Server.GameObjects; -using Robust.Shared.GameStates; using Robust.Shared.Map; using Robust.Shared.Map.Components; -using Robust.Shared.Physics; -using Robust.Shared.Physics.Components; +using Robust.Shared.Timing; +using System.Diagnostics.CodeAnalysis; namespace Content.Server.Pinpointer; /// /// Handles data to be used for in-grid map displays. /// -public sealed class NavMapSystem : SharedNavMapSystem +public sealed partial class NavMapSystem : SharedNavMapSystem { [Dependency] private readonly IAdminLogManager _adminLog = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; - [Dependency] private readonly TagSystem _tags = default!; - [Dependency] private readonly MapSystem _map = default!; + [Dependency] private readonly SharedMapSystem _mapSystem = default!; + [Dependency] private readonly SharedTransformSystem _transformSystem = default!; [Dependency] private readonly IMapManager _mapManager = default!; - [Dependency] private readonly TransformSystem _transform = default!; - - private EntityQuery _physicsQuery; - private EntityQuery _tagQuery; + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly ITileDefinitionManager _tileDefManager = default!; public const float CloseDistance = 15f; public const float FarDistance = 30f; + private EntityQuery _airtightQuery; + private EntityQuery _gridQuery; + private EntityQuery _navQuery; + public override void Initialize() { base.Initialize(); - _physicsQuery = GetEntityQuery(); - _tagQuery = GetEntityQuery(); + var categories = Enum.GetNames(typeof(NavMapChunkType)).Length - 1; // -1 due to "Invalid" entry. + if (Categories != categories) + throw new Exception($"{nameof(Categories)} must be equal to the number of chunk types"); + + _airtightQuery = GetEntityQuery(); + _gridQuery = GetEntityQuery(); + _navQuery = GetEntityQuery(); - SubscribeLocalEvent(OnAnchorChange); - SubscribeLocalEvent(OnReAnchor); + // Initialization events SubscribeLocalEvent(OnStationInit); - SubscribeLocalEvent(OnNavMapStartup); - SubscribeLocalEvent(OnGetState); + + // Grid change events SubscribeLocalEvent(OnNavMapSplit); + SubscribeLocalEvent(OnTileChanged); + + SubscribeLocalEvent(OnAirtightChange); + // Beacon events SubscribeLocalEvent(OnNavMapBeaconMapInit); - SubscribeLocalEvent(OnNavMapBeaconStartup); SubscribeLocalEvent(OnNavMapBeaconAnchor); - - SubscribeLocalEvent(OnNavMapDoorStartup); - SubscribeLocalEvent(OnNavMapDoorAnchor); - SubscribeLocalEvent(OnConfigureMessage); SubscribeLocalEvent(OnConfigurableMapInit); SubscribeLocalEvent(OnConfigurableExamined); @@ -67,64 +71,137 @@ private void OnStationInit(StationGridAddedEvent ev) RefreshGrid(comp, Comp(ev.GridId)); } - private void OnNavMapBeaconMapInit(EntityUid uid, NavMapBeaconComponent component, MapInitEvent args) + #region: Grid change event handling + + private void OnNavMapSplit(ref GridSplitEvent args) { - if (component.DefaultText == null || component.Text != null) + if (!_navQuery.TryComp(args.Grid, out var comp)) return; - component.Text = Loc.GetString(component.DefaultText); - Dirty(uid, component); - RefreshNavGrid(uid); + foreach (var grid in args.NewGrids) + { + var newComp = EnsureComp(grid); + RefreshGrid(comp, newComp); + } + + RefreshGrid(comp, _gridQuery.GetComponent(args.Grid)); } - private void OnNavMapBeaconStartup(EntityUid uid, NavMapBeaconComponent component, ComponentStartup args) + private NavMapChunk EnsureChunk(NavMapComponent component, Vector2i origin) { - RefreshNavGrid(uid); + if (!component.Chunks.TryGetValue(origin, out var chunk)) + { + chunk = new(origin); + component.Chunks[origin] = chunk; + } + + return chunk; } - private void OnNavMapBeaconAnchor(EntityUid uid, NavMapBeaconComponent component, ref AnchorStateChangedEvent args) + private void OnTileChanged(ref TileChangedEvent ev) { - UpdateBeaconEnabledVisuals((uid, component)); - RefreshNavGrid(uid); + if (!ev.EmptyChanged || !_navQuery.TryComp(ev.NewTile.GridUid, out var navMap)) + return; + + var tile = ev.NewTile.GridIndices; + var chunkOrigin = SharedMapSystem.GetChunkIndices(tile, ChunkSize); + + var chunk = EnsureChunk(navMap, chunkOrigin); + + // This could be easily replaced in the future to accommodate diagonal tiles + var relative = SharedMapSystem.GetChunkRelative(tile, ChunkSize); + ref var tileData = ref chunk.TileData[GetTileIndex(relative)]; + + if (ev.NewTile.IsSpace(_tileDefManager)) + { + tileData = 0; + if (PruneEmpty((ev.NewTile.GridUid, navMap), chunk)) + return; + } + else + { + tileData = FloorMask; + } + + DirtyChunk((ev.NewTile.GridUid, navMap), chunk); } - private void OnNavMapDoorStartup(Entity ent, ref ComponentStartup args) + private void DirtyChunk(Entity entity, NavMapChunk chunk) { - RefreshNavGrid(ent); + if (chunk.LastUpdate == _gameTiming.CurTick) + return; + + chunk.LastUpdate = _gameTiming.CurTick; + Dirty(entity); } - private void OnNavMapDoorAnchor(Entity ent, ref AnchorStateChangedEvent args) + private void OnAirtightChange(ref AirtightChanged args) { - RefreshNavGrid(ent); + if (args.AirBlockedChanged) + return; + + var gridUid = args.Position.Grid; + + if (!_navQuery.TryComp(gridUid, out var navMap) || + !_gridQuery.TryComp(gridUid, out var mapGrid)) + { + return; + } + + var chunkOrigin = SharedMapSystem.GetChunkIndices(args.Position.Tile, ChunkSize); + var (newValue, chunk) = RefreshTileEntityContents(gridUid, navMap, mapGrid, chunkOrigin, args.Position.Tile, setFloor: false); + + if (newValue == 0 && PruneEmpty((gridUid, navMap), chunk)) + return; + + DirtyChunk((gridUid, navMap), chunk); } - private void OnConfigureMessage(Entity ent, ref NavMapBeaconConfigureBuiMessage args) + #endregion + + #region: Beacon event handling + + private void OnNavMapBeaconMapInit(EntityUid uid, NavMapBeaconComponent component, MapInitEvent args) { - if (args.Session.AttachedEntity is not { } user) + if (component.DefaultText == null || component.Text != null) return; - if (!TryComp(ent, out var navMap)) + component.Text = Loc.GetString(component.DefaultText); + Dirty(uid, component); + + UpdateNavMapBeaconData(uid, component); + } + + private void OnNavMapBeaconAnchor(EntityUid uid, NavMapBeaconComponent component, ref AnchorStateChangedEvent args) + { + UpdateBeaconEnabledVisuals((uid, component)); + UpdateNavMapBeaconData(uid, component); + } + + private void OnConfigureMessage(Entity ent, ref NavMapBeaconConfigureBuiMessage args) + { + if (!TryComp(ent, out var beacon)) return; - if (navMap.Text == args.Text && - navMap.Color == args.Color && - navMap.Enabled == args.Enabled) + if (beacon.Text == args.Text && + beacon.Color == args.Color && + beacon.Enabled == args.Enabled) return; _adminLog.Add(LogType.Action, LogImpact.Medium, - $"{ToPrettyString(user):player} configured NavMapBeacon \'{ToPrettyString(ent):entity}\' with text \'{args.Text}\', color {args.Color.ToHexNoAlpha()}, and {(args.Enabled ? "enabled" : "disabled")} it."); + $"{ToPrettyString(args.Actor):player} configured NavMapBeacon \'{ToPrettyString(ent):entity}\' with text \'{args.Text}\', color {args.Color.ToHexNoAlpha()}, and {(args.Enabled ? "enabled" : "disabled")} it."); if (TryComp(ent, out var warpPoint)) { warpPoint.Location = args.Text; } - navMap.Text = args.Text; - navMap.Color = args.Color; - navMap.Enabled = args.Enabled; - UpdateBeaconEnabledVisuals((ent, navMap)); - Dirty(ent, navMap); - RefreshNavGrid(ent); + beacon.Text = args.Text; + beacon.Color = args.Color; + beacon.Enabled = args.Enabled; + + UpdateBeaconEnabledVisuals((ent, beacon)); + UpdateNavMapBeaconData(ent, beacon); } private void OnConfigurableMapInit(Entity ent, ref MapInitEvent args) @@ -134,9 +211,7 @@ private void OnConfigurableMapInit(Entity ent // We set this on mapinit just in case the text was edited via VV or something. if (TryComp(ent, out var warpPoint)) - { warpPoint.Location = navMap.Text; - } UpdateBeaconEnabledVisuals((ent, navMap)); } @@ -152,231 +227,117 @@ private void OnConfigurableExamined(Entity en ("label", navMap.Text ?? string.Empty))); } - private void UpdateBeaconEnabledVisuals(Entity ent) - { - _appearance.SetData(ent, NavMapBeaconVisuals.Enabled, ent.Comp.Enabled && Transform(ent).Anchored); - } - - /// - /// Refreshes the grid for the corresponding beacon. - /// - /// - private void RefreshNavGrid(EntityUid uid) - { - var xform = Transform(uid); - - if (!TryComp(xform.GridUid, out var navMap)) - return; + #endregion - Dirty(xform.GridUid.Value, navMap); - } + #region: Grid functions - private bool CanBeacon(EntityUid uid, TransformComponent? xform = null) - { - if (!Resolve(uid, ref xform)) - return false; - - return xform.GridUid != null && xform.Anchored; - } - - private void OnNavMapStartup(EntityUid uid, NavMapComponent component, ComponentStartup args) - { - if (!TryComp(uid, out var grid)) - return; - - RefreshGrid(component, grid); - } - - private void OnNavMapSplit(ref GridSplitEvent args) - { - if (!TryComp(args.Grid, out NavMapComponent? comp)) - return; - - var gridQuery = GetEntityQuery(); - - foreach (var grid in args.NewGrids) - { - var newComp = EnsureComp(grid); - RefreshGrid(newComp, gridQuery.GetComponent(grid)); - } - - RefreshGrid(comp, gridQuery.GetComponent(args.Grid)); - } - - private void RefreshGrid(NavMapComponent component, MapGridComponent grid) + private void RefreshGrid(NavMapComponent component, MapGridComponent mapGrid) { + // Clear stale data component.Chunks.Clear(); + component.Beacons.Clear(); - var tiles = grid.GetAllTilesEnumerator(); + // Loop over all tiles + var tileRefs = _mapSystem.GetAllTiles(mapGrid.Owner, mapGrid); - while (tiles.MoveNext(out var tile)) + foreach (var tileRef in tileRefs) { - var chunkOrigin = SharedMapSystem.GetChunkIndices(tile.Value.GridIndices, ChunkSize); + var tile = tileRef.GridIndices; + var chunkOrigin = SharedMapSystem.GetChunkIndices(tile, ChunkSize); - if (!component.Chunks.TryGetValue(chunkOrigin, out var chunk)) - { - chunk = new NavMapChunk(chunkOrigin); - component.Chunks[chunkOrigin] = chunk; - } - - RefreshTile(grid, component, chunk, tile.Value.GridIndices); + var chunk = EnsureChunk(component, chunkOrigin); + chunk.LastUpdate = _gameTiming.CurTick; + RefreshTileEntityContents(mapGrid.Owner, component, mapGrid, chunkOrigin, tile, setFloor: true); } + + Dirty(mapGrid.Owner, component); } - private void OnGetState(EntityUid uid, NavMapComponent component, ref ComponentGetState args) + private (int NewVal, NavMapChunk Chunk) RefreshTileEntityContents(EntityUid uid, + NavMapComponent component, + MapGridComponent mapGrid, + Vector2i chunkOrigin, + Vector2i tile, + bool setFloor) { - if (!TryComp(uid, out var mapGrid)) - return; - - var data = new Dictionary(component.Chunks.Count); - foreach (var (index, chunk) in component.Chunks) - { - data.Add(index, chunk.TileData); - } + var relative = SharedMapSystem.GetChunkRelative(tile, ChunkSize); + var chunk = EnsureChunk(component, chunkOrigin); + ref var tileData = ref chunk.TileData[GetTileIndex(relative)]; - var beaconQuery = AllEntityQuery(); - var beacons = new List(); + // Clear all data except for floor bits + if (setFloor) + tileData = FloorMask; + else + tileData &= FloorMask; - while (beaconQuery.MoveNext(out var beaconUid, out var beacon, out var xform)) + var enumerator = _mapSystem.GetAnchoredEntitiesEnumerator(uid, mapGrid, tile); + while (enumerator.MoveNext(out var ent)) { - if (!beacon.Enabled || xform.GridUid != uid || !CanBeacon(beaconUid, xform)) + if (!_airtightQuery.TryComp(ent, out var airtight)) continue; - // TODO: Make warp points use metadata name instead. - string? name = beacon.Text; - - if (string.IsNullOrEmpty(name)) - { - if (TryComp(beaconUid, out var warpPoint) && warpPoint.Location != null) - { - name = warpPoint.Location; - } - else - { - name = MetaData(beaconUid).EntityName; - } - } - - beacons.Add(new NavMapBeacon(beacon.Color, name, xform.LocalPosition)); + var category = GetEntityType(ent.Value); + if (category == NavMapChunkType.Invalid) + continue; + + var directions = (int)airtight.AirBlockedDirection; + tileData |= directions << (int) category; } - var airlockQuery = EntityQueryEnumerator(); - var airlocks = new List(); - while (airlockQuery.MoveNext(out _, out _, out var xform)) - { - if (xform.GridUid != uid || !xform.Anchored) - continue; + // Remove walls that intersect with doors (unless they can both physically fit on the same tile) + // TODO NAVMAP why can this even happen? + // Is this for blast-doors or something? - var pos = _map.TileIndicesFor(uid, mapGrid, xform.Coordinates); - var enumerator = _map.GetAnchoredEntitiesEnumerator(uid, mapGrid, pos); - - var wallPresent = false; - while (enumerator.MoveNext(out var ent)) - { - if (!_physicsQuery.TryGetComponent(ent, out var body) || - !body.CanCollide || - !body.Hard || - body.BodyType != BodyType.Static || - !_tags.HasTag(ent.Value, "Wall", _tagQuery) && - !_tags.HasTag(ent.Value, "Window", _tagQuery)) - { - continue; - } - - wallPresent = true; - break; - } - - if (wallPresent) - continue; + // Shift airlock bits over to the wall bits + var shiftedAirlockBits = (tileData & AirlockMask) >> ((int) NavMapChunkType.Airlock - (int) NavMapChunkType.Wall); - airlocks.Add(new NavMapAirlock(xform.LocalPosition)); - } + // And then mask door bits + tileData &= ~shiftedAirlockBits; - // TODO: Diffs - args.State = new NavMapComponentState() - { - TileData = data, - Beacons = beacons, - Airlocks = airlocks - }; + return (tileData, chunk); } - private void OnReAnchor(ref ReAnchorEvent ev) + private bool PruneEmpty(Entity entity, NavMapChunk chunk) { - if (TryComp(ev.OldGrid, out var oldGrid) && - TryComp(ev.OldGrid, out var navMap)) + foreach (var val in chunk.TileData) { - var chunkOrigin = SharedMapSystem.GetChunkIndices(ev.TilePos, ChunkSize); - - if (navMap.Chunks.TryGetValue(chunkOrigin, out var chunk)) - { - RefreshTile(oldGrid, navMap, chunk, ev.TilePos); - } + // TODO NAVMAP SIMD + if (val != 0) + return false; } - HandleAnchor(ev.Xform); + entity.Comp.Chunks.Remove(chunk.Origin); + Dirty(entity); + return true; } - private void OnAnchorChange(ref AnchorStateChangedEvent ev) - { - HandleAnchor(ev.Transform); - } + #endregion - private void HandleAnchor(TransformComponent xform) + #region: Beacon functions + + private void UpdateNavMapBeaconData(EntityUid uid, NavMapBeaconComponent component, TransformComponent? xform = null) { - if (!TryComp(xform.GridUid, out var navMap) || - !TryComp(xform.GridUid, out var grid)) + if (!Resolve(uid, ref xform) + || xform.GridUid == null + || !_navQuery.TryComp(xform.GridUid, out var navMap)) return; - var tile = grid.LocalToTile(xform.Coordinates); - var chunkOrigin = SharedMapSystem.GetChunkIndices(tile, ChunkSize); + var meta = MetaData(uid); + var changed = navMap.Beacons.Remove(meta.NetEntity); - if (!navMap.Chunks.TryGetValue(chunkOrigin, out var chunk)) + if (TryCreateNavMapBeaconData(uid, component, xform, meta, out var beaconData)) { - chunk = new NavMapChunk(chunkOrigin); - navMap.Chunks[chunkOrigin] = chunk; + navMap.Beacons.Add(meta.NetEntity, beaconData.Value); + changed = true; } - RefreshTile(grid, navMap, chunk, tile); + if (changed) + Dirty(xform.GridUid.Value, navMap); } - private void RefreshTile(MapGridComponent grid, NavMapComponent component, NavMapChunk chunk, Vector2i tile) + private void UpdateBeaconEnabledVisuals(Entity ent) { - var relative = SharedMapSystem.GetChunkRelative(tile, ChunkSize); - var existing = chunk.TileData; - var flag = GetFlag(relative); - - chunk.TileData &= ~flag; - - var enumerator = grid.GetAnchoredEntitiesEnumerator(tile); - // TODO: Use something to get convex poly. - - while (enumerator.MoveNext(out var ent)) - { - if (!_physicsQuery.TryGetComponent(ent, out var body) || - !body.CanCollide || - !body.Hard || - body.BodyType != BodyType.Static || - !_tags.HasTag(ent.Value, "Wall", _tagQuery) && - !_tags.HasTag(ent.Value, "Window", _tagQuery)) - { - continue; - } - - chunk.TileData |= flag; - break; - } - - if (chunk.TileData == 0) - { - component.Chunks.Remove(chunk.Origin); - } - - if (existing == chunk.TileData) - return; - - Dirty(component); + _appearance.SetData(ent, NavMapBeaconVisuals.Enabled, ent.Comp.Enabled && Transform(ent).Anchored); } /// @@ -389,9 +350,6 @@ public void SetBeaconEnabled(EntityUid uid, bool enabled, NavMapBeaconComponent? comp.Enabled = enabled; UpdateBeaconEnabledVisuals((uid, comp)); - Dirty(uid, comp); - - RefreshNavGrid(uid); } /// @@ -419,7 +377,7 @@ public bool TryGetNearestBeacon(Entity ent, if (!Resolve(ent, ref ent.Comp)) return false; - return TryGetNearestBeacon(_transform.GetMapCoordinates(ent, ent.Comp), out beacon, out beaconCoords); + return TryGetNearestBeacon(_transformSystem.GetMapCoordinates(ent, ent.Comp), out beacon, out beaconCoords); } /// @@ -446,7 +404,7 @@ public bool TryGetNearestBeacon(MapCoordinates coordinates, if (coordinates.MapId != xform.MapID) continue; - var coords = _transform.GetWorldPosition(xform); + var coords = _transformSystem.GetWorldPosition(xform); var distanceSquared = (coordinates.Position - coords).LengthSquared(); if (!float.IsInfinity(minDistance) && distanceSquared >= minDistance) continue; @@ -465,7 +423,7 @@ public string GetNearestBeaconString(Entity ent) if (!Resolve(ent, ref ent.Comp)) return Loc.GetString("nav-beacon-pos-no-beacons"); - return GetNearestBeaconString(_transform.GetMapCoordinates(ent, ent.Comp)); + return GetNearestBeaconString(_transformSystem.GetMapCoordinates(ent, ent.Comp)); } public string GetNearestBeaconString(MapCoordinates coordinates) @@ -494,11 +452,13 @@ public string GetNearestBeaconString(MapCoordinates coordinates) ? Loc.GetString("nav-beacon-pos-format-direction-mod-far") : string.Empty; - // we can null suppress the text being null because TRyGetNearestVisibleStationBeacon always gives us a beacon with not-null text. + // we can null suppress the text being null because TryGetNearestVisibleStationBeacon always gives us a beacon with not-null text. return Loc.GetString("nav-beacon-pos-format-direction", ("modifier", modifier), ("direction", ContentLocalizationManager.FormatDirection(adjustedDir).ToLowerInvariant()), ("color", beacon.Value.Comp.Color), ("marker", beacon.Value.Comp.Text!)); } + + #endregion } diff --git a/Content.Server/Pinpointer/StationMapSystem.cs b/Content.Server/Pinpointer/StationMapSystem.cs index c9db560fef..b0b3141fb0 100644 --- a/Content.Server/Pinpointer/StationMapSystem.cs +++ b/Content.Server/Pinpointer/StationMapSystem.cs @@ -24,29 +24,23 @@ public override void Initialize() private void OnStationMapClosed(EntityUid uid, StationMapComponent component, BoundUIClosedEvent args) { - if (!Equals(args.UiKey, StationMapUiKey.Key) || args.Session.AttachedEntity == null) + if (!Equals(args.UiKey, StationMapUiKey.Key)) return; - RemCompDeferred(args.Session.AttachedEntity.Value); + RemCompDeferred(args.Actor); } private void OnUserParentChanged(EntityUid uid, StationMapUserComponent component, ref EntParentChangedMessage args) { - if (TryComp(uid, out var actor)) - { - _ui.TryClose(component.Map, StationMapUiKey.Key, actor.PlayerSession); - } + _ui.CloseUi(component.Map, StationMapUiKey.Key, uid); } private void OnStationMapOpened(EntityUid uid, StationMapComponent component, BoundUIOpenedEvent args) { - if (args.Session.AttachedEntity == null) - return; - if (!_cell.TryUseActivatableCharge(uid)) return; - var comp = EnsureComp(args.Session.AttachedEntity.Value); + var comp = EnsureComp(args.Actor); comp.Map = uid; } } diff --git a/Content.Server/Players/JobWhitelist/JobWhitelistManager.cs b/Content.Server/Players/JobWhitelist/JobWhitelistManager.cs new file mode 100644 index 0000000000..04289a4098 --- /dev/null +++ b/Content.Server/Players/JobWhitelist/JobWhitelistManager.cs @@ -0,0 +1,114 @@ +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Content.Server.Database; +using Content.Shared.CCVar; +using Content.Shared.Players.JobWhitelist; +using Content.Shared.Roles; +using Robust.Server.Player; +using Robust.Shared.Configuration; +using Robust.Shared.Network; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; +using Serilog; + +namespace Content.Server.Players.JobWhitelist; + +public sealed class JobWhitelistManager : IPostInjectInit +{ + [Dependency] private readonly IConfigurationManager _config = default!; + [Dependency] private readonly IServerDbManager _db = default!; + [Dependency] private readonly INetManager _net = default!; + [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly IPrototypeManager _prototypes = default!; + [Dependency] private readonly UserDbDataManager _userDb = default!; + + private readonly Dictionary> _whitelists = new(); + + public void Initialize() + { + _net.RegisterNetMessage(); + } + + private async Task LoadData(ICommonSession session, CancellationToken cancel) + { + var whitelists = await _db.GetJobWhitelists(session.UserId, cancel); + cancel.ThrowIfCancellationRequested(); + _whitelists[session.UserId] = whitelists.ToHashSet(); + } + + private void FinishLoad(ICommonSession session) + { + SendJobWhitelist(session); + } + + private void ClientDisconnected(ICommonSession session) + { + _whitelists.Remove(session.UserId); + } + + public async void AddWhitelist(NetUserId player, ProtoId job) + { + if (_whitelists.TryGetValue(player, out var whitelists)) + whitelists.Add(job); + + await _db.AddJobWhitelist(player, job); + + if (_player.TryGetSessionById(player, out var session)) + SendJobWhitelist(session); + } + + public bool IsAllowed(ICommonSession session, ProtoId job) + { + if (!_config.GetCVar(CCVars.GameRoleWhitelist)) + return true; + + if (!_prototypes.TryIndex(job, out var jobPrototype) || + !jobPrototype.Whitelisted) + { + return true; + } + + return IsWhitelisted(session.UserId, job); + } + + public bool IsWhitelisted(NetUserId player, ProtoId job) + { + if (!_whitelists.TryGetValue(player, out var whitelists)) + { + Log.Error("Unable to check if player {Player} is whitelisted for {Job}. Stack trace:\\n{StackTrace}", + player, + job, + Environment.StackTrace); + return false; + } + + return whitelists.Contains(job); + } + + public async void RemoveWhitelist(NetUserId player, ProtoId job) + { + _whitelists.GetValueOrDefault(player)?.Remove(job); + await _db.RemoveJobWhitelist(player, job); + + if (_player.TryGetSessionById(new NetUserId(player), out var session)) + SendJobWhitelist(session); + } + + public void SendJobWhitelist(ICommonSession player) + { + var msg = new MsgJobWhitelist + { + Whitelist = _whitelists.GetValueOrDefault(player.UserId) ?? new HashSet() + }; + + _net.ServerSendMessage(msg, player.Channel); + } + + void IPostInjectInit.PostInject() + { + _userDb.AddOnLoadPlayer(LoadData); + _userDb.AddOnFinishLoad(FinishLoad); + _userDb.AddOnPlayerDisconnect(ClientDisconnected); + } +} diff --git a/Content.Server/Players/JobWhitelist/JobWhitelistSystem.cs b/Content.Server/Players/JobWhitelist/JobWhitelistSystem.cs new file mode 100644 index 0000000000..aaada99dea --- /dev/null +++ b/Content.Server/Players/JobWhitelist/JobWhitelistSystem.cs @@ -0,0 +1,83 @@ +using System.Collections.Immutable; +using Content.Server.GameTicking.Events; +using Content.Server.Station.Events; +using Content.Shared.CCVar; +using Content.Shared.Roles; +using Robust.Server.Player; +using Robust.Shared.Configuration; +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; + +namespace Content.Server.Players.JobWhitelist; + +public sealed class JobWhitelistSystem : EntitySystem +{ + [Dependency] private readonly IConfigurationManager _config = default!; + [Dependency] private readonly JobWhitelistManager _manager = default!; + [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly IPrototypeManager _prototypes = default!; + + private ImmutableArray> _whitelistedJobs = []; + + public override void Initialize() + { + SubscribeLocalEvent(OnPrototypesReloaded); + SubscribeLocalEvent(OnStationJobsGetCandidates); + SubscribeLocalEvent(OnIsJobAllowed); + SubscribeLocalEvent(OnGetDisallowedJobs); + + CacheJobs(); + } + + private void OnPrototypesReloaded(PrototypesReloadedEventArgs ev) + { + if (ev.WasModified()) + CacheJobs(); + } + + private void OnStationJobsGetCandidates(ref StationJobsGetCandidatesEvent ev) + { + if (!_config.GetCVar(CCVars.GameRoleWhitelist)) + return; + + for (var i = ev.Jobs.Count - 1; i >= 0; i--) + { + var jobId = ev.Jobs[i]; + if (_player.TryGetSessionById(ev.Player, out var player) && + !_manager.IsAllowed(player, jobId)) + { + ev.Jobs.RemoveSwap(i); + } + } + } + + private void OnIsJobAllowed(ref IsJobAllowedEvent ev) + { + if (!_manager.IsAllowed(ev.Player, ev.JobId)) + ev.Cancelled = true; + } + + private void OnGetDisallowedJobs(ref GetDisallowedJobsEvent ev) + { + if (!_config.GetCVar(CCVars.GameRoleWhitelist)) + return; + + foreach (var job in _whitelistedJobs) + { + if (!_manager.IsAllowed(ev.Player, job)) + ev.Jobs.Add(job); + } + } + + private void CacheJobs() + { + var builder = ImmutableArray.CreateBuilder>(); + foreach (var job in _prototypes.EnumeratePrototypes()) + { + if (job.Whitelisted) + builder.Add(job.ID); + } + + _whitelistedJobs = builder.ToImmutable(); + } +} diff --git a/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingManager.cs b/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingManager.cs index 02d5780315..295d11f898 100644 --- a/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingManager.cs +++ b/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingManager.cs @@ -55,7 +55,7 @@ namespace Content.Server.Players.PlayTimeTracking; /// Operations like refreshing and sending play time info to clients are deferred until the next frame (note: not tick). /// /// -public sealed partial class PlayTimeTrackingManager : ISharedPlaytimeManager +public sealed partial class PlayTimeTrackingManager : ISharedPlaytimeManager, IPostInjectInit { [Dependency] private readonly IServerDbManager _db = default!; [Dependency] private readonly IServerNetManager _net = default!; @@ -63,6 +63,7 @@ public sealed partial class PlayTimeTrackingManager : ISharedPlaytimeManager [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly ITaskManager _task = default!; [Dependency] private readonly IRuntimeLog _runtimeLog = default!; + [Dependency] private readonly UserDbDataManager _userDb = default!; private ISawmill _sawmill = default!; @@ -317,7 +318,7 @@ public async Task LoadData(ICommonSession session, CancellationToken cancel) var data = new PlayTimeData(); _playTimeData.Add(session, data); - var playTimes = await _db.GetPlayTimes(session.UserId); + var playTimes = await _db.GetPlayTimes(session.UserId, cancel); cancel.ThrowIfCancellationRequested(); foreach (var timer in playTimes) @@ -456,4 +457,10 @@ private sealed class PlayTimeData /// public readonly HashSet DbTrackersDirty = new(); } + + void IPostInjectInit.PostInject() + { + _userDb.AddOnLoadPlayer(LoadData); + _userDb.AddOnPlayerDisconnect(ClientDisconnected); + } } diff --git a/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingSystem.cs b/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingSystem.cs index 8368388d81..ba7b9f3c37 100644 --- a/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingSystem.cs +++ b/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingSystem.cs @@ -1,8 +1,12 @@ using System.Linq; +using Content.Server.Administration; +using Content.Server.Administration.Managers; using Content.Server.Afk; using Content.Server.Afk.Events; using Content.Server.GameTicking; +using Content.Server.GameTicking.Events; using Content.Server.Mind; +using Content.Server.Station.Events; using Content.Server.Preferences.Managers; using Content.Shared.CCVar; using Content.Shared.Customization.Systems; @@ -13,7 +17,6 @@ using Content.Shared.Players.PlayTimeTracking; using Content.Shared.Preferences; using Content.Shared.Roles; -using Robust.Server.GameObjects; using Robust.Server.Player; using Robust.Shared.Configuration; using Robust.Shared.Network; @@ -34,6 +37,7 @@ public sealed class PlayTimeTrackingSystem : EntitySystem [Dependency] private readonly IConfigurationManager _cfg = default!; [Dependency] private readonly MindSystem _minds = default!; [Dependency] private readonly PlayTimeTrackingManager _tracking = default!; + [Dependency] private readonly IAdminManager _adminManager = default!; [Dependency] private readonly CharacterRequirementsSystem _characterRequirements = default!; [Dependency] private readonly IServerPreferencesManager _prefs = default!; [Dependency] private readonly IConfigurationManager _config = default!; @@ -54,6 +58,10 @@ public override void Initialize() SubscribeLocalEvent(OnUnAFK); SubscribeLocalEvent(OnMobStateChanged); SubscribeLocalEvent(OnPlayerJoinedLobby); + SubscribeLocalEvent(OnStationJobsGetCandidates); + SubscribeLocalEvent(OnIsJobAllowed); + SubscribeLocalEvent(OnGetDisallowedJobs); + _adminManager.OnPermsChanged += AdminPermsChanged; } public override void Shutdown() @@ -61,6 +69,7 @@ public override void Shutdown() base.Shutdown(); _tracking.CalcTrackers -= CalcTrackers; + _adminManager.OnPermsChanged -= AdminPermsChanged; } private void CalcTrackers(ICommonSession player, HashSet trackers) @@ -68,6 +77,13 @@ private void CalcTrackers(ICommonSession player, HashSet trackers) if (_afk.IsAfk(player)) return; + if (_adminManager.IsAdmin(player)) + { + trackers.Add(PlayTimeTrackingShared.TrackerAdmin); + trackers.Add(PlayTimeTrackingShared.TrackerOverall); + return; + } + if (!IsPlayerAlive(player)) return; @@ -138,6 +154,11 @@ private void OnAFK(ref AFKEvent ev) _tracking.QueueRefreshTrackers(ev.Session); } + private void AdminPermsChanged(AdminPermsChangedEventArgs admin) + { + _tracking.QueueRefreshTrackers(admin.Player); + } + private void OnPlayerAttached(PlayerAttachedEvent ev) { _tracking.QueueRefreshTrackers(ev.Player); @@ -157,6 +178,22 @@ private void OnMobStateChanged(MobStateChangedEvent ev) _tracking.QueueRefreshTrackers(actor.PlayerSession); } + private void OnStationJobsGetCandidates(ref StationJobsGetCandidatesEvent ev) + { + RemoveDisallowedJobs(ev.Player, ev.Jobs); + } + + private void OnIsJobAllowed(ref IsJobAllowedEvent ev) + { + if (!IsAllowed(ev.Player, ev.JobId)) + ev.Cancelled = true; + } + + private void OnGetDisallowedJobs(ref GetDisallowedJobsEvent ev) + { + ev.Jobs.UnionWith(GetDisallowedJobs(ev.Player)); + } + private void OnPlayerJoinedLobby(PlayerJoinedLobbyEvent ev) { _tracking.QueueRefreshTrackers(ev.PlayerSession); @@ -186,15 +223,16 @@ public bool IsAllowed(ICommonSession player, string role) (HumanoidCharacterProfile) _prefs.GetPreferences(player.UserId).SelectedCharacter, playTimes, isWhitelisted, + job, EntityManager, _prototypes, _config, out _); } - public HashSet GetDisallowedJobs(ICommonSession player) + public HashSet> GetDisallowedJobs(ICommonSession player) { - var roles = new HashSet(); + var roles = new HashSet>(); if (!_cfg.GetCVar(CCVars.GameRoleTimers)) return roles; @@ -216,6 +254,7 @@ public HashSet GetDisallowedJobs(ICommonSession player) (HumanoidCharacterProfile) _prefs.GetPreferences(player.UserId).SelectedCharacter, playTimes, isWhitelisted, + job, EntityManager, _prototypes, _config, @@ -232,7 +271,7 @@ public HashSet GetDisallowedJobs(ICommonSession player) return roles; } - public void RemoveDisallowedJobs(NetUserId userId, ref List jobs) + public void RemoveDisallowedJobs(NetUserId userId, List> jobs) { if (!_cfg.GetCVar(CCVars.GameRoleTimers)) return; @@ -251,21 +290,22 @@ public void RemoveDisallowedJobs(NetUserId userId, ref List jobs) { var job = jobs[i]; - if (!_prototypes.TryIndex(job, out var jobber) || + if (!_prototypes.TryIndex(job, out var jobber) || jobber.Requirements == null || jobber.Requirements.Count == 0) continue; if (!_characterRequirements.CheckRequirementsValid( - jobber.Requirements, - jobber, - (HumanoidCharacterProfile) _prefs.GetPreferences(userId).SelectedCharacter, - _tracking.GetPlayTimes(_playerManager.GetSessionById(userId)), - _playerManager.GetSessionById(userId).ContentData()?.Whitelisted ?? false, - EntityManager, - _prototypes, - _config, - out _)) + jobber.Requirements, + jobber, + (HumanoidCharacterProfile) _prefs.GetPreferences(userId).SelectedCharacter, + _tracking.GetPlayTimes(_playerManager.GetSessionById(userId)), + _playerManager.GetSessionById(userId).ContentData()?.Whitelisted ?? false, + jobber, + EntityManager, + _prototypes, + _config, + out _)) { jobs.RemoveSwap(i); i--; diff --git a/Content.Server/Pointing/EntitySystems/PointingSystem.cs b/Content.Server/Pointing/EntitySystems/PointingSystem.cs index 9b2e14eff8..c9e86772fb 100644 --- a/Content.Server/Pointing/EntitySystems/PointingSystem.cs +++ b/Content.Server/Pointing/EntitySystems/PointingSystem.cs @@ -171,7 +171,7 @@ public bool TryPoint(ICommonSession? session, EntityCoordinates coordsPointed, E { var arrowVisibility = EntityManager.EnsureComponent(arrow); layer = playerVisibility.Layer; - _visibilitySystem.SetLayer(arrow, arrowVisibility, layer); + _visibilitySystem.SetLayer((arrow, arrowVisibility), (ushort) layer); } // Get players that are in range and whose visibility layer matches the arrow's. diff --git a/Content.Server/Polymorph/Systems/ChameleonProjectorSystem.cs b/Content.Server/Polymorph/Systems/ChameleonProjectorSystem.cs new file mode 100644 index 0000000000..1586973a21 --- /dev/null +++ b/Content.Server/Polymorph/Systems/ChameleonProjectorSystem.cs @@ -0,0 +1,99 @@ +using Content.Server.Polymorph.Components; +using Content.Shared.Actions; +using Content.Shared.Construction.Components; +using Content.Shared.Hands; +using Content.Shared.Mobs; +using Content.Shared.Mobs.Components; +using Content.Shared.Mobs.Systems; +using Content.Shared.Polymorph; +using Content.Shared.Polymorph.Components; +using Content.Shared.Polymorph.Systems; +using Content.Shared.StatusIcon.Components; +using Robust.Shared.Physics.Components; + +namespace Content.Server.Polymorph.Systems; + +public sealed class ChameleonProjectorSystem : SharedChameleonProjectorSystem +{ + [Dependency] private readonly MetaDataSystem _meta = default!; + [Dependency] private readonly MobThresholdSystem _mobThreshold = default!; + [Dependency] private readonly PolymorphSystem _polymorph = default!; + [Dependency] private readonly SharedActionsSystem _actions = default!; + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly SharedTransformSystem _xform = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnEquippedHand); + SubscribeLocalEvent(OnToggleNoRot); + SubscribeLocalEvent(OnToggleAnchored); + } + + private void OnEquippedHand(Entity ent, ref GotEquippedHandEvent args) + { + if (!TryComp(ent, out var poly)) + return; + + _polymorph.Revert((ent, poly)); + args.Handled = true; + } + + public override void Disguise(ChameleonProjectorComponent proj, EntityUid user, EntityUid entity) + { + if (_polymorph.PolymorphEntity(user, proj.Polymorph) is not {} disguise) + return; + + // make disguise look real (for simple things at least) + var meta = MetaData(entity); + _meta.SetEntityName(disguise, meta.EntityName); + _meta.SetEntityDescription(disguise, meta.EntityDescription); + + var comp = EnsureComp(disguise); + comp.SourceEntity = entity; + comp.SourceProto = Prototype(entity)?.ID; + Dirty(disguise, comp); + + // no sechud trolling + RemComp(disguise); + + _appearance.CopyData(entity, disguise); + + var mass = CompOrNull(entity)?.Mass ?? 0f; + + // let the disguise die when its taken enough damage, which then transfers to the player + // health is proportional to mass, and capped to not be insane + if (TryComp(disguise, out var thresholds)) + { + // if the player is of flesh and blood, cap max health to theirs + // so that when reverting damage scales 1:1 and not round removing + var playerMax = _mobThreshold.GetThresholdForState(user, MobState.Dead).Float(); + var max = playerMax == 0f ? proj.MaxHealth : Math.Max(proj.MaxHealth, playerMax); + + var health = Math.Clamp(mass, proj.MinHealth, proj.MaxHealth); + _mobThreshold.SetMobStateThreshold(disguise, health, MobState.Critical, thresholds); + _mobThreshold.SetMobStateThreshold(disguise, max, MobState.Dead, thresholds); + } + + // add actions for controlling transform aspects + _actions.AddAction(disguise, proj.NoRotAction); + _actions.AddAction(disguise, proj.AnchorAction); + } + + private void OnToggleNoRot(Entity ent, ref DisguiseToggleNoRotEvent args) + { + var xform = Transform(ent); + xform.NoLocalRotation = !xform.NoLocalRotation; + } + + private void OnToggleAnchored(Entity ent, ref DisguiseToggleAnchoredEvent args) + { + var uid = ent.Owner; + var xform = Transform(uid); + if (xform.Anchored) + _xform.Unanchor(uid, xform); + else + _xform.AnchorEntity((uid, xform)); + } +} diff --git a/Content.Server/Polymorph/Systems/PolymorphSystem.cs b/Content.Server/Polymorph/Systems/PolymorphSystem.cs index 8cae15d70d..5c970b1a74 100644 --- a/Content.Server/Polymorph/Systems/PolymorphSystem.cs +++ b/Content.Server/Polymorph/Systems/PolymorphSystem.cs @@ -20,6 +20,7 @@ using Robust.Server.GameObjects; using Robust.Shared.Map; using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.Manager; using Robust.Shared.Timing; using Robust.Shared.Utility; @@ -31,6 +32,7 @@ public sealed partial class PolymorphSystem : EntitySystem [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IPrototypeManager _proto = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly ISerializationManager _serialization = default!; [Dependency] private readonly ActionsSystem _actions = default!; [Dependency] private readonly AudioSystem _audio = default!; [Dependency] private readonly SharedBuckleSystem _buckle = default!; @@ -199,8 +201,21 @@ private void OnDestruction(Entity ent, ref Destructi var targetTransformComp = Transform(uid); - var child = Spawn(configuration.Entity, targetTransformComp.Coordinates); + var child = Spawn(configuration.Entity, _transform.GetMapCoordinates(uid, targetTransformComp), rotation: _transform.GetWorldRotation(uid)); + // Copy specified components over + foreach (var compName in configuration.CopiedComponents) + { + if (!_compFact.TryGetRegistration(compName, out var reg) + || !EntityManager.TryGetComponent(uid, reg.Idx, out var comp)) + continue; + + var copy = _serialization.CreateCopy(comp, notNullableOverride: true); + copy.Owner = child; + AddComp(child, copy, true); + } + + // Ensure the resulting entity is sentient (why? this sucks) MakeSentientCommand.MakeSentient(child, EntityManager); var polymorphedComp = _compFact.GetComponent(); diff --git a/Content.Server/Popups/PopupSystem.cs b/Content.Server/Popups/PopupSystem.cs index d1163a2be1..4aa3d39224 100644 --- a/Content.Server/Popups/PopupSystem.cs +++ b/Content.Server/Popups/PopupSystem.cs @@ -88,11 +88,19 @@ public override void PopupEntity(string? message, EntityUid uid, EntityUid recip RaiseNetworkEvent(new PopupEntityEvent(message, type, GetNetEntity(uid)), actor.PlayerSession); } + public override void PopupClient(string? message, EntityUid? recipient, PopupType type = PopupType.Small) + { + } + public override void PopupClient(string? message, EntityUid uid, EntityUid? recipient, PopupType type = PopupType.Small) { // do nothing duh its for client only } + public override void PopupClient(string? message, EntityCoordinates coordinates, EntityUid? recipient, PopupType type = PopupType.Small) + { + } + public override void PopupEntity(string? message, EntityUid uid, ICommonSession recipient, PopupType type = PopupType.Small) { if (message == null) diff --git a/Content.Server/Power/Components/ActivatableUIRequiresPowerComponent.cs b/Content.Server/Power/Components/ActivatableUIRequiresPowerComponent.cs deleted file mode 100644 index c387457adb..0000000000 --- a/Content.Server/Power/Components/ActivatableUIRequiresPowerComponent.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Content.Server.Power.Components -{ - [RegisterComponent] - public sealed partial class ActivatableUIRequiresPowerComponent : Component - { - } -} - diff --git a/Content.Server/Power/Components/ActiveChargerComponent.cs b/Content.Server/Power/Components/ActiveChargerComponent.cs new file mode 100644 index 0000000000..9f75db853d --- /dev/null +++ b/Content.Server/Power/Components/ActiveChargerComponent.cs @@ -0,0 +1,7 @@ +using Content.Shared.Containers.ItemSlots; +using Content.Shared.Power; + +namespace Content.Server.Power.Components; + +[RegisterComponent] +public sealed partial class ActiveChargerComponent : Component { } diff --git a/Content.Server/Power/Components/BatteryDrinkerComponent.cs b/Content.Server/Power/Components/BatteryDrinkerComponent.cs new file mode 100644 index 0000000000..1a72807af6 --- /dev/null +++ b/Content.Server/Power/Components/BatteryDrinkerComponent.cs @@ -0,0 +1,31 @@ +namespace Content.Server.Power; + +[RegisterComponent] +public sealed partial class BatteryDrinkerComponent : Component +{ + /// + /// Is this drinker allowed to drink batteries not tagged as ? + /// + [DataField] + public bool DrinkAll; + + /// + /// How long it takes to drink from a battery, in seconds. + /// Is multiplied by the source. + /// + [DataField] + public float DrinkSpeed = 1.5f; + + /// + /// The multiplier for the amount of power to attempt to drink. + /// Default amount is 1000 + /// + [DataField] + public float DrinkMultiplier = 5f; + + /// + /// The multiplier for how long it takes to drink a non-source battery, if is true. + /// + [DataField] + public float DrinkAllMultiplier = 2.5f; +} diff --git a/Content.Server/Power/Components/ChargerComponent.cs b/Content.Server/Power/Components/ChargerComponent.cs index af4498f01b..4a3c83ae03 100644 --- a/Content.Server/Power/Components/ChargerComponent.cs +++ b/Content.Server/Power/Components/ChargerComponent.cs @@ -26,5 +26,12 @@ public sealed partial class ChargerComponent : Component /// [DataField("whitelist")] public EntityWhitelist? Whitelist; + + /// + /// Indicates whether the charger is portable and thus subject to EMP effects + /// and bypasses checks for transform, anchored, and ApcPowerReceiverComponent. + /// + [DataField] + public bool Portable = false; } } diff --git a/Content.Server/Power/Components/ChargingComponent.cs b/Content.Server/Power/Components/ChargingComponent.cs deleted file mode 100644 index db7c14f708..0000000000 --- a/Content.Server/Power/Components/ChargingComponent.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Content.Shared.Containers.ItemSlots; -using Content.Shared.Power; - -namespace Content.Server.Power.Components -{ - [RegisterComponent] - public sealed partial class ChargingComponent : Component - { - /// - ///References the entity of the charger that is currently powering this battery - /// - public EntityUid ChargerUid; - - /// - ///References the component of the charger that is currently powering this battery - /// - public ChargerComponent ChargerComponent; - } -} diff --git a/Content.Server/Power/Components/RandomBatteryChargeComponent.cs b/Content.Server/Power/Components/RandomBatteryChargeComponent.cs new file mode 100644 index 0000000000..c27d74f10c --- /dev/null +++ b/Content.Server/Power/Components/RandomBatteryChargeComponent.cs @@ -0,0 +1,26 @@ +using System.Numerics; + +namespace Content.Server.Power.Components; + +[RegisterComponent] +public sealed partial class RandomBatteryChargeComponent : Component +{ + /// + /// The minimum and maximum max charge the battery can have. + /// + [DataField] + public Vector2 BatteryMaxMinMax = new(0.85f, 1.15f); + + /// + /// The minimum and maximum current charge the battery can have. + /// + [DataField] + public Vector2 BatteryChargeMinMax = new(1f, 1f); + + /// + /// False if the randomized charge of the battery should be a multiple of the preexisting current charge of the battery. + /// True if the randomized charge of the battery should be a multiple of the max charge of the battery post max charge randomization. + /// + [DataField] + public bool BasedOnMaxCharge = true; +} diff --git a/Content.Server/Power/Components/SiliconEmitSoundOnDrainedComponent.cs b/Content.Server/Power/Components/SiliconEmitSoundOnDrainedComponent.cs new file mode 100644 index 0000000000..481311d968 --- /dev/null +++ b/Content.Server/Power/Components/SiliconEmitSoundOnDrainedComponent.cs @@ -0,0 +1,27 @@ +using Robust.Shared.Audio; +using Content.Server.Sound.Components; +using Content.Shared.Sound.Components; + +namespace Content.Server.Silicon; + +/// +/// Applies a to a Silicon when its battery is drained, and removes it when it's not. +/// +[RegisterComponent] +public sealed partial class SiliconEmitSoundOnDrainedComponent : Component +{ + [DataField] + public SoundSpecifier Sound = default!; + + [DataField] + public TimeSpan MinInterval = TimeSpan.FromSeconds(8); + + [DataField] + public TimeSpan MaxInterval = TimeSpan.FromSeconds(15); + + [DataField] + public float PlayChance = 1f; + + [DataField] + public string? PopUp; +} diff --git a/Content.Server/Power/Components/UpgradeBatteryComponent.cs b/Content.Server/Power/Components/UpgradeBatteryComponent.cs new file mode 100644 index 0000000000..b676883b71 --- /dev/null +++ b/Content.Server/Power/Components/UpgradeBatteryComponent.cs @@ -0,0 +1,28 @@ +using Content.Shared.Construction.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Server.Power.Components +{ + + [RegisterComponent] + public sealed partial class UpgradeBatteryComponent : Component + { + /// + /// The machine part that affects the power capacity. + /// + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string MachinePartPowerCapacity = "PowerCell"; + + /// + /// The machine part rating is raised to this power when calculating power gain + /// + [DataField] + public float MaxChargeMultiplier = 2f; + + /// + /// Power gain scaling + /// + [DataField] + public float BaseMaxCharge = 8000000; + } +} diff --git a/Content.Server/Power/Components/UpgradePowerDrawComponent.cs b/Content.Server/Power/Components/UpgradePowerDrawComponent.cs new file mode 100644 index 0000000000..23db4905cc --- /dev/null +++ b/Content.Server/Power/Components/UpgradePowerDrawComponent.cs @@ -0,0 +1,41 @@ +using Content.Server.Construction.Components; +using Content.Shared.Construction.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Server.Power.Components; + +/// +/// This is used for machines whose power draw +/// can be decreased through machine part upgrades. +/// +[RegisterComponent] +public sealed partial class UpgradePowerDrawComponent : Component +{ + /// + /// The base power draw of the machine. + /// Prioritizes hv/mv draw over lv draw. + /// Value is initializezd on map init from + /// + [ViewVariables(VVAccess.ReadWrite)] + public float BaseLoad; + + /// + /// The machine part that affects the power draw. + /// + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer)), ViewVariables(VVAccess.ReadWrite)] + public string MachinePartPowerDraw = "Capacitor"; + + /// + /// The multiplier used for scaling the power draw. + /// + [DataField(required: true), ViewVariables(VVAccess.ReadWrite)] + public float PowerDrawMultiplier = 1f; + + /// + /// What type of scaling is being used? + /// + [DataField(required: true), ViewVariables(VVAccess.ReadWrite)] + public MachineUpgradeScalingType Scaling; +} + + diff --git a/Content.Server/Power/Components/UpgradePowerSupplierComponent.cs b/Content.Server/Power/Components/UpgradePowerSupplierComponent.cs new file mode 100644 index 0000000000..012c38a6b9 --- /dev/null +++ b/Content.Server/Power/Components/UpgradePowerSupplierComponent.cs @@ -0,0 +1,36 @@ +using Content.Server.Construction.Components; +using Content.Shared.Construction.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Server.Power.Components; + +[RegisterComponent] +public sealed partial class UpgradePowerSupplierComponent : Component +{ + [ViewVariables(VVAccess.ReadWrite)] + public float BaseSupplyRate; + + /// + /// The machine part that affects the power supplu. + /// + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string MachinePartPowerSupply = "Capacitor"; + + /// + /// The multiplier used for scaling the power supply. + /// + [DataField(required: true)] + public float PowerSupplyMultiplier = 1f; + + /// + /// What type of scaling is being used? + /// + [DataField(required: true)] + public MachineUpgradeScalingType Scaling; + + /// + /// The current value that the power supply is being scaled by, + /// + [DataField] + public float ActualScalar = 1f; +} diff --git a/Content.Server/Power/Components/UpgradePowerSupplyRampingComponent.cs b/Content.Server/Power/Components/UpgradePowerSupplyRampingComponent.cs new file mode 100644 index 0000000000..61a654b383 --- /dev/null +++ b/Content.Server/Power/Components/UpgradePowerSupplyRampingComponent.cs @@ -0,0 +1,36 @@ +using Content.Server.Construction.Components; +using Content.Shared.Construction.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Server.Power.Components; + +[RegisterComponent] +public sealed partial class UpgradePowerSupplyRampingComponent : Component +{ + [ViewVariables(VVAccess.ReadWrite)] + public float BaseRampRate; + + /// + /// The machine part that affects the power supply ramping + /// + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string MachinePartRampRate = "Capacitor"; + + /// + /// The multiplier used for scaling the power supply ramping + /// + [DataField] + public float SupplyRampingMultiplier = 1f; + + /// + /// What type of scaling is being used? + /// + [DataField(required: true)] + public MachineUpgradeScalingType Scaling; + + /// + /// The current value that the power supply is being scaled by + /// + [DataField] + public float ActualScalar = 1f; +} diff --git a/Content.Server/Power/EntitySystems/ActivatableUIRequiresPowerSystem.cs b/Content.Server/Power/EntitySystems/ActivatableUIRequiresPowerSystem.cs index 561b0e71f0..72843a65b8 100644 --- a/Content.Server/Power/EntitySystems/ActivatableUIRequiresPowerSystem.cs +++ b/Content.Server/Power/EntitySystems/ActivatableUIRequiresPowerSystem.cs @@ -4,11 +4,12 @@ using JetBrains.Annotations; using Content.Shared.Wires; using Content.Server.UserInterface; +using Content.Shared.Power.Components; +using ActivatableUISystem = Content.Shared.UserInterface.ActivatableUISystem; namespace Content.Server.Power.EntitySystems; -[UsedImplicitly] -internal sealed class ActivatableUIRequiresPowerSystem : EntitySystem +public sealed class ActivatableUIRequiresPowerSystem : EntitySystem { [Dependency] private readonly ActivatableUISystem _activatableUI = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; diff --git a/Content.Server/Power/EntitySystems/ApcSystem.cs b/Content.Server/Power/EntitySystems/ApcSystem.cs index f345c9e88e..a449f3555a 100644 --- a/Content.Server/Power/EntitySystems/ApcSystem.cs +++ b/Content.Server/Power/EntitySystems/ApcSystem.cs @@ -68,11 +68,8 @@ private void OnApcInit(EntityUid uid, ApcComponent component, MapInitEvent args) //Update the HasAccess var for UI to read private void OnBoundUiOpen(EntityUid uid, ApcComponent component, BoundUIOpenedEvent args) { - if (args.Session.AttachedEntity == null) - return; - // TODO: this should be per-player not stored on the apc - component.HasAccess = _accessReader.IsAllowed(args.Session.AttachedEntity.Value, uid); + component.HasAccess = _accessReader.IsAllowed(args.Actor, uid); UpdateApcState(uid, component); } @@ -83,21 +80,18 @@ private void OnToggleMainBreaker(EntityUid uid, ApcComponent component, ApcToggl if (attemptEv.Cancelled) { _popup.PopupCursor(Loc.GetString("apc-component-on-toggle-cancel"), - args.Session, PopupType.Medium); + args.Actor, PopupType.Medium); return; } - if (args.Session.AttachedEntity == null) - return; - - if (_accessReader.IsAllowed(args.Session.AttachedEntity.Value, uid)) + if (_accessReader.IsAllowed(args.Actor, uid)) { ApcToggleBreaker(uid, component); } else { _popup.PopupCursor(Loc.GetString("apc-component-insufficient-access"), - args.Session, PopupType.Medium); + args.Actor, PopupType.Medium); } } @@ -160,7 +154,7 @@ public void UpdateUIState(EntityUid uid, (int) MathF.Ceiling(battery.CurrentSupply), apc.LastExternalState, battery.CurrentStorage / battery.Capacity); - _ui.TrySetUiState(uid, ApcUiKey.Key, state, ui: ui); + _ui.SetUiState((uid, ui), ApcUiKey.Key, state); } private ApcChargeState CalcChargeState(EntityUid uid, PowerState.Battery battery) diff --git a/Content.Server/Power/EntitySystems/BatterySystem.cs b/Content.Server/Power/EntitySystems/BatterySystem.cs index 1c5d83b094..cbe61f6671 100644 --- a/Content.Server/Power/EntitySystems/BatterySystem.cs +++ b/Content.Server/Power/EntitySystems/BatterySystem.cs @@ -21,7 +21,6 @@ public override void Initialize() SubscribeLocalEvent(OnBatteryRejuvenate); SubscribeLocalEvent(CalculateBatteryPrice); SubscribeLocalEvent(OnEmpPulse); - SubscribeLocalEvent(OnEmpDisabledRemoved); SubscribeLocalEvent(PreSync); SubscribeLocalEvent(PostSync); @@ -106,17 +105,6 @@ private void OnEmpPulse(EntityUid uid, BatteryComponent component, ref EmpPulseE UseCharge(uid, args.EnergyConsumption, component); } - // if a disabled battery is put into a recharged, - // allow the recharger to start recharging again after the disable ends - private void OnEmpDisabledRemoved(EntityUid uid, BatteryComponent component, ref EmpDisabledRemoved args) - { - if (!TryComp(uid, out var charging)) - return; - - var ev = new ChargerUpdateStatusEvent(); - RaiseLocalEvent(charging.ChargerUid, ref ev); - } - public float UseCharge(EntityUid uid, float value, BatteryComponent? battery = null) { if (value <= 0 || !Resolve(uid, ref battery) || battery.CurrentCharge == 0) @@ -191,10 +179,6 @@ public bool IsFull(EntityUid uid, BatteryComponent? battery = null) if (!Resolve(uid, ref battery)) return false; - // If the battery is full, remove its charging component. - if (TryComp(uid, out _)) - RemComp(uid); - return battery.CurrentCharge / battery.MaxCharge >= 0.99f; } } diff --git a/Content.Server/Power/EntitySystems/ChargerSystem.cs b/Content.Server/Power/EntitySystems/ChargerSystem.cs index ae6b024162..1ff13a24f2 100644 --- a/Content.Server/Power/EntitySystems/ChargerSystem.cs +++ b/Content.Server/Power/EntitySystems/ChargerSystem.cs @@ -10,7 +10,6 @@ using System.Diagnostics.CodeAnalysis; using Content.Shared.Storage.Components; using Robust.Server.Containers; -using Content.Shared.Whitelist; namespace Content.Server.Power.EntitySystems; @@ -32,10 +31,7 @@ public override void Initialize() SubscribeLocalEvent(OnEntityStorageInsertAttempt); SubscribeLocalEvent(OnChargerExamine); - SubscribeLocalEvent(OnUpdateStatus); - SubscribeLocalEvent(OnEmpPulse); - SubscribeLocalEvent(OnEmpDisabledRemoved); } private void OnStartup(EntityUid uid, ChargerComponent component, ComponentStartup args) @@ -48,58 +44,21 @@ private void OnChargerExamine(EntityUid uid, ChargerComponent component, Examine args.PushMarkup(Loc.GetString("charger-examine", ("color", "yellow"), ("chargeRate", (int) component.ChargeRate))); } - private void StartChargingBattery(EntityUid uid, ChargerComponent component, EntityUid target) - { - bool charge = true; - - if (HasComp(uid)) - charge = false; - else - if (!TryComp(target, out var battery)) - charge = false; - else - if (Math.Abs(battery.MaxCharge - battery.CurrentCharge) < 0.01) - charge = false; - - // wrap functionality in an if statement instead of returning... - if (charge) - { - var charging = EnsureComp(target); - charging.ChargerUid = uid; - charging.ChargerComponent = component; - } - - // ...so the status always updates (for insertin a power cell) - UpdateStatus(uid, component); - } - - private void StopChargingBattery(EntityUid uid, ChargerComponent component, EntityUid target) - { - if (HasComp(target)) - RemComp(target); - UpdateStatus(uid, component); - } - public override void Update(float frameTime) { - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out var charging)) + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out _, out var charger, out var containerComp)) { - if (!TryComp(charging.ChargerUid, out var chargerComponent)) + if (!_container.TryGetContainer(uid, charger.SlotId, out var container, containerComp)) continue; - if (charging.ChargerComponent.Status == CellChargerStatus.Off || charging.ChargerComponent.Status == CellChargerStatus.Empty) + if (charger.Status == CellChargerStatus.Empty || charger.Status == CellChargerStatus.Charged || container.ContainedEntities.Count == 0) continue; - if (HasComp(charging.ChargerUid)) - continue; - - if (!TryComp(uid, out var battery)) - continue; - - if (Math.Abs(battery.MaxCharge - battery.CurrentCharge) < 0.01) - StopChargingBattery(charging.ChargerUid, charging.ChargerComponent, uid); - TransferPower(charging.ChargerUid, uid, charging.ChargerComponent, frameTime); + foreach (var contained in container.ContainedEntities) + { + TransferPower(uid, contained, charger, frameTime); + } } } @@ -116,7 +75,7 @@ private void OnInserted(EntityUid uid, ChargerComponent component, EntInsertedIn if (args.Container.ID != component.SlotId) return; - StartChargingBattery(uid, component, args.Entity); + UpdateStatus(uid, component); } private void OnRemoved(EntityUid uid, ChargerComponent component, EntRemovedFromContainerMessage args) @@ -124,7 +83,7 @@ private void OnRemoved(EntityUid uid, ChargerComponent component, EntRemovedFrom if (args.Container.ID != component.SlotId) return; - StopChargingBattery(uid, component, args.Entity); + UpdateStatus(uid, component); } /// @@ -157,11 +116,6 @@ private void OnEntityStorageInsertAttempt(EntityUid uid, ChargerComponent compon args.Cancelled = true; } - private void OnUpdateStatus(EntityUid uid, ChargerComponent component, ref ChargerUpdateStatusEvent args) - { - UpdateStatus(uid, component); - } - private void UpdateStatus(EntityUid uid, ChargerComponent component) { var status = GetStatus(uid, component); @@ -176,6 +130,15 @@ private void UpdateStatus(EntityUid uid, ChargerComponent component) component.Status = status; + if (component.Status == CellChargerStatus.Charging) + { + AddComp(uid); + } + else + { + RemComp(uid); + } + switch (component.Status) { case CellChargerStatus.Off: @@ -187,7 +150,7 @@ private void UpdateStatus(EntityUid uid, ChargerComponent component) _appearance.SetData(uid, CellVisual.Light, CellChargerStatus.Empty, appearance); break; case CellChargerStatus.Charging: - receiver.Load = component.ChargeRate; //does not scale with multiple slotted batteries + receiver.Load = component.ChargeRate; _appearance.SetData(uid, CellVisual.Light, CellChargerStatus.Charging, appearance); break; case CellChargerStatus.Charged: @@ -198,55 +161,28 @@ private void UpdateStatus(EntityUid uid, ChargerComponent component) throw new ArgumentOutOfRangeException(); } } - + private void OnEmpPulse(EntityUid uid, ChargerComponent component, ref EmpPulseEvent args) { - // we don't care if we haven't been disabled - if (!args.Disabled) - return; - - // if the recharger is hit by an emp pulse, - // stop recharging contained batteries to save resources - if (!_container.TryGetContainer(uid, component.SlotId, out var container)) - return; - - foreach (var containedEntity in container.ContainedEntities) - { - if (!SearchForBattery(containedEntity, out _, out _)) - continue; - - StopChargingBattery(uid, component, containedEntity); - } + args.Affected = true; + args.Disabled = true; } - private void OnEmpDisabledRemoved(EntityUid uid, ChargerComponent component, ref EmpDisabledRemoved args) + private CellChargerStatus GetStatus(EntityUid uid, ChargerComponent component) { - // if an emp disable subsides, - // attempt to start charging all batteries - if (!_container.TryGetContainer(uid, component.SlotId, out var container)) - return; - - foreach (var containedEntity in container.ContainedEntities) + if (!component.Portable) { - if (!SearchForBattery(containedEntity, out _, out _)) - continue; - - StartChargingBattery(uid, component, containedEntity); + if (!TryComp(uid, out TransformComponent? transformComponent) || !transformComponent.Anchored) + return CellChargerStatus.Off; } - } - private CellChargerStatus GetStatus(EntityUid uid, ChargerComponent component) - { - if (!TryComp(uid, out TransformComponent? transformComponent)) + if (!TryComp(uid, out ApcPowerReceiverComponent? apcPowerReceiverComponent)) return CellChargerStatus.Off; - if (!transformComponent.Anchored) + if (!component.Portable && !apcPowerReceiverComponent.Powered) return CellChargerStatus.Off; - if (!TryComp(uid, out ApcPowerReceiverComponent? apcPowerReceiverComponent)) - return CellChargerStatus.Off; - - if (!apcPowerReceiverComponent.Powered) + if (HasComp(uid)) return CellChargerStatus.Off; if (!_container.TryGetContainer(uid, component.SlotId, out var container)) @@ -255,30 +191,15 @@ private CellChargerStatus GetStatus(EntityUid uid, ChargerComponent component) if (container.ContainedEntities.Count == 0) return CellChargerStatus.Empty; - var statusOut = CellChargerStatus.Off; - - foreach (var containedEntity in container.ContainedEntities) - { - // if none of the slotted items are actually batteries, represent the charger as off - if (!SearchForBattery(containedEntity, out _, out _)) - continue; - - // if all batteries are either EMP'd or fully charged, represent the charger as fully charged - statusOut = CellChargerStatus.Charged; - if (HasComp(containedEntity)) - continue; - - if (!HasComp(containedEntity)) - continue; + if (!SearchForBattery(container.ContainedEntities[0], out _, out var heldBattery)) + return CellChargerStatus.Off; - // if we have atleast one battery being charged, represent the charger as charging; - statusOut = CellChargerStatus.Charging; - break; - } + if (Math.Abs(heldBattery.MaxCharge - heldBattery.CurrentCharge) < 0.01) + return CellChargerStatus.Charged; - return statusOut; + return CellChargerStatus.Charging; } - + private void TransferPower(EntityUid uid, EntityUid targetEntity, ChargerComponent component, float frameTime) { if (!TryComp(uid, out ApcPowerReceiverComponent? receiverComponent)) @@ -293,11 +214,11 @@ private void TransferPower(EntityUid uid, EntityUid targetEntity, ChargerCompone if (!SearchForBattery(targetEntity, out var batteryUid, out var heldBattery)) return; - _battery.TrySetCharge(batteryUid.Value, heldBattery.CurrentCharge + component.ChargeRate * frameTime, heldBattery); + _battery.SetCharge(batteryUid.Value, heldBattery.CurrentCharge + component.ChargeRate * frameTime, heldBattery); // Just so the sprite won't be set to 99.99999% visibility if (heldBattery.MaxCharge - heldBattery.CurrentCharge < 0.01) { - _battery.TrySetCharge(batteryUid.Value, heldBattery.MaxCharge, heldBattery); + _battery.SetCharge(batteryUid.Value, heldBattery.MaxCharge, heldBattery); } UpdateStatus(uid, component); @@ -315,6 +236,3 @@ private bool SearchForBattery(EntityUid uid, [NotNullWhen(true)] out EntityUid? return true; } } - -[ByRefEvent] -public record struct ChargerUpdateStatusEvent(); \ No newline at end of file diff --git a/Content.Server/Power/EntitySystems/PowerMonitoringConsoleSystem.cs b/Content.Server/Power/EntitySystems/PowerMonitoringConsoleSystem.cs index 107d09c898..42c84b7f43 100644 --- a/Content.Server/Power/EntitySystems/PowerMonitoringConsoleSystem.cs +++ b/Content.Server/Power/EntitySystems/PowerMonitoringConsoleSystem.cs @@ -1,7 +1,5 @@ -using Content.Server.GameTicking.Rules.Components; using Content.Server.NodeContainer; using Content.Server.NodeContainer.EntitySystems; -using Content.Server.NodeContainer.NodeGroups; using Content.Server.NodeContainer.Nodes; using Content.Server.Power.Components; using Content.Server.Power.Nodes; @@ -13,10 +11,9 @@ using JetBrains.Annotations; using Robust.Server.GameObjects; using Robust.Shared.Map.Components; -using Robust.Shared.Player; using Robust.Shared.Utility; using System.Linq; -using System.Diagnostics.CodeAnalysis; +using Content.Server.GameTicking.Components; namespace Content.Server.Power.EntitySystems; @@ -162,7 +159,7 @@ public void OnCableAnchorStateChanged(EntityUid uid, CableComponent component, C allChunks = new(); var tile = _sharedMapSystem.LocalToTile(xform.GridUid.Value, grid, xform.Coordinates); - var chunkOrigin = SharedMapSystem.GetChunkIndices(tile, SharedNavMapSystem.ChunkSize); + var chunkOrigin = SharedMapSystem.GetChunkIndices(tile, ChunkSize); if (!allChunks.TryGetValue(chunkOrigin, out var chunk)) { @@ -170,8 +167,8 @@ public void OnCableAnchorStateChanged(EntityUid uid, CableComponent component, C allChunks[chunkOrigin] = chunk; } - var relative = SharedMapSystem.GetChunkRelative(tile, SharedNavMapSystem.ChunkSize); - var flag = SharedNavMapSystem.GetFlag(relative); + var relative = SharedMapSystem.GetChunkRelative(tile, ChunkSize); + var flag = GetFlag(relative); if (args.Anchored) chunk.PowerCableData[(int) component.CableType] |= flag; @@ -285,20 +282,17 @@ public override void Update(float frameTime) var query = AllEntityQuery(); while (query.MoveNext(out var ent, out var console)) { - if (!_userInterfaceSystem.TryGetUi(ent, PowerMonitoringConsoleUiKey.Key, out var bui)) + if (!_userInterfaceSystem.IsUiOpen(ent, PowerMonitoringConsoleUiKey.Key)) continue; - foreach (var session in bui.SubscribedSessions) - UpdateUIState(ent, console, session); + UpdateUIState(ent, console); + } } } - public void UpdateUIState(EntityUid uid, PowerMonitoringConsoleComponent component, ICommonSession session) + private void UpdateUIState(EntityUid uid, PowerMonitoringConsoleComponent component) { - if (!_userInterfaceSystem.TryGetUi(uid, PowerMonitoringConsoleUiKey.Key, out var bui)) - return; - var consoleXform = Transform(uid); if (consoleXform?.GridUid == null) @@ -421,15 +415,15 @@ public void UpdateUIState(EntityUid uid, PowerMonitoringConsoleComponent compone } // Set the UI state - _userInterfaceSystem.SetUiState(bui, + _userInterfaceSystem.SetUiState(uid, + PowerMonitoringConsoleUiKey.Key, new PowerMonitoringConsoleBoundInterfaceState (totalSources, totalBatteryUsage, totalLoads, allEntries.ToArray(), sourcesForFocus.ToArray(), - loadsForFocus.ToArray()), - session); + loadsForFocus.ToArray())); } private double GetPrimaryPowerValues(EntityUid uid, PowerMonitoringDeviceComponent device, out double powerSupplied, out double powerUsage, out double batteryUsage) @@ -723,8 +717,8 @@ private void GetLoadsForNode(EntityUid uid, Node node, out List RefreshPowerCableGrid(EntityUid gr continue; var tile = _sharedMapSystem.GetTileRef(gridUid, grid, entXform.Coordinates); - var chunkOrigin = SharedMapSystem.GetChunkIndices(tile.GridIndices, SharedNavMapSystem.ChunkSize); + var chunkOrigin = SharedMapSystem.GetChunkIndices(tile.GridIndices, ChunkSize); if (!allChunks.TryGetValue(chunkOrigin, out var chunk)) { @@ -894,8 +888,8 @@ private Dictionary RefreshPowerCableGrid(EntityUid gr allChunks[chunkOrigin] = chunk; } - var relative = SharedMapSystem.GetChunkRelative(tile.GridIndices, SharedNavMapSystem.ChunkSize); - var flag = SharedNavMapSystem.GetFlag(relative); + var relative = SharedMapSystem.GetChunkRelative(tile.GridIndices, ChunkSize); + var flag = GetFlag(relative); chunk.PowerCableData[(int) cable.CableType] |= flag; } @@ -912,7 +906,7 @@ private void UpdateFocusNetwork(EntityUid uid, PowerMonitoringCableNetworksCompo var xform = Transform(ent); var tile = _sharedMapSystem.GetTileRef(gridUid, grid, xform.Coordinates); var gridIndices = tile.GridIndices; - var chunkOrigin = SharedMapSystem.GetChunkIndices(gridIndices, SharedNavMapSystem.ChunkSize); + var chunkOrigin = SharedMapSystem.GetChunkIndices(gridIndices, ChunkSize); if (!component.FocusChunks.TryGetValue(chunkOrigin, out var chunk)) { @@ -920,8 +914,8 @@ private void UpdateFocusNetwork(EntityUid uid, PowerMonitoringCableNetworksCompo component.FocusChunks[chunkOrigin] = chunk; } - var relative = SharedMapSystem.GetChunkRelative(gridIndices, SharedNavMapSystem.ChunkSize); - var flag = SharedNavMapSystem.GetFlag(relative); + var relative = SharedMapSystem.GetChunkRelative(gridIndices, ChunkSize); + var flag = GetFlag(relative); if (TryComp(ent, out var cable)) chunk.PowerCableData[(int) cable.CableType] |= flag; diff --git a/Content.Server/Power/EntitySystems/PowerNetSystem.cs b/Content.Server/Power/EntitySystems/PowerNetSystem.cs index 07ecc2eafb..2d80c810e2 100644 --- a/Content.Server/Power/EntitySystems/PowerNetSystem.cs +++ b/Content.Server/Power/EntitySystems/PowerNetSystem.cs @@ -3,9 +3,11 @@ using Content.Server.Power.Components; using Content.Server.Power.NodeGroups; using Content.Server.Power.Pow3r; +using Content.Shared.CCVar; using Content.Shared.Power; using JetBrains.Annotations; using Robust.Server.GameObjects; +using Robust.Shared.Configuration; using Robust.Shared.Threading; namespace Content.Server.Power.EntitySystems @@ -18,19 +20,21 @@ public sealed class PowerNetSystem : EntitySystem { [Dependency] private readonly AppearanceSystem _appearance = default!; [Dependency] private readonly PowerNetConnectorSystem _powerNetConnector = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; [Dependency] private readonly IParallelManager _parMan = default!; private readonly PowerState _powerState = new(); private readonly HashSet _powerNetReconnectQueue = new(); private readonly HashSet _apcNetReconnectQueue = new(); - private readonly BatteryRampPegSolver _solver = new(); + private BatteryRampPegSolver _solver = new(); public override void Initialize() { base.Initialize(); UpdatesAfter.Add(typeof(NodeGroupSystem)); + _solver = new(_cfg.GetCVar(CCVars.DebugPow3rDisableParallel)); SubscribeLocalEvent(ApcPowerReceiverInit); SubscribeLocalEvent(ApcPowerReceiverShutdown); @@ -52,6 +56,13 @@ public override void Initialize() SubscribeLocalEvent(PowerSupplierShutdown); SubscribeLocalEvent(PowerSupplierPaused); SubscribeLocalEvent(PowerSupplierUnpaused); + + Subs.CVar(_cfg, CCVars.DebugPow3rDisableParallel, DebugPow3rDisableParallelChanged); + } + + private void DebugPow3rDisableParallelChanged(bool val) + { + _solver = new(val); } private void ApcPowerReceiverInit(EntityUid uid, ApcPowerReceiverComponent component, ComponentInit args) diff --git a/Content.Server/Power/EntitySystems/UpgradeBatterySystem.cs b/Content.Server/Power/EntitySystems/UpgradeBatterySystem.cs new file mode 100644 index 0000000000..734cf9d89c --- /dev/null +++ b/Content.Server/Power/EntitySystems/UpgradeBatterySystem.cs @@ -0,0 +1,39 @@ +using Content.Server.Construction; +using Content.Server.Power.Components; +using JetBrains.Annotations; + +namespace Content.Server.Power.EntitySystems +{ + [UsedImplicitly] + public sealed class UpgradeBatterySystem : EntitySystem + { + [Dependency] private readonly BatterySystem _batterySystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnRefreshParts); + SubscribeLocalEvent(OnUpgradeExamine); + } + + public void OnRefreshParts(EntityUid uid, UpgradeBatteryComponent component, RefreshPartsEvent args) + { + var powerCellRating = args.PartRatings[component.MachinePartPowerCapacity]; + + if (TryComp(uid, out var batteryComp)) + { + _batterySystem.SetMaxCharge(uid, MathF.Pow(component.MaxChargeMultiplier, powerCellRating - 1) * component.BaseMaxCharge, batteryComp); + } + } + + private void OnUpgradeExamine(EntityUid uid, UpgradeBatteryComponent component, UpgradeExamineEvent args) + { + // UpgradeBatteryComponent.MaxChargeMultiplier is not the actual multiplier, so we have to do this. + if (TryComp(uid, out var batteryComp)) + { + args.AddPercentageUpgrade("upgrade-max-charge", batteryComp.MaxCharge / component.BaseMaxCharge); + } + } + } +} diff --git a/Content.Server/Power/EntitySystems/UpgradePowerSystem.cs b/Content.Server/Power/EntitySystems/UpgradePowerSystem.cs new file mode 100644 index 0000000000..d2f6ee4f56 --- /dev/null +++ b/Content.Server/Power/EntitySystems/UpgradePowerSystem.cs @@ -0,0 +1,151 @@ +using Content.Server.Construction; +using Content.Server.Construction.Components; +using Content.Server.Power.Components; + +namespace Content.Server.Power.EntitySystems; + +/// +/// This handles using upgraded machine parts +/// to modify the power supply/generation of a machine. +/// +public sealed class UpgradePowerSystem : EntitySystem +{ + /// + public override void Initialize() + { + SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnRefreshParts); + SubscribeLocalEvent(OnUpgradeExamine); + + SubscribeLocalEvent(OnSupplierMapInit); + SubscribeLocalEvent(OnSupplierRefreshParts); + SubscribeLocalEvent(OnSupplierUpgradeExamine); + + SubscribeLocalEvent(OnSupplyRampingMapInit); + SubscribeLocalEvent(OnSupplyRampingRefreshParts); + SubscribeLocalEvent(OnSupplyRampingUpgradeExamine); + } + + private void OnMapInit(EntityUid uid, UpgradePowerDrawComponent component, MapInitEvent args) + { + if (TryComp(uid, out var powa)) + component.BaseLoad = powa.DrawRate; + else if (TryComp(uid, out var powa2)) + component.BaseLoad = powa2.Load; + } + + private void OnRefreshParts(EntityUid uid, UpgradePowerDrawComponent component, RefreshPartsEvent args) + { + var load = component.BaseLoad; + var rating = args.PartRatings[component.MachinePartPowerDraw]; + switch (component.Scaling) + + { + case MachineUpgradeScalingType.Linear: + load += component.PowerDrawMultiplier * (rating - 1); + break; + case MachineUpgradeScalingType.Exponential: + load *= MathF.Pow(component.PowerDrawMultiplier, rating - 1); + break; + default: + Log.Error($"invalid power scaling type for {ToPrettyString(uid)}."); + load = 0; + break; + } + + if (TryComp(uid, out var powa)) + powa.Load = load; + if (TryComp(uid, out var powa2)) + powa2.DrawRate = load; + } + + private void OnUpgradeExamine(EntityUid uid, UpgradePowerDrawComponent component, UpgradeExamineEvent args) + { + // UpgradePowerDrawComponent.PowerDrawMultiplier is not the actual multiplier, so we have to do this. + var powerDrawMultiplier = CompOrNull(uid)?.Load / component.BaseLoad + ?? CompOrNull(uid)?.DrawRate / component.BaseLoad; + + if (powerDrawMultiplier is not null) + args.AddPercentageUpgrade("upgrade-power-draw", powerDrawMultiplier.Value); + } + + private void OnSupplierMapInit(EntityUid uid, UpgradePowerSupplierComponent component, MapInitEvent args) + { + if (!TryComp(uid, out var supplier)) + return; + + component.BaseSupplyRate = supplier.MaxSupply; + } + + private void OnSupplierRefreshParts(EntityUid uid, UpgradePowerSupplierComponent component, RefreshPartsEvent args) + { + var supply = component.BaseSupplyRate; + var rating = args.PartRatings[component.MachinePartPowerSupply]; + switch (component.Scaling) + + { + case MachineUpgradeScalingType.Linear: + supply += component.PowerSupplyMultiplier * component.BaseSupplyRate * (rating - 1); + break; + case MachineUpgradeScalingType.Exponential: + supply *= MathF.Pow(component.PowerSupplyMultiplier, rating - 1); + break; + default: + Log.Error($"invalid power scaling type for {ToPrettyString(uid)}."); + supply = component.BaseSupplyRate; + break; + } + + component.ActualScalar = supply / component.BaseSupplyRate; + + if (!TryComp(uid, out var powa)) + return; + + powa.MaxSupply = supply; + } + + private void OnSupplierUpgradeExamine(EntityUid uid, UpgradePowerSupplierComponent component, UpgradeExamineEvent args) + { + args.AddPercentageUpgrade("upgrade-power-supply", component.ActualScalar); + } + + private void OnSupplyRampingMapInit(EntityUid uid, UpgradePowerSupplyRampingComponent component, MapInitEvent args) + { + if (!TryComp(uid, out var battery)) + return; + + component.BaseRampRate = battery.SupplyRampRate; + } + + private void OnSupplyRampingRefreshParts(EntityUid uid, UpgradePowerSupplyRampingComponent component, RefreshPartsEvent args) + { + var rampRate = component.BaseRampRate; + var rating = args.PartRatings[component.MachinePartRampRate]; + switch (component.Scaling) + + { + case MachineUpgradeScalingType.Linear: + rampRate += component.SupplyRampingMultiplier * component.BaseRampRate * (rating - 1); + break; + case MachineUpgradeScalingType.Exponential: + rampRate *= MathF.Pow(component.SupplyRampingMultiplier, rating - 1); + break; + default: + Log.Error($"invalid power supply ramping type for {ToPrettyString(uid)}."); + rampRate = component.BaseRampRate; + break; + } + + component.ActualScalar = rampRate / component.BaseRampRate; + + if (!TryComp(uid, out var battery)) + return; + + battery.SupplyRampRate = rampRate; + } + + private void OnSupplyRampingUpgradeExamine(EntityUid uid, UpgradePowerSupplyRampingComponent component, UpgradeExamineEvent args) + { + args.AddPercentageUpgrade("upgrade-power-supply-ramping", component.ActualScalar); + } +} diff --git a/Content.Server/Power/Generation/Teg/TegSystem.cs b/Content.Server/Power/Generation/Teg/TegSystem.cs index 3510a3da45..540bd6c483 100644 --- a/Content.Server/Power/Generation/Teg/TegSystem.cs +++ b/Content.Server/Power/Generation/Teg/TegSystem.cs @@ -7,6 +7,7 @@ using Content.Server.NodeContainer; using Content.Server.NodeContainer.Nodes; using Content.Server.Power.Components; +using Content.Shared.Atmos; using Content.Shared.DeviceNetwork; using Content.Shared.Examine; using Content.Shared.Power.Generation.Teg; diff --git a/Content.Server/Power/Generator/GeneratorExhaustGasSystem.cs b/Content.Server/Power/Generator/GeneratorExhaustGasSystem.cs index cd85e67221..359c31d75b 100644 --- a/Content.Server/Power/Generator/GeneratorExhaustGasSystem.cs +++ b/Content.Server/Power/Generator/GeneratorExhaustGasSystem.cs @@ -1,5 +1,6 @@ using Content.Server.Atmos; using Content.Server.Atmos.EntitySystems; +using Content.Shared.Atmos; using Content.Shared.Power.Generator; namespace Content.Server.Power.Generator; diff --git a/Content.Server/Power/Generator/GeneratorSignalControlComponent.cs b/Content.Server/Power/Generator/GeneratorSignalControlComponent.cs index f16a09eae3..19ae0bd687 100644 --- a/Content.Server/Power/Generator/GeneratorSignalControlComponent.cs +++ b/Content.Server/Power/Generator/GeneratorSignalControlComponent.cs @@ -1,4 +1,5 @@ using Content.Shared.DeviceLinking; +using Content.Shared.Power.Generator; using Robust.Shared.Prototypes; namespace Content.Server.Power.Generator; diff --git a/Content.Server/Power/Generator/GeneratorSystem.cs b/Content.Server/Power/Generator/GeneratorSystem.cs index a75d1e4113..721a959820 100644 --- a/Content.Server/Power/Generator/GeneratorSystem.cs +++ b/Content.Server/Power/Generator/GeneratorSystem.cs @@ -26,8 +26,11 @@ public sealed class GeneratorSystem : SharedGeneratorSystem [Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly PuddleSystem _puddle = default!; + private EntityQuery _upgradeQuery; + public override void Initialize() { + _upgradeQuery = GetEntityQuery(); UpdatesBefore.Add(typeof(PowerNetSystem)); @@ -225,7 +228,9 @@ public override void Update(float frameTime) supplier.Enabled = true; - supplier.MaxSupply = gen.TargetPower; + var upgradeMultiplier = _upgradeQuery.CompOrNull(uid)?.ActualScalar ?? 1f; + + supplier.MaxSupply = gen.TargetPower * upgradeMultiplier; var eff = 1 / CalcFuelEfficiency(gen.TargetPower, gen.OptimalPower, gen); var consumption = gen.OptimalBurnRate * frameTime * eff; diff --git a/Content.Server/Power/Generator/PortableGeneratorSystem.cs b/Content.Server/Power/Generator/PortableGeneratorSystem.cs index e7dfa35178..e2996a54d7 100644 --- a/Content.Server/Power/Generator/PortableGeneratorSystem.cs +++ b/Content.Server/Power/Generator/PortableGeneratorSystem.cs @@ -48,30 +48,21 @@ public override void Initialize() private void GeneratorSwitchOutputMessage(EntityUid uid, PortableGeneratorComponent component, PortableGeneratorSwitchOutputMessage args) { - if (args.Session.AttachedEntity == null) - return; - var fuelGenerator = Comp(uid); if (fuelGenerator.On) return; - _switchable.Cycle(uid, args.Session.AttachedEntity.Value); + _switchable.Cycle(uid, args.Actor); } private void GeneratorStopMessage(EntityUid uid, PortableGeneratorComponent component, PortableGeneratorStopMessage args) { - if (args.Session.AttachedEntity == null) - return; - - StopGenerator(uid, component, args.Session.AttachedEntity.Value); + StopGenerator(uid, component, args.Actor); } private void GeneratorStartMessage(EntityUid uid, PortableGeneratorComponent component, PortableGeneratorStartMessage args) { - if (args.Session.AttachedEntity == null) - return; - - StartGenerator(uid, component, args.Session.AttachedEntity.Value); + StartGenerator(uid, component, args.Actor); } private void StartGenerator(EntityUid uid, PortableGeneratorComponent component, EntityUid user) @@ -234,7 +225,7 @@ private void UpdateUI( if (powerSupplier.Net is { IsConnectedNetwork: true } net) networkStats = (net.NetworkNode.LastCombinedLoad, net.NetworkNode.LastCombinedSupply); - _uiSystem.TrySetUiState( + _uiSystem.SetUiState( uid, GeneratorComponentUiKey.Key, new PortableGeneratorComponentBuiState(fuelComp, fuel, clogged, networkStats)); diff --git a/Content.Server/Power/Pow3r/BatteryRampPegSolver.cs b/Content.Server/Power/Pow3r/BatteryRampPegSolver.cs index 5d52bde377..34ed2695f5 100644 --- a/Content.Server/Power/Pow3r/BatteryRampPegSolver.cs +++ b/Content.Server/Power/Pow3r/BatteryRampPegSolver.cs @@ -8,9 +8,11 @@ namespace Content.Server.Power.Pow3r public sealed class BatteryRampPegSolver : IPowerSolver { private UpdateNetworkJob _networkJob; + private bool _disableParallel; - public BatteryRampPegSolver() + public BatteryRampPegSolver(bool disableParallel = false) { + _disableParallel = disableParallel; _networkJob = new() { Solver = this, @@ -54,7 +56,10 @@ public void Tick(float frameTime, PowerState state, IParallelManager parallel) // suppliers + discharger) Then decide based on total layer size whether its worth parallelizing that // layer? _networkJob.Networks = group; - parallel.ProcessNow(_networkJob, group.Count); + if (_disableParallel) + parallel.ProcessSerialNow(_networkJob, group.Count); + else + parallel.ProcessNow(_networkJob, group.Count); } ClearBatteries(state); diff --git a/Content.Server/Power/Systems/BatteryDrinkerSystem.cs b/Content.Server/Power/Systems/BatteryDrinkerSystem.cs new file mode 100644 index 0000000000..e42783c4d8 --- /dev/null +++ b/Content.Server/Power/Systems/BatteryDrinkerSystem.cs @@ -0,0 +1,128 @@ +using System.Linq; +using Content.Server.Power.Components; +using Content.Shared.Containers.ItemSlots; +using Content.Shared.DoAfter; +using Content.Shared.PowerCell.Components; +using Content.Shared.Silicon; +using Content.Shared.Verbs; +using Robust.Shared.Utility; +using Content.Server.Silicon.Charge; +using Content.Server.Power.EntitySystems; +using Content.Server.Popups; +using Content.Server.PowerCell; +using Content.Shared.Popups; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Containers; + +namespace Content.Server.Power; + +public sealed class BatteryDrinkerSystem : EntitySystem +{ + [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly BatterySystem _battery = default!; + [Dependency] private readonly SiliconChargeSystem _silicon = default!; + [Dependency] private readonly PopupSystem _popup = default!; + [Dependency] private readonly SharedContainerSystem _container = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent>(AddAltVerb); + + SubscribeLocalEvent(OnDoAfter); + } + + private void AddAltVerb(EntityUid uid, BatteryComponent batteryComponent, GetVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract + || !TryComp(args.User, out var drinkerComp) + || !TestDrinkableBattery(uid, drinkerComp) + || !_silicon.TryGetSiliconBattery(args.User, out var _)) + return; + + AlternativeVerb verb = new() + { + Act = () => DrinkBattery(uid, args.User, drinkerComp), + Text = Loc.GetString("battery-drinker-verb-drink"), + Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/smite.svg.192dpi.png")), + }; + + args.Verbs.Add(verb); + } + + private bool TestDrinkableBattery(EntityUid target, BatteryDrinkerComponent drinkerComp) + { + if (!drinkerComp.DrinkAll && !HasComp(target)) + return false; + + return true; + } + + private void DrinkBattery(EntityUid target, EntityUid user, BatteryDrinkerComponent drinkerComp) + { + var doAfterTime = drinkerComp.DrinkSpeed; + + if (TryComp(target, out var sourceComp)) + doAfterTime *= sourceComp.DrinkSpeedMulti; + else + doAfterTime *= drinkerComp.DrinkAllMultiplier; + + var args = new DoAfterArgs(EntityManager, user, doAfterTime, new BatteryDrinkerDoAfterEvent(), user, target) // TODO: Make this doafter loop, once we merge Upstream. + { + BreakOnDamage = true, + BreakOnTargetMove = true, + BreakOnUserMove = true, + Broadcast = false, + DistanceThreshold = 1.35f, + RequireCanInteract = true, + CancelDuplicate = false + }; + + _doAfter.TryStartDoAfter(args); + } + + private void OnDoAfter(EntityUid uid, BatteryDrinkerComponent drinkerComp, DoAfterEvent args) + { + if (args.Cancelled || args.Target == null + || !TryComp(args.Target.Value, out var sourceBattery) + || !_silicon.TryGetSiliconBattery(uid, out var drinkerBatteryComponent) + || !TryComp(uid, out PowerCellSlotComponent? batterySlot) + || !TryComp(args.Target.Value, out var sourceComp) + || !_container.TryGetContainer(uid, batterySlot.CellSlotId, out var container) + || container.ContainedEntities is null) + return; + + var source = args.Target.Value; + var drinkerBattery = container.ContainedEntities.First(); + var amountToDrink = drinkerComp.DrinkMultiplier * 1000; + + amountToDrink = MathF.Min(amountToDrink, sourceBattery.CurrentCharge); + amountToDrink = MathF.Min(amountToDrink, drinkerBatteryComponent!.MaxCharge - drinkerBatteryComponent.CurrentCharge); + + if (sourceComp.MaxAmount > 0) + amountToDrink = MathF.Min(amountToDrink, (float) sourceComp.MaxAmount); + + if (amountToDrink <= 0) + { + _popup.PopupEntity(Loc.GetString("battery-drinker-empty", ("target", source)), uid, uid); + return; + } + + if (_battery.TryUseCharge(source, amountToDrink)) + _battery.SetCharge(drinkerBattery, drinkerBatteryComponent.CurrentCharge + amountToDrink, drinkerBatteryComponent); + else + { + _battery.SetCharge(drinkerBattery, sourceBattery.CurrentCharge + drinkerBatteryComponent.CurrentCharge, drinkerBatteryComponent); + _battery.SetCharge(source, 0); + } + + if (sourceComp.DrinkSound is null) + return; + + _popup.PopupEntity(Loc.GetString("ipc-recharge-tip"), uid, uid, PopupType.SmallCaution); + _audio.PlayPvs(sourceComp.DrinkSound, source); + Spawn("EffectSparks", Transform(source).Coordinates); + } +} diff --git a/Content.Server/Power/Systems/BatteryElectrocuteChargeSystem.cs b/Content.Server/Power/Systems/BatteryElectrocuteChargeSystem.cs new file mode 100644 index 0000000000..707d7c679e --- /dev/null +++ b/Content.Server/Power/Systems/BatteryElectrocuteChargeSystem.cs @@ -0,0 +1,37 @@ +using Content.Server.Electrocution; +using Content.Server.Popups; +using Content.Server.Power.Components; +using Content.Server.Power.EntitySystems; +using Content.Shared.Electrocution; +using Robust.Shared.Random; + +namespace Content.Server.Power.Systems; + +public sealed class BatteryElectrocuteChargeSystem : EntitySystem +{ + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly PopupSystem _popup = default!; + [Dependency] private readonly BatterySystem _battery = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnElectrocuted); + } + + private void OnElectrocuted(EntityUid uid, BatteryComponent battery, ElectrocutedEvent args) + { + if (args.ShockDamage == null || args.ShockDamage <= 0) + return; + + var charge = Math.Min(args.ShockDamage.Value * args.SiemensCoefficient + / ElectrocutionSystem.ElectrifiedDamagePerWatt * 2, + battery.MaxCharge * 0.25f) + * _random.NextFloat(0.75f, 1.25f); + + _battery.SetCharge(uid, battery.CurrentCharge + charge); + + _popup.PopupEntity(Loc.GetString("battery-electrocute-charge"), uid, uid); + } +} diff --git a/Content.Server/Power/Systems/SiliconEmitSoundOnDrainedSystem.cs b/Content.Server/Power/Systems/SiliconEmitSoundOnDrainedSystem.cs new file mode 100644 index 0000000000..f95a940aae --- /dev/null +++ b/Content.Server/Power/Systems/SiliconEmitSoundOnDrainedSystem.cs @@ -0,0 +1,42 @@ +using Content.Server.Silicon.Death; +using Content.Shared.Sound.Components; +using Content.Server.Sound; +using Content.Shared.Mobs; + +namespace Content.Server.Silicon; + +public sealed class EmitSoundOnCritSystem : EntitySystem +{ + [Dependency] private readonly EmitSoundSystem _emitSound = default!; + public override void Initialize() + { + SubscribeLocalEvent(OnDeath); + SubscribeLocalEvent(OnAlive); + SubscribeLocalEvent(OnStateChange); + } + + private void OnDeath(EntityUid uid, SiliconEmitSoundOnDrainedComponent component, SiliconChargeDeathEvent args) + { + var spamComp = EnsureComp(uid); + + spamComp.MinInterval = component.MinInterval; + spamComp.MaxInterval = component.MaxInterval; + spamComp.PopUp = component.PopUp; + spamComp.Sound = component.Sound; + _emitSound.SetEnabled((uid, spamComp), true); + } + + private void OnAlive(EntityUid uid, SiliconEmitSoundOnDrainedComponent component, SiliconChargeAliveEvent args) + { + RemComp(uid); // This component is bad and I don't feel like making a janky work around because of it. + // If you give something the SiliconEmitSoundOnDrainedComponent, know that it can't have the SpamEmitSoundComponent, and any other systems that play with it will just be broken. + } + + public void OnStateChange(EntityUid uid, SiliconEmitSoundOnDrainedComponent component, MobStateChangedEvent args) + { + if (args.NewMobState != MobState.Dead) + return; + + RemComp(uid); + } +} diff --git a/Content.Server/PowerCell/PowerCellSystem.cs b/Content.Server/PowerCell/PowerCellSystem.cs index d4c1faa4c9..f45a01b2e1 100644 --- a/Content.Server/PowerCell/PowerCellSystem.cs +++ b/Content.Server/PowerCell/PowerCellSystem.cs @@ -11,6 +11,7 @@ using Content.Server.UserInterface; using Content.Shared.Containers.ItemSlots; using Content.Shared.Popups; +using ActivatableUISystem = Content.Shared.UserInterface.ActivatableUISystem; namespace Content.Server.PowerCell; diff --git a/Content.Server/Prayer/PrayerSystem.cs b/Content.Server/Prayer/PrayerSystem.cs index f5051741c0..c8ef368dad 100644 --- a/Content.Server/Prayer/PrayerSystem.cs +++ b/Content.Server/Prayer/PrayerSystem.cs @@ -39,7 +39,7 @@ private void AddPrayVerb(EntityUid uid, PrayableComponent comp, GetVerbsEvent> GetSelectedProfilesForPlayers(List userIds); bool HavePreferencesLoaded(ICommonSession session); + + Task SetProfile(NetUserId userId, int slot, ICharacterProfile profile); } } diff --git a/Content.Server/Preferences/Managers/ServerPreferencesManager.cs b/Content.Server/Preferences/Managers/ServerPreferencesManager.cs index e262fde64d..df7bac72e9 100644 --- a/Content.Server/Preferences/Managers/ServerPreferencesManager.cs +++ b/Content.Server/Preferences/Managers/ServerPreferencesManager.cs @@ -3,16 +3,13 @@ using System.Threading; using System.Threading.Tasks; using Content.Server.Database; -using Content.Server.Humanoid; using Content.Shared.CCVar; -using Content.Shared.Humanoid.Prototypes; using Content.Shared.Preferences; -using Content.Shared.Roles; using Robust.Server.Player; using Robust.Shared.Configuration; using Robust.Shared.Network; using Robust.Shared.Player; -using Robust.Shared.Prototypes; +using Robust.Shared.Utility; namespace Content.Server.Preferences.Managers @@ -21,18 +18,22 @@ namespace Content.Server.Preferences.Managers /// Sends before the client joins the lobby. /// Receives and at any time. /// - public sealed class ServerPreferencesManager : IServerPreferencesManager + public sealed class ServerPreferencesManager : IServerPreferencesManager, IPostInjectInit { [Dependency] private readonly IServerNetManager _netManager = default!; [Dependency] private readonly IConfigurationManager _cfg = default!; [Dependency] private readonly IServerDbManager _db = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; - [Dependency] private readonly IPrototypeManager _protos = default!; + [Dependency] private readonly IDependencyCollection _dependencies = default!; + [Dependency] private readonly ILogManager _log = default!; + [Dependency] private readonly UserDbDataManager _userDb = default!; // Cache player prefs on the server so we don't need as much async hell related to them. private readonly Dictionary _cachedPlayerPrefs = new(); + private ISawmill _sawmill = default!; + private int MaxCharacterSlots => _cfg.GetCVar(CCVars.GameMaxCharacterSlots); public void Init() @@ -41,6 +42,7 @@ public void Init() _netManager.RegisterNetMessage(HandleSelectCharacterMessage); _netManager.RegisterNetMessage(HandleUpdateCharacterMessage); _netManager.RegisterNetMessage(HandleDeleteCharacterMessage); + _sawmill = _log.GetSawmill("prefs"); } private async void HandleSelectCharacterMessage(MsgSelectCharacter message) @@ -77,33 +79,30 @@ private async void HandleSelectCharacterMessage(MsgSelectCharacter message) private async void HandleUpdateCharacterMessage(MsgUpdateCharacter message) { - var slot = message.Slot; - var profile = message.Profile; var userId = message.MsgChannel.UserId; - if (profile == null) - { - Logger.WarningS("prefs", - $"User {userId} sent a {nameof(MsgUpdateCharacter)} with a null profile in slot {slot}."); - return; - } + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + if (message.Profile == null) + _sawmill.Error($"User {userId} sent a {nameof(MsgUpdateCharacter)} with a null profile in slot {message.Slot}."); + else + await SetProfile(userId, message.Slot, message.Profile); + } + public async Task SetProfile(NetUserId userId, int slot, ICharacterProfile profile) + { if (!_cachedPlayerPrefs.TryGetValue(userId, out var prefsData) || !prefsData.PrefsLoaded) { - Logger.WarningS("prefs", $"User {userId} tried to modify preferences before they loaded."); + _sawmill.Error($"Tried to modify user {userId} preferences before they loaded."); return; } if (slot < 0 || slot >= MaxCharacterSlots) - { return; - } var curPrefs = prefsData.Prefs!; var session = _playerManager.GetSessionById(userId); - var collection = IoCManager.Instance!; - profile.EnsureValid(session, collection); + profile.EnsureValid(session, _dependencies); var profiles = new Dictionary(curPrefs.Characters) { @@ -112,10 +111,8 @@ private async void HandleUpdateCharacterMessage(MsgUpdateCharacter message) prefsData.Prefs = new PlayerPreferences(profiles, slot, curPrefs.AdminOOCColor); - if (ShouldStorePrefs(message.MsgChannel.AuthType)) - { - await _db.SaveCharacterSlotAsync(message.MsgChannel.UserId, message.Profile, message.Slot); - } + if (ShouldStorePrefs(session.Channel.AuthType)) + await _db.SaveCharacterSlotAsync(userId, profile, slot); } private async void HandleDeleteCharacterMessage(MsgDeleteCharacter message) @@ -142,7 +139,7 @@ private async void HandleDeleteCharacterMessage(MsgDeleteCharacter message) if (curPrefs.SelectedCharacterIndex == slot) { // That ! on the end is because Rider doesn't like .NET 5. - var (ns, profile) = curPrefs.Characters.FirstOrDefault(p => p.Key != message.Slot)!; + var (ns, profile) = curPrefs.Characters.FirstOrDefault(p => p.Key != message.Slot); if (profile == null) { // Only slot left, can't delete. @@ -157,16 +154,18 @@ private async void HandleDeleteCharacterMessage(MsgDeleteCharacter message) prefsData.Prefs = new PlayerPreferences(arr, nextSlot ?? curPrefs.SelectedCharacterIndex, curPrefs.AdminOOCColor); - if (ShouldStorePrefs(message.MsgChannel.AuthType)) + if (!ShouldStorePrefs(message.MsgChannel.AuthType)) { - if (nextSlot != null) - { - await _db.DeleteSlotAndSetSelectedIndex(userId, slot, nextSlot.Value); - } - else - { - await _db.SaveCharacterSlotAsync(userId, null, slot); - } + return; + } + + if (nextSlot != null) + { + await _db.DeleteSlotAndSetSelectedIndex(userId, slot, nextSlot.Value); + } + else + { + await _db.SaveCharacterSlotAsync(userId, null, slot); } } @@ -180,7 +179,7 @@ public async Task LoadData(ICommonSession session, CancellationToken cancel) { PrefsLoaded = true, Prefs = new PlayerPreferences( - new[] {new KeyValuePair(0, HumanoidCharacterProfile.Random())}, + new[] { new KeyValuePair(0, HumanoidCharacterProfile.Random()) }, 0, Color.Transparent) }; @@ -196,21 +195,32 @@ public async Task LoadData(ICommonSession session, CancellationToken cancel) async Task LoadPrefs() { - var prefs = await GetOrCreatePreferencesAsync(session.UserId); + var prefs = await GetOrCreatePreferencesAsync(session.UserId, cancel); prefsData.Prefs = prefs; - prefsData.PrefsLoaded = true; - - var msg = new MsgPreferencesAndSettings(); - msg.Preferences = prefs; - msg.Settings = new GameSettings - { - MaxCharacterSlots = MaxCharacterSlots - }; - _netManager.ServerSendMessage(msg, session.Channel); } } } + public void FinishLoad(ICommonSession session) + { + // This is a separate step from the actual database load. + // Sanitizing preferences requires play time info due to loadouts. + // And play time info is loaded concurrently from the DB with preferences. + var prefsData = _cachedPlayerPrefs[session.UserId]; + DebugTools.Assert(prefsData.Prefs != null); + prefsData.Prefs = SanitizePreferences(session, prefsData.Prefs, _dependencies); + + prefsData.PrefsLoaded = true; + + var msg = new MsgPreferencesAndSettings(); + msg.Preferences = prefsData.Prefs; + msg.Settings = new GameSettings + { + MaxCharacterSlots = MaxCharacterSlots + }; + _netManager.ServerSendMessage(msg, session.Channel); + } + public void OnClientDisconnected(ICommonSession session) { _cachedPlayerPrefs.Remove(session.UserId); @@ -221,7 +231,6 @@ public bool HavePreferencesLoaded(ICommonSession session) return _cachedPlayerPrefs.ContainsKey(session.UserId); } - /// /// Tries to get the preferences from the cache /// @@ -256,18 +265,29 @@ public PlayerPreferences GetPreferences(NetUserId userId) return prefs; } - private async Task GetOrCreatePreferencesAsync(NetUserId userId) + /// + /// Retrieves preferences for the given username from storage or returns null. + /// Creates and saves default preferences if they are not found, then returns them. + /// + public PlayerPreferences? GetPreferencesOrNull(NetUserId? userId) { - var prefs = await _db.GetPlayerPreferencesAsync(userId); + if (userId == null) + return null; + + if (_cachedPlayerPrefs.TryGetValue(userId.Value, out var pref)) + return pref.Prefs; + return null; + } + + private async Task GetOrCreatePreferencesAsync(NetUserId userId, CancellationToken cancel) + { + var prefs = await _db.GetPlayerPreferencesAsync(userId, cancel); if (prefs is null) { - return await _db.InitPrefsAsync(userId, HumanoidCharacterProfile.Random()); + return await _db.InitPrefsAsync(userId, HumanoidCharacterProfile.Random(), cancel); } - var session = _playerManager.GetSessionById(userId); - var collection = IoCManager.Instance!; - - return SanitizePreferences(session, prefs, collection); + return prefs; } private PlayerPreferences SanitizePreferences(ICommonSession session, PlayerPreferences prefs, @@ -275,10 +295,8 @@ private PlayerPreferences SanitizePreferences(ICommonSession session, PlayerPref { // Clean up preferences in case of changes to the game, // such as removed jobs still being selected. - return new PlayerPreferences(prefs.Characters.Select(p => - { - return new KeyValuePair(p.Key, p.Value.Validated(session, collection)); - }), prefs.SelectedCharacterIndex, prefs.AdminOOCColor); + return new PlayerPreferences(prefs.Characters.Select(p => new KeyValuePair(p.Key, + p.Value.Validated(session, collection))), prefs.SelectedCharacterIndex, prefs.AdminOOCColor); } public IEnumerable> GetSelectedProfilesForPlayers( @@ -304,5 +322,12 @@ private sealed class PlayerPrefData public bool PrefsLoaded; public PlayerPreferences? Prefs; } + + void IPostInjectInit.PostInject() + { + _userDb.AddOnLoadPlayer(LoadData); + _userDb.AddOnFinishLoad(FinishLoad); + _userDb.AddOnPlayerDisconnect(OnClientDisconnected); + } } } diff --git a/Content.Server/Procedural/DungeonJob.PrefabDunGen.cs b/Content.Server/Procedural/DungeonJob.PrefabDunGen.cs index 1783a56790..a19f7e4701 100644 --- a/Content.Server/Procedural/DungeonJob.PrefabDunGen.cs +++ b/Content.Server/Procedural/DungeonJob.PrefabDunGen.cs @@ -19,7 +19,7 @@ private async Task GeneratePrefabDungeon(PrefabDunGen prefab, EntityUid var gen = _prototype.Index(preset); var dungeonRotation = _dungeon.GetDungeonRotation(seed); - var dungeonTransform = Matrix3.CreateTransform(_position, dungeonRotation); + var dungeonTransform = Matrix3Helpers.CreateTransform(_position, dungeonRotation); var roomPackProtos = new Dictionary>(); foreach (var pack in _prototype.EnumeratePrototypes()) @@ -69,7 +69,7 @@ private async Task GeneratePrefabDungeon(PrefabDunGen prefab, EntityUid var dungeon = new Dungeon(); var availablePacks = new List(); var chosenPacks = new DungeonRoomPackPrototype?[gen.RoomPacks.Count]; - var packTransforms = new Matrix3[gen.RoomPacks.Count]; + var packTransforms = new Matrix3x2[gen.RoomPacks.Count]; var packRotations = new Angle[gen.RoomPacks.Count]; // Actually pick the room packs and rooms @@ -97,7 +97,7 @@ private async Task GeneratePrefabDungeon(PrefabDunGen prefab, EntityUid // Iterate every pack random.Shuffle(availablePacks); - Matrix3 packTransform = default!; + Matrix3x2 packTransform = default!; var found = false; DungeonRoomPackPrototype pack = default!; @@ -128,7 +128,7 @@ private async Task GeneratePrefabDungeon(PrefabDunGen prefab, EntityUid var aRotation = dir.AsDir().ToAngle(); // Use this pack - packTransform = Matrix3.CreateTransform(bounds.Center, aRotation); + packTransform = Matrix3Helpers.CreateTransform(bounds.Center, aRotation); packRotations[i] = aRotation; pack = aPack; break; @@ -168,7 +168,7 @@ private async Task GeneratePrefabDungeon(PrefabDunGen prefab, EntityUid { var roomDimensions = new Vector2i(roomSize.Width, roomSize.Height); Angle roomRotation = Angle.Zero; - Matrix3 matty; + Matrix3x2 matty; if (!roomProtos.TryGetValue(roomDimensions, out var roomProto)) { @@ -176,13 +176,13 @@ private async Task GeneratePrefabDungeon(PrefabDunGen prefab, EntityUid if (!roomProtos.TryGetValue(roomDimensions, out roomProto)) { - Matrix3.Multiply(packTransform, dungeonTransform, out matty); + matty = Matrix3x2.Multiply(packTransform, dungeonTransform); for (var x = roomSize.Left; x < roomSize.Right; x++) { for (var y = roomSize.Bottom; y < roomSize.Top; y++) { - var index = matty.Transform(new Vector2(x, y) + grid.TileSizeHalfVector - packCenter).Floored(); + var index = Vector2.Transform(new Vector2(x, y) + grid.TileSizeHalfVector - packCenter, matty).Floored(); tiles.Add((index, new Tile(_tileDefManager["FloorPlanetGrass"].TileId))); } } @@ -209,10 +209,10 @@ private async Task GeneratePrefabDungeon(PrefabDunGen prefab, EntityUid roomRotation += Math.PI; } - var roomTransform = Matrix3.CreateTransform(roomSize.Center - packCenter, roomRotation); + var roomTransform = Matrix3Helpers.CreateTransform(roomSize.Center - packCenter, roomRotation); - Matrix3.Multiply(roomTransform, packTransform, out matty); - Matrix3.Multiply(matty, dungeonTransform, out var dungeonMatty); + matty = Matrix3x2.Multiply(roomTransform, packTransform); + var dungeonMatty = Matrix3x2.Multiply(matty, dungeonTransform); // The expensive bit yippy. _dungeon.SpawnRoom(gridUid, grid, dungeonMatty, room); @@ -232,7 +232,7 @@ private async Task GeneratePrefabDungeon(PrefabDunGen prefab, EntityUid continue; } - var tilePos = dungeonMatty.Transform(new Vector2i(x + room.Offset.X, y + room.Offset.Y) + tileOffset); + var tilePos = Vector2.Transform(new Vector2i(x + room.Offset.X, y + room.Offset.Y) + tileOffset, dungeonMatty); exterior.Add(tilePos.Floored()); } } @@ -244,7 +244,7 @@ private async Task GeneratePrefabDungeon(PrefabDunGen prefab, EntityUid for (var y = 0; y < room.Size.Y; y++) { var roomTile = new Vector2i(x + room.Offset.X, y + room.Offset.Y); - var tilePos = dungeonMatty.Transform(roomTile + tileOffset); + var tilePos = Vector2.Transform(roomTile + tileOffset, dungeonMatty); var tileIndex = tilePos.Floored(); roomTiles.Add(tileIndex); diff --git a/Content.Server/Procedural/DungeonSystem.Rooms.cs b/Content.Server/Procedural/DungeonSystem.Rooms.cs index 03bcc2b4b1..5b4de34906 100644 --- a/Content.Server/Procedural/DungeonSystem.Rooms.cs +++ b/Content.Server/Procedural/DungeonSystem.Rooms.cs @@ -67,7 +67,7 @@ public void SpawnRoom( bool clearExisting = false, bool rotation = false) { - var originTransform = Matrix3.CreateTranslation(origin); + var originTransform = Matrix3Helpers.CreateTranslation(origin.X, origin.Y); var roomRotation = Angle.Zero; if (rotation) @@ -75,8 +75,8 @@ public void SpawnRoom( roomRotation = GetRoomRotation(room, random); } - var roomTransform = Matrix3.CreateTransform((Vector2) room.Size / 2f, roomRotation); - Matrix3.Multiply(roomTransform, originTransform, out var finalTransform); + var roomTransform = Matrix3Helpers.CreateTransform((Vector2) room.Size / 2f, roomRotation); + var finalTransform = Matrix3x2.Multiply(roomTransform, originTransform); SpawnRoom(gridUid, grid, finalTransform, room, clearExisting); } @@ -101,7 +101,7 @@ public Angle GetRoomRotation(DungeonRoomPrototype room, Random random) public void SpawnRoom( EntityUid gridUid, MapGridComponent grid, - Matrix3 roomTransform, + Matrix3x2 roomTransform, DungeonRoomPrototype room, bool clearExisting = false) { @@ -116,7 +116,7 @@ public void SpawnRoom( // go BRRNNTTT on existing stuff if (clearExisting) { - var gridBounds = new Box2(roomTransform.Transform(Vector2.Zero), roomTransform.Transform(room.Size)); + var gridBounds = new Box2(Vector2.Transform(Vector2.Zero, roomTransform), Vector2.Transform(room.Size, roomTransform)); _entitySet.Clear(); // Polygon skin moment gridBounds = gridBounds.Enlarged(-0.05f); @@ -148,7 +148,7 @@ public void SpawnRoom( var indices = new Vector2i(x + room.Offset.X, y + room.Offset.Y); var tileRef = _maps.GetTileRef(templateMapUid, templateGrid, indices); - var tilePos = roomTransform.Transform(indices + tileOffset); + var tilePos = Vector2.Transform(indices + tileOffset, roomTransform); var rounded = tilePos.Floored(); _tiles.Add((rounded, tileRef.Tile)); } @@ -164,7 +164,7 @@ public void SpawnRoom( foreach (var templateEnt in _lookup.GetEntitiesIntersecting(templateMapUid, bounds, LookupFlags.Uncontained)) { var templateXform = _xformQuery.GetComponent(templateEnt); - var childPos = roomTransform.Transform(templateXform.LocalPosition - roomCenter); + var childPos = Vector2.Transform(templateXform.LocalPosition - roomCenter, roomTransform); var childRot = templateXform.LocalRotation + finalRoomRotation; var protoId = _metaQuery.GetComponent(templateEnt).EntityPrototype?.ID; @@ -192,7 +192,7 @@ public void SpawnRoom( // Offset by 0.5 because decals are offset from bot-left corner // So we convert it to center of tile then convert it back again after transform. // Do these shenanigans because 32x32 decals assume as they are centered on bottom-left of tiles. - var position = roomTransform.Transform(decal.Coordinates + Vector2Helpers.Half - roomCenter); + var position = Vector2.Transform(decal.Coordinates + Vector2Helpers.Half - roomCenter, roomTransform); position -= Vector2Helpers.Half; // Umm uhh I love decals so uhhhh idk what to do about this diff --git a/Content.Server/Psionics/AntiPsychicWeaponComponent.cs b/Content.Server/Psionics/AntiPsychicWeaponComponent.cs index 00528afbe9..32238c2adb 100644 --- a/Content.Server/Psionics/AntiPsychicWeaponComponent.cs +++ b/Content.Server/Psionics/AntiPsychicWeaponComponent.cs @@ -2,23 +2,59 @@ namespace Content.Server.Psionics { + /// + /// A component for weapons intended to have special effects when wielded against Psionic Entities. + /// [RegisterComponent] public sealed partial class AntiPsionicWeaponComponent : Component { - [DataField("modifiers", required: true)] + [DataField(required: true)] public DamageModifierSet Modifiers = default!; - [DataField("psychicStaminaDamage")] + [DataField] public float PsychicStaminaDamage = 30f; - [DataField("disableChance")] + /// + /// How long (in seconds) should this weapon temporarily disable powers + /// + [DataField] + public float DisableDuration = 10f; + + /// + /// The chances of this weapon temporarily disabling psionic powers + /// + [DataField] public float DisableChance = 0.3f; /// - /// Punish when used against a non-psychic. + /// The condition to be inflicted on a Psionic entity + /// + [DataField] + public string DisableStatus = "PsionicsDisabled"; + + /// + /// Whether or not the user of this weapon risks Punishment by the gods if they dare use it on non-Psionic Entities /// + /// The odds of divine punishment per non-Psionic Entity attacked + /// + [DataField] + public float PunishChances = 0.5f; + + /// + /// How much Shock damage to take when Punish(ed) by the gods for using this weapon + /// + [DataField] + public int PunishSelfDamage = 20; + + /// + /// How long (in seconds) should the user be stunned when punished by the gods + /// + [DataField] + public float PunishStunDuration = 5f; } } diff --git a/Content.Server/Psionics/Invisibility/PsionicInvisibilitySystem.cs b/Content.Server/Psionics/Invisibility/PsionicInvisibilitySystem.cs index 31e6b89f13..b9aac6ccee 100644 --- a/Content.Server/Psionics/Invisibility/PsionicInvisibilitySystem.cs +++ b/Content.Server/Psionics/Invisibility/PsionicInvisibilitySystem.cs @@ -3,6 +3,7 @@ using Content.Shared.Eye; using Content.Server.NPC.Systems; using Robust.Shared.Containers; +using Robust.Shared.Player; using Robust.Server.GameObjects; namespace Content.Server.Psionics @@ -17,13 +18,12 @@ public override void Initialize() { base.Initialize(); /// Masking - SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnInit); SubscribeLocalEvent(OnInsulInit); SubscribeLocalEvent(OnInsulShutdown); - SubscribeLocalEvent(OnEyeInit); /// Layer - SubscribeLocalEvent(OnInvisInit); + SubscribeLocalEvent(OnInvisInit); SubscribeLocalEvent(OnInvisShutdown); // PVS Stuff @@ -31,16 +31,14 @@ public override void Initialize() SubscribeLocalEvent(OnEntRemoved); } - private void OnInit(EntityUid uid, PotentialPsionicComponent component, ComponentInit args) + private void OnInit(EntityUid uid, ActorComponent component, ComponentInit args) { - SetCanSeePsionicInvisiblity(uid, false); + if (!HasComp(uid)) + SetCanSeePsionicInvisiblity(uid, false); } private void OnInsulInit(EntityUid uid, PsionicInsulationComponent component, ComponentInit args) { - if (!HasComp(uid)) - return; - if (HasComp(uid)) _invisSystem.ToggleInvisibility(uid); @@ -61,9 +59,6 @@ private void OnInsulInit(EntityUid uid, PsionicInsulationComponent component, Co private void OnInsulShutdown(EntityUid uid, PsionicInsulationComponent component, ComponentShutdown args) { - if (!HasComp(uid)) - return; - SetCanSeePsionicInvisiblity(uid, false); if (!HasComp(uid)) @@ -79,12 +74,12 @@ private void OnInsulShutdown(EntityUid uid, PsionicInsulationComponent component component.SuppressedFactions.Clear(); } - private void OnInvisInit(EntityUid uid, PsionicallyInvisibleComponent component, ComponentInit args) + private void OnInvisInit(EntityUid uid, PsionicallyInvisibleComponent component, ComponentStartup args) { var visibility = EntityManager.EnsureComponent(uid); - _visibilitySystem.AddLayer(uid, visibility, (int) VisibilityFlags.PsionicInvisibility, false); - _visibilitySystem.RemoveLayer(uid, visibility, (int) VisibilityFlags.Normal, false); + _visibilitySystem.AddLayer((uid, visibility), (int) VisibilityFlags.PsionicInvisibility, false); + _visibilitySystem.RemoveLayer((uid, visibility), (int) VisibilityFlags.Normal, false); _visibilitySystem.RefreshVisibility(uid, visibility); } @@ -93,16 +88,12 @@ private void OnInvisShutdown(EntityUid uid, PsionicallyInvisibleComponent compon { if (TryComp(uid, out var visibility)) { - _visibilitySystem.RemoveLayer(uid, visibility, (int) VisibilityFlags.PsionicInvisibility, false); - _visibilitySystem.AddLayer(uid, visibility, (int) VisibilityFlags.Normal, false); + _visibilitySystem.RemoveLayer((uid, visibility), (int) VisibilityFlags.PsionicInvisibility, false); + _visibilitySystem.AddLayer((uid, visibility), (int) VisibilityFlags.Normal, false); _visibilitySystem.RefreshVisibility(uid, visibility); } } - private void OnEyeInit(EntityUid uid, EyeComponent component, ComponentInit args) - { - //SetCanSeePsionicInvisiblity(uid, true); //JJ Comment - Not allowed to modifies .yml on spawn any longer. See UninitializedSaveTest. - } private void OnEntInserted(EntityUid uid, PsionicallyInvisibleComponent component, EntInsertedIntoContainerMessage args) { DirtyEntity(args.Entity); @@ -121,11 +112,12 @@ public void SetCanSeePsionicInvisiblity(EntityUid uid, bool set) { _eye.SetVisibilityMask(uid, eye.VisibilityMask | (int) VisibilityFlags.PsionicInvisibility, eye); } - } else + } + else { if (EntityManager.TryGetComponent(uid, out EyeComponent? eye)) { - _eye.SetVisibilityMask(uid, eye.VisibilityMask & ~ (int) VisibilityFlags.PsionicInvisibility, eye); + _eye.SetVisibilityMask(uid, eye.VisibilityMask & ~(int) VisibilityFlags.PsionicInvisibility, eye); } } } diff --git a/Content.Server/Psionics/PotentialPsionicComponent.cs b/Content.Server/Psionics/PotentialPsionicComponent.cs deleted file mode 100644 index 9499497cd1..0000000000 --- a/Content.Server/Psionics/PotentialPsionicComponent.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Content.Server.Psionics -{ - [RegisterComponent] - public sealed partial class PotentialPsionicComponent : Component - { - [DataField("chance")] - public float Chance = 0.04f; - - /// - /// YORO (you only reroll once) - /// - public bool Rerolled = false; - } -} diff --git a/Content.Server/Psionics/PsionicAwaitingPlayerComponent.cs b/Content.Server/Psionics/PsionicAwaitingPlayerComponent.cs deleted file mode 100644 index f9cc9339d4..0000000000 --- a/Content.Server/Psionics/PsionicAwaitingPlayerComponent.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Content.Server.Psionics -{ - /// - /// Will open the 'accept psionics' UI when a player attaches. - /// - [RegisterComponent] - public sealed partial class PsionicAwaitingPlayerComponent : Component - {} -} diff --git a/Content.Server/Psionics/PsionicBonusChanceComponent.cs b/Content.Server/Psionics/PsionicBonusChanceComponent.cs deleted file mode 100644 index d9cbc51147..0000000000 --- a/Content.Server/Psionics/PsionicBonusChanceComponent.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Content.Server.Psionics -{ - [RegisterComponent] - public sealed partial class PsionicBonusChanceComponent : Component - { - [DataField("multiplier")] - public float Multiplier = 1f; - [DataField("flatBonus")] - public float FlatBonus = 0; - - /// - /// Whether we should warn the user they are about to receive psionics. - /// It's here because AddComponentSpecial can't overwrite a component, and this is very role dependent. - /// - [DataField("warn")] - public bool Warn = true; - } -} diff --git a/Content.Server/Psionics/PsionicsCommands.cs b/Content.Server/Psionics/PsionicsCommands.cs index 959251d1fb..565146629b 100644 --- a/Content.Server/Psionics/PsionicsCommands.cs +++ b/Content.Server/Psionics/PsionicsCommands.cs @@ -1,11 +1,11 @@ using Content.Server.Administration; using Content.Shared.Administration; using Content.Shared.Abilities.Psionics; -using Content.Shared.Mobs.Components; using Robust.Shared.Console; -using Robust.Server.GameObjects; -using Content.Shared.Actions; using Robust.Shared.Player; +using Content.Server.Abilities.Psionics; +using Robust.Shared.Prototypes; +using Content.Shared.Psionics; namespace Content.Server.Psionics; @@ -17,19 +17,49 @@ public sealed class ListPsionicsCommand : IConsoleCommand public string Help => Loc.GetString("command-lspsionic-help"); public async void Execute(IConsoleShell shell, string argStr, string[] args) { - SharedActionsSystem actions = default!; var entMan = IoCManager.Resolve(); - foreach (var (actor, mob, psionic, meta) in entMan.EntityQuery()){ - // filter out xenos, etc, with innate telepathy - actions.TryGetActionData( psionic.PsionicAbility, out var actionData ); - if (actionData == null || actionData.ToString() == null) - return; + foreach (var (actor, psionic, meta) in entMan.EntityQuery()) + { + var powerList = new List(); + foreach (var power in psionic.ActivePowers) + powerList.Add(power.Name); - var psiPowerName = actionData.ToString(); - if (psiPowerName == null) - return; + shell.WriteLine(meta.EntityName + " (" + meta.Owner + ") - " + actor.PlayerSession.Name + powerList); + } + } +} - shell.WriteLine(meta.EntityName + " (" + meta.Owner + ") - " + actor.PlayerSession.Name + Loc.GetString(psiPowerName)); +[AdminCommand(AdminFlags.Fun)] +public sealed class AddPsionicPowerCommand : IConsoleCommand +{ + public string Command => "addpsionicpower"; + public string Description => Loc.GetString("command-addpsionicpower-description"); + public string Help => Loc.GetString("command-addpsionicpower-help"); + public async void Execute(IConsoleShell shell, string argStr, string[] args) + { + var entMan = IoCManager.Resolve(); + var psionicPowers = entMan.System(); + var protoMan = IoCManager.Resolve(); + + if (args.Length != 2) + { + shell.WriteError(Loc.GetString("shell-need-exactly-one-argument")); + return; + } + + if (!EntityUid.TryParse(args[0], out var uid)) + { + shell.WriteError(Loc.GetString("addpsionicpower-args-one-error")); + return; } + + if (!protoMan.TryIndex(args[1], out var powerProto)) + { + shell.WriteError(Loc.GetString("addpsionicpower-args-two-error")); + return; + } + + entMan.EnsureComponent(uid, out var psionic); + psionicPowers.InitializePsionicPower(uid, powerProto, psionic); } } diff --git a/Content.Server/Psionics/PsionicsSystem.Events.cs b/Content.Server/Psionics/PsionicsSystem.Events.cs new file mode 100644 index 0000000000..f82621115d --- /dev/null +++ b/Content.Server/Psionics/PsionicsSystem.Events.cs @@ -0,0 +1,8 @@ + +namespace Content.Server.Psionics; + +/// +/// Raised on an entity about to roll for a Psionic Power, after their baseline chances of success are calculated. +/// +[ByRefEvent] +public record struct OnRollPsionicsEvent(EntityUid Roller, float BaselineChance); diff --git a/Content.Server/Psionics/PsionicsSystem.cs b/Content.Server/Psionics/PsionicsSystem.cs index fb5d18f284..9685334dab 100644 --- a/Content.Server/Psionics/PsionicsSystem.cs +++ b/Content.Server/Psionics/PsionicsSystem.cs @@ -5,140 +5,311 @@ using Content.Shared.Damage.Events; using Content.Shared.CCVar; using Content.Server.Abilities.Psionics; -using Content.Server.Chat.Systems; using Content.Server.Electrocution; using Content.Server.NPC.Components; using Content.Server.NPC.Systems; using Robust.Shared.Audio.Systems; using Robust.Shared.Configuration; using Robust.Shared.Random; +using Content.Shared.Popups; +using Content.Shared.Chat; +using Robust.Server.Player; +using Content.Server.Chat.Managers; +using Robust.Shared.Prototypes; +using Content.Shared.Mobs; +using Content.Shared.Damage; +using Content.Shared.Interaction.Events; -namespace Content.Server.Psionics +namespace Content.Server.Psionics; + +public sealed class PsionicsSystem : EntitySystem { - public sealed class PsionicsSystem : EntitySystem - { - [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly PsionicAbilitiesSystem _psionicAbilitiesSystem = default!; - [Dependency] private readonly StatusEffectsSystem _statusEffects = default!; - [Dependency] private readonly ElectrocutionSystem _electrocutionSystem = default!; - [Dependency] private readonly MindSwapPowerSystem _mindSwapPowerSystem = default!; - [Dependency] private readonly GlimmerSystem _glimmerSystem = default!; - [Dependency] private readonly NpcFactionSystem _npcFactonSystem = default!; - [Dependency] private readonly IConfigurationManager _cfg = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; - - /// - /// Unfortunately, since spawning as a normal role and anything else is so different, - /// this is the only way to unify them, for now at least. - /// - Queue<(PotentialPsionicComponent component, EntityUid uid)> _rollers = new(); - public override void Update(float frameTime) - { - base.Update(frameTime); - foreach (var roller in _rollers) - RollPsionics(roller.uid, roller.component, false); - _rollers.Clear(); - } - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnStartup); - SubscribeLocalEvent(OnMeleeHit); - SubscribeLocalEvent(OnStamHit); + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly PsionicAbilitiesSystem _psionicAbilitiesSystem = default!; + [Dependency] private readonly StatusEffectsSystem _statusEffects = default!; + [Dependency] private readonly ElectrocutionSystem _electrocutionSystem = default!; + [Dependency] private readonly MindSwapPowerSystem _mindSwapPowerSystem = default!; + [Dependency] private readonly GlimmerSystem _glimmerSystem = default!; + [Dependency] private readonly NpcFactionSystem _npcFactonSystem = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedPopupSystem _popups = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IChatManager _chatManager = default!; + [Dependency] private readonly IPrototypeManager _protoMan = default!; + [Dependency] private readonly PsionicFamiliarSystem _psionicFamiliar = default!; + [Dependency] private readonly NPCRetaliationSystem _retaliationSystem = default!; - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnRemove); - } + private const string BaselineAmplification = "Baseline Amplification"; + private const string BaselineDampening = "Baseline Dampening"; - private void OnStartup(EntityUid uid, PotentialPsionicComponent component, MapInitEvent args) - { - if (HasComp(uid)) - return; + // Yes these are a mirror of what's normally default datafields on the PsionicPowerPrototype. + // We haven't generated a prototype yet, and I'm not going to duplicate them on the PsionicComponent. + private const string PsionicRollFailedMessage = "psionic-roll-failed"; + private const string PsionicRollFailedColor = "#8A00C2"; + private const int PsionicRollFailedFontSize = 12; + private const ChatChannel PsionicRollFailedChatChannel = ChatChannel.Emotes; - _rollers.Enqueue((component, uid)); - } + /// + /// Unfortunately, since spawning as a normal role and anything else is so different, + /// this is the only way to unify them, for now at least. + /// + Queue<(PsionicComponent component, EntityUid uid)> _rollers = new(); + public override void Update(float frameTime) + { + base.Update(frameTime); + foreach (var roller in _rollers) + RollPsionics(roller.uid, roller.component, true); + _rollers.Clear(); + } + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnStartup); + SubscribeLocalEvent(OnMeleeHit); + SubscribeLocalEvent(OnStamHit); + SubscribeLocalEvent(OnMobstateChanged); + SubscribeLocalEvent(OnDamageChanged); + SubscribeLocalEvent(OnAttackAttempt); - private void OnMeleeHit(EntityUid uid, AntiPsionicWeaponComponent component, MeleeHitEvent args) - { - foreach (var entity in args.HitEntities) - { - if (HasComp(entity)) - { - _audio.PlayPvs("/Audio/Effects/lightburn.ogg", entity); - args.ModifiersList.Add(component.Modifiers); - if (_random.Prob(component.DisableChance)) - _statusEffects.TryAddStatusEffect(entity, "PsionicsDisabled", TimeSpan.FromSeconds(10), true, "PsionicsDisabled"); - } - - if (TryComp(entity, out var swapped)) - { - _mindSwapPowerSystem.Swap(entity, swapped.OriginalEntity, true); - return; - } - - if (component.Punish && HasComp(entity) && !HasComp(entity) && _random.Prob(0.5f)) - _electrocutionSystem.TryDoElectrocution(args.User, null, 20, TimeSpan.FromSeconds(5), false); - } - } + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnRemove); + } - private void OnInit(EntityUid uid, PsionicComponent component, ComponentInit args) - { - if (!component.Removable - || !TryComp(uid, out var factions) - || _npcFactonSystem.ContainsFaction(uid, "GlimmerMonster", factions)) - return; + private void OnStartup(EntityUid uid, PsionicComponent component, MapInitEvent args) + { + if (!component.Removable + || !component.CanReroll) + return; - _npcFactonSystem.AddFaction(uid, "PsionicInterloper"); - } + CheckPowerCost(uid, component); + _rollers.Enqueue((component, uid)); + } + + /// + /// On MapInit, PsionicComponent isn't going to contain any powers. + /// So before we send a Latent Psychic into the roundstart roll queue, we need to calculate their power cost in advance. + /// + private void CheckPowerCost(EntityUid uid, PsionicComponent component) + { + if (!TryComp(uid, out var innate)) + return; - private void OnRemove(EntityUid uid, PsionicComponent component, ComponentRemove args) + var powerCount = 0; + foreach (var powerId in innate.PowersToAdd) + if (_protoMan.TryIndex(powerId, out var power)) + powerCount += power.PowerSlotCost; + + component.NextPowerCost = 100 * MathF.Pow(2, powerCount); + } + + private void OnMeleeHit(EntityUid uid, AntiPsionicWeaponComponent component, MeleeHitEvent args) + { + foreach (var entity in args.HitEntities) + CheckAntiPsionic(entity, component, args); + } + + private void CheckAntiPsionic(EntityUid entity, AntiPsionicWeaponComponent component, MeleeHitEvent args) + { + if (HasComp(entity)) { - if (!HasComp(uid)) + _audio.PlayPvs("/Audio/Effects/lightburn.ogg", entity); + args.ModifiersList.Add(component.Modifiers); + + if (!_random.Prob(component.DisableChance)) return; - _npcFactonSystem.RemoveFaction(uid, "PsionicInterloper"); + _statusEffects.TryAddStatusEffect(entity, component.DisableStatus, TimeSpan.FromSeconds(component.DisableDuration), true, component.DisableStatus); } - private void OnStamHit(EntityUid uid, AntiPsionicWeaponComponent component, TakeStaminaDamageEvent args) - { - if (HasComp(args.Target)) - args.FlatModifier += component.PsychicStaminaDamage; - } + if (TryComp(entity, out var swapped)) + _mindSwapPowerSystem.Swap(entity, swapped.OriginalEntity, true); - public void RollPsionics(EntityUid uid, PotentialPsionicComponent component, bool applyGlimmer = true, float multiplier = 1f) - { - if (HasComp(uid) - || !_cfg.GetCVar(CCVars.PsionicRollsEnabled)) - return; + if (!component.Punish + || HasComp(entity) + || !_random.Prob(component.PunishChances)) + return; + + _electrocutionSystem.TryDoElectrocution(args.User, null, component.PunishSelfDamage, TimeSpan.FromSeconds(component.PunishStunDuration), false); + } + + private void OnInit(EntityUid uid, PsionicComponent component, ComponentStartup args) + { + component.AmplificationSources.Add(BaselineAmplification, _random.NextFloat(component.BaselineAmplification.Item1, component.BaselineAmplification.Item2)); + component.DampeningSources.Add(BaselineDampening, _random.NextFloat(component.BaselineDampening.Item1, component.BaselineDampening.Item2)); + + if (!component.Removable + || !TryComp(uid, out var factions) + || _npcFactonSystem.ContainsFaction(uid, "GlimmerMonster", factions)) + return; + + _npcFactonSystem.AddFaction(uid, "PsionicInterloper"); + } + + private void OnRemove(EntityUid uid, PsionicComponent component, ComponentRemove args) + { + if (!HasComp(uid)) + return; + + _npcFactonSystem.RemoveFaction(uid, "PsionicInterloper"); + } - var chance = component.Chance; - var warn = true; - if (TryComp(uid, out var bonus)) - { - chance *= bonus.Multiplier; - chance += bonus.FlatBonus; - warn = bonus.Warn; - } + private void OnStamHit(EntityUid uid, AntiPsionicWeaponComponent component, TakeStaminaDamageEvent args) + { + if (!HasComp(args.Target)) + return; - if (applyGlimmer) - chance += ((float) _glimmerSystem.Glimmer / 1000); + args.FlatModifier += component.PsychicStaminaDamage; + } - chance *= multiplier; + /// + /// Now we handle Potentia calculations, the more powers you have, the harder it is to obtain psionics, but the content of your roll carries over to the next roll. + /// Your first power costs 100(2^0 is always 1), your second power costs 200, your 3rd power costs 400, and so on. This also considers people with roundstart powers. + /// Such that a Mystagogue(who has 3 powers at roundstart) needs 800 Potentia to gain his 4th power. + /// + /// + /// This exponential cost is mainly done to prevent stations from becoming "Space Hogwarts", + /// which was a common complaint with Psionic Refactor opening up the opportunity for people to have multiple powers. + /// + private bool HandlePotentiaCalculations(EntityUid uid, PsionicComponent component, float psionicChance) + { + component.Potentia += _random.NextFloat(0 + psionicChance, 100 + psionicChance); + + if (component.Potentia < component.NextPowerCost) + return false; + + component.Potentia -= component.NextPowerCost; + _psionicAbilitiesSystem.AddPsionics(uid); + component.NextPowerCost = 100 * MathF.Pow(2, component.PowerSlotsTaken); + return true; + } + + /// + /// Provide the player with feedback about their roll failure, so they don't just think nothing happened. + /// TODO: Add an audio cue to this and other areas of psionic player feedback. + /// + private void HandleRollFeedback(EntityUid uid) + { + if (!_playerManager.TryGetSessionByEntity(uid, out var session) + || !Loc.TryGetString(PsionicRollFailedMessage, out var rollFailedMessage)) + return; - chance = Math.Clamp(chance, 0, 1); + _popups.PopupEntity(rollFailedMessage, uid, uid, PopupType.MediumCaution); - if (_random.Prob(chance)) - _psionicAbilitiesSystem.AddPsionics(uid, warn); + // Popups only last a few seconds, and are easily ignored. + // So we also put a message in chat to make it harder to miss. + var feedbackMessage = $"[font size={PsionicRollFailedFontSize}][color={PsionicRollFailedColor}]{rollFailedMessage}[/color][/font]"; + _chatManager.ChatMessageToOne( + PsionicRollFailedChatChannel, + feedbackMessage, + feedbackMessage, + EntityUid.Invalid, + false, + session.Channel); + } + + /// + /// This function attempts to generate a psionic power by incrementing a Psion's Potentia stat by a random amount, then checking if it beats a certain threshold. + /// Please consider going through RerollPsionics or PsionicAbilitiesSystem.InitializePsionicPower instead of this function, particularly if you don't have a good reason to call this directly. + /// + public void RollPsionics(EntityUid uid, PsionicComponent component, bool applyGlimmer = true, float rollEventMultiplier = 1f) + { + if (!_cfg.GetCVar(CCVars.PsionicRollsEnabled) + || !component.Removable) + return; + + // Calculate the initial odds based on the innate potential + var baselineChance = component.Chance + * component.PowerRollMultiplier + + component.PowerRollFlatBonus + + _random.NextFloat(0, 100); + + // Increase the initial odds based on Glimmer. + // TODO: Change this equation when I do my Glimmer Refactor + baselineChance += applyGlimmer + ? (float) _glimmerSystem.Glimmer / 1000 //Convert from Glimmer to %chance + : 0; + + // Certain sources of power rolls provide their own multiplier. + baselineChance *= rollEventMultiplier; + + // Ask if the Roller has any other effects to contribute, such as Traits. + var ev = new OnRollPsionicsEvent(uid, baselineChance); + RaiseLocalEvent(uid, ref ev); + + if (HandlePotentiaCalculations(uid, component, ev.BaselineChance)) + return; + + HandleRollFeedback(uid); + } + + /// + /// Each person has a single free reroll for their Psionics, which certain conditions can restore. + /// This function attempts to "Spend" a reroll, if one is available. + /// + public void RerollPsionics(EntityUid uid, PsionicComponent? psionic = null, float bonusMuliplier = 1f) + { + if (!Resolve(uid, ref psionic, false) + || !psionic.Removable + || !psionic.CanReroll) + return; + + RollPsionics(uid, psionic, true, bonusMuliplier); + psionic.CanReroll = false; + } + + private void OnMobstateChanged(EntityUid uid, PsionicComponent component, MobStateChangedEvent args) + { + if (component.Familiars.Count <= 0 + || args.NewMobState != MobState.Dead) + return; + + foreach (var familiar in component.Familiars) + { + if (!TryComp(familiar, out var familiarComponent) + || !familiarComponent.DespawnOnMasterDeath) + continue; + + _psionicFamiliar.DespawnFamiliar(familiar, familiarComponent); } + } - public void RerollPsionics(EntityUid uid, PotentialPsionicComponent? psionic = null, float bonusMuliplier = 1f) + /// + /// When a caster with active summons is attacked, aggro their familiars to the attacker. + /// + private void OnDamageChanged(EntityUid uid, PsionicComponent component, DamageChangedEvent args) + { + if (component.Familiars.Count <= 0 + || !args.DamageIncreased + || args.Origin is not { } origin + || origin == uid) + return; + + SetFamiliarTarget(origin, component); + } + + /// + /// When a caster with active summons attempts to attack something, aggro their familiars to the target. + /// + private void OnAttackAttempt(EntityUid uid, PsionicComponent component, AttackAttemptEvent args) + { + if (component.Familiars.Count <= 0 + || args.Target == uid + || args.Target is not { } target + || component.Familiars.Contains(target)) + return; + + SetFamiliarTarget(target, component); + } + + private void SetFamiliarTarget(EntityUid target, PsionicComponent component) + { + foreach (var familiar in component.Familiars) { - if (!Resolve(uid, ref psionic, false) - || psionic.Rerolled) - return; + if (!TryComp(familiar, out var retaliationComponent)) + continue; - RollPsionics(uid, psionic, multiplier: bonusMuliplier); - psionic.Rerolled = true; + _retaliationSystem.TryRetaliate(familiar, target, retaliationComponent); } } } diff --git a/Content.Server/Punpun/PunpunComponent.cs b/Content.Server/Punpun/PunpunComponent.cs new file mode 100644 index 0000000000..19d2da42c9 --- /dev/null +++ b/Content.Server/Punpun/PunpunComponent.cs @@ -0,0 +1,9 @@ +namespace Content.Server.Punpun; + +[RegisterComponent] +public sealed partial class PunpunComponent : Component +{ + /// How many rounds Punpun will be around for before disappearing with a note + [DataField] + public int Lifetime = 14; +} diff --git a/Content.Server/Punpun/PunpunSystem.cs b/Content.Server/Punpun/PunpunSystem.cs new file mode 100644 index 0000000000..5f1f22b42a --- /dev/null +++ b/Content.Server/Punpun/PunpunSystem.cs @@ -0,0 +1,114 @@ +using System.Linq; +using Content.Server.GameTicking; +using Content.Shared.Containers.ItemSlots; +using Content.Shared.Inventory; +using Content.Shared.Mobs; +using Content.Shared.Mobs.Components; +using Robust.Server.GameObjects; + +namespace Content.Server.Punpun; + +public sealed class PunpunSystem : EntitySystem +{ + [Dependency] private readonly InventorySystem _inventory = default!; + [Dependency] private readonly ServerMetaDataSystem _meta = default!; + + private (int, string, string, string) _punpunData = (1, string.Empty, string.Empty, string.Empty); + + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnRoundStart); + SubscribeLocalEvent(OnRoundEnd); + } + + + // Checks if the Punpun data has any items to equip, and names the Punpun upon initialization + private void OnRoundStart(EntityUid uid, PunpunComponent component, ComponentStartup args) + { + if (_punpunData.Item1 > component.Lifetime) + { + EntityManager.SpawnEntity("PaperWrittenPunpunNote", Transform(uid).Coordinates); + EntityManager.QueueDeleteEntity(uid); + _punpunData = (1, string.Empty, string.Empty, string.Empty); + + return; + } + + var meta = MetaData(uid); + _meta.SetEntityName(uid, $"{meta.EntityName} {ToRomanNumeral(_punpunData.Item1)}", meta); + + if (!EntityManager.TryGetComponent(uid, out _)) + return; + EquipItem(uid, "head", _punpunData.Item2); + EquipItem(uid, "mask", _punpunData.Item3); + EquipItem(uid, "jumpsuit", _punpunData.Item4); + } + + // Checks if Punpun exists, and is alive at round end + // If so, stores the items and increments the Punpun count + private void OnRoundEnd(RoundEndTextAppendEvent ev) + { + // I couldn't find a method to get a single entity, so this just enumerates over the first and disposes it + var punpunComponents = EntityManager.EntityQueryEnumerator(); + punpunComponents.MoveNext(out var punpun, out _); + + if (!EntityManager.TryGetComponent(punpun, out var mobState) + || mobState.CurrentState == MobState.Dead) + _punpunData = (1, string.Empty, string.Empty, string.Empty); + + _punpunData.Item1++; + + if (EntityManager.HasComponent(punpun)) + { + _punpunData.Item2 = CheckSlot(punpun, "head"); + _punpunData.Item3 = CheckSlot(punpun, "mask"); + _punpunData.Item4 = CheckSlot(punpun, "jumpsuit"); + } + + punpunComponents.Dispose(); + } + + // Equips an item to a slot, and names it. + private void EquipItem(EntityUid uid, string slot, string item) + { + if (item == string.Empty) + return; + + var itemEnt = EntityManager.SpawnEntity(item, EntityManager.GetComponent(uid).Coordinates); + if (_inventory.TryEquip(uid, itemEnt, slot, true, true)) + _meta.SetEntityName(itemEnt, $"{MetaData(uid).EntityName}'s {MetaData(itemEnt).EntityName}"); + else + EntityManager.DeleteEntity(itemEnt); + } + + // Checks if an item exists in a slot, and returns its name + private string CheckSlot(EntityUid uid, string slot) + { + return _inventory.TryGetSlotEntity(uid, slot, out var item) + ? EntityManager.GetComponent(item.Value).EntityPrototype!.ID + : string.Empty; + } + + + // Punpun, the lord of Roman Numerals + public static List RomanNumerals = new() { "M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I" }; + public static List Numerals = new() { 1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1 }; + + public static string ToRomanNumeral(int number) + { + var romanNumeral = string.Empty; + while (number > 0) + { + // Find the biggest numeral that is less than equal to number + var index = Numerals.FindIndex(x => x <= number); + // Subtract its value from your number + number -= Numerals[index]; + // Add it onto the end of your roman numeral + romanNumeral += RomanNumerals[index]; + } + return romanNumeral; + } +} diff --git a/Content.Server/Radiation/Systems/RadiationSystem.GridCast.cs b/Content.Server/Radiation/Systems/RadiationSystem.GridCast.cs index b8193c4d2f..ccee7cf227 100644 --- a/Content.Server/Radiation/Systems/RadiationSystem.GridCast.cs +++ b/Content.Server/Radiation/Systems/RadiationSystem.GridCast.cs @@ -195,11 +195,11 @@ private RadiationRay Gridcast(Entity grid, RadiationRay ray, b Vector2 srcLocal = sourceTrs.ParentUid == grid.Owner ? sourceTrs.LocalPosition - : gridTrs.InvLocalMatrix.Transform(ray.Source); + : Vector2.Transform(ray.Source, gridTrs.InvLocalMatrix); Vector2 dstLocal = destTrs.ParentUid == grid.Owner ? destTrs.LocalPosition - : gridTrs.InvLocalMatrix.Transform(ray.Destination); + : Vector2.Transform(ray.Destination, gridTrs.InvLocalMatrix); Vector2i sourceGrid = new( (int) Math.Floor(srcLocal.X / grid.Comp.TileSize), diff --git a/Content.Server/Radio/Components/RadioJammerComponent.cs b/Content.Server/Radio/Components/RadioJammerComponent.cs deleted file mode 100644 index 93504ef957..0000000000 --- a/Content.Server/Radio/Components/RadioJammerComponent.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Content.Server.Radio.EntitySystems; - -namespace Content.Server.Radio.Components; - -/// -/// When activated () prevents from sending messages in range -/// -[RegisterComponent] -[Access(typeof(JammerSystem))] -public sealed partial class RadioJammerComponent : Component -{ - [DataField("range"), ViewVariables(VVAccess.ReadWrite)] - public float Range = 8f; - - /// - /// Power usage per second when enabled - /// - [DataField("wattage"), ViewVariables(VVAccess.ReadWrite)] - public float Wattage = 2f; -} diff --git a/Content.Server/Radio/EntitySystems/JammerSystem.cs b/Content.Server/Radio/EntitySystems/JammerSystem.cs index bba8c4766e..6e0689390e 100644 --- a/Content.Server/Radio/EntitySystems/JammerSystem.cs +++ b/Content.Server/Radio/EntitySystems/JammerSystem.cs @@ -1,27 +1,22 @@ using Content.Server.DeviceNetwork.Components; -using Content.Server.DeviceNetwork.Systems; -using Content.Server.Medical.CrewMonitoring; using Content.Server.Medical.SuitSensors; -using Content.Server.Popups; using Content.Server.Power.EntitySystems; using Content.Server.PowerCell; using Content.Server.Radio.Components; -using Content.Server.Station.Systems; using Content.Shared.DeviceNetwork.Components; using Content.Shared.Examine; using Content.Shared.Interaction; using Content.Shared.PowerCell.Components; +using Content.Shared.RadioJammer; +using Content.Shared.Radio.EntitySystems; namespace Content.Server.Radio.EntitySystems; -public sealed class JammerSystem : EntitySystem +public sealed class JammerSystem : SharedJammerSystem { [Dependency] private readonly PowerCellSystem _powerCell = default!; [Dependency] private readonly BatterySystem _battery = default!; - [Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; - [Dependency] private readonly StationSystem _stationSystem = default!; - [Dependency] private readonly SingletonDeviceNetServerSystem _singletonServerSystem = default!; public override void Initialize() { @@ -37,14 +32,37 @@ public override void Initialize() public override void Update(float frameTime) { var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var _, out var jam)) { - if (_powerCell.TryGetBatteryFromSlot(uid, out var batteryUid, out var battery) && - !_battery.TryUseCharge(batteryUid.Value, jam.Wattage * frameTime, battery)) + + if (_powerCell.TryGetBatteryFromSlot(uid, out var batteryUid, out var battery)) { - RemComp(uid); - RemComp(uid); + if (!_battery.TryUseCharge(batteryUid.Value, GetCurrentWattage(jam) * frameTime, battery)) + { + ChangeLEDState(false, uid); + RemComp(uid); + RemComp(uid); + } + else + { + var percentCharged = battery.CurrentCharge / battery.MaxCharge; + if (percentCharged > .50) + { + ChangeChargeLevel(RadioJammerChargeLevel.High, uid); + } + else if (percentCharged < .15) + { + ChangeChargeLevel(RadioJammerChargeLevel.Low, uid); + } + else + { + ChangeChargeLevel(RadioJammerChargeLevel.Medium, uid); + } + } + } + } } @@ -52,40 +70,49 @@ private void OnActivate(EntityUid uid, RadioJammerComponent comp, ActivateInWorl { var activated = !HasComp(uid) && _powerCell.TryGetBatteryFromSlot(uid, out var battery) && - battery.CurrentCharge > comp.Wattage; + battery.CurrentCharge > GetCurrentWattage(comp); if (activated) { + ChangeLEDState(true, uid); EnsureComp(uid); EnsureComp(uid, out var jammingComp); - jammingComp.Range = comp.Range; + jammingComp.Range = GetCurrentRange(comp); jammingComp.JammableNetworks.Add(DeviceNetworkComponent.DeviceNetIdDefaults.Wireless.ToString()); Dirty(uid, jammingComp); } else { - RemComp(uid); - RemComp(uid); + ChangeLEDState(false, uid); + RemCompDeferred(uid); + RemCompDeferred(uid); } var state = Loc.GetString(activated ? "radio-jammer-component-on-state" : "radio-jammer-component-off-state"); var message = Loc.GetString("radio-jammer-component-on-use", ("state", state)); - _popup.PopupEntity(message, args.User, args.User); + Popup.PopupEntity(message, args.User, args.User); args.Handled = true; } private void OnPowerCellChanged(EntityUid uid, ActiveRadioJammerComponent comp, PowerCellChangedEvent args) { if (args.Ejected) - RemComp(uid); + { + ChangeLEDState(false, uid); + RemCompDeferred(uid); + } } private void OnExamine(EntityUid uid, RadioJammerComponent comp, ExaminedEvent args) { if (args.IsInDetailsRange) { - var msg = HasComp(uid) + var powerIndicator = HasComp(uid) ? Loc.GetString("radio-jammer-component-examine-on-state") : Loc.GetString("radio-jammer-component-examine-off-state"); - args.PushMarkup(msg); + args.PushMarkup(powerIndicator); + + var powerLevel = Loc.GetString(comp.Settings[comp.SelectedPowerLevel].Name); + var switchIndicator = Loc.GetString("radio-jammer-component-switch-setting", ("powerLevel", powerLevel)); + args.PushMarkup(switchIndicator); } } @@ -112,7 +139,7 @@ private bool ShouldCancelSend(EntityUid sourceUid) while (query.MoveNext(out _, out _, out var jam, out var transform)) { - if (source.InRange(EntityManager, _transform, transform.Coordinates, jam.Range)) + if (source.InRange(EntityManager, _transform, transform.Coordinates, GetCurrentRange(jam))) { return true; } diff --git a/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs b/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs index fc3f69a3ba..6ade9aea34 100644 --- a/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs +++ b/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs @@ -203,11 +203,14 @@ private void OnAttemptListen(EntityUid uid, RadioMicrophoneComponent component, private void OnReceiveRadio(EntityUid uid, RadioSpeakerComponent component, ref RadioReceiveEvent args) { - var nameEv = new TransformSpeakerNameEvent(args.MessageSource, Name(args.MessageSource)); + if (uid == args.RadioSource) + return; + + var nameEv = new TransformSpeakerSpeechEvent(args.MessageSource, Name(args.MessageSource)); RaiseLocalEvent(args.MessageSource, nameEv); var name = Loc.GetString("speech-name-relay", ("speaker", Name(uid)), - ("originalName", nameEv.Name)); + ("originalName", nameEv.VoiceName ?? Name(args.MessageSource))); // log to chat so people can identity the speaker/source, but avoid clogging ghost chat if there are many radios var message = args.OriginalChatMsg.Message; // The chat system will handle the rest and re-obfuscate if needed. @@ -221,25 +224,25 @@ private void OnBeforeIntercomUiOpen(EntityUid uid, IntercomComponent component, private void OnToggleIntercomMic(EntityUid uid, IntercomComponent component, ToggleIntercomMicMessage args) { - if (component.RequiresPower && !this.IsPowered(uid, EntityManager) || args.Session.AttachedEntity is not { } user) + if (component.RequiresPower && !this.IsPowered(uid, EntityManager)) return; - SetMicrophoneEnabled(uid, user, args.Enabled, true); + SetMicrophoneEnabled(uid, args.Actor, args.Enabled, true); UpdateIntercomUi(uid, component); } private void OnToggleIntercomSpeaker(EntityUid uid, IntercomComponent component, ToggleIntercomSpeakerMessage args) { - if (component.RequiresPower && !this.IsPowered(uid, EntityManager) || args.Session.AttachedEntity is not { } user) + if (component.RequiresPower && !this.IsPowered(uid, EntityManager)) return; - SetSpeakerEnabled(uid, user, args.Enabled, true); + SetSpeakerEnabled(uid, args.Actor, args.Enabled, true); UpdateIntercomUi(uid, component); } private void OnSelectIntercomChannel(EntityUid uid, IntercomComponent component, SelectIntercomChannelMessage args) { - if (component.RequiresPower && !this.IsPowered(uid, EntityManager) || args.Session.AttachedEntity is not { }) + if (component.RequiresPower && !this.IsPowered(uid, EntityManager)) return; if (!_protoMan.TryIndex(args.Channel, out _) || !component.SupportedChannels.Contains(args.Channel)) @@ -262,6 +265,6 @@ private void UpdateIntercomUi(EntityUid uid, IntercomComponent component) var availableChannels = component.SupportedChannels; var selectedChannel = micComp?.BroadcastChannel ?? SharedChatSystem.CommonChannel; var state = new IntercomBoundUIState(micEnabled, speakerEnabled, availableChannels, selectedChannel); - _ui.TrySetUiState(uid, IntercomUiKey.Key, state); + _ui.SetUiState(uid, IntercomUiKey.Key, state); } } diff --git a/Content.Server/Radio/EntitySystems/RadioSystem.cs b/Content.Server/Radio/EntitySystems/RadioSystem.cs index 5fce6f770a..71fb4ff502 100644 --- a/Content.Server/Radio/EntitySystems/RadioSystem.cs +++ b/Content.Server/Radio/EntitySystems/RadioSystem.cs @@ -3,8 +3,6 @@ using Content.Server.Language; using Content.Server.Power.Components; using Content.Server.Radio.Components; -using Content.Server.Speech; -using Content.Server.VoiceMask; using Content.Shared.Chat; using Content.Shared.Database; using Content.Shared.Language; @@ -92,28 +90,13 @@ public void SendRadioMessage(EntityUid messageSource, string message, RadioChann if (!_messages.Add(message)) return; - var name = TryComp(messageSource, out VoiceMaskComponent? mask) && mask.Enabled - ? mask.VoiceName - : MetaData(messageSource).EntityName; - - // Delta-V: Support syrinx voice mask on radio. - if (TryComp(messageSource, out SyrinxVoiceMaskComponent? syrinx) && syrinx.Enabled) - name = syrinx.VoiceName; + var evt = new TransformSpeakerSpeechEvent(messageSource, Name(messageSource)); + RaiseLocalEvent(messageSource, evt); + var name = evt.VoiceName ?? Name(messageSource); name = FormattedMessage.EscapeText(name); // most radios are relayed to chat, so lets parse the chat message beforehand - SpeechVerbPrototype speech; - if (mask != null - && mask.Enabled - && mask.SpeechVerb != null - && _prototype.TryIndex(mask.SpeechVerb, out var proto)) - { - speech = proto; - } - else - speech = _chat.GetSpeechVerb(messageSource, message); - var content = escapeMarkup ? FormattedMessage.EscapeText(message) : message; @@ -126,7 +109,7 @@ public void SendRadioMessage(EntityUid messageSource, string message, RadioChann var obfuscatedWrapped = WrapRadioMessage(messageSource, channel, name, obfuscated, language); var notUdsMsg = new ChatMessage(ChatChannel.Radio, obfuscated, obfuscatedWrapped, NetEntity.Invalid, null); - var ev = new RadioReceiveEvent(messageSource, channel, msg, notUdsMsg, language); + var ev = new RadioReceiveEvent(messageSource, channel, msg, notUdsMsg, language, radioSource); var sendAttemptEv = new RadioSendAttemptEvent(channel, radioSource); RaiseLocalEvent(ref sendAttemptEv); @@ -183,6 +166,9 @@ private string WrapRadioMessage(EntityUid source, RadioChannelPrototype channel, var languageColor = channel.Color; if (language.SpeechOverride.Color is { } colorOverride) languageColor = Color.InterpolateBetween(languageColor, colorOverride, colorOverride.A); + var languageDisplay = language.IsVisibleLanguage + ? $"{language.ChatName} | " + : ""; return Loc.GetString(speech.Bold ? "chat-radio-message-wrap-bold" : "chat-radio-message-wrap", ("color", channel.Color), @@ -192,7 +178,8 @@ private string WrapRadioMessage(EntityUid source, RadioChannelPrototype channel, ("verb", Loc.GetString(_random.Pick(speech.SpeechVerbStrings))), ("channel", $"\\[{channel.LocalizedName}\\]"), ("name", name), - ("message", message)); + ("message", message), + ("language", languageDisplay)); } /// diff --git a/Content.Server/Radio/IntrinsicRadioKeySystem.cs b/Content.Server/Radio/IntrinsicRadioKeySystem.cs new file mode 100644 index 0000000000..eeea64c2f7 --- /dev/null +++ b/Content.Server/Radio/IntrinsicRadioKeySystem.cs @@ -0,0 +1,32 @@ +using Content.Server.Radio.Components; +using Content.Shared.Radio; +using Content.Shared.Radio.Components; + +namespace Content.Server.Radio; + +public sealed class IntrinsicRadioKeySystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnTransmitterChannelsChanged); + SubscribeLocalEvent(OnReceiverChannelsChanged); + } + + private void OnTransmitterChannelsChanged(EntityUid uid, IntrinsicRadioTransmitterComponent component, EncryptionChannelsChangedEvent args) + { + UpdateChannels(uid, args.Component, ref component.Channels); + } + + private void OnReceiverChannelsChanged(EntityUid uid, ActiveRadioComponent component, EncryptionChannelsChangedEvent args) + { + UpdateChannels(uid, args.Component, ref component.Channels); + } + + private void UpdateChannels(EntityUid _, EncryptionKeyHolderComponent keyHolderComp, ref HashSet channels) + { + channels.Clear(); + channels.UnionWith(keyHolderComp.Channels); + } +} diff --git a/Content.Server/Radio/RadioEvent.cs b/Content.Server/Radio/RadioEvent.cs index 35220d1d75..1198f7c133 100644 --- a/Content.Server/Radio/RadioEvent.cs +++ b/Content.Server/Radio/RadioEvent.cs @@ -10,12 +10,12 @@ namespace Content.Server.Radio; /// [ByRefEvent] public readonly record struct RadioReceiveEvent( - // Einstein-Engines - languages mechanic EntityUid MessageSource, RadioChannelPrototype Channel, ChatMessage OriginalChatMsg, ChatMessage LanguageObfuscatedChatMsg, - LanguagePrototype Language + LanguagePrototype Language, + EntityUid RadioSource ); /// diff --git a/Content.Server/RandomMetadata/RandomMetadataSystem.cs b/Content.Server/RandomMetadata/RandomMetadataSystem.cs index c088d57fd9..abab5e5fc3 100644 --- a/Content.Server/RandomMetadata/RandomMetadataSystem.cs +++ b/Content.Server/RandomMetadata/RandomMetadataSystem.cs @@ -1,4 +1,4 @@ -using Content.Shared.Dataset; +using Content.Shared.Dataset; using JetBrains.Annotations; using Robust.Shared.Prototypes; using Robust.Shared.Random; @@ -47,9 +47,16 @@ public string GetRandomFromSegments(List segments, string? separator) var outputSegments = new List(); foreach (var segment in segments) { - outputSegments.Add(_prototype.TryIndex(segment, out var proto) - ? Loc.GetString(_random.Pick(proto.Values)) - : Loc.GetString(segment)); + if (_prototype.TryIndex(segment, out var proto)) { + var random = _random.Pick(proto.Values); + if (Loc.TryGetString(random, out var localizedSegment)) + outputSegments.Add(localizedSegment); + else + outputSegments.Add(random); + } else if (Loc.TryGetString(segment, out var localizedSegment)) + outputSegments.Add(localizedSegment); + else + outputSegments.Add(segment); } return string.Join(separator, outputSegments); } diff --git a/Content.Server/Repairable/RepairableComponent.cs b/Content.Server/Repairable/RepairableComponent.cs index c436386110..bab70f66b5 100644 --- a/Content.Server/Repairable/RepairableComponent.cs +++ b/Content.Server/Repairable/RepairableComponent.cs @@ -1,5 +1,6 @@ using Content.Shared.Damage; using Content.Shared.Tools; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; namespace Content.Server.Repairable @@ -14,28 +15,28 @@ public sealed partial class RepairableComponent : Component /// If this data-field is specified, it will change damage by this amount instead of setting all damage to 0. /// in order to heal/repair the damage values have to be negative. /// - [ViewVariables(VVAccess.ReadWrite)] [DataField("damage")] + [DataField] public DamageSpecifier? Damage; - [ViewVariables(VVAccess.ReadWrite)] [DataField("fuelCost")] + [DataField] public int FuelCost = 5; - [ViewVariables(VVAccess.ReadWrite)] [DataField("qualityNeeded", customTypeSerializer:typeof(PrototypeIdSerializer))] - public string QualityNeeded = "Welding"; + [DataField] + public ProtoId QualityNeeded = "Welding"; - [ViewVariables(VVAccess.ReadWrite)] [DataField("doAfterDelay")] + [DataField] public int DoAfterDelay = 1; /// /// A multiplier that will be applied to the above if an entity is repairing themselves. /// - [ViewVariables(VVAccess.ReadWrite)] [DataField("selfRepairPenalty")] + [DataField] public float SelfRepairPenalty = 3f; /// /// Whether or not an entity is allowed to repair itself. /// - [DataField("allowSelfRepair")] + [DataField] public bool AllowSelfRepair = true; } } diff --git a/Content.Server/Repairable/RepairableSystem.cs b/Content.Server/Repairable/RepairableSystem.cs index 5bd580756d..ec24cd8197 100644 --- a/Content.Server/Repairable/RepairableSystem.cs +++ b/Content.Server/Repairable/RepairableSystem.cs @@ -4,7 +4,6 @@ using Content.Shared.Interaction; using Content.Shared.Popups; using Content.Shared.Repairable; -using Content.Shared.Tools; using SharedToolSystem = Content.Shared.Tools.Systems.SharedToolSystem; namespace Content.Server.Repairable @@ -70,7 +69,7 @@ public async void Repair(EntityUid uid, RepairableComponent component, InteractU } // Run the repairing doafter - args.Handled = _toolSystem.UseTool(args.Used, args.User, uid, delay, component.QualityNeeded, new RepairFinishedEvent()); + args.Handled = _toolSystem.UseTool(args.Used, args.User, uid, delay, component.QualityNeeded, new RepairFinishedEvent(), component.FuelCost); } } } diff --git a/Content.Server/Research/Oracle/OracleSystem.cs b/Content.Server/Research/Oracle/OracleSystem.cs index 63dcefbadd..15f0a47447 100644 --- a/Content.Server/Research/Oracle/OracleSystem.cs +++ b/Content.Server/Research/Oracle/OracleSystem.cs @@ -1,11 +1,11 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using Content.Server.Botany; +using Content.Server.Chat; using Content.Server.Chat.Managers; using Content.Server.Chat.Systems; using Content.Server.Chemistry.Containers.EntitySystems; using Content.Server.Fluids.EntitySystems; -using Content.Server.Psionics; using Content.Server.Research.Systems; using Content.Shared.Abilities.Psionics; using Content.Shared.Chat; @@ -14,6 +14,7 @@ using Content.Shared.Interaction; using Content.Shared.Mobs.Components; using Content.Shared.Psionics.Glimmer; +using Content.Shared.Psionics.Passives; using Content.Shared.Random.Helpers; using Content.Shared.Research.Components; using Content.Shared.Research.Prototypes; @@ -30,6 +31,7 @@ namespace Content.Server.Research.Oracle; public sealed class OracleSystem : EntitySystem { [Dependency] private readonly ChatSystem _chat = default!; + [Dependency] private readonly TelepathicChatSystem _tChat = default!; [Dependency] private readonly IChatManager _chatMan = default!; [Dependency] private readonly GlimmerSystem _glimmer = default!; [Dependency] private readonly IPrototypeManager _protoMan = default!; @@ -74,16 +76,16 @@ public override void Initialize() private void OnInteractHand(Entity oracle, ref InteractHandEvent args) { - if (!HasComp(args.User) || HasComp(args.User) + if (!HasComp(args.User) || HasComp(args.User) || !TryComp(args.User, out var actor)) return; SendTelepathicInfo(oracle, actor.PlayerSession.Channel, - Loc.GetString("oracle-current-item", ("item", oracle.Comp.DesiredPrototype.Name))); + Loc.GetString("oracle-current-item", ("item", oracle.Comp.DesiredPrototype.Name)), HasComp(args.User)); if (oracle.Comp.LastDesiredPrototype != null) SendTelepathicInfo(oracle, actor.PlayerSession.Channel, - Loc.GetString("oracle-previous-item", ("item", oracle.Comp.LastDesiredPrototype.Name))); + Loc.GetString("oracle-previous-item", ("item", oracle.Comp.LastDesiredPrototype.Name)), HasComp(args.User)); } private void OnInteractUsing(Entity oracle, ref InteractUsingEvent args) @@ -124,14 +126,25 @@ private void OnInteractUsing(Entity oracle, ref InteractUsingEv NextItem(oracle); } - private void SendTelepathicInfo(Entity oracle, INetChannel client, string message) + private void SendTelepathicInfo(Entity oracle, INetChannel client, string message, bool psychognomist = false) { - var messageWrap = Loc.GetString("chat-manager-send-telepathic-chat-wrap-message", - ("telepathicChannelName", Loc.GetString("chat-manager-telepathic-channel-name")), - ("message", message)); + if (!psychognomist) + { + var messageWrap = Loc.GetString("chat-manager-send-telepathic-chat-wrap-message", + ("telepathicChannelName", Loc.GetString("chat-manager-telepathic-channel-name")), + ("message", message)); - _chatMan.ChatMessageToOne(ChatChannel.Telepathic, - message, messageWrap, oracle, false, client, Color.PaleVioletRed); + _chatMan.ChatMessageToOne(ChatChannel.Telepathic, + message, messageWrap, oracle, false, client, Color.PaleVioletRed); + } + else + { + var descriptor = _tChat.SourceToDescriptor(oracle); + var psychogMessageWrap = Loc.GetString("chat-manager-send-telepathic-chat-wrap-message-psychognomy", + ("source", descriptor.ToUpper()), ("message", message)); + + _chatMan.ChatMessageToOne(ChatChannel.Telepathic, message, psychogMessageWrap, oracle, false, client, Color.PaleVioletRed); + } } private bool IsCorrectItem(EntityPrototype given, EntityPrototype target) diff --git a/Content.Server/Research/Systems/ResearchSystem.Client.cs b/Content.Server/Research/Systems/ResearchSystem.Client.cs index 6bd5300d8f..f8fdba55b7 100644 --- a/Content.Server/Research/Systems/ResearchSystem.Client.cs +++ b/Content.Server/Research/Systems/ResearchSystem.Client.cs @@ -45,7 +45,7 @@ private void OnConsoleSelect(EntityUid uid, ResearchClientComponent component, C if (!this.IsPowered(uid, EntityManager)) return; - _uiSystem.TryToggleUi(uid, ResearchClientUiKey.Key, args.Session); + _uiSystem.TryToggleUi(uid, ResearchClientUiKey.Key, args.Actor); } #endregion @@ -88,7 +88,7 @@ private void UpdateClientInterface(EntityUid uid, ResearchClientComponent? compo var state = new ResearchClientBoundInterfaceState(names.Length, names, GetServerIds(), serverComponent?.Id ?? -1); - _uiSystem.TrySetUiState(uid, ResearchClientUiKey.Key, state); + _uiSystem.SetUiState(uid, ResearchClientUiKey.Key, state); } /// diff --git a/Content.Server/Research/Systems/ResearchSystem.Console.cs b/Content.Server/Research/Systems/ResearchSystem.Console.cs index 9f95fd2517..5358ddefcd 100644 --- a/Content.Server/Research/Systems/ResearchSystem.Console.cs +++ b/Content.Server/Research/Systems/ResearchSystem.Console.cs @@ -20,8 +20,7 @@ private void InitializeConsole() private void OnConsoleUnlock(EntityUid uid, ResearchConsoleComponent component, ConsoleUnlockTechnologyMessage args) { - if (args.Session.AttachedEntity is not { } ent) - return; + var act = args.Actor; if (!this.IsPowered(uid, EntityManager)) return; @@ -29,13 +28,13 @@ private void OnConsoleUnlock(EntityUid uid, ResearchConsoleComponent component, if (!PrototypeManager.TryIndex(args.Id, out var technologyPrototype)) return; - if (TryComp(uid, out var access) && !_accessReader.IsAllowed(ent, uid, access)) + if (TryComp(uid, out var access) && !_accessReader.IsAllowed(act, uid, access)) { - _popup.PopupEntity(Loc.GetString("research-console-no-access-popup"), ent); + _popup.PopupEntity(Loc.GetString("research-console-no-access-popup"), act); return; } - if (!UnlockTechnology(uid, args.Id, ent)) + if (!UnlockTechnology(uid, args.Id, act)) return; var message = Loc.GetString("research-console-unlock-technology-radio-broadcast", @@ -68,7 +67,7 @@ private void UpdateConsoleInterface(EntityUid uid, ResearchConsoleComponent? com state = new ResearchConsoleBoundInterfaceState(default); } - _uiSystem.TrySetUiState(uid, ResearchConsoleUiKey.Key, state); + _uiSystem.SetUiState(uid, ResearchConsoleUiKey.Key, state); } private void OnPointsChanged(EntityUid uid, ResearchConsoleComponent component, ref ResearchServerPointsChangedEvent args) diff --git a/Content.Server/Research/TechnologyDisk/Systems/DiskConsoleSystem.cs b/Content.Server/Research/TechnologyDisk/Systems/DiskConsoleSystem.cs index 2064abd8eb..6700247522 100644 --- a/Content.Server/Research/TechnologyDisk/Systems/DiskConsoleSystem.cs +++ b/Content.Server/Research/TechnologyDisk/Systems/DiskConsoleSystem.cs @@ -91,7 +91,7 @@ public void UpdateUserInterface(EntityUid uid, DiskConsoleComponent? component = totalPoints >= component.PricePerDisk; var state = new DiskConsoleBoundUserInterfaceState(totalPoints, component.PricePerDisk, canPrint); - _ui.TrySetUiState(uid, DiskConsoleUiKey.Key, state); + _ui.SetUiState(uid, DiskConsoleUiKey.Key, state); } private void OnShutdown(EntityUid uid, DiskConsolePrintingComponent component, ComponentShutdown args) diff --git a/Content.Server/Resist/CanEscapeInventoryComponent.cs b/Content.Server/Resist/CanEscapeInventoryComponent.cs index 978e03d95f..2b4045143a 100644 --- a/Content.Server/Resist/CanEscapeInventoryComponent.cs +++ b/Content.Server/Resist/CanEscapeInventoryComponent.cs @@ -6,9 +6,9 @@ namespace Content.Server.Resist; public sealed partial class CanEscapeInventoryComponent : Component { /// - /// Base doafter length for uncontested breakouts. + /// Base doafter length for uncontested breakouts. /// - [DataField("baseResistTime")] + [DataField] public float BaseResistTime = 5f; public bool IsEscaping => DoAfter != null; diff --git a/Content.Server/Resist/EscapeInventorySystem.cs b/Content.Server/Resist/EscapeInventorySystem.cs index a992436f07..2e060b1b42 100644 --- a/Content.Server/Resist/EscapeInventorySystem.cs +++ b/Content.Server/Resist/EscapeInventorySystem.cs @@ -7,6 +7,7 @@ using Content.Server.Storage.Components; using Content.Shared.ActionBlocker; using Content.Shared.Actions; +using Content.Shared.Contests; using Content.Shared.DoAfter; using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction.Events; @@ -28,6 +29,7 @@ public sealed class EscapeInventorySystem : EntitySystem [Dependency] private readonly SharedHandsSystem _handsSystem = default!; [Dependency] private readonly CarryingSystem _carryingSystem = default!; // Carrying system from Nyanotrasen. [Dependency] private readonly SharedActionsSystem _actions = default!; + [Dependency] private readonly ContestsSystem _contests = default!; /// /// You can't escape the hands of an entity this many times more massive than you. @@ -67,7 +69,8 @@ private void OnRelayMovement(EntityUid uid, CanEscapeInventoryComponent componen // Contested if (_handsSystem.IsHolding(container.Owner, uid, out _)) { - AttemptEscape(uid, container.Owner, component); + var disadvantage = _contests.MassContest(container.Owner, uid, rangeFactor: 3f); + AttemptEscape(uid, container.Owner, component, disadvantage); return; } diff --git a/Content.Server/Revenant/EntitySystems/CorporealSystem.cs b/Content.Server/Revenant/EntitySystems/CorporealSystem.cs index 1d43cb3ac8..5f31a2f280 100644 --- a/Content.Server/Revenant/EntitySystems/CorporealSystem.cs +++ b/Content.Server/Revenant/EntitySystems/CorporealSystem.cs @@ -1,4 +1,4 @@ -using Content.Server.GameTicking; +using Content.Server.GameTicking; using Content.Shared.Eye; using Content.Shared.Revenant.Components; using Content.Shared.Revenant.EntitySystems; @@ -17,8 +17,8 @@ public override void OnStartup(EntityUid uid, CorporealComponent component, Comp if (TryComp(uid, out var visibility)) { - _visibilitySystem.RemoveLayer(uid, visibility, (int) VisibilityFlags.Ghost, false); - _visibilitySystem.AddLayer(uid, visibility, (int) VisibilityFlags.Normal, false); + _visibilitySystem.RemoveLayer((uid, visibility), (int) VisibilityFlags.Ghost, false); + _visibilitySystem.AddLayer((uid, visibility), (int) VisibilityFlags.Normal, false); _visibilitySystem.RefreshVisibility(uid, visibility); } } @@ -29,8 +29,8 @@ public override void OnShutdown(EntityUid uid, CorporealComponent component, Com if (TryComp(uid, out var visibility) && _ticker.RunLevel != GameRunLevel.PostRound) { - _visibilitySystem.AddLayer(uid, visibility, (int) VisibilityFlags.Ghost, false); - _visibilitySystem.RemoveLayer(uid, visibility, (int) VisibilityFlags.Normal, false); + _visibilitySystem.AddLayer((uid, visibility), (int) VisibilityFlags.Ghost, false); + _visibilitySystem.RemoveLayer((uid, visibility), (int) VisibilityFlags.Normal, false); _visibilitySystem.RefreshVisibility(uid, visibility); } } diff --git a/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs b/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs index cd64f043a0..5d7c7514b8 100644 --- a/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs +++ b/Content.Server/Revenant/EntitySystems/RevenantSystem.Abilities.cs @@ -15,6 +15,7 @@ using System.Linq; using System.Numerics; using Content.Server.Revenant.Components; +using Content.Shared.Physics; using Content.Shared.DoAfter; using Content.Shared.Emag.Systems; using Content.Shared.FixedPoint; @@ -135,6 +136,12 @@ private void BeginHarvestDoAfter(EntityUid uid, EntityUid target, RevenantCompon return; } + if(_physics.GetEntitiesIntersectingBody(uid, (int) CollisionGroup.Impassable).Count > 0) + { + _popup.PopupEntity(Loc.GetString("revenant-in-solid"), uid, uid); + return; + } + var doAfter = new DoAfterArgs(EntityManager, uid, revenant.HarvestDebuffs.X, new HarvestEvent(), uid, target: target) { DistanceThreshold = 2, @@ -238,7 +245,7 @@ private void OnDefileAction(EntityUid uid, RevenantComponent component, Revenant { //hardcoded damage specifiers til i die. var dspec = new DamageSpecifier(); - dspec.DamageDict.Add("Structural", 15); + dspec.DamageDict.Add("Structural", 60); _damage.TryChangeDamage(ent, dspec, origin: uid); } diff --git a/Content.Server/Revenant/EntitySystems/RevenantSystem.cs b/Content.Server/Revenant/EntitySystems/RevenantSystem.cs index 86be70c41f..428d1ecb75 100644 --- a/Content.Server/Revenant/EntitySystems/RevenantSystem.cs +++ b/Content.Server/Revenant/EntitySystems/RevenantSystem.cs @@ -77,8 +77,8 @@ private void OnStartup(EntityUid uid, RevenantComponent component, ComponentStar if (_ticker.RunLevel == GameRunLevel.PostRound && TryComp(uid, out var visibility)) { - _visibility.AddLayer(uid, visibility, (int) VisibilityFlags.Ghost, false); - _visibility.RemoveLayer(uid, visibility, (int) VisibilityFlags.Normal, false); + _visibility.AddLayer((uid, visibility), (int) VisibilityFlags.Ghost, false); + _visibility.RemoveLayer((uid, visibility), (int) VisibilityFlags.Normal, false); _visibility.RefreshVisibility(uid, visibility); } @@ -191,13 +191,13 @@ public void MakeVisible(bool visible) { if (visible) { - _visibility.AddLayer(uid, vis, (int) VisibilityFlags.Normal, false); - _visibility.RemoveLayer(uid, vis, (int) VisibilityFlags.Ghost, false); + _visibility.AddLayer((uid, vis), (int) VisibilityFlags.Normal, false); + _visibility.RemoveLayer((uid, vis), (int) VisibilityFlags.Ghost, false); } else { - _visibility.AddLayer(uid, vis, (int) VisibilityFlags.Ghost, false); - _visibility.RemoveLayer(uid, vis, (int) VisibilityFlags.Normal, false); + _visibility.AddLayer((uid, vis), (int) VisibilityFlags.Ghost, false); + _visibility.RemoveLayer((uid, vis), (int) VisibilityFlags.Normal, false); } _visibility.RefreshVisibility(uid, vis); } diff --git a/Content.Server/Revolutionary/Components/CommandStaffComponent.cs b/Content.Server/Revolutionary/Components/CommandStaffComponent.cs index 8e42f41cb3..d7395bf16e 100644 --- a/Content.Server/Revolutionary/Components/CommandStaffComponent.cs +++ b/Content.Server/Revolutionary/Components/CommandStaffComponent.cs @@ -3,10 +3,11 @@ namespace Content.Server.Revolutionary.Components; /// -/// Given to heads at round start for Revs. Used for tracking if heads died or not. +/// Component for tracking if someone is a Head of Staff. /// [RegisterComponent, Access(typeof(RevolutionaryRuleSystem))] public sealed partial class CommandStaffComponent : Component { - + public float PsionicBonusModifier = 1; + public float PsionicBonusOffset = 0.25f; } diff --git a/Content.Server/Revolutionary/Components/CommandStaffSystem.cs b/Content.Server/Revolutionary/Components/CommandStaffSystem.cs new file mode 100644 index 0000000000..d940ec9c26 --- /dev/null +++ b/Content.Server/Revolutionary/Components/CommandStaffSystem.cs @@ -0,0 +1,16 @@ +using Content.Server.Psionics; + +namespace Content.Server.Revolutionary.Components; +public sealed partial class CommandStaffSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnRollPsionics); + } + + private void OnRollPsionics(EntityUid uid, CommandStaffComponent component, ref OnRollPsionicsEvent args) + { + args.BaselineChance = args.BaselineChance * component.PsionicBonusModifier + component.PsionicBonusOffset; + } +} \ No newline at end of file diff --git a/Content.Server/Robotics/Systems/RoboticsConsoleSystem.cs b/Content.Server/Robotics/Systems/RoboticsConsoleSystem.cs new file mode 100644 index 0000000000..916694fdd8 --- /dev/null +++ b/Content.Server/Robotics/Systems/RoboticsConsoleSystem.cs @@ -0,0 +1,146 @@ +using Content.Server.Administration.Logs; +using Content.Server.DeviceNetwork; +using Content.Server.DeviceNetwork.Systems; +using Content.Server.Radio.EntitySystems; +using Content.Shared.Lock; +using Content.Shared.Database; +using Content.Shared.DeviceNetwork; +using Content.Shared.Robotics; +using Content.Shared.Robotics.Components; +using Content.Shared.Robotics.Systems; +using Robust.Server.GameObjects; +using Robust.Shared.Timing; +using System.Diagnostics.CodeAnalysis; + +namespace Content.Server.Research.Systems; + +/// +/// Handles UI and state receiving for the robotics control console. +/// BorgTransponderComponent broadcasts state from the station's borgs to consoles. +/// +public sealed class RoboticsConsoleSystem : SharedRoboticsConsoleSystem +{ + [Dependency] private readonly DeviceNetworkSystem _deviceNetwork = default!; + [Dependency] private readonly IAdminLogManager _adminLogger = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly LockSystem _lock = default!; + [Dependency] private readonly RadioSystem _radio = default!; + [Dependency] private readonly UserInterfaceSystem _ui = default!; + + // almost never timing out more than 1 per tick so initialize with that capacity + private List _removing = new(1); + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnPacketReceived); + Subs.BuiEvents(RoboticsConsoleUiKey.Key, subs => + { + subs.Event(OnOpened); + subs.Event(OnDisable); + subs.Event(OnDestroy); + // TODO: camera stuff + }); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var now = _timing.CurTime; + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var comp)) + { + // remove cyborgs that havent pinged in a while + _removing.Clear(); + foreach (var (address, data) in comp.Cyborgs) + { + if (now >= data.Timeout) + _removing.Add(address); + } + + // needed to prevent modifying while iterating it + foreach (var address in _removing) + { + comp.Cyborgs.Remove(address); + } + + if (_removing.Count > 0) + UpdateUserInterface((uid, comp)); + } + } + + private void OnPacketReceived(Entity ent, ref DeviceNetworkPacketEvent args) + { + var payload = args.Data; + if (!payload.TryGetValue(DeviceNetworkConstants.Command, out string? command)) + return; + if (command != DeviceNetworkConstants.CmdUpdatedState) + return; + + if (!payload.TryGetValue(RoboticsConsoleConstants.NET_CYBORG_DATA, out CyborgControlData? data)) + return; + + var real = data.Value; + real.Timeout = _timing.CurTime + ent.Comp.Timeout; + ent.Comp.Cyborgs[args.SenderAddress] = real; + + UpdateUserInterface(ent); + } + + private void OnOpened(Entity ent, ref BoundUIOpenedEvent args) + { + UpdateUserInterface(ent); + } + + private void OnDisable(Entity ent, ref RoboticsConsoleDisableMessage args) + { + if (_lock.IsLocked(ent.Owner)) + return; + + if (!ent.Comp.Cyborgs.TryGetValue(args.Address, out var data)) + return; + + var payload = new NetworkPayload() + { + [DeviceNetworkConstants.Command] = RoboticsConsoleConstants.NET_DISABLE_COMMAND + }; + + _deviceNetwork.QueuePacket(ent, args.Address, payload); + _adminLogger.Add(LogType.Action, LogImpact.High, $"{ToPrettyString(args.Actor):user} disabled borg {data.Name} with address {args.Address}"); + } + + private void OnDestroy(Entity ent, ref RoboticsConsoleDestroyMessage args) + { + if (_lock.IsLocked(ent.Owner)) + return; + + var now = _timing.CurTime; + if (now < ent.Comp.NextDestroy) + return; + + if (!ent.Comp.Cyborgs.Remove(args.Address, out var data)) + return; + + var payload = new NetworkPayload() + { + [DeviceNetworkConstants.Command] = RoboticsConsoleConstants.NET_DESTROY_COMMAND + }; + + _deviceNetwork.QueuePacket(ent, args.Address, payload); + + var message = Loc.GetString(ent.Comp.DestroyMessage, ("name", data.Name)); + _radio.SendRadioMessage(ent, message, ent.Comp.RadioChannel, ent); + _adminLogger.Add(LogType.Action, LogImpact.Extreme, $"{ToPrettyString(args.Actor):user} destroyed borg {data.Name} with address {args.Address}"); + + ent.Comp.NextDestroy = now + ent.Comp.DestroyCooldown; + Dirty(ent, ent.Comp); + } + + private void UpdateUserInterface(Entity ent) + { + var state = new RoboticsConsoleState(ent.Comp.Cyborgs); + _ui.SetUiState(ent.Owner, RoboticsConsoleUiKey.Key, state); + } +} diff --git a/Content.Server/Roles/RoleSystem.cs b/Content.Server/Roles/RoleSystem.cs index f7a5177357..c53fa1cf9e 100644 --- a/Content.Server/Roles/RoleSystem.cs +++ b/Content.Server/Roles/RoleSystem.cs @@ -15,7 +15,6 @@ public override void Initialize() SubscribeAntagEvents(); SubscribeAntagEvents(); SubscribeAntagEvents(); - SubscribeAntagEvents(); SubscribeAntagEvents(); SubscribeAntagEvents(); SubscribeAntagEvents(); diff --git a/Content.Server/RoundEnd/RoundEndSystem.cs b/Content.Server/RoundEnd/RoundEndSystem.cs index d79fe2d2cc..0866b975c2 100644 --- a/Content.Server/RoundEnd/RoundEndSystem.cs +++ b/Content.Server/RoundEnd/RoundEndSystem.cs @@ -147,11 +147,15 @@ public void RequestRoundEnd(EntityUid? requester = null, bool checkCooldown = tr public void RequestRoundEnd(TimeSpan countdownTime, EntityUid? requester = null, bool checkCooldown = true, string text = "round-end-system-shuttle-called-announcement", string name = "Station") { - if (_gameTicker.RunLevel != GameRunLevel.InRound) return; + if (_gameTicker.RunLevel != GameRunLevel.InRound) + return; - if (checkCooldown && _cooldownTokenSource != null) return; + if (checkCooldown && _cooldownTokenSource != null) + return; + + if (_countdownTokenSource != null) + return; - if (_countdownTokenSource != null) return; _countdownTokenSource = new(); if (requester != null) @@ -191,6 +195,8 @@ public void RequestRoundEnd(TimeSpan countdownTime, EntityUid? requester = null, LastCountdownStart = _gameTiming.CurTime; ExpectedCountdownEnd = _gameTiming.CurTime + countdownTime; + + // TODO full game saves Timer.Spawn(countdownTime, _shuttle.CallEmergencyShuttle, _countdownTokenSource.Token); ActivateCooldown(); @@ -343,6 +349,8 @@ private void ActivateCooldown() { _cooldownTokenSource?.Cancel(); _cooldownTokenSource = new(); + + // TODO full game saves Timer.Spawn(DefaultCooldownDuration, () => { _cooldownTokenSource.Cancel(); diff --git a/Content.Server/Salvage/Expeditions/SalvageExpeditionComponent.cs b/Content.Server/Salvage/Expeditions/SalvageExpeditionComponent.cs index 20d4be2d90..ff3c8176fd 100644 --- a/Content.Server/Salvage/Expeditions/SalvageExpeditionComponent.cs +++ b/Content.Server/Salvage/Expeditions/SalvageExpeditionComponent.cs @@ -4,7 +4,6 @@ using Robust.Shared.Audio; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; namespace Content.Server.Salvage.Expeditions; @@ -41,11 +40,21 @@ public sealed partial class SalvageExpeditionComponent : SharedSalvageExpedition /// /// Countdown audio stream. /// + [DataField, AutoNetworkedField] public EntityUid? Stream = null; /// /// Sound that plays when the mission end is imminent. /// - [ViewVariables(VVAccess.ReadWrite), DataField("sound", customTypeSerializer: typeof(PrototypeIdSerializer))] // Parkstation-ExpeditionMusic - public string Sound = "ExpeditionCountdownDefault"; // Parkstation-ExpeditionMusic + [ViewVariables(VVAccess.ReadWrite), DataField] + public SoundSpecifier Sound = new SoundCollectionSpecifier("ExpeditionEnd") + { + Params = AudioParams.Default.WithVolume(-5), + }; + + /// + /// Song selected on MapInit so we can predict the audio countdown properly. + /// + [DataField] + public SoundPathSpecifier SelectedSong; } diff --git a/Content.Server/Salvage/FultonSystem.cs b/Content.Server/Salvage/FultonSystem.cs index a24bab4584..8c656790e9 100644 --- a/Content.Server/Salvage/FultonSystem.cs +++ b/Content.Server/Salvage/FultonSystem.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Content.Shared.Salvage.Fulton; using Robust.Shared.Containers; using Robust.Shared.Map; @@ -61,8 +62,9 @@ private void Fulton(EntityUid uid, FultonedComponent component) var metadata = MetaData(uid); var oldCoords = xform.Coordinates; var offset = _random.NextVector2(1.5f); - var localPos = TransformSystem.GetInvWorldMatrix(beaconXform.ParentUid) - .Transform(TransformSystem.GetWorldPosition(beaconXform)) + offset; + var localPos = Vector2.Transform( + TransformSystem.GetWorldPosition(beaconXform), + TransformSystem.GetInvWorldMatrix(beaconXform.ParentUid)) + offset; TransformSystem.SetCoordinates(uid, new EntityCoordinates(beaconXform.ParentUid, localPos)); diff --git a/Content.Server/Salvage/SalvageSystem.ExpeditionConsole.cs b/Content.Server/Salvage/SalvageSystem.ExpeditionConsole.cs index 61636bea7c..d031418476 100644 --- a/Content.Server/Salvage/SalvageSystem.ExpeditionConsole.cs +++ b/Content.Server/Salvage/SalvageSystem.ExpeditionConsole.cs @@ -56,7 +56,7 @@ private void UpdateConsoles(Entity component) if (station != component.Owner) continue; - _ui.TrySetUiState(uid, SalvageConsoleUiKey.Expedition, state, ui: uiComp); + _ui.SetUiState((uid, uiComp), SalvageConsoleUiKey.Expedition, state); } } @@ -74,6 +74,6 @@ private void UpdateConsole(Entity component) state = new SalvageExpeditionConsoleState(TimeSpan.Zero, false, true, 0, new List()); } - _ui.TrySetUiState(component, SalvageConsoleUiKey.Expedition, state); + _ui.SetUiState(component.Owner, SalvageConsoleUiKey.Expedition, state); } } diff --git a/Content.Server/Salvage/SalvageSystem.Expeditions.cs b/Content.Server/Salvage/SalvageSystem.Expeditions.cs index 4d5d569dab..923880169d 100644 --- a/Content.Server/Salvage/SalvageSystem.Expeditions.cs +++ b/Content.Server/Salvage/SalvageSystem.Expeditions.cs @@ -4,7 +4,9 @@ using Content.Server.Salvage.Expeditions.Structure; using Content.Shared.CCVar; using Content.Shared.Examine; +using Content.Shared.Random.Helpers; using Content.Shared.Salvage.Expeditions; +using Robust.Shared.Audio; using Robust.Shared.CPUJob.JobQueues; using Robust.Shared.CPUJob.JobQueues.Queues; using Robust.Shared.GameStates; @@ -32,6 +34,7 @@ private void InitializeExpeditions() SubscribeLocalEvent(OnSalvageConsoleParent); SubscribeLocalEvent(OnSalvageClaimMessage); + SubscribeLocalEvent(OnExpeditionMapInit); SubscribeLocalEvent(OnExpeditionShutdown); SubscribeLocalEvent(OnExpeditionGetState); @@ -64,6 +67,12 @@ private void SetCooldownChange(float obj) _cooldown = obj; } + private void OnExpeditionMapInit(EntityUid uid, SalvageExpeditionComponent component, MapInitEvent args) + { + var selectedFile = _audio.GetSound(component.Sound); + component.SelectedSong = new SoundPathSpecifier(selectedFile, component.Sound.Params); + } + private void OnExpeditionShutdown(EntityUid uid, SalvageExpeditionComponent component, ComponentShutdown args) { component.Stream = _audio.Stop(component.Stream); diff --git a/Content.Server/Salvage/SalvageSystem.Magnet.cs b/Content.Server/Salvage/SalvageSystem.Magnet.cs index e4711a5876..4b7291298b 100644 --- a/Content.Server/Salvage/SalvageSystem.Magnet.cs +++ b/Content.Server/Salvage/SalvageSystem.Magnet.cs @@ -35,11 +35,6 @@ private void InitializeMagnet() private void OnMagnetClaim(EntityUid uid, SalvageMagnetComponent component, ref MagnetClaimOfferEvent args) { - var player = args.Session.AttachedEntity; - - if (player is null) - return; - var station = _station.GetOwningStation(uid); if (!TryComp(station, out SalvageMagnetDataComponent? dataComp) || @@ -177,12 +172,12 @@ private void CreateMagnetOffers(Entity data) // Fuck with the seed to mix wrecks and asteroids. seed = (int) (seed / 10f) * 10; - + if (i >= data.Comp.OfferCount / 2) { seed++; } - + data.Comp.Offered.Add(seed); } @@ -216,7 +211,7 @@ private void UpdateMagnetUI(Entity entity, TransformComp if (!TryComp(station, out SalvageMagnetDataComponent? dataComp)) return; - _ui.TrySetUiState(entity, SalvageMagnetUiKey.Key, + _ui.SetUiState(entity.Owner, SalvageMagnetUiKey.Key, new SalvageMagnetBoundUserInterfaceState(dataComp.Offered) { Cooldown = dataComp.OfferCooldown, @@ -238,7 +233,7 @@ private void UpdateMagnetUIs(Entity data) if (station != data.Owner) continue; - _ui.TrySetUiState(magnetUid, SalvageMagnetUiKey.Key, + _ui.SetUiState(magnetUid, SalvageMagnetUiKey.Key, new SalvageMagnetBoundUserInterfaceState(data.Comp.Offered) { Cooldown = data.Comp.OfferCooldown, diff --git a/Content.Server/Salvage/SalvageSystem.Runner.cs b/Content.Server/Salvage/SalvageSystem.Runner.cs index 8a1498cbe9..23607e2bdc 100644 --- a/Content.Server/Salvage/SalvageSystem.Runner.cs +++ b/Content.Server/Salvage/SalvageSystem.Runner.cs @@ -1,9 +1,7 @@ using System.Numerics; using Content.Server.Salvage.Expeditions; -using Content.Server.Salvage.Expeditions.Structure; using Content.Server.Shuttles.Components; using Content.Server.Shuttles.Events; -using Content.Server.Shuttles.Systems; using Content.Server.Station.Components; using Content.Shared.Chat; using Content.Shared.Humanoid; @@ -13,7 +11,6 @@ using Content.Shared.Shuttles.Components; using Robust.Shared.Map.Components; using Robust.Shared.Player; -using Robust.Shared.Utility; namespace Content.Server.Salvage; @@ -144,6 +141,7 @@ private void UpdateRunner() while (query.MoveNext(out var uid, out var comp)) { var remaining = comp.EndTime - _timing.CurTime; + var audioLength = _audio.GetAudioLength(comp.SelectedSong.Path.ToString()); if (comp.Stage < ExpeditionStage.FinalCountdown && remaining < TimeSpan.FromSeconds(45)) { @@ -151,13 +149,14 @@ private void UpdateRunner() Dirty(uid, comp); Announce(uid, Loc.GetString("salvage-expedition-announcement-countdown-seconds", ("duration", TimeSpan.FromSeconds(45).Seconds))); } - else if (comp.Stage < ExpeditionStage.MusicCountdown && remaining < TimeSpan.FromMinutes(2)) + else if (comp.Stream == null && remaining < audioLength) { - // TODO: Some way to play audio attached to a map for players. - comp.Stream = _audio.PlayGlobal(comp.Sound, Filter.BroadcastMap(Comp(uid).MapId), true).Value.Entity; + var audio = _audio.PlayPvs(comp.Sound, uid).Value; + comp.Stream = audio.Entity; + _audio.SetMapAudio(audio); comp.Stage = ExpeditionStage.MusicCountdown; Dirty(uid, comp); - Announce(uid, Loc.GetString("salvage-expedition-announcement-countdown-minutes", ("duration", TimeSpan.FromMinutes(2).Minutes))); + Announce(uid, Loc.GetString("salvage-expedition-announcement-countdown-minutes", ("duration", audioLength.Minutes))); } else if (comp.Stage < ExpeditionStage.Countdown && remaining < TimeSpan.FromMinutes(4)) { @@ -166,16 +165,16 @@ private void UpdateRunner() Announce(uid, Loc.GetString("salvage-expedition-announcement-countdown-minutes", ("duration", TimeSpan.FromMinutes(5).Minutes))); } // Auto-FTL out any shuttles - else if (remaining < TimeSpan.FromSeconds(ShuttleSystem.DefaultStartupTime) + TimeSpan.FromSeconds(0.5)) + else if (remaining < TimeSpan.FromSeconds(_shuttle.DefaultStartupTime) + TimeSpan.FromSeconds(0.5)) { var ftlTime = (float) remaining.TotalSeconds; - if (remaining < TimeSpan.FromSeconds(ShuttleSystem.DefaultStartupTime)) + if (remaining < TimeSpan.FromSeconds(_shuttle.DefaultStartupTime)) { ftlTime = MathF.Max(0, (float) remaining.TotalSeconds - 0.5f); } - ftlTime = MathF.Min(ftlTime, ShuttleSystem.DefaultStartupTime); + ftlTime = MathF.Min(ftlTime, _shuttle.DefaultStartupTime); var shuttleQuery = AllEntityQuery(); if (TryComp(comp.Station, out var data)) diff --git a/Content.Server/Salvage/SpawnSalvageMissionJob.cs b/Content.Server/Salvage/SpawnSalvageMissionJob.cs index 47123e9784..ce844e57a1 100644 --- a/Content.Server/Salvage/SpawnSalvageMissionJob.cs +++ b/Content.Server/Salvage/SpawnSalvageMissionJob.cs @@ -34,8 +34,6 @@ using Robust.Shared.Timing; using Robust.Shared.Utility; using Content.Server.Shuttles.Components; -using Content.Shared.Coordinates; -using Content.Shared.Shuttles.Components; namespace Content.Server.Salvage; diff --git a/Content.Server/SensorMonitoring/SensorMonitoringConsoleComponent.cs b/Content.Server/SensorMonitoring/SensorMonitoringConsoleComponent.cs index 63b4d9daef..b5a954f166 100644 --- a/Content.Server/SensorMonitoring/SensorMonitoringConsoleComponent.cs +++ b/Content.Server/SensorMonitoring/SensorMonitoringConsoleComponent.cs @@ -27,7 +27,7 @@ public sealed partial class SensorMonitoringConsoleComponent : Component public TimeSpan RetentionTime = TimeSpan.FromMinutes(1); // UI update tracking stuff. - public HashSet InitialUIStateSent = new(); + public HashSet InitialUIStateSent = new(); public TimeSpan LastUIUpdate; public ValueList RemovedSensors; diff --git a/Content.Server/SensorMonitoring/SensorMonitoringConsoleSystem.UI.cs b/Content.Server/SensorMonitoring/SensorMonitoringConsoleSystem.UI.cs index 26c6b17831..dec3e6c36e 100644 --- a/Content.Server/SensorMonitoring/SensorMonitoringConsoleSystem.UI.cs +++ b/Content.Server/SensorMonitoring/SensorMonitoringConsoleSystem.UI.cs @@ -18,27 +18,26 @@ private void InitUI() private void UpdateConsoleUI(EntityUid uid, SensorMonitoringConsoleComponent comp) { - if (!_userInterface.TryGetUi(uid, SensorMonitoringConsoleUiKey.Key, out var ui)) - return; - - if (ui.SubscribedSessions.Count == 0) + if (!_userInterface.IsUiOpen(uid, SensorMonitoringConsoleUiKey.Key)) + { return; + } ConsoleUIState? fullState = null; SensorMonitoringIncrementalUpdate? incrementalUpdate = null; - foreach (var session in ui.SubscribedSessions) + foreach (var actorUid in _userInterface.GetActors(uid, SensorMonitoringConsoleUiKey.Key)) { - if (comp.InitialUIStateSent.Contains(session)) + if (comp.InitialUIStateSent.Contains(actorUid)) { incrementalUpdate ??= CalculateIncrementalUpdate(); - _userInterface.TrySendUiMessage(ui, incrementalUpdate, session); + _userInterface.ServerSendUiMessage(uid, SensorMonitoringConsoleUiKey.Key, incrementalUpdate, actorUid); } else { fullState ??= CalculateFullState(); - _userInterface.SetUiState(ui, fullState, session); - comp.InitialUIStateSent.Add(session); + _userInterface.SetUiState(uid, SensorMonitoringConsoleUiKey.Key, fullState); + comp.InitialUIStateSent.Add(actorUid); } } @@ -131,9 +130,6 @@ private static void ConsoleUIClosed( if (!args.UiKey.Equals(SensorMonitoringConsoleUiKey.Key)) return; - if (args.Session is not { } player) - return; - - component.InitialUIStateSent.Remove(player); + component.InitialUIStateSent.Remove(args.Actor); } } diff --git a/Content.Server/Shadowkin/EtherealLightComponent.cs b/Content.Server/Shadowkin/EtherealLightComponent.cs new file mode 100644 index 0000000000..8f47d86200 --- /dev/null +++ b/Content.Server/Shadowkin/EtherealLightComponent.cs @@ -0,0 +1,15 @@ +namespace Content.Server.Shadowkin; + +[RegisterComponent] +public sealed partial class EtherealLightComponent : Component +{ + public EntityUid AttachedEntity = EntityUid.Invalid; + + public float OldRadius = 0f; + + public bool OldRadiusEdited = false; + + public float OldEnergy = 0f; + + public bool OldEnergyEdited = false; +} \ No newline at end of file diff --git a/Content.Server/Shadowkin/EtherealStunItemSystem.cs b/Content.Server/Shadowkin/EtherealStunItemSystem.cs new file mode 100644 index 0000000000..b48b4d4fec --- /dev/null +++ b/Content.Server/Shadowkin/EtherealStunItemSystem.cs @@ -0,0 +1,44 @@ +using Content.Shared.Interaction.Events; +using Content.Shared.Damage.Components; +using Content.Shared.Damage.Systems; +using Content.Shared.Shadowkin; +using Content.Shared.Abilities.Psionics; +using Content.Shared.Stacks; + +namespace Content.Server.Shadowkin; + +public sealed class EtherealStunItemSystem : EntitySystem +{ + [Dependency] private readonly StaminaSystem _stamina = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly SharedStackSystem _sharedStackSystem = default!; + public override void Initialize() + { + SubscribeLocalEvent(OnUseInHand); + } + + private void OnUseInHand(EntityUid uid, EtherealStunItemComponent component, UseInHandEvent args) + { + foreach (var ent in _lookup.GetEntitiesInRange(uid, component.Radius)) + { + if (!TryComp(ent, out var ethereal)) + continue; + + RemComp(ent, ethereal); + + if (TryComp(ent, out var stamina)) + _stamina.TakeStaminaDamage(ent, stamina.CritThreshold, stamina, ent); + + if (TryComp(ent, out var magic)) + magic.Mana = 0; + } + + if (!component.DeleteOnUse) + return; + + if (TryComp(uid, out var stack)) + _sharedStackSystem.Use(uid, 1, stack); + else + QueueDel(uid); + } +} diff --git a/Content.Server/Shadowkin/EtherealSystem.cs b/Content.Server/Shadowkin/EtherealSystem.cs new file mode 100644 index 0000000000..2622547a3f --- /dev/null +++ b/Content.Server/Shadowkin/EtherealSystem.cs @@ -0,0 +1,216 @@ +using Content.Shared.Eye; +using Content.Shared.Shadowkin; +using Robust.Server.GameObjects; +using Content.Server.Atmos.Components; +using Content.Server.Temperature.Components; +using Content.Shared.Movement.Components; +using Content.Shared.Stealth; +using Content.Shared.Stealth.Components; +using Content.Server.Body.Components; +using Content.Server.NPC.Components; +using Content.Server.NPC.Systems; +using System.Linq; +using Content.Shared.Abilities.Psionics; +using Robust.Shared.Random; +using Content.Server.Light.Components; + +namespace Content.Server.Shadowkin; + +public sealed class EtherealSystem : SharedEtherealSystem +{ + [Dependency] private readonly VisibilitySystem _visibilitySystem = default!; + [Dependency] private readonly SharedStealthSystem _stealth = default!; + [Dependency] private readonly EyeSystem _eye = default!; + [Dependency] private readonly NpcFactionSystem _factions = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly SharedPointLightSystem _light = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + + public override void OnStartup(EntityUid uid, EtherealComponent component, MapInitEvent args) + { + base.OnStartup(uid, component, args); + + var visibility = EnsureComp(uid); + _visibilitySystem.RemoveLayer((uid, visibility), (int) VisibilityFlags.Normal, false); + _visibilitySystem.AddLayer((uid, visibility), (int) VisibilityFlags.Ethereal, false); + _visibilitySystem.RefreshVisibility(uid, visibility); + + if (TryComp(uid, out var eye)) + _eye.SetVisibilityMask(uid, eye.VisibilityMask | (int) (VisibilityFlags.Ethereal), eye); + + if (TryComp(uid, out var temp)) + temp.AtmosTemperatureTransferEfficiency = 0; + + var stealth = EnsureComp(uid); + _stealth.SetVisibility(uid, 0.8f, stealth); + + SuppressFactions(uid, component, true); + + EnsureComp(uid); + EnsureComp(uid); + EnsureComp(uid); + + if (HasComp(uid)) + RemComp(uid, component); + } + + public override void OnShutdown(EntityUid uid, EtherealComponent component, ComponentShutdown args) + { + base.OnShutdown(uid, component, args); + + if (TryComp(uid, out var visibility)) + { + _visibilitySystem.AddLayer((uid, visibility), (int) VisibilityFlags.Normal, false); + _visibilitySystem.RemoveLayer((uid, visibility), (int) VisibilityFlags.Ethereal, false); + _visibilitySystem.RefreshVisibility(uid, visibility); + } + + if (TryComp(uid, out var eye)) + _eye.SetVisibilityMask(uid, (int) VisibilityFlags.Normal, eye); + + if (TryComp(uid, out var temp)) + temp.AtmosTemperatureTransferEfficiency = 0.1f; + + SuppressFactions(uid, component, false); + + RemComp(uid); + RemComp(uid); + RemComp(uid); + RemComp(uid); + + SpawnAtPosition("ShadowkinShadow", Transform(uid).Coordinates); + SpawnAtPosition("EffectFlashShadowkinDarkSwapOff", Transform(uid).Coordinates); + + foreach (var light in component.DarkenedLights.ToArray()) + { + if (!TryComp(light, out var pointLight) + || !TryComp(light, out var etherealLight)) + continue; + + ResetLight(light, pointLight, etherealLight); + } + } + + public void SuppressFactions(EntityUid uid, EtherealComponent component, bool set) + { + if (set) + { + if (!TryComp(uid, out var factions)) + return; + + component.SuppressedFactions = factions.Factions.ToList(); + + foreach (var faction in factions.Factions) + _factions.RemoveFaction(uid, faction); + } + else + { + foreach (var faction in component.SuppressedFactions) + _factions.AddFaction(uid, faction); + + component.SuppressedFactions.Clear(); + } + } + + public void ResetLight(EntityUid uid, PointLightComponent light, EtherealLightComponent etherealLight) + { + etherealLight.AttachedEntity = EntityUid.Invalid; + + if (etherealLight.OldRadiusEdited) + _light.SetRadius(uid, etherealLight.OldRadius); + etherealLight.OldRadiusEdited = false; + + if (etherealLight.OldEnergyEdited) + _light.SetEnergy(uid, etherealLight.OldEnergy); + etherealLight.OldEnergyEdited = false; + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var component)) + { + if (!component.Darken) + continue; + + component.DarkenAccumulator += frameTime; + + if (component.DarkenAccumulator <= 1) + continue; + + component.DarkenAccumulator -= component.DarkenRate; + + var darkened = new List(); + var lightQuery = _lookup.GetEntitiesInRange(uid, component.DarkenRange, flags: LookupFlags.StaticSundries) + .Where(x => HasComp(x) && HasComp(x)); + + foreach (var entity in lightQuery) + if (!darkened.Contains(entity)) + darkened.Add(entity); + + _random.Shuffle(darkened); + component.DarkenedLights = darkened; + + var playerPos = _transform.GetWorldPosition(uid); + + foreach (var light in component.DarkenedLights.ToArray()) + { + var lightPos = _transform.GetWorldPosition(light); + if (!TryComp(light, out var pointLight) + || !TryComp(light, out var etherealLight)) + continue; + + if (TryComp(light, out var powered) && !powered.On) + { + ResetLight(light, pointLight, etherealLight); + continue; + } + + if (etherealLight.AttachedEntity == EntityUid.Invalid) + etherealLight.AttachedEntity = uid; + + if (etherealLight.AttachedEntity != EntityUid.Invalid + && etherealLight.AttachedEntity != uid) + { + component.DarkenedLights.Remove(light); + continue; + } + + if (etherealLight.AttachedEntity == uid + && _random.Prob(0.03f)) + etherealLight.AttachedEntity = EntityUid.Invalid; + + if (!etherealLight.OldRadiusEdited) + { + etherealLight.OldRadius = pointLight.Radius; + etherealLight.OldRadiusEdited = true; + } + if (!etherealLight.OldEnergyEdited) + { + etherealLight.OldEnergy = pointLight.Energy; + etherealLight.OldEnergyEdited = true; + } + + var distance = (lightPos - playerPos).Length(); + var radius = distance * 2f; + var energy = distance * 0.8f; + + if (etherealLight.OldRadiusEdited && radius > etherealLight.OldRadius) + radius = etherealLight.OldRadius; + if (etherealLight.OldRadiusEdited && radius < etherealLight.OldRadius * 0.20f) + radius = etherealLight.OldRadius * 0.20f; + + if (etherealLight.OldEnergyEdited && energy > etherealLight.OldEnergy) + energy = etherealLight.OldEnergy; + if (etherealLight.OldEnergyEdited && energy < etherealLight.OldEnergy * 0.20f) + energy = etherealLight.OldEnergy * 0.20f; + + _light.SetRadius(light, radius); + _light.SetEnergy(light, energy); + } + } + } +} \ No newline at end of file diff --git a/Content.Server/Shadowkin/ShadowkinCuffSystem.cs b/Content.Server/Shadowkin/ShadowkinCuffSystem.cs new file mode 100644 index 0000000000..ce2b258817 --- /dev/null +++ b/Content.Server/Shadowkin/ShadowkinCuffSystem.cs @@ -0,0 +1,29 @@ +using Content.Shared.Inventory.Events; +using Content.Shared.Clothing.Components; +using Content.Shared.Shadowkin; + +namespace Content.Server.Shadowkin; + +public sealed class ShadowkinCuffSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnEquipped); + SubscribeLocalEvent(OnUnequipped); + } + + private void OnEquipped(EntityUid uid, ShadowkinCuffComponent component, GotEquippedEvent args) + { + if (!TryComp(uid, out var clothing) + || !clothing.Slots.HasFlag(args.SlotFlags)) + return; + + EnsureComp(args.Equipee); + } + + private void OnUnequipped(EntityUid uid, ShadowkinCuffComponent component, GotUnequippedEvent args) + { + RemComp(args.Equipee); + } +} \ No newline at end of file diff --git a/Content.Server/Shadowkin/ShadowkinSystem.cs b/Content.Server/Shadowkin/ShadowkinSystem.cs new file mode 100644 index 0000000000..83461e7a7f --- /dev/null +++ b/Content.Server/Shadowkin/ShadowkinSystem.cs @@ -0,0 +1,181 @@ +using Content.Shared.Examine; +using Content.Shared.Abilities.Psionics; +using Content.Shared.Humanoid; +using Content.Shared.Psionics; +using Content.Shared.Bed.Sleep; +using Content.Shared.Damage.Components; +using Content.Shared.Damage.Systems; +using Content.Shared.Shadowkin; +using Content.Shared.Rejuvenate; +using Content.Shared.Alert; +using Content.Shared.Rounding; +using Content.Shared.Actions; +using Robust.Shared.Prototypes; +using Content.Server.Abilities.Psionics; + +namespace Content.Server.Shadowkin; + +public sealed class ShadowkinSystem : EntitySystem +{ + [Dependency] private readonly StaminaSystem _stamina = default!; + [Dependency] private readonly PsionicAbilitiesSystem _psionicAbilitiesSystem = default!; + [Dependency] private readonly AlertsSystem _alerts = default!; + [Dependency] private readonly SharedActionsSystem _actionsSystem = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + + public const string ShadowkinSleepActionId = "ShadowkinActionSleep"; + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnExamined); + SubscribeLocalEvent(OnMindbreak); + SubscribeLocalEvent(OnAttemptPowerUse); + SubscribeLocalEvent(OnManaUpdate); + SubscribeLocalEvent(OnRejuvenate); + SubscribeLocalEvent(OnEyeColorChange); + } + + private void OnInit(EntityUid uid, ShadowkinComponent component, ComponentStartup args) + { + if (component.BlackeyeSpawn) + ApplyBlackEye(uid); + + _actionsSystem.AddAction(uid, ref component.ShadowkinSleepAction, ShadowkinSleepActionId, uid); + + UpdateShadowkinAlert(uid, component); + } + + private void OnEyeColorChange(EntityUid uid, ShadowkinComponent component, EyeColorInitEvent args) + { + if (!TryComp(uid, out var humanoid) + || !component.BlackeyeSpawn + || humanoid.EyeColor == component.OldEyeColor) + return; + + component.OldEyeColor = humanoid.EyeColor; + humanoid.EyeColor = component.BlackEyeColor; + Dirty(humanoid); + } + + private void OnExamined(EntityUid uid, ShadowkinComponent component, ExaminedEvent args) + { + if (!args.IsInDetailsRange + || !TryComp(uid, out var magic) + || HasComp(uid)) + return; + + var severity = "shadowkin-power-" + ContentHelpers.RoundToLevels(magic.Mana, magic.MaxMana, 6); + var powerType = Loc.GetString(severity); + + if (args.Examined == args.Examiner) + args.PushMarkup(Loc.GetString("shadowkin-power-examined-self", + ("power", Math.Floor(magic.Mana)), + ("powerMax", Math.Floor(magic.MaxMana)), + ("powerType", powerType) + )); + else + args.PushMarkup(Loc.GetString("shadowkin-power-examined-other", + ("target", uid), + ("powerType", powerType) + )); + } + + /// + /// Update the Shadowkin Alert, if Blackeye will remove the Alert, if not will update to its current power status. + /// + public void UpdateShadowkinAlert(EntityUid uid, ShadowkinComponent component) + { + if (TryComp(uid, out var magic)) + { + var severity = (short) ContentHelpers.RoundToLevels(magic.Mana, magic.MaxMana, 8); + _alerts.ShowAlert(uid, AlertType.ShadowkinPower, severity); + } + else + _alerts.ClearAlert(uid, AlertType.ShadowkinPower); + } + + private void OnAttemptPowerUse(EntityUid uid, ShadowkinComponent component, OnAttemptPowerUseEvent args) + { + if (HasComp(uid)) + args.Cancel(); + } + + private void OnManaUpdate(EntityUid uid, ShadowkinComponent component, ref OnManaUpdateEvent args) + { + if (!TryComp(uid, out var magic)) + return; + + if (component.SleepManaRegen + && TryComp(uid, out var sleep)) + magic.ManaGainMultiplier = component.SleepManaRegenMultiplier; + else + magic.ManaGainMultiplier = 1; + + if (magic.Mana <= component.BlackEyeMana) + ApplyBlackEye(uid); + + Dirty(magic); // Update Shadowkin Overlay. + UpdateShadowkinAlert(uid, component); + } + + /// + /// Blackeye the Shadowkin, its just a function to mindbreak the shadowkin but making sure "Removable" is checked true during it. + /// + /// + public void ApplyBlackEye(EntityUid uid) + { + if (!TryComp(uid, out var magic)) + return; + + magic.Removable = true; + _psionicAbilitiesSystem.MindBreak(uid); + } + + private void OnMindbreak(EntityUid uid, ShadowkinComponent component, ref OnMindbreakEvent args) + { + if (TryComp(uid, out var mindbreak)) + mindbreak.MindbrokenExaminationText = "examine-mindbroken-shadowkin-message"; + + if (TryComp(uid, out var humanoid)) + { + component.OldEyeColor = humanoid.EyeColor; + humanoid.EyeColor = component.BlackEyeColor; + Dirty(humanoid); + } + + if (component.BlackeyeSpawn) + return; + + if (TryComp(uid, out var stamina)) + _stamina.TakeStaminaDamage(uid, stamina.CritThreshold, stamina, uid); + } + + private void OnRejuvenate(EntityUid uid, ShadowkinComponent component, RejuvenateEvent args) + { + if (component.BlackeyeSpawn + || !HasComp(uid)) + return; + + RemComp(uid); + + if (TryComp(uid, out var humanoid)) + { + humanoid.EyeColor = component.OldEyeColor; + Dirty(humanoid); + } + + EnsureComp(uid, out var magic); + magic.Mana = 250; + magic.MaxMana = 250; + magic.ManaGain = 0.25f; + magic.BypassManaCheck = true; + magic.Removable = false; + magic.MindbreakingFeedback = "shadowkin-blackeye"; + + if (_prototypeManager.TryIndex("ShadowkinPowers", out var shadowkinPowers)) + _psionicAbilitiesSystem.InitializePsionicPower(uid, shadowkinPowers); + + UpdateShadowkinAlert(uid, component); + } +} diff --git a/Content.Server/Shadowkin/ShowEtherealSystem.cs b/Content.Server/Shadowkin/ShowEtherealSystem.cs new file mode 100644 index 0000000000..151c379afb --- /dev/null +++ b/Content.Server/Shadowkin/ShowEtherealSystem.cs @@ -0,0 +1,88 @@ +using Content.Shared.Shadowkin; +using Content.Shared.Eye; +using Robust.Server.GameObjects; +using Content.Shared.Inventory.Events; +using Content.Shared.Interaction.Events; +using Robust.Shared.Timing; +using Content.Shared.Popups; +using Content.Shared.Clothing.Components; + +namespace Content.Server.Shadowkin; +public sealed class ShowEtherealSystem : EntitySystem +{ + [Dependency] private readonly EyeSystem _eye = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnEquipped); + SubscribeLocalEvent(OnUnequipped); + SubscribeLocalEvent(OnInteractionAttempt); + SubscribeLocalEvent(OnAttackAttempt); + } + + private void OnInit(EntityUid uid, ShowEtherealComponent component, MapInitEvent args) + { + Toggle(uid, true); + } + + public void OnShutdown(EntityUid uid, ShowEtherealComponent component, ComponentShutdown args) + { + Toggle(uid, false); + } + + private void OnEquipped(EntityUid uid, ShowEtherealComponent component, GotEquippedEvent args) + { + if (!TryComp(uid, out var clothing) + || !clothing.Slots.HasFlag(args.SlotFlags)) + return; + + EnsureComp(args.Equipee); + } + + private void OnUnequipped(EntityUid uid, ShowEtherealComponent component, GotUnequippedEvent args) + { + RemComp(args.Equipee); + } + + private void Toggle(EntityUid uid, bool toggle) + { + if (!TryComp(uid, out var eye)) + return; + + if (toggle) + { + _eye.SetVisibilityMask(uid, eye.VisibilityMask | (int) (VisibilityFlags.Ethereal), eye); + return; + } + else if (HasComp(uid)) + return; + + _eye.SetVisibilityMask(uid, (int) VisibilityFlags.Normal, eye); + } + + private void OnInteractionAttempt(EntityUid uid, ShowEtherealComponent component, InteractionAttemptEvent args) + { + if (HasComp(uid) + || !HasComp(args.Target)) + return; + + args.Cancel(); + if (_gameTiming.InPrediction) + return; + + _popup.PopupEntity(Loc.GetString("ethereal-pickup-fail"), args.Target.Value, uid); + } + + private void OnAttackAttempt(EntityUid uid, ShowEtherealComponent component, AttackAttemptEvent args) + { + if (HasComp(uid) + || !HasComp(args.Target)) + return; + + args.Cancel(); + } +} \ No newline at end of file diff --git a/Content.Server/ShortConstruction/ShortConstructionSystem.cs b/Content.Server/ShortConstruction/ShortConstructionSystem.cs new file mode 100644 index 0000000000..b274b6e9bf --- /dev/null +++ b/Content.Server/ShortConstruction/ShortConstructionSystem.cs @@ -0,0 +1,24 @@ +using Content.Shared.RadialSelector; +using Content.Shared.ShortConstruction; +using Content.Shared.UserInterface; +using Robust.Server.GameObjects; + +namespace Content.Server.ShortConstruction; + +public sealed class ShortConstructionSystem : EntitySystem +{ + [Dependency] private readonly UserInterfaceSystem _ui = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(BeforeUiOpen); + } + + private void BeforeUiOpen(Entity ent, ref BeforeActivatableUIOpenEvent args) + { + var state = new RadialSelectorState(ent.Comp.Entries); + _ui.SetUiState(ent.Owner, RadialSelectorUiKey.Key, state); + } +} diff --git a/Content.Server/Shuttles/Components/DockingSignalControlComponent.cs b/Content.Server/Shuttles/Components/DockingSignalControlComponent.cs new file mode 100644 index 0000000000..4361265080 --- /dev/null +++ b/Content.Server/Shuttles/Components/DockingSignalControlComponent.cs @@ -0,0 +1,14 @@ +using Content.Shared.DeviceLinking; +using Robust.Shared.Prototypes; + +namespace Content.Server.Shuttles.Components; + +[RegisterComponent] +public sealed partial class DockingSignalControlComponent : Component +{ + /// + /// Output port that is high while docked. + /// + [DataField] + public ProtoId DockStatusSignalPort = "DockStatus"; +} diff --git a/Content.Server/Shuttles/Components/FTLComponent.cs b/Content.Server/Shuttles/Components/FTLComponent.cs index ab40875e29..c9b8406423 100644 --- a/Content.Server/Shuttles/Components/FTLComponent.cs +++ b/Content.Server/Shuttles/Components/FTLComponent.cs @@ -14,6 +14,8 @@ namespace Content.Server.Shuttles.Components; [RegisterComponent] public sealed partial class FTLComponent : Component { + // TODO Full game save / add datafields + [ViewVariables] public FTLState State = FTLState.Available; @@ -23,6 +25,7 @@ public sealed partial class FTLComponent : Component [ViewVariables(VVAccess.ReadWrite)] public float StartupTime = 0f; + // Because of sphagetti, actual travel time is Math.Max(TravelTime, DefaultArrivalTime) [ViewVariables(VVAccess.ReadWrite)] public float TravelTime = 0f; diff --git a/Content.Server/Shuttles/Components/StationEmergencyShuttleComponent.cs b/Content.Server/Shuttles/Components/StationEmergencyShuttleComponent.cs index bdfdcb273a..58d23ee432 100644 --- a/Content.Server/Shuttles/Components/StationEmergencyShuttleComponent.cs +++ b/Content.Server/Shuttles/Components/StationEmergencyShuttleComponent.cs @@ -13,7 +13,7 @@ public sealed partial class StationEmergencyShuttleComponent : Component /// /// The emergency shuttle assigned to this station. /// - [ViewVariables, Access(typeof(ShuttleSystem), typeof(EmergencyShuttleSystem), Friend = AccessPermissions.ReadWrite)] + [DataField, Access(typeof(ShuttleSystem), typeof(EmergencyShuttleSystem), Friend = AccessPermissions.ReadWrite)] public EntityUid? EmergencyShuttle; /// diff --git a/Content.Server/Shuttles/Components/ThrusterComponent.cs b/Content.Server/Shuttles/Components/ThrusterComponent.cs index 3bba9b5a7f..f64d9bdcbf 100644 --- a/Content.Server/Shuttles/Components/ThrusterComponent.cs +++ b/Content.Server/Shuttles/Components/ThrusterComponent.cs @@ -1,8 +1,10 @@ using System.Numerics; using Content.Server.Shuttles.Systems; +using Content.Shared.Construction.Prototypes; using Content.Shared.Damage; using Robust.Shared.GameStates; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; namespace Content.Server.Shuttles.Components { @@ -13,7 +15,7 @@ public sealed partial class ThrusterComponent : Component /// /// Whether the thruster has been force to be enabled / disabled (e.g. VV, interaction, etc.) /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public bool Enabled { get; set; } = true; /// @@ -22,9 +24,12 @@ public sealed partial class ThrusterComponent : Component public bool IsOn; // Need to serialize this because RefreshParts isn't called on Init and this will break post-mapinit maps! - [ViewVariables(VVAccess.ReadWrite), DataField("thrust")] + [DataField] public float Thrust = 100f; + [DataField] + public float BaseThrust = 100f; + [DataField("thrusterType")] public ThrusterType Type = ThrusterType.Linear; @@ -37,24 +42,31 @@ public sealed partial class ThrusterComponent : Component }; /// - /// How much damage is done per second to anything colliding with our thrust. + /// How much damage is done per second to anything colliding with our thrust. /// - [DataField("damage")] public DamageSpecifier? Damage = new(); + [DataField] + public DamageSpecifier? Damage = new(); - [DataField("requireSpace")] + [DataField] public bool RequireSpace = true; // Used for burns public List Colliding = new(); - public bool Firing = false; + public bool Firing; /// - /// Next time we tick damage for anyone colliding. + /// Next time we tick damage for anyone colliding. /// - [ViewVariables(VVAccess.ReadWrite), DataField("nextFire", customTypeSerializer:typeof(TimeOffsetSerializer))] + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] public TimeSpan NextFire; + + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string MachinePartThrust = "Capacitor"; + + [DataField] + public float PartRatingThrustMultiplier = 1.5f; } public enum ThrusterType diff --git a/Content.Server/Shuttles/Events/FTLStartedEvent.cs b/Content.Server/Shuttles/Events/FTLStartedEvent.cs index 965da7f0c5..eafbeddd5c 100644 --- a/Content.Server/Shuttles/Events/FTLStartedEvent.cs +++ b/Content.Server/Shuttles/Events/FTLStartedEvent.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Robust.Shared.Map; namespace Content.Server.Shuttles.Events; @@ -6,4 +7,4 @@ namespace Content.Server.Shuttles.Events; /// Raised when a shuttle has moved to FTL space. /// [ByRefEvent] -public readonly record struct FTLStartedEvent(EntityUid Entity, EntityCoordinates TargetCoordinates, EntityUid? FromMapUid, Matrix3 FTLFrom, Angle FromRotation); +public readonly record struct FTLStartedEvent(EntityUid Entity, EntityCoordinates TargetCoordinates, EntityUid? FromMapUid, Matrix3x2 FTLFrom, Angle FromRotation); diff --git a/Content.Server/Shuttles/Systems/ArrivalsSystem.cs b/Content.Server/Shuttles/Systems/ArrivalsSystem.cs index ae742cf1f9..62fe3e13ff 100644 --- a/Content.Server/Shuttles/Systems/ArrivalsSystem.cs +++ b/Content.Server/Shuttles/Systems/ArrivalsSystem.cs @@ -1,15 +1,17 @@ using System.Linq; +using System.Numerics; using Content.Server.Administration; +using Content.Server.DeviceNetwork.Components; +using Content.Server.DeviceNetwork.Systems; using Content.Server.GameTicking; using Content.Server.GameTicking.Events; using Content.Server.Parallax; -using Content.Server.DeviceNetwork.Components; -using Content.Server.DeviceNetwork.Systems; using Content.Server.Screens.Components; using Content.Server.Shuttles.Components; using Content.Server.Shuttles.Events; using Content.Server.Spawners.Components; using Content.Server.Station.Components; +using Content.Server.Station.Events; using Content.Server.Station.Systems; using Content.Shared.Administration; using Content.Shared.CCVar; @@ -17,6 +19,7 @@ using Content.Shared.Mobs.Components; using Content.Shared.Movement.Components; using Content.Shared.Parallax.Biomes; +using Content.Shared.Roles; using Content.Shared.Salvage; using Content.Shared.Shuttles.Components; using Content.Shared.Tiles; @@ -77,7 +80,7 @@ public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnArrivalsStartup); + SubscribeLocalEvent(OnStationPostInit); SubscribeLocalEvent(OnShuttleStartup); SubscribeLocalEvent(OnShuttleTag); @@ -187,7 +190,7 @@ private void OnArrivalsFTL(EntityUid shuttleUid, ArrivalsShuttleComponent compon if (TryComp(shuttleUid, out var netComp)) { TryComp(shuttleUid, out var ftlComp); - var ftlTime = TimeSpan.FromSeconds(ftlComp?.TravelTime ?? ShuttleSystem.DefaultTravelTime); + var ftlTime = TimeSpan.FromSeconds(ftlComp?.TravelTime ?? _shuttles.DefaultTravelTime); var payload = new NetworkPayload { @@ -253,7 +256,7 @@ private void OnArrivalsFTL(EntityUid shuttleUid, ArrivalsShuttleComponent compon private void OnArrivalsDocked(EntityUid uid, ArrivalsShuttleComponent component, ref FTLCompletedEvent args) { - TimeSpan dockTime = component.NextTransfer - _timing.CurTime + TimeSpan.FromSeconds(ShuttleSystem.DefaultStartupTime); + var dockTime = component.NextTransfer - _timing.CurTime + TimeSpan.FromSeconds(_shuttles.DefaultStartupTime); if (TryComp(uid, out var netComp)) { @@ -276,7 +279,7 @@ private void DumpChildren(EntityUid uid, ref FTLStartedEvent args) foreach (var (ent, xform) in toDump) { var rotation = xform.LocalRotation; - _transform.SetCoordinates(ent, new EntityCoordinates(args.FromMapUid!.Value, args.FTLFrom.Transform(xform.LocalPosition))); + _transform.SetCoordinates(ent, new EntityCoordinates(args.FromMapUid!.Value, Vector2.Transform(xform.LocalPosition, args.FTLFrom))); _transform.SetWorldRotation(ent, args.FromRotation + rotation); } } @@ -310,6 +313,12 @@ public void HandlePlayerSpawning(PlayerSpawningEvent ev) if (!Enabled || _ticker.RunLevel != GameRunLevel.InRound) return; + if (ev.Job is not null + && ev.Job.Prototype is not null + && _protoManager.Index(ev.Job.Prototype.Value.Id).AlwaysUseSpawner) + return; + + if (!HasComp(ev.Station)) return; @@ -419,13 +428,13 @@ public override void Update(float frameTime) if (comp.NextTransfer > curTime || !TryComp(comp.Station, out var data)) continue; - var tripTime = ShuttleSystem.DefaultTravelTime + ShuttleSystem.DefaultStartupTime; + var tripTime = _shuttles.DefaultTravelTime + _shuttles.DefaultStartupTime; // Go back to arrivals source if (xform.MapUid != arrivalsXform.MapUid) { if (arrivals.IsValid()) - _shuttles.FTLToDock(uid, shuttle, arrivals); + _shuttles.FTLToDock(uid, shuttle, arrivals, _cfgManager.GetCVar(CCVars.ArrivalsStartupTime), _cfgManager.GetCVar(CCVars.ArrivalsHyperspaceTime), "DockArrivals"); comp.NextArrivalsTime = _timing.CurTime + TimeSpan.FromSeconds(tripTime); } @@ -435,7 +444,7 @@ public override void Update(float frameTime) var targetGrid = _station.GetLargestGrid(data); if (targetGrid != null) - _shuttles.FTLToDock(uid, shuttle, targetGrid.Value); + _shuttles.FTLToDock(uid, shuttle, targetGrid.Value, _cfgManager.GetCVar(CCVars.ArrivalsStartupTime), _cfgManager.GetCVar(CCVars.ArrivalsHyperspaceTime), "DockArrivals"); // The ArrivalsCooldown includes the trip there, so we only need to add the time taken for // the trip back. @@ -530,7 +539,7 @@ private void SetArrivals(bool obj) } } - private void OnArrivalsStartup(EntityUid uid, StationArrivalsComponent component, ComponentStartup args) + private void OnStationPostInit(EntityUid uid, StationArrivalsComponent component, ref StationPostInitEvent args) { if (!Enabled) return; diff --git a/Content.Server/Shuttles/Systems/DockingSignalControlSystem.cs b/Content.Server/Shuttles/Systems/DockingSignalControlSystem.cs new file mode 100644 index 0000000000..34cade7f1c --- /dev/null +++ b/Content.Server/Shuttles/Systems/DockingSignalControlSystem.cs @@ -0,0 +1,28 @@ +using Content.Server.DeviceLinking.Systems; +using Content.Server.Shuttles.Components; +using Content.Server.Shuttles.Events; + +namespace Content.Server.Shuttles.Systems; + +public sealed class DockingSignalControlSystem : EntitySystem +{ + [Dependency] private readonly DeviceLinkSystem _deviceLinkSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnDocked); + SubscribeLocalEvent(OnUndocked); + } + + private void OnDocked(Entity ent, ref DockEvent args) + { + _deviceLinkSystem.SendSignal(ent, ent.Comp.DockStatusSignalPort, signal: true); + } + + private void OnUndocked(Entity ent, ref UndockEvent args) + { + _deviceLinkSystem.SendSignal(ent, ent.Comp.DockStatusSignalPort, signal: false); + } +} diff --git a/Content.Server/Shuttles/Systems/DockingSystem.Shuttle.cs b/Content.Server/Shuttles/Systems/DockingSystem.Shuttle.cs index e46a7c715f..1a95ef9cb2 100644 --- a/Content.Server/Shuttles/Systems/DockingSystem.Shuttle.cs +++ b/Content.Server/Shuttles/Systems/DockingSystem.Shuttle.cs @@ -46,13 +46,13 @@ private bool CanDock( FixturesComponent shuttleFixtures, MapGridComponent grid, bool isMap, - out Matrix3 matty, + out Matrix3x2 matty, out Box2 shuttleDockedAABB, out Angle gridRotation) { shuttleDockedAABB = Box2.UnitCentered; gridRotation = Angle.Zero; - matty = Matrix3.Identity; + matty = Matrix3x2.Identity; if (shuttleDock.Docked || gridDock.Docked || @@ -71,9 +71,9 @@ private bool CanDock( var gridDockAngle = gridDockXform.LocalRotation.Opposite(); var offsetAngle = gridDockAngle - shuttleDockAngle; - var stationDockMatrix = Matrix3.CreateInverseTransform(stationDockPos, shuttleDockAngle); - var gridXformMatrix = Matrix3.CreateTransform(gridDockXform.LocalPosition, gridDockAngle); - Matrix3.Multiply(in stationDockMatrix, in gridXformMatrix, out matty); + var stationDockMatrix = Matrix3Helpers.CreateInverseTransform(stationDockPos, shuttleDockAngle); + var gridXformMatrix = Matrix3Helpers.CreateTransform(gridDockXform.LocalPosition, gridDockAngle); + matty = Matrix3x2.Multiply(stationDockMatrix, gridXformMatrix); if (!ValidSpawn(grid, matty, offsetAngle, shuttleFixtures, isMap)) return false; @@ -193,7 +193,7 @@ private List GetDockingConfigs( } // Can't just use the AABB as we want to get bounds as tight as possible. - var gridPosition = new EntityCoordinates(targetGrid, matty.Transform(Vector2.Zero)); + var gridPosition = new EntityCoordinates(targetGrid, Vector2.Transform(Vector2.Zero, matty)); var spawnPosition = new EntityCoordinates(targetGridXform.MapUid!.Value, gridPosition.ToMapPos(EntityManager, _transform)); // TODO: use tight bounds @@ -303,9 +303,9 @@ private List GetDockingConfigs( /// /// Checks whether the shuttle can warp to the specified position. /// - private bool ValidSpawn(MapGridComponent grid, Matrix3 matty, Angle angle, FixturesComponent shuttleFixturesComp, bool isMap) + private bool ValidSpawn(MapGridComponent grid, Matrix3x2 matty, Angle angle, FixturesComponent shuttleFixturesComp, bool isMap) { - var transform = new Transform(matty.Transform(Vector2.Zero), angle); + var transform = new Transform(Vector2.Transform(Vector2.Zero, matty), angle); // Because some docking bounds are tight af need to check each chunk individually foreach (var fix in shuttleFixturesComp.Fixtures.Values) diff --git a/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.Console.cs b/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.Console.cs index 3f6eafb454..6c040e7a0e 100644 --- a/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.Console.cs +++ b/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.Console.cs @@ -1,10 +1,8 @@ using System.Threading; -using Content.Server.DeviceNetwork; using Content.Server.DeviceNetwork.Components; using Content.Server.Screens.Components; using Content.Server.Shuttles.Components; using Content.Server.Shuttles.Events; -using Content.Shared.UserInterface; using Content.Shared.Access; using Content.Shared.CCVar; using Content.Shared.Database; @@ -13,12 +11,15 @@ using Content.Shared.Shuttles.BUIStates; using Content.Shared.Shuttles.Events; using Content.Shared.Shuttles.Systems; +using Content.Shared.UserInterface; using Robust.Shared.Map; using Robust.Shared.Player; using Timer = Robust.Shared.Timing.Timer; namespace Content.Server.Shuttles.Systems; +// TODO full game saves +// Move state data into the emergency shuttle component public sealed partial class EmergencyShuttleSystem { /* @@ -55,7 +56,7 @@ public sealed partial class EmergencyShuttleSystem /// /// How long it will take for the emergency shuttle to arrive at CentComm. /// - public float TransitTime { get; private set; } + public float TransitTime; /// /// @@ -129,9 +130,17 @@ private void OnEmergencyStartup(EntityUid uid, EmergencyShuttleConsoleComponent private void UpdateEmergencyConsole(float frameTime) { // Add some buffer time so eshuttle always first. - var minTime = -(TransitTime - (ShuttleSystem.DefaultStartupTime + ShuttleSystem.DefaultTravelTime + 1f)); + var minTime = -(TransitTime - (_shuttle.DefaultStartupTime + _shuttle.DefaultTravelTime + 1f)); // TODO: I know this is shit but I already just cleaned up a billion things. + + // This is very cursed spaghetti code. I don't even know what the fuck this is doing or why it exists. + // But I think it needs to be less than or equal to zero or the shuttle might never leave??? + // TODO Shuttle AAAAAAAAAAAAAAAAAAAAAAAAA + // Clean this up, just have a single timer with some state system. + // I.e., dont infer state from the current interval that the accumulator is in??? + minTime = Math.Min(0, minTime); // ???? + if (_consoleAccumulator < minTime) { return; @@ -147,7 +156,7 @@ private void UpdateEmergencyConsole(float frameTime) } // Imminent departure - if (!_launchedShuttles && _consoleAccumulator <= ShuttleSystem.DefaultStartupTime) + if (!_launchedShuttles && _consoleAccumulator <= _shuttle.DefaultStartupTime) { _launchedShuttles = true; @@ -241,19 +250,18 @@ private void UpdateEmergencyConsole(float frameTime) private void OnEmergencyRepealAll(EntityUid uid, EmergencyShuttleConsoleComponent component, EmergencyShuttleRepealAllMessage args) { - var player = args.Session.AttachedEntity; - if (player == null) return; + var player = args.Actor; - if (!_reader.FindAccessTags(player.Value).Contains(EmergencyRepealAllAccess)) + if (!_reader.FindAccessTags(player).Contains(EmergencyRepealAllAccess)) { - _popup.PopupCursor(Loc.GetString("emergency-shuttle-console-denied"), player.Value, PopupType.Medium); + _popup.PopupCursor(Loc.GetString("emergency-shuttle-console-denied"), player, PopupType.Medium); return; } if (component.AuthorizedEntities.Count == 0) return; - _logger.Add(LogType.EmergencyShuttle, LogImpact.High, $"Emergency shuttle early launch REPEAL ALL by {args.Session:user}"); + _logger.Add(LogType.EmergencyShuttle, LogImpact.High, $"Emergency shuttle early launch REPEAL ALL by {args.Actor:user}"); _announcer.SendAnnouncement( _announcer.GetAnnouncementId("ShuttleAuthRevoked"), Filter.Broadcast(), @@ -267,13 +275,11 @@ private void OnEmergencyRepealAll(EntityUid uid, EmergencyShuttleConsoleComponen private void OnEmergencyRepeal(EntityUid uid, EmergencyShuttleConsoleComponent component, EmergencyShuttleRepealMessage args) { - var player = args.Session.AttachedEntity; - if (player == null) - return; + var player = args.Actor; - if (!_idSystem.TryFindIdCard(player.Value, out var idCard) || !_reader.IsAllowed(idCard, uid)) + if (!_idSystem.TryFindIdCard(player, out var idCard) || !_reader.IsAllowed(idCard, uid)) { - _popup.PopupCursor(Loc.GetString("emergency-shuttle-console-denied"), player.Value, PopupType.Medium); + _popup.PopupCursor(Loc.GetString("emergency-shuttle-console-denied"), player, PopupType.Medium); return; } @@ -281,7 +287,7 @@ private void OnEmergencyRepeal(EntityUid uid, EmergencyShuttleConsoleComponent c if (!component.AuthorizedEntities.Remove(MetaData(idCard).EntityName)) return; - _logger.Add(LogType.EmergencyShuttle, LogImpact.High, $"Emergency shuttle early launch REPEAL by {args.Session:user}"); + _logger.Add(LogType.EmergencyShuttle, LogImpact.High, $"Emergency shuttle early launch REPEAL by {args.Actor:user}"); var remaining = component.AuthorizationsRequired - component.AuthorizedEntities.Count; _announcer.SendAnnouncement( _announcer.GetAnnouncementId("ShuttleAuthRevoked"), @@ -296,13 +302,11 @@ private void OnEmergencyRepeal(EntityUid uid, EmergencyShuttleConsoleComponent c private void OnEmergencyAuthorize(EntityUid uid, EmergencyShuttleConsoleComponent component, EmergencyShuttleAuthorizeMessage args) { - var player = args.Session.AttachedEntity; - if (player == null) - return; + var player = args.Actor; - if (!_idSystem.TryFindIdCard(player.Value, out var idCard) || !_reader.IsAllowed(idCard, uid)) + if (!_idSystem.TryFindIdCard(player, out var idCard) || !_reader.IsAllowed(idCard, uid)) { - _popup.PopupCursor(Loc.GetString("emergency-shuttle-console-denied"), args.Session, PopupType.Medium); + _popup.PopupCursor(Loc.GetString("emergency-shuttle-console-denied"), args.Actor, PopupType.Medium); return; } @@ -310,7 +314,7 @@ private void OnEmergencyAuthorize(EntityUid uid, EmergencyShuttleConsoleComponen if (!component.AuthorizedEntities.Add(MetaData(idCard).EntityName)) return; - _logger.Add(LogType.EmergencyShuttle, LogImpact.High, $"Emergency shuttle early launch AUTH by {args.Session:user}"); + _logger.Add(LogType.EmergencyShuttle, LogImpact.High, $"Emergency shuttle early launch AUTH by {args.Actor:user}"); var remaining = component.AuthorizationsRequired - component.AuthorizedEntities.Count; if (remaining > 0) @@ -362,9 +366,10 @@ private void UpdateConsoleState(EntityUid uid, EmergencyShuttleConsoleComponent auths.Add(auth); } - if (_uiSystem.TryGetUi(uid, EmergencyConsoleUiKey.Key, out var bui)) + if (_uiSystem.HasUi(uid, EmergencyConsoleUiKey.Key)) _uiSystem.SetUiState( - bui, + uid, + EmergencyConsoleUiKey.Key, new EmergencyConsoleBoundUserInterfaceState() { EarlyLaunchTime = EarlyLaunchAuthorized ? _timing.CurTime + TimeSpan.FromSeconds(_consoleAccumulator) : null, diff --git a/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs b/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs index 2df0bc148c..90be263ae2 100644 --- a/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs +++ b/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs @@ -15,6 +15,7 @@ using Content.Server.Shuttles.Components; using Content.Server.Shuttles.Events; using Content.Server.Station.Components; +using Content.Server.Station.Events; using Content.Server.Station.Systems; using Content.Shared.Access.Systems; using Content.Shared.CCVar; @@ -84,9 +85,9 @@ public override void Initialize() SubscribeLocalEvent(OnRoundStart); SubscribeLocalEvent(OnRoundCleanup); - SubscribeLocalEvent(OnStationStartup); + SubscribeLocalEvent(OnStationStartup); SubscribeLocalEvent(OnCentcommShutdown); - SubscribeLocalEvent(OnCentcommInit); + SubscribeLocalEvent(OnStationInit); SubscribeLocalEvent(OnEmergencyFTL); SubscribeLocalEvent(OnEmergencyFTLComplete); @@ -201,10 +202,9 @@ private void OnShuttleRequestPosition(EmergencyShuttleRequestPositionMessage msg /// private void OnEmergencyFTL(EntityUid uid, EmergencyShuttleComponent component, ref FTLStartedEvent args) { - TimeSpan ftlTime = TimeSpan.FromSeconds + var ftlTime = TimeSpan.FromSeconds ( - TryComp(uid, out var ftlComp) ? - ftlComp.TravelTime : ShuttleSystem.DefaultTravelTime + TryComp(uid, out var ftlComp) ? ftlComp.TravelTime : _shuttle.DefaultTravelTime ); if (TryComp(uid, out var netComp)) @@ -260,10 +260,13 @@ private void OnEmergencyFTLComplete(EntityUid uid, EmergencyShuttleComponent com /// public void CallEmergencyShuttle(EntityUid stationUid, StationEmergencyShuttleComponent? stationShuttle = null) { - if (!Resolve(stationUid, ref stationShuttle) || - !TryComp(stationShuttle.EmergencyShuttle, out var xform) || + if (!Resolve(stationUid, ref stationShuttle)) + return; + + if (!TryComp(stationShuttle.EmergencyShuttle, out var xform) || !TryComp(stationShuttle.EmergencyShuttle, out var shuttle)) { + Log.Error($"Attempted to call an emergency shuttle for an uninitialized station? Station: {ToPrettyString(stationUid)}. Shuttle: {ToPrettyString(stationShuttle.EmergencyShuttle)}"); return; } @@ -329,8 +332,10 @@ public void CallEmergencyShuttle(EntityUid stationUid, StationEmergencyShuttleCo } } - private void OnCentcommInit(EntityUid uid, StationCentcommComponent component, ComponentInit args) + private void OnStationInit(EntityUid uid, StationCentcommComponent component, MapInitEvent args) { + // This is handled on map-init, so that centcomm has finished initializing by the time the StationPostInitEvent + // gets raised if (!_emergencyShuttleEnabled) return; @@ -341,12 +346,12 @@ private void OnCentcommInit(EntityUid uid, StationCentcommComponent component, C return; } - AddCentcomm(component); + AddCentcomm(uid, component); } - private void OnStationStartup(EntityUid uid, StationEmergencyShuttleComponent component, ComponentStartup args) + private void OnStationStartup(Entity ent, ref StationPostInitEvent args) { - AddEmergencyShuttle(uid, component); + AddEmergencyShuttle((ent, ent)); } /// @@ -383,19 +388,22 @@ private void SetupEmergencyShuttle() var centcommQuery = AllEntityQuery(); - while (centcommQuery.MoveNext(out var centcomm)) + while (centcommQuery.MoveNext(out var uid, out var centcomm)) { - AddCentcomm(centcomm); + AddCentcomm(uid, centcomm); } var query = AllEntityQuery(); while (query.MoveNext(out var uid, out var comp)) - AddEmergencyShuttle(uid, comp); + { + AddEmergencyShuttle((uid, comp)); + } } - private void AddCentcomm(StationCentcommComponent component) + private void AddCentcomm(EntityUid station, StationCentcommComponent component) { + DebugTools.Assert(LifeStage(station)>= EntityLifeStage.MapInitialized); if (component.MapEntity != null || component.Entity != null) { Log.Warning("Attempted to re-add an existing centcomm map."); @@ -411,12 +419,13 @@ private void AddCentcomm(StationCentcommComponent component) if (!Exists(otherComp.MapEntity) || !Exists(otherComp.Entity)) { - Log.Error($"Disconvered invalid centcomm component?"); + Log.Error($"Discovered invalid centcomm component?"); ClearCentcomm(otherComp); continue; } component.MapEntity = otherComp.MapEntity; + component.Entity = otherComp.Entity; component.ShuttleIndex = otherComp.ShuttleIndex; return; } @@ -460,6 +469,7 @@ private void AddCentcomm(StationCentcommComponent component) component.MapEntity = map; component.Entity = grid; _shuttle.TryAddFTLDestination(mapId, false, out _); + Log.Info($"Created centcomm grid {ToPrettyString(grid)} on map {ToPrettyString(map)} for station {ToPrettyString(station)}"); } public HashSet GetCentcommMaps() @@ -476,49 +486,67 @@ public HashSet GetCentcommMaps() return maps; } - private void AddEmergencyShuttle(EntityUid uid, StationEmergencyShuttleComponent component) + private void AddEmergencyShuttle(Entity ent) { - if (!_emergencyShuttleEnabled - || component.EmergencyShuttle != null || - !TryComp(uid, out var centcomm) - || !TryComp(centcomm.MapEntity, out MapComponent? map)) + if (!Resolve(ent.Owner, ref ent.Comp1, ref ent.Comp2)) + return; + + if (!_emergencyShuttleEnabled) + return; + + if (ent.Comp1.EmergencyShuttle != null ) + { + if (Exists(ent.Comp1.EmergencyShuttle)) + { + Log.Error($"Attempted to add an emergency shuttle to {ToPrettyString(ent)}, despite a shuttle already existing?"); + return; + } + + Log.Error($"Encountered deleted emergency shuttle during initialization of {ToPrettyString(ent)}"); + ent.Comp1.EmergencyShuttle = null; + } + + if (!TryComp(ent.Comp2.MapEntity, out MapComponent? map)) { + Log.Error($"Failed to add emergency shuttle - centcomm has not been initialized? {ToPrettyString(ent)}"); return; } // Load escape shuttle - var shuttlePath = component.EmergencyShuttlePath; + var shuttlePath = ent.Comp1.EmergencyShuttlePath; var shuttle = _map.LoadGrid(map.MapId, shuttlePath.ToString(), new MapLoadOptions() { // Should be far enough... right? I'm too lazy to bounds check CentCom rn. - Offset = new Vector2(500f + centcomm.ShuttleIndex, 0f), + Offset = new Vector2(500f + ent.Comp2.ShuttleIndex, 0f), // fun fact: if you just fucking yeet centcomm into nullspace anytime you try to spawn the shuttle, then any distance is far enough. so lets not do that LoadMap = false, }); if (shuttle == null) { - Log.Error($"Unable to spawn emergency shuttle {shuttlePath} for {ToPrettyString(uid)}"); + Log.Error($"Unable to spawn emergency shuttle {shuttlePath} for {ToPrettyString(ent)}"); return; } - centcomm.ShuttleIndex += Comp(shuttle.Value).LocalAABB.Width + ShuttleSpawnBuffer; + ent.Comp2.ShuttleIndex += Comp(shuttle.Value).LocalAABB.Width + ShuttleSpawnBuffer; // Update indices for all centcomm comps pointing to same map var query = AllEntityQuery(); while (query.MoveNext(out var comp)) { - if (comp == centcomm || comp.MapEntity != centcomm.MapEntity) + if (comp == ent.Comp2 || comp.MapEntity != ent.Comp2.MapEntity) continue; - comp.ShuttleIndex = centcomm.ShuttleIndex; + comp.ShuttleIndex = ent.Comp2.ShuttleIndex; } - component.EmergencyShuttle = shuttle; + ent.Comp1.EmergencyShuttle = shuttle; EnsureComp(shuttle.Value); EnsureComp(shuttle.Value); EnsureComp(shuttle.Value); + + Log.Info($"Added emergency shuttle {ToPrettyString(shuttle)} for station {ToPrettyString(ent)} and centcomm {ToPrettyString(ent.Comp2.Entity)}"); } /// diff --git a/Content.Server/Shuttles/Systems/RadarConsoleSystem.cs b/Content.Server/Shuttles/Systems/RadarConsoleSystem.cs index b7f08b4b34..1de20a8734 100644 --- a/Content.Server/Shuttles/Systems/RadarConsoleSystem.cs +++ b/Content.Server/Shuttles/Systems/RadarConsoleSystem.cs @@ -39,7 +39,7 @@ protected override void UpdateState(EntityUid uid, RadarConsoleComponent compone angle = Angle.Zero; } - if (_uiSystem.TryGetUi(uid, RadarConsoleUiKey.Key, out var bui)) + if (_uiSystem.HasUi(uid, RadarConsoleUiKey.Key)) { NavInterfaceState state; var docks = _console.GetAllDocks(); @@ -53,7 +53,7 @@ protected override void UpdateState(EntityUid uid, RadarConsoleComponent compone state = _console.GetNavState(uid, docks); } - _uiSystem.SetUiState(bui, new NavBoundUserInterfaceState(state)); + _uiSystem.SetUiState(uid, RadarConsoleUiKey.Key, new NavBoundUserInterfaceState(state)); } } } diff --git a/Content.Server/Shuttles/Systems/ShuttleConsoleSystem.cs b/Content.Server/Shuttles/Systems/ShuttleConsoleSystem.cs index fac74e34c5..6f24208c3a 100644 --- a/Content.Server/Shuttles/Systems/ShuttleConsoleSystem.cs +++ b/Content.Server/Shuttles/Systems/ShuttleConsoleSystem.cs @@ -136,13 +136,12 @@ public void RefreshShuttleConsoles() /// private void OnConsoleUIClose(EntityUid uid, ShuttleConsoleComponent component, BoundUIClosedEvent args) { - if ((ShuttleConsoleUiKey) args.UiKey != ShuttleConsoleUiKey.Key || - args.Session.AttachedEntity is not { } user) + if ((ShuttleConsoleUiKey) args.UiKey != ShuttleConsoleUiKey.Key) { return; } - RemovePilot(user); + RemovePilot(args.Actor); } private void OnConsoleUIOpenAttempt(EntityUid uid, ShuttleConsoleComponent component, @@ -265,9 +264,9 @@ private void UpdateState(EntityUid consoleUid, ref DockingInterfaceState? dockSt new List()); } - if (_ui.TryGetUi(consoleUid, ShuttleConsoleUiKey.Key, out var bui)) + if (_ui.HasUi(consoleUid, ShuttleConsoleUiKey.Key)) { - _ui.SetUiState(bui, new ShuttleBoundUserInterfaceState(navState, mapState, dockState)); + _ui.SetUiState(consoleUid, ShuttleConsoleUiKey.Key, new ShuttleBoundUserInterfaceState(navState, mapState, dockState)); } } diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs b/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs index 5128869103..11cc16e0cd 100644 --- a/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs +++ b/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs @@ -6,6 +6,8 @@ using Content.Server.Station.Events; using Content.Shared.Body.Components; using Content.Shared.Buckle.Components; +using Content.Shared.CCVar; +using Content.Shared.Database; using Content.Shared.Ghost; using Content.Shared.Maps; using Content.Shared.Parallax; @@ -33,14 +35,6 @@ public sealed partial class ShuttleSystem * This is a way to move a shuttle from one location to another, via an intermediate map for fanciness. */ - public const float DefaultStartupTime = 5.5f; - public const float DefaultTravelTime = 20f; - public const float DefaultArrivalTime = 5f; - private const float FTLCooldown = 10f; - public const float FTLMassLimit = 300f; - - // I'm too lazy to make CVars. - private readonly SoundSpecifier _startupSound = new SoundPathSpecifier("/Audio/Effects/Shuttle/hyperspace_begin.ogg") { Params = AudioParams.Default.WithVolume(-5f), @@ -51,7 +45,12 @@ public sealed partial class ShuttleSystem Params = AudioParams.Default.WithVolume(-5f), }; - private readonly TimeSpan _hyperspaceKnockdownTime = TimeSpan.FromSeconds(5); + public float DefaultStartupTime; + public float DefaultTravelTime; + public float DefaultArrivalTime; + private float FTLCooldown; + public float FTLMassLimit; + private TimeSpan _hyperspaceKnockdownTime = TimeSpan.FromSeconds(5); /// /// Left-side of the station we're allowed to use @@ -89,6 +88,13 @@ private void InitializeFTL() _physicsQuery = GetEntityQuery(); _statusQuery = GetEntityQuery(); _xformQuery = GetEntityQuery(); + + _cfg.OnValueChanged(CCVars.FTLStartupTime, time => DefaultStartupTime = time, true); + _cfg.OnValueChanged(CCVars.FTLTravelTime, time => DefaultTravelTime = time, true); + _cfg.OnValueChanged(CCVars.FTLArrivalTime, time => DefaultArrivalTime = time, true); + _cfg.OnValueChanged(CCVars.FTLCooldown, time => FTLCooldown = time, true); + _cfg.OnValueChanged(CCVars.FTLMassLimit, time => FTLMassLimit = time, true); + _cfg.OnValueChanged(CCVars.HyperspaceKnockdownTime, time => _hyperspaceKnockdownTime = TimeSpan.FromSeconds(time), true); } private void OnStationPostInit(ref StationPostInitEvent ev) @@ -210,7 +216,9 @@ public bool CanFTL(EntityUid shuttleUid, [NotNullWhen(false)] out string? reason return false; } - if (TryComp(shuttleUid, out PhysicsComponent? shuttlePhysics) && shuttlePhysics.Mass > FTLMassLimit) + if (FTLMassLimit > 0 && + TryComp(shuttleUid, out PhysicsComponent? shuttlePhysics) && + shuttlePhysics.Mass > FTLMassLimit) { reason = Loc.GetString("shuttle-console-mass"); return false; @@ -243,15 +251,18 @@ public void FTLToCoordinates( ShuttleComponent component, EntityCoordinates coordinates, Angle angle, - float startupTime = DefaultStartupTime, - float hyperspaceTime = DefaultTravelTime, + float? startupTime = null, + float? hyperspaceTime = null, string? priorityTag = null) { if (!TrySetupFTL(shuttleUid, component, out var hyperspace)) return; - hyperspace.StartupTime = startupTime; - hyperspace.TravelTime = hyperspaceTime; + startupTime ??= DefaultStartupTime; + hyperspaceTime ??= DefaultTravelTime; + + hyperspace.StartupTime = startupTime.Value; + hyperspace.TravelTime = hyperspaceTime.Value; hyperspace.StateTime = StartEndTime.FromStartDuration( _gameTiming.CurTime, TimeSpan.FromSeconds(hyperspace.StartupTime)); @@ -275,16 +286,19 @@ public void FTLToDock( EntityUid shuttleUid, ShuttleComponent component, EntityUid target, - float startupTime = DefaultStartupTime, - float hyperspaceTime = DefaultTravelTime, + float? startupTime = null, + float? hyperspaceTime = null, string? priorityTag = null) { if (!TrySetupFTL(shuttleUid, component, out var hyperspace)) return; + startupTime ??= DefaultStartupTime; + hyperspaceTime ??= DefaultTravelTime; + var config = _dockSystem.GetDockingConfig(shuttleUid, target, priorityTag); - hyperspace.StartupTime = startupTime; - hyperspace.TravelTime = hyperspaceTime; + hyperspace.StartupTime = startupTime.Value; + hyperspace.TravelTime = hyperspaceTime.Value; hyperspace.StateTime = StartEndTime.FromStartDuration( _gameTiming.CurTime, TimeSpan.FromSeconds(hyperspace.StartupTime)); @@ -863,6 +877,8 @@ private void Smimsh(EntityUid uid, FixturesComponent? manager = null, MapGridCom if (_bodyQuery.TryGetComponent(ent, out var mob)) { + _logger.Add(LogType.Gib, LogImpact.Extreme, $"{ToPrettyString(ent):player} got gibbed by the shuttle" + + $" {ToPrettyString(uid)} arriving from FTL at {xform.Coordinates:coordinates}"); var gibs = _bobby.GibBody(ent, body: mob); _immuneEnts.UnionWith(gibs); continue; diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.IFF.cs b/Content.Server/Shuttles/Systems/ShuttleSystem.IFF.cs index bf265da2e6..ce79466b58 100644 --- a/Content.Server/Shuttles/Systems/ShuttleSystem.IFF.cs +++ b/Content.Server/Shuttles/Systems/ShuttleSystem.IFF.cs @@ -57,7 +57,7 @@ private void OnIFFConsoleAnchor(EntityUid uid, IFFConsoleComponent component, re !TryComp(uid, out var xform) || !TryComp(xform.GridUid, out var iff)) { - _uiSystem.TrySetUiState(uid, IFFConsoleUiKey.Key, new IFFConsoleBoundUserInterfaceState() + _uiSystem.SetUiState(uid, IFFConsoleUiKey.Key, new IFFConsoleBoundUserInterfaceState() { AllowedFlags = component.AllowedFlags, Flags = IFFFlags.None, @@ -65,7 +65,7 @@ private void OnIFFConsoleAnchor(EntityUid uid, IFFConsoleComponent component, re } else { - _uiSystem.TrySetUiState(uid, IFFConsoleUiKey.Key, new IFFConsoleBoundUserInterfaceState() + _uiSystem.SetUiState(uid, IFFConsoleUiKey.Key, new IFFConsoleBoundUserInterfaceState() { AllowedFlags = component.AllowedFlags, Flags = iff.Flags, @@ -83,7 +83,7 @@ protected override void UpdateIFFInterfaces(EntityUid gridUid, IFFComponent comp if (xform.GridUid != gridUid) continue; - _uiSystem.TrySetUiState(uid, IFFConsoleUiKey.Key, new IFFConsoleBoundUserInterfaceState() + _uiSystem.SetUiState(uid, IFFConsoleUiKey.Key, new IFFConsoleBoundUserInterfaceState() { AllowedFlags = comp.AllowedFlags, Flags = component.Flags, diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.Impact.cs b/Content.Server/Shuttles/Systems/ShuttleSystem.Impact.cs index f346398cda..8a8d2d883d 100644 --- a/Content.Server/Shuttles/Systems/ShuttleSystem.Impact.cs +++ b/Content.Server/Shuttles/Systems/ShuttleSystem.Impact.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Content.Server.Shuttles.Components; using Content.Shared.Audio; using Robust.Shared.Audio; @@ -38,8 +39,8 @@ private void OnShuttleCollide(EntityUid uid, ShuttleComponent component, ref Sta var otherXform = Transform(args.OtherEntity); - var ourPoint = ourXform.InvWorldMatrix.Transform(args.WorldPoint); - var otherPoint = otherXform.InvWorldMatrix.Transform(args.WorldPoint); + var ourPoint = Vector2.Transform(args.WorldPoint, ourXform.InvWorldMatrix); + var otherPoint = Vector2.Transform(args.WorldPoint, otherXform.InvWorldMatrix); var ourVelocity = _physics.GetLinearVelocity(uid, ourPoint, ourBody, ourXform); var otherVelocity = _physics.GetLinearVelocity(args.OtherEntity, otherPoint, otherBody, otherXform); diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.cs b/Content.Server/Shuttles/Systems/ShuttleSystem.cs index 6dc25e8d76..6fe2324d51 100644 --- a/Content.Server/Shuttles/Systems/ShuttleSystem.cs +++ b/Content.Server/Shuttles/Systems/ShuttleSystem.cs @@ -1,3 +1,4 @@ +using Content.Server.Administration.Logs; using Content.Server.Body.Systems; using Content.Server.Doors.Systems; using Content.Server.Parallax; @@ -48,6 +49,7 @@ public sealed partial class ShuttleSystem : SharedShuttleSystem [Dependency] private readonly ThrowingSystem _throwing = default!; [Dependency] private readonly ThrusterSystem _thruster = default!; [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; + [Dependency] private readonly IAdminLogManager _logger = default!; public const float TileMassMultiplier = 0.5f; diff --git a/Content.Server/Shuttles/Systems/ThrusterSystem.cs b/Content.Server/Shuttles/Systems/ThrusterSystem.cs index be55cd9a62..46715dd291 100644 --- a/Content.Server/Shuttles/Systems/ThrusterSystem.cs +++ b/Content.Server/Shuttles/Systems/ThrusterSystem.cs @@ -1,5 +1,6 @@ using System.Numerics; using Content.Server.Audio; +using Content.Server.Construction; using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; using Content.Server.Shuttles.Components; @@ -51,6 +52,9 @@ public override void Initialize() SubscribeLocalEvent(OnThrusterExamine); + SubscribeLocalEvent(OnRefreshParts); + SubscribeLocalEvent(OnUpgradeExamine); + SubscribeLocalEvent(OnShuttleTileChange); } @@ -264,11 +268,6 @@ public void EnableThruster(EntityUid uid, ThrusterComponent component, Transform return; } - if (TryComp(uid, out var apcPower)) - { - apcPower.NeedsPower = true; - } - component.IsOn = true; if (!EntityManager.TryGetComponent(xform.GridUid, out ShuttleComponent? shuttleComponent)) @@ -371,11 +370,6 @@ public void DisableThruster(EntityUid uid, ThrusterComponent component, EntityUi if (!EntityManager.TryGetComponent(gridId, out ShuttleComponent? shuttleComponent)) return; - if (TryComp(uid, out var apcPower)) - { - apcPower.NeedsPower = false; - } - // Logger.DebugS("thruster", $"Disabled thruster {uid}"); switch (component.Type) @@ -580,6 +574,24 @@ public void SetAngularThrust(ShuttleComponent component, bool on) } } + private void OnRefreshParts(EntityUid uid, ThrusterComponent component, RefreshPartsEvent args) + { + if (component.IsOn) // safely disable thruster to prevent negative thrust + DisableThruster(uid, component); + + var thrustRating = args.PartRatings[component.MachinePartThrust]; + + component.Thrust = component.BaseThrust * MathF.Pow(component.PartRatingThrustMultiplier, thrustRating - 1); + + if (component.Enabled && CanEnable(uid, component)) + EnableThruster(uid, component); + } + + private void OnUpgradeExamine(EntityUid uid, ThrusterComponent component, UpgradeExamineEvent args) + { + args.AddPercentageUpgrade("thruster-comp-upgrade-thrust", component.Thrust / component.BaseThrust); + } + #endregion private int GetFlagIndex(DirectionFlag flag) diff --git a/Content.Server/Silicon/BatteryLocking/BatterySlotRequiresLockComponent.cs b/Content.Server/Silicon/BatteryLocking/BatterySlotRequiresLockComponent.cs new file mode 100644 index 0000000000..693ed1b483 --- /dev/null +++ b/Content.Server/Silicon/BatteryLocking/BatterySlotRequiresLockComponent.cs @@ -0,0 +1,8 @@ +namespace Content.Server.Silicon.BatteryLocking; + +[RegisterComponent] +public sealed partial class BatterySlotRequiresLockComponent : Component +{ + [DataField] + public string ItemSlot = string.Empty; +} diff --git a/Content.Server/Silicon/BatteryLocking/BatterySlotRequiresLockSystem.cs b/Content.Server/Silicon/BatteryLocking/BatterySlotRequiresLockSystem.cs new file mode 100644 index 0000000000..12e8669128 --- /dev/null +++ b/Content.Server/Silicon/BatteryLocking/BatterySlotRequiresLockSystem.cs @@ -0,0 +1,41 @@ +using Content.Shared.Containers.ItemSlots; +using Content.Shared.Lock; +using Content.Shared.Popups; +using Content.Shared.Silicon.Components; +using Content.Shared.IdentityManagement; + +namespace Content.Server.Silicon.BatteryLocking; + +public sealed class BatterySlotRequiresLockSystem : EntitySystem + +{ + [Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!; + [Dependency] private readonly SharedPopupSystem _popupSystem = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(LockToggled); + SubscribeLocalEvent(LockToggleAttempted); + } + + private void LockToggled(EntityUid uid, BatterySlotRequiresLockComponent component, LockToggledEvent args) + { + if (!TryComp(uid, out var lockComp) + || !TryComp(uid, out var itemslots) + || !_itemSlotsSystem.TryGetSlot(uid, component.ItemSlot, out var slot, itemslots)) + return; + + _itemSlotsSystem.SetLock(uid, slot, lockComp.Locked, itemslots); + } + + private void LockToggleAttempted(EntityUid uid, BatterySlotRequiresLockComponent component, LockToggleAttemptEvent args) + { + if (args.User == uid + || !HasComp(uid)) + return; + + _popupSystem.PopupEntity(Loc.GetString("batteryslotrequireslock-component-alert-owner", ("user", Identity.Entity(args.User, EntityManager))), uid, uid, PopupType.Large); + } + +} diff --git a/Content.Server/Silicon/BlindHealing/BlindHealingComponent.cs b/Content.Server/Silicon/BlindHealing/BlindHealingComponent.cs new file mode 100644 index 0000000000..fe21849170 --- /dev/null +++ b/Content.Server/Silicon/BlindHealing/BlindHealingComponent.cs @@ -0,0 +1,23 @@ +namespace Content.Server.Silicon.BlindHealing; + +[RegisterComponent] +public sealed partial class BlindHealingComponent : Component +{ + [DataField] + public int DoAfterDelay = 3; + + /// + /// A multiplier that will be applied to the above if an entity is repairing themselves. + /// + [DataField] + public float SelfHealPenalty = 3f; + + /// + /// Whether or not an entity is allowed to repair itself. + /// + [DataField] + public bool AllowSelfHeal = true; + + [DataField(required: true)] + public List DamageContainers; +} diff --git a/Content.Server/Silicon/BlindHealing/BlindHealingSystem.cs b/Content.Server/Silicon/BlindHealing/BlindHealingSystem.cs new file mode 100644 index 0000000000..b9d26b59f7 --- /dev/null +++ b/Content.Server/Silicon/BlindHealing/BlindHealingSystem.cs @@ -0,0 +1,98 @@ +using Content.Server.Administration.Logs; +using Content.Server.Cargo.Components; +using Content.Server.Stack; +using Content.Shared.Silicon.BlindHealing; +using Content.Shared.Damage; +using Content.Shared.Database; +using Content.Shared.DoAfter; +using Content.Shared.Eye.Blinding.Components; +using Content.Shared.Eye.Blinding.Systems; +using Content.Shared.Interaction; +using Content.Shared.Interaction.Events; +using Content.Shared.Popups; +using Content.Shared.Stacks; + +namespace Content.Server.Silicon.BlindHealing; + +public sealed class BlindHealingSystem : SharedBlindHealingSystem +{ + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly IAdminLogManager _adminLogger = default!; + [Dependency] private readonly BlindableSystem _blindableSystem = default!; + [Dependency] private readonly StackSystem _stackSystem = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + + public override void Initialize() + { + SubscribeLocalEvent(OnUse); + SubscribeLocalEvent(OnInteract); + SubscribeLocalEvent(OnHealingFinished); + } + + private void OnHealingFinished(EntityUid uid, BlindHealingComponent component, HealingDoAfterEvent args) + { + if (args.Cancelled || args.Target == null + || !TryComp(args.Target, out var blindComp) + || blindComp is { EyeDamage: 0 }) + return; + + if (TryComp(uid, out var stackComponent) + && TryComp(uid, out var stackPrice)) + _stackSystem.SetCount(uid, (int) (_stackSystem.GetCount(uid, stackComponent) - stackPrice.Price), stackComponent); + + _blindableSystem.AdjustEyeDamage((args.Target.Value, blindComp), -blindComp.EyeDamage); + + _adminLogger.Add(LogType.Healed, $"{ToPrettyString(args.User):user} repaired {ToPrettyString(uid):target}'s vision"); + + var str = Loc.GetString("comp-repairable-repair", + ("target", uid), + ("tool", args.Used!)); + _popup.PopupEntity(str, uid, args.User); + + } + + private bool TryHealBlindness(EntityUid uid, EntityUid user, EntityUid target, float delay) + { + var doAfterEventArgs = + new DoAfterArgs(EntityManager, user, delay, new HealingDoAfterEvent(), uid, target: target, used: uid) + { + NeedHand = true, + BreakOnUserMove = true, + BreakOnWeightlessMove = false, + }; + + _doAfter.TryStartDoAfter(doAfterEventArgs); + return true; + } + + private void OnInteract(EntityUid uid, BlindHealingComponent component, ref AfterInteractEvent args) + { + + if (args.Handled + || !TryComp(args.User, out var damageable) + || damageable.DamageContainerID != null && !component.DamageContainers.Contains(damageable.DamageContainerID) + || !TryComp(args.User, out var blindcomp) + || blindcomp.EyeDamage == 0 + || args.User == args.Target && !component.AllowSelfHeal) + return; + + TryHealBlindness(uid, args.User, args.User, + args.User == args.Target + ? component.DoAfterDelay * component.SelfHealPenalty + : component.DoAfterDelay); + } + + private void OnUse(EntityUid uid, BlindHealingComponent component, ref UseInHandEvent args) + { + if (args.Handled + || !TryComp(args.User, out var damageable) + || damageable.DamageContainerID != null && !component.DamageContainers.Contains(damageable.DamageContainerID) + || !TryComp(args.User, out var blindcomp) + || blindcomp.EyeDamage == 0 + || !component.AllowSelfHeal) + return; + + TryHealBlindness(uid, args.User, args.User, + component.DoAfterDelay * component.SelfHealPenalty); + } +} diff --git a/Content.Server/Silicon/Charge/Components/BatteryDrinkerSourceComponent.cs b/Content.Server/Silicon/Charge/Components/BatteryDrinkerSourceComponent.cs new file mode 100644 index 0000000000..2a1bfeb87e --- /dev/null +++ b/Content.Server/Silicon/Charge/Components/BatteryDrinkerSourceComponent.cs @@ -0,0 +1,26 @@ +using Robust.Shared.Audio; + +namespace Content.Server.Silicon.Charge; + +[RegisterComponent] +public sealed partial class BatteryDrinkerSourceComponent : Component +{ + /// + /// The max amount of power this source can provide in one sip. + /// No limit if null. + /// + [DataField] + public int? MaxAmount = null; + + /// + /// The multiplier for the drink speed. + /// + [DataField] + public float DrinkSpeedMulti = 1f; + + /// + /// The sound to play when the battery gets drunk from. + /// + [DataField] + public SoundSpecifier? DrinkSound = new SoundCollectionSpecifier("sparks"); +} diff --git a/Content.Server/Silicon/Charge/Components/SiliconDownOnDeadComponent.cs b/Content.Server/Silicon/Charge/Components/SiliconDownOnDeadComponent.cs new file mode 100644 index 0000000000..4b3aad33ab --- /dev/null +++ b/Content.Server/Silicon/Charge/Components/SiliconDownOnDeadComponent.cs @@ -0,0 +1,17 @@ +namespace Content.Server.Silicon.Death; + +/// +/// Marks a Silicon as becoming incapacitated when they run out of battery charge. +/// +/// +/// Uses the Silicon System's charge states to do so, so make sure they're a battery powered Silicon. +/// +[RegisterComponent] +public sealed partial class SiliconDownOnDeadComponent : Component +{ + /// + /// Is this Silicon currently dead? + /// + [ViewVariables(VVAccess.ReadOnly)] + public bool Dead; +} diff --git a/Content.Server/Silicon/Charge/Systems/SiliconChargeDeathSystem.cs b/Content.Server/Silicon/Charge/Systems/SiliconChargeDeathSystem.cs new file mode 100644 index 0000000000..d4d1db5ed9 --- /dev/null +++ b/Content.Server/Silicon/Charge/Systems/SiliconChargeDeathSystem.cs @@ -0,0 +1,127 @@ +using Content.Server.Power.Components; +using Content.Shared.Silicon.Systems; +using Content.Server.Bed.Sleep; +using Content.Shared.Bed.Sleep; +using Content.Server.Silicon.Charge; +using Content.Server.Humanoid; +using Content.Shared.Humanoid; + +namespace Content.Server.Silicon.Death; + +public sealed class SiliconDeathSystem : EntitySystem +{ + [Dependency] private readonly SleepingSystem _sleep = default!; + [Dependency] private readonly SiliconChargeSystem _silicon = default!; + [Dependency] private readonly HumanoidAppearanceSystem _humanoidAppearanceSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnSiliconChargeStateUpdate); + } + + private void OnSiliconChargeStateUpdate(EntityUid uid, SiliconDownOnDeadComponent siliconDeadComp, SiliconChargeStateUpdateEvent args) + { + if (!_silicon.TryGetSiliconBattery(uid, out var batteryComp)) + { + SiliconDead(uid, siliconDeadComp, batteryComp, uid); + return; + } + + if (args.ChargePercent == 0 && siliconDeadComp.Dead) + return; + + if (args.ChargePercent == 0 && !siliconDeadComp.Dead) + SiliconDead(uid, siliconDeadComp, batteryComp, uid); + else if (args.ChargePercent != 0 && siliconDeadComp.Dead) + SiliconUnDead(uid, siliconDeadComp, batteryComp, uid); + } + + private void SiliconDead(EntityUid uid, SiliconDownOnDeadComponent siliconDeadComp, BatteryComponent? batteryComp, EntityUid batteryUid) + { + var deadEvent = new SiliconChargeDyingEvent(uid, batteryComp, batteryUid); + RaiseLocalEvent(uid, deadEvent); + + if (deadEvent.Cancelled) + return; + + EntityManager.EnsureComponent(uid); + EntityManager.EnsureComponent(uid); + + if (TryComp(uid, out var humanoidAppearanceComponent)) + { + var layers = HumanoidVisualLayersExtension.Sublayers(HumanoidVisualLayers.HeadSide); + _humanoidAppearanceSystem.SetLayersVisibility(uid, layers, false, true, humanoidAppearanceComponent); + } + + siliconDeadComp.Dead = true; + + RaiseLocalEvent(uid, new SiliconChargeDeathEvent(uid, batteryComp, batteryUid)); + } + + private void SiliconUnDead(EntityUid uid, SiliconDownOnDeadComponent siliconDeadComp, BatteryComponent? batteryComp, EntityUid batteryUid) + { + RemComp(uid); + _sleep.TryWaking(uid, null, true); + + siliconDeadComp.Dead = false; + + RaiseLocalEvent(uid, new SiliconChargeAliveEvent(uid, batteryComp, batteryUid)); + } +} + +/// +/// A cancellable event raised when a Silicon is about to go down due to charge. +/// +/// +/// This probably shouldn't be modified unless you intend to fill the Silicon's battery, +/// as otherwise it'll just be triggered again next frame. +/// +public sealed class SiliconChargeDyingEvent : CancellableEntityEventArgs +{ + public EntityUid SiliconUid { get; } + public BatteryComponent? BatteryComp { get; } + public EntityUid BatteryUid { get; } + + public SiliconChargeDyingEvent(EntityUid siliconUid, BatteryComponent? batteryComp, EntityUid batteryUid) + { + SiliconUid = siliconUid; + BatteryComp = batteryComp; + BatteryUid = batteryUid; + } +} + +/// +/// An event raised after a Silicon has gone down due to charge. +/// +public sealed class SiliconChargeDeathEvent : EntityEventArgs +{ + public EntityUid SiliconUid { get; } + public BatteryComponent? BatteryComp { get; } + public EntityUid BatteryUid { get; } + + public SiliconChargeDeathEvent(EntityUid siliconUid, BatteryComponent? batteryComp, EntityUid batteryUid) + { + SiliconUid = siliconUid; + BatteryComp = batteryComp; + BatteryUid = batteryUid; + } +} + +/// +/// An event raised after a Silicon has reawoken due to an increase in charge. +/// +public sealed class SiliconChargeAliveEvent : EntityEventArgs +{ + public EntityUid SiliconUid { get; } + public BatteryComponent? BatteryComp { get; } + public EntityUid BatteryUid { get; } + + public SiliconChargeAliveEvent(EntityUid siliconUid, BatteryComponent? batteryComp, EntityUid batteryUid) + { + SiliconUid = siliconUid; + BatteryComp = batteryComp; + BatteryUid = batteryUid; + } +} diff --git a/Content.Server/Silicon/Charge/Systems/SiliconChargeSystem.cs b/Content.Server/Silicon/Charge/Systems/SiliconChargeSystem.cs new file mode 100644 index 0000000000..d8b034a69f --- /dev/null +++ b/Content.Server/Silicon/Charge/Systems/SiliconChargeSystem.cs @@ -0,0 +1,196 @@ +using Robust.Shared.Random; +using Content.Shared.Silicon.Components; +using Content.Server.Power.Components; +using Content.Shared.Mobs.Systems; +using Content.Server.Temperature.Components; +using Content.Server.Atmos.Components; +using Content.Server.Atmos.EntitySystems; +using Content.Server.Popups; +using Content.Shared.Popups; +using Content.Shared.Silicon.Systems; +using Content.Shared.Movement.Systems; +using Content.Server.Body.Components; +using Robust.Shared.Containers; +using Content.Shared.Mind.Components; +using System.Diagnostics.CodeAnalysis; +using Content.Server.PowerCell; +using Robust.Shared.Timing; +using Robust.Shared.Configuration; +using Robust.Shared.Utility; +using Content.Shared.CCVar; +using Content.Shared.PowerCell.Components; +using Content.Shared.Alert; + +namespace Content.Server.Silicon.Charge; + +public sealed class SiliconChargeSystem : EntitySystem +{ + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly MobStateSystem _mobState = default!; + [Dependency] private readonly FlammableSystem _flammable = default!; + [Dependency] private readonly PopupSystem _popup = default!; + [Dependency] private readonly MovementSpeedModifierSystem _moveMod = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly IConfigurationManager _config = default!; + [Dependency] private readonly PowerCellSystem _powerCell = default!; + [Dependency] private readonly AlertsSystem _alerts = default!; + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnSiliconStartup); + } + + public bool TryGetSiliconBattery(EntityUid silicon, [NotNullWhen(true)] out BatteryComponent? batteryComp) + { + batteryComp = null; + if (!HasComp(silicon)) + return false; + + // try get a battery directly on the inserted entity + if (TryComp(silicon, out batteryComp) + || _powerCell.TryGetBatteryFromSlot(silicon, out batteryComp)) + return true; + + //DebugTools.Assert("SiliconComponent does not contain Battery"); + return false; + } + + private void OnSiliconStartup(EntityUid uid, SiliconComponent component, ComponentStartup args) + { + if (!HasComp(uid)) + return; + + if (component.EntityType.GetType() != typeof(SiliconType)) + DebugTools.Assert("SiliconComponent.EntityType is not a SiliconType enum."); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + // For each siliconComp entity with a battery component, drain their charge. + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var silicon, out var siliconComp)) + { + if (_mobState.IsDead(silicon) + || !siliconComp.BatteryPowered) + continue; + + // Check if the Silicon is an NPC, and if so, follow the delay as specified in the CVAR. + if (siliconComp.EntityType.Equals(SiliconType.Npc)) + { + var updateTime = _config.GetCVar(CCVars.SiliconNpcUpdateTime); + if (_timing.CurTime - siliconComp.LastDrainTime < TimeSpan.FromSeconds(updateTime)) + continue; + + siliconComp.LastDrainTime = _timing.CurTime; + } + + // If you can't find a battery, set the indicator and skip it. + if (!TryGetSiliconBattery(silicon, out var batteryComp)) + { + UpdateChargeState(silicon, 0, siliconComp); + if (_alerts.IsShowingAlert(silicon, AlertType.BorgBattery)) + { + _alerts.ClearAlert(silicon, AlertType.BorgBattery); + _alerts.ShowAlert(silicon, AlertType.BorgBatteryNone); + } + continue; + } + + // If the silicon ghosted or is SSD while still being powered, skip it. + if (TryComp(silicon, out var mindContComp) + && !mindContComp.HasMind) + continue; + + var drainRate = siliconComp.DrainPerSecond; + + // All multipliers will be subtracted by 1, and then added together, and then multiplied by the drain rate. This is then added to the base drain rate. + // This is to stop exponential increases, while still allowing for less-than-one multipliers. + var drainRateFinalAddi = 0f; + + // TODO: Devise a method of adding multis where other systems can alter the drain rate. + // Maybe use something similar to refreshmovespeedmodifiers, where it's stored in the component. + // Maybe it doesn't matter, and stuff should just use static drain? + if (!siliconComp.EntityType.Equals(SiliconType.Npc)) // Don't bother checking heat if it's an NPC. It's a waste of time, and it'd be delayed due to the update time. + drainRateFinalAddi += SiliconHeatEffects(silicon, siliconComp, frameTime) - 1; // This will need to be changed at some point if we allow external batteries, since the heat of the Silicon might not be applicable. + + // Ensures that the drain rate is at least 10% of normal, + // and would allow at least 4 minutes of life with a max charge, to prevent cheese. + drainRate += Math.Clamp(drainRateFinalAddi, drainRate * -0.9f, batteryComp.MaxCharge / 240); + + // Drain the battery. + _powerCell.TryUseCharge(silicon, frameTime * drainRate); + + // Figure out the current state of the Silicon. + var chargePercent = (short) MathF.Round(batteryComp.CurrentCharge / batteryComp.MaxCharge * 10f); + + UpdateChargeState(silicon, chargePercent, siliconComp); + } + } + + /// + /// Checks if anything needs to be updated, and updates it. + /// + public void UpdateChargeState(EntityUid uid, short chargePercent, SiliconComponent component) + { + component.ChargeState = chargePercent; + + RaiseLocalEvent(uid, new SiliconChargeStateUpdateEvent(chargePercent)); + + _moveMod.RefreshMovementSpeedModifiers(uid); + + // If the battery was replaced and the no battery indicator is showing, replace the indicator + if (_alerts.IsShowingAlert(uid, AlertType.BorgBatteryNone) && chargePercent != 0) + { + _alerts.ClearAlert(uid, AlertType.BorgBatteryNone); + _alerts.ShowAlert(uid, AlertType.BorgBattery, chargePercent); + } + } + + private float SiliconHeatEffects(EntityUid silicon, SiliconComponent siliconComp, float frameTime) + { + if (!TryComp(silicon, out var temperComp) + || !TryComp(silicon, out var thermalComp)) + return 0; + + // If the Silicon is hot, drain the battery faster, if it's cold, drain it slower, capped. + var upperThresh = thermalComp.NormalBodyTemperature + thermalComp.ThermalRegulationTemperatureThreshold; + var upperThreshHalf = thermalComp.NormalBodyTemperature + thermalComp.ThermalRegulationTemperatureThreshold * 0.5f; + + // Check if the silicon is in a hot environment. + if (temperComp.CurrentTemperature > upperThreshHalf) + { + // Divide the current temp by the max comfortable temp capped to 4, then add that to the multiplier. + var hotTempMulti = Math.Min(temperComp.CurrentTemperature / upperThreshHalf, 4); + + // If the silicon is hot enough, it has a chance to catch fire. + + siliconComp.OverheatAccumulator += frameTime; + if (!(siliconComp.OverheatAccumulator >= 5)) + return hotTempMulti; + + siliconComp.OverheatAccumulator -= 5; + + if (!EntityManager.TryGetComponent(silicon, out var flamComp) + || flamComp is { OnFire: true } + || !(temperComp.CurrentTemperature > temperComp.HeatDamageThreshold)) + return hotTempMulti; + + _popup.PopupEntity(Loc.GetString("silicon-overheating"), silicon, silicon, PopupType.MediumCaution); + if (!_random.Prob(Math.Clamp(temperComp.CurrentTemperature / (upperThresh * 5), 0.001f, 0.9f))) + return hotTempMulti; + + _flammable.AdjustFireStacks(silicon, Math.Clamp(siliconComp.FireStackMultiplier, -10, 10), flamComp); + _flammable.Ignite(silicon, silicon, flamComp); + return hotTempMulti; + } + + // Check if the silicon is in a cold environment. + if (temperComp.CurrentTemperature < thermalComp.NormalBodyTemperature) + return 0.5f + temperComp.CurrentTemperature / thermalComp.NormalBodyTemperature * 0.5f; + + return 0; + } +} diff --git a/Content.Server/Silicon/DeadStartupButtonSystem/DeadStartupButtonSystem.cs b/Content.Server/Silicon/DeadStartupButtonSystem/DeadStartupButtonSystem.cs new file mode 100644 index 0000000000..6e516f3011 --- /dev/null +++ b/Content.Server/Silicon/DeadStartupButtonSystem/DeadStartupButtonSystem.cs @@ -0,0 +1,79 @@ +using Content.Server.Lightning; +using Content.Server.Popups; +using Content.Server.PowerCell; +using Content.Server.Silicon.Charge; +using Content.Shared.Silicon.DeadStartupButton; +using Content.Shared.Audio; +using Content.Shared.Damage; +using Content.Shared.Electrocution; +using Content.Shared.Mobs; +using Content.Shared.Mobs.Components; +using Content.Shared.Mobs.Systems; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Random; + +namespace Content.Server.Silicon.DeadStartupButtonSystem; + +public sealed class DeadStartupButtonSystem : SharedDeadStartupButtonSystem +{ + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly MobStateSystem _mobState = default!; + [Dependency] private readonly MobThresholdSystem _mobThreshold = default!; + [Dependency] private readonly PopupSystem _popup = default!; + [Dependency] private readonly IRobustRandom _robustRandom = default!; + [Dependency] private readonly LightningSystem _lightning = default!; + [Dependency] private readonly SiliconChargeSystem _siliconChargeSystem = default!; + [Dependency] private readonly PowerCellSystem _powerCell = default!; + + /// + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnDoAfter); + SubscribeLocalEvent(OnElectrocuted); + SubscribeLocalEvent(OnMobStateChanged); + + } + + private void OnDoAfter(EntityUid uid, DeadStartupButtonComponent comp, OnDoAfterButtonPressedEvent args) + { + if (args.Handled || args.Cancelled + || !TryComp(uid, out var mobStateComponent) + || !_mobState.IsDead(uid, mobStateComponent) + || !TryComp(uid, out var mobThresholdsComponent) + || !TryComp(uid, out var damageable) + || !_mobThreshold.TryGetThresholdForState(uid, MobState.Critical, out var criticalThreshold, mobThresholdsComponent)) + return; + + if (damageable.TotalDamage < criticalThreshold) + _mobState.ChangeMobState(uid, MobState.Alive, mobStateComponent); + else + { + _audio.PlayPvs(comp.BuzzSound, uid, AudioHelpers.WithVariation(0.05f, _robustRandom)); + _popup.PopupEntity(Loc.GetString("dead-startup-system-reboot-failed", ("target", MetaData(uid).EntityName)), uid); + Spawn("EffectSparks", Transform(uid).Coordinates); + } + } + + private void OnElectrocuted(EntityUid uid, DeadStartupButtonComponent comp, ElectrocutedEvent args) + { + if (!TryComp(uid, out var mobStateComponent) + || !_mobState.IsDead(uid, mobStateComponent) + || !_siliconChargeSystem.TryGetSiliconBattery(uid, out var bateria) + || bateria.CurrentCharge <= 0) + return; + + _lightning.ShootRandomLightnings(uid, 2, 4); + _powerCell.TryUseCharge(uid, bateria.CurrentCharge); + + } + + private void OnMobStateChanged(EntityUid uid, DeadStartupButtonComponent comp, MobStateChangedEvent args) + { + if (args.NewMobState != MobState.Alive) + return; + + _popup.PopupEntity(Loc.GetString("dead-startup-system-reboot-success", ("target", MetaData(uid).EntityName)), uid); + _audio.PlayPvs(comp.Sound, uid); + } +} diff --git a/Content.Server/Silicon/EmitBuzzWhileDamaged/EmitBuzzWhileDamagedSystem.cs b/Content.Server/Silicon/EmitBuzzWhileDamaged/EmitBuzzWhileDamagedSystem.cs new file mode 100644 index 0000000000..161a912d06 --- /dev/null +++ b/Content.Server/Silicon/EmitBuzzWhileDamaged/EmitBuzzWhileDamagedSystem.cs @@ -0,0 +1,55 @@ +using Content.Server.Popups; +using Content.Shared.Silicon.EmitBuzzWhileDamaged; +using Content.Shared.Audio; +using Content.Shared.Damage; +using Content.Shared.Mobs; +using Content.Shared.Mobs.Systems; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Random; +using Robust.Shared.Timing; +using Content.Shared.Mobs.Components; + +namespace Content.Server.Silicon.EmitBuzzOnCrit; + +/// +/// This handles the buzzing popup and sound of a silicon based race when it is pretty damaged. +/// +public sealed class EmitBuzzWhileDamagedSystem : EntitySystem +{ + [Dependency] private readonly MobStateSystem _mobState = default!; + [Dependency] private readonly MobThresholdSystem _mobThreshold = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly PopupSystem _popupSystem = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly IRobustRandom _robustRandom = default!; + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var query = EntityQueryEnumerator(); + + while (query.MoveNext(out var uid, out var emitBuzzOnCritComponent, out var mobStateComponent, out var thresholdsComponent, out var damageableComponent)) + { + if (_mobState.IsDead(uid, mobStateComponent) + || !_mobThreshold.TryGetThresholdForState(uid, MobState.Critical, out var threshold, thresholdsComponent) + || damageableComponent.TotalDamage < threshold / 2) + continue; + + // Check update time + emitBuzzOnCritComponent.AccumulatedFrametime += frameTime; + if (emitBuzzOnCritComponent.AccumulatedFrametime < emitBuzzOnCritComponent.CycleDelay) + continue; + emitBuzzOnCritComponent.AccumulatedFrametime -= emitBuzzOnCritComponent.CycleDelay; + + if (_gameTiming.CurTime <= emitBuzzOnCritComponent.LastBuzzPopupTime + emitBuzzOnCritComponent.BuzzPopupCooldown) + continue; + + // Start buzzing + emitBuzzOnCritComponent.LastBuzzPopupTime = _gameTiming.CurTime; + _popupSystem.PopupEntity(Loc.GetString("silicon-behavior-buzz"), uid); + Spawn("EffectSparks", Transform(uid).Coordinates); + _audio.PlayPvs(emitBuzzOnCritComponent.Sound, uid, AudioHelpers.WithVariation(0.05f, _robustRandom)); + } + } +} diff --git a/Content.Server/Silicon/EncryptionHolderRequiresLock/EncryptionHolderRequiresLockComponent.cs b/Content.Server/Silicon/EncryptionHolderRequiresLock/EncryptionHolderRequiresLockComponent.cs new file mode 100644 index 0000000000..d524a6a2a6 --- /dev/null +++ b/Content.Server/Silicon/EncryptionHolderRequiresLock/EncryptionHolderRequiresLockComponent.cs @@ -0,0 +1,6 @@ +namespace Content.Server.Silicon.EncryptionHolderRequiresLock; + +[RegisterComponent] +public sealed partial class EncryptionHolderRequiresLockComponent : Component +{ +} diff --git a/Content.Server/Silicon/EncryptionHolderRequiresLock/EncryptionHolderRequiresLockSystem.cs b/Content.Server/Silicon/EncryptionHolderRequiresLock/EncryptionHolderRequiresLockSystem.cs new file mode 100644 index 0000000000..6efb51096c --- /dev/null +++ b/Content.Server/Silicon/EncryptionHolderRequiresLock/EncryptionHolderRequiresLockSystem.cs @@ -0,0 +1,28 @@ +using Content.Shared.Lock; +using Content.Shared.Radio.Components; +using Content.Shared.Radio.EntitySystems; + +namespace Content.Server.Silicon.EncryptionHolderRequiresLock; + +public sealed class EncryptionHolderRequiresLockSystem : EntitySystem + +{ + [Dependency] private readonly EncryptionKeySystem _encryptionKeySystem = default!; + + /// + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(LockToggled); + } + + private void LockToggled(EntityUid uid, EncryptionHolderRequiresLockComponent component, LockToggledEvent args) + { + if (!TryComp(uid, out var lockComp) + || !TryComp(uid, out var keyHolder)) + return; + + keyHolder.KeysUnlocked = !lockComp.Locked; + _encryptionKeySystem.UpdateChannels(uid, keyHolder); + } +} diff --git a/Content.Server/Silicon/IPC/InternalEncryptionKeySpawner.cs b/Content.Server/Silicon/IPC/InternalEncryptionKeySpawner.cs new file mode 100644 index 0000000000..5f799a102e --- /dev/null +++ b/Content.Server/Silicon/IPC/InternalEncryptionKeySpawner.cs @@ -0,0 +1,34 @@ +using Content.Shared.Roles; +using Content.Shared.Radio.Components; +using Content.Shared.Containers; +using Robust.Shared.Containers; + +namespace Content.Server.Silicon.IPC; +public sealed partial class InternalEncryptionKeySpawner : EntitySystem +{ + [Dependency] private readonly SharedContainerSystem _container = default!; + public void TryInsertEncryptionKey(EntityUid target, StartingGearPrototype startingGear, IEntityManager entityManager) + { +#pragma warning disable CS8073 + if (target == null // target can be null during race conditions intentionally created by awful tests. +#pragma warning restore CS8073 + || !TryComp(target, out var keyHolderComp) + || keyHolderComp is null + || !startingGear.Equipment.TryGetValue("ears", out var earEquipString) + || string.IsNullOrEmpty(earEquipString)) + return; + + var earEntity = entityManager.SpawnEntity(earEquipString, entityManager.GetComponent(target).Coordinates); + if (!entityManager.HasComponent(earEntity) + || !entityManager.TryGetComponent(earEntity, out var fillComp) + || !fillComp.Containers.TryGetValue(EncryptionKeyHolderComponent.KeyContainerName, out var defaultKeys)) + return; + + _container.CleanContainer(keyHolderComp.KeyContainer); + + foreach (var key in defaultKeys) + entityManager.SpawnInContainerOrDrop(key, target, keyHolderComp.KeyContainer.ID, out _); + + entityManager.QueueDeleteEntity(earEntity); + } +} diff --git a/Content.Server/Silicon/WeldingHealable/WeldingHealableComponent.cs b/Content.Server/Silicon/WeldingHealable/WeldingHealableComponent.cs new file mode 100644 index 0000000000..9f4b6c3483 --- /dev/null +++ b/Content.Server/Silicon/WeldingHealable/WeldingHealableComponent.cs @@ -0,0 +1,5 @@ +namespace Content.Server.Silicon.WeldingHealable; + +[RegisterComponent] +public sealed partial class WeldingHealableComponent : Component { } + diff --git a/Content.Server/Silicon/WeldingHealable/WeldingHealableSystem.cs b/Content.Server/Silicon/WeldingHealable/WeldingHealableSystem.cs new file mode 100644 index 0000000000..df7ac7ac0f --- /dev/null +++ b/Content.Server/Silicon/WeldingHealable/WeldingHealableSystem.cs @@ -0,0 +1,105 @@ +using Content.Server.Silicon.WeldingHealing; +using Content.Shared.Chemistry.Components; +using Content.Shared.Silicon.WeldingHealing; +using Content.Shared.Chemistry.Components.SolutionManager; +using Content.Shared.Chemistry.EntitySystems; +using Content.Shared.Damage; +using Content.Shared.Interaction; +using Content.Shared.Popups; +using Content.Shared.Tools.Components; +using SharedToolSystem = Content.Shared.Tools.Systems.SharedToolSystem; + +namespace Content.Server.Silicon.WeldingHealable; + +public sealed class WeldingHealableSystem : SharedWeldingHealableSystem +{ + [Dependency] private readonly SharedToolSystem _toolSystem = default!; + [Dependency] private readonly DamageableSystem _damageableSystem = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!; + + public override void Initialize() + { + SubscribeLocalEvent(Repair); + SubscribeLocalEvent(OnRepairFinished); + } + + private void OnRepairFinished(EntityUid uid, WeldingHealableComponent healableComponent, SiliconRepairFinishedEvent args) + { + if (args.Cancelled || args.Used == null + || !TryComp(args.Target, out var damageable) + || !TryComp(args.Used, out var component) + || damageable.DamageContainerID is null + || !component.DamageContainers.Contains(damageable.DamageContainerID) + || !HasDamage(damageable, component) + || !TryComp(args.Used, out var welder) + || !TryComp(args.Used, out var solutionContainer)) + return; + + _damageableSystem.TryChangeDamage(uid, component.Damage, true, false, origin: args.User); + + Entity? sol = new(); + if (!_solutionContainer.ResolveSolution(((EntityUid) args.Used, solutionContainer), welder.FuelSolutionName, ref sol, out _)) + return; + _solutionContainer.RemoveReagent(sol.Value, welder.FuelReagent, component.FuelCost); + + var str = Loc.GetString("comp-repairable-repair", + ("target", uid), + ("tool", args.Used!)); + _popup.PopupEntity(str, uid, args.User); + + if (!args.Used.HasValue) + return; + + args.Handled = _toolSystem.UseTool + (args.Used.Value, + args.User, + uid, + args.Delay, + component.QualityNeeded, + new SiliconRepairFinishedEvent + { + Delay = args.Delay + }); + } + + private async void Repair(EntityUid uid, WeldingHealableComponent healableComponent, InteractUsingEvent args) + { + if (args.Handled + || !EntityManager.TryGetComponent(args.Used, out WeldingHealingComponent? component) + || !EntityManager.TryGetComponent(args.Target, out DamageableComponent? damageable) + || damageable.DamageContainerID is null + || !component.DamageContainers.Contains(damageable.DamageContainerID) + || !HasDamage(damageable, component) + || !_toolSystem.HasQuality(args.Used, component.QualityNeeded) + || args.User == args.Target && !component.AllowSelfHeal) + return; + + float delay = args.User == args.Target + ? component.DoAfterDelay * component.SelfHealPenalty + : component.DoAfterDelay; + + args.Handled = _toolSystem.UseTool + (args.Used, + args.User, + args.Target, + delay, + component.QualityNeeded, + new SiliconRepairFinishedEvent + { + Delay = delay, + }); + } + + private bool HasDamage(DamageableComponent component, WeldingHealingComponent healable) + { + if (healable.Damage.DamageDict is null) + return false; + + foreach (var type in healable.Damage.DamageDict) + if (component.Damage.DamageDict[type.Key].Value > 0) + return true; + return false; + } +} + diff --git a/Content.Server/Silicon/WeldingHealing/WeldingHealingComponent.cs b/Content.Server/Silicon/WeldingHealing/WeldingHealingComponent.cs new file mode 100644 index 0000000000..a7aa170793 --- /dev/null +++ b/Content.Server/Silicon/WeldingHealing/WeldingHealingComponent.cs @@ -0,0 +1,47 @@ +using Content.Shared.Damage; +using Content.Shared.Tools; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Server.Silicon.WeldingHealing; + +[RegisterComponent] +public sealed partial class WeldingHealingComponent : Component +{ + /// + /// All the damage to change information is stored in this . + /// + /// + /// If this data-field is specified, it will change damage by this amount instead of setting all damage to 0. + /// in order to heal/repair the damage values have to be negative. + /// + + [DataField(required: true)] + public DamageSpecifier Damage; + + [DataField(customTypeSerializer:typeof(PrototypeIdSerializer))] + public string QualityNeeded = "Welding"; + + /// + /// The fuel amount needed to repair physical related damage + /// + [DataField] + public int FuelCost = 5; + + [DataField] + public int DoAfterDelay = 3; + + /// + /// A multiplier that will be applied to the above if an entity is repairing themselves. + /// + [DataField] + public float SelfHealPenalty = 3f; + + /// + /// Whether or not an entity is allowed to repair itself. + /// + [DataField] + public bool AllowSelfHeal = true; + + [DataField(required: true)] + public List DamageContainers; +} diff --git a/Content.Server/Silicons/Borgs/BorgSystem.Transponder.cs b/Content.Server/Silicons/Borgs/BorgSystem.Transponder.cs new file mode 100644 index 0000000000..1c10cbe667 --- /dev/null +++ b/Content.Server/Silicons/Borgs/BorgSystem.Transponder.cs @@ -0,0 +1,107 @@ +using Content.Shared.DeviceNetwork; +using Content.Shared.Emag.Components; +using Content.Shared.Popups; +using Content.Shared.Robotics; +using Content.Shared.Silicons.Borgs.Components; +using Content.Server.DeviceNetwork; +using Content.Server.DeviceNetwork.Components; +using Content.Server.DeviceNetwork.Systems; +using Content.Server.Explosion.Components; + +namespace Content.Server.Silicons.Borgs; + +/// +public sealed partial class BorgSystem +{ + private void InitializeTransponder() + { + SubscribeLocalEvent(OnPacketReceived); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var now = _timing.CurTime; + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var comp, out var chassis, out var device, out var meta)) + { + if (now < comp.NextBroadcast) + continue; + + var charge = 0f; + if (_powerCell.TryGetBatteryFromSlot(uid, out var battery)) + charge = battery.CurrentCharge / battery.MaxCharge; + + var data = new CyborgControlData( + comp.Sprite, + comp.Name, + meta.EntityName, + charge, + chassis.ModuleCount, + chassis.BrainEntity != null); + + var payload = new NetworkPayload() + { + [DeviceNetworkConstants.Command] = DeviceNetworkConstants.CmdUpdatedState, + [RoboticsConsoleConstants.NET_CYBORG_DATA] = data + }; + _deviceNetwork.QueuePacket(uid, null, payload, device: device); + + comp.NextBroadcast = now + comp.BroadcastDelay; + } + } + + private void OnPacketReceived(Entity ent, ref DeviceNetworkPacketEvent args) + { + var payload = args.Data; + if (!payload.TryGetValue(DeviceNetworkConstants.Command, out string? command)) + return; + + if (command == RoboticsConsoleConstants.NET_DISABLE_COMMAND) + Disable(ent); + else if (command == RoboticsConsoleConstants.NET_DESTROY_COMMAND) + Destroy(ent.Owner); + } + + private void Disable(Entity ent) + { + if (!Resolve(ent, ref ent.Comp2) || ent.Comp2.BrainEntity is not {} brain) + return; + + // this won't exactly be stealthy but if you are malf its better than actually disabling you + if (CheckEmagged(ent, "disabled")) + return; + + var message = Loc.GetString(ent.Comp1.DisabledPopup, ("name", Name(ent))); + Popup.PopupEntity(message, ent); + _container.Remove(brain, ent.Comp2.BrainContainer); + } + + private void Destroy(Entity ent) + { + if (!Resolve(ent, ref ent.Comp)) + return; + + // this is stealthy until someone realises you havent exploded + if (CheckEmagged(ent, "destroyed")) + { + // prevent reappearing on the console a few seconds later + RemComp(ent); + return; + } + + _explosion.TriggerExplosive(ent, ent.Comp, delete: false); + } + + private bool CheckEmagged(EntityUid uid, string name) + { + if (HasComp(uid)) + { + Popup.PopupEntity(Loc.GetString($"borg-transponder-emagged-{name}-popup"), uid, uid, PopupType.LargeCaution); + return true; + } + + return false; + } +} diff --git a/Content.Server/Silicons/Borgs/BorgSystem.Ui.cs b/Content.Server/Silicons/Borgs/BorgSystem.Ui.cs index 3dcdd78aff..d0e9f80e36 100644 --- a/Content.Server/Silicons/Borgs/BorgSystem.Ui.cs +++ b/Content.Server/Silicons/Borgs/BorgSystem.Ui.cs @@ -28,20 +28,19 @@ private void OnBeforeBorgUiOpen(EntityUid uid, BorgChassisComponent component, B private void OnEjectBrainBuiMessage(EntityUid uid, BorgChassisComponent component, BorgEjectBrainBuiMessage args) { - if (args.Session.AttachedEntity is not { } attachedEntity || component.BrainEntity is not { } brain) + if (component.BrainEntity is not { } brain) return; _adminLog.Add(LogType.Action, LogImpact.Medium, - $"{ToPrettyString(attachedEntity):player} removed brain {ToPrettyString(brain)} from borg {ToPrettyString(uid)}"); + $"{ToPrettyString(args.Actor):player} removed brain {ToPrettyString(brain)} from borg {ToPrettyString(uid)}"); _container.Remove(brain, component.BrainContainer); - _hands.TryPickupAnyHand(attachedEntity, brain); + _hands.TryPickupAnyHand(args.Actor, brain); UpdateUI(uid, component); } private void OnEjectBatteryBuiMessage(EntityUid uid, BorgChassisComponent component, BorgEjectBatteryBuiMessage args) { - if (args.Session.AttachedEntity is not { } attachedEntity || - !TryComp(uid, out var slotComp) || + if (!TryComp(uid, out var slotComp) || !Container.TryGetContainer(uid, slotComp.CellSlotId, out var container) || !container.ContainedEntities.Any()) { @@ -49,14 +48,11 @@ private void OnEjectBatteryBuiMessage(EntityUid uid, BorgChassisComponent compon } var ents = Container.EmptyContainer(container); - _hands.TryPickupAnyHand(attachedEntity, ents.First()); + _hands.TryPickupAnyHand(args.Actor, ents.First()); } private void OnSetNameBuiMessage(EntityUid uid, BorgChassisComponent component, BorgSetNameBuiMessage args) { - if (args.Session.AttachedEntity is not { } attachedEntity) - return; - if (args.Name.Length > HumanoidCharacterProfile.MaxNameLength || args.Name.Length == 0 || string.IsNullOrWhiteSpace(args.Name) || @@ -75,24 +71,21 @@ private void OnSetNameBuiMessage(EntityUid uid, BorgChassisComponent component, if (metaData.EntityName.Equals(name, StringComparison.InvariantCulture)) return; - _adminLog.Add(LogType.Action, LogImpact.High, $"{ToPrettyString(attachedEntity):player} set borg \"{ToPrettyString(uid)}\"'s name to: {name}"); + _adminLog.Add(LogType.Action, LogImpact.High, $"{ToPrettyString(args.Actor):player} set borg \"{ToPrettyString(uid)}\"'s name to: {name}"); _metaData.SetEntityName(uid, name, metaData); } private void OnRemoveModuleBuiMessage(EntityUid uid, BorgChassisComponent component, BorgRemoveModuleBuiMessage args) { - if (args.Session.AttachedEntity is not { } attachedEntity) - return; - var module = GetEntity(args.Module); if (!component.ModuleContainer.Contains(module)) return; _adminLog.Add(LogType.Action, LogImpact.Medium, - $"{ToPrettyString(attachedEntity):player} removed module {ToPrettyString(module)} from borg {ToPrettyString(uid)}"); + $"{ToPrettyString(args.Actor):player} removed module {ToPrettyString(module)} from borg {ToPrettyString(uid)}"); _container.Remove(module, component.ModuleContainer); - _hands.TryPickupAnyHand(attachedEntity, module); + _hands.TryPickupAnyHand(args.Actor, module); UpdateUI(uid, component); } @@ -111,6 +104,6 @@ public void UpdateUI(EntityUid uid, BorgChassisComponent? component = null) } var state = new BorgBuiState(chargePercent, hasBattery); - _ui.TrySetUiState(uid, BorgUiKey.Key, state); + _ui.SetUiState(uid, BorgUiKey.Key, state); } } diff --git a/Content.Server/Silicons/Borgs/BorgSystem.cs b/Content.Server/Silicons/Borgs/BorgSystem.cs index 869c279704..75f25a3a92 100644 --- a/Content.Server/Silicons/Borgs/BorgSystem.cs +++ b/Content.Server/Silicons/Borgs/BorgSystem.cs @@ -1,6 +1,8 @@ using Content.Server.Actions; using Content.Server.Administration.Logs; using Content.Server.Administration.Managers; +using Content.Server.DeviceNetwork.Systems; +using Content.Server.Explosion.EntitySystems; using Content.Server.Hands.Systems; using Content.Server.PowerCell; using Content.Shared.UserInterface; @@ -26,6 +28,7 @@ using Robust.Shared.Containers; using Robust.Shared.Player; using Robust.Shared.Random; +using Robust.Shared.Timing; namespace Content.Server.Silicons.Borgs; @@ -34,10 +37,13 @@ public sealed partial class BorgSystem : SharedBorgSystem { [Dependency] private readonly IAdminLogManager _adminLog = default!; [Dependency] private readonly IBanManager _banManager = default!; + [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly SharedAccessSystem _access = default!; [Dependency] private readonly ActionsSystem _actions = default!; [Dependency] private readonly AlertsSystem _alerts = default!; + [Dependency] private readonly DeviceNetworkSystem _deviceNetwork = default!; + [Dependency] private readonly ExplosionSystem _explosion = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly HandsSystem _hands = default!; [Dependency] private readonly MetaDataSystem _metaData = default!; @@ -73,6 +79,7 @@ public override void Initialize() InitializeModules(); InitializeMMI(); InitializeUI(); + InitializeTransponder(); } private void OnMapInit(EntityUid uid, BorgChassisComponent component, MapInitEvent args) diff --git a/Content.Server/Silicons/Laws/SiliconLawSystem.cs b/Content.Server/Silicons/Laws/SiliconLawSystem.cs index 010682bc0d..bc7a7b35c2 100644 --- a/Content.Server/Silicons/Laws/SiliconLawSystem.cs +++ b/Content.Server/Silicons/Laws/SiliconLawSystem.cs @@ -55,7 +55,6 @@ public override void Initialize() SubscribeLocalEvent(OnEmagLawsAdded); SubscribeLocalEvent(OnEmagMindAdded); SubscribeLocalEvent(OnEmagMindRemoved); - SubscribeLocalEvent(OnExamined); } private void OnComponentShutdown(EntityUid uid, SiliconLawBoundComponent component, ComponentShutdown args) @@ -93,10 +92,10 @@ private void OnToggleLawsScreen(EntityUid uid, SiliconLawBoundComponent componen private void OnBoundUIOpened(EntityUid uid, SiliconLawBoundComponent component, BoundUIOpenedEvent args) { _entityManager.TryGetComponent(uid, out var intrinsicRadio); - HashSet? radioChannels = intrinsicRadio?.Channels; + var radioChannels = intrinsicRadio?.Channels; var state = new SiliconLawBuiState(GetLaws(uid).Laws, radioChannels); - _userInterface.TrySetUiState(args.Entity, SiliconLawsUiKey.Key, state, args.Session); + _userInterface.SetUiState(args.Entity, SiliconLawsUiKey.Key, state); } private void OnPlayerSpawnComplete(EntityUid uid, SiliconLawBoundComponent component, PlayerSpawnCompleteEvent args) @@ -155,17 +154,6 @@ private void OnEmagLawsAdded(EntityUid uid, SiliconLawProviderComponent componen }); } - private void OnExamined(EntityUid uid, EmagSiliconLawComponent component, ExaminedEvent args) - { - if (!args.IsInDetailsRange || !HasComp(uid)) - return; - - if (component.RequireOpenPanel && TryComp(uid, out var panel) && !panel.Open) - return; - - args.PushMarkup(Loc.GetString("laws-compromised-examine")); - } - protected override void OnGotEmagged(EntityUid uid, EmagSiliconLawComponent component, ref GotEmaggedEvent args) { if (component.RequireOpenPanel && TryComp(uid, out var panel) && !panel.Open) diff --git a/Content.Server/Singularity/EntitySystems/EmitterSystem.cs b/Content.Server/Singularity/EntitySystems/EmitterSystem.cs index a9763b64d9..652ca236e4 100644 --- a/Content.Server/Singularity/EntitySystems/EmitterSystem.cs +++ b/Content.Server/Singularity/EntitySystems/EmitterSystem.cs @@ -1,6 +1,7 @@ using System.Numerics; using System.Threading; using Content.Server.Administration.Logs; +using Content.Server.Construction; using Content.Server.DeviceLinking.Events; using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; @@ -47,6 +48,8 @@ public override void Initialize() SubscribeLocalEvent(OnInteractHand); SubscribeLocalEvent>(OnGetVerb); SubscribeLocalEvent(OnExamined); + SubscribeLocalEvent(OnRefreshParts); + SubscribeLocalEvent(OnUpgradeExamine); SubscribeLocalEvent(OnAnchorStateChanged); SubscribeLocalEvent(OnSignalReceived); } @@ -176,6 +179,20 @@ private void OnApcChanged(EntityUid uid, EmitterComponent component, ref PowerCh } } + private void OnRefreshParts(EntityUid uid, EmitterComponent component, RefreshPartsEvent args) + { + var fireRateRating = args.PartRatings[component.MachinePartFireRate]; + + component.FireInterval = component.BaseFireInterval * MathF.Pow(component.FireRateMultiplier, fireRateRating - 1); + component.FireBurstDelayMin = component.BaseFireBurstDelayMin * MathF.Pow(component.FireRateMultiplier, fireRateRating - 1); + component.FireBurstDelayMax = component.BaseFireBurstDelayMax * MathF.Pow(component.FireRateMultiplier, fireRateRating - 1); + } + + private void OnUpgradeExamine(EntityUid uid, EmitterComponent component, UpgradeExamineEvent args) + { + args.AddPercentageUpgrade("emitter-component-upgrade-fire-rate", (float) (component.BaseFireInterval.TotalSeconds / component.FireInterval.TotalSeconds)); + } + public void SwitchOff(EntityUid uid, EmitterComponent component) { component.IsOn = false; diff --git a/Content.Server/Singularity/EntitySystems/GravityWellSystem.cs b/Content.Server/Singularity/EntitySystems/GravityWellSystem.cs index f1d0af6f90..779b2f5971 100644 --- a/Content.Server/Singularity/EntitySystems/GravityWellSystem.cs +++ b/Content.Server/Singularity/EntitySystems/GravityWellSystem.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Content.Server.Atmos.Components; using Content.Server.Singularity.Components; using Content.Shared.Ghost; @@ -126,7 +127,7 @@ private bool CanGravPulseAffect(EntityUid entity) /// The minimum distance at which entities can be affected by the gravity pulse. /// The base velocity added to any entities within affected by the gravity pulse scaled by the displacement of those entities from the epicenter. /// (optional) The transform of the entity at the epicenter of the gravitational pulse. - public void GravPulse(EntityUid uid, float maxRange, float minRange, in Matrix3 baseMatrixDeltaV, TransformComponent? xform = null) + public void GravPulse(EntityUid uid, float maxRange, float minRange, in Matrix3x2 baseMatrixDeltaV, TransformComponent? xform = null) { if (Resolve(uid, ref xform)) GravPulse(xform.Coordinates, maxRange, minRange, in baseMatrixDeltaV); @@ -154,7 +155,7 @@ public void GravPulse(EntityUid uid, float maxRange, float minRange, float baseR /// The maximum distance at which entities can be affected by the gravity pulse. /// The minimum distance at which entities can be affected by the gravity pulse. /// The base velocity added to any entities within affected by the gravity pulse scaled by the displacement of those entities from the epicenter. - public void GravPulse(EntityCoordinates entityPos, float maxRange, float minRange, in Matrix3 baseMatrixDeltaV) + public void GravPulse(EntityCoordinates entityPos, float maxRange, float minRange, in Matrix3x2 baseMatrixDeltaV) => GravPulse(entityPos.ToMap(EntityManager, _transform), maxRange, minRange, in baseMatrixDeltaV); /// @@ -175,7 +176,7 @@ public void GravPulse(EntityCoordinates entityPos, float maxRange, float minRang /// The maximum distance at which entities can be affected by the gravity pulse. /// The minimum distance at which entities can be affected by the gravity pulse. Exists to prevent div/0 errors. /// The base velocity added to any entities within affected by the gravity pulse scaled by the displacement of those entities from the epicenter. - public void GravPulse(MapCoordinates mapPos, float maxRange, float minRange, in Matrix3 baseMatrixDeltaV) + public void GravPulse(MapCoordinates mapPos, float maxRange, float minRange, in Matrix3x2 baseMatrixDeltaV) { if (mapPos == MapCoordinates.Nullspace) return; // No gravpulses in nullspace please. @@ -205,7 +206,7 @@ public void GravPulse(MapCoordinates mapPos, float maxRange, float minRange, in continue; var scaling = (1f / distance2) * physics.Mass; // TODO: Variable falloff gradiants. - _physics.ApplyLinearImpulse(entity, (displacement * baseMatrixDeltaV) * scaling, body: physics); + _physics.ApplyLinearImpulse(entity, Vector2.Transform(displacement, baseMatrixDeltaV) * scaling, body: physics); } } @@ -218,10 +219,9 @@ public void GravPulse(MapCoordinates mapPos, float maxRange, float minRange, in /// The base amount of velocity that will be added to entities in range towards the epicenter of the pulse. /// The base amount of velocity that will be added to entities in range counterclockwise relative to the epicenter of the pulse. public void GravPulse(MapCoordinates mapPos, float maxRange, float minRange = 0.0f, float baseRadialDeltaV = 0.0f, float baseTangentialDeltaV = 0.0f) - => GravPulse(mapPos, maxRange, minRange, new Matrix3( - baseRadialDeltaV, +baseTangentialDeltaV, 0.0f, - -baseTangentialDeltaV, baseRadialDeltaV, 0.0f, - 0.0f, 0.0f, 1.0f + => GravPulse(mapPos, maxRange, minRange, new Matrix3x2( + baseRadialDeltaV, -baseTangentialDeltaV, 0.0f, + +baseTangentialDeltaV, baseRadialDeltaV, 0.0f )); #endregion GravPulse diff --git a/Content.Server/Singularity/EntitySystems/RadiationCollectorSystem.cs b/Content.Server/Singularity/EntitySystems/RadiationCollectorSystem.cs index b26ab301c6..9107ff2e32 100644 --- a/Content.Server/Singularity/EntitySystems/RadiationCollectorSystem.cs +++ b/Content.Server/Singularity/EntitySystems/RadiationCollectorSystem.cs @@ -151,7 +151,8 @@ private void OnAnalyzed(EntityUid uid, RadiationCollectorComponent component, Ga if (!TryGetLoadedGasTank(uid, out var gasTankComponent)) return; - args.GasMixtures = new Dictionary { { Name(uid), gasTankComponent.Air } }; + args.GasMixtures ??= new List<(string, GasMixture?)>(); + args.GasMixtures.Add((Name(uid), gasTankComponent.Air)); } public void ToggleCollector(EntityUid uid, EntityUid? user = null, RadiationCollectorComponent? component = null) diff --git a/Content.Server/Solar/EntitySystems/PowerSolarControlConsoleSystem.cs b/Content.Server/Solar/EntitySystems/PowerSolarControlConsoleSystem.cs index 179cadcfbc..dd3f0c0054 100644 --- a/Content.Server/Solar/EntitySystems/PowerSolarControlConsoleSystem.cs +++ b/Content.Server/Solar/EntitySystems/PowerSolarControlConsoleSystem.cs @@ -35,13 +35,13 @@ public override void Update(float frameTime) _updateTimer -= 1; var state = new SolarControlConsoleBoundInterfaceState(_powerSolarSystem.TargetPanelRotation, _powerSolarSystem.TargetPanelVelocity, _powerSolarSystem.TotalPanelPower, _powerSolarSystem.TowardsSun); var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out var _, out var uiComp)) + while (query.MoveNext(out var uid, out _, out var uiComp)) { - _uiSystem.TrySetUiState(uid, SolarControlConsoleUiKey.Key, state, ui: uiComp); + _uiSystem.SetUiState((uid, uiComp), SolarControlConsoleUiKey.Key, state); } } } - + private void OnUIMessage(EntityUid uid, SolarControlConsoleComponent component, SolarControlConsoleAdjustMessage msg) { if (double.IsFinite(msg.Rotation)) diff --git a/Content.Server/Spawners/Components/ConditionalSpawnerComponent.cs b/Content.Server/Spawners/Components/ConditionalSpawnerComponent.cs index 5b98989bb3..1b06367b0d 100644 --- a/Content.Server/Spawners/Components/ConditionalSpawnerComponent.cs +++ b/Content.Server/Spawners/Components/ConditionalSpawnerComponent.cs @@ -2,7 +2,7 @@ namespace Content.Server.Spawners.Components { - [RegisterComponent] + [RegisterComponent, EntityCategory("Spawner")] [Virtual] public partial class ConditionalSpawnerComponent : Component { diff --git a/Content.Server/Spawners/Components/RandomSpawnerComponent.cs b/Content.Server/Spawners/Components/RandomSpawnerComponent.cs index 9bf4d6d253..e6a597f365 100644 --- a/Content.Server/Spawners/Components/RandomSpawnerComponent.cs +++ b/Content.Server/Spawners/Components/RandomSpawnerComponent.cs @@ -2,7 +2,7 @@ namespace Content.Server.Spawners.Components { - [RegisterComponent] + [RegisterComponent, EntityCategory("Spawner")] public sealed partial class RandomSpawnerComponent : ConditionalSpawnerComponent { [ViewVariables(VVAccess.ReadWrite)] diff --git a/Content.Server/Spawners/Components/TimedSpawnerComponent.cs b/Content.Server/Spawners/Components/TimedSpawnerComponent.cs index b60afbc88b..828e541717 100644 --- a/Content.Server/Spawners/Components/TimedSpawnerComponent.cs +++ b/Content.Server/Spawners/Components/TimedSpawnerComponent.cs @@ -9,7 +9,7 @@ namespace Content.Server.Spawners.Components; /// Can configure the set of entities, spawn timing, spawn chance, /// and min/max number of entities to spawn. /// -[RegisterComponent] +[RegisterComponent, EntityCategory("Spawner")] public sealed partial class TimedSpawnerComponent : Component, ISerializationHooks { /// diff --git a/Content.Server/Spawners/EntitySystems/ConditionalSpawnerSystem.cs b/Content.Server/Spawners/EntitySystems/ConditionalSpawnerSystem.cs index 506fd61d55..75f8618798 100644 --- a/Content.Server/Spawners/EntitySystems/ConditionalSpawnerSystem.cs +++ b/Content.Server/Spawners/EntitySystems/ConditionalSpawnerSystem.cs @@ -1,5 +1,6 @@ using System.Numerics; using Content.Server.GameTicking; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.Spawners.Components; using JetBrains.Annotations; diff --git a/Content.Server/Speech/Components/ListenWireAction.cs b/Content.Server/Speech/Components/ListenWireAction.cs index 68d2201862..b8b1c19e84 100644 --- a/Content.Server/Speech/Components/ListenWireAction.cs +++ b/Content.Server/Speech/Components/ListenWireAction.cs @@ -1,9 +1,12 @@ -using Content.Server.Speech.Components; using Content.Server.Chat.Systems; -using Content.Server.VoiceMask; +using Content.Shared.Radio; +using Content.Server.Radio.Components; +using Content.Server.Radio.EntitySystems; +using Content.Server.Speech.Components; using Content.Server.Wires; -using Content.Shared.Speech; using Content.Shared.Wires; +using Content.Shared.Speech; +using Robust.Shared.Prototypes; namespace Content.Server.Speech; @@ -11,17 +14,13 @@ public sealed partial class ListenWireAction : BaseToggleWireAction { private WiresSystem _wires = default!; private ChatSystem _chat = default!; + private RadioSystem _radio = default!; + private IPrototypeManager _protoMan = default!; /// /// Length of the gibberish string sent when pulsing the wire /// private const int NoiseLength = 16; - - /// - /// Identifier of the SpeechVerbPrototype to use when pulsing the wire - /// - [ValidatePrototypeId] - private const string SpeechVerb = "Electricity"; public override Color Color { get; set; } = Color.Green; public override string Name { get; set; } = "wire-name-listen"; @@ -37,6 +36,8 @@ public override void Initialize() _wires = EntityManager.System(); _chat = EntityManager.System(); + _radio = EntityManager.System(); + _protoMan = IoCManager.Resolve(); } public override StatusLightState? GetLightState(Wire wire) { @@ -72,46 +73,20 @@ public override void Pulse(EntityUid user, Wire wire) if (!GetValue(wire.Owner) || !IsPowered(wire.Owner)) return; - // We have to use a valid euid in the ListenEvent. The user seems - // like a sensible choice, but we need to mask their name. - - // Save the user's existing voicemask if they have one - var oldEnabled = true; - var oldVoiceName = Loc.GetString("wire-listen-pulse-error-name"); - string? oldSpeechVerb = null; - if (EntityManager.TryGetComponent(user, out var oldMask)) - { - oldEnabled = oldMask.Enabled; - oldVoiceName = oldMask.VoiceName; - oldSpeechVerb = oldMask.SpeechVerb; - } - - // Give the user a temporary voicemask component - var mask = EntityManager.EnsureComponent(user); - mask.Enabled = true; - mask.VoiceName = Loc.GetString("wire-listen-pulse-identifier"); - mask.SpeechVerb = SpeechVerb; - var chars = Loc.GetString("wire-listen-pulse-characters").ToCharArray(); var noiseMsg = _chat.BuildGibberishString(chars, NoiseLength); - var attemptEv = new ListenAttemptEvent(wire.Owner); - EntityManager.EventBus.RaiseLocalEvent(wire.Owner, attemptEv); - if (!attemptEv.Cancelled) - { - var ev = new ListenEvent(noiseMsg, user); - EntityManager.EventBus.RaiseLocalEvent(wire.Owner, ev); - } + if (!EntityManager.TryGetComponent(wire.Owner, out var radioMicroPhoneComp)) + return; - // Remove the voicemask component, or set it back to what it was before - if (oldMask == null) - EntityManager.RemoveComponent(user, mask); - else - { - mask.Enabled = oldEnabled; - mask.VoiceName = oldVoiceName; - mask.SpeechVerb = oldSpeechVerb; - } + if (!EntityManager.TryGetComponent(wire.Owner, out var voiceOverrideComp)) + return; + + // The reason for the override is to make the voice sound like its coming from electrity rather than the intercom. + voiceOverrideComp.NameOverride = Loc.GetString("wire-listen-pulse-identifier"); + voiceOverrideComp.Enabled = true; + _radio.SendRadioMessage(wire.Owner, noiseMsg, _protoMan.Index(radioMicroPhoneComp.BroadcastChannel), wire.Owner); + voiceOverrideComp.Enabled = false; base.Pulse(user, wire); } diff --git a/Content.Server/Speech/Components/PirateAccentComponent.cs b/Content.Server/Speech/Components/PirateAccentComponent.cs index 0559d9854b..b5b292775d 100644 --- a/Content.Server/Speech/Components/PirateAccentComponent.cs +++ b/Content.Server/Speech/Components/PirateAccentComponent.cs @@ -15,6 +15,7 @@ public sealed partial class PirateAccentComponent : Component { "accent-pirate-prefix-1", "accent-pirate-prefix-2", - "accent-pirate-prefix-3" + "accent-pirate-prefix-3", + "accent-pirate-prefix-4", }; } diff --git a/Content.Server/Speech/Components/RandomBarkComponent.cs b/Content.Server/Speech/Components/RandomBarkComponent.cs index 7229428f53..470e49e7fa 100644 --- a/Content.Server/Speech/Components/RandomBarkComponent.cs +++ b/Content.Server/Speech/Components/RandomBarkComponent.cs @@ -37,27 +37,15 @@ public sealed partial class RandomBarkComponent : Component public float BarkMultiplier = 1f; /// - /// List of things to be said. Filled with garbage to be modified by an accent, but can be specified in the .yml + /// Bark type, for use in locales. Locale keys follow the format "bark-{type}-{index between 1 and BarkLocaleCount}". /// [DataField] - public IReadOnlyList Barks = new[] - { - "Bark", - "Boof", - "Woofums", - "Rawrl", - "Eeeeeee", - "Barkums", - "Awooooooooooooooooooo awoo awoooo", - "Grrrrrrrrrrrrrrrrrr", - "Rarrwrarrwr", - "Goddamn I love gold fish crackers", - "Bork bork boof boof bork bork boof boof boof bork", - "Bark", - "Boof", - "Woofums", - "Rawrl", - "Eeeeeee", - "Barkums", - }; + public string BarkType = "default"; + + /// + /// Number of bark locales. If not specified, then it will be figured out by fetching the locale string + /// with the key "bark-{type}-count" and parsing it as an integer. + /// + [DataField] + public int? BarkLocaleCount = null; } diff --git a/Content.Server/Speech/Components/ReplacementAccentComponent.cs b/Content.Server/Speech/Components/ReplacementAccentComponent.cs index ac4e9fbafe..e7f57b80d0 100644 --- a/Content.Server/Speech/Components/ReplacementAccentComponent.cs +++ b/Content.Server/Speech/Components/ReplacementAccentComponent.cs @@ -32,5 +32,11 @@ public sealed partial class ReplacementAccentComponent : Component { [DataField("accent", customTypeSerializer: typeof(PrototypeIdSerializer), required: true)] public string Accent = default!; + + /// + /// Allows you to substitute words, not always, but with some chance + /// + [DataField] + public float ReplacementChance = 1f; } } diff --git a/Content.Server/Speech/Components/VoiceOverrideComponent.cs b/Content.Server/Speech/Components/VoiceOverrideComponent.cs new file mode 100644 index 0000000000..70deba5ba5 --- /dev/null +++ b/Content.Server/Speech/Components/VoiceOverrideComponent.cs @@ -0,0 +1,35 @@ +using Content.Shared.Speech; +using Robust.Shared.Prototypes; + +namespace Content.Server.Speech.Components; + +/// +/// Will change the voice of the entity that has the component (e.g radio and speech). +/// +/// +/// Before using this component, please take a look at the the TransformSpeakerNameEvent (and the inventory relay version). +/// Depending on what you're doing, it could be a better choice! +/// +[RegisterComponent] +public sealed partial class VoiceOverrideComponent : Component +{ + /// + /// The name that will be used instead of an entities default one. + /// Uses the localized version of the string and if null wont do anything. + /// + [DataField] + public string? NameOverride = null; + + /// + /// The verb that will be used insteand of an entities default one. + /// If null, the defaut will be used. + /// + [DataField] + public ProtoId? SpeechVerbOverride = null; + + /// + /// If true, the override values (if they are not null) will be applied. + /// + [DataField] + public bool Enabled = true; +} diff --git a/Content.Server/Speech/EmotesMenuSystem.cs b/Content.Server/Speech/EmotesMenuSystem.cs new file mode 100644 index 0000000000..a69b5a65e4 --- /dev/null +++ b/Content.Server/Speech/EmotesMenuSystem.cs @@ -0,0 +1,30 @@ +using Content.Server.Chat.Systems; +using Content.Shared.Chat; +using Robust.Shared.Prototypes; + +namespace Content.Server.Speech; + +public sealed partial class EmotesMenuSystem : EntitySystem +{ + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly ChatSystem _chat = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeAllEvent(OnPlayEmote); + } + + private void OnPlayEmote(PlayEmoteMessage msg, EntitySessionEventArgs args) + { + var player = args.SenderSession.AttachedEntity; + if (!player.HasValue) + return; + + if (!_prototypeManager.TryIndex(msg.ProtoId, out var proto) || proto.ChatTriggers.Count == 0) + return; + + _chat.TryEmoteWithChat(player.Value, msg.ProtoId); + } +} diff --git a/Content.Server/Speech/EntitySystems/AddAccentClothingSystem.cs b/Content.Server/Speech/EntitySystems/AddAccentClothingSystem.cs index 1f707c2249..897cd061f4 100644 --- a/Content.Server/Speech/EntitySystems/AddAccentClothingSystem.cs +++ b/Content.Server/Speech/EntitySystems/AddAccentClothingSystem.cs @@ -1,6 +1,5 @@ using Content.Server.Speech.Components; -using Content.Shared.Clothing.Components; -using Content.Shared.Inventory.Events; +using Content.Shared.Clothing; namespace Content.Server.Speech.EntitySystems; @@ -11,29 +10,20 @@ public sealed class AddAccentClothingSystem : EntitySystem public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnGotEquipped); - SubscribeLocalEvent(OnGotUnequipped); + SubscribeLocalEvent(OnGotEquipped); + SubscribeLocalEvent(OnGotUnequipped); } - private void OnGotEquipped(EntityUid uid, AddAccentClothingComponent component, GotEquippedEvent args) + private void OnGotEquipped(EntityUid uid, AddAccentClothingComponent component, ref ClothingGotEquippedEvent args) { - if (!TryComp(uid, out ClothingComponent? clothing)) - return; - - // check if entity was actually used as clothing - // not just taken in pockets or something - var isCorrectSlot = clothing.Slots.HasFlag(args.SlotFlags); - if (!isCorrectSlot) - return; - // does the user already has this accent? var componentType = _componentFactory.GetRegistration(component.Accent).Type; - if (HasComp(args.Equipee, componentType)) + if (HasComp(args.Wearer, componentType)) return; // add accent to the user var accentComponent = (Component) _componentFactory.GetComponent(componentType); - AddComp(args.Equipee, accentComponent); + AddComp(args.Wearer, accentComponent); // snowflake case for replacement accent if (accentComponent is ReplacementAccentComponent rep) @@ -42,16 +32,16 @@ private void OnGotEquipped(EntityUid uid, AddAccentClothingComponent component, component.IsActive = true; } - private void OnGotUnequipped(EntityUid uid, AddAccentClothingComponent component, GotUnequippedEvent args) + private void OnGotUnequipped(EntityUid uid, AddAccentClothingComponent component, ref ClothingGotUnequippedEvent args) { if (!component.IsActive) return; // try to remove accent var componentType = _componentFactory.GetRegistration(component.Accent).Type; - if (EntityManager.HasComponent(args.Equipee, componentType)) + if (EntityManager.HasComponent(args.Wearer, componentType)) { - EntityManager.RemoveComponent(args.Equipee, componentType); + EntityManager.RemoveComponent(args.Wearer, componentType); } component.IsActive = false; diff --git a/Content.Server/Speech/EntitySystems/FrenchAccentSystem.cs b/Content.Server/Speech/EntitySystems/FrenchAccentSystem.cs index 5637288732..f6d259c115 100644 --- a/Content.Server/Speech/EntitySystems/FrenchAccentSystem.cs +++ b/Content.Server/Speech/EntitySystems/FrenchAccentSystem.cs @@ -10,6 +10,10 @@ public sealed class FrenchAccentSystem : EntitySystem { [Dependency] private readonly ReplacementAccentSystem _replacement = default!; + private static readonly Regex RegexTh = new(@"th", RegexOptions.IgnoreCase); + private static readonly Regex RegexStartH = new(@"(? DirectReplacements = new() - { - { "let me", "lemme" }, - { "should", "oughta" }, - { "the", "da" }, - { "them", "dem" }, - { "attack", "whack" }, - { "kill", "whack" }, - { "murder", "whack" }, - { "dead", "sleepin' with da fishies"}, - { "hey", "ey'o" }, - { "hi", "ey'o"}, - { "hello", "ey'o"}, - { "rules", "roolz" }, - { "you", "yous" }, - { "have to", "gotta" }, - { "going to", "boutta" }, - { "about to", "boutta" }, - { "here", "'ere" } - }; - public override void Initialize() { base.Initialize(); @@ -49,20 +36,30 @@ public string Accentuate(string message, MobsterAccentComponent component) // thinking -> thinkin' // king -> king - msg = Regex.Replace(msg, @"(?<=\w\w)ing(?!\w)", "in'", RegexOptions.IgnoreCase); + //Uses captures groups to make sure the captialization of IN is kept + msg = RegexIng.Replace(msg, "$1'"); // or -> uh and ar -> ah in the middle of words (fuhget, tahget) - msg = Regex.Replace(msg, @"(?<=\w)or(?=\w)", "uh", RegexOptions.IgnoreCase); - msg = Regex.Replace(msg, @"(?<=\w)ar(?=\w)", "ah", RegexOptions.IgnoreCase); + msg = RegexLowerOr.Replace(msg, "uh"); + msg = RegexUpperOr.Replace(msg, "UH"); + msg = RegexLowerAr.Replace(msg, "ah"); + msg = RegexUpperAr.Replace(msg, "AH"); // Prefix if (_random.Prob(0.15f)) { + //Checks if the first word of the sentence is all caps + //So the prefix can be allcapped and to not resanitize the captial + var firstWordAllCaps = !RegexFirstWord.Match(msg).Value.Any(char.IsLower); var pick = _random.Next(1, 2); // Reverse sanitize capital - msg = msg[0].ToString().ToLower() + msg.Remove(0, 1); - msg = Loc.GetString($"accent-mobster-prefix-{pick}") + " " + msg; + var prefix = Loc.GetString($"accent-mobster-prefix-{pick}"); + if (!firstWordAllCaps) + msg = msg[0].ToString().ToLower() + msg.Remove(0, 1); + else + prefix = prefix.ToUpper(); + msg = prefix + " " + msg; } // Sanitize capital again, in case we substituted a word that should be capitalized @@ -71,16 +68,23 @@ public string Accentuate(string message, MobsterAccentComponent component) // Suffixes if (_random.Prob(0.4f)) { + //Checks if the last word of the sentence is all caps + //So the suffix can be allcapped + var lastWordAllCaps = !RegexLastWord.Match(msg).Value.Any(char.IsLower); + var suffix = ""; if (component.IsBoss) { var pick = _random.Next(1, 4); - msg += Loc.GetString($"accent-mobster-suffix-boss-{pick}"); + suffix = Loc.GetString($"accent-mobster-suffix-boss-{pick}"); } else { var pick = _random.Next(1, 3); - msg += Loc.GetString($"accent-mobster-suffix-minion-{pick}"); + suffix = Loc.GetString($"accent-mobster-suffix-minion-{pick}"); } + if (lastWordAllCaps) + suffix = suffix.ToUpper(); + msg += suffix; } return msg; diff --git a/Content.Server/Speech/EntitySystems/MothAccentSystem.cs b/Content.Server/Speech/EntitySystems/MothAccentSystem.cs index 3de4651b4a..84b79d4ce9 100644 --- a/Content.Server/Speech/EntitySystems/MothAccentSystem.cs +++ b/Content.Server/Speech/EntitySystems/MothAccentSystem.cs @@ -5,6 +5,9 @@ namespace Content.Server.Speech.EntitySystems; public sealed class MothAccentSystem : EntitySystem { + private static readonly Regex RegexLowerBuzz = new Regex("z{1,3}"); + private static readonly Regex RegexUpperBuzz = new Regex("Z{1,3}"); + public override void Initialize() { base.Initialize(); @@ -16,10 +19,10 @@ private void OnAccent(EntityUid uid, MothAccentComponent component, AccentGetEve var message = args.Message; // buzzz - message = Regex.Replace(message, "z{1,3}", "zzz"); + message = RegexLowerBuzz.Replace(message, "zzz"); // buZZZ - message = Regex.Replace(message, "Z{1,3}", "ZZZ"); - + message = RegexUpperBuzz.Replace(message, "ZZZ"); + args.Message = message; } } diff --git a/Content.Server/Speech/EntitySystems/ParrotAccentSystem.cs b/Content.Server/Speech/EntitySystems/ParrotAccentSystem.cs index 10437c235d..ae8fe522b9 100644 --- a/Content.Server/Speech/EntitySystems/ParrotAccentSystem.cs +++ b/Content.Server/Speech/EntitySystems/ParrotAccentSystem.cs @@ -7,6 +7,8 @@ namespace Content.Server.Speech.EntitySystems; public sealed partial class ParrotAccentSystem : EntitySystem { + private static readonly Regex WordCleanupRegex = new Regex("[^A-Za-z0-9 -]"); + [Dependency] private readonly IRobustRandom _random = default!; public override void Initialize() @@ -27,7 +29,7 @@ public string Accentuate(Entity entity, string message) if (_random.Prob(entity.Comp.LongestWordRepeatChance)) { // Don't count non-alphanumeric characters as parts of words - var cleaned = Regex.Replace(message, "[^A-Za-z0-9 -]", string.Empty); + var cleaned = WordCleanupRegex.Replace(message, string.Empty); // Split on whitespace and favor words towards the end of the message var words = cleaned.Split(null).Reverse(); // Find longest word diff --git a/Content.Server/Speech/EntitySystems/PirateAccentSystem.cs b/Content.Server/Speech/EntitySystems/PirateAccentSystem.cs index f1d64ede10..84298cbf01 100644 --- a/Content.Server/Speech/EntitySystems/PirateAccentSystem.cs +++ b/Content.Server/Speech/EntitySystems/PirateAccentSystem.cs @@ -1,3 +1,4 @@ +using System.Linq; using Content.Server.Speech.Components; using Robust.Shared.Random; using System.Text.RegularExpressions; @@ -6,6 +7,8 @@ namespace Content.Server.Speech.EntitySystems; public sealed class PirateAccentSystem : EntitySystem { + private static readonly Regex FirstWordAllCapsRegex = new(@"^(\S+)"); + [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly ReplacementAccentSystem _replacement = default!; @@ -19,17 +22,22 @@ public override void Initialize() // converts left word when typed into the right word. For example typing you becomes ye. public string Accentuate(string message, PirateAccentComponent component) { - var msg = message; - - msg = _replacement.ApplyReplacements(msg, "pirate"); + var msg = _replacement.ApplyReplacements(message, "pirate"); if (!_random.Prob(component.YarrChance)) return msg; + //Checks if the first word of the sentence is all caps + //So the prefix can be allcapped and to not resanitize the captial + var firstWordAllCaps = !FirstWordAllCapsRegex.Match(msg).Value.Any(char.IsLower); var pick = _random.Pick(component.PirateWords); + var pirateWord = Loc.GetString(pick); // Reverse sanitize capital - msg = msg[0].ToString().ToLower() + msg.Remove(0, 1); - msg = Loc.GetString(pick) + " " + msg; + if (!firstWordAllCaps) + msg = msg[0].ToString().ToLower() + msg.Remove(0, 1); + else + pirateWord = pirateWord.ToUpper(); + msg = pirateWord + " " + msg; return msg; } diff --git a/Content.Server/Speech/EntitySystems/ReplacementAccentSystem.cs b/Content.Server/Speech/EntitySystems/ReplacementAccentSystem.cs index 36fa7e07ad..da198bcc12 100644 --- a/Content.Server/Speech/EntitySystems/ReplacementAccentSystem.cs +++ b/Content.Server/Speech/EntitySystems/ReplacementAccentSystem.cs @@ -24,6 +24,9 @@ public override void Initialize() private void OnAccent(EntityUid uid, ReplacementAccentComponent component, AccentGetEvent args) { + if (!_random.Prob(component.ReplacementChance)) + return; + args.Message = ApplyReplacements(args.Message, component.Accent); } @@ -46,6 +49,12 @@ public string ApplyReplacements(string message, string accent) if (prototype.WordReplacements == null) return message; + // Prohibition of repeated word replacements. + // All replaced words placed in the final message are placed here as dashes (___) with the same length. + // The regex search goes through this buffer message, from which the already replaced words are crossed out, + // ensuring that the replaced words cannot be replaced again. + var maskMessage = message; + foreach (var (first, replace) in prototype.WordReplacements) { var f = _loc.GetString(first); @@ -53,10 +62,10 @@ public string ApplyReplacements(string message, string accent) // this is kind of slow but its not that bad // essentially: go over all matches, try to match capitalization where possible, then replace // rather than using regex.replace - for (int i = Regex.Count(message, $@"(? 0; i--) + for (int i = Regex.Count(maskMessage, $@"(? 0; i--) { // fetch the match again as the character indices may have changed - Match match = Regex.Match(message, $@"(? DirectReplacements = new() { { "fuck you", "I've got a BONE to pick with you" }, @@ -45,7 +48,7 @@ public string Accentuate(string message, SkeletonAccentComponent component) // Character manipulations: // At the start of words, any non-vowel + "one" becomes "bone", e.g. tone -> bone ; lonely -> bonely; clone -> clone (remains unchanged). - msg = Regex.Replace(msg, @"(?] + private const string MuzzleAccent = "mumble"; public override void Initialize() { @@ -53,7 +59,10 @@ private void OnSexChanged(EntityUid uid, VocalComponent component, SexChangedEve private void OnEmote(EntityUid uid, VocalComponent component, ref EmoteEvent args) { - if (args.Handled || !args.Emote.Category.HasFlag(EmoteCategory.Vocal)) + if (args.Handled + || !args.Emote.Category.HasFlag(EmoteCategory.Vocal) + || !_actionBlocker.CanSpeak(uid) + || TryComp(uid, out var replacement) && replacement.Accent == MuzzleAccent) // This is not ideal, but it works. return; // snowflake case for wilhelm scream easter egg diff --git a/Content.Server/Speech/EntitySystems/VoiceOverrideSystem.cs b/Content.Server/Speech/EntitySystems/VoiceOverrideSystem.cs new file mode 100644 index 0000000000..97510966f5 --- /dev/null +++ b/Content.Server/Speech/EntitySystems/VoiceOverrideSystem.cs @@ -0,0 +1,22 @@ +using Content.Shared.Chat; +using Content.Server.Speech.Components; + +namespace Content.Server.Speech.EntitySystems; + +public sealed partial class VoiceOverrideSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnTransformSpeakerName); + } + + private void OnTransformSpeakerName(Entity entity, ref TransformSpeakerSpeechEvent args) + { + if (!entity.Comp.Enabled) + return; + + args.VoiceName = entity.Comp.NameOverride ?? args.VoiceName; + args.SpeechVerb = entity.Comp.SpeechVerbOverride ?? args.SpeechVerb; + } +} diff --git a/Content.Server/Speech/Systems/RandomBarkSystem.cs b/Content.Server/Speech/Systems/RandomBarkSystem.cs index 863ea79843..5706a791bf 100644 --- a/Content.Server/Speech/Systems/RandomBarkSystem.cs +++ b/Content.Server/Speech/Systems/RandomBarkSystem.cs @@ -10,20 +10,22 @@ public sealed class RandomBarkSystem : EntitySystem { [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly ChatSystem _chat = default!; - [Dependency] private readonly EntityManager _entity = default!; + + private static readonly string[] AddedPunctuation = [".", "...", "!", "..!", "!!"]; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnInit); } - private void OnInit(EntityUid uid, RandomBarkComponent barker, ComponentInit args) + private void OnInit(Entity ent, ref MapInitEvent args) { - barker.BarkAccumulator = _random.NextFloat(barker.MinTime, barker.MaxTime) * barker.BarkMultiplier; + ent.Comp.BarkAccumulator = _random.NextFloat(ent.Comp.MinTime, ent.Comp.MaxTime) * ent.Comp.BarkMultiplier; + ent.Comp.BarkLocaleCount ??= GetBarkLocaleCount(ent); } public override void Update(float frameTime) @@ -38,11 +40,55 @@ public override void Update(float frameTime) continue; barker.BarkAccumulator = _random.NextFloat(barker.MinTime, barker.MaxTime) * barker.BarkMultiplier; - if (_entity.TryGetComponent(uid, out var actComp) && - actComp.HasMind) + if (TryComp(uid, out var actComp) && actComp.HasMind + || GetNextBark((uid, barker)) is not { } bark) continue; - _chat.TrySendInGameICMessage(uid, _random.Pick(barker.Barks), InGameICChatType.Speak, barker.ChatLog ? ChatTransmitRange.Normal : ChatTransmitRange.HideChat); + _chat.TrySendInGameICMessage(uid, bark, InGameICChatType.Speak, barker.ChatLog ? ChatTransmitRange.Normal : ChatTransmitRange.HideChat); + } + } + + /// + /// Tries to get the next bark for the given entity. Returns null if it fails. + /// + public string? GetNextBark(Entity ent) + { + var count = GetBarkLocaleCount(ent); + if (count <= 0) + return null; + + var index = _random.Next(0, count) + 1; + if (!Loc.TryGetString($"bark-{ent.Comp.BarkType}-{index}", out var bark)) + { + Log.Error($"Could not find bark with index {index} and type {ent.Comp.BarkType} for entity {ent.Owner}."); + return null; } + + // If the last char of the string is an alphanumeric one, then add a random punctuation mark + if (bark.Length > 0 && char.IsLetterOrDigit(bark[^1])) + bark += _random.Pick(AddedPunctuation); + + return bark; + } + + private int GetBarkLocaleCount(Entity ent) + { + if (ent.Comp.BarkLocaleCount is { } localeCount) + return localeCount; + + // All the error logging should cause certain integration tests to fail should someone setup randombark incorrectly + if (!Loc.TryGetString($"bark-{ent.Comp.BarkType}-count", out var localeCountStr)) + { + Log.Error($"Entity {ent.Owner} has a bark type {ent.Comp.BarkType} which does not have a respective bark count locale."); + return 0; + } + + if (!int.TryParse(localeCountStr, out localeCount) || localeCount < 0) + { + Log.Error($"Entity {ent.Owner} has a bark type {ent.Comp.BarkType} whose respective bark count locale is not a valid number."); + return 0; + } + + return localeCount; } } diff --git a/Content.Server/Standing/LayingDownComponent.cs b/Content.Server/Standing/LayingDownComponent.cs deleted file mode 100644 index 7921749f14..0000000000 --- a/Content.Server/Standing/LayingDownComponent.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Content.Server.Standing; - -[RegisterComponent] -public sealed partial class LayingDownComponent : Component -{ - [DataField] - public float DownedSpeedMultiplier = 0.15f; - - [DataField] - public TimeSpan Cooldown = TimeSpan.FromSeconds(2.5f); - - [DataField] - public TimeSpan NextToggleAttempt = TimeSpan.Zero; -} diff --git a/Content.Server/Standing/LayingDownSystem.cs b/Content.Server/Standing/LayingDownSystem.cs index 73a929fdfc..fcad649c9a 100644 --- a/Content.Server/Standing/LayingDownSystem.cs +++ b/Content.Server/Standing/LayingDownSystem.cs @@ -1,101 +1,33 @@ -using Content.Shared.ActionBlocker; +using Content.Shared.Standing; +using Content.Shared.CCVar; using Content.Shared.Input; using Content.Shared.Movement.Systems; using Content.Shared.Popups; -using Content.Shared.Standing; +using Robust.Shared.Configuration; using Robust.Shared.Input.Binding; using Robust.Shared.Player; -using Robust.Shared.Timing; namespace Content.Server.Standing; -/// Unfortunately cannot be shared because some standing conditions are server-side only -public sealed class LayingDownSystem : EntitySystem +public sealed class LayingDownSystem : SharedLayingDownSystem { - [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; - [Dependency] private readonly MovementSpeedModifierSystem _movement = default!; - [Dependency] private readonly SharedPopupSystem _popups = default!; - [Dependency] private readonly Shared.Standing.StandingStateSystem _standing = default!; // WHY IS THERE TWO DIFFERENT STANDING SYSTEMS?! - [Dependency] private readonly IGameTiming _timing = default!; - + [Dependency] private readonly INetConfigurationManager _cfg = default!; public override void Initialize() { - CommandBinds.Builder - .Bind(ContentKeyFunctions.ToggleStanding, InputCmdHandler.FromDelegate(ToggleStanding, handle: false, outsidePrediction: false)) - .Register(); + base.Initialize(); - SubscribeLocalEvent(DoRefreshMovementSpeed); - SubscribeLocalEvent(DoRefreshMovementSpeed); - SubscribeLocalEvent(OnRefreshMovementSpeed); - SubscribeLocalEvent(OnParentChanged); + SubscribeNetworkEvent(OnCheckAutoGetUp); } - public override void Shutdown() + private void OnCheckAutoGetUp(CheckAutoGetUpEvent ev, EntitySessionEventArgs args) { - base.Shutdown(); + var uid = GetEntity(ev.User); - CommandBinds.Unregister(); - } - - private void DoRefreshMovementSpeed(EntityUid uid, LayingDownComponent component, object args) - { - _movement.RefreshMovementSpeedModifiers(uid); - } - - private void OnRefreshMovementSpeed(EntityUid uid, LayingDownComponent component, RefreshMovementSpeedModifiersEvent args) - { - if (TryComp(uid, out var standingState) && standingState.Standing) + if (!TryComp(uid, out LayingDownComponent? layingDown)) return; - args.ModifySpeed(component.DownedSpeedMultiplier, component.DownedSpeedMultiplier, bypassImmunity: true); - } - - private void OnParentChanged(EntityUid uid, LayingDownComponent component, EntParentChangedMessage args) - { - // If the entity is not on a grid, try to make it stand up to avoid issues - if (!TryComp(uid, out var standingState) - || standingState.Standing - || Transform(uid).GridUid != null) - return; - - _standing.Stand(uid, standingState); - } - - private void ToggleStanding(ICommonSession? session) - { - if (session is not { AttachedEntity: { Valid: true } uid } playerSession - || !Exists(uid) - || !TryComp(uid, out var standingState) - || !TryComp(uid, out var layingDown)) - return; - - // If successful, show popup to self and others. Otherwise, only to self. - if (ToggleStandingImpl(uid, standingState, layingDown, out var popupBranch)) - { - _popups.PopupEntity(Loc.GetString($"laying-comp-{popupBranch}-other", ("entity", uid)), uid, Filter.PvsExcept(uid), true); - layingDown.NextToggleAttempt = _timing.CurTime + layingDown.Cooldown; - } - - _popups.PopupEntity(Loc.GetString($"laying-comp-{popupBranch}-self", ("entity", uid)), uid, uid); - } - - private bool ToggleStandingImpl(EntityUid uid, StandingStateComponent standingState, LayingDownComponent layingDown, out string popupBranch) - { - var success = layingDown.NextToggleAttempt <= _timing.CurTime; - - if (_standing.IsDown(uid, standingState)) - { - success = success && _standing.Stand(uid, standingState, force: false); - popupBranch = success ? "stand-success" : "stand-fail"; - } - else - { - success = success && Transform(uid).GridUid != null; // Do not allow laying down when not on a surface. - success = success && _standing.Down(uid, standingState: standingState, playSound: true, dropHeldItems: false); - popupBranch = success ? "lay-success" : "lay-fail"; - } - - return success; + layingDown.AutoGetUp = _cfg.GetClientCVar(args.SenderSession.Channel, CCVars.AutoGetUp); + Dirty(uid, layingDown); } } diff --git a/Content.Server/Station/Components/StationBiomeComponent.cs b/Content.Server/Station/Components/StationBiomeComponent.cs new file mode 100644 index 0000000000..0eb64aaff8 --- /dev/null +++ b/Content.Server/Station/Components/StationBiomeComponent.cs @@ -0,0 +1,22 @@ +using Content.Server.Station.Systems; +using Content.Shared.Parallax.Biomes; +using Robust.Shared.Prototypes; + +namespace Content.Server.Station.Components; + +/// +/// Runs EnsurePlanet against the largest grid on Mapinit. +/// +[RegisterComponent, Access(typeof(StationBiomeSystem))] +public sealed partial class StationBiomeComponent : Component +{ + [DataField(required: true)] + public ProtoId Biome = "Grasslands"; + + // If null, its random + [DataField] + public int? Seed = null; + + [DataField] + public Color MapLightColor = Color.Black; +} diff --git a/Content.Server/Station/Events/StationJobsGetCandidatesEvent.cs b/Content.Server/Station/Events/StationJobsGetCandidatesEvent.cs new file mode 100644 index 0000000000..58d860b1e1 --- /dev/null +++ b/Content.Server/Station/Events/StationJobsGetCandidatesEvent.cs @@ -0,0 +1,8 @@ +using Content.Shared.Roles; +using Robust.Shared.Network; +using Robust.Shared.Prototypes; + +namespace Content.Server.Station.Events; + +[ByRefEvent] +public readonly record struct StationJobsGetCandidatesEvent(NetUserId Player, List> Jobs); diff --git a/Content.Server/Station/Events/StationPostInitEvent.cs b/Content.Server/Station/Events/StationPostInitEvent.cs index 4f7927cee5..54b8eeeb31 100644 --- a/Content.Server/Station/Events/StationPostInitEvent.cs +++ b/Content.Server/Station/Events/StationPostInitEvent.cs @@ -4,6 +4,8 @@ namespace Content.Server.Station.Events; /// /// Raised directed on a station after it has been initialized, as well as broadcast. +/// This gets raised after the entity has been map-initialized, and the station's centcomm map/entity (if any) has been +/// set up. /// [ByRefEvent] public readonly record struct StationPostInitEvent(Entity Station); diff --git a/Content.Server/Station/Systems/StationBiomeSystem.cs b/Content.Server/Station/Systems/StationBiomeSystem.cs new file mode 100644 index 0000000000..8c80806ccf --- /dev/null +++ b/Content.Server/Station/Systems/StationBiomeSystem.cs @@ -0,0 +1,35 @@ +using Content.Server.Parallax; +using Content.Server.Station.Components; +using Content.Server.Station.Events; +using Content.Server.Station.Systems; +using Robust.Shared.Map; +using Robust.Shared.Prototypes; + +namespace Content.Server.Station.Systems; +public sealed partial class StationBiomeSystem : EntitySystem +{ + [Dependency] private readonly BiomeSystem _biome = default!; + [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly IPrototypeManager _proto = default!; + [Dependency] private readonly StationSystem _station = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnStationPostInit); + } + + private void OnStationPostInit(Entity map, ref StationPostInitEvent args) + { + if (!TryComp(map, out StationDataComponent? dataComp)) + return; + + var station = _station.GetLargestGrid(dataComp); + if (station == null) return; + + var mapId = Transform(station.Value).MapID; + var mapUid = _mapManager.GetMapEntityId(mapId); + + _biome.EnsurePlanet(mapUid, _proto.Index(map.Comp.Biome), map.Comp.Seed, mapLight: map.Comp.MapLightColor); + } +} diff --git a/Content.Server/Station/Systems/StationJobsSystem.Roundstart.cs b/Content.Server/Station/Systems/StationJobsSystem.Roundstart.cs index 4b7cd64961..c3c3865c7b 100644 --- a/Content.Server/Station/Systems/StationJobsSystem.Roundstart.cs +++ b/Content.Server/Station/Systems/StationJobsSystem.Roundstart.cs @@ -2,6 +2,7 @@ using Content.Server.Administration.Managers; using Content.Server.Players.PlayTimeTracking; using Content.Server.Station.Components; +using Content.Server.Station.Events; using Content.Shared.Preferences; using Content.Shared.Roles; using Robust.Shared.Network; @@ -342,8 +343,9 @@ private Dictionary> GetPlayersJobCandidates(int? weight, foreach (var (player, profile) in profiles) { var roleBans = _banManager.GetJobBans(player); - var profileJobs = profile.JobPriorities.Keys.ToList(); - _playTime.RemoveDisallowedJobs(player, ref profileJobs); + var profileJobs = profile.JobPriorities.Keys.Select(k => new ProtoId(k)).ToList(); + var ev = new StationJobsGetCandidatesEvent(player, profileJobs); + RaiseLocalEvent(ref ev); List? availableJobs = null; @@ -354,7 +356,7 @@ private Dictionary> GetPlayersJobCandidates(int? weight, if (!(priority == selectedPriority || selectedPriority is null)) continue; - if (!_prototypeManager.TryIndex(jobId, out JobPrototype? job)) + if (!_prototypeManager.TryIndex(jobId, out var job)) continue; if (weight is not null && job.Weight != weight.Value) diff --git a/Content.Server/Station/Systems/StationJobsSystem.cs b/Content.Server/Station/Systems/StationJobsSystem.cs index debac8902e..3bfa815af1 100644 --- a/Content.Server/Station/Systems/StationJobsSystem.cs +++ b/Content.Server/Station/Systems/StationJobsSystem.cs @@ -428,7 +428,7 @@ public IReadOnlySet GetOverflowJobs(EntityUid station, StationJobsCompon /// Whether or not to pick from the overflow list. /// A set of disallowed jobs, if any. /// The selected job, if any. - public string? PickBestAvailableJobWithPriority(EntityUid station, IReadOnlyDictionary jobPriorities, bool pickOverflows, IReadOnlySet? disallowedJobs = null) + public string? PickBestAvailableJobWithPriority(EntityUid station, IReadOnlyDictionary jobPriorities, bool pickOverflows, IReadOnlySet>? disallowedJobs = null) { if (station == EntityUid.Invalid) return null; diff --git a/Content.Server/Station/Systems/StationSpawningSystem.cs b/Content.Server/Station/Systems/StationSpawningSystem.cs index 7ec9a9d798..85be5f740d 100644 --- a/Content.Server/Station/Systems/StationSpawningSystem.cs +++ b/Content.Server/Station/Systems/StationSpawningSystem.cs @@ -5,6 +5,7 @@ using Content.Server.Mind.Commands; using Content.Server.PDA; using Content.Server.Shuttles.Systems; +using Content.Server.Silicon.IPC; using Content.Server.Spawners.EntitySystems; using Content.Server.Spawners.Components; using Content.Server.Station.Components; @@ -46,7 +47,7 @@ public sealed class StationSpawningSystem : SharedStationSpawningSystem [Dependency] private readonly SharedAccessSystem _accessSystem = default!; [Dependency] private readonly IdentitySystem _identity = default!; [Dependency] private readonly MetaDataSystem _metaSystem = default!; - + [Dependency] private readonly InternalEncryptionKeySpawner _internalEncryption = default!; [Dependency] private readonly ArrivalsSystem _arrivalsSystem = default!; [Dependency] private readonly ContainerSpawnPointSystem _containerSpawnPointSystem = default!; @@ -57,6 +58,7 @@ public sealed class StationSpawningSystem : SharedStationSpawningSystem /// public override void Initialize() { + base.Initialize(); Subs.CVar(_configurationManager, CCVars.ICRandomCharacters, e => _randomizeCharacters = e, true); _spawnerCallbacks = new Dictionary>() @@ -84,7 +86,6 @@ public override void Initialize() if (station != null && !Resolve(station.Value, ref stationSpawning)) throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station)); - // Delta-V: Set desired spawn point type. var ev = new PlayerSpawningEvent(job, profile, station, spawnPointType); if (station != null && profile != null) @@ -173,11 +174,15 @@ public EntityUid SpawnPlayerMob( if (prototype?.StartingGear != null) { var startingGear = _prototypeManager.Index(prototype.StartingGear); - EquipStartingGear(entity.Value, startingGear, profile); + EquipStartingGear(entity.Value, startingGear, raiseEvent: false); if (profile != null) EquipIdCard(entity.Value, profile.Name, prototype, station); + _internalEncryption.TryInsertEncryptionKey(entity.Value, startingGear, EntityManager); } + var gearEquippedEv = new StartingGearEquippedEvent(entity.Value); + RaiseLocalEvent(entity.Value, ref gearEquippedEv, true); + if (profile != null) { _humanoidSystem.LoadProfile(entity.Value, profile); @@ -274,7 +279,7 @@ public sealed class PlayerSpawningEvent : EntityEventArgs /// public readonly EntityUid? Station; /// - /// Delta-V: Desired SpawnPointType, if any. + /// Desired SpawnPointType, if any. /// public readonly SpawnPointType DesiredSpawnPointType; diff --git a/Content.Server/Station/Systems/StationSystem.cs b/Content.Server/Station/Systems/StationSystem.cs index 408b3ebc55..88e419ae39 100644 --- a/Content.Server/Station/Systems/StationSystem.cs +++ b/Content.Server/Station/Systems/StationSystem.cs @@ -28,22 +28,16 @@ namespace Content.Server.Station.Systems; [PublicAPI] public sealed class StationSystem : EntitySystem { - [Dependency] private readonly IConfigurationManager _configurationManager = default!; [Dependency] private readonly ILogManager _logManager = default!; [Dependency] private readonly IPlayerManager _player = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly ChatSystem _chatSystem = default!; - [Dependency] private readonly GameTicker _gameTicker = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly MetaDataSystem _metaData = default!; [Dependency] private readonly MapSystem _map = default!; private ISawmill _sawmill = default!; - private bool _randomStationOffset; - private bool _randomStationRotation; - private float _maxRandomStationOffset; - /// public override void Initialize() { @@ -112,26 +106,12 @@ private void OnPostGameMapLoad(PostGameMapLoad ev) { var dict = new Dictionary>(); - void AddGrid(string station, EntityUid grid) - { - if (dict.ContainsKey(station)) - { - dict[station].Add(grid); - } - else - { - dict[station] = new List {grid}; - } - } - // Iterate over all BecomesStation foreach (var grid in ev.Grids) { // We still setup the grid - if (!TryComp(grid, out var becomesStation)) - continue; - - AddGrid(becomesStation.Id, grid); + if (TryComp(grid, out var becomesStation)) + dict.GetOrNew(becomesStation.Id).Add(grid); } if (!dict.Any()) @@ -294,8 +274,6 @@ public EntityUid InitializeNewStation(StationConfig stationConfig, IEnumerable + /// Alert level to set the station to when the event starts. + /// + [DataField] + public string AlertLevel = "blue"; +} diff --git a/Content.Server/StationEvents/Components/GlimmerMobRuleComponent.cs b/Content.Server/StationEvents/Components/GlimmerMobRuleComponent.cs new file mode 100644 index 0000000000..982cb35ae8 --- /dev/null +++ b/Content.Server/StationEvents/Components/GlimmerMobRuleComponent.cs @@ -0,0 +1,51 @@ +using Content.Server.StationEvents.Events; +using Content.Shared.Psionics.Glimmer; +using Robust.Shared.Prototypes; + +namespace Content.Server.StationEvents.Components; + +/// +/// Tries to spawn a random number of mobs scaling with psionic people. +/// Rolls glimmer sources then vents then midround spawns in that order. +/// +[RegisterComponent, Access(typeof(GlimmerMobRule))] +public sealed partial class GlimmerMobRuleComponent : Component +{ + /// + /// The mob to spawn. + /// + [DataField(required: true)] + public EntProtoId MobPrototype = string.Empty; + + /// + /// Every this number of psionics spawns 1 mob. + /// + [DataField] + public int MobsPerPsionic = 10; + + /// + /// If the current glimmer tier is above this, mob count gets multiplied by the difference. + /// So by default 500-900 glimmer will double it and 900+ will triple it. + /// + [DataField] + public GlimmerTier GlimmerTier = GlimmerTier.Moderate; + + /// + /// Probability of rolling a glimmer source location. + /// + [DataField] + public float GlimmerProb = 0.4f; + + /// + /// Probability of rolling a vent location. + /// + [DataField] + public float NormalProb = 1f; + + /// + /// Probability of rolling a midround antag location. + /// Should always be 1 to guarantee the right number of spawned mobs. + /// + [DataField] + public float HiddenProb = 1f; +} diff --git a/Content.Server/StationEvents/Components/GlimmerWispRuleComponent.cs b/Content.Server/StationEvents/Components/GlimmerWispRuleComponent.cs deleted file mode 100644 index 60477e6a6a..0000000000 --- a/Content.Server/StationEvents/Components/GlimmerWispRuleComponent.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Content.Server.StationEvents.Events; - -namespace Content.Server.StationEvents.Components; - -[RegisterComponent, Access(typeof(GlimmerWispRule))] -public sealed partial class GlimmerWispRuleComponent : Component -{ -} diff --git a/Content.Server/StationEvents/Components/LoneOpsSpawnRuleComponent.cs b/Content.Server/StationEvents/Components/LoneOpsSpawnRuleComponent.cs deleted file mode 100644 index 92911e0858..0000000000 --- a/Content.Server/StationEvents/Components/LoneOpsSpawnRuleComponent.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Content.Server.StationEvents.Events; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Server.StationEvents.Components; - -[RegisterComponent, Access(typeof(LoneOpsSpawnRule))] -public sealed partial class LoneOpsSpawnRuleComponent : Component -{ - [DataField("loneOpsShuttlePath")] - public string LoneOpsShuttlePath = "Maps/Shuttles/striker.yml"; - - [DataField("gameRuleProto", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string GameRuleProto = "Nukeops"; - - [DataField("additionalRule")] - public EntityUid? AdditionalRule; -} diff --git a/Content.Server/StationEvents/Components/NoosphericZapRuleComponent.cs b/Content.Server/StationEvents/Components/NoosphericZapRuleComponent.cs index bfa82644cd..56c56005b7 100644 --- a/Content.Server/StationEvents/Components/NoosphericZapRuleComponent.cs +++ b/Content.Server/StationEvents/Components/NoosphericZapRuleComponent.cs @@ -5,4 +5,18 @@ namespace Content.Server.StationEvents.Components; [RegisterComponent, Access(typeof(NoosphericZapRule))] public sealed partial class NoosphericZapRuleComponent : Component { + /// + /// How long (in seconds) should this event stun its victims. + /// + public float StunDuration = 5f; + + /// + /// How long (in seconds) should this event give its victims the Stuttering condition. + /// + public float StutterDuration = 10f; + + /// + /// When paralyzing a Psion with a reroll still available, how much should this event modify the odds of generating a power. + /// + public float PowerRerollMultiplier = 0.25f; } diff --git a/Content.Server/StationEvents/Components/OscillatingStationEventSchedulerComponent.cs b/Content.Server/StationEvents/Components/OscillatingStationEventSchedulerComponent.cs new file mode 100644 index 0000000000..7f28401a5a --- /dev/null +++ b/Content.Server/StationEvents/Components/OscillatingStationEventSchedulerComponent.cs @@ -0,0 +1,69 @@ +namespace Content.Server.StationEvents.Components; + +/// +/// A station event scheduler that emits events at irregular intervals, with occasional chaos and occasional calmness. +/// +[RegisterComponent] +public sealed partial class OscillatingStationEventSchedulerComponent : Component +{ + // TODO cvars? + [DataField] + public float MinChaos = 0.1f, MaxChaos = 15f; + + /// + /// The amount of chaos at the beginning of the round. + /// + [DataField] + public float StartingChaosRatio = 0f; + + /// + /// The value of the first derivative of the event delay function at the beginning of the shift. + /// Must be between 1 and -1. + /// + [DataField] + public float StartingSlope = 1f; + + /// + /// Biases that determine how likely the event rate is to go up or down, and how fast it's going to happen. + /// + /// + /// Downwards bias must always be negative, and upwards must be positive. Otherwise, you'll get odd behavior or errors. + /// + [DataField] + public float DownwardsBias = -1f, UpwardsBias = 1f; + + /// + /// Limits that define how large the chaos slope can become. + /// + /// + /// Downwards limit must always be negative, and upwards must be positive. Otherwise, you'll get odd behavior or errors. + /// + [DataField] + public float DownwardsLimit = -1f, UpwardsLimit = 1f; + + /// + /// A value between 0 and 1 that determines how slowly the chaos and its first derivative change in time. + /// + /// + /// Changing these values will have a great impact on how fast the event rate changes. + /// + [DataField] + public float ChaosStickiness = 0.93f, SlopeStickiness = 0.96f; + + /// + /// Actual chaos data at the current moment. Those are overridden at runtime. + /// + [DataField] + public float CurrentChaos, CurrentSlope, LastAcceleration; + + + [DataField] + public TimeSpan NextUpdate = TimeSpan.Zero, LastEventTime = TimeSpan.Zero; + + /// + /// Update interval, which determines how often current chaos is recalculated. + /// Modifying this value does not directly impact the event rate, but changes how stable the slope is. + /// + [DataField] + public TimeSpan UpdateInterval = TimeSpan.FromSeconds(5f); +} diff --git a/Content.Server/StationEvents/Components/PirateRadioSpawnRuleComponent.cs b/Content.Server/StationEvents/Components/PirateRadioSpawnRuleComponent.cs new file mode 100644 index 0000000000..3bfa189ec6 --- /dev/null +++ b/Content.Server/StationEvents/Components/PirateRadioSpawnRuleComponent.cs @@ -0,0 +1,37 @@ +using Content.Server.StationEvents.Events; + +namespace Content.Server.StationEvents.Components; + +[RegisterComponent, Access(typeof(PirateRadioSpawnRule))] +public sealed partial class PirateRadioSpawnRuleComponent : Component +{ + [DataField] + public List PirateRadioShuttlePath { get; private set; } = new() + { + "Maps/Shuttles/pirateradio.yml", + }; + + [DataField] + public EntityUid? AdditionalRule; + + [DataField] + public int DebrisCount { get; set; } + + [DataField] + public float MinimumDistance { get; set; } = 750f; + + [DataField] + public float MaximumDistance { get; set; } = 1250f; + + [DataField] + public float MinimumDebrisDistance { get; set; } = 150f; + + [DataField] + public float MaximumDebrisDistance { get; set; } = 250f; + + [DataField] + public float DebrisMinimumOffset { get; set; } = 50f; + + [DataField] + public float DebrisMaximumOffset { get; set; } = 100f; +} diff --git a/Content.Server/StationEvents/Components/RampingStationEventSchedulerComponent.cs b/Content.Server/StationEvents/Components/RampingStationEventSchedulerComponent.cs index 282ee5b612..9771e02e03 100644 --- a/Content.Server/StationEvents/Components/RampingStationEventSchedulerComponent.cs +++ b/Content.Server/StationEvents/Components/RampingStationEventSchedulerComponent.cs @@ -24,6 +24,7 @@ public sealed partial class RampingStationEventSchedulerComponent : Component /// /// The number by which average expected shift length is multiplied. Higher values lead to slower chaos growth. /// + [DataField] public float ShiftLengthModifier = 1f; // Everything below is overridden in the RampingStationEventSchedulerSystem based on CVars diff --git a/Content.Server/StationEvents/Components/StationEventComponent.cs b/Content.Server/StationEvents/Components/StationEventComponent.cs index 980034aa7b..54af1a59d9 100644 --- a/Content.Server/StationEvents/Components/StationEventComponent.cs +++ b/Content.Server/StationEvents/Components/StationEventComponent.cs @@ -1,4 +1,4 @@ -using Robust.Shared.Audio; +using Robust.Shared.Audio; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; namespace Content.Server.StationEvents.Components; @@ -15,43 +15,43 @@ public sealed partial class StationEventComponent : Component public const float WeightHigh = 15.0f; public const float WeightVeryHigh = 20.0f; - [DataField("weight")] + [DataField] public float Weight = WeightNormal; - [DataField("startAnnouncement")] + [DataField] public bool StartAnnouncement; - [DataField("endAnnouncement")] + [DataField] public bool EndAnnouncement; /// /// In minutes, when is the first round time this event can start /// - [DataField("earliestStart")] + [DataField] public int EarliestStart = 5; /// /// In minutes, the amount of time before the same event can occur again /// - [DataField("reoccurrenceDelay")] + [DataField] public int ReoccurrenceDelay = 30; /// /// How long after being added does the event start /// - [DataField("startDelay")] + [DataField] public TimeSpan StartDelay = TimeSpan.Zero; /// /// How long the event lasts. /// - [DataField("duration")] + [DataField] public TimeSpan? Duration = TimeSpan.FromSeconds(1); /// /// The max amount of time the event lasts. /// - [DataField("maxDuration")] + [DataField] public TimeSpan? MaxDuration; /// @@ -60,13 +60,13 @@ public sealed partial class StationEventComponent : Component /// /// To avoid running deadly events with low-pop /// - [DataField("minimumPlayers")] + [DataField] public int MinimumPlayers; /// /// How many times this even can occur in a single round /// - [DataField("maxOccurrences")] + [DataField] public int? MaxOccurrences; /// diff --git a/Content.Server/StationEvents/EventManagerSystem.cs b/Content.Server/StationEvents/EventManagerSystem.cs index 2d8606e929..9c0005d06d 100644 --- a/Content.Server/StationEvents/EventManagerSystem.cs +++ b/Content.Server/StationEvents/EventManagerSystem.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.Server.Chat.Managers; using Content.Server.GameTicking; using Content.Server.StationEvents.Components; using Content.Shared.CCVar; @@ -16,6 +17,7 @@ public sealed class EventManagerSystem : EntitySystem [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IPrototypeManager _prototype = default!; + [Dependency] private readonly IChatManager _chat = default!; [Dependency] public readonly GameTicker GameTicker = default!; [Dependency] private readonly GlimmerSystem _glimmerSystem = default!; //Nyano - Summary: pulls in the glimmer system. @@ -45,6 +47,7 @@ public string RunRandomEvent() var ent = GameTicker.AddGameRule(randomEvent); var str = Loc.GetString("station-event-system-run-event",("eventName", ToPrettyString(ent))); + _chat.SendAdminAlert(str); Log.Info(str); return str; } diff --git a/Content.Server/StationEvents/Events/AlertLevelInterceptionRule.cs b/Content.Server/StationEvents/Events/AlertLevelInterceptionRule.cs new file mode 100644 index 0000000000..a78a542d3b --- /dev/null +++ b/Content.Server/StationEvents/Events/AlertLevelInterceptionRule.cs @@ -0,0 +1,23 @@ +using Content.Server.GameTicking.Components; +using Content.Server.StationEvents.Components; +using Content.Server.AlertLevel; + +namespace Content.Server.StationEvents.Events; + +public sealed class AlertLevelInterceptionRule : StationEventSystem +{ + [Dependency] private readonly AlertLevelSystem _alertLevelSystem = default!; + + protected override void Started(EntityUid uid, AlertLevelInterceptionRuleComponent component, GameRuleComponent gameRule, + GameRuleStartedEvent args) + { + base.Started(uid, component, gameRule, args); + + if (!TryGetRandomStation(out var chosenStation)) + return; + if (_alertLevelSystem.GetLevel(chosenStation.Value) != "green") + return; + + _alertLevelSystem.SetLevel(chosenStation.Value, component.AlertLevel, true, true, true); + } +} \ No newline at end of file diff --git a/Content.Server/StationEvents/Events/AnomalySpawnRule.cs b/Content.Server/StationEvents/Events/AnomalySpawnRule.cs index 4cd94d3e71..98d5aa76a6 100644 --- a/Content.Server/StationEvents/Events/AnomalySpawnRule.cs +++ b/Content.Server/StationEvents/Events/AnomalySpawnRule.cs @@ -1,4 +1,5 @@ using Content.Server.Anomaly; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.Station.Components; using Content.Server.StationEvents.Components; diff --git a/Content.Server/StationEvents/Events/BluespaceArtifactRule.cs b/Content.Server/StationEvents/Events/BluespaceArtifactRule.cs index b25c1d6561..29c1897657 100644 --- a/Content.Server/StationEvents/Events/BluespaceArtifactRule.cs +++ b/Content.Server/StationEvents/Events/BluespaceArtifactRule.cs @@ -1,4 +1,5 @@ -using Content.Server.GameTicking.Rules.Components; +using Content.Server.GameTicking.Components; +using Content.Server.GameTicking.Rules.Components; using Content.Server.StationEvents.Components; using Robust.Shared.Random; using Content.Server.Announcements.Systems; diff --git a/Content.Server/StationEvents/Events/BluespaceLockerRule.cs b/Content.Server/StationEvents/Events/BluespaceLockerRule.cs index 709b750334..eef9850e73 100644 --- a/Content.Server/StationEvents/Events/BluespaceLockerRule.cs +++ b/Content.Server/StationEvents/Events/BluespaceLockerRule.cs @@ -1,4 +1,5 @@ -using Content.Server.GameTicking.Rules.Components; +using Content.Server.GameTicking.Components; +using Content.Server.GameTicking.Rules.Components; using Content.Server.Resist; using Content.Server.Station.Components; using Content.Server.StationEvents.Components; diff --git a/Content.Server/StationEvents/Events/BreakerFlipRule.cs b/Content.Server/StationEvents/Events/BreakerFlipRule.cs index e7574f27ad..3b2368556b 100644 --- a/Content.Server/StationEvents/Events/BreakerFlipRule.cs +++ b/Content.Server/StationEvents/Events/BreakerFlipRule.cs @@ -1,4 +1,5 @@ -using Content.Server.GameTicking.Rules.Components; +using Content.Server.GameTicking.Components; +using Content.Server.GameTicking.Rules.Components; using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; using Content.Server.Station.Components; diff --git a/Content.Server/StationEvents/Events/BureaucraticErrorRule.cs b/Content.Server/StationEvents/Events/BureaucraticErrorRule.cs index feb88d9b84..282e28e499 100644 --- a/Content.Server/StationEvents/Events/BureaucraticErrorRule.cs +++ b/Content.Server/StationEvents/Events/BureaucraticErrorRule.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.Station.Components; using Content.Server.Station.Systems; diff --git a/Content.Server/StationEvents/Events/CargoGiftsRule.cs b/Content.Server/StationEvents/Events/CargoGiftsRule.cs index 80af23c6fa..550f799b27 100644 --- a/Content.Server/StationEvents/Events/CargoGiftsRule.cs +++ b/Content.Server/StationEvents/Events/CargoGiftsRule.cs @@ -1,7 +1,8 @@ -using System.Linq; +using System.Linq; using Content.Server.Cargo.Components; using Content.Server.Cargo.Systems; using Content.Server.GameTicking; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.Station.Components; using Content.Server.StationEvents.Components; @@ -73,13 +74,14 @@ protected override void ActiveTick(EntityUid uid, CargoGiftsRuleComponent compon if (!_cargoSystem.AddAndApproveOrder( station!.Value, product.Product, + product.Name, product.Cost, qty, Loc.GetString(component.Sender), Loc.GetString(component.Description), Loc.GetString(component.Dest), cargoDb, - stationData! + (station.Value, stationData) )) { break; diff --git a/Content.Server/StationEvents/Events/ClericalErrorRule.cs b/Content.Server/StationEvents/Events/ClericalErrorRule.cs index dd4473952c..854ee685b3 100644 --- a/Content.Server/StationEvents/Events/ClericalErrorRule.cs +++ b/Content.Server/StationEvents/Events/ClericalErrorRule.cs @@ -1,4 +1,5 @@ -using Content.Server.GameTicking.Rules.Components; +using Content.Server.GameTicking.Components; +using Content.Server.GameTicking.Rules.Components; using Content.Server.StationEvents.Components; using Content.Server.StationRecords; using Content.Server.StationRecords.Systems; diff --git a/Content.Server/StationEvents/Events/FalseAlarmRule.cs b/Content.Server/StationEvents/Events/FalseAlarmRule.cs index cd434a721b..2d129b3558 100644 --- a/Content.Server/StationEvents/Events/FalseAlarmRule.cs +++ b/Content.Server/StationEvents/Events/FalseAlarmRule.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.StationEvents.Components; using JetBrains.Annotations; diff --git a/Content.Server/StationEvents/Events/FreeProberRule.cs b/Content.Server/StationEvents/Events/FreeProberRule.cs index 0aa8ecc47c..a5dfdd6b6e 100644 --- a/Content.Server/StationEvents/Events/FreeProberRule.cs +++ b/Content.Server/StationEvents/Events/FreeProberRule.cs @@ -1,3 +1,4 @@ +using Content.Server.GameTicking.Components; using Robust.Shared.Map; using Robust.Shared.Random; using Content.Server.GameTicking.Rules.Components; diff --git a/Content.Server/StationEvents/Events/GasLeakRule.cs b/Content.Server/StationEvents/Events/GasLeakRule.cs index 68544e416c..1221612171 100644 --- a/Content.Server/StationEvents/Events/GasLeakRule.cs +++ b/Content.Server/StationEvents/Events/GasLeakRule.cs @@ -1,4 +1,5 @@ using Content.Server.Atmos.EntitySystems; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.StationEvents.Components; using Robust.Shared.Audio; diff --git a/Content.Server/StationEvents/Events/GlimmerEventSystem.cs b/Content.Server/StationEvents/Events/GlimmerEventSystem.cs index a3d36ae715..3e0762c834 100644 --- a/Content.Server/StationEvents/Events/GlimmerEventSystem.cs +++ b/Content.Server/StationEvents/Events/GlimmerEventSystem.cs @@ -1,3 +1,4 @@ +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.Psionics.Glimmer; using Content.Shared.Psionics.Glimmer; diff --git a/Content.Server/StationEvents/Events/GlimmerMobSpawnRule.cs b/Content.Server/StationEvents/Events/GlimmerMobSpawnRule.cs new file mode 100644 index 0000000000..702147842c --- /dev/null +++ b/Content.Server/StationEvents/Events/GlimmerMobSpawnRule.cs @@ -0,0 +1,77 @@ +using System.Linq; +using Content.Server.GameTicking.Components; +using Robust.Shared.Random; +using Content.Server.GameTicking; +using Content.Server.NPC.Components; +using Content.Server.Psionics.Glimmer; +using Content.Server.Station.Systems; +using Content.Server.StationEvents.Components; +using Content.Shared.Psionics.Glimmer; +using Content.Shared.Abilities.Psionics; +using Robust.Shared.Map; + +namespace Content.Server.StationEvents.Events; + +public sealed class GlimmerMobRule : StationEventSystem +{ + [Dependency] private readonly GameTicker _gameTicker = default!; + [Dependency] private readonly GlimmerSystem _glimmer = default!; + [Dependency] private readonly StationSystem _stations = default!; + + protected override void Started(EntityUid uid, GlimmerMobRuleComponent comp, GameRuleComponent gameRule, GameRuleStartedEvent args) + { + base.Started(uid, comp, gameRule, args); + + var stations = _gameTicker.GetSpawnableStations(); + if (stations.Count <= 0) + return; + + List + glimmerSources = GetCoords(stations), + normalSpawns = GetCoords(stations), + hiddenSpawns = GetCoords(stations); + + var psionics = EntityQuery().Count(); + var baseCount = Math.Max(1, psionics / comp.MobsPerPsionic); + var multiplier = Math.Max(1, (int) _glimmer.GetGlimmerTier() - (int) comp.GlimmerTier); + var total = baseCount * multiplier; + + Log.Info($"Spawning {total} of {comp.MobPrototype} from {ToPrettyString(uid):rule}"); + + for (var i = 0; i < total; i++) + { + // if we cant get a spawn just give up + if (!TrySpawn(comp, glimmerSources, comp.GlimmerProb) && + !TrySpawn(comp, normalSpawns, comp.NormalProb) && + !TrySpawn(comp, hiddenSpawns, comp.HiddenProb)) + return; + } + } + + private List GetCoords(List allowedStations) where T : IComponent + { + var coords = new List(); + var query = EntityQueryEnumerator(); + + while (query.MoveNext(out var uid, out _, out var xform)) + { + var station = _stations.GetOwningStation(uid, xform); + if (station is null || !allowedStations.Contains(station.Value)) + continue; + + coords.Add(xform.Coordinates); + } + + return coords; + } + + private bool TrySpawn(GlimmerMobRuleComponent comp, List spawns, float prob) + { + if (spawns.Count == 0 || !RobustRandom.Prob(prob)) + return false; + + var coords = RobustRandom.Pick(spawns); + Spawn(comp.MobPrototype, coords); + return true; + } +} diff --git a/Content.Server/StationEvents/Events/GlimmerRandomSentienceRule.cs b/Content.Server/StationEvents/Events/GlimmerRandomSentienceRule.cs index c086462b40..a288710356 100644 --- a/Content.Server/StationEvents/Events/GlimmerRandomSentienceRule.cs +++ b/Content.Server/StationEvents/Events/GlimmerRandomSentienceRule.cs @@ -1,7 +1,8 @@ using System.Linq; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.Ghost.Roles.Components; -using Content.Server.Psionics; +using Content.Shared.Abilities.Psionics; using Content.Server.Speech.Components; using Content.Server.StationEvents.Components; using Content.Shared.Mobs.Systems; @@ -50,7 +51,7 @@ protected override void Started(EntityUid uid, GlimmerRandomSentienceRuleCompone comp.RoleDescription = Loc.GetString("station-event-random-sentience-role-description", ("name", comp.RoleName)); RemComp(target); RemComp(target); - EnsureComp(target); + EnsureComp(target); EnsureComp(target); } } diff --git a/Content.Server/StationEvents/Events/GlimmerRevenantSpawnRule.cs b/Content.Server/StationEvents/Events/GlimmerRevenantSpawnRule.cs index 8bab321db7..152d6d9fe5 100644 --- a/Content.Server/StationEvents/Events/GlimmerRevenantSpawnRule.cs +++ b/Content.Server/StationEvents/Events/GlimmerRevenantSpawnRule.cs @@ -1,3 +1,4 @@ +using Content.Server.GameTicking.Components; using Robust.Shared.Random; using Content.Server.GameTicking.Rules.Components; using Content.Server.Psionics.Glimmer; diff --git a/Content.Server/StationEvents/Events/GlimmerWispSpawnRule.cs b/Content.Server/StationEvents/Events/GlimmerWispSpawnRule.cs deleted file mode 100644 index 66eea988ae..0000000000 --- a/Content.Server/StationEvents/Events/GlimmerWispSpawnRule.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System.Linq; -using Robust.Shared.Random; -using Content.Server.GameTicking.Rules.Components; -using Content.Server.NPC.Components; -using Content.Server.Psionics.Glimmer; -using Content.Server.StationEvents.Components; -using Content.Shared.Psionics.Glimmer; -using Content.Shared.Abilities.Psionics; - -namespace Content.Server.StationEvents.Events; - -internal sealed class GlimmerWispRule : StationEventSystem -{ - [Dependency] private readonly IRobustRandom _robustRandom = default!; - [Dependency] private readonly GlimmerSystem _glimmerSystem = default!; - - private static readonly string WispPrototype = "MobGlimmerWisp"; - - protected override void Started(EntityUid uid, GlimmerWispRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args) - { - base.Started(uid, component, gameRule, args); - - var glimmerSources = EntityManager.EntityQuery().ToList(); - var normalSpawnLocations = EntityManager.EntityQuery().ToList(); - var hiddenSpawnLocations = EntityManager.EntityQuery().ToList(); - - var baseCount = Math.Max(1, EntityManager.EntityQuery().Count() / 10); - int multiplier = Math.Max(1, (int) _glimmerSystem.GetGlimmerTier() - 2); - - var total = baseCount * multiplier; - - int i = 0; - while (i < total) - { - if (glimmerSources.Count != 0 && _robustRandom.Prob(0.4f)) - { - EntityManager.SpawnEntity(WispPrototype, _robustRandom.Pick(glimmerSources).Item2.Coordinates); - i++; - continue; - } - - if (normalSpawnLocations.Count != 0) - { - EntityManager.SpawnEntity(WispPrototype, _robustRandom.Pick(normalSpawnLocations).Item2.Coordinates); - i++; - continue; - } - - if (hiddenSpawnLocations.Count != 0) - { - EntityManager.SpawnEntity(WispPrototype, _robustRandom.Pick(hiddenSpawnLocations).Item2.Coordinates); - i++; - continue; - } - return; - } - } -} diff --git a/Content.Server/StationEvents/Events/ImmovableRodRule.cs b/Content.Server/StationEvents/Events/ImmovableRodRule.cs index a61c6b69e1..45d6c18189 100644 --- a/Content.Server/StationEvents/Events/ImmovableRodRule.cs +++ b/Content.Server/StationEvents/Events/ImmovableRodRule.cs @@ -1,4 +1,5 @@ using System.Numerics; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.ImmovableRod; using Content.Server.StationEvents.Components; @@ -22,7 +23,9 @@ protected override void Started(EntityUid uid, ImmovableRodRuleComponent compone var proto = _prototypeManager.Index(component.RodPrototype); if (proto.TryGetComponent(out var rod) && proto.TryGetComponent(out var despawn)) { - TryFindRandomTile(out _, out _, out _, out var targetCoords); + if (!TryFindRandomTile(out _, out _, out _, out var targetCoords)) + return; + var speed = RobustRandom.NextFloat(rod.MinSpeed, rod.MaxSpeed); var angle = RobustRandom.NextAngle(); var direction = angle.ToVec(); diff --git a/Content.Server/StationEvents/Events/IonStormRule.cs b/Content.Server/StationEvents/Events/IonStormRule.cs index cd3cd63ae8..8361cc6048 100644 --- a/Content.Server/StationEvents/Events/IonStormRule.cs +++ b/Content.Server/StationEvents/Events/IonStormRule.cs @@ -1,5 +1,5 @@ +using Content.Server.GameTicking.Components; using System.Linq; -using Content.Server.GameTicking.Rules.Components; using Content.Server.Silicons.Laws; using Content.Server.Station.Components; using Content.Server.StationEvents.Components; diff --git a/Content.Server/StationEvents/Events/KudzuGrowthRule.cs b/Content.Server/StationEvents/Events/KudzuGrowthRule.cs index 3fa12cd4e9..5b56e03846 100644 --- a/Content.Server/StationEvents/Events/KudzuGrowthRule.cs +++ b/Content.Server/StationEvents/Events/KudzuGrowthRule.cs @@ -1,3 +1,4 @@ +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.StationEvents.Components; diff --git a/Content.Server/StationEvents/Events/LoneOpsSpawnRule.cs b/Content.Server/StationEvents/Events/LoneOpsSpawnRule.cs deleted file mode 100644 index 4b15e59099..0000000000 --- a/Content.Server/StationEvents/Events/LoneOpsSpawnRule.cs +++ /dev/null @@ -1,47 +0,0 @@ -using Robust.Server.GameObjects; -using Robust.Server.Maps; -using Content.Server.GameTicking.Rules.Components; -using Content.Server.StationEvents.Components; -using Content.Server.RoundEnd; - -namespace Content.Server.StationEvents.Events; - -public sealed class LoneOpsSpawnRule : StationEventSystem -{ - [Dependency] private readonly MapLoaderSystem _map = default!; - - protected override void Started(EntityUid uid, LoneOpsSpawnRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args) - { - base.Started(uid, component, gameRule, args); - - // Loneops can only spawn if there is no nukeops active - if (GameTicker.IsGameRuleAdded()) - { - ForceEndSelf(uid, gameRule); - return; - } - - var shuttleMap = MapManager.CreateMap(); - var options = new MapLoadOptions - { - LoadMap = true, - }; - - _map.TryLoad(shuttleMap, component.LoneOpsShuttlePath, out _, options); - - var nukeopsEntity = GameTicker.AddGameRule(component.GameRuleProto); - component.AdditionalRule = nukeopsEntity; - var nukeopsComp = Comp(nukeopsEntity); - nukeopsComp.SpawnOutpost = false; - nukeopsComp.RoundEndBehavior = RoundEndBehavior.Nothing; - GameTicker.StartGameRule(nukeopsEntity); - } - - protected override void Ended(EntityUid uid, LoneOpsSpawnRuleComponent component, GameRuleComponent gameRule, GameRuleEndedEvent args) - { - base.Ended(uid, component, gameRule, args); - - if (component.AdditionalRule != null) - GameTicker.EndGameRule(component.AdditionalRule.Value); - } -} diff --git a/Content.Server/StationEvents/Events/MassHallucinationsRule.cs b/Content.Server/StationEvents/Events/MassHallucinationsRule.cs index 4fc158f864..2239db7f70 100644 --- a/Content.Server/StationEvents/Events/MassHallucinationsRule.cs +++ b/Content.Server/StationEvents/Events/MassHallucinationsRule.cs @@ -1,3 +1,4 @@ +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.StationEvents.Components; using Content.Server.Traits.Assorted; diff --git a/Content.Server/StationEvents/Events/MassMindSwapRule.cs b/Content.Server/StationEvents/Events/MassMindSwapRule.cs index 6394456326..beb08eb8a7 100644 --- a/Content.Server/StationEvents/Events/MassMindSwapRule.cs +++ b/Content.Server/StationEvents/Events/MassMindSwapRule.cs @@ -1,6 +1,7 @@ using Robust.Server.GameObjects; using Robust.Shared.Random; using Content.Server.Abilities.Psionics; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.Psionics; using Content.Server.StationEvents.Components; @@ -27,7 +28,7 @@ protected override void Started(EntityUid uid, MassMindSwapRuleComponent compone List psionicPool = new(); List psionicActors = new(); - var query = EntityQueryEnumerator(); + var query = EntityQueryEnumerator(); while (query.MoveNext(out var psion, out _, out _)) { if (_mobStateSystem.IsAlive(psion) && !HasComp(psion)) diff --git a/Content.Server/StationEvents/Events/MeteorSwarmRule.cs b/Content.Server/StationEvents/Events/MeteorSwarmRule.cs index ad56479b37..455011259d 100644 --- a/Content.Server/StationEvents/Events/MeteorSwarmRule.cs +++ b/Content.Server/StationEvents/Events/MeteorSwarmRule.cs @@ -1,4 +1,5 @@ using System.Numerics; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.StationEvents.Components; using Robust.Shared.Map; diff --git a/Content.Server/StationEvents/Events/NinjaSpawnRule.cs b/Content.Server/StationEvents/Events/NinjaSpawnRule.cs index 8ad5c8602e..d9d68a386c 100644 --- a/Content.Server/StationEvents/Events/NinjaSpawnRule.cs +++ b/Content.Server/StationEvents/Events/NinjaSpawnRule.cs @@ -1,3 +1,4 @@ +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.Ninja.Systems; using Content.Server.Station.Components; diff --git a/Content.Server/StationEvents/Events/NoosphericFryRule.cs b/Content.Server/StationEvents/Events/NoosphericFryRule.cs index c04543d219..85f98d6f4b 100644 --- a/Content.Server/StationEvents/Events/NoosphericFryRule.cs +++ b/Content.Server/StationEvents/Events/NoosphericFryRule.cs @@ -3,6 +3,7 @@ using Robust.Shared.Player; using Content.Server.Atmos.Components; using Content.Server.Atmos.EntitySystems; +using Content.Server.GameTicking.Components; using Content.Shared.Construction.EntitySystems; using Content.Server.GameTicking.Rules.Components; using Content.Server.Popups; diff --git a/Content.Server/StationEvents/Events/NoosphericStormRule.cs b/Content.Server/StationEvents/Events/NoosphericStormRule.cs index 175318e15b..1de8bad89b 100644 --- a/Content.Server/StationEvents/Events/NoosphericStormRule.cs +++ b/Content.Server/StationEvents/Events/NoosphericStormRule.cs @@ -1,5 +1,6 @@ using Robust.Shared.Random; using Content.Server.Abilities.Psionics; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.StationEvents.Components; using Content.Server.Psionics; @@ -23,17 +24,14 @@ protected override void Started(EntityUid uid, NoosphericStormRuleComponent comp List validList = new(); - var query = EntityManager.EntityQueryEnumerator(); - while (query.MoveNext(out var potentialPsionic, out var potentialPsionicComponent)) + var query = EntityManager.EntityQueryEnumerator(); + while (query.MoveNext(out var Psionic, out var PsionicComponent)) { - if (_mobStateSystem.IsDead(potentialPsionic)) + if (_mobStateSystem.IsDead(Psionic) + || HasComp(Psionic)) continue; - // Skip over those who are already psionic or those who are insulated, or zombies. - if (HasComp(potentialPsionic) || HasComp(potentialPsionic) || HasComp(potentialPsionic)) - continue; - - validList.Add(potentialPsionic); + validList.Add(Psionic); } // Give some targets psionic abilities. diff --git a/Content.Server/StationEvents/Events/NoosphericZapRule.cs b/Content.Server/StationEvents/Events/NoosphericZapRule.cs index 82c3d72b13..96c3361203 100644 --- a/Content.Server/StationEvents/Events/NoosphericZapRule.cs +++ b/Content.Server/StationEvents/Events/NoosphericZapRule.cs @@ -1,3 +1,4 @@ +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.Popups; using Content.Server.Psionics; @@ -25,29 +26,25 @@ protected override void Started(EntityUid uid, NoosphericZapRuleComponent compon { base.Started(uid, component, gameRule, args); - var query = EntityQueryEnumerator(); + var query = EntityQueryEnumerator(); - while (query.MoveNext(out var psion, out var potentialPsionicComponent, out _)) + while (query.MoveNext(out var psion, out var psionicComponent, out _)) { if (!_mobStateSystem.IsAlive(psion) || HasComp(psion)) continue; - _stunSystem.TryParalyze(psion, TimeSpan.FromSeconds(5), false); - _statusEffectsSystem.TryAddStatusEffect(psion, "Stutter", TimeSpan.FromSeconds(10), false, "StutteringAccent"); + _stunSystem.TryParalyze(psion, TimeSpan.FromSeconds(component.StunDuration), false); + _statusEffectsSystem.TryAddStatusEffect(psion, "Stutter", TimeSpan.FromSeconds(component.StutterDuration), false, "StutteringAccent"); - if (HasComp(psion)) - _popupSystem.PopupEntity(Loc.GetString("noospheric-zap-seize"), psion, psion, Shared.Popups.PopupType.LargeCaution); + if (!psionicComponent.CanReroll) + { + psionicComponent.CanReroll = true; + _popupSystem.PopupEntity(Loc.GetString("noospheric-zap-seize-potential-regained"), psion, psion, Shared.Popups.PopupType.LargeCaution); + } else { - if (potentialPsionicComponent.Rerolled) - { - potentialPsionicComponent.Rerolled = false; - _popupSystem.PopupEntity(Loc.GetString("noospheric-zap-seize-potential-regained"), psion, psion, Shared.Popups.PopupType.LargeCaution); - } else - { - _psionicsSystem.RollPsionics(psion, potentialPsionicComponent, multiplier: 0.25f); - _popupSystem.PopupEntity(Loc.GetString("noospheric-zap-seize"), psion, psion, Shared.Popups.PopupType.LargeCaution); - } + _psionicsSystem.RollPsionics(psion, psionicComponent, true, component.PowerRerollMultiplier); + _popupSystem.PopupEntity(Loc.GetString("noospheric-zap-seize"), psion, psion, Shared.Popups.PopupType.LargeCaution); } } } diff --git a/Content.Server/StationEvents/Events/PirateRadioSpawnRule.cs b/Content.Server/StationEvents/Events/PirateRadioSpawnRule.cs new file mode 100644 index 0000000000..e6d36839f9 --- /dev/null +++ b/Content.Server/StationEvents/Events/PirateRadioSpawnRule.cs @@ -0,0 +1,99 @@ +using Robust.Server.GameObjects; +using Robust.Server.Maps; +using Robust.Shared.Configuration; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; +using Content.Server.GameTicking; +using Content.Server.StationEvents.Components; +using Content.Shared.Salvage; +using Content.Shared.Random.Helpers; +using System.Linq; +using Content.Server.GameTicking.Components; +using Content.Shared.CCVar; +using Robust.Shared.Serialization.Manager; +using Content.Shared.Parallax.Biomes; + +namespace Content.Server.StationEvents.Events; + +public sealed class PirateRadioSpawnRule : StationEventSystem +{ + [Dependency] private readonly MapLoaderSystem _map = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IConfigurationManager _confMan = default!; + [Dependency] private readonly GameTicker _gameTicker = default!; + [Dependency] private readonly TransformSystem _xform = default!; + [Dependency] private readonly ISerializationManager _serializationManager = default!; + + protected override void Started(EntityUid uid, PirateRadioSpawnRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args) + { + base.Started(uid, component, gameRule, args); + + var stations = _gameTicker.GetSpawnableStations(); + if (stations is null) + return; + + // Remove any station from the list that is on a Planet's surface. + // We have to do this because if we spawn the listening post 1.5 kilometers from the station on a planet + // The server will then attempt to generate 7 billion entities, immediately exceeding the 32bit signed integer limit for EntityUids. + var stationsCopy = _serializationManager.CreateCopy(stations, notNullableOverride: true); + foreach (var station in stationsCopy) + if (HasComp(Transform(station).MapUid)) + stations.Remove(station); + + // _random forces Test Fails if given an empty list. which is guaranteed to happen during Tests. + if (stations.Count <= 0) + return; + + var targetStation = _random.Pick(stations); + var randomOffset = _random.NextVector2(component.MinimumDistance, component.MaximumDistance); + + var outpostOptions = new MapLoadOptions + { + Offset = _xform.GetWorldPosition(targetStation) + randomOffset, + LoadMap = false, + }; + + if (!_map.TryLoad(GameTicker.DefaultMap, _random.Pick(component.PirateRadioShuttlePath), out var outpostids, outpostOptions)) + return; + + SpawnDebris(component, outpostids); + } + + private void SpawnDebris(PirateRadioSpawnRuleComponent component, IReadOnlyList outpostids) + { + if (_confMan.GetCVar(CCVars.WorldgenEnabled) + || component.DebrisCount <= 0) + return; + + foreach (var id in outpostids) + { + var outpostaabb = _xform.GetWorldPosition(id); + var k = 0; + while (k < component.DebrisCount) + { + var debrisRandomOffset = _random.NextVector2(component.MinimumDebrisDistance, component.MaximumDebrisDistance); + var randomer = _random.NextVector2(component.DebrisMinimumOffset, component.DebrisMaximumOffset); //Second random vector to ensure the outpost isn't perfectly centered in the debris field + var debrisOptions = new MapLoadOptions + { + Offset = outpostaabb + debrisRandomOffset + randomer, + LoadMap = false, + }; + + var salvageProto = _random.Pick(_prototypeManager.EnumeratePrototypes().ToList()); + if (!_map.TryLoad(GameTicker.DefaultMap, salvageProto.MapPath.ToString(), out _, debrisOptions)) + return; + + k++; + } + } + } + + protected override void Ended(EntityUid uid, PirateRadioSpawnRuleComponent component, GameRuleComponent gameRule, GameRuleEndedEvent args) + { + base.Ended(uid, component, gameRule, args); + + if (component.AdditionalRule != null) + GameTicker.EndGameRule(component.AdditionalRule.Value); + } +} diff --git a/Content.Server/StationEvents/Events/PowerGridCheckRule.cs b/Content.Server/StationEvents/Events/PowerGridCheckRule.cs index 97e8948461..b0a0bbc9fe 100644 --- a/Content.Server/StationEvents/Events/PowerGridCheckRule.cs +++ b/Content.Server/StationEvents/Events/PowerGridCheckRule.cs @@ -1,4 +1,5 @@ using System.Threading; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; diff --git a/Content.Server/StationEvents/Events/PsionicCatGotYourTongueRule.cs b/Content.Server/StationEvents/Events/PsionicCatGotYourTongueRule.cs index 63e0a435cb..b92097b28d 100644 --- a/Content.Server/StationEvents/Events/PsionicCatGotYourTongueRule.cs +++ b/Content.Server/StationEvents/Events/PsionicCatGotYourTongueRule.cs @@ -1,6 +1,6 @@ +using Content.Server.GameTicking.Components; using Robust.Shared.Random; using Robust.Shared.Player; -using Content.Server.Psionics; using Content.Server.GameTicking.Rules.Components; using Content.Server.StationEvents.Components; using Content.Shared.Mobs.Components; @@ -28,7 +28,7 @@ protected override void Started(EntityUid uid, PsionicCatGotYourTongueRuleCompon List psionicList = new(); - var query = EntityQueryEnumerator(); + var query = EntityQueryEnumerator(); while (query.MoveNext(out var psion, out _, out _)) { if (_mobStateSystem.IsAlive(psion) && !HasComp(psion)) diff --git a/Content.Server/StationEvents/Events/RandomEntityStorageSpawnRule.cs b/Content.Server/StationEvents/Events/RandomEntityStorageSpawnRule.cs index c3cd719cc4..87d50fc8b2 100644 --- a/Content.Server/StationEvents/Events/RandomEntityStorageSpawnRule.cs +++ b/Content.Server/StationEvents/Events/RandomEntityStorageSpawnRule.cs @@ -1,3 +1,4 @@ +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.StationEvents.Components; using Content.Server.Storage.Components; diff --git a/Content.Server/StationEvents/Events/RandomSentienceRule.cs b/Content.Server/StationEvents/Events/RandomSentienceRule.cs index f667ad7975..7b9173241f 100644 --- a/Content.Server/StationEvents/Events/RandomSentienceRule.cs +++ b/Content.Server/StationEvents/Events/RandomSentienceRule.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.Ghost.Roles.Components; using Content.Server.StationEvents.Components; diff --git a/Content.Server/StationEvents/Events/RandomSpawnRule.cs b/Content.Server/StationEvents/Events/RandomSpawnRule.cs index c514acc623..77744d44e4 100644 --- a/Content.Server/StationEvents/Events/RandomSpawnRule.cs +++ b/Content.Server/StationEvents/Events/RandomSpawnRule.cs @@ -1,3 +1,4 @@ +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.StationEvents.Components; diff --git a/Content.Server/StationEvents/Events/SolarFlareRule.cs b/Content.Server/StationEvents/Events/SolarFlareRule.cs index a4ec74b43b..0370b4ee61 100644 --- a/Content.Server/StationEvents/Events/SolarFlareRule.cs +++ b/Content.Server/StationEvents/Events/SolarFlareRule.cs @@ -1,3 +1,4 @@ +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.Radio; using Robust.Shared.Random; diff --git a/Content.Server/StationEvents/Events/StationEventSystem.cs b/Content.Server/StationEvents/Events/StationEventSystem.cs index 6de8024bd0..040ebad226 100644 --- a/Content.Server/StationEvents/Events/StationEventSystem.cs +++ b/Content.Server/StationEvents/Events/StationEventSystem.cs @@ -1,5 +1,7 @@ +using System.Linq; using Content.Server.Administration.Logs; using Content.Server.Chat.Systems; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules; using Content.Server.GameTicking.Rules.Components; using Content.Server.Station.Systems; @@ -45,6 +47,7 @@ protected override void Added(EntityUid uid, T component, GameRuleComponent game if (!TryComp(uid, out var stationEvent)) return; + AdminLogManager.Add(LogType.EventAnnounced, $"Event added / announced: {ToPrettyString(uid)}"); stationEvent.StartTime = Timing.CurTime + stationEvent.StartDelay; diff --git a/Content.Server/StationEvents/Events/VentClogRule.cs b/Content.Server/StationEvents/Events/VentClogRule.cs index e263a5f4f6..867f41dccc 100644 --- a/Content.Server/StationEvents/Events/VentClogRule.cs +++ b/Content.Server/StationEvents/Events/VentClogRule.cs @@ -6,6 +6,7 @@ using Robust.Shared.Random; using System.Linq; using Content.Server.Fluids.EntitySystems; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.StationEvents.Components; diff --git a/Content.Server/StationEvents/Events/VentCrittersRule.cs b/Content.Server/StationEvents/Events/VentCrittersRule.cs index cdcf2bf6ff..c2605039bc 100644 --- a/Content.Server/StationEvents/Events/VentCrittersRule.cs +++ b/Content.Server/StationEvents/Events/VentCrittersRule.cs @@ -1,3 +1,4 @@ +using Content.Server.GameTicking.Components; using Content.Server.StationEvents.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.Station.Components; diff --git a/Content.Server/StationEvents/OscillatingStationEventScheduler.cs b/Content.Server/StationEvents/OscillatingStationEventScheduler.cs new file mode 100644 index 0000000000..1e6dbd14a1 --- /dev/null +++ b/Content.Server/StationEvents/OscillatingStationEventScheduler.cs @@ -0,0 +1,102 @@ +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using Content.Server.GameTicking; +using Content.Server.GameTicking.Rules; +using Content.Server.GameTicking.Components; +using Content.Server.StationEvents.Components; +using Content.Shared.CCVar; +using Robust.Shared.Configuration; +using Robust.Shared.Random; +using Robust.Shared.Timing; +using Robust.Shared.Utility; + +namespace Content.Server.StationEvents; + +public sealed class OscillatingStationEventSchedulerSystem : GameRuleSystem +{ + [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly EventManagerSystem _event = default!; + [Dependency] private readonly GameTicker _gameTicker = default!; + [Dependency] private readonly IGameTiming _timing = default!; + + [Conditional("DEBUG")] + private void DebugValidateParams(OscillatingStationEventSchedulerComponent c) + { + // This monstrousity is necessary because if someone fucks up one of these parameters, + // it will likely either crash the game (in debug), or cause the event scheduler to stop working and spam the server console (in prod) + DebugTools.Assert(c.DownwardsBias <= 0f && c.UpwardsBias >= 0f, "Fix your scheduler bias"); + DebugTools.Assert(c.DownwardsLimit <= 0f && c.UpwardsLimit >= 0f, "Fix your scheduler slope limits"); + DebugTools.Assert(c.UpdateInterval > TimeSpan.Zero, "Scheduler update interval must be positive"); + DebugTools.Assert(c.ChaosStickiness >= 0f && c.ChaosStickiness <= 1f, "Scheduler stickiness must be between 0 and 1"); + DebugTools.Assert(c.SlopeStickiness >= 0f && c.SlopeStickiness <= 1f, "Scheduler stickiness must be between 0 and 1"); + DebugTools.Assert(c.MinChaos < c.MaxChaos, "Don't set the minimum above the maximum"); + } + + private TimeSpan CalculateAverageEventTime(OscillatingStationEventSchedulerComponent comp) + { + //TODO Those cvars are bad + var min = _cfg.GetCVar(CCVars.GameEventsOscillatingMinimumTime); + var max = _cfg.GetCVar(CCVars.GameEventsOscillatingAverageTime); + + return TimeSpan.FromSeconds(min + (max - min) / comp.CurrentChaos); // Why does C# have no math.lerp?????????????? + } + + protected override void Started(EntityUid uid, OscillatingStationEventSchedulerComponent comp, GameRuleComponent gameRule, GameRuleStartedEvent args) + { + DebugValidateParams(comp); + + comp.CurrentChaos = comp.MinChaos + comp.StartingChaosRatio * (comp.MaxChaos - comp.MinChaos); + comp.CurrentSlope = comp.StartingSlope; + + comp.NextUpdate = _timing.CurTime + CalculateAverageEventTime(comp); + comp.LastEventTime = _timing.CurTime; // Just so we don't run an event the very moment this scheduler gets added + } + + protected override void ActiveTick(EntityUid uid, OscillatingStationEventSchedulerComponent comp, GameRuleComponent gameRule, float frameTime) + { + if (comp.NextUpdate > _timing.CurTime) + return; + comp.NextUpdate = _timing.CurTime + comp.UpdateInterval; + DebugValidateParams(comp); + + // Slope is the first derivative of chaos, and acceleration is the second + // We randomize acceleration on each tick and simulate its effect on the slope and base function + // But we spread the effect across a longer time span to achieve a smooth and pleasant result + var delta = (float) comp.UpdateInterval.TotalSeconds; + var newAcceleration = _random.NextFloat(comp.DownwardsBias, comp.UpwardsBias); + var newSlope = + Math.Clamp(comp.CurrentSlope + newAcceleration * delta, comp.DownwardsLimit, comp.UpwardsLimit) * (1 - comp.SlopeStickiness) + + comp.CurrentSlope * comp.SlopeStickiness; + var newChaos = + Math.Clamp(comp.CurrentChaos + newSlope * delta, comp.MinChaos, comp.MaxChaos) * (1 - comp.ChaosStickiness) + + comp.CurrentChaos * comp.ChaosStickiness; + + comp.CurrentChaos = newChaos; + comp.CurrentSlope = newSlope; + comp.LastAcceleration = newAcceleration; + + // We do not use fixed "next event" times because that can cause us to skip over chaos spikes due to periods of low chaos + // Instead we recalculate the time until next event every time, so it can change before the event is even started + var targetDelay = CalculateAverageEventTime(comp); + if (_timing.CurTime > comp.LastEventTime + targetDelay && TryRunNextEvent(uid, comp, out _)) + { + #if DEBUG + var passed = _timing.CurTime - comp.LastEventTime; + Log.Debug($"Running an event after {passed.TotalSeconds} sec since last event. Next event scheduled in {CalculateAverageEventTime(comp).TotalSeconds} sec."); + #endif + + comp.LastEventTime = _timing.CurTime; + } + } + + public bool TryRunNextEvent(EntityUid uid, OscillatingStationEventSchedulerComponent comp, [NotNullWhen(true)] out string? runEvent) + { + runEvent = _event.PickRandomEvent(); + if (runEvent == null) + return false; + + _gameTicker.AddGameRule(runEvent); + return true; + } +} diff --git a/Content.Server/StationEvents/RampingStationEventSchedulerSystem.cs b/Content.Server/StationEvents/RampingStationEventSchedulerSystem.cs index aa0c9b214b..a6c38ef765 100644 --- a/Content.Server/StationEvents/RampingStationEventSchedulerSystem.cs +++ b/Content.Server/StationEvents/RampingStationEventSchedulerSystem.cs @@ -1,4 +1,5 @@ using Content.Server.GameTicking; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules; using Content.Server.GameTicking.Rules.Components; using Content.Server.StationEvents.Components; diff --git a/Content.Server/StationGoal/StationGoalPaperSystem.cs b/Content.Server/StationGoal/StationGoalPaperSystem.cs index 6a059c37a3..8442f9b4aa 100644 --- a/Content.Server/StationGoal/StationGoalPaperSystem.cs +++ b/Content.Server/StationGoal/StationGoalPaperSystem.cs @@ -9,6 +9,7 @@ using Robust.Shared.Prototypes; using Robust.Shared.Random; using Content.Shared.Dataset; +using Content.Shared.Fax.Components; namespace Content.Server.StationGoal; diff --git a/Content.Server/StationGoal/StationGoalPrototype.cs b/Content.Server/StationGoal/StationGoalPrototype.cs index 733759a37d..c49d49e939 100644 --- a/Content.Server/StationGoal/StationGoalPrototype.cs +++ b/Content.Server/StationGoal/StationGoalPrototype.cs @@ -3,7 +3,7 @@ namespace Content.Server.StationGoal { [Serializable, Prototype("stationGoal")] - public sealed class StationGoalPrototype : IPrototype + public sealed partial class StationGoalPrototype : IPrototype { [IdDataFieldAttribute] public string ID { get; } = default!; diff --git a/Content.Server/StationRecords/Systems/GeneralStationRecordConsoleSystem.cs b/Content.Server/StationRecords/Systems/GeneralStationRecordConsoleSystem.cs index 721eff6f2c..a5202285d9 100644 --- a/Content.Server/StationRecords/Systems/GeneralStationRecordConsoleSystem.cs +++ b/Content.Server/StationRecords/Systems/GeneralStationRecordConsoleSystem.cs @@ -57,7 +57,7 @@ private void UpdateUserInterface(Entity en if (!TryComp(owningStation, out var stationRecords)) { - _ui.TrySetUiState(uid, GeneralStationRecordConsoleKey.Key, new GeneralStationRecordConsoleState()); + _ui.SetUiState(uid, GeneralStationRecordConsoleKey.Key, new GeneralStationRecordConsoleState()); return; } @@ -66,7 +66,7 @@ private void UpdateUserInterface(Entity en switch (listing.Count) { case 0: - _ui.TrySetUiState(uid, GeneralStationRecordConsoleKey.Key, new GeneralStationRecordConsoleState()); + _ui.SetUiState(uid, GeneralStationRecordConsoleKey.Key, new GeneralStationRecordConsoleState()); return; case 1: console.ActiveKey = listing.Keys.First(); @@ -80,6 +80,6 @@ private void UpdateUserInterface(Entity en _stationRecords.TryGetRecord(key, out var record, stationRecords); GeneralStationRecordConsoleState newState = new(id, record, listing, console.Filter); - _ui.TrySetUiState(uid, GeneralStationRecordConsoleKey.Key, newState); + _ui.SetUiState(uid, GeneralStationRecordConsoleKey.Key, newState); } } diff --git a/Content.Server/StationRecords/Systems/StationRecordsSystem.cs b/Content.Server/StationRecords/Systems/StationRecordsSystem.cs index 67f50d7a4e..f04d997043 100644 --- a/Content.Server/StationRecords/Systems/StationRecordsSystem.cs +++ b/Content.Server/StationRecords/Systems/StationRecordsSystem.cs @@ -65,7 +65,11 @@ private void CreateGeneralRecord(EntityUid station, EntityUid player, HumanoidCh TryComp(player, out var fingerprintComponent); TryComp(player, out var dnaComponent); - CreateGeneralRecord(station, idUid.Value, profile.Name, profile.Age, profile.Species, profile.Gender, jobId, fingerprintComponent?.Fingerprint, dnaComponent?.DNA, profile, records); + string specie = profile.Species; + if (!string.IsNullOrEmpty(profile.Customspeciename)) + specie = profile.Customspeciename; + + CreateGeneralRecord(station, idUid.Value, profile.Name, profile.Age, specie, profile.Gender, jobId, fingerprintComponent?.Fingerprint, dnaComponent?.DNA, profile, records); } @@ -211,7 +215,7 @@ public bool TryGetRecord(StationRecordKey key, [NotNullWhen(true)] out T? ent /// public uint? GetRecordByName(EntityUid station, string name, StationRecordsComponent? records = null) { - if (!Resolve(station, ref records)) + if (!Resolve(station, ref records, false)) return null; foreach (var (id, record) in GetRecordsOfType(station, records)) diff --git a/Content.Server/Storage/Components/EntityStorageComponent.cs b/Content.Server/Storage/Components/EntityStorageComponent.cs index 40fdb1b326..3fba89b64a 100644 --- a/Content.Server/Storage/Components/EntityStorageComponent.cs +++ b/Content.Server/Storage/Components/EntityStorageComponent.cs @@ -1,4 +1,5 @@ using Content.Server.Atmos; +using Content.Shared.Atmos; using Content.Shared.Storage.Components; using Robust.Shared.GameStates; diff --git a/Content.Server/Storage/EntitySystems/SpawnItemsOnUseSystem.cs b/Content.Server/Storage/EntitySystems/SpawnItemsOnUseSystem.cs index c49bfdec93..3549587aa5 100644 --- a/Content.Server/Storage/EntitySystems/SpawnItemsOnUseSystem.cs +++ b/Content.Server/Storage/EntitySystems/SpawnItemsOnUseSystem.cs @@ -82,7 +82,9 @@ private void OnUseInHand(EntityUid uid, SpawnItemsOnUseComponent component, UseI if (component.Sound != null) { - _audio.PlayPvs(component.Sound, uid); + // The entity is often deleted, so play the sound at its position rather than parenting + var coordinates = Transform(uid).Coordinates; + _audio.PlayPvs(component.Sound, coordinates); } component.Uses--; @@ -97,6 +99,7 @@ private void OnUseInHand(EntityUid uid, SpawnItemsOnUseComponent component, UseI if (entityToPlaceInHands != null) { _hands.PickupOrDrop(args.User, entityToPlaceInHands.Value); + _audio.PlayPvs(component.Sound, entityToPlaceInHands.Value); } } } diff --git a/Content.Server/Storage/EntitySystems/StorageSystem.Fill.cs b/Content.Server/Storage/EntitySystems/StorageSystem.Fill.cs index 10278cc805..768491f987 100644 --- a/Content.Server/Storage/EntitySystems/StorageSystem.Fill.cs +++ b/Content.Server/Storage/EntitySystems/StorageSystem.Fill.cs @@ -65,12 +65,23 @@ private void FillStorage(Entity entity var sortedItems = items .OrderByDescending(x => ItemSystem.GetItemShape(x.Comp).GetArea()); + ClearCantFillReasons(); foreach (var ent in sortedItems) { if (Insert(uid, ent, out _, out var reason, storageComp: storage, playSound: false)) continue; + if (CantFillReasons.Count > 0) + { + var reasons = string.Join(", ", CantFillReasons.Select(s => Loc.GetString(s))); + if (reason == null) + reason = reasons; + else + reason += $", {reasons}"; + } + Log.Error($"Tried to StorageFill {ToPrettyString(ent)} inside {ToPrettyString(uid)} but can't. reason: {reason}"); + ClearCantFillReasons(); Del(ent); } } diff --git a/Content.Server/Storage/EntitySystems/StorageSystem.cs b/Content.Server/Storage/EntitySystems/StorageSystem.cs index 0f5efda74d..4b5dd7290c 100644 --- a/Content.Server/Storage/EntitySystems/StorageSystem.cs +++ b/Content.Server/Storage/EntitySystems/StorageSystem.cs @@ -3,17 +3,12 @@ using Content.Shared.Explosion; using Content.Shared.Ghost; using Content.Shared.Hands; -using Content.Shared.Input; -using Content.Shared.Inventory; using Content.Shared.Lock; using Content.Shared.Storage; using Content.Shared.Storage.Components; using Content.Shared.Storage.EntitySystems; -using Content.Shared.Timing; using Content.Shared.Verbs; using Robust.Server.GameObjects; -using Robust.Shared.Audio.Systems; -using Robust.Shared.Input.Binding; using Robust.Shared.Map; using Robust.Shared.Player; using Robust.Shared.Prototypes; @@ -23,95 +18,14 @@ namespace Content.Server.Storage.EntitySystems; public sealed partial class StorageSystem : SharedStorageSystem { - [Dependency] private readonly IAdminManager _admin = default!; [Dependency] private readonly IPrototypeManager _prototype = default!; - [Dependency] private readonly InventorySystem _inventory = default!; - [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly UseDelaySystem _useDelay = default!; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent>(AddUiVerb); - Subs.BuiEvents(StorageComponent.StorageUiKey.Key, subs => - { - subs.Event(OnBoundUIClosed); - }); SubscribeLocalEvent(OnExploded); SubscribeLocalEvent(OnStorageFillMapInit); - - CommandBinds.Builder - .Bind(ContentKeyFunctions.OpenBackpack, InputCmdHandler.FromDelegate(HandleOpenBackpack)) - .Bind(ContentKeyFunctions.OpenBelt, InputCmdHandler.FromDelegate(HandleOpenBelt)) - .Register(); - } - - private void AddUiVerb(EntityUid uid, StorageComponent component, GetVerbsEvent args) - { - var silent = false; - if (!args.CanAccess || !args.CanInteract || TryComp(uid, out var lockComponent) && lockComponent.Locked) - { - // we allow admins to open the storage anyways - if (!_admin.HasAdminFlag(args.User, AdminFlags.Admin)) - return; - - silent = true; - } - - silent |= HasComp(args.User); - - // Get the session for the user - if (!TryComp(args.User, out var actor)) - return; - - // Does this player currently have the storage UI open? - var uiOpen = _uiSystem.SessionHasOpenUi(uid, StorageComponent.StorageUiKey.Key, actor.PlayerSession); - - ActivationVerb verb = new() - { - Act = () => - { - if (uiOpen) - { - _uiSystem.TryClose(uid, StorageComponent.StorageUiKey.Key, actor.PlayerSession); - } - else - { - OpenStorageUI(uid, args.User, component, silent); - } - } - }; - if (uiOpen) - { - verb.Text = Loc.GetString("verb-common-close-ui"); - verb.Icon = new SpriteSpecifier.Texture( - new("/Textures/Interface/VerbIcons/close.svg.192dpi.png")); - } - else - { - verb.Text = Loc.GetString("verb-common-open-ui"); - verb.Icon = new SpriteSpecifier.Texture( - new("/Textures/Interface/VerbIcons/open.svg.192dpi.png")); - } - args.Verbs.Add(verb); - } - - private void OnBoundUIClosed(EntityUid uid, StorageComponent storageComp, BoundUIClosedEvent args) - { - if (TryComp(args.Session.AttachedEntity, out var actor) && actor?.PlayerSession != null) - CloseNestedInterfaces(uid, actor.PlayerSession, storageComp); - - // If UI is closed for everyone - if (!_uiSystem.IsUiOpen(uid, args.UiKey)) - { - storageComp.IsUiOpen = false; - UpdateAppearance((uid, storageComp, null)); - - if (storageComp.StorageCloseSound is not null) - Audio.PlayEntity(storageComp.StorageCloseSound, Filter.Pvs(uid, entityManager: EntityManager), uid, true, storageComp.StorageCloseSound.Params); - } } private void OnExploded(Entity ent, ref BeforeExplodeEvent args) @@ -119,34 +33,6 @@ private void OnExploded(Entity ent, ref BeforeExplodeEvent arg args.Contents.AddRange(ent.Comp.Container.ContainedEntities); } - /// - /// Opens the storage UI for an entity - /// - /// The entity to open the UI for - public override void OpenStorageUI(EntityUid uid, EntityUid entity, StorageComponent? storageComp = null, bool silent = false) - { - if (!Resolve(uid, ref storageComp, false) || !TryComp(entity, out ActorComponent? player)) - return; - - // prevent spamming bag open / honkerton honk sound - silent |= TryComp(uid, out var useDelay) && _useDelay.IsDelayed((uid, useDelay)); - if (!silent) - { - if (!storageComp.IsUiOpen) - _audio.PlayPvs(storageComp.StorageOpenSound, uid); - if (useDelay != null) - _useDelay.TryResetDelay((uid, useDelay)); - } - - Log.Debug($"Storage (UID {uid}) \"used\" by player session (UID {player.PlayerSession.AttachedEntity})."); - - var bui = _uiSystem.GetUiOrNull(uid, StorageComponent.StorageUiKey.Key); - if (bui == null) - return; - _uiSystem.OpenUi(bui, player.PlayerSession); - _uiSystem.SendUiMessage(bui, new StorageModifyWindowMessage()); - } - /// public override void PlayPickupAnimation(EntityUid uid, EntityCoordinates initialCoordinates, EntityCoordinates finalCoordinates, Angle initialRotation, EntityUid? user = null) @@ -154,57 +40,4 @@ public override void PlayPickupAnimation(EntityUid uid, EntityCoordinates initia var filter = Filter.Pvs(uid).RemoveWhereAttachedEntity(e => e == user); RaiseNetworkEvent(new PickupAnimationEvent(GetNetEntity(uid), GetNetCoordinates(initialCoordinates), GetNetCoordinates(finalCoordinates), initialRotation), filter); } - - /// - /// If the user has nested-UIs open (e.g., PDA UI open when pda is in a backpack), close them. - /// - /// - public void CloseNestedInterfaces(EntityUid uid, ICommonSession session, StorageComponent? storageComp = null) - { - if (!Resolve(uid, ref storageComp)) - return; - - // for each containing thing - // if it has a storage comp - // ensure unsubscribe from session - // if it has a ui component - // close ui - foreach (var entity in storageComp.Container.ContainedEntities) - { - if (!TryComp(entity, out UserInterfaceComponent? ui)) - continue; - - foreach (var bui in ui.Interfaces.Values) - { - _uiSystem.TryClose(entity, bui.UiKey, session, ui); - } - } - } - - private void HandleOpenBackpack(ICommonSession? session) - { - HandleOpenSlotUI(session, "back"); - } - - private void HandleOpenBelt(ICommonSession? session) - { - HandleOpenSlotUI(session, "belt"); - } - - private void HandleOpenSlotUI(ICommonSession? session, string slot) - { - if (session is not { } playerSession) - return; - - if (playerSession.AttachedEntity is not {Valid: true} playerEnt || !Exists(playerEnt)) - return; - - if (!_inventory.TryGetSlotEntity(playerEnt, slot, out var storageEnt)) - return; - - if (!ActionBlocker.CanInteract(playerEnt, storageEnt)) - return; - - OpenStorageUI(storageEnt.Value, playerEnt); - } } diff --git a/Content.Server/Store/Systems/StoreSystem.Refund.cs b/Content.Server/Store/Systems/StoreSystem.Refund.cs index d59ee75e3e..5a8be4be2b 100644 --- a/Content.Server/Store/Systems/StoreSystem.Refund.cs +++ b/Content.Server/Store/Systems/StoreSystem.Refund.cs @@ -1,4 +1,4 @@ -using Content.Server.Store.Components; +using Content.Server.Store.Components; using Robust.Shared.Containers; namespace Content.Server.Store.Systems; diff --git a/Content.Server/Store/Systems/StoreSystem.Ui.cs b/Content.Server/Store/Systems/StoreSystem.Ui.cs index 25f64ba4b6..0d2f3af91b 100644 --- a/Content.Server/Store/Systems/StoreSystem.Ui.cs +++ b/Content.Server/Store/Systems/StoreSystem.Ui.cs @@ -73,7 +73,7 @@ public void CloseUi(EntityUid uid, StoreComponent? component = null) if (!Resolve(uid, ref component)) return; - _ui.TryCloseAll(uid, StoreUiKey.Key); + _ui.CloseUi(uid, StoreUiKey.Key); } /// @@ -83,12 +83,13 @@ public void CloseUi(EntityUid uid, StoreComponent? component = null) /// The store entity itself /// The store component being refreshed. /// - public void UpdateUserInterface(EntityUid? user, EntityUid store, StoreComponent? component = null, PlayerBoundUserInterface? ui = null) + public void UpdateUserInterface(EntityUid? user, EntityUid store, StoreComponent? component = null) { if (!Resolve(store, ref component)) return; - if (ui == null && !_ui.TryGetUi(store, StoreUiKey.Key, out ui)) + // TODO: Why is the state not being set unless this? + if (!_ui.HasUi(store, StoreUiKey.Key)) return; //this is the person who will be passed into logic for all listing filtering. @@ -113,12 +114,12 @@ public void UpdateUserInterface(EntityUid? user, EntityUid store, StoreComponent // only tell operatives to lock their uplink if it can be locked var showFooter = HasComp(store); var state = new StoreUpdateState(component.LastAvailableListings, allCurrency, showFooter, component.RefundAllowed); - _ui.SetUiState(ui, state); + _ui.SetUiState(store, StoreUiKey.Key, state); } private void OnRequestUpdate(EntityUid uid, StoreComponent component, StoreRequestUpdateInterfaceMessage args) { - UpdateUserInterface(args.Session.AttachedEntity, GetEntity(args.Entity), component); + UpdateUserInterface(args.Actor, GetEntity(args.Entity), component); } private void BeforeActivatableUiOpen(EntityUid uid, StoreComponent component, BeforeActivatableUIOpenEvent args) @@ -139,8 +140,7 @@ private void OnBuyRequest(EntityUid uid, StoreComponent component, StoreBuyListi return; } - if (msg.Session.AttachedEntity is not { Valid: true } buyer) - return; + var buyer = msg.Actor; //verify that we can actually buy this listing and it wasn't added if (!ListingHasCategory(listing, component.Categories)) @@ -215,11 +215,11 @@ private void OnBuyRequest(EntityUid uid, StoreComponent component, StoreBuyListi { HandleRefundComp(uid, component, actionId.Value); - if (listing.ProductUpgradeID != null) + if (listing.ProductUpgradeId != null) { foreach (var upgradeListing in component.Listings) { - if (upgradeListing.ID == listing.ProductUpgradeID) + if (upgradeListing.ID == listing.ProductUpgradeId) { upgradeListing.ProductActionEntity = actionId.Value; break; @@ -229,7 +229,7 @@ private void OnBuyRequest(EntityUid uid, StoreComponent component, StoreBuyListi } } - if (listing is { ProductUpgradeID: not null, ProductActionEntity: not null }) + if (listing is { ProductUpgradeId: not null, ProductActionEntity: not null }) { if (listing.ProductActionEntity != null) { @@ -263,7 +263,13 @@ private void OnBuyRequest(EntityUid uid, StoreComponent component, StoreBuyListi $"{ToPrettyString(buyer):player} purchased listing \"{ListingLocalisationHelpers.GetLocalisedNameOrEntityName(listing, _prototypeManager)}\" from {ToPrettyString(uid)}"); listing.PurchaseAmount++; //track how many times something has been purchased - _audio.PlayEntity(component.BuySuccessSound, msg.Session, uid); //cha-ching! + _audio.PlayEntity(component.BuySuccessSound, msg.Actor, uid); //cha-ching! + + if (listing.SaleLimit != 0 && listing.DiscountValue > 0 && listing.PurchaseAmount >= listing.SaleLimit) + { + listing.DiscountValue = 0; + listing.Cost = listing.OldCost; + } UpdateUserInterface(buyer, uid, component); } @@ -289,8 +295,7 @@ private void OnRequestWithdraw(EntityUid uid, StoreComponent component, StoreReq if (proto.Cash == null || !proto.CanWithdraw) return; - if (msg.Session.AttachedEntity is not { Valid: true } buyer) - return; + var buyer = msg.Actor; FixedPoint2 amountRemaining = msg.Amount; var coordinates = Transform(buyer).Coordinates; @@ -313,7 +318,7 @@ private void OnRequestRefund(EntityUid uid, StoreComponent component, StoreReque { // TODO: Remove guardian/holopara - if (args.Session.AttachedEntity is not { Valid: true } buyer) + if (args.Actor is not { Valid: true } buyer) return; if (!IsOnStartingMap(uid, component)) diff --git a/Content.Server/Store/Systems/StoreSystem.cs b/Content.Server/Store/Systems/StoreSystem.cs index 72aeb29d19..978d98ac0c 100644 --- a/Content.Server/Store/Systems/StoreSystem.cs +++ b/Content.Server/Store/Systems/StoreSystem.cs @@ -10,6 +10,7 @@ using Robust.Server.GameObjects; using Robust.Shared.Prototypes; using System.Linq; +using Content.Server.StoreDiscount; using Robust.Shared.Utility; namespace Content.Server.Store.Systems; @@ -22,6 +23,7 @@ public sealed partial class StoreSystem : EntitySystem { [Dependency] private readonly IPrototypeManager _proto = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly StoreDiscountSystem _storeDiscount = default!; public override void Initialize() { @@ -100,7 +102,7 @@ private void OnAfterInteract(EntityUid uid, CurrencyComponent component, AfterIn if (args.Handled) { var msg = Loc.GetString("store-currency-inserted", ("used", args.Used), ("target", args.Target)); - _popup.PopupEntity(msg, args.Target.Value); + _popup.PopupEntity(msg, args.Target.Value, args.User); QueueDel(args.Used); } } @@ -199,11 +201,10 @@ public void InitializeFromPreset(StorePresetPrototype preset, EntityUid uid, Sto if (component.Balance == new Dictionary() && preset.InitialBalance != null) //if we don't have a value stored, use the preset TryAddCurrency(preset.InitialBalance, uid, component); - var ui = _ui.GetUiOrNull(uid, StoreUiKey.Key); - if (ui != null) - { - _ui.SetUiState(ui, new StoreInitializeState(preset.StoreName)); - } + _storeDiscount.ApplyDiscounts(component.Listings, preset); + + if (_ui.HasUi(uid, StoreUiKey.Key)) + _ui.SetUiState(uid, StoreUiKey.Key, new StoreInitializeState(preset.StoreName)); } } @@ -225,7 +226,7 @@ public CurrencyInsertAttemptEvent(EntityUid user, EntityUid target, EntityUid us /// -/// Nyano/DeltaV Code. For penguin bombs and what not. +/// Nyano/DeltaV Code. For penguin bombs and what not. /// Raised on an item when it is purchased. /// An item may need to set it upself up for its purchaser. /// For example, to make sure it isn't hostile to them or diff --git a/Content.Server/StoreDiscount/StoreDiscountSystem.cs b/Content.Server/StoreDiscount/StoreDiscountSystem.cs new file mode 100644 index 0000000000..8f77eba780 --- /dev/null +++ b/Content.Server/StoreDiscount/StoreDiscountSystem.cs @@ -0,0 +1,55 @@ +using System.Linq; +using Content.Shared.FixedPoint; +using Content.Shared.Store; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; + +namespace Content.Server.StoreDiscount; + +public sealed class StoreDiscountSystem : EntitySystem +{ + [Dependency] private readonly IRobustRandom _random = default!; + + public void ApplyDiscounts(IEnumerable listings, StorePresetPrototype store) + { + if (!store.Sales.Enabled) + return; + + var count = _random.Next(store.Sales.MinItems, store.Sales.MaxItems + 1); + + listings = listings + .Where(l => + !l.SaleBlacklist + && l.Cost.Any(x => x.Value > 1) + && store.Categories.Overlaps(ChangedFormatCategories(l.Categories))) + .OrderBy(_ => _random.Next()).Take(count).ToList(); + + foreach (var listing in listings) + { + var sale = GetDiscount(store.Sales.MinMultiplier, store.Sales.MaxMultiplier); + var newCost = listing.Cost.ToDictionary(x => x.Key, + x => FixedPoint2.New(Math.Max(1, (int) MathF.Round(x.Value.Float() * sale)))); + + if (listing.Cost.All(x => x.Value.Int() == newCost[x.Key].Int())) + continue; + + var key = listing.Cost.First(x => x.Value > 0).Key; + listing.OldCost = listing.Cost; + listing.DiscountValue = 100 - (newCost[key] / listing.Cost[key] * 100).Int(); + listing.Cost = newCost; + listing.Categories = new() {store.Sales.SalesCategory}; + } + } + + private IEnumerable ChangedFormatCategories(List> categories) + { + var modified = from p in categories select p.Id; + + return modified; + } + + private float GetDiscount(float minMultiplier, float maxMultiplier) + { + return _random.NextFloat() * (maxMultiplier - minMultiplier) + minMultiplier; + } +} diff --git a/Content.Server/Strip/StrippableSystem.cs b/Content.Server/Strip/StrippableSystem.cs index d9f084252a..73f2ce17a6 100644 --- a/Content.Server/Strip/StrippableSystem.cs +++ b/Content.Server/Strip/StrippableSystem.cs @@ -112,17 +112,15 @@ public override void StartOpeningStripper(EntityUid user, Entity(user, out var mode) && mode.IsInCombatMode && !openInCombat) return; - if (TryComp(user, out var actor) && HasComp(user)) + if (HasComp(user)) { - if (_userInterfaceSystem.SessionHasOpenUi(strippable, StrippingUiKey.Key, actor.PlayerSession)) - return; - _userInterfaceSystem.TryOpen(strippable, StrippingUiKey.Key, actor.PlayerSession); + _userInterfaceSystem.OpenUi(strippable.Owner, StrippingUiKey.Key, user); } } private void OnStripButtonPressed(Entity strippable, ref StrippingSlotButtonPressed args) { - if (args.Session.AttachedEntity is not { Valid: true } user || + if (args.Actor is not { Valid: true } user || !TryComp(user, out var userHands)) return; @@ -174,7 +172,7 @@ private void StripHand( private void OnStripEnsnareMessage(EntityUid uid, EnsnareableComponent component, StrippingEnsnareButtonPressed args) { - if (args.Session.AttachedEntity is not { Valid: true } user) + if (args.Actor is not { Valid: true } user) return; foreach (var entity in component.Container.ContainedEntities) diff --git a/Content.Server/Stunnable/Components/KnockdownOnHitComponent.cs b/Content.Server/Stunnable/Components/KnockdownOnHitComponent.cs new file mode 100644 index 0000000000..b89e674ece --- /dev/null +++ b/Content.Server/Stunnable/Components/KnockdownOnHitComponent.cs @@ -0,0 +1,16 @@ +using Content.Shared.Standing; + +namespace Content.Server.Stunnable.Components; + +[RegisterComponent] +public sealed partial class KnockdownOnHitComponent : Component +{ + [DataField] + public TimeSpan Duration = TimeSpan.FromSeconds(1); + + [DataField] + public DropHeldItemsBehavior DropHeldItemsBehavior = DropHeldItemsBehavior.NoDrop; + + [DataField] + public bool RefreshDuration = true; +} diff --git a/Content.Server/Stunnable/Systems/KnockdownOnHitSystem.cs b/Content.Server/Stunnable/Systems/KnockdownOnHitSystem.cs new file mode 100644 index 0000000000..d1300c54f0 --- /dev/null +++ b/Content.Server/Stunnable/Systems/KnockdownOnHitSystem.cs @@ -0,0 +1,40 @@ +using System.Linq; +using Content.Server.Stunnable.Components; +using Content.Shared.StatusEffect; +using Content.Shared.Stunnable.Events; +using Content.Shared.Weapons.Melee.Events; + +namespace Content.Server.Stunnable.Systems; + +public sealed class KnockdownOnHitSystem : EntitySystem +{ + [Dependency] private readonly StunSystem _stun = default!; + + public override void Initialize() + { + SubscribeLocalEvent(OnMeleeHit); + } + + private void OnMeleeHit(Entity entity, ref MeleeHitEvent args) + { + if (args.Direction.HasValue || !args.IsHit || !args.HitEntities.Any() || entity.Comp.Duration <= TimeSpan.Zero) + return; + + var ev = new KnockdownOnHitAttemptEvent(); + RaiseLocalEvent(entity, ref ev); + if (ev.Cancelled) + return; + + foreach (var target in args.HitEntities) + { + if (!TryComp(target, out StatusEffectsComponent? statusEffects)) + continue; + + _stun.TryKnockdown(target, + entity.Comp.Duration, + entity.Comp.RefreshDuration, + entity.Comp.DropHeldItemsBehavior, + statusEffects); + } + } +} diff --git a/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraMonitorSystem.cs b/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraMonitorSystem.cs index f258fbe89c..ca0f59cd14 100644 --- a/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraMonitorSystem.cs +++ b/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraMonitorSystem.cs @@ -90,7 +90,7 @@ private void OnComponentStartup(EntityUid uid, SurveillanceCameraMonitorComponen private void OnSubnetRequest(EntityUid uid, SurveillanceCameraMonitorComponent component, SurveillanceCameraMonitorSubnetRequestMessage args) { - if (args.Session.AttachedEntity != null) + if (args.Actor != null) { SetActiveSubnet(uid, args.Subnet, component); } @@ -208,13 +208,9 @@ private void OnSurveillanceCameraDeactivate(EntityUid uid, SurveillanceCameraMon private void OnBoundUiClose(EntityUid uid, SurveillanceCameraMonitorComponent component, BoundUIClosedEvent args) { - if (args.Session.AttachedEntity == null) - { - return; - } - - RemoveViewer(uid, args.Session.AttachedEntity.Value, component); + RemoveViewer(uid, args.Actor, component); } + #endregion private void SendHeartbeat(EntityUid uid, SurveillanceCameraMonitorComponent? monitor = null) @@ -487,6 +483,6 @@ private void UpdateUserInterface(EntityUid uid, SurveillanceCameraMonitorCompone } var state = new SurveillanceCameraMonitorUiState(GetNetEntity(monitor.ActiveCamera), monitor.KnownSubnets.Keys.ToHashSet(), monitor.ActiveCameraAddress, monitor.ActiveSubnet, monitor.KnownCameras); - _userInterface.TrySetUiState(uid, SurveillanceCameraMonitorUiKey.Key, state); + _userInterface.SetUiState(uid, SurveillanceCameraMonitorUiKey.Key, state); } } diff --git a/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraRouterSystem.cs b/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraRouterSystem.cs index ac41723026..d0c2cd78d3 100644 --- a/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraRouterSystem.cs +++ b/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraRouterSystem.cs @@ -134,14 +134,14 @@ private void OnSetNetwork(EntityUid uid, SurveillanceCameraRouterComponent compo UpdateSetupInterface(uid, component); } - private void OpenSetupInterface(EntityUid uid, EntityUid player, SurveillanceCameraRouterComponent? camera = null, ActorComponent? actor = null) + private void OpenSetupInterface(EntityUid uid, EntityUid player, SurveillanceCameraRouterComponent? camera = null) { - if (!Resolve(uid, ref camera) || !Resolve(player, ref actor)) + if (!Resolve(uid, ref camera)) return; - if (!_userInterface.TryGetUi(uid, SurveillanceCameraSetupUiKey.Router, out var bui)) + + if (!_userInterface.TryOpenUi(uid, SurveillanceCameraSetupUiKey.Router, player)) return; - _userInterface.OpenUi(bui, actor.PlayerSession); UpdateSetupInterface(uid, camera); } @@ -154,13 +154,13 @@ private void UpdateSetupInterface(EntityUid uid, SurveillanceCameraRouterCompone if (router.AvailableNetworks.Count == 0 || router.SubnetFrequencyId != null) { - _userInterface.TryCloseAll(uid, SurveillanceCameraSetupUiKey.Router); + _userInterface.CloseUi(uid, SurveillanceCameraSetupUiKey.Router); return; } var state = new SurveillanceCameraSetupBoundUiState(router.SubnetName, deviceNet.ReceiveFrequency ?? 0, router.AvailableNetworks, true, router.SubnetFrequencyId != null); - _userInterface.TrySetUiState(uid, SurveillanceCameraSetupUiKey.Router, state); + _userInterface.SetUiState(uid, SurveillanceCameraSetupUiKey.Router, state); } private void SendHeartbeat(EntityUid uid, string origin, string destination, diff --git a/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraSpeakerSystem.cs b/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraSpeakerSystem.cs index 206c0800f0..22af959fbd 100644 --- a/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraSpeakerSystem.cs +++ b/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraSpeakerSystem.cs @@ -45,11 +45,11 @@ private void OnSpeechSent(EntityUid uid, SurveillanceCameraSpeakerComponent comp component.LastSoundPlayed = time; } - var nameEv = new TransformSpeakerNameEvent(args.Speaker, Name(args.Speaker)); + var nameEv = new TransformSpeakerSpeechEvent(args.Speaker, Name(args.Speaker)); RaiseLocalEvent(args.Speaker, nameEv); var name = Loc.GetString("speech-name-relay", ("speaker", Name(uid)), - ("originalName", nameEv.Name)); + ("originalName", nameEv.VoiceName ?? Name(uid))); // log to chat so people can identity the speaker/source, but avoid clogging ghost chat if there are many radios _chatSystem.TrySendInGameICMessage(uid, args.Message, InGameICChatType.Speak, ChatTransmitRange.GhostRangeLimit, nameOverride: name); diff --git a/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraSystem.cs b/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraSystem.cs index ec3d33157a..accaa75d1c 100644 --- a/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraSystem.cs +++ b/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraSystem.cs @@ -192,14 +192,14 @@ private void OnSetNetwork(EntityUid uid, SurveillanceCameraComponent component, UpdateSetupInterface(uid, component); } - private void OpenSetupInterface(EntityUid uid, EntityUid player, SurveillanceCameraComponent? camera = null, ActorComponent? actor = null) + private void OpenSetupInterface(EntityUid uid, EntityUid player, SurveillanceCameraComponent? camera = null) { - if (!Resolve(uid, ref camera) || !Resolve(player, ref actor)) + if (!Resolve(uid, ref camera)) return; - if (!_userInterface.TryGetUi(uid, SurveillanceCameraSetupUiKey.Camera, out var bui)) + + if (!_userInterface.TryOpenUi(uid, SurveillanceCameraSetupUiKey.Camera, player)) return; - _userInterface.OpenUi(bui, actor.PlayerSession); UpdateSetupInterface(uid, camera); } @@ -212,7 +212,7 @@ private void UpdateSetupInterface(EntityUid uid, SurveillanceCameraComponent? ca if (camera.NameSet && camera.NetworkSet) { - _userInterface.TryCloseAll(uid, SurveillanceCameraSetupUiKey.Camera); + _userInterface.CloseUi(uid, SurveillanceCameraSetupUiKey.Camera); return; } @@ -224,14 +224,14 @@ private void UpdateSetupInterface(EntityUid uid, SurveillanceCameraComponent? ca } else if (!camera.NetworkSet) { - _userInterface.TryCloseAll(uid, SurveillanceCameraSetupUiKey.Camera); + _userInterface.CloseUi(uid, SurveillanceCameraSetupUiKey.Camera); return; } } var state = new SurveillanceCameraSetupBoundUiState(camera.CameraId, deviceNet.ReceiveFrequency ?? 0, camera.AvailableNetworks, camera.NameSet, camera.NetworkSet); - _userInterface.TrySetUiState(uid, SurveillanceCameraSetupUiKey.Camera, state); + _userInterface.SetUiState(uid, SurveillanceCameraSetupUiKey.Camera, state); } // If the camera deactivates for any reason, it must have all viewers removed, diff --git a/Content.Server/Targeting/TargetingSystem.cs b/Content.Server/Targeting/TargetingSystem.cs new file mode 100644 index 0000000000..3fc8ea5964 --- /dev/null +++ b/Content.Server/Targeting/TargetingSystem.cs @@ -0,0 +1,54 @@ +using Content.Shared.Body.Systems; +using Content.Shared.Mobs; +using Content.Shared.Targeting; +using Content.Shared.Targeting.Events; + +namespace Content.Server.Targeting; +public sealed class TargetingSystem : SharedTargetingSystem +{ + [Dependency] private readonly SharedBodySystem _bodySystem = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeNetworkEvent(OnTargetChange); + SubscribeLocalEvent(OnMobStateChange); + } + + private void OnTargetChange(TargetChangeEvent message, EntitySessionEventArgs args) + { + if (!TryComp(GetEntity(message.Uid), out var target)) + return; + + target.Target = message.BodyPart; + Dirty(GetEntity(message.Uid), target); + } + + private void OnMobStateChange(EntityUid uid, TargetingComponent component, MobStateChangedEvent args) + { + // Revival is handled by the server, so we're keeping all of this here. + var changed = false; + + if (args.NewMobState == MobState.Dead) + { + foreach (var part in GetValidParts()) + { + component.BodyStatus[part] = TargetIntegrity.Dead; + changed = true; + } + // I love groin shitcode. + component.BodyStatus[TargetBodyPart.Groin] = TargetIntegrity.Dead; + } + else if (args.OldMobState == MobState.Dead && (args.NewMobState == MobState.Alive || args.NewMobState == MobState.Critical)) + { + component.BodyStatus = _bodySystem.GetBodyPartStatus(uid); + changed = true; + } + + if (changed) + { + Dirty(uid, component); + RaiseNetworkEvent(new TargetIntegrityChangeEvent(GetNetEntity(uid)), uid); + } + } +} diff --git a/Content.Server/Telescope/TelescopeSystem.cs b/Content.Server/Telescope/TelescopeSystem.cs new file mode 100644 index 0000000000..0e53cc15a2 --- /dev/null +++ b/Content.Server/Telescope/TelescopeSystem.cs @@ -0,0 +1,5 @@ +using Content.Shared.Telescope; + +namespace Content.Server.Telescope; + +public sealed class TelescopeSystem : SharedTelescopeSystem; diff --git a/Content.Server/TelescopicBaton/TelescopicBatonComponent.cs b/Content.Server/TelescopicBaton/TelescopicBatonComponent.cs new file mode 100644 index 0000000000..1846c76c75 --- /dev/null +++ b/Content.Server/TelescopicBaton/TelescopicBatonComponent.cs @@ -0,0 +1,17 @@ +namespace Content.Server.TelescopicBaton; + +[RegisterComponent] +public sealed partial class TelescopicBatonComponent : Component +{ + [DataField] + public bool CanKnockDown; + + /// + /// The amount of time during which the baton will be able to knockdown someone after activating it. + /// + [DataField] + public TimeSpan AttackTimeframe = TimeSpan.FromSeconds(1.5f); + + [ViewVariables(VVAccess.ReadOnly)] + public TimeSpan TimeframeAccumulator = TimeSpan.FromSeconds(0); +} diff --git a/Content.Server/TelescopicBaton/TelescopicBatonSystem.cs b/Content.Server/TelescopicBaton/TelescopicBatonSystem.cs new file mode 100644 index 0000000000..a859b44750 --- /dev/null +++ b/Content.Server/TelescopicBaton/TelescopicBatonSystem.cs @@ -0,0 +1,67 @@ +using Content.Shared.Item.ItemToggle.Components; +using Content.Shared.Stunnable.Events; +using Content.Shared.TelescopicBaton; +using Robust.Server.GameObjects; + +namespace Content.Server.TelescopicBaton; + +public sealed class TelescopicBatonSystem : EntitySystem +{ + [Dependency] private readonly AppearanceSystem _appearance = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnToggled); + SubscribeLocalEvent(OnKnockdownAttempt); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var baton)) + { + if (!baton.CanKnockDown) + continue; + + baton.TimeframeAccumulator += TimeSpan.FromSeconds(frameTime); + if (baton.TimeframeAccumulator <= baton.AttackTimeframe) + continue; + + baton.CanKnockDown = false; // Only disable knockdown + baton.TimeframeAccumulator = TimeSpan.Zero; + } + } + + private void OnMapInit(Entity baton, ref MapInitEvent args) + { + ToggleBaton(baton, false); + } + + private void OnToggled(Entity baton, ref ItemToggledEvent args) + { + ToggleBaton(baton, args.Activated); + } + + private void OnKnockdownAttempt(Entity baton, ref KnockdownOnHitAttemptEvent args) + { + if (!baton.Comp.CanKnockDown) + { + args.Cancelled = true; + return; + } + + baton.Comp.CanKnockDown = false; + } + + public void ToggleBaton(Entity baton, bool state) + { + baton.Comp.TimeframeAccumulator = TimeSpan.Zero; + baton.Comp.CanKnockDown = state; + _appearance.SetData(baton, TelescopicBatonVisuals.State, state); + } +} diff --git a/Content.Server/Terminator/Components/TerminatorComponent.cs b/Content.Server/Terminator/Components/TerminatorComponent.cs deleted file mode 100644 index 9427f95eed..0000000000 --- a/Content.Server/Terminator/Components/TerminatorComponent.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Content.Server.Terminator.Systems; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; - -namespace Content.Server.Terminator.Components; - -/// -/// Main terminator component, handles the target, if any, and objectives. -/// -[RegisterComponent, Access(typeof(TerminatorSystem))] -public sealed partial class TerminatorComponent : Component -{ - /// - /// Used to force the terminate objective's target. - /// If null it will be a random person. - /// - [DataField("target")] - public EntityUid? Target; -} diff --git a/Content.Server/Terminator/Components/TerminatorTargetComponent.cs b/Content.Server/Terminator/Components/TerminatorTargetComponent.cs deleted file mode 100644 index 786cbd1167..0000000000 --- a/Content.Server/Terminator/Components/TerminatorTargetComponent.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Content.Server.Terminator.Systems; - -namespace Content.Server.Terminator.Components; - -/// -/// Sets after the ghost role spawns. -/// -[RegisterComponent, Access(typeof(TerminatorSystem))] -public sealed partial class TerminatorTargetComponent : Component -{ - /// - /// The target to set after the ghost role spawns. - /// - [DataField("target")] - public EntityUid? Target; -} diff --git a/Content.Server/Terminator/Systems/TerminatorSystem.cs b/Content.Server/Terminator/Systems/TerminatorSystem.cs deleted file mode 100644 index 837778d3c4..0000000000 --- a/Content.Server/Terminator/Systems/TerminatorSystem.cs +++ /dev/null @@ -1,75 +0,0 @@ -using Content.Server.Body.Components; -using Content.Server.GenericAntag; -using Content.Server.Ghost.Roles.Events; -using Content.Server.Roles; -using Content.Server.Terminator.Components; -using Content.Shared.Roles; -using Robust.Shared.Map; - -namespace Content.Server.Terminator.Systems; - -public sealed class TerminatorSystem : EntitySystem -{ - [Dependency] private readonly SharedRoleSystem _role = default!; - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnMapInit); - SubscribeLocalEvent(OnSpawned); - SubscribeLocalEvent(OnCreated); - } - - private void OnMapInit(EntityUid uid, TerminatorComponent comp, MapInitEvent args) - { - // cyborg doesn't need to breathe - //RemComp(uid); // DeltaV - paradox anomaly does actually need to breathe - } - - private void OnSpawned(EntityUid uid, TerminatorComponent comp, GhostRoleSpawnerUsedEvent args) - { - if (!TryComp(args.Spawner, out var target)) - return; - - comp.Target = target.Target; - } - - private void OnCreated(EntityUid uid, TerminatorComponent comp, ref GenericAntagCreatedEvent args) - { - var mindId = args.MindId; - var mind = args.Mind; - - _role.MindAddRole(mindId, new RoleBriefingComponent - { - Briefing = Loc.GetString("terminator-role-briefing") - }, mind); - _role.MindAddRole(mindId, new TerminatorRoleComponent(), mind); - } - - /// - /// DeltaV - used for paradox anomaly. - /// - public void SetTarget(Entity ent, EntityUid mindId) - { - ent.Comp ??= EnsureComp(ent); - ent.Comp.Target = mindId; - } - - /// - /// Create a spawner at a position and return it. - /// - /// Coordinates to create the spawner at - /// Optional target mind to force the terminator to target - public EntityUid CreateSpawner(EntityCoordinates coords, EntityUid? target) - { - var uid = Spawn("SpawnPointGhostTerminator", coords); - if (target != null) - { - var comp = EnsureComp(uid); - comp.Target = target; - } - - return uid; - } -} diff --git a/Content.Server/Thief/Systems/ThiefUndeterminedBackpackSystem.cs b/Content.Server/Thief/Systems/ThiefUndeterminedBackpackSystem.cs index 9918565328..133876bd75 100644 --- a/Content.Server/Thief/Systems/ThiefUndeterminedBackpackSystem.cs +++ b/Content.Server/Thief/Systems/ThiefUndeterminedBackpackSystem.cs @@ -79,6 +79,6 @@ private void UpdateUI(EntityUid uid, ThiefUndeterminedBackpackComponent? compone data.Add(i, info); } - _ui.TrySetUiState(uid, ThiefBackpackUIKey.Key, new ThiefBackpackBoundUserInterfaceState(data, MaxSelectedSets)); + _ui.SetUiState(uid, ThiefBackpackUIKey.Key, new ThiefBackpackBoundUserInterfaceState(data, MaxSelectedSets)); } } diff --git a/Content.Server/Tips/TipsSystem.cs b/Content.Server/Tips/TipsSystem.cs index cc45a3a1d5..168f11de8f 100644 --- a/Content.Server/Tips/TipsSystem.cs +++ b/Content.Server/Tips/TipsSystem.cs @@ -1,9 +1,13 @@ -using Content.Server.Chat.Managers; +using Content.Server.Chat.Managers; using Content.Server.GameTicking; using Content.Shared.CCVar; using Content.Shared.Chat; using Content.Shared.Dataset; +using Content.Shared.Tips; +using Robust.Server.GameObjects; +using Robust.Server.Player; using Robust.Shared.Configuration; +using Robust.Shared.Console; using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.Shared.Random; @@ -22,11 +26,14 @@ public sealed class TipsSystem : EntitySystem [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly GameTicker _ticker = default!; + [Dependency] private readonly IConsoleHost _conHost = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; private bool _tipsEnabled; private float _tipTimeOutOfRound; private float _tipTimeInRound; private string _tipsDataset = ""; + private float _tipTippyChance; [ViewVariables(VVAccess.ReadWrite)] private TimeSpan _nextTipTime = TimeSpan.Zero; @@ -40,10 +47,100 @@ public override void Initialize() Subs.CVar(_cfg, CCVars.TipFrequencyInRound, SetInRound, true); Subs.CVar(_cfg, CCVars.TipsEnabled, SetEnabled, true); Subs.CVar(_cfg, CCVars.TipsDataset, SetDataset, true); + Subs.CVar(_cfg, CCVars.TipsTippyChance, SetTippyChance, true); RecalculateNextTipTime(); + _conHost.RegisterCommand("tippy", Loc.GetString("cmd-tippy-desc"), Loc.GetString("cmd-tippy-help"), SendTippy, SendTippyHelper); + _conHost.RegisterCommand("tip", Loc.GetString("cmd-tip-desc"), "tip", SendTip); } + private CompletionResult SendTippyHelper(IConsoleShell shell, string[] args) + { + return args.Length switch + { + 1 => CompletionResult.FromHintOptions(CompletionHelper.SessionNames(), Loc.GetString("cmd-tippy-auto-1")), + 2 => CompletionResult.FromHint(Loc.GetString("cmd-tippy-auto-2")), + 3 => CompletionResult.FromHintOptions(CompletionHelper.PrototypeIDs(), Loc.GetString("cmd-tippy-auto-3")), + 4 => CompletionResult.FromHint(Loc.GetString("cmd-tippy-auto-4")), + 5 => CompletionResult.FromHint(Loc.GetString("cmd-tippy-auto-5")), + 6 => CompletionResult.FromHint(Loc.GetString("cmd-tippy-auto-6")), + _ => CompletionResult.Empty + }; + } + + private void SendTip(IConsoleShell shell, string argstr, string[] args) + { + AnnounceRandomTip(); + RecalculateNextTipTime(); + } + + private void SendTippy(IConsoleShell shell, string argstr, string[] args) + { + if (args.Length < 2) + { + shell.WriteLine(Loc.GetString("cmd-tippy-help")); + return; + } + + ActorComponent? actor = null; + if (args[0] != "all") + { + ICommonSession? session; + if (args.Length > 0) + { + // Get player entity + if (!_playerManager.TryGetSessionByUsername(args[0], out session)) + { + shell.WriteLine(Loc.GetString("cmd-tippy-error-no-user")); + return; + } + } + else + { + session = shell.Player; + } + + if (session?.AttachedEntity is not { } user) + { + shell.WriteLine(Loc.GetString("cmd-tippy-error-no-user")); + return; + } + + if (!TryComp(user, out actor)) + { + shell.WriteError(Loc.GetString("cmd-tippy-error-no-user")); + return; + } + } + + var ev = new TippyEvent(args[1]); + + if (args.Length > 2) + { + ev.Proto = args[2]; + if (!_prototype.HasIndex(args[2])) + { + shell.WriteError(Loc.GetString("cmd-tippy-error-no-prototype", ("proto", args[2]))); + return; + } + } + + if (args.Length > 3) + ev.SpeakTime = float.Parse(args[3]); + + if (args.Length > 4) + ev.SlideTime = float.Parse(args[4]); + + if (args.Length > 5) + ev.WaddleInterval = float.Parse(args[5]); + + if (actor != null) + RaiseNetworkEvent(ev, actor.PlayerSession); + else + RaiseNetworkEvent(ev); + } + + public override void Update(float frameTime) { base.Update(frameTime); @@ -81,6 +178,11 @@ private void SetDataset(string value) _tipsDataset = value; } + private void SetTippyChance(float value) + { + _tipTippyChance = value; + } + private void AnnounceRandomTip() { if (!_prototype.TryIndex(_tipsDataset, out var tips)) @@ -89,8 +191,16 @@ private void AnnounceRandomTip() var tip = _random.Pick(tips.Values); var msg = Loc.GetString("tips-system-chat-message-wrap", ("tip", tip)); - _chat.ChatMessageToManyFiltered(Filter.Broadcast(), ChatChannel.OOC, tip, msg, + if (_random.Prob(_tipTippyChance)) + { + var ev = new TippyEvent(msg); + ev.SpeakTime = 1 + tip.Length * 0.05f; + RaiseNetworkEvent(ev); + } else + { + _chat.ChatMessageToManyFiltered(Filter.Broadcast(), ChatChannel.OOC, tip, msg, EntityUid.Invalid, false, false, Color.MediumPurple); + } } private void RecalculateNextTipTime() diff --git a/Content.Server/Tools/Components/WelderComponent.cs b/Content.Server/Tools/Components/WelderComponent.cs deleted file mode 100644 index b0db2c58e8..0000000000 --- a/Content.Server/Tools/Components/WelderComponent.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Content.Shared.Chemistry.Components; -using Content.Shared.Chemistry.Reagent; -using Content.Shared.FixedPoint; -using Content.Shared.Tools.Components; -using Robust.Shared.Audio; -using Robust.Shared.Prototypes; - -namespace Content.Server.Tools.Components -{ - [RegisterComponent] - public sealed partial class WelderComponent : SharedWelderComponent - { - /// - /// Name of . - /// - [DataField("fuelSolution"), ViewVariables(VVAccess.ReadWrite)] - public string FuelSolutionName = "Welder"; - - /// - /// Solution on the entity that contains the fuel. - /// - [DataField("fuelSolutionRef")] - public Entity? FuelSolution = null; - - /// - /// Reagent that will be used as fuel for welding. - /// - [DataField, ViewVariables(VVAccess.ReadWrite)] - public ProtoId FuelReagent = "WeldingFuel"; - - /// - /// Fuel consumption per second while the welder is active. - /// - [DataField, ViewVariables(VVAccess.ReadWrite)] - public FixedPoint2 FuelConsumption = FixedPoint2.New(2.0f); - - /// - /// A fuel amount to be consumed when the welder goes from being unlit to being lit. - /// - [DataField, ViewVariables(VVAccess.ReadWrite)] - public FixedPoint2 FuelLitCost = FixedPoint2.New(0.5f); - - /// - /// Sound played when refilling the welder. - /// - [DataField] - public SoundSpecifier WelderRefill = new SoundPathSpecifier("/Audio/Effects/refill.ogg"); - - /// - /// Whether the item is safe to refill while lit without exploding the tank. - /// - [DataField] - public bool TankSafe = false; //I have no idea what I'm doing - - } -} diff --git a/Content.Server/Tools/ToolSystem.Welder.cs b/Content.Server/Tools/ToolSystem.Welder.cs deleted file mode 100644 index 727526b398..0000000000 --- a/Content.Server/Tools/ToolSystem.Welder.cs +++ /dev/null @@ -1,211 +0,0 @@ -using Content.Server.Chemistry.Components; -using Content.Server.IgnitionSource; -using Content.Server.Tools.Components; -using Content.Shared.Chemistry.Components.SolutionManager; -using Content.Shared.Database; -using Content.Shared.DoAfter; -using Content.Shared.Examine; -using Content.Shared.FixedPoint; -using Content.Shared.Interaction; -using Content.Shared.Item.ItemToggle; -using Content.Shared.Tools.Components; -using Robust.Shared.GameStates; -using System.Linq; -using Content.Shared.Item.ItemToggle.Components; - -namespace Content.Server.Tools -{ - public sealed partial class ToolSystem - { - [Dependency] private readonly SharedItemToggleSystem _itemToggle = default!; - [Dependency] private readonly IgnitionSourceSystem _ignitionSource = default!; - private readonly HashSet _activeWelders = new(); - - private const float WelderUpdateTimer = 1f; - private float _welderTimer; - - public void InitializeWelders() - { - SubscribeLocalEvent(OnWelderExamine); - SubscribeLocalEvent(OnWelderAfterInteract); - SubscribeLocalEvent>(OnWelderToolUseAttempt); - SubscribeLocalEvent(OnWelderShutdown); - SubscribeLocalEvent(OnWelderGetState); - SubscribeLocalEvent(OnToggle); - SubscribeLocalEvent(OnActivateAttempt); - } - - public (FixedPoint2 fuel, FixedPoint2 capacity) GetWelderFuelAndCapacity(EntityUid uid, WelderComponent? welder = null, SolutionContainerManagerComponent? solutionContainer = null) - { - if (!Resolve(uid, ref welder, ref solutionContainer) - || !_solutionContainer.ResolveSolution((uid, solutionContainer), welder.FuelSolutionName, ref welder.FuelSolution, out var fuelSolution)) - return (FixedPoint2.Zero, FixedPoint2.Zero); - - return (fuelSolution.GetTotalPrototypeQuantity(welder.FuelReagent), fuelSolution.MaxVolume); - } - - private void OnToggle(Entity entity, ref ItemToggledEvent args) - { - if (args.Activated) - TurnOn(entity, args.User); - else - TurnOff(entity, args.User); - } - - private void OnActivateAttempt(Entity entity, ref ItemToggleActivateAttemptEvent args) - { - if (!_solutionContainer.ResolveSolution(entity.Owner, entity.Comp.FuelSolutionName, ref entity.Comp.FuelSolution, out var solution)) - { - args.Cancelled = true; - args.Popup = Loc.GetString("welder-component-no-fuel-message"); - return; - } - - var fuel = solution.GetTotalPrototypeQuantity(entity.Comp.FuelReagent); - if (fuel == FixedPoint2.Zero || fuel < entity.Comp.FuelLitCost) - { - args.Popup = Loc.GetString("welder-component-no-fuel-message"); - args.Cancelled = true; - } - } - - public void TurnOn(Entity entity, EntityUid? user) - { - if (!_solutionContainer.ResolveSolution(entity.Owner, entity.Comp.FuelSolutionName, ref entity.Comp.FuelSolution)) - return; - - _solutionContainer.RemoveReagent(entity.Comp.FuelSolution.Value, entity.Comp.FuelReagent, entity.Comp.FuelLitCost); - AdminLogger.Add(LogType.InteractActivate, LogImpact.Low, - $"{ToPrettyString(user):user} toggled {ToPrettyString(entity.Owner):welder} on"); - - var xform = Transform(entity); - if (xform.GridUid is { } gridUid) - { - var position = _transformSystem.GetGridOrMapTilePosition(entity.Owner, xform); - _atmosphereSystem.HotspotExpose(gridUid, position, 700, 50, entity.Owner, true); - } - - _activeWelders.Add(entity); - } - - public void TurnOff(Entity entity, EntityUid? user) - { - AdminLogger.Add(LogType.InteractActivate, LogImpact.Low, - $"{ToPrettyString(user):user} toggled {ToPrettyString(entity.Owner):welder} off"); - _activeWelders.Remove(entity); - } - - private void OnWelderExamine(Entity entity, ref ExaminedEvent args) - { - using (args.PushGroup(nameof(WelderComponent))) - { - if (_itemToggle.IsActivated(entity.Owner)) - { - args.PushMarkup(Loc.GetString("welder-component-on-examine-welder-lit-message")); - } - else - { - args.PushMarkup(Loc.GetString("welder-component-on-examine-welder-not-lit-message")); - } - - if (args.IsInDetailsRange) - { - var (fuel, capacity) = GetWelderFuelAndCapacity(entity.Owner, entity.Comp); - - args.PushMarkup(Loc.GetString("welder-component-on-examine-detailed-message", - ("colorName", fuel < capacity / FixedPoint2.New(4f) ? "darkorange" : "orange"), - ("fuelLeft", fuel), - ("fuelCapacity", capacity), - ("status", string.Empty))); // Lit status is handled above - } - } - } - - private void OnWelderAfterInteract(Entity entity, ref AfterInteractEvent args) - { - if (args.Handled) - return; - - if (args.Target is not { Valid: true } target || !args.CanReach) - return; - - if (TryComp(target, out ReagentTankComponent? tank) - && tank.TankType == ReagentTankType.Fuel - && _solutionContainer.TryGetDrainableSolution(target, out var targetSoln, out var targetSolution) - && _solutionContainer.ResolveSolution(entity.Owner, entity.Comp.FuelSolutionName, ref entity.Comp.FuelSolution, out var welderSolution)) - { - var trans = FixedPoint2.Min(welderSolution.AvailableVolume, targetSolution.Volume); - if (trans > 0) - { - var drained = _solutionContainer.Drain(target, targetSoln.Value, trans); - _solutionContainer.TryAddSolution(entity.Comp.FuelSolution.Value, drained); - _audio.PlayPvs(entity.Comp.WelderRefill, entity); - _popup.PopupEntity(Loc.GetString("welder-component-after-interact-refueled-message"), entity, args.User); - } - else if (welderSolution.AvailableVolume <= 0) - { - _popup.PopupEntity(Loc.GetString("welder-component-already-full"), entity, args.User); - } - else - { - _popup.PopupEntity(Loc.GetString("welder-component-no-fuel-in-tank", ("owner", args.Target)), entity, args.User); - } - - args.Handled = true; - } - } - - private void OnWelderToolUseAttempt(Entity entity, ref DoAfterAttemptEvent args) - { - var user = args.DoAfter.Args.User; - - if (!_itemToggle.IsActivated(entity.Owner)) - { - _popup.PopupEntity(Loc.GetString("welder-component-welder-not-lit-message"), entity, user); - args.Cancel(); - } - } - - private void OnWelderShutdown(Entity entity, ref ComponentShutdown args) - { - _activeWelders.Remove(entity); - } - - private void OnWelderGetState(Entity entity, ref ComponentGetState args) - { - var (fuel, capacity) = GetWelderFuelAndCapacity(entity.Owner, entity.Comp); - args.State = new WelderComponentState(capacity.Float(), fuel.Float()); - } - - private void UpdateWelders(float frameTime) - { - _welderTimer += frameTime; - - if (_welderTimer < WelderUpdateTimer) - return; - - // TODO Serialization. _activeWelders is not serialized. - // Need to use some "active" component, and EntityQuery over that. - // Probably best to generalize it to a "ToggleableFuelDrain" component. - foreach (var tool in _activeWelders.ToArray()) - { - if (!TryComp(tool, out WelderComponent? welder) - || !TryComp(tool, out SolutionContainerManagerComponent? solutionContainer)) - continue; - - if (!_solutionContainer.ResolveSolution((tool, solutionContainer), welder.FuelSolutionName, ref welder.FuelSolution, out var solution)) - continue; - - _solutionContainer.RemoveReagent(welder.FuelSolution.Value, welder.FuelReagent, welder.FuelConsumption * _welderTimer); - - if (solution.GetTotalPrototypeQuantity(welder.FuelReagent) <= FixedPoint2.Zero) - { - _itemToggle.Toggle(tool, predicted: false); - } - - Dirty(tool, welder); - } - _welderTimer -= WelderUpdateTimer; - } - } -} diff --git a/Content.Server/Tools/ToolSystem.cs b/Content.Server/Tools/ToolSystem.cs index 7bae177892..7738a6398f 100644 --- a/Content.Server/Tools/ToolSystem.cs +++ b/Content.Server/Tools/ToolSystem.cs @@ -1,40 +1,63 @@ using Content.Server.Atmos.EntitySystems; -using Content.Server.Chemistry.Containers.EntitySystems; -using Content.Server.Popups; -using Content.Server.Tools.Components; +using Content.Shared.Chemistry.Components.SolutionManager; +using Content.Shared.FixedPoint; +using Content.Shared.Tools.Components; using Robust.Server.GameObjects; -using Robust.Shared.Audio.Systems; using SharedToolSystem = Content.Shared.Tools.Systems.SharedToolSystem; -namespace Content.Server.Tools +namespace Content.Server.Tools; + +public sealed class ToolSystem : SharedToolSystem { - // TODO move tool system to shared, and make it a friend of Tool Component. - public sealed partial class ToolSystem : SharedToolSystem - { - [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; - [Dependency] private readonly PopupSystem _popup = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly SolutionContainerSystem _solutionContainer = default!; - [Dependency] private readonly TransformSystem _transformSystem = default!; + [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; + [Dependency] private readonly TransformSystem _transformSystem = default!; - public override void Initialize() + public override void TurnOn(Entity entity, EntityUid? user) + { + base.TurnOn(entity, user); + var xform = Transform(entity); + if (xform.GridUid is { } gridUid) { - base.Initialize(); - - InitializeWelders(); + var position = _transformSystem.GetGridOrMapTilePosition(entity.Owner, xform); + _atmosphereSystem.HotspotExpose(gridUid, position, 700, 50, entity.Owner, true); } + } - public override void Update(float frameTime) - { - base.Update(frameTime); + public override void Update(float frameTime) + { + base.Update(frameTime); - UpdateWelders(frameTime); - } + UpdateWelders(frameTime); + } - protected override bool IsWelder(EntityUid uid) + //todo move to shared once you can remove reagents from shared without it freaking out. + private void UpdateWelders(float frameTime) + { + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var welder, out var solutionContainer)) { - return HasComp(uid); + if (!welder.Enabled) + continue; + + welder.WelderTimer += frameTime; + + if (welder.WelderTimer < welder.WelderUpdateTimer) + continue; + + if (!SolutionContainerSystem.TryGetSolution((uid, solutionContainer), welder.FuelSolutionName, out var solutionComp, out var solution)) + continue; + + SolutionContainerSystem.RemoveReagent(solutionComp.Value, welder.FuelReagent, welder.FuelConsumption * welder.WelderTimer); + + if (solution.GetTotalPrototypeQuantity(welder.FuelReagent) <= FixedPoint2.Zero) + { + ItemToggle.Toggle(uid, predicted: false); + } + + Dirty(uid, welder); + welder.WelderTimer -= welder.WelderUpdateTimer; } } } + diff --git a/Content.Server/Traitor/Components/AutoTraitorComponent.cs b/Content.Server/Traitor/Components/AutoTraitorComponent.cs index ab4bee2f26..473441ccec 100644 --- a/Content.Server/Traitor/Components/AutoTraitorComponent.cs +++ b/Content.Server/Traitor/Components/AutoTraitorComponent.cs @@ -11,12 +11,12 @@ public sealed partial class AutoTraitorComponent : Component /// /// Whether to give the traitor an uplink or not. /// - [DataField("giveUplink"), ViewVariables(VVAccess.ReadWrite)] + [DataField, ViewVariables(VVAccess.ReadWrite)] public bool GiveUplink = true; /// /// Whether to give the traitor objectives or not. /// - [DataField("giveObjectives"), ViewVariables(VVAccess.ReadWrite)] + [DataField, ViewVariables(VVAccess.ReadWrite)] public bool GiveObjectives = true; } diff --git a/Content.Server/Traitor/Systems/AutoTraitorSystem.cs b/Content.Server/Traitor/Systems/AutoTraitorSystem.cs index 15deae2552..e9307effbc 100644 --- a/Content.Server/Traitor/Systems/AutoTraitorSystem.cs +++ b/Content.Server/Traitor/Systems/AutoTraitorSystem.cs @@ -1,6 +1,7 @@ -using Content.Server.GameTicking.Rules; +using Content.Server.Antag; using Content.Server.Traitor.Components; using Content.Shared.Mind.Components; +using Robust.Shared.Prototypes; namespace Content.Server.Traitor.Systems; @@ -9,7 +10,10 @@ namespace Content.Server.Traitor.Systems; /// public sealed class AutoTraitorSystem : EntitySystem { - [Dependency] private readonly TraitorRuleSystem _traitorRule = default!; + [Dependency] private readonly AntagSelectionSystem _antag = default!; + + [ValidatePrototypeId] + private const string DefaultTraitorRule = "Traitor"; public override void Initialize() { @@ -20,44 +24,6 @@ public override void Initialize() private void OnMindAdded(EntityUid uid, AutoTraitorComponent comp, MindAddedMessage args) { - TryMakeTraitor(uid, comp); - } - - /// - /// Sets the GiveUplink field. - /// - public void SetGiveUplink(EntityUid uid, bool giveUplink, AutoTraitorComponent? comp = null) - { - if (!Resolve(uid, ref comp)) - return; - - comp.GiveUplink = giveUplink; - } - - /// - /// Sets the GiveObjectives field. - /// - public void SetGiveObjectives(EntityUid uid, bool giveObjectives, AutoTraitorComponent? comp = null) - { - if (!Resolve(uid, ref comp)) - return; - - comp.GiveObjectives = giveObjectives; - } - - /// - /// Checks if there is a mind, then makes it a traitor using the options. - /// - public bool TryMakeTraitor(EntityUid uid, AutoTraitorComponent? comp = null) - { - if (!Resolve(uid, ref comp)) - return false; - - //Start the rule if it has not already been started - var traitorRuleComponent = _traitorRule.StartGameRule(); - _traitorRule.MakeTraitor(uid, traitorRuleComponent, giveUplink: comp.GiveUplink, giveObjectives: comp.GiveObjectives); - // prevent spamming anything if it fails - RemComp(uid); - return true; + _antag.ForceMakeAntag(args.Mind.Comp.Session, DefaultTraitorRule); } } diff --git a/Content.Server/Traitor/Uplink/Commands/AddUplinkCommand.cs b/Content.Server/Traitor/Uplink/Commands/AddUplinkCommand.cs index cdaed3f928..63be36b360 100644 --- a/Content.Server/Traitor/Uplink/Commands/AddUplinkCommand.cs +++ b/Content.Server/Traitor/Uplink/Commands/AddUplinkCommand.cs @@ -12,7 +12,6 @@ namespace Content.Server.Traitor.Uplink.Commands [AdminCommand(AdminFlags.Admin)] public sealed class AddUplinkCommand : IConsoleCommand { - [Dependency] private readonly IConfigurationManager _cfgManager = default!; [Dependency] private readonly IEntityManager _entManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; @@ -83,12 +82,9 @@ public void Execute(IConsoleShell shell, string argStr, string[] args) uplinkEntity = eUid; } - // Get TC count - var tcCount = _cfgManager.GetCVar(CCVars.TraitorStartingBalance); - Logger.Debug(_entManager.ToPrettyString(user)); // Finally add uplink var uplinkSys = _entManager.System(); - if (!uplinkSys.AddUplink(user, FixedPoint2.New(tcCount), uplinkEntity: uplinkEntity)) + if (!uplinkSys.AddUplink(user, 20, uplinkEntity: uplinkEntity)) { shell.WriteLine(Loc.GetString("add-uplink-command-error-2")); } diff --git a/Content.Server/Traits/Assorted/ForeignerTraitComponent.cs b/Content.Server/Traits/Assorted/ForeignerTraitComponent.cs index 756f44e742..a35972f1c8 100644 --- a/Content.Server/Traits/Assorted/ForeignerTraitComponent.cs +++ b/Content.Server/Traits/Assorted/ForeignerTraitComponent.cs @@ -13,7 +13,7 @@ public sealed partial class ForeignerTraitComponent : Component { /// /// The "base" language that is to be removed and substituted with a translator. - /// By default, equals to the fallback language, which is GalacticCommon. + /// By default, equals to the fallback language, which is TauCetiBasic. /// [DataField] public ProtoId BaseLanguage = SharedLanguageSystem.FallbackLanguagePrototype; diff --git a/Content.Server/Traits/Assorted/ForeignerTraitSystem.cs b/Content.Server/Traits/Assorted/ForeignerTraitSystem.cs index 58e974227c..ce9fb51a03 100644 --- a/Content.Server/Traits/Assorted/ForeignerTraitSystem.cs +++ b/Content.Server/Traits/Assorted/ForeignerTraitSystem.cs @@ -38,7 +38,7 @@ private void OnSpawn(Entity entity, ref ComponentInit a } var alternateLanguage = knowledge.SpokenLanguages.Find(it => it != entity.Comp.BaseLanguage); - if (alternateLanguage == null) + if (alternateLanguage == default) { Log.Warning($"Entity {entity.Owner} does not have an alternative language to choose from (must have at least one non-GC for ForeignerTrait)!"); return; @@ -46,12 +46,12 @@ private void OnSpawn(Entity entity, ref ComponentInit a if (TryGiveTranslator(entity.Owner, entity.Comp.BaseTranslator, entity.Comp.BaseLanguage, alternateLanguage, out var translator)) { - _languages.RemoveLanguage(entity, entity.Comp.BaseLanguage, entity.Comp.CantSpeak, entity.Comp.CantUnderstand, knowledge); + _languages.RemoveLanguage(entity.Owner, entity.Comp.BaseLanguage, entity.Comp.CantSpeak, entity.Comp.CantUnderstand); } } /// - /// Tries to create and give the entity a translator to translator that translates speech between the two specified languages. + /// Tries to create and give the entity a translator that translates speech between the two specified languages. /// public bool TryGiveTranslator( EntityUid uid, diff --git a/Content.Server/Traits/Assorted/LanguageKnowledgeModifierComponent.cs b/Content.Server/Traits/Assorted/LanguageKnowledgeModifierComponent.cs deleted file mode 100644 index 170dae40fa..0000000000 --- a/Content.Server/Traits/Assorted/LanguageKnowledgeModifierComponent.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Content.Shared.Language; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; - -namespace Content.Server.Traits.Assorted; - -/// -/// Used for traits that modify entities' language knowledge. -/// -[RegisterComponent] -public sealed partial class LanguageKnowledgeModifierComponent : Component -{ - /// - /// List of languages this entity will learn to speak. - /// - [DataField("speaks")] - public List NewSpokenLanguages = new(); - - /// - /// List of languages this entity will learn to understand. - /// - [DataField("understands")] - public List NewUnderstoodLanguages = new(); -} diff --git a/Content.Server/Traits/Assorted/LanguageKnowledgeModifierSystem.cs b/Content.Server/Traits/Assorted/LanguageKnowledgeModifierSystem.cs deleted file mode 100644 index 9053c9404f..0000000000 --- a/Content.Server/Traits/Assorted/LanguageKnowledgeModifierSystem.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Linq; -using Content.Server.Language; -using Content.Shared.Language.Components; - -namespace Content.Server.Traits.Assorted; - -public sealed class LanguageKnowledgeModifierSystem : EntitySystem -{ - [Dependency] private readonly LanguageSystem _languages = default!; - - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnStartup); - } - - private void OnStartup(Entity entity, ref ComponentInit args) - { - if (!TryComp(entity, out var knowledge)) - { - Log.Warning($"Entity {entity.Owner} does not have a LanguageKnowledge but has a LanguageKnowledgeModifier!"); - return; - } - - foreach (var spokenLanguage in entity.Comp.NewSpokenLanguages) - { - _languages.AddLanguage(entity, spokenLanguage, true, false, knowledge); - } - - foreach (var understoodLanguage in entity.Comp.NewUnderstoodLanguages) - { - _languages.AddLanguage(entity, understoodLanguage, false, true, knowledge); - } - } -} diff --git a/Content.Server/Traits/Assorted/LayingDownModifierSystem.cs b/Content.Server/Traits/Assorted/LayingDownModifierSystem.cs index dc6dcd2de3..8c47d65233 100644 --- a/Content.Server/Traits/Assorted/LayingDownModifierSystem.cs +++ b/Content.Server/Traits/Assorted/LayingDownModifierSystem.cs @@ -1,5 +1,5 @@ using Content.Server.Traits.Assorted; -using Content.Server.Standing; +using Content.Shared.Standing; namespace Content.Shared.Traits.Assorted.Systems; @@ -16,7 +16,7 @@ private void OnStartup(EntityUid uid, LayingDownModifierComponent component, Com if (!TryComp(uid, out var layingDown)) return; - layingDown.Cooldown *= component.LayingDownCooldownMultiplier; - layingDown.DownedSpeedMultiplier *= component.DownedSpeedMultiplierMultiplier; + layingDown.StandingUpTime *= component.LayingDownCooldownMultiplier; + layingDown.LyingSpeedModifier *= component.DownedSpeedMultiplierMultiplier; } } diff --git a/Content.Server/Traits/Assorted/LightweightDrunkSystem.cs b/Content.Server/Traits/Assorted/LightweightDrunkSystem.cs index b5e9b87776..f974fe7560 100644 --- a/Content.Server/Traits/Assorted/LightweightDrunkSystem.cs +++ b/Content.Server/Traits/Assorted/LightweightDrunkSystem.cs @@ -13,7 +13,7 @@ public override void Initialize() private void OnTryMetabolizeReagent(EntityUid uid, LightweightDrunkComponent comp, ref TryMetabolizeReagent args) { - Log.Debug(args.Prototype.ID); + //Log.Debug(args.Prototype.ID); if (args.Prototype.ID != "Ethanol") return; diff --git a/Content.Server/Traits/Assorted/NarcolepsyComponent.cs b/Content.Server/Traits/Assorted/NarcolepsyComponent.cs index efa3458495..d18d35f22c 100644 --- a/Content.Server/Traits/Assorted/NarcolepsyComponent.cs +++ b/Content.Server/Traits/Assorted/NarcolepsyComponent.cs @@ -20,5 +20,21 @@ public sealed partial class NarcolepsyComponent : Component [DataField("durationOfIncident", required: true)] public Vector2 DurationOfIncident { get; private set; } + [DataField] public float NextIncidentTime; + + /// + /// Locales for popups shown when the entity is about to fall asleep/is waking up. + /// They are fetched in the format of "(base)-(random number between 1 and count)", e.g. "narcolepsy-warning-popup-3". + /// + [DataField] + public string WarningLocaleBase = "narcolepsy-warning-popup", WakeupLocaleBase = "narcolepsy-wakeup-popup"; + + [DataField] + public int WarningLocaleCount = 5, WakeupLocaleCount = 3; + + [DataField] + public float TimeBeforeWarning = 20f, WarningChancePerSecond = 0.25f; + + public float LastWarningRollTime = float.MaxValue; } diff --git a/Content.Server/Traits/Assorted/NarcolepsySystem.cs b/Content.Server/Traits/Assorted/NarcolepsySystem.cs index e4fa1ccbc7..1a6e6d92bc 100644 --- a/Content.Server/Traits/Assorted/NarcolepsySystem.cs +++ b/Content.Server/Traits/Assorted/NarcolepsySystem.cs @@ -1,5 +1,9 @@ +using Content.Server.Chat.Managers; using Content.Shared.Bed.Sleep; +using Content.Shared.Chat; +using Content.Shared.Popups; using Content.Shared.StatusEffect; +using Robust.Shared.Player; using Robust.Shared.Random; namespace Content.Server.Traits.Assorted; @@ -12,6 +16,8 @@ public sealed class NarcolepsySystem : EntitySystem [ValidatePrototypeId] private const string StatusEffectKey = "ForcedSleep"; // Same one used by N2O and other sleep chems. + [Dependency] private readonly IChatManager _chatMan = default!; + [Dependency] private readonly SharedPopupSystem _popups = default!; [Dependency] private readonly StatusEffectsSystem _statusEffects = default!; [Dependency] private readonly IRobustRandom _random = default!; @@ -19,20 +25,38 @@ public sealed class NarcolepsySystem : EntitySystem public override void Initialize() { SubscribeLocalEvent(SetupNarcolepsy); + SubscribeLocalEvent(OnSleepChanged); } private void SetupNarcolepsy(EntityUid uid, NarcolepsyComponent component, ComponentStartup args) { - component.NextIncidentTime = - _random.NextFloat(component.TimeBetweenIncidents.X, component.TimeBetweenIncidents.Y); + PrepareNextIncident((uid, component)); } - public void AdjustNarcolepsyTimer(EntityUid uid, int TimerReset, NarcolepsyComponent? narcolepsy = null) + private void OnSleepChanged(Entity ent, ref SleepStateChangedEvent args) { - if (!Resolve(uid, ref narcolepsy, false)) + // When falling asleep while an incident is nigh, force it to happen immediately. + if (args.FellAsleep) + { + if (ent.Comp.NextIncidentTime < ent.Comp.TimeBeforeWarning) + StartIncident(ent); + return; + } + + // When waking up after sleeping for at least the minimum time of an incident, reset the incident timer and show a popup. + if (args.TimeSlept is null || args.TimeSlept.Value.TotalSeconds < ent.Comp.DurationOfIncident.X) return; - narcolepsy.NextIncidentTime = TimerReset; + ShowRandomPopup(ent, ent.Comp.WakeupLocaleBase, ent.Comp.WakeupLocaleCount); + PrepareNextIncident(ent); + } + + public void AdjustNarcolepsyTimer(EntityUid uid, float setTime, NarcolepsyComponent? narcolepsy = null) + { + if (!Resolve(uid, ref narcolepsy, false) || narcolepsy.NextIncidentTime > setTime) + return; + + narcolepsy.NextIncidentTime = setTime; } public override void Update(float frameTime) @@ -42,22 +66,52 @@ public override void Update(float frameTime) var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var narcolepsy)) { + if (HasComp(uid)) + continue; + narcolepsy.NextIncidentTime -= frameTime; + if (narcolepsy.NextIncidentTime <= narcolepsy.TimeBeforeWarning && narcolepsy.NextIncidentTime < narcolepsy.LastWarningRollTime - 1f) + { + // Roll for showing a popup. There should really be a class for doing this. + narcolepsy.LastWarningRollTime = narcolepsy.NextIncidentTime; + if (_random.Prob(narcolepsy.WarningChancePerSecond)) + { + ShowRandomPopup(uid, narcolepsy.WarningLocaleBase, narcolepsy.WakeupLocaleCount); + narcolepsy.LastWarningRollTime = 0f; // Do not show any more popups for the upcoming incident + } + } if (narcolepsy.NextIncidentTime >= 0) continue; - // Set the new time. - narcolepsy.NextIncidentTime += - _random.NextFloat(narcolepsy.TimeBetweenIncidents.X, narcolepsy.TimeBetweenIncidents.Y); + StartIncident((uid, narcolepsy)); + } + } - var duration = _random.NextFloat(narcolepsy.DurationOfIncident.X, narcolepsy.DurationOfIncident.Y); + public void StartIncident(Entity ent) + { + var duration = _random.NextFloat(ent.Comp.DurationOfIncident.X, ent.Comp.DurationOfIncident.Y); + PrepareNextIncident(ent, duration); - // Make sure the sleep time doesn't cut into the time to next incident. - narcolepsy.NextIncidentTime += duration; + _statusEffects.TryAddStatusEffect(ent, StatusEffectKey, TimeSpan.FromSeconds(duration), false); + } - _statusEffects.TryAddStatusEffect(uid, StatusEffectKey, - TimeSpan.FromSeconds(duration), false); - } + private void PrepareNextIncident(Entity ent, float startingFrom = 0f) + { + var time = _random.NextFloat(ent.Comp.TimeBetweenIncidents.X, ent.Comp.TimeBetweenIncidents.Y); + ent.Comp.NextIncidentTime = startingFrom + time; + ent.Comp.LastWarningRollTime = float.MaxValue; + } + + private void ShowRandomPopup(EntityUid uid, string prefix, int count) + { + if (count <= 0 || !TryComp(uid, out var actor)) + return; + + var popup = Loc.GetString($"{prefix}-{_random.Next(1, count + 1)}"); + _popups.PopupEntity(popup, uid, uid, PopupType.MediumCaution); + // This should use ChatChannel.Visual, but it's not displayed on the client. + _chatMan.ChatMessageToOne(ChatChannel.Notifications, popup, popup, uid, false, + actor.PlayerSession.Channel, Color.IndianRed); } } diff --git a/Content.Server/Traits/Assorted/PotentiaModifierComponent.cs b/Content.Server/Traits/Assorted/PotentiaModifierComponent.cs new file mode 100644 index 0000000000..2ddfd633b9 --- /dev/null +++ b/Content.Server/Traits/Assorted/PotentiaModifierComponent.cs @@ -0,0 +1,20 @@ +namespace Content.Server.Traits.Assorted; + +/// +/// This is used for traits that modify the outcome of Potentia Rolls +/// +[RegisterComponent] +public sealed partial class PotentiaModifierComponent : Component +{ + /// + /// When rolling for psionic powers, increase the potentia gains by a flat amount. + /// + [DataField] + public float PotentiaFlatModifier = 0; + + /// + /// When rolling for psionic powers, multiply the potentia gains by a specific factor. + /// + [DataField] + public float PotentiaMultiplier = 1; +} diff --git a/Content.Server/Traits/Assorted/PotentiaModifierSystem.cs b/Content.Server/Traits/Assorted/PotentiaModifierSystem.cs new file mode 100644 index 0000000000..f05f788a08 --- /dev/null +++ b/Content.Server/Traits/Assorted/PotentiaModifierSystem.cs @@ -0,0 +1,19 @@ +using Content.Server.Psionics; + +namespace Content.Server.Traits.Assorted; +public sealed partial class PotentiaModifierSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnRollPsionics); + } + + private void OnRollPsionics(EntityUid uid, PotentiaModifierComponent component, ref OnRollPsionicsEvent args) + { + if (uid != args.Roller) + return; + + args.BaselineChance = args.BaselineChance * component.PotentiaMultiplier + component.PotentiaFlatModifier; + } +} diff --git a/Content.Server/Traits/Assorted/SingerSystem.cs b/Content.Server/Traits/Assorted/SingerSystem.cs index 7b560957b6..78a4101a65 100644 --- a/Content.Server/Traits/Assorted/SingerSystem.cs +++ b/Content.Server/Traits/Assorted/SingerSystem.cs @@ -65,9 +65,7 @@ private void OnEquip(GotEquippedEvent args) if (TryComp(args.Equipment, out var accent) && accent.ReplacementPrototype == "mumble" && args.Slot == "mask") - { CloseMidiUi(args.Equipee); - } } private void OnMobStateChangedEvent(EntityUid uid, SharedInstrumentComponent component, MobStateChangedEvent args) @@ -154,8 +152,6 @@ public override void CloseMidiUi(EntityUid uid) { if (HasComp(uid) && TryComp(uid, out var actor)) - { - _instrument.ToggleInstrumentUi(uid, actor.PlayerSession); - } + _instrument.ToggleInstrumentUi(uid, actor.PlayerSession.AttachedEntity ?? EntityUid.Invalid); } } diff --git a/Content.Server/Traits/Assorted/UncloneableSystem.cs b/Content.Server/Traits/Assorted/UncloneableSystem.cs new file mode 100644 index 0000000000..6f2af10647 --- /dev/null +++ b/Content.Server/Traits/Assorted/UncloneableSystem.cs @@ -0,0 +1,23 @@ +using Content.Shared.Cloning; + +namespace Content.Server.Traits.Assorted +{ + public sealed class UncloneableSystem : EntitySystem + { + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnAttemptCloning); + } + + private void OnAttemptCloning(EntityUid uid, UncloneableComponent component, ref AttemptCloningEvent args) + { + if (args.CloningFailMessage is not null + || args.Cancelled) + return; + + args.CloningFailMessage = "cloning-console-uncloneable-trait-error"; + args.Cancelled = true; + } + } +} diff --git a/Content.Server/Traits/BloodDeficiencyComponent.cs b/Content.Server/Traits/BloodDeficiencyComponent.cs index 616f60cd83..ce438278f7 100644 --- a/Content.Server/Traits/BloodDeficiencyComponent.cs +++ b/Content.Server/Traits/BloodDeficiencyComponent.cs @@ -7,8 +7,14 @@ namespace Content.Server.Traits.Assorted; public sealed partial class BloodDeficiencyComponent : Component { // - // How much reagent of blood should be removed in each update interval? + /// How much percentage of max blood volume should be removed in each update interval? // [DataField(required: true)] - public float BloodLossAmount; + public float BloodLossPercentage; + + /// + /// Whether the effects of this trait should be active. + /// + [DataField] + public bool Active = true; } diff --git a/Content.Server/Traits/BloodDeficiencySystem.cs b/Content.Server/Traits/BloodDeficiencySystem.cs index f1ae490995..8928bc57a1 100644 --- a/Content.Server/Traits/BloodDeficiencySystem.cs +++ b/Content.Server/Traits/BloodDeficiencySystem.cs @@ -1,23 +1,24 @@ -using Content.Server.Body.Systems; using Content.Server.Body.Components; -using Content.Shared.Damage; +using Content.Server.Body.Events; +using Content.Server.Traits.Assorted; +using Content.Shared.FixedPoint; -namespace Content.Server.Traits.Assorted; +namespace Content.Server.Traits; public sealed class BloodDeficiencySystem : EntitySystem { public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnStartup); + SubscribeLocalEvent(OnBloodRegen); } - private void OnStartup(EntityUid uid, BloodDeficiencyComponent component, ComponentStartup args) + private void OnBloodRegen(Entity ent, ref NaturalBloodRegenerationAttemptEvent args) { - if (!TryComp(uid, out var bloodstream)) + if (!ent.Comp.Active || !TryComp(ent.Owner, out var bloodstream)) return; - bloodstream.HasBloodDeficiency = true; - bloodstream.BloodDeficiencyLossAmount = component.BloodLossAmount; + args.Amount = FixedPoint2.Min(args.Amount, 0) // If the blood regen amount already was negative, we keep it. + - bloodstream.BloodMaxVolume * ent.Comp.BloodLossPercentage; } } diff --git a/Content.Server/Traits/TraitSystem.Functions.cs b/Content.Server/Traits/TraitSystem.Functions.cs new file mode 100644 index 0000000000..07794903d3 --- /dev/null +++ b/Content.Server/Traits/TraitSystem.Functions.cs @@ -0,0 +1,265 @@ +using Content.Shared.Traits; +using JetBrains.Annotations; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.Manager; +using Content.Shared.Implants; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set; +using Content.Shared.Actions; +using Content.Server.Abilities.Psionics; +using Content.Shared.Psionics; +using Content.Server.Language; +using Content.Shared.Mood; +using Content.Server.NPC.Systems; + +namespace Content.Server.Traits; + +/// Used for traits that add a Component upon spawning in, overwriting the pre-existing component if it already exists. +[UsedImplicitly] +public sealed partial class TraitReplaceComponent : TraitFunction +{ + [DataField, AlwaysPushInheritance] + public ComponentRegistry Components { get; private set; } = new(); + + public override void OnPlayerSpawn(EntityUid uid, + IComponentFactory factory, + IEntityManager entityManager, + ISerializationManager serializationManager) + { + foreach (var (_, data) in Components) + { + var comp = (Component) serializationManager.CreateCopy(data.Component, notNullableOverride: true); + comp.Owner = uid; + entityManager.AddComponent(uid, comp, true); + } + } +} + +/// +/// Used for traits that add a Component upon spawning in. +/// This will do nothing if the Component already exists. +/// +[UsedImplicitly] +public sealed partial class TraitAddComponent : TraitFunction +{ + [DataField, AlwaysPushInheritance] + public ComponentRegistry Components { get; private set; } = new(); + + public override void OnPlayerSpawn(EntityUid uid, + IComponentFactory factory, + IEntityManager entityManager, + ISerializationManager serializationManager) + { + foreach (var entry in Components.Values) + { + if (entityManager.HasComponent(uid, entry.Component.GetType())) + continue; + + var comp = (Component) serializationManager.CreateCopy(entry.Component, notNullableOverride: true); + comp.Owner = uid; + entityManager.AddComponent(uid, comp); + } + } +} + +/// Used for traits that remove a component upon a player spawning in. +[UsedImplicitly] +public sealed partial class TraitRemoveComponent : TraitFunction +{ + [DataField, AlwaysPushInheritance] + public ComponentRegistry Components { get; private set; } = new(); + + public override void OnPlayerSpawn(EntityUid uid, + IComponentFactory factory, + IEntityManager entityManager, + ISerializationManager serializationManager) + { + foreach (var (name, _) in Components) + entityManager.RemoveComponentDeferred(uid, factory.GetComponent(name).GetType()); + } +} + +/// Used for traits that add an action upon a player spawning in. +[UsedImplicitly] +public sealed partial class TraitAddActions : TraitFunction +{ + [DataField, AlwaysPushInheritance] + public List Actions { get; private set; } = new(); + + public override void OnPlayerSpawn(EntityUid uid, + IComponentFactory factory, + IEntityManager entityManager, + ISerializationManager serializationManager) + { + var actionSystem = entityManager.System(); + + foreach (var id in Actions) + { + EntityUid? actionId = null; + if (actionSystem.AddAction(uid, ref actionId, id)) + actionSystem.StartUseDelay(actionId); + } + } +} + +/// Used for traits that add an Implant upon spawning in. +[UsedImplicitly] +public sealed partial class TraitAddImplant : TraitFunction +{ + [DataField(customTypeSerializer: typeof(PrototypeIdHashSetSerializer))] + [AlwaysPushInheritance] + public HashSet Implants { get; private set; } = new(); + + public override void OnPlayerSpawn(EntityUid uid, + IComponentFactory factory, + IEntityManager entityManager, + ISerializationManager serializationManager) + { + var implantSystem = entityManager.System(); + implantSystem.AddImplants(uid, Implants); + } +} + +/// +/// If a trait includes any Psionic Powers, this enters the powers into PsionicSystem to be initialized. +/// If the lack of logic here seems startling, it's okay. All of the logic necessary for adding Psionics is handled by InitializePsionicPower. +/// +[UsedImplicitly] +public sealed partial class TraitAddPsionics : TraitFunction +{ + [DataField, AlwaysPushInheritance] + public List> PsionicPowers { get; private set; } = new(); + + public override void OnPlayerSpawn(EntityUid uid, + IComponentFactory factory, + IEntityManager entityManager, + ISerializationManager serializationManager) + { + var prototype = IoCManager.Resolve(); + var psionic = entityManager.System(); + + foreach (var powerProto in PsionicPowers) + if (prototype.TryIndex(powerProto, out var psionicPower)) + psionic.InitializePsionicPower(uid, psionicPower, false); + } +} + +/// Handles all modification of Known Languages. Removes languages before adding them. +[UsedImplicitly] +public sealed partial class TraitModifyLanguages : TraitFunction +{ + /// The list of all Spoken Languages that this trait adds. + [DataField, AlwaysPushInheritance] + public List? LanguagesSpoken { get; private set; } = default!; + + /// The list of all Understood Languages that this trait adds. + [DataField, AlwaysPushInheritance] + public List? LanguagesUnderstood { get; private set; } = default!; + + /// The list of all Spoken Languages that this trait removes. + [DataField, AlwaysPushInheritance] + public List? RemoveLanguagesSpoken { get; private set; } = default!; + + /// The list of all Understood Languages that this trait removes. + [DataField, AlwaysPushInheritance] + public List? RemoveLanguagesUnderstood { get; private set; } = default!; + + public override void OnPlayerSpawn(EntityUid uid, + IComponentFactory factory, + IEntityManager entityManager, + ISerializationManager serializationManager) + { + var language = entityManager.System(); + + if (RemoveLanguagesSpoken is not null) + foreach (var lang in RemoveLanguagesSpoken) + language.RemoveLanguage(uid, lang, true, false); + + if (RemoveLanguagesUnderstood is not null) + foreach (var lang in RemoveLanguagesUnderstood) + language.RemoveLanguage(uid, lang, false, true); + + if (LanguagesSpoken is not null) + foreach (var lang in LanguagesSpoken) + language.AddLanguage(uid, lang, true, false); + + if (LanguagesUnderstood is not null) + foreach (var lang in LanguagesUnderstood) + language.AddLanguage(uid, lang, false, true); + } +} + +/// Handles adding Moodlets to a player character upon spawning in. Typically used for permanent moodlets or drug addictions. +[UsedImplicitly] +public sealed partial class TraitAddMoodlets : TraitFunction +{ + /// The list of all Moodlets that this trait adds. + [DataField, AlwaysPushInheritance] + public List> MoodEffects { get; private set; } = new(); + + public override void OnPlayerSpawn(EntityUid uid, + IComponentFactory factory, + IEntityManager entityManager, + ISerializationManager serializationManager) + { + var prototype = IoCManager.Resolve(); + + foreach (var moodProto in MoodEffects) + if (prototype.TryIndex(moodProto, out var moodlet)) + entityManager.EventBus.RaiseLocalEvent(uid, new MoodEffectEvent(moodlet.ID)); + } +} + +/// Add or remove Factions from a player upon spawning in. +[UsedImplicitly] +public sealed partial class TraitModifyFactions : TraitFunction +{ + /// + /// The list of all Factions that this trait removes. + /// + /// + /// I can't actually Validate these because the proto lives in Shared. + /// + [DataField, AlwaysPushInheritance] + public List RemoveFactions { get; private set; } = new(); + + /// + /// The list of all Factions that this trait adds. + /// + /// + /// I can't actually Validate these because the proto lives in Shared. + /// + [DataField, AlwaysPushInheritance] + public List AddFactions { get; private set; } = new(); + + public override void OnPlayerSpawn(EntityUid uid, + IComponentFactory factory, + IEntityManager entityManager, + ISerializationManager serializationManager) + { + var factionSystem = entityManager.System(); + + foreach (var faction in RemoveFactions) + factionSystem.RemoveFaction(uid, faction); + + foreach (var faction in AddFactions) + factionSystem.AddFaction(uid, faction); + } +} + +/// Only use this if you know what you're doing. This function directly writes to any arbitrary component. +[UsedImplicitly] +public sealed partial class TraitVVEdit : TraitFunction +{ + [DataField, AlwaysPushInheritance] + public Dictionary VVEdit { get; private set; } = new(); + + public override void OnPlayerSpawn(EntityUid uid, + IComponentFactory factory, + IEntityManager entityManager, + ISerializationManager serializationManager) + { + var vvm = IoCManager.Resolve(); + foreach (var (path, value) in VVEdit) + vvm.WritePath(path, value); + } +} diff --git a/Content.Server/Traits/TraitSystem.cs b/Content.Server/Traits/TraitSystem.cs index 628cb43b8d..75771a5743 100644 --- a/Content.Server/Traits/TraitSystem.cs +++ b/Content.Server/Traits/TraitSystem.cs @@ -2,15 +2,13 @@ using Content.Server.GameTicking; using Content.Server.Players.PlayTimeTracking; using Content.Shared.Customization.Systems; -using Content.Shared.Hands.Components; -using Content.Shared.Hands.EntitySystems; using Content.Shared.Players; using Content.Shared.Roles; using Content.Shared.Traits; -using Pidgin.Configuration; using Robust.Shared.Configuration; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.Manager; +using Robust.Shared.Utility; namespace Content.Server.Traits; @@ -18,10 +16,10 @@ public sealed class TraitSystem : EntitySystem { [Dependency] private readonly IPrototypeManager _prototype = default!; [Dependency] private readonly ISerializationManager _serialization = default!; - [Dependency] private readonly SharedHandsSystem _hands = default!; [Dependency] private readonly CharacterRequirementsSystem _characterRequirements = default!; [Dependency] private readonly PlayTimeTrackingManager _playTimeTracking = default!; [Dependency] private readonly IConfigurationManager _configuration = default!; + [Dependency] private readonly IComponentFactory _componentFactory = default!; public override void Initialize() { @@ -37,28 +35,28 @@ private void OnPlayerSpawnComplete(PlayerSpawnCompleteEvent args) { if (!_prototype.TryIndex(traitId, out var traitPrototype)) { - Log.Warning($"No trait found with ID {traitId}!"); + DebugTools.Assert($"No trait found with ID {traitId}!"); return; } if (!_characterRequirements.CheckRequirementsValid( traitPrototype.Requirements, _prototype.Index(args.JobId ?? _prototype.EnumeratePrototypes().First().ID), - args.Profile, _playTimeTracking.GetTrackerTimes(args.Player), args.Player.ContentData()?.Whitelisted ?? false, + args.Profile, _playTimeTracking.GetTrackerTimes(args.Player), args.Player.ContentData()?.Whitelisted ?? false, traitPrototype, EntityManager, _prototype, _configuration, out _)) continue; - // Add all components required by the prototype - foreach (var entry in traitPrototype.Components.Values) - { - if (HasComp(args.Mob, entry.Component.GetType())) - continue; - - var comp = (Component) _serialization.CreateCopy(entry.Component, notNullableOverride: true); - comp.Owner = args.Mob; - EntityManager.AddComponent(args.Mob, comp); - } + AddTrait(args.Mob, traitPrototype); } } + + /// + /// Adds a single Trait Prototype to an Entity. + /// + public void AddTrait(EntityUid uid, TraitPrototype traitPrototype) + { + foreach (var function in traitPrototype.Functions) + function.OnPlayerSpawn(uid, _componentFactory, EntityManager, _serialization); + } } diff --git a/Content.Server/UserInterface/ActivatableUIComponent.cs b/Content.Server/UserInterface/ActivatableUIComponent.cs deleted file mode 100644 index cc0e5008e4..0000000000 --- a/Content.Server/UserInterface/ActivatableUIComponent.cs +++ /dev/null @@ -1,75 +0,0 @@ -using Content.Shared.Whitelist; -using Robust.Server.GameObjects; -using Robust.Server.Player; -using Robust.Shared.Player; -using Robust.Shared.Serialization.TypeSerializers.Implementations; - -namespace Content.Server.UserInterface -{ - [RegisterComponent] - public sealed partial class ActivatableUIComponent : Component - { - [DataField(required: true, customTypeSerializer:typeof(EnumSerializer))] - public Enum? Key { get; set; } - - [ViewVariables(VVAccess.ReadWrite)] - [DataField] - public bool InHandsOnly { get; set; } = false; - - [DataField] - public bool SingleUser { get; set; } = false; - - [ViewVariables(VVAccess.ReadWrite)] - [DataField] - public bool AdminOnly { get; set; } = false; - - [DataField] - public LocId VerbText = "ui-verb-toggle-open"; - - /// - /// Whether you need a hand to operate this UI. The hand does not need to be free, you just need to have one. - /// - /// - /// This should probably be true for most machines & computers, but there will still be UIs that represent a - /// more generic interaction / configuration that might not require hands. - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField] - public bool RequireHands = true; - - /// - /// Entities that are required to open this UI. - /// - [DataField("allowedItems")] - [ViewVariables(VVAccess.ReadWrite)] - public EntityWhitelist? AllowedItems = null; - - /// - /// Whether you can activate this ui with activateinhand or not - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField] - public bool RightClickOnly; - - /// - /// Whether spectators (non-admin ghosts) should be allowed to view this UI. - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField] - public bool AllowSpectator = true; - - /// - /// Whether the UI should close when the item is deselected due to a hand swap or drop - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField] - public bool CloseOnHandDeselect = true; - - /// - /// The client channel currently using the object, or null if there's none/not single user. - /// NOTE: DO NOT DIRECTLY SET, USE ActivatableUISystem.SetCurrentSingleUser - /// - [ViewVariables] - public ICommonSession? CurrentSingleUser; - } -} diff --git a/Content.Server/UserInterface/ActivatableUIRequiresPowerCellComponent.cs b/Content.Server/UserInterface/ActivatableUIRequiresPowerCellComponent.cs deleted file mode 100644 index fdc56f89a0..0000000000 --- a/Content.Server/UserInterface/ActivatableUIRequiresPowerCellComponent.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Content.Server.PowerCell; -using Content.Shared.PowerCell; -using Content.Shared.UserInterface; - -namespace Content.Server.UserInterface; - -/// -/// Specifies that the attached entity requires power. -/// -[RegisterComponent] -public sealed partial class ActivatableUIRequiresPowerCellComponent : Component -{ - -} diff --git a/Content.Server/UserInterface/ActivatableUISystem.Power.cs b/Content.Server/UserInterface/ActivatableUISystem.Power.cs deleted file mode 100644 index d4dcc91d46..0000000000 --- a/Content.Server/UserInterface/ActivatableUISystem.Power.cs +++ /dev/null @@ -1,81 +0,0 @@ -using Content.Server.PowerCell; -using Content.Shared.PowerCell; -using Content.Shared.UserInterface; -using Robust.Shared.Containers; - -namespace Content.Server.UserInterface; - -public sealed partial class ActivatableUISystem -{ - [Dependency] private readonly PowerCellSystem _cell = default!; - - private void InitializePower() - { - SubscribeLocalEvent(OnBatteryOpenAttempt); - SubscribeLocalEvent(OnBatteryOpened); - SubscribeLocalEvent(OnBatteryClosed); - - SubscribeLocalEvent(OnPowerCellRemoved); - } - - private void OnPowerCellRemoved(EntityUid uid, PowerCellDrawComponent component, EntRemovedFromContainerMessage args) - { - _cell.SetPowerCellDrawEnabled(uid, false); - - if (HasComp(uid) && - TryComp(uid, out var activatable) && - activatable.Key != null) - { - _uiSystem.TryCloseAll(uid, activatable.Key); - } - } - - private void OnBatteryOpened(EntityUid uid, ActivatableUIRequiresPowerCellComponent component, BoundUIOpenedEvent args) - { - var activatable = Comp(uid); - - if (!args.UiKey.Equals(activatable.Key)) - return; - - _cell.SetPowerCellDrawEnabled(uid, true); - } - - private void OnBatteryClosed(EntityUid uid, ActivatableUIRequiresPowerCellComponent component, BoundUIClosedEvent args) - { - var activatable = Comp(uid); - - if (!args.UiKey.Equals(activatable.Key)) - return; - - // Stop drawing power if this was the last person with the UI open. - if (!_uiSystem.IsUiOpen(uid, activatable.Key)) - _cell.SetPowerCellDrawEnabled(uid, false); - } - - /// - /// Call if you want to check if the UI should close due to a recent battery usage. - /// - public void CheckUsage(EntityUid uid, ActivatableUIComponent? active = null, ActivatableUIRequiresPowerCellComponent? component = null, PowerCellDrawComponent? draw = null) - { - if (!Resolve(uid, ref component, ref draw, ref active, false) || active.Key == null) - return; - - if (_cell.HasCharge(uid, draw.UseRate)) - return; - - _uiSystem.TryCloseAll(uid, active.Key); - } - - private void OnBatteryOpenAttempt(EntityUid uid, ActivatableUIRequiresPowerCellComponent component, ActivatableUIOpenAttemptEvent args) - { - if (!TryComp(uid, out var draw)) - return; - - // Check if we have the appropriate drawrate / userate to even open it. - if (args.Cancelled || !_cell.HasCharge(uid, MathF.Max(draw.DrawRate, draw.UseRate), user: args.User)) - { - args.Cancel(); - return; - } - } -} diff --git a/Content.Server/UserInterface/ActivatableUISystem.cs b/Content.Server/UserInterface/ActivatableUISystem.cs deleted file mode 100644 index 5f2314df90..0000000000 --- a/Content.Server/UserInterface/ActivatableUISystem.cs +++ /dev/null @@ -1,235 +0,0 @@ -using Content.Server.Administration.Managers; -using Content.Shared.ActionBlocker; -using Content.Shared.Ghost; -using Content.Shared.Hands; -using Content.Shared.Hands.Components; -using Content.Shared.Interaction; -using Content.Shared.Interaction.Events; -using Content.Shared.Popups; -using Content.Shared.UserInterface; -using Content.Shared.Verbs; -using Robust.Server.GameObjects; -using Robust.Shared.Player; - -namespace Content.Server.UserInterface; - -public sealed partial class ActivatableUISystem : EntitySystem -{ - [Dependency] private readonly IAdminManager _adminManager = default!; - [Dependency] private readonly ActionBlockerSystem _blockerSystem = default!; - [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; - [Dependency] private readonly SharedPopupSystem _popupSystem = default!; - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnActivate); - SubscribeLocalEvent(OnUseInHand); - SubscribeLocalEvent(OnInteractUsing); - SubscribeLocalEvent(OnHandDeselected); - SubscribeLocalEvent((uid, aui, _) => CloseAll(uid, aui)); - // *THIS IS A BLATANT WORKAROUND!* RATIONALE: Microwaves need it - SubscribeLocalEvent(OnParentChanged); - SubscribeLocalEvent(OnUIClose); - SubscribeLocalEvent(OnBoundInterfaceInteractAttempt); - - SubscribeLocalEvent>(AddOpenUiVerb); - - SubscribeLocalEvent(OnActionPerform); - - InitializePower(); - } - - private void OnBoundInterfaceInteractAttempt(BoundUserInterfaceMessageAttempt ev) - { - if (!TryComp(ev.Target, out ActivatableUIComponent? comp)) - return; - - if (!comp.RequireHands) - return; - - if (!TryComp(ev.Sender.AttachedEntity, out HandsComponent? hands) || hands.Hands.Count == 0) - ev.Cancel(); - } - - private void OnActionPerform(EntityUid uid, UserInterfaceComponent component, OpenUiActionEvent args) - { - if (args.Handled || args.Key == null) - return; - - if (!TryComp(args.Performer, out ActorComponent? actor)) - return; - - args.Handled = _uiSystem.TryToggleUi(uid, args.Key, actor.PlayerSession); - } - - private void AddOpenUiVerb(EntityUid uid, ActivatableUIComponent component, GetVerbsEvent args) - { - if (!args.CanAccess) - return; - - if (component.RequireHands && args.Hands == null) - return; - - if (component.InHandsOnly && args.Using != uid) - return; - - if (!args.CanInteract && (!component.AllowSpectator || !HasComp(args.User))) - return; - - ActivationVerb verb = new(); - verb.Act = () => InteractUI(args.User, uid, component); - verb.Text = Loc.GetString(component.VerbText); - // TODO VERBS add "open UI" icon? - args.Verbs.Add(verb); - } - - private void OnActivate(EntityUid uid, ActivatableUIComponent component, ActivateInWorldEvent args) - { - if (args.Handled) - return; - - if (component.InHandsOnly) - return; - - if (component.AllowedItems != null) - return; - - args.Handled = InteractUI(args.User, uid, component); - } - - private void OnUseInHand(EntityUid uid, ActivatableUIComponent component, UseInHandEvent args) - { - if (args.Handled) - return; - - if (component.RightClickOnly) - return; - - if (component.AllowedItems != null) - return; - - args.Handled = InteractUI(args.User, uid, component); - } - - private void OnInteractUsing(EntityUid uid, ActivatableUIComponent component, InteractUsingEvent args) - { - if (args.Handled) return; - if (component.AllowedItems == null) return; - if (!component.AllowedItems.IsValid(args.Used, EntityManager)) return; - args.Handled = InteractUI(args.User, uid, component); - } - - private void OnParentChanged(EntityUid uid, ActivatableUIComponent aui, ref EntParentChangedMessage args) - { - CloseAll(uid, aui); - } - - private void OnUIClose(EntityUid uid, ActivatableUIComponent component, BoundUIClosedEvent args) - { - if (args.Session != component.CurrentSingleUser) - return; - - if (!Equals(args.UiKey, component.Key)) - return; - - SetCurrentSingleUser(uid, null, component); - } - - private bool InteractUI(EntityUid user, EntityUid uiEntity, ActivatableUIComponent aui) - { - if (!TryComp(user, out ActorComponent? actor)) - return false; - - if (aui.Key == null) - return false; - - if (!_uiSystem.TryGetUi(uiEntity, aui.Key, out var ui)) - return false; - - if (ui.SubscribedSessions.Contains(actor.PlayerSession)) - { - _uiSystem.CloseUi(ui, actor.PlayerSession); - return true; - } - - if (!_blockerSystem.CanInteract(user, uiEntity) && (!aui.AllowSpectator || !HasComp(user))) - return false; - - if (aui.RequireHands && !HasComp(user)) - return false; - - if (aui.AdminOnly && !_adminManager.IsAdmin(actor.PlayerSession)) - return false; - - if (aui.SingleUser && (aui.CurrentSingleUser != null) && (actor.PlayerSession != aui.CurrentSingleUser)) - { - string message = Loc.GetString("machine-already-in-use", ("machine", uiEntity)); - _popupSystem.PopupEntity(message, uiEntity, user); - - // If we get here, supposedly, the object is in use. - // Check with BUI that it's ACTUALLY in use just in case. - // Since this could brick the object if it goes wrong. - if (ui.SubscribedSessions.Count != 0) - return false; - } - - // If we've gotten this far, fire a cancellable event that indicates someone is about to activate this. - // This is so that stuff can require further conditions (like power). - var oae = new ActivatableUIOpenAttemptEvent(user); - var uae = new UserOpenActivatableUIAttemptEvent(user, uiEntity); - RaiseLocalEvent(user, uae); - RaiseLocalEvent(uiEntity, oae); - if (oae.Cancelled || uae.Cancelled) - return false; - - // Give the UI an opportunity to prepare itself if it needs to do anything - // before opening - var bae = new BeforeActivatableUIOpenEvent(user); - RaiseLocalEvent(uiEntity, bae); - - SetCurrentSingleUser(uiEntity, actor.PlayerSession, aui); - _uiSystem.OpenUi(ui, actor.PlayerSession); - - //Let the component know a user opened it so it can do whatever it needs to do - var aae = new AfterActivatableUIOpenEvent(user, actor.PlayerSession); - RaiseLocalEvent(uiEntity, aae); - - return true; - } - - public void SetCurrentSingleUser(EntityUid uid, ICommonSession? v, ActivatableUIComponent? aui = null) - { - if (!Resolve(uid, ref aui)) - return; - if (!aui.SingleUser) - return; - - aui.CurrentSingleUser = v; - - RaiseLocalEvent(uid, new ActivatableUIPlayerChangedEvent()); - } - - public void CloseAll(EntityUid uid, ActivatableUIComponent? aui = null) - { - if (!Resolve(uid, ref aui, false)) - return; - - if (aui.Key == null || !_uiSystem.TryGetUi(uid, aui.Key, out var ui)) - return; - - _uiSystem.CloseAll(ui); - } - - private void OnHandDeselected(EntityUid uid, ActivatableUIComponent? aui, HandDeselectedEvent args) - { - if (!Resolve(uid, ref aui, false)) - return; - - if (!aui.CloseOnHandDeselect) - return; - - CloseAll(uid, aui); - } -} diff --git a/Content.Server/UserInterface/IntrinsicUIComponent.cs b/Content.Server/UserInterface/IntrinsicUIComponent.cs index 4d3c7ffba9..83936edc8c 100644 --- a/Content.Server/UserInterface/IntrinsicUIComponent.cs +++ b/Content.Server/UserInterface/IntrinsicUIComponent.cs @@ -9,18 +9,12 @@ public sealed partial class IntrinsicUIComponent : Component /// /// List of UIs and their actions that this entity has. /// - [DataField("uis", required: true)] public List UIs = new(); + [DataField("uis", required: true)] public Dictionary UIs = new(); } [DataDefinition] -public partial class IntrinsicUIEntry +public sealed partial class IntrinsicUIEntry { - /// - /// The BUI key that this intrinsic UI should open. - /// - [DataField("key", required: true)] - public Enum? Key { get; private set; } - [DataField("toggleAction", customTypeSerializer: typeof(PrototypeIdSerializer), required: true)] public string? ToggleAction; diff --git a/Content.Server/UserInterface/IntrinsicUISystem.cs b/Content.Server/UserInterface/IntrinsicUISystem.cs index fa725e524a..0f7261865d 100644 --- a/Content.Server/UserInterface/IntrinsicUISystem.cs +++ b/Content.Server/UserInterface/IntrinsicUISystem.cs @@ -18,50 +18,31 @@ public override void Initialize() private void OnActionToggle(EntityUid uid, IntrinsicUIComponent component, ToggleIntrinsicUIEvent args) { + if (args.Key == null) + return; + args.Handled = InteractUI(uid, args.Key, component); } private void InitActions(EntityUid uid, IntrinsicUIComponent component, MapInitEvent args) { - foreach (var entry in component.UIs) + foreach (var entry in component.UIs.Values) { _actionsSystem.AddAction(uid, ref entry.ToggleActionEntity, entry.ToggleAction); } } - public bool InteractUI(EntityUid uid, Enum? key, IntrinsicUIComponent? iui = null, ActorComponent? actor = null) + public bool InteractUI(EntityUid uid, Enum key, IntrinsicUIComponent? iui = null, ActorComponent? actor = null) { if (!Resolve(uid, ref iui, ref actor)) return false; - if (key is null) - { - Log.Error($"Entity {ToPrettyString(uid)} has an invalid intrinsic UI."); - } - - var ui = GetUIOrNull(uid, key, iui); - - if (ui is null) - { - Log.Error($"Couldn't get UI {key} on {ToPrettyString(uid)}"); - return false; - } - var attempt = new IntrinsicUIOpenAttemptEvent(uid, key); RaiseLocalEvent(uid, attempt); if (attempt.Cancelled) return false; - _uiSystem.ToggleUi(ui, actor.PlayerSession); - return true; - } - - private PlayerBoundUserInterface? GetUIOrNull(EntityUid uid, Enum? key, IntrinsicUIComponent? component = null) - { - if (!Resolve(uid, ref component)) - return null; - - return key is null ? null : _uiSystem.GetUiOrNull(uid, key); + return _uiSystem.TryToggleUi(uid, key, actor.PlayerSession); } } diff --git a/Content.Server/Vampire/BloodSuckerSystem.cs b/Content.Server/Vampire/BloodSuckerSystem.cs index a63334a894..41afe0d666 100644 --- a/Content.Server/Vampire/BloodSuckerSystem.cs +++ b/Content.Server/Vampire/BloodSuckerSystem.cs @@ -6,14 +6,15 @@ using Content.Shared.Inventory; using Content.Shared.Administration.Logs; using Content.Shared.Vampiric; +using Content.Shared.Cocoon; using Content.Server.Atmos.Components; using Content.Server.Body.Components; using Content.Server.Body.Systems; using Content.Shared.Chemistry.EntitySystems; using Content.Server.Popups; -using Content.Server.HealthExaminable; using Content.Server.DoAfter; using Content.Server.Nutrition.Components; +using Content.Shared.HealthExaminable; using Robust.Shared.Prototypes; using Robust.Shared.Audio.Systems; using Robust.Shared.Utility; @@ -45,23 +46,28 @@ public override void Initialize() private void AddSuccVerb(EntityUid uid, BloodSuckerComponent component, GetVerbsEvent args) { - if (args.User == args.Target) - return; - if (component.WebRequired) - return; // handled elsewhere - if (!TryComp(args.Target, out var bloodstream)) + + var victim = args.Target; + var ignoreClothes = false; + + if (TryComp(args.Target, out var cocoon)) + { + victim = cocoon.Victim ?? args.Target; + ignoreClothes = cocoon.Victim != null; + } else if (component.WebRequired) return; - if (!args.CanAccess) + + if (!TryComp(victim, out var bloodstream) || args.User == victim || !args.CanAccess) return; InnateVerb verb = new() { Act = () => { - StartSuccDoAfter(uid, args.Target, component, bloodstream); // start doafter + StartSuccDoAfter(uid, victim, component, bloodstream, !ignoreClothes); // start doafter }, Text = Loc.GetString("action-name-suck-blood"), - Icon = new SpriteSpecifier.Texture(new ("/Textures/Nyanotrasen/Icons/verbiconfangs.png")), + Icon = new SpriteSpecifier.Texture(new("/Textures/Nyanotrasen/Icons/verbiconfangs.png")), Priority = 2 }; args.Verbs.Add(verb); @@ -80,10 +86,8 @@ private void OnDamageChanged(EntityUid uid, BloodSuckedComponent component, Dama if (_prototypeManager.TryIndex("Brute", out var brute) && args.Damageable.Damage.TryGetDamageInGroup(brute, out var bruteTotal) && _prototypeManager.TryIndex("Airloss", out var airloss) && args.Damageable.Damage.TryGetDamageInGroup(airloss, out var airlossTotal)) - { if (bruteTotal == 0 && airlossTotal == 0) RemComp(uid); - } } private void OnDoAfter(EntityUid uid, BloodSuckerComponent component, BloodSuckDoAfterEvent args) @@ -96,18 +100,13 @@ private void OnDoAfter(EntityUid uid, BloodSuckerComponent component, BloodSuckD public void StartSuccDoAfter(EntityUid bloodsucker, EntityUid victim, BloodSuckerComponent? bloodSuckerComponent = null, BloodstreamComponent? stream = null, bool doChecks = true) { - if (!Resolve(bloodsucker, ref bloodSuckerComponent)) - return; - - if (!Resolve(victim, ref stream)) + if (!Resolve(bloodsucker, ref bloodSuckerComponent) || !Resolve(victim, ref stream)) return; if (doChecks) { if (!_interactionSystem.InRangeUnobstructed(bloodsucker, victim)) - { return; - } if (_inventorySystem.TryGetSlotEntity(victim, "head", out var headUid) && HasComp(headUid)) { @@ -125,19 +124,15 @@ public void StartSuccDoAfter(EntityUid bloodsucker, EntityUid victim, BloodSucke } if (stream.BloodReagent != "Blood") - { - _popups.PopupEntity(Loc.GetString("bloodsucker-fail-not-blood", ("target", victim)), victim, bloodsucker, Shared.Popups.PopupType.Medium); - return; - } - - if (_solutionSystem.PercentFull(stream.Owner) != 0) + _popups.PopupEntity(Loc.GetString("bloodsucker-not-blood", ("target", victim)), victim, bloodsucker, Shared.Popups.PopupType.Medium); + else if (_solutionSystem.PercentFull(victim) != 0) _popups.PopupEntity(Loc.GetString("bloodsucker-fail-no-blood", ("target", victim)), victim, bloodsucker, Shared.Popups.PopupType.Medium); + else + _popups.PopupEntity(Loc.GetString("bloodsucker-doafter-start", ("target", victim)), victim, bloodsucker, Shared.Popups.PopupType.Medium); _popups.PopupEntity(Loc.GetString("bloodsucker-doafter-start-victim", ("sucker", bloodsucker)), victim, victim, Shared.Popups.PopupType.LargeCaution); - _popups.PopupEntity(Loc.GetString("bloodsucker-doafter-start", ("target", victim)), victim, bloodsucker, Shared.Popups.PopupType.Medium); - var ev = new BloodSuckDoAfterEvent(); - var args = new DoAfterArgs(EntityManager, bloodsucker, bloodSuckerComponent.Delay, ev, bloodsucker, target: victim) + var args = new DoAfterArgs(EntityManager, bloodsucker, bloodSuckerComponent.Delay, new BloodSuckDoAfterEvent(), bloodsucker, target: victim) { BreakOnTargetMove = true, BreakOnUserMove = false, @@ -206,8 +201,5 @@ public bool TrySucc(EntityUid bloodsucker, EntityUid victim, BloodSuckerComponen //} return true; } - - private record struct BloodSuckData() - {} } } diff --git a/Content.Server/VendingMachines/VendingMachineSystem.cs b/Content.Server/VendingMachines/VendingMachineSystem.cs index 36fa69313e..60be1ad6f1 100644 --- a/Content.Server/VendingMachines/VendingMachineSystem.cs +++ b/Content.Server/VendingMachines/VendingMachineSystem.cs @@ -114,7 +114,7 @@ private void UpdateVendingMachineInterfaceState(EntityUid uid, VendingMachineCom { var state = new VendingMachineInterfaceState(GetAllInventory(uid, component)); - _userInterfaceSystem.TrySetUiState(uid, VendingMachineUiKey.Key, state); + _userInterfaceSystem.SetUiState(uid, VendingMachineUiKey.Key, state); } private void OnInventoryEjectMessage(EntityUid uid, VendingMachineComponent component, VendingMachineEjectMessage args) @@ -122,7 +122,7 @@ private void OnInventoryEjectMessage(EntityUid uid, VendingMachineComponent comp if (!this.IsPowered(uid, EntityManager)) return; - if (args.Session.AttachedEntity is not { Valid: true } entity || Deleted(entity)) + if (args.Actor is not { Valid: true } entity || Deleted(entity)) return; AuthorizedVend(uid, entity, args.Type, args.ID, component); diff --git a/Content.Server/VoiceMask/VoiceMaskComponent.cs b/Content.Server/VoiceMask/VoiceMaskComponent.cs index d0c9200300..d3116f94db 100644 --- a/Content.Server/VoiceMask/VoiceMaskComponent.cs +++ b/Content.Server/VoiceMask/VoiceMaskComponent.cs @@ -3,21 +3,38 @@ namespace Content.Server.VoiceMask; +/// +/// This component is for voice mask items! Adding this component to clothing will give the the voice mask UI +/// and allow the wearer to change their voice and verb at will. +/// +/// +/// DO NOT use this if you do not want the interface. +/// The VoiceOverrideSystem is probably what your looking for (Or you might have to make something similar)! +/// [RegisterComponent] public sealed partial class VoiceMaskComponent : Component { + /// + /// The name that will override an entities default name. If null, it will use the default override. + /// + [DataField] + public string? VoiceMaskName = null; + + /// + /// The speech verb that will override an entities default one. If null, it will use the entities default verb. + /// [DataField] - [ViewVariables(VVAccess.ReadWrite)] - public bool Enabled = true; + public ProtoId? VoiceMaskSpeechVerb; + /// + /// The action that gets displayed when the voice mask is equipped. + /// [DataField] - [ViewVariables(VVAccess.ReadWrite)] - public string VoiceName = "Unknown"; + public EntProtoId Action = "ActionChangeVoiceMask"; /// - /// If EnableSpeechVerbModification is true, overrides the speech verb used when this entity speaks. + /// Reference to the action. /// [DataField] - [ViewVariables(VVAccess.ReadWrite)] - public ProtoId? SpeechVerb; + public EntityUid? ActionEntity; } diff --git a/Content.Server/VoiceMask/VoiceMaskSystem.Equip.cs b/Content.Server/VoiceMask/VoiceMaskSystem.Equip.cs deleted file mode 100644 index bd0c40f26a..0000000000 --- a/Content.Server/VoiceMask/VoiceMaskSystem.Equip.cs +++ /dev/null @@ -1,53 +0,0 @@ -using Content.Server.Actions; -using Content.Shared.Inventory; -using Content.Shared.Inventory.Events; - -namespace Content.Server.VoiceMask; - -// This partial deals with equipment, i.e., the syndicate voice mask. -public sealed partial class VoiceMaskSystem -{ - [Dependency] private readonly InventorySystem _inventory = default!; - [Dependency] private readonly ActionsSystem _actions = default!; - - private const string MaskSlot = "mask"; - - private void OnEquip(EntityUid uid, VoiceMaskerComponent component, GotEquippedEvent args) - { - var user = args.Equipee; - // have to be wearing the mask to use it, duh. - if (!_inventory.TryGetSlotEntity(user, MaskSlot, out var maskEntity) || maskEntity != uid) - return; - - var comp = EnsureComp(user); - comp.VoiceName = component.LastSetName; - comp.SpeechVerb = component.LastSpeechVerb; - - _actions.AddAction(user, ref component.ActionEntity, component.Action, uid); - } - - private void OnUnequip(EntityUid uid, VoiceMaskerComponent compnent, GotUnequippedEvent args) - { - RemComp(args.Equipee); - } - - private VoiceMaskerComponent? TryGetMask(EntityUid user) - { - if (!HasComp(user) || !_inventory.TryGetSlotEntity(user, MaskSlot, out var maskEntity)) - return null; - - return CompOrNull(maskEntity); - } - - private void TrySetLastKnownName(EntityUid user, string name) - { - if (TryGetMask(user) is {} comp) - comp.LastSetName = name; - } - - private void TrySetLastSpeechVerb(EntityUid user, string? verb) - { - if (TryGetMask(user) is {} comp) - comp.LastSpeechVerb = verb; - } -} diff --git a/Content.Server/VoiceMask/VoiceMaskSystem.cs b/Content.Server/VoiceMask/VoiceMaskSystem.cs index 19ed156a0b..98f6b18f53 100644 --- a/Content.Server/VoiceMask/VoiceMaskSystem.cs +++ b/Content.Server/VoiceMask/VoiceMaskSystem.cs @@ -1,121 +1,115 @@ -using Content.Server.Administration.Logs; -using Content.Server.Chat.Systems; -using Content.Server.Popups; +using Content.Shared.Actions; +using Content.Shared.Administration.Logs; +using Content.Shared.Chat; using Content.Shared.Clothing; using Content.Shared.Database; -using Content.Shared.Inventory.Events; +using Content.Shared.Implants; +using Content.Shared.Inventory; using Content.Shared.Popups; using Content.Shared.Preferences; using Content.Shared.Speech; using Content.Shared.VoiceMask; -using Robust.Server.GameObjects; -using Robust.Shared.Player; using Robust.Shared.Prototypes; namespace Content.Server.VoiceMask; public sealed partial class VoiceMaskSystem : EntitySystem { - [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; - [Dependency] private readonly PopupSystem _popupSystem = default!; - [Dependency] private readonly IAdminLogManager _adminLogger = default!; + [Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!; + [Dependency] private readonly SharedPopupSystem _popupSystem = default!; + [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly IPrototypeManager _proto = default!; + [Dependency] private readonly SharedActionsSystem _actions = default!; public override void Initialize() { - SubscribeLocalEvent(OnSpeakerNameTransform); + base.Initialize(); + SubscribeLocalEvent>(OnTransformSpeakerName); + SubscribeLocalEvent>(OnTransformSpeakerNameImplant); SubscribeLocalEvent(OnChangeName); SubscribeLocalEvent(OnChangeVerb); - SubscribeLocalEvent(OnMaskToggled); - SubscribeLocalEvent(OnEquip); - SubscribeLocalEvent(OnUnequip); - SubscribeLocalEvent(OnSetName); - // SubscribeLocalEvent>(GetVerbs); + SubscribeLocalEvent(OnEquip); + SubscribeLocalEvent(OpenUI); } - private void OnSetName(VoiceMaskSetNameEvent ev) + private void OnTransformSpeakerName(Entity entity, ref InventoryRelayedEvent args) { - OpenUI(ev.Performer); + args.Args.VoiceName = GetCurrentVoiceName(entity); + args.Args.SpeechVerb = entity.Comp.VoiceMaskSpeechVerb ?? args.Args.SpeechVerb; } - private void OnChangeName(EntityUid uid, VoiceMaskComponent component, VoiceMaskChangeNameMessage message) + private void OnTransformSpeakerNameImplant(Entity entity, ref ImplantRelayEvent args) { - if (message.Name.Length > HumanoidCharacterProfile.MaxNameLength || message.Name.Length <= 0) - { - _popupSystem.PopupEntity(Loc.GetString("voice-mask-popup-failure"), uid, message.Session, PopupType.SmallCaution); - return; - } - - component.VoiceName = message.Name; - if (message.Session.AttachedEntity != null) - _adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(message.Session.AttachedEntity.Value):player} set voice of {ToPrettyString(uid):mask}: {component.VoiceName}"); - else - _adminLogger.Add(LogType.Action, LogImpact.Medium, $"Voice of {ToPrettyString(uid):mask} set: {component.VoiceName}"); - - _popupSystem.PopupEntity(Loc.GetString("voice-mask-popup-success"), uid, message.Session); - - TrySetLastKnownName(uid, message.Name); - - UpdateUI(uid, component); + args.Event.VoiceName = GetCurrentVoiceName(entity); + args.Event.SpeechVerb = entity.Comp.VoiceMaskSpeechVerb ?? args.Event.SpeechVerb; } - private void OnChangeVerb(Entity ent, ref VoiceMaskChangeVerbMessage msg) + #region User inputs from UI + private void OnChangeVerb(Entity entity, ref VoiceMaskChangeVerbMessage msg) { - if (msg.Verb is {} id && !_proto.HasIndex(id)) + if (msg.Verb is { } id && !_proto.HasIndex(id)) return; - ent.Comp.SpeechVerb = msg.Verb; + entity.Comp.VoiceMaskSpeechVerb = msg.Verb; // verb is only important to metagamers so no need to log as opposed to name - _popupSystem.PopupEntity(Loc.GetString("voice-mask-popup-success"), ent, msg.Session); - - TrySetLastSpeechVerb(ent, msg.Verb); + _popupSystem.PopupEntity(Loc.GetString("voice-mask-popup-success"), entity, msg.Actor); - UpdateUI(ent, ent.Comp); + UpdateUI(entity); } - private void OnSpeakerNameTransform(EntityUid uid, VoiceMaskComponent component, TransformSpeakerNameEvent args) + private void OnChangeName(Entity entity, ref VoiceMaskChangeNameMessage message) { - if (component.Enabled) + if (message.Name.Length > HumanoidCharacterProfile.MaxNameLength || message.Name.Length <= 0) { - /* - args.Name = _idCard.TryGetIdCard(uid, out var card) && !string.IsNullOrEmpty(card.FullName) - ? card.FullName - : Loc.GetString("voice-mask-unknown"); - */ - - args.Name = component.VoiceName; - if (component.SpeechVerb != null) - args.SpeechVerb = component.SpeechVerb; + _popupSystem.PopupEntity(Loc.GetString("voice-mask-popup-failure"), entity, message.Actor, PopupType.SmallCaution); + return; } + + entity.Comp.VoiceMaskName = message.Name; + _adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(message.Actor):player} set voice of {ToPrettyString(entity):mask}: {entity.Comp.VoiceMaskName}"); + + _popupSystem.PopupEntity(Loc.GetString("voice-mask-popup-success"), entity, message.Actor); + + UpdateUI(entity); } + #endregion - private void OnMaskToggled(Entity ent, ref WearerMaskToggledEvent args) + #region UI + private void OnEquip(EntityUid uid, VoiceMaskComponent component, ClothingGotEquippedEvent args) { - ent.Comp.Enabled = !args.IsToggled; + _actions.AddAction(args.Wearer, ref component.ActionEntity, component.Action, uid); } - private void OpenUI(EntityUid player, ActorComponent? actor = null) + private void OpenUI(VoiceMaskSetNameEvent ev) { - // Delta-V: `logMissing: false` because of syrinx. - if (!Resolve(player, ref actor, logMissing: false)) + // If this ever becomes an entity remove this! + if (!TryComp(ev.Action, out var actionComp)) + return; + + var maskEntity = actionComp.Container; + + if (!TryComp(maskEntity, out var voiceMaskComp)) return; - if (!_uiSystem.TryGetUi(player, VoiceMaskUIKey.Key, out var bui)) + + if (!_uiSystem.HasUi(maskEntity.Value, VoiceMaskUIKey.Key)) return; - _uiSystem.OpenUi(bui, actor.PlayerSession); - UpdateUI(player); + _uiSystem.OpenUi(maskEntity.Value, VoiceMaskUIKey.Key, ev.Performer); + UpdateUI((maskEntity.Value, voiceMaskComp)); } - private void UpdateUI(EntityUid owner, VoiceMaskComponent? component = null) + private void UpdateUI(Entity entity) { - // Delta-V: `logMissing: false` because of syrinx - if (!Resolve(owner, ref component, logMissing: false)) - { - return; - } + if (_uiSystem.HasUi(entity, VoiceMaskUIKey.Key)) + _uiSystem.SetUiState(entity.Owner, VoiceMaskUIKey.Key, new VoiceMaskBuiState(GetCurrentVoiceName(entity), entity.Comp.VoiceMaskSpeechVerb)); + } + #endregion - if (_uiSystem.TryGetUi(owner, VoiceMaskUIKey.Key, out var bui)) - _uiSystem.SetUiState(bui, new VoiceMaskBuiState(component.VoiceName, component.SpeechVerb)); + #region Helper functions + private string GetCurrentVoiceName(Entity entity) + { + return entity.Comp.VoiceMaskName ?? Loc.GetString("voice-mask-default-name-override"); } + #endregion } diff --git a/Content.Server/VoiceMask/VoiceMaskerComponent.cs b/Content.Server/VoiceMask/VoiceMaskerComponent.cs deleted file mode 100644 index afea5877df..0000000000 --- a/Content.Server/VoiceMask/VoiceMaskerComponent.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Content.Shared.Speech; -using Robust.Shared.Prototypes; - -namespace Content.Server.VoiceMask; - -[RegisterComponent] -public sealed partial class VoiceMaskerComponent : Component -{ - [DataField] - public string LastSetName = "Unknown"; - - [DataField] - public ProtoId? LastSpeechVerb; - - [DataField] - public EntProtoId Action = "ActionChangeVoiceMask"; - - [DataField] - public EntityUid? ActionEntity; -} diff --git a/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs b/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs index d63dd093ee..74e76f0dd5 100644 --- a/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs +++ b/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs @@ -136,8 +136,7 @@ protected override bool DoDisarm(EntityUid user, DisarmAttackEvent ev, EntityUid return false; var chance = CalculateDisarmChance(user, target, inTargetHand, combatMode); - - if (_random.Prob(chance)) + if (!_random.Prob(chance)) { // Don't play a sound as the swing is already predicted. // Also don't play popups because most disarms will miss. @@ -163,7 +162,10 @@ protected override bool DoDisarm(EntityUid user, DisarmAttackEvent ev, EntityUid _audio.PlayPvs(combatMode.DisarmSuccessSound, user, AudioParams.Default.WithVariation(0.025f).WithVolume(5f)); AdminLogger.Add(LogType.DisarmedAction, $"{ToPrettyString(user):user} used disarm on {ToPrettyString(target):target}"); - var eventArgs = new DisarmedEvent { Target = target, Source = user, PushProbability = 1 - chance }; + var staminaDamage = (TryComp(user, out var shoving) ? shoving.StaminaDamage : ShovingComponent.DefaultStaminaDamage) + * Math.Clamp(chance, 0f, 1f); + + var eventArgs = new DisarmedEvent { Target = target, Source = user, PushProbability = chance, StaminaDamage = staminaDamage }; RaiseLocalEvent(target, eventArgs); if (!eventArgs.Handled) @@ -183,15 +185,10 @@ protected override bool InRange(EntityUid user, EntityUid target, float range, I if (session is { } pSession) { (targetCoordinates, targetLocalAngle) = _lag.GetCoordinatesAngle(target, pSession); - } - else - { - var xform = Transform(target); - targetCoordinates = xform.Coordinates; - targetLocalAngle = xform.LocalRotation; + return Interaction.InRangeUnobstructed(user, target, targetCoordinates, targetLocalAngle, range); } - return Interaction.InRangeUnobstructed(user, target, targetCoordinates, targetLocalAngle, range); + return Interaction.InRangeUnobstructed(user, target, range); } protected override void DoDamageEffect(List targets, EntityUid? user, TransformComponent targetXform) @@ -208,17 +205,18 @@ private float CalculateDisarmChance(EntityUid disarmer, EntityUid disarmed, Enti if (HasComp(disarmed)) return 0.0f; - var chance = disarmerComp.BaseDisarmFailChance; + var chance = 1 - disarmerComp.BaseDisarmFailChance; if (inTargetHand != null && TryComp(inTargetHand, out var malus)) - { - chance += malus.Malus; - } + chance -= malus.Malus; + + if (TryComp(disarmer, out var shoving)) + chance += shoving.DisarmBonus; return Math.Clamp(chance - * _contests.MassContest(disarmer, disarmed, false, 0.5f) + * _contests.MassContest(disarmer, disarmed, false, 2f) * _contests.StaminaContest(disarmer, disarmed, false, 0.5f) - * _contests.HealthContest(disarmer, disarmed, false, 0.5f), + * _contests.HealthContest(disarmer, disarmed, false, 1f), 0f, 1f); } diff --git a/Content.Server/Weapons/Melee/ShovingComponent.cs b/Content.Server/Weapons/Melee/ShovingComponent.cs new file mode 100644 index 0000000000..8295845ad0 --- /dev/null +++ b/Content.Server/Weapons/Melee/ShovingComponent.cs @@ -0,0 +1,23 @@ +namespace Content.Server.Weapons.Melee; + +[RegisterComponent] +public sealed partial class ShovingComponent : Component +{ + /// + /// Default shoving stamina damage used if the shoving entity has no ShovingComponent. See . + /// + public const float DefaultStaminaDamage = 50f; + + /// + /// Amount of stamina damage dealt on successful shove if the attacker has a 100% chance to shove the target. + /// If the chance is less than 100% (which it almost always is), the damage is multiplied by the chance. + /// + [DataField] + public float StaminaDamage = DefaultStaminaDamage; + + /// + /// Added to the shove/disarm chance on attacks done by this mob, acts opposite to DisarmMalus for targets. + /// + [DataField] + public float DisarmBonus = 0f; +} diff --git a/Content.Server/Weapons/Ranged/Systems/GunSystem.cs b/Content.Server/Weapons/Ranged/Systems/GunSystem.cs index fbc46f5917..cbbfc289cf 100644 --- a/Content.Server/Weapons/Ranged/Systems/GunSystem.cs +++ b/Content.Server/Weapons/Ranged/Systems/GunSystem.cs @@ -107,7 +107,7 @@ public override void Shoot(EntityUid gunUid, GunComponent gun, List<(EntityUid? // Update shot based on the recoil toMap = fromMap.Position + angle.ToVec() * mapDirection.Length(); mapDirection = toMap - fromMap.Position; - var gunVelocity = Physics.GetMapLinearVelocity(gunUid); + var gunVelocity = Physics.GetMapLinearVelocity(fromEnt); // I must be high because this was getting tripped even when true. // DebugTools.Assert(direction != Vector2.Zero); @@ -156,7 +156,7 @@ public override void Shoot(EntityUid gunUid, GunComponent gun, List<(EntityUid? }); SetCartridgeSpent(ent.Value, cartridge, true); - MuzzleFlash(gunUid, cartridge, user); + MuzzleFlash(gunUid, cartridge, mapDirection.ToAngle(), user); Audio.PlayPredicted(gun.SoundGunshotModified, gunUid, user); if (cartridge.DeleteOnSpawn) @@ -177,7 +177,7 @@ public override void Shoot(EntityUid gunUid, GunComponent gun, List<(EntityUid? // Ammo shoots itself case AmmoComponent newAmmo: shotProjectiles.Add(ent!.Value); - MuzzleFlash(gunUid, newAmmo, user); + MuzzleFlash(gunUid, newAmmo, mapDirection.ToAngle(), user); Audio.PlayPredicted(gun.SoundGunshotModified, gunUid, user); ShootOrThrow(ent.Value, mapDirection, gunVelocity, gun, gunUid, user); break; @@ -336,9 +336,9 @@ private Angle GetRecoilAngle(TimeSpan curTime, GunComponent component, Angle dir protected override void Popup(string message, EntityUid? uid, EntityUid? user) { } - protected override void CreateEffect(EntityUid uid, MuzzleFlashEvent message, EntityUid? user = null) + protected override void CreateEffect(EntityUid gunUid, MuzzleFlashEvent message, EntityUid? user = null) { - var filter = Filter.Pvs(uid, entityManager: EntityManager); + var filter = Filter.Pvs(gunUid, entityManager: EntityManager); if (TryComp(user, out var actor)) filter.RemovePlayer(actor.PlayerSession); @@ -399,7 +399,7 @@ private void FireEffects(EntityCoordinates fromCoordinates, float distance, Angl var (_, gridRot, gridInvMatrix) = TransformSystem.GetWorldPositionRotationInvMatrix(gridXform, xformQuery); fromCoordinates = new EntityCoordinates(gridUid.Value, - gridInvMatrix.Transform(fromCoordinates.ToMapPos(EntityManager, TransformSystem))); + Vector2.Transform(fromCoordinates.ToMapPos(EntityManager, TransformSystem), gridInvMatrix)); // Use the fallback angle I guess? angle -= gridRot; diff --git a/Content.Server/Wires/WiresSystem.cs b/Content.Server/Wires/WiresSystem.cs index 0f9ee8b0f7..fce81db327 100644 --- a/Content.Server/Wires/WiresSystem.cs +++ b/Content.Server/Wires/WiresSystem.cs @@ -17,6 +17,7 @@ using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.Shared.Random; +using ActivatableUISystem = Content.Shared.UserInterface.ActivatableUISystem; namespace Content.Server.Wires; @@ -347,7 +348,7 @@ public override void Update(float frameTime) } } - private class ActiveWireAction + private sealed class ActiveWireAction { /// /// The wire action's ID. This is so that once the action is finished, @@ -393,11 +394,11 @@ private void OnWiresPowered(EntityUid uid, WiresComponent component, ref PowerCh private void OnWiresActionMessage(EntityUid uid, WiresComponent component, WiresActionMessage args) { - if (args.Session.AttachedEntity == null) + if (args.Actor == null) { return; } - var player = (EntityUid) args.Session.AttachedEntity; + var player = (EntityUid) args.Actor; if (!EntityManager.TryGetComponent(player, out HandsComponent? handsComponent)) { @@ -458,7 +459,7 @@ private void OnInteractUsing(EntityUid uid, WiresComponent component, InteractUs { if (TryComp(args.User, out ActorComponent? actor)) { - _uiSystem.TryOpen(uid, WiresUiKey.Key, actor.PlayerSession); + _uiSystem.OpenUi(uid, WiresUiKey.Key, actor.PlayerSession); args.Handled = true; } } @@ -468,7 +469,8 @@ private void OnPanelChanged(Entity ent, ref PanelChangedEvent ar { if (args.Open) return; - _uiSystem.TryCloseAll(ent, WiresUiKey.Key); + + _uiSystem.CloseUi(ent.Owner, WiresUiKey.Key); } private void OnAttemptOpenActivatableUI(EntityUid uid, ActivatableUIRequiresPanelComponent component, ActivatableUIOpenAttemptEvent args) @@ -574,18 +576,17 @@ private void UpdateUserInterface(EntityUid uid, WiresComponent? wires = null, Us statuses.Sort((a, b) => a.position.CompareTo(b.position)); - _uiSystem.TrySetUiState(uid, WiresUiKey.Key, new WiresBoundUserInterfaceState( + _uiSystem.SetUiState((uid, ui), WiresUiKey.Key, new WiresBoundUserInterfaceState( clientList.ToArray(), statuses.Select(p => new StatusEntry(p.key, p.value)).ToArray(), Loc.GetString(wires.BoardName), wires.SerialNumber, - wires.WireSeed), ui: ui); + wires.WireSeed)); } public void OpenUserInterface(EntityUid uid, ICommonSession player) { - if (_uiSystem.TryGetUi(uid, WiresUiKey.Key, out var ui)) - _uiSystem.OpenUi(ui, player); + _uiSystem.OpenUi(uid, WiresUiKey.Key, player); } /// @@ -629,7 +630,7 @@ public void SetWiresPanelSecurity(EntityUid uid, WiresPanelSecurityComponent com if (!args.WiresAccessible) { - _uiSystem.TryCloseAll(uid, WiresUiKey.Key); + _uiSystem.CloseUi(uid, WiresUiKey.Key); } } diff --git a/Content.Server/Worldgen/Prototypes/NoiseChannelPrototype.cs b/Content.Server/Worldgen/Prototypes/NoiseChannelPrototype.cs index 67da4c4df1..02ca383d30 100644 --- a/Content.Server/Worldgen/Prototypes/NoiseChannelPrototype.cs +++ b/Content.Server/Worldgen/Prototypes/NoiseChannelPrototype.cs @@ -80,7 +80,7 @@ public class NoiseChannelConfig } [Prototype("noiseChannel")] -public sealed class NoiseChannelPrototype : NoiseChannelConfig, IPrototype, IInheritingPrototype +public sealed partial class NoiseChannelPrototype : NoiseChannelConfig, IPrototype, IInheritingPrototype { /// [ParentDataField(typeof(AbstractPrototypeIdArraySerializer))] diff --git a/Content.Server/Xenoarchaeology/Equipment/Components/TraversalDistorterComponent.cs b/Content.Server/Xenoarchaeology/Equipment/Components/TraversalDistorterComponent.cs index ec16083c53..1461021e3d 100644 --- a/Content.Server/Xenoarchaeology/Equipment/Components/TraversalDistorterComponent.cs +++ b/Content.Server/Xenoarchaeology/Equipment/Components/TraversalDistorterComponent.cs @@ -1,4 +1,7 @@ -namespace Content.Server.Xenoarchaeology.Equipment.Components; +using Content.Shared.Construction.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Server.Xenoarchaeology.Equipment.Components; /// /// This is used for a machine that biases @@ -8,7 +11,19 @@ public sealed partial class TraversalDistorterComponent : Component { [ViewVariables(VVAccess.ReadWrite)] - public BiasDirection BiasDirection = BiasDirection.In; + public float BiasChance; + + [DataField] + public float BaseBiasChance = 0.7f; + + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string MachinePartBiasChance = "Manipulator"; + + [DataField] + public float PartRatingBiasChance = 1.1f; + + [ViewVariables(VVAccess.ReadWrite)] + public BiasDirection BiasDirection = BiasDirection.Up; public TimeSpan NextActivation = default!; public TimeSpan ActivationDelay = TimeSpan.FromSeconds(1); @@ -16,6 +31,6 @@ public sealed partial class TraversalDistorterComponent : Component public enum BiasDirection : byte { - In, //down the tree, towards depth 0 - Out //up the tree, away from depth 0 + Up, //Towards depth 0 + Down, //Away from depth 0 } diff --git a/Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactAnalyzerSystem.cs b/Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactAnalyzerSystem.cs index 27caebef80..72d99460fe 100644 --- a/Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactAnalyzerSystem.cs +++ b/Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactAnalyzerSystem.cs @@ -41,7 +41,8 @@ public sealed class ArtifactAnalyzerSystem : EntitySystem [Dependency] private readonly PaperSystem _paper = default!; [Dependency] private readonly ResearchSystem _research = default!; [Dependency] private readonly MetaDataSystem _metaSystem = default!; - [Dependency] private readonly GlimmerSystem _glimmerSystem = default!; //Nyano - Summary: pulls in the glimmer system. + [Dependency] private readonly TraversalDistorterSystem _traversalDistorter = default!; + [Dependency] private readonly GlimmerSystem _glimmerSystem = default!; /// public override void Initialize() @@ -63,6 +64,7 @@ public override void Initialize() SubscribeLocalEvent(OnScanButton); SubscribeLocalEvent(OnPrintButton); SubscribeLocalEvent(OnExtractButton); + SubscribeLocalEvent(OnBiasButton); SubscribeLocalEvent((e, c, _) => UpdateUserInterface(e, c), after: new[] { typeof(ResearchSystem) }); @@ -194,6 +196,7 @@ private void UpdateUserInterface(EntityUid uid, AnalysisConsoleComponent? compon var canScan = false; var canPrint = false; var points = 0; + if (TryComp(component.AnalyzerEntity, out var analyzer)) { artifact = analyzer.LastAnalyzedArtifact; @@ -207,18 +210,22 @@ private void UpdateUserInterface(EntityUid uid, AnalysisConsoleComponent? compon if (GetArtifactForAnalysis(component.AnalyzerEntity, placer) is { } current) points = _artifact.GetResearchPointValue(current); } + var analyzerConnected = component.AnalyzerEntity != null; var serverConnected = TryComp(uid, out var client) && client.ConnectedToServer; var scanning = TryComp(component.AnalyzerEntity, out var active); var paused = active != null ? active.AnalysisPaused : false; + var biasDirection = BiasDirection.Up; - var state = new AnalysisConsoleScanUpdateState(GetNetEntity(artifact), analyzerConnected, serverConnected, - canScan, canPrint, msg, scanning, paused, active?.StartTime, active?.AccumulatedRunTime, totalTime, points); + if (TryComp(component.AnalyzerEntity, out var trav)) + biasDirection = trav.BiasDirection; - var bui = _ui.GetUi(uid, ArtifactAnalzyerUiKey.Key); - _ui.SetUiState(bui, state); + var state = new AnalysisConsoleUpdateState(GetNetEntity(artifact), analyzerConnected, serverConnected, + canScan, canPrint, msg, scanning, paused, active?.StartTime, active?.AccumulatedRunTime, totalTime, points, biasDirection == BiasDirection.Down); + + _ui.SetUiState(uid, ArtifactAnalzyerUiKey.Key, state); } /// @@ -229,7 +236,7 @@ private void UpdateUserInterface(EntityUid uid, AnalysisConsoleComponent? compon /// private void OnServerSelectionMessage(EntityUid uid, AnalysisConsoleComponent component, AnalysisConsoleServerSelectionMessage args) { - _ui.TryOpen(uid, ResearchClientUiKey.Key, args.Session); + _ui.OpenUi(uid, ResearchClientUiKey.Key, args.Actor); } /// @@ -382,6 +389,20 @@ private void OnExtractButton(EntityUid uid, AnalysisConsoleComponent component, UpdateUserInterface(uid, component); } + private void OnBiasButton(EntityUid uid, AnalysisConsoleComponent component, AnalysisConsoleBiasButtonPressedMessage args) + { + if (component.AnalyzerEntity == null) + return; + + if (!TryComp(component.AnalyzerEntity, out var trav)) + return; + + if (!_traversalDistorter.SetState(component.AnalyzerEntity.Value, trav, args.IsDown)) + return; + + UpdateUserInterface(uid, component); + } + /// /// Cancels scans if the artifact changes nodes (is activated) during the scan. /// @@ -505,4 +526,3 @@ private void OnPowerChanged(EntityUid uid, ActiveArtifactAnalyzerComponent activ } } } - diff --git a/Content.Server/Xenoarchaeology/Equipment/Systems/TraversalDistorterSystem.cs b/Content.Server/Xenoarchaeology/Equipment/Systems/TraversalDistorterSystem.cs index 230e639af4..67812a8a7b 100644 --- a/Content.Server/Xenoarchaeology/Equipment/Systems/TraversalDistorterSystem.cs +++ b/Content.Server/Xenoarchaeology/Equipment/Systems/TraversalDistorterSystem.cs @@ -1,9 +1,12 @@ -using Content.Server.Popups; +using Content.Server.Construction; +using Content.Server.Popups; using Content.Server.Power.EntitySystems; using Content.Server.Xenoarchaeology.Equipment.Components; +using Content.Server.Xenoarchaeology.XenoArtifacts; using Content.Shared.Examine; using Content.Shared.Interaction; using Content.Shared.Placeable; +using Robust.Shared.Player; using Robust.Shared.Timing; namespace Content.Server.Xenoarchaeology.Equipment.Systems; @@ -11,15 +14,14 @@ namespace Content.Server.Xenoarchaeology.Equipment.Systems; public sealed class TraversalDistorterSystem : EntitySystem { [Dependency] private readonly IGameTiming _timing = default!; - [Dependency] private readonly PopupSystem _popup = default!; /// public override void Initialize() { SubscribeLocalEvent(OnInit); - - SubscribeLocalEvent(OnInteract); SubscribeLocalEvent(OnExamine); + SubscribeLocalEvent(OnRefreshParts); + SubscribeLocalEvent(OnUpgradeExamine); SubscribeLocalEvent(OnItemPlaced); SubscribeLocalEvent(OnItemRemoved); @@ -30,30 +32,25 @@ private void OnInit(EntityUid uid, TraversalDistorterComponent component, MapIni component.NextActivation = _timing.CurTime; } - private void OnInteract(EntityUid uid, TraversalDistorterComponent component, ActivateInWorldEvent args) + /// + /// Switches the state of the traversal distorter between up and down. + /// + /// The distorter's entity + /// The component on the entity + /// If the distorter changed state + public bool SetState(EntityUid uid, TraversalDistorterComponent component, bool isDown) { - if (args.Handled || !this.IsPowered(uid, EntityManager)) - return; + if (!this.IsPowered(uid, EntityManager)) + return false; + if (_timing.CurTime < component.NextActivation) - return; - args.Handled = true; + return false; + component.NextActivation = _timing.CurTime + component.ActivationDelay; - component.BiasDirection = component.BiasDirection == BiasDirection.In - ? BiasDirection.Out - : BiasDirection.In; + component.BiasDirection = isDown ? BiasDirection.Down : BiasDirection.Up; - var toPopup = string.Empty; - switch (component.BiasDirection) - { - case BiasDirection.In: - toPopup = Loc.GetString("traversal-distorter-set-in"); - break; - case BiasDirection.Out: - toPopup = Loc.GetString("traversal-distorter-set-out"); - break; - } - _popup.PopupEntity(toPopup, uid); + return true; } private void OnExamine(EntityUid uid, TraversalDistorterComponent component, ExaminedEvent args) @@ -61,17 +58,28 @@ private void OnExamine(EntityUid uid, TraversalDistorterComponent component, Exa string examine = string.Empty; switch (component.BiasDirection) { - case BiasDirection.In: - examine = Loc.GetString("traversal-distorter-desc-in"); + case BiasDirection.Up: + examine = Loc.GetString("traversal-distorter-desc-up"); break; - case BiasDirection.Out: - examine = Loc.GetString("traversal-distorter-desc-out"); + case BiasDirection.Down: + examine = Loc.GetString("traversal-distorter-desc-down"); break; } - args.PushMarkup(examine); } + private void OnRefreshParts(EntityUid uid, TraversalDistorterComponent component, RefreshPartsEvent args) + { + var biasRating = args.PartRatings[component.MachinePartBiasChance]; + + component.BiasChance = component.BaseBiasChance * MathF.Pow(component.PartRatingBiasChance, biasRating - 1); + } + + private void OnUpgradeExamine(EntityUid uid, TraversalDistorterComponent component, UpgradeExamineEvent args) + { + args.AddPercentageUpgrade("traversal-distorter-upgrade-bias", component.BiasChance / component.BaseBiasChance); + } + private void OnItemPlaced(EntityUid uid, TraversalDistorterComponent component, ref ItemPlacedEvent args) { var bias = EnsureComp(args.OtherEntity); diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.Nodes.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.Nodes.cs index 647f31a895..895bb0217b 100644 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.Nodes.cs +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.Nodes.cs @@ -19,26 +19,46 @@ public sealed partial class ArtifactSystem /// /// /// - /// The amount of nodes it has. - private void GenerateArtifactNodeTree(EntityUid artifact, ref List allNodes, int nodeAmount) + /// The amount of nodes it has. + private void GenerateArtifactNodeTree(EntityUid artifact, List allNodes, int nodesToCreate) { - if (nodeAmount < 1) + if (nodesToCreate < 1) { - Log.Error($"nodeAmount {nodeAmount} is less than 1. Aborting artifact tree generation."); + Log.Error($"nodesToCreate {nodesToCreate} is less than 1. Aborting artifact tree generation."); return; } _usedNodeIds.Clear(); + var uninitializedNodes = new List { new(){ Id = GetValidNodeId() } }; + var createdNodes = 1; - var rootNode = new ArtifactNode + while (uninitializedNodes.Count > 0) { - Id = GetValidNodeId() - }; - var uninitializedNodes = new List { rootNode }; - while (uninitializedNodes.Any()) - { - GenerateNode(artifact, ref uninitializedNodes, ref allNodes, nodeAmount); + var node = uninitializedNodes[0]; + uninitializedNodes.Remove(node); + + node.Trigger = GetRandomTrigger(artifact, ref node); + node.Effect = GetRandomEffect(artifact, ref node); + + var maxChildren = _random.Next(1, MaxEdgesPerNode - 1); + + for (var i = 0; i < maxChildren; i++) + { + if (nodesToCreate <= createdNodes) + { + break; + } + + var child = new ArtifactNode {Id = GetValidNodeId(), Depth = node.Depth + 1}; + node.Edges.Add(child.Id); + child.Edges.Add(node.Id); + + uninitializedNodes.Add(child); + createdNodes++; + } + + allNodes.Add(node); } } @@ -51,44 +71,8 @@ private int GetValidNodeId() } _usedNodeIds.Add(id); - return id; - } - /// - /// Generate an individual node on the tree. - /// - private void GenerateNode(EntityUid artifact, ref List uninitializedNodes, ref List allNodes, int targetNodeAmount) - { - if (!uninitializedNodes.Any()) - return; - - var node = uninitializedNodes.First(); - uninitializedNodes.Remove(node); - - //Generate the connected nodes - var maxEdges = Math.Max(1, targetNodeAmount - allNodes.Count - uninitializedNodes.Count - 1); - maxEdges = Math.Min(maxEdges, MaxEdgesPerNode); - var minEdges = Math.Clamp(targetNodeAmount - allNodes.Count - uninitializedNodes.Count - 1, 0, 1); - - var edgeAmount = _random.Next(minEdges, maxEdges); - - for (var i = 0; i < edgeAmount; i++) - { - var neighbor = new ArtifactNode - { - Depth = node.Depth + 1, - Id = GetValidNodeId() - }; - node.Edges.Add(neighbor.Id); - neighbor.Edges.Add(node.Id); - - uninitializedNodes.Add(neighbor); - } - - node.Trigger = GetRandomTrigger(artifact, ref node); - node.Effect = GetRandomEffect(artifact, ref node); - - allNodes.Add(node); + return id; } //yeah these two functions are near duplicates but i don't @@ -159,6 +143,7 @@ private int GetRandomTargetDepth(Dictionary weights) return key; } } + return _random.Pick(weights.Keys); //shouldn't happen } diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.cs index 955fe827d7..f170075977 100644 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.cs +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.cs @@ -129,7 +129,7 @@ public void RandomizeArtifact(EntityUid uid, ArtifactComponent component) { var nodeAmount = _random.Next(component.NodesMin, component.NodesMax); - GenerateArtifactNodeTree(uid, ref component.NodeTree, nodeAmount); + GenerateArtifactNodeTree(uid, component.NodeTree, nodeAmount); var firstNode = GetRootNode(component.NodeTree); EnterNode(uid, ref firstNode, component); } @@ -184,13 +184,14 @@ public void ForceActivateArtifact(EntityUid uid, EntityUid? user = null, Artifac var currentNode = GetNodeFromId(component.CurrentNodeId.Value, component); currentNode.Triggered = true; - if (currentNode.Edges.Any()) - { - var newNode = GetNewNode(uid, component); - if (newNode == null) - return; - EnterNode(uid, ref newNode, component); - } + if (currentNode.Edges.Count == 0) + return; + + var newNode = GetNewNode(uid, component); + if (newNode == null) + return; + + EnterNode(uid, ref newNode, component); } private ArtifactNode? GetNewNode(EntityUid uid, ArtifactComponent component) @@ -206,19 +207,20 @@ public void ForceActivateArtifact(EntityUid uid, EntityUid? user = null, Artifac if (TryComp(uid, out var bias) && TryComp(bias.Provider, out var trav) && + _random.Prob(trav.BiasChance) && this.IsPowered(bias.Provider, EntityManager)) { switch (trav.BiasDirection) { - case BiasDirection.In: - var foo = allNodes.Where(x => GetNodeFromId(x, component).Depth < currentNode.Depth).ToHashSet(); - if (foo.Any()) - allNodes = foo; + case BiasDirection.Up: + var upNodes = allNodes.Where(x => GetNodeFromId(x, component).Depth < currentNode.Depth).ToHashSet(); + if (upNodes.Count != 0) + allNodes = upNodes; break; - case BiasDirection.Out: - var bar = allNodes.Where(x => GetNodeFromId(x, component).Depth > currentNode.Depth).ToHashSet(); - if (bar.Any()) - allNodes = bar; + case BiasDirection.Down: + var downNodes = allNodes.Where(x => GetNodeFromId(x, component).Depth > currentNode.Depth).ToHashSet(); + if (downNodes.Count != 0) + allNodes = downNodes; break; } } @@ -226,12 +228,14 @@ public void ForceActivateArtifact(EntityUid uid, EntityUid? user = null, Artifac var undiscoveredNodes = allNodes.Where(x => !GetNodeFromId(x, component).Discovered).ToList(); Log.Debug($"Undiscovered nodes: {string.Join(", ", undiscoveredNodes)}"); var newNode = _random.Pick(allNodes); - if (undiscoveredNodes.Any() && _random.Prob(0.75f)) + + if (undiscoveredNodes.Count != 0 && _random.Prob(0.75f)) { newNode = _random.Pick(undiscoveredNodes); } Log.Debug($"Going to node {newNode}"); + return GetNodeFromId(newNode, component); } diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/GasArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/GasArtifactSystem.cs index e24d31a113..dc054d2318 100644 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/GasArtifactSystem.cs +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/GasArtifactSystem.cs @@ -2,6 +2,7 @@ using Content.Server.Atmos.EntitySystems; using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components; using Content.Server.Xenoarchaeology.XenoArtifacts.Events; +using Content.Shared.Atmos; namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems; diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/PortalArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/PortalArtifactSystem.cs index e2d2172355..e44ee6baa1 100644 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/PortalArtifactSystem.cs +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/PortalArtifactSystem.cs @@ -35,8 +35,7 @@ private void OnActivate(Entity artifact, ref ArtifactAc var secondPortal = Spawn(artifact.Comp.PortalProto, _transform.GetMapCoordinates(target)); //Manual position swapping, because the portal that opens doesn't trigger a collision, and doesn't teleport targets the first time. - _transform.SetCoordinates(artifact, Transform(secondPortal).Coordinates); - _transform.SetCoordinates(target, Transform(firstPortal).Coordinates); + _transform.SwapPositions(target, secondPortal); _link.TryLink(firstPortal, secondPortal, true); } diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/ShuffleArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/ShuffleArtifactSystem.cs index b977cb038c..c39627818a 100644 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/ShuffleArtifactSystem.cs +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/ShuffleArtifactSystem.cs @@ -22,7 +22,6 @@ private void OnActivated(EntityUid uid, ShuffleArtifactComponent component, Arti { var mobState = GetEntityQuery(); - List allCoords = new(); List> toShuffle = new(); foreach (var ent in _lookup.GetEntitiesInRange(uid, component.Radius, LookupFlags.Dynamic | LookupFlags.Sundries)) @@ -33,13 +32,15 @@ private void OnActivated(EntityUid uid, ShuffleArtifactComponent component, Arti var xform = Transform(ent); toShuffle.Add((ent, xform)); - allCoords.Add(xform.Coordinates); } - foreach (var xform in toShuffle) + _random.Shuffle(toShuffle); + + while (toShuffle.Count > 1) { - var xformUid = xform.Owner; - _xform.SetCoordinates(xformUid, xform, _random.PickAndTake(allCoords)); + var ent1 = _random.PickAndTake(toShuffle); + var ent2 = _random.PickAndTake(toShuffle); + _xform.SwapPositions((ent1, ent1), (ent2, ent2)); } } } diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/TemperatureArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/TemperatureArtifactSystem.cs index f314d4a4fb..e62ce36b5b 100644 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/TemperatureArtifactSystem.cs +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/TemperatureArtifactSystem.cs @@ -2,6 +2,7 @@ using Content.Server.Atmos.EntitySystems; using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components; using Content.Server.Xenoarchaeology.XenoArtifacts.Events; +using Content.Shared.Atmos; using Robust.Server.GameObjects; namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems; diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactExamineTriggerSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactExamineTriggerSystem.cs index cbade1682e..b7afbcfc8b 100644 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactExamineTriggerSystem.cs +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactExamineTriggerSystem.cs @@ -1,5 +1,6 @@ using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components; using Content.Shared.Examine; +using Content.Shared.Ghost; namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems; @@ -15,6 +16,10 @@ public override void Initialize() private void OnExamine(EntityUid uid, ArtifactExamineTriggerComponent component, ExaminedEvent args) { + // Prevent ghosts from activating this trigger unless they have CanGhostInteract + if (TryComp(args.Examiner, out var ghost) && !ghost.CanGhostInteract) + return; + _artifact.TryActivateArtifact(uid); } } diff --git a/Content.Server/Zombies/IncurableZombieComponent.cs b/Content.Server/Zombies/IncurableZombieComponent.cs index 2695865690..375b814b51 100644 --- a/Content.Server/Zombies/IncurableZombieComponent.cs +++ b/Content.Server/Zombies/IncurableZombieComponent.cs @@ -1,10 +1,16 @@ -namespace Content.Server.Zombies; +using Robust.Shared.Prototypes; + +namespace Content.Server.Zombies; /// -/// This is used for a zombie that cannot be cured by any methods. +/// This is used for a zombie that cannot be cured by any methods. Gives a succumb to zombie infection action. /// [RegisterComponent] public sealed partial class IncurableZombieComponent : Component { + [DataField] + public EntProtoId ZombifySelfActionPrototype = "ActionTurnUndead"; + [DataField] + public EntityUid? Action; } diff --git a/Content.Server/Zombies/InitialInfectedExemptComponent.cs b/Content.Server/Zombies/InitialInfectedExemptComponent.cs index 46077935c6..f2dfda3f87 100644 --- a/Content.Server/Zombies/InitialInfectedExemptComponent.cs +++ b/Content.Server/Zombies/InitialInfectedExemptComponent.cs @@ -1,7 +1,7 @@ namespace Content.Server.Zombies; [RegisterComponent] -public partial class InitialInfectedExemptComponent : Component +public sealed partial class InitialInfectedExemptComponent : Component { } diff --git a/Content.Server/Zombies/PendingZombieComponent.cs b/Content.Server/Zombies/PendingZombieComponent.cs index a49b424c53..811d3f9644 100644 --- a/Content.Server/Zombies/PendingZombieComponent.cs +++ b/Content.Server/Zombies/PendingZombieComponent.cs @@ -16,7 +16,7 @@ public sealed partial class PendingZombieComponent : Component { DamageDict = new () { - { "Poison", 0.3 }, + { "Poison", 0.2 }, } }; @@ -35,6 +35,18 @@ public sealed partial class PendingZombieComponent : Component [DataField("gracePeriod"), ViewVariables(VVAccess.ReadWrite)] public TimeSpan GracePeriod = TimeSpan.Zero; + /// + /// The minimum amount of time initial infected have before they start taking infection damage. + /// + [DataField] + public TimeSpan MinInitialInfectedGrace = TimeSpan.FromMinutes(12.5f); + + /// + /// The maximum amount of time initial infected have before they start taking damage. + /// + [DataField] + public TimeSpan MaxInitialInfectedGrace = TimeSpan.FromMinutes(15f); + /// /// The chance each second that a warning will be shown. /// @@ -50,6 +62,4 @@ public sealed partial class PendingZombieComponent : Component "zombie-infection-warning", "zombie-infection-underway" }; - - [DataField] public EntityUid? Action; } diff --git a/Content.Server/Zombies/ZombieSystem.Transform.cs b/Content.Server/Zombies/ZombieSystem.Transform.cs index c6c71b8034..a906652765 100644 --- a/Content.Server/Zombies/ZombieSystem.Transform.cs +++ b/Content.Server/Zombies/ZombieSystem.Transform.cs @@ -1,4 +1,3 @@ -using Content.Server.Actions; using Content.Server.Atmos.Components; using Content.Server.Body.Components; using Content.Server.Chat; @@ -37,6 +36,7 @@ using Content.Shared.Prying.Components; using Robust.Shared.Audio.Systems; using Content.Shared.Traits.Assorted.Components; +using Content.Server.Abilities.Psionics; namespace Content.Server.Zombies { @@ -60,7 +60,7 @@ public sealed partial class ZombieSystem [Dependency] private readonly MindSystem _mind = default!; [Dependency] private readonly SharedRoleSystem _roles = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly ActionsSystem _actions = default!; // DeltaV - No psionic zombies + [Dependency] private readonly PsionicAbilitiesSystem _psionic = default!; /// /// Handles an entity turning into a zombie when they die or go into crit @@ -80,7 +80,7 @@ private void OnDamageChanged(EntityUid uid, ZombifyOnDeathComponent component, M /// the entity being zombified /// /// - /// ALRIGHT BIG BOY. YOU'VE COME TO THE LAYER OF THE BEAST. THIS IS YOUR WARNING. + /// ALRIGHT BIG BOYS, GIRLS AND ANYONE ELSE. YOU'VE COME TO THE LAYER OF THE BEAST. THIS IS YOUR WARNING. /// This function is the god function for zombie stuff, and it is cursed. I have /// attempted to label everything thouroughly for your sanity. I have attempted to /// rewrite this, but this is how it shall lie eternal. Turn back now. @@ -108,17 +108,9 @@ public void ZombifyEntity(EntityUid target, MobStateComponent? mobState = null) RemComp(target); RemComp(target); - if (TryComp(target, out var psionic)) // DeltaV - Prevent psionic zombies + if (HasComp(target)) // Prevent psionic zombies { - if (psionic.ActivePowers.Count > 0) - { - foreach (var power in psionic.ActivePowers) - { - RemComp(target, power); - } - psionic.ActivePowers.Clear(); - } - RemComp(target); + _psionic.RemoveAllPsionicPowers(target, true); } //funny voice @@ -244,6 +236,11 @@ public void ZombifyEntity(EntityUid target, MobStateComponent? mobState = null) _identity.QueueIdentityUpdate(target); + var htn = EnsureComp(target); + htn.RootTask = new HTNCompoundTask() { Task = "SimpleHostileCompound" }; + htn.Blackboard.SetValue(NPCBlackboard.Owner, target); + _npc.SleepNPC(target, htn); + //He's gotta have a mind var hasMind = _mind.TryGetMind(target, out var mindId, out _); if (hasMind && _mind.TryGetSession(mindId, out var session)) @@ -259,9 +256,6 @@ public void ZombifyEntity(EntityUid target, MobStateComponent? mobState = null) } else { - var htn = EnsureComp(target); - htn.RootTask = new HTNCompoundTask() { Task = "SimpleHostileCompound" }; - htn.Blackboard.SetValue(NPCBlackboard.Owner, target); _npc.WakeNPC(target, htn); } diff --git a/Content.Server/Zombies/ZombieSystem.cs b/Content.Server/Zombies/ZombieSystem.cs index 080bef44e7..552fd2781c 100644 --- a/Content.Server/Zombies/ZombieSystem.cs +++ b/Content.Server/Zombies/ZombieSystem.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.Server.Actions; using Content.Server.Body.Systems; using Content.Server.Chat; using Content.Server.Chat.Systems; @@ -30,6 +31,7 @@ public sealed partial class ZombieSystem : SharedZombieSystem [Dependency] private readonly BloodstreamSystem _bloodstream = default!; [Dependency] private readonly DamageableSystem _damageable = default!; [Dependency] private readonly ChatSystem _chat = default!; + [Dependency] private readonly ActionsSystem _actions = default!; [Dependency] private readonly AutoEmoteSystem _autoEmote = default!; [Dependency] private readonly EmoteOnDamageSystem _emoteOnDamage = default!; [Dependency] private readonly MetaDataSystem _metaData = default!; @@ -62,9 +64,16 @@ public override void Initialize() SubscribeLocalEvent(OnPendingMapInit); + SubscribeLocalEvent(OnPendingMapInit); + SubscribeLocalEvent(OnDamageChanged); } + private void OnPendingMapInit(EntityUid uid, IncurableZombieComponent component, MapInitEvent args) + { + _actions.AddAction(uid, ref component.Action, component.ZombifySelfActionPrototype); + } + private void OnPendingMapInit(EntityUid uid, PendingZombieComponent component, MapInitEvent args) { if (_mobState.IsDead(uid)) @@ -74,6 +83,7 @@ private void OnPendingMapInit(EntityUid uid, PendingZombieComponent component, M } component.NextTick = _timing.CurTime + TimeSpan.FromSeconds(1f); + component.GracePeriod = _random.Next(component.MinInitialInfectedGrace, component.MaxInitialInfectedGrace); } public override void Update(float frameTime) diff --git a/Content.Shared/Access/Components/IdCardComponent.cs b/Content.Shared/Access/Components/IdCardComponent.cs index 26e83c5586..39d5d9d27f 100644 --- a/Content.Shared/Access/Components/IdCardComponent.cs +++ b/Content.Shared/Access/Components/IdCardComponent.cs @@ -40,4 +40,10 @@ public sealed partial class IdCardComponent : Component /// [DataField, ViewVariables(VVAccess.ReadWrite)] public bool BypassLogging; + + [DataField] + public LocId NameLocId = "access-id-card-component-owner-name-job-title-text"; + + [DataField] + public LocId FullNameLocId = "access-id-card-component-owner-full-name-job-title-text"; } diff --git a/Content.Shared/Access/Components/IdCardConsoleComponent.cs b/Content.Shared/Access/Components/IdCardConsoleComponent.cs index c994d83d9c..a34709807b 100644 --- a/Content.Shared/Access/Components/IdCardConsoleComponent.cs +++ b/Content.Shared/Access/Components/IdCardConsoleComponent.cs @@ -3,8 +3,6 @@ using Robust.Shared.GameStates; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; -using Robust.Shared.Prototypes; namespace Content.Shared.Access.Components; diff --git a/Content.Shared/Access/Components/IdExaminableComponent.cs b/Content.Shared/Access/Components/IdExaminableComponent.cs new file mode 100644 index 0000000000..87d3e03a14 --- /dev/null +++ b/Content.Shared/Access/Components/IdExaminableComponent.cs @@ -0,0 +1,6 @@ +using Content.Shared.Access.Systems; + +namespace Content.Shared.Access.Components; + +[RegisterComponent, Access(typeof(IdExaminableSystem))] +public sealed partial class IdExaminableComponent : Component; diff --git a/Content.Shared/Access/SharedAgentIDCardSystem.cs b/Content.Shared/Access/SharedAgentIDCardSystem.cs index ef6690cc35..d027a3937f 100644 --- a/Content.Shared/Access/SharedAgentIDCardSystem.cs +++ b/Content.Shared/Access/SharedAgentIDCardSystem.cs @@ -26,12 +26,14 @@ public sealed class AgentIDCardBoundUserInterfaceState : BoundUserInterfaceState public readonly HashSet Icons; public string CurrentName { get; } public string CurrentJob { get; } + public string CurrentJobIconId { get; } - public AgentIDCardBoundUserInterfaceState(string currentName, string currentJob, HashSet icons) + public AgentIDCardBoundUserInterfaceState(string currentName, string currentJob, string currentJobIconId, HashSet icons) { Icons = icons; CurrentName = currentName; CurrentJob = currentJob; + CurrentJobIconId = currentJobIconId; } } @@ -60,11 +62,11 @@ public AgentIDCardJobChangedMessage(string job) [Serializable, NetSerializable] public sealed class AgentIDCardJobIconChangedMessage : BoundUserInterfaceMessage { - public string JobIcon { get; } + public string JobIconId { get; } - public AgentIDCardJobIconChangedMessage(string jobIcon) + public AgentIDCardJobIconChangedMessage(string jobIconId) { - JobIcon = jobIcon; + JobIconId = jobIconId; } } } diff --git a/Content.Shared/Access/Systems/AccessReaderSystem.cs b/Content.Shared/Access/Systems/AccessReaderSystem.cs index 89c08e0a4e..3670e24bd3 100644 --- a/Content.Shared/Access/Systems/AccessReaderSystem.cs +++ b/Content.Shared/Access/Systems/AccessReaderSystem.cs @@ -153,7 +153,7 @@ public bool IsAllowed( return IsAllowedInternal(access, stationKeys, reader); if (!_containerSystem.TryGetContainer(target, reader.ContainerAccessProvider, out var container)) - return false; + return Paused(target); // when mapping, containers with electronics arent spawned foreach (var entity in container.ContainedEntities) { diff --git a/Content.Server/Access/Systems/IdExaminableSystem.cs b/Content.Shared/Access/Systems/IdExaminableSystem.cs similarity index 85% rename from Content.Server/Access/Systems/IdExaminableSystem.cs rename to Content.Shared/Access/Systems/IdExaminableSystem.cs index c2231605e4..333272e27a 100644 --- a/Content.Server/Access/Systems/IdExaminableSystem.cs +++ b/Content.Shared/Access/Systems/IdExaminableSystem.cs @@ -1,4 +1,3 @@ -using Content.Server.Access.Components; using Content.Shared.Access.Components; using Content.Shared.Examine; using Content.Shared.Inventory; @@ -6,7 +5,7 @@ using Content.Shared.Verbs; using Robust.Shared.Utility; -namespace Content.Server.Access.Systems; +namespace Content.Shared.Access.Systems; public sealed class IdExaminableSystem : EntitySystem { @@ -22,7 +21,7 @@ public override void Initialize() private void OnGetExamineVerbs(EntityUid uid, IdExaminableComponent component, GetVerbsEvent args) { var detailsRange = _examineSystem.IsInDetailsRange(args.User, uid); - var info = GetInfo(uid) ?? Loc.GetString("id-examinable-component-verb-no-id"); + var info = GetMessage(uid); var verb = new ExamineVerb() { @@ -41,7 +40,12 @@ private void OnGetExamineVerbs(EntityUid uid, IdExaminableComponent component, G args.Verbs.Add(verb); } - private string? GetInfo(EntityUid uid) + public string GetMessage(EntityUid uid) + { + return GetInfo(uid) ?? Loc.GetString("id-examinable-component-verb-no-id"); + } + + public string? GetInfo(EntityUid uid) { if (_inventorySystem.TryGetSlotEntity(uid, "id", out var idUid)) { @@ -65,9 +69,9 @@ private string GetNameAndJob(IdCardComponent id) var jobSuffix = string.IsNullOrWhiteSpace(id.JobTitle) ? string.Empty : $" ({id.JobTitle})"; var val = string.IsNullOrWhiteSpace(id.FullName) - ? Loc.GetString("access-id-card-component-owner-name-job-title-text", + ? Loc.GetString(id.NameLocId, ("jobSuffix", jobSuffix)) - : Loc.GetString("access-id-card-component-owner-full-name-job-title-text", + : Loc.GetString(id.FullNameLocId, ("fullName", id.FullName), ("jobSuffix", jobSuffix)); diff --git a/Content.Shared/Access/Systems/SharedIdCardSystem.cs b/Content.Shared/Access/Systems/SharedIdCardSystem.cs index 842e7e7e6a..bfde70f504 100644 --- a/Content.Shared/Access/Systems/SharedIdCardSystem.cs +++ b/Content.Shared/Access/Systems/SharedIdCardSystem.cs @@ -1,13 +1,32 @@ using Content.Shared.Access.Components; +using Content.Shared.Administration.Logs; +using Content.Shared.Database; using Content.Shared.Hands.Components; using Content.Shared.Inventory; using Content.Shared.PDA; +using Content.Shared.Roles; +using Content.Shared.StatusIcon; +using Robust.Shared.Prototypes; namespace Content.Shared.Access.Systems; public abstract class SharedIdCardSystem : EntitySystem { + [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly InventorySystem _inventorySystem = default!; + [Dependency] private readonly MetaDataSystem _metaSystem = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnMapInit); + } + + private void OnMapInit(EntityUid uid, IdCardComponent id, MapInitEvent args) + { + UpdateEntityName(uid, id); + } /// /// Attempt to find an ID card on an entity. This will look in the entity itself, in the entity's hands, and @@ -56,4 +75,143 @@ public bool TryGetIdCard(EntityUid uid, out Entity idCard) idCard = default; return false; } + + /// + /// Attempts to change the job title of a card. + /// Returns true/false. + /// + /// + /// If provided with a player's EntityUid to the player parameter, adds the change to the admin logs. + /// + public bool TryChangeJobTitle(EntityUid uid, string? jobTitle, IdCardComponent? id = null, EntityUid? player = null) + { + if (!Resolve(uid, ref id)) + return false; + + if (!string.IsNullOrWhiteSpace(jobTitle)) + { + jobTitle = jobTitle.Trim(); + + if (jobTitle.Length > IdCardConsoleComponent.MaxJobTitleLength) + jobTitle = jobTitle[..IdCardConsoleComponent.MaxJobTitleLength]; + } + else + { + jobTitle = null; + } + + if (id.JobTitle == jobTitle) + return true; + id.JobTitle = jobTitle; + Dirty(uid, id); + UpdateEntityName(uid, id); + + if (player != null) + { + _adminLogger.Add(LogType.Identity, LogImpact.Low, + $"{ToPrettyString(player.Value):player} has changed the job title of {ToPrettyString(uid):entity} to {jobTitle} "); + } + return true; + } + + public bool TryChangeJobIcon(EntityUid uid, StatusIconPrototype jobIcon, IdCardComponent? id = null, EntityUid? player = null) + { + if (!Resolve(uid, ref id)) + { + return false; + } + + if (id.JobIcon == jobIcon.ID) + { + return true; + } + + id.JobIcon = jobIcon.ID; + Dirty(uid, id); + + if (player != null) + { + _adminLogger.Add(LogType.Identity, LogImpact.Low, + $"{ToPrettyString(player.Value):player} has changed the job icon of {ToPrettyString(uid):entity} to {jobIcon} "); + } + + return true; + } + + public bool TryChangeJobDepartment(EntityUid uid, JobPrototype job, IdCardComponent? id = null) + { + if (!Resolve(uid, ref id)) + return false; + + id.JobDepartments.Clear(); + foreach (var department in _prototypeManager.EnumeratePrototypes()) + { + if (department.Roles.Contains(job.ID)) + id.JobDepartments.Add("department-" + department.ID); + } + + Dirty(uid, id); + + return true; + } + + /// + /// Attempts to change the full name of a card. + /// Returns true/false. + /// + /// + /// If provided with a player's EntityUid to the player parameter, adds the change to the admin logs. + /// + public bool TryChangeFullName(EntityUid uid, string? fullName, IdCardComponent? id = null, EntityUid? player = null) + { + if (!Resolve(uid, ref id)) + return false; + + if (!string.IsNullOrWhiteSpace(fullName)) + { + fullName = fullName.Trim(); + if (fullName.Length > IdCardConsoleComponent.MaxFullNameLength) + fullName = fullName[..IdCardConsoleComponent.MaxFullNameLength]; + } + else + { + fullName = null; + } + + if (id.FullName == fullName) + return true; + id.FullName = fullName; + Dirty(uid, id); + UpdateEntityName(uid, id); + + if (player != null) + { + _adminLogger.Add(LogType.Identity, LogImpact.Low, + $"{ToPrettyString(player.Value):player} has changed the name of {ToPrettyString(uid):entity} to {fullName} "); + } + return true; + } + + /// + /// Changes the name of the id's owner. + /// + /// + /// If either or is empty, it's replaced by placeholders. + /// If both are empty, the original entity's name is restored. + /// + private void UpdateEntityName(EntityUid uid, IdCardComponent? id = null) + { + if (!Resolve(uid, ref id)) + return; + + var jobSuffix = string.IsNullOrWhiteSpace(id.JobTitle) ? string.Empty : $" ({id.JobTitle})"; + + var val = string.IsNullOrWhiteSpace(id.FullName) + ? Loc.GetString(id.NameLocId, + ("jobSuffix", jobSuffix)) + : Loc.GetString(id.FullNameLocId, + ("fullName", id.FullName), + ("jobSuffix", jobSuffix)); + _metaSystem.SetEntityName(uid, val); + } } diff --git a/Content.Shared/ActionBlocker/ActionBlockerSystem.cs b/Content.Shared/ActionBlocker/ActionBlockerSystem.cs index f5ed2df227..47b3997806 100644 --- a/Content.Shared/ActionBlocker/ActionBlockerSystem.cs +++ b/Content.Shared/ActionBlocker/ActionBlockerSystem.cs @@ -169,8 +169,16 @@ public bool CanEmote(EntityUid uid) public bool CanAttack(EntityUid uid, EntityUid? target = null, Entity? weapon = null, bool disarm = false) { + // If target is in a container can we attack + if (target != null && _container.IsEntityInContainer(target.Value)) + { + return false; + } + _container.TryGetOuterContainer(uid, Transform(uid), out var outerContainer); - if (target != null && target != outerContainer?.Owner && _container.IsEntityInContainer(uid)) + + // If we're in a container can we attack the target. + if (target != null && target != outerContainer?.Owner && _container.IsEntityInContainer(uid)) { var containerEv = new CanAttackFromContainerEvent(uid, target); RaiseLocalEvent(uid, containerEv); diff --git a/Content.Shared/Actions/ActionEvents.cs b/Content.Shared/Actions/ActionEvents.cs index 72a566b8c8..6cc50bc21b 100644 --- a/Content.Shared/Actions/ActionEvents.cs +++ b/Content.Shared/Actions/ActionEvents.cs @@ -68,9 +68,10 @@ public void AddAction(ref EntityUid? actionId, string prototypeId) AddAction(ref actionId, prototypeId, Provider); } - public void AddAction(EntityUid actionId) + public void AddAction(EntityUid? actionId) { - Actions.Add(actionId); + if (actionId != null) + Actions.Add(actionId.Value); } } @@ -154,4 +155,9 @@ public abstract partial class BaseActionEvent : HandledEntityEventArgs /// The user performing the action. /// public EntityUid Performer; + + /// + /// The action the event belongs to. + /// + public EntityUid Action; } diff --git a/Content.Shared/Actions/BaseActionComponent.cs b/Content.Shared/Actions/BaseActionComponent.cs index 6d9242acc1..9156f747f5 100644 --- a/Content.Shared/Actions/BaseActionComponent.cs +++ b/Content.Shared/Actions/BaseActionComponent.cs @@ -1,15 +1,16 @@ -using Content.Shared.Mobs; -using Robust.Shared.Audio; +using Robust.Shared.Audio; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization; using Robust.Shared.Utility; namespace Content.Shared.Actions; -// TODO ACTIONS make this a seprate component and remove the inheritance stuff. +// TODO ACTIONS make this a separate component and remove the inheritance stuff. // TODO ACTIONS convert to auto comp state? // TODO add access attribute. Need to figure out what to do with decal & mapping actions. // [Access(typeof(SharedActionsSystem))] +[EntityCategory("Actions")] public abstract partial class BaseActionComponent : Component { public abstract BaseActionEvent? BaseEvent { get; } @@ -25,6 +26,11 @@ public abstract partial class BaseActionComponent : Component /// [DataField("iconOn")] public SpriteSpecifier? IconOn; + /// + /// For toggle actions only, background to show when toggled on. + /// + [DataField] public SpriteSpecifier? BackgroundOn; + /// /// If not null, this color will modulate the action icon color. /// diff --git a/Content.Shared/Actions/EntityTargetActionComponent.cs b/Content.Shared/Actions/EntityTargetActionComponent.cs index 9024f42e0e..0680a3d3f5 100644 --- a/Content.Shared/Actions/EntityTargetActionComponent.cs +++ b/Content.Shared/Actions/EntityTargetActionComponent.cs @@ -12,24 +12,27 @@ public sealed partial class EntityTargetActionComponent : BaseTargetActionCompon /// /// The local-event to raise when this action is performed. /// - [DataField("event")] + [DataField] [NonSerialized] public EntityTargetActionEvent? Event; - [DataField("whitelist")] public EntityWhitelist? Whitelist; + [DataField] public EntityWhitelist? Whitelist; + [DataField] public EntityWhitelist? Blacklist; - [DataField("canTargetSelf")] public bool CanTargetSelf = true; + [DataField] public bool CanTargetSelf = true; } [Serializable, NetSerializable] public sealed class EntityTargetActionComponentState : BaseActionComponentState { public EntityWhitelist? Whitelist; + public EntityWhitelist? Blacklist; public bool CanTargetSelf; public EntityTargetActionComponentState(EntityTargetActionComponent component, IEntityManager entManager) : base(component, entManager) { Whitelist = component.Whitelist; + Blacklist = component.Blacklist; CanTargetSelf = component.CanTargetSelf; } } diff --git a/Content.Shared/Actions/Events/ActionPerformedEvent.cs b/Content.Shared/Actions/Events/ActionPerformedEvent.cs new file mode 100644 index 0000000000..530d7c9335 --- /dev/null +++ b/Content.Shared/Actions/Events/ActionPerformedEvent.cs @@ -0,0 +1,8 @@ +namespace Content.Shared.Actions.Events; + +/// +/// Raised on the action entity when it is used and . +/// +/// The entity that performed this action. +[ByRefEvent] +public readonly record struct ActionPerformedEvent(EntityUid Performer); diff --git a/Content.Shared/Actions/Events/AnomalyPowerActionEvent.cs b/Content.Shared/Actions/Events/AnomalyPowerActionEvent.cs new file mode 100644 index 0000000000..9184460153 --- /dev/null +++ b/Content.Shared/Actions/Events/AnomalyPowerActionEvent.cs @@ -0,0 +1,384 @@ +using Content.Shared.Anomaly.Effects.Components; +using Content.Shared.Atmos; +using Content.Shared.Damage; +using Robust.Shared.Audio; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; +using Content.Shared.Explosion; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Actions.Events; + +public sealed partial class AnomalyPowerActionEvent : InstantActionEvent +{ + /// + /// Contains settings common to all "Anomalist" Powers. + /// + [DataField] + public AnomalyPowerSettings Settings; + + /// + /// Contains settings specific to "Bluespace Anomaly" powers. + /// + [DataField] + public BluespaceAnomalySettings? Bluespace; + + /// + /// Contains settings specific to "Electrical Anomaly" powers. + /// + [DataField] + public ElectricalAnomalySettings? Electricity; + + /// + /// What entities will be spawned by this action, using the same arguments as an EntitySpawnAnomalyComponent. + /// + [DataField] + public List? EntitySpawnEntries; + + /// + /// Contains settings specific to "Explosion Anomaly" powers. + /// + [DataField] + public ExplosionAnomalySettings? Explosion; + + /// + /// Contains settings specific to "Gas Producer Anomaly" powers. + /// + [DataField] + public GasProducerAnomalySettings? Gas; + + /// + /// Contains settings specific to "Gravity Anomaly" powers. + /// + [DataField] + public GravityAnomalySettings? Gravity; + + /// + /// Contains settings specific to "Injection Anomaly" powers. + /// + [DataField] + public InjectionAnomalySettings? Injection; + + /// + /// Contains settings specific to "Puddle Create Anomaly" powers. + /// + [DataField] + public PuddleAnomalySettings? Puddle; + + /// + /// Contains settings specific to "Pyroclastic Anomaly" powers. + /// + [DataField] + public PyroclasticAnomalySettings? Pyroclastic; +} + +[DataRecord] +public partial record struct AnomalyPowerSettings() +{ + public string PowerName; + + public float ManaCost; + + public bool CheckInsulation; + + /// + /// When casting above the Supercritical Threshold, if not 0, this will cause all powers to enter cooldown for the given duration. + /// + public float OverchargeCooldown; + + /// + /// When casting above the Supercritical Threshold, if not 0, this will deal recoil damage to the caster of the specified amounts. + /// + public DamageSpecifier? OverchargeRecoil; + + /// + /// When casting above the Supercritical Threshold, play a popup above the caster's head. + /// + public string? OverchargeFeedback; + + /// + /// The minimum amount of glimmer generated by this power. + /// + public int MinGlimmer; + + /// + /// The maximum amount of glimmer generated by this power. + /// + public int MaxGlimmer; + + /// + /// The amount to multiply glimmer generation by when above the Supercritical Threshold + /// + public int SupercriticalGlimmerMultiplier = 1; + + /// + /// The threshold of glimmer at which this power will play a sound. + /// + public float GlimmerSoundThreshold; + + /// + /// The glimmer threshold(divided by amplification and multiplied by dampening) at which this power will act as a Supercritical Anomaly. + /// + public float SupercriticalThreshold = 500f; + + /// + /// The maximum amount Dampening can increase the Supercritical threshold to. + /// + public float MaxSupercriticalThreshold = 800f; + + /// + /// The sound to be played upon activating this power(and not Supercritically) + /// + public SoundSpecifier? PulseSound = new SoundCollectionSpecifier("RadiationPulse"); + + /// + /// The sound plays when this power is activated above a Supercritical glimmer threshold + /// + public SoundSpecifier? SupercriticalSound = new SoundCollectionSpecifier("Explosion"); + + public bool DoSupercritical = true; +} + +[DataRecord] +public partial record struct BluespaceAnomalySettings() +{ + /// + /// The maximum radius that the shuffle effect will extend for + /// scales with stability + /// + public float MaxShuffleRadius = 10; + + /// + /// Whether or not a standard pulse teleports the caster. + /// + public bool PulseTeleportsCaster; + + /// + /// Whether or not a supercrit teleports the caster. + /// + public bool SupercritTeleportsCaster; + + /// + /// How far the supercritical event can teleport you + /// + public float SupercriticalTeleportRadius = 50f; + + /// + /// The sound played after players are shuffled/teleported around + /// + public SoundSpecifier TeleportSound = new SoundPathSpecifier("/Audio/Effects/teleport_arrival.ogg"); +} + +[DataRecord] +public partial record struct ElectricalAnomalySettings() +{ + /// + /// the minimum number of lightning strikes + /// + public int MinBoltCount = 2; + + /// + /// The number of lightning strikes, at the maximum severity of the anomaly + /// + public int MaxBoltCount = 5; + + /// + /// The maximum radius of the passive electrocution effect + /// scales with stability + /// + public float MaxElectrocuteRange = 7f; + + /// + /// Energy consumed from devices by the emp pulse upon going supercritical. + /// + public float EmpEnergyConsumption = 100000f; + + /// + /// Duration of devices being disabled by the emp pulse upon going supercritical. + /// + public float EmpDisabledDuration = 60f; +} + +[DataRecord] +public partial record struct ExplosionAnomalySettings() +{ + /// + /// The explosion prototype to spawn + /// + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string? ExplosionPrototype = default!; + + /// + /// The total amount of intensity an explosion can achieve + /// + public float TotalIntensity = 100f; + + /// + /// How quickly does the explosion's power slope? Higher = smaller area and more concentrated damage, lower = larger area and more spread out damage + /// + public float Dropoff = 10f; + + /// + /// How much intensity can be applied per tile? + /// + public float MaxTileIntensity = 10f; + + /// + /// The explosion prototype to spawn on Supercrit + /// + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string? SupercritExplosionPrototype = default!; + + /// + /// The total amount of intensity an explosion can achieve + /// + public float SupercritTotalIntensity = 100f; + + /// + /// How quickly does the explosion's power slope? Higher = smaller area and more concentrated damage, lower = larger area and more spread out damage + /// + public float SupercritDropoff = 10f; + + /// + /// How much intensity can be applied per tile? + /// + public float SupercritMaxTileIntensity = 10f; +} + +[DataRecord] +public partial record struct GasProducerAnomalySettings() +{ + /// + /// The gas to release + /// + public Gas ReleasedGas = Gas.WaterVapor; + + /// + /// The gas to release + /// + public Gas SupercritReleasedGas = Gas.WaterVapor; + + /// + /// The amount of gas released passively + /// + public float MoleAmount = 1f; + + /// + /// The radius of random gas spawns. + /// + public float SpawnRadius = 3; + + /// + /// The number of tiles which will be modified. + /// + public int TileCount = 1; + + /// + /// The the amount the temperature should be modified by (negative for decreasing temp) + /// + public float TempChange = 0; + + /// + /// The amount of gas released when the anomaly reaches max severity + /// + public float SupercritMoleAmount = 150f; + + /// + /// The radius of random gas spawns. + /// + public float SupercritSpawnRadius = 10; + + /// + /// The number of tiles which will be modified. + /// + public int SupercritTileCount = 10; + + /// + /// The the amount the temperature should be modified by (negative for decreasing temp) + /// + public float SupercritTempChange = 0; +} + +[DataRecord] +public partial record struct GravityAnomalySettings() +{ + /// + /// The maximum distance from which the anomaly + /// can throw you via a pulse. + /// + public float MaxThrowRange = 5f; + + /// + /// The maximum strength the anomaly + /// can throw you via a pulse + /// + public float MaxThrowStrength = 10; + + /// + /// The range around the anomaly that will be spaced on supercritical. + /// + public float SpaceRange = 3f; +} + +[DataRecord] +public partial record struct InjectionAnomalySettings() +{ + /// + /// the maximum amount of injection of a substance into an entity per pulsation + /// scales with Severity + /// + public float MaxSolutionInjection = 15; + + /// + /// The maximum amount of injection of a substance into an entity in the supercritical phase + /// + public float SuperCriticalSolutionInjection = 50; + + /// + /// The maximum radius in which the anomaly injects reagents into the surrounding containers. + /// + public float InjectRadius = 3; + + /// + /// The maximum radius in which the anomaly injects reagents into the surrounding containers. + /// + public float SuperCriticalInjectRadius = 15; + + /// + /// The name of the prototype of the special effect that appears above the entities into which the injection was carried out + /// + public EntProtoId VisualEffectPrototype = "PuddleSparkle"; + + /// + /// Solution name that can be drained. + /// + public string Solution { get; set; } = "default"; +} + +[DataRecord] +public partial record struct PuddleAnomalySettings() +{ + /// + /// The maximum amount of solution that an anomaly can splash out of the storage on the floor during pulsation. + /// Scales with Amplification. + /// + public float MaxPuddleSize = 100; + + /// + /// Solution name that can be drained. + /// + public string Solution { get; set; } = "default"; +} + +[DataRecord] +public partial record struct PyroclasticAnomalySettings() +{ + /// + /// The maximum distance from which entities will be ignited. + /// + public float MaximumIgnitionRadius = 5f; + + /// + /// The maximum distance from which entities will be ignited on a Supercrit cast. + /// + public float SupercritMaximumIgnitionRadius = 20f; +} diff --git a/Content.Shared/Actions/Events/DarkSwapActionEvent.cs b/Content.Shared/Actions/Events/DarkSwapActionEvent.cs new file mode 100644 index 0000000000..ad60d0ede4 --- /dev/null +++ b/Content.Shared/Actions/Events/DarkSwapActionEvent.cs @@ -0,0 +1,9 @@ +namespace Content.Shared.Actions.Events; +public sealed partial class DarkSwapActionEvent : InstantActionEvent +{ + [DataField] + public float ManaCost; + + [DataField] + public bool CheckInsulation; +} \ No newline at end of file diff --git a/Content.Shared/Actions/Events/PsionicHealOtherPowerActionEvent.cs b/Content.Shared/Actions/Events/PsionicHealOtherPowerActionEvent.cs new file mode 100644 index 0000000000..cdcba6740c --- /dev/null +++ b/Content.Shared/Actions/Events/PsionicHealOtherPowerActionEvent.cs @@ -0,0 +1,72 @@ +using Robust.Shared.Audio; +using Content.Shared.Damage; +using Content.Shared.Popups; + +namespace Content.Shared.Actions.Events; +public sealed partial class PsionicHealOtherPowerActionEvent : EntityTargetActionEvent +{ + /// + /// Caster's Amplification that has been modified by the results of a MoodContest. + /// + public float ModifiedAmplification = default!; + + /// + /// Caster's Dampening that has been modified by the results of a MoodContest. + /// + public float ModifiedDampening = default!; + + [DataField] + public DamageSpecifier? HealingAmount = default!; + + [DataField] + public string PowerName = default!; + + /// Controls whether or not a power fires immediately and with no DoAfter + [DataField] + public bool Immediate; + + [DataField] + public string? PopupText; + + [DataField] + public float? RotReduction; + + [DataField] + public bool DoRevive; + + [DataField] + public bool BreakOnUserMove = true; + + [DataField] + public bool BreakOnTargetMove = false; + + [DataField] + public float UseDelay = 8f; + + [DataField] + public int MinGlimmer = 8; + + [DataField] + public int MaxGlimmer = 12; + + [DataField] + public int GlimmerSoundThreshold; + + [DataField] + public int GlimmerPopupThreshold; + + [DataField] + public int GlimmerDoAfterVisibilityThreshold; + + [DataField] + public PopupType PopupType = PopupType.Medium; + + [DataField] + public AudioParams AudioParams = default!; + + [DataField] + public bool PlaySound; + + [DataField] + public SoundSpecifier SoundUse = new SoundPathSpecifier("/Audio/Psionics/heartbeat_fast.ogg"); +} diff --git a/Content.Shared/Actions/Events/SummonPsionicFamiliarActionEvent.cs b/Content.Shared/Actions/Events/SummonPsionicFamiliarActionEvent.cs new file mode 100644 index 0000000000..0df9d86f51 --- /dev/null +++ b/Content.Shared/Actions/Events/SummonPsionicFamiliarActionEvent.cs @@ -0,0 +1,54 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.Actions.Events; + +public sealed partial class SummonPsionicFamiliarActionEvent : InstantActionEvent +{ + /// + /// The entity to be spawned by this power. + /// + [DataField] + public EntProtoId? FamiliarProto; + + /// + /// The name of this power, used for logging purposes. + /// + [DataField] + public string PowerName; + + /// + /// How much Mana this power should cost, if any. + /// + [DataField] + public float ManaCost; + + /// + /// Whether this power checks if the wearer is psionically insulated. + /// + [DataField] + public bool CheckInsulation; + + /// + /// Whether this power generates glimmer when used. + /// + [DataField] + public bool DoGlimmerEffects; + + /// + /// Whether the summoned entity will follow the one who summoned it. + /// + [DataField] + public bool FollowMaster; + + /// + /// The minimum amount of glimmer generated by this power. + /// + [DataField] + public int MinGlimmer; + + /// + /// The maximum amount of glimmer generated by this power. + /// + [DataField] + public int MaxGlimmer; +} diff --git a/Content.Shared/Actions/Events/ValidateActionEntityTargetEvent.cs b/Content.Shared/Actions/Events/ValidateActionEntityTargetEvent.cs new file mode 100644 index 0000000000..9f22e7973a --- /dev/null +++ b/Content.Shared/Actions/Events/ValidateActionEntityTargetEvent.cs @@ -0,0 +1,4 @@ +namespace Content.Shared.Actions.Events; + +[ByRefEvent] +public record struct ValidateActionEntityTargetEvent(EntityUid User, EntityUid Target, bool Cancelled = false); diff --git a/Content.Shared/Actions/Events/ValidateActionWorldTargetEvent.cs b/Content.Shared/Actions/Events/ValidateActionWorldTargetEvent.cs new file mode 100644 index 0000000000..43e398aad4 --- /dev/null +++ b/Content.Shared/Actions/Events/ValidateActionWorldTargetEvent.cs @@ -0,0 +1,6 @@ +using Robust.Shared.Map; + +namespace Content.Shared.Actions.Events; + +[ByRefEvent] +public record struct ValidateActionWorldTargetEvent(EntityUid User, EntityCoordinates Target, bool Cancelled = false); diff --git a/Content.Shared/Actions/SharedActionsSystem.cs b/Content.Shared/Actions/SharedActionsSystem.cs index 9f3fb96410..6445039b9c 100644 --- a/Content.Shared/Actions/SharedActionsSystem.cs +++ b/Content.Shared/Actions/SharedActionsSystem.cs @@ -8,14 +8,13 @@ using Content.Shared.Interaction; using Content.Shared.Inventory.Events; using Content.Shared.Mind; -using Content.Shared.Mobs.Components; +using Content.Shared.Rejuvenate; using Robust.Shared.Audio.Systems; using Robust.Shared.Containers; using Robust.Shared.GameStates; using Robust.Shared.Map; using Robust.Shared.Timing; using Robust.Shared.Utility; -using Content.Shared.Rejuvenate; namespace Content.Shared.Actions; @@ -145,9 +144,6 @@ public bool ResolveActionData( public void SetCooldown(EntityUid? actionId, TimeSpan start, TimeSpan end) { - if (actionId == null) - return; - if (!TryGetActionData(actionId, out var action)) return; @@ -163,9 +159,6 @@ public void SetCooldown(EntityUid? actionId, TimeSpan cooldown) public void ClearCooldown(EntityUid? actionId) { - if (actionId == null) - return; - if (!TryGetActionData(actionId, out var action)) return; @@ -176,6 +169,27 @@ public void ClearCooldown(EntityUid? actionId) Dirty(actionId.Value, action); } + /// + /// Sets the cooldown for this action only if it is bigger than the one it already has. + /// + public void SetIfBiggerCooldown(EntityUid? actionId, TimeSpan? cooldown) + { + if (cooldown == null || + cooldown.Value <= TimeSpan.Zero || + !TryGetActionData(actionId, out var action)) + { + return; + } + + var start = GameTiming.CurTime; + var end = start + cooldown; + if (action.Cooldown?.End > end) + return; + + action.Cooldown = (start, end.Value); + Dirty(actionId.Value, action); + } + public void StartUseDelay(EntityUid? actionId) { if (actionId == null) @@ -389,7 +403,7 @@ private void OnActionRequest(RequestPerformActionEvent ev, EntitySessionEventArg var targetWorldPos = _transformSystem.GetWorldPosition(entityTarget); _rotateToFaceSystem.TryFaceCoordinates(user, targetWorldPos); - if (!ValidateEntityTarget(user, entityTarget, entityAction)) + if (!ValidateEntityTarget(user, entityTarget, (actionEnt, entityAction))) return; _adminLogger.Add(LogType.Action, @@ -413,7 +427,7 @@ private void OnActionRequest(RequestPerformActionEvent ev, EntitySessionEventArg var entityCoordinatesTarget = GetCoordinates(netCoordinatesTarget); _rotateToFaceSystem.TryFaceCoordinates(user, entityCoordinatesTarget.ToMapPos(EntityManager, _transformSystem)); - if (!ValidateWorldTarget(user, entityCoordinatesTarget, worldAction)) + if (!ValidateWorldTarget(user, entityCoordinatesTarget, (actionEnt, worldAction))) return; _adminLogger.Add(LogType.Action, @@ -439,13 +453,26 @@ private void OnActionRequest(RequestPerformActionEvent ev, EntitySessionEventArg } if (performEvent != null) + { performEvent.Performer = user; + performEvent.Action = actionEnt; + } // All checks passed. Perform the action! PerformAction(user, component, actionEnt, action, performEvent, curTime); } - public bool ValidateEntityTarget(EntityUid user, EntityUid target, EntityTargetActionComponent action) + public bool ValidateEntityTarget(EntityUid user, EntityUid target, Entity actionEnt) + { + if (!ValidateEntityTargetBase(user, target, actionEnt)) + return false; + + var ev = new ValidateActionEntityTargetEvent(user, target); + RaiseLocalEvent(actionEnt, ref ev); + return !ev.Cancelled; + } + + private bool ValidateEntityTargetBase(EntityUid user, EntityUid target, EntityTargetActionComponent action) { if (!target.IsValid() || Deleted(target)) return false; @@ -453,6 +480,9 @@ public bool ValidateEntityTarget(EntityUid user, EntityUid target, EntityTargetA if (action.Whitelist != null && !action.Whitelist.IsValid(target, EntityManager)) return false; + if (action.Blacklist != null && action.Blacklist.IsValid(target, EntityManager)) + return false; + if (action.CheckCanInteract && !_actionBlockerSystem.CanInteract(user, target)) return false; @@ -475,16 +505,20 @@ public bool ValidateEntityTarget(EntityUid user, EntityUid target, EntityTargetA return distance <= action.Range; } - if (_interactionSystem.InRangeUnobstructed(user, target, range: action.Range) - && _containerSystem.IsInSameOrParentContainer(user, target)) - { - return true; - } + return _interactionSystem.InRangeAndAccessible(user, target, range: action.Range); + } + + public bool ValidateWorldTarget(EntityUid user, EntityCoordinates coords, Entity action) + { + if (!ValidateWorldTargetBase(user, coords, action)) + return false; - return _interactionSystem.CanAccessViaStorage(user, target); + var ev = new ValidateActionWorldTargetEvent(user, coords); + RaiseLocalEvent(action, ref ev); + return !ev.Cancelled; } - public bool ValidateWorldTarget(EntityUid user, EntityCoordinates coords, WorldTargetActionComponent action) + private bool ValidateWorldTargetBase(EntityUid user, EntityCoordinates coords, WorldTargetActionComponent action) { if (action.CheckCanInteract && !_actionBlockerSystem.CanInteract(user, null)) return false; @@ -532,13 +566,12 @@ public void PerformAction(EntityUid performer, ActionsComponent? component, Enti handled = actionEvent.Handled; } - _audio.PlayPredicted(action.Sound, performer,predicted ? performer : null); - handled |= action.Sound != null; - if (!handled) return; // no interaction occurred. - // reduce charges, start cooldown, and mark as dirty (if required). + // play sound, reduce charges, start cooldown, and mark as dirty (if required). + + _audio.PlayPredicted(action.Sound, performer,predicted ? performer : null); var dirty = toggledBefore == action.Toggled; @@ -561,6 +594,9 @@ public void PerformAction(EntityUid performer, ActionsComponent? component, Enti if (dirty && component != null) Dirty(performer, component); + + var ev = new ActionPerformedEvent(performer); + RaiseLocalEvent(actionId, ref ev); } #endregion diff --git a/Content.Shared/Administration/AdminFlags.cs b/Content.Shared/Administration/AdminFlags.cs index 9842e638c2..71a08a70db 100644 --- a/Content.Shared/Administration/AdminFlags.cs +++ b/Content.Shared/Administration/AdminFlags.cs @@ -100,6 +100,16 @@ public enum AdminFlags : uint /// Stealth = 1 << 16, + /// + /// Allows you to use Admin chat + /// + Adminchat = 1 << 17, + + /// + /// Permits the visibility of Pii in game and on SS14 Admin + /// + Pii = 1 << 18, + /// /// DeltaV - The ability to whitelist people. Either this permission or +BAN is required for remove. /// diff --git a/Content.Shared/Administration/Components/SharedHeadstandComponent.cs b/Content.Shared/Administration/Components/SharedHeadstandComponent.cs index ebc23c7b05..96a4dfc2dd 100644 --- a/Content.Shared/Administration/Components/SharedHeadstandComponent.cs +++ b/Content.Shared/Administration/Components/SharedHeadstandComponent.cs @@ -1,6 +1,9 @@ -namespace Content.Shared.Administration.Components; +using Robust.Shared.GameStates; + +namespace Content.Shared.Administration.Components; /// /// Flips the target's sprite on it's head, so they do a headstand. /// +[NetworkedComponent] public abstract partial class SharedHeadstandComponent : Component { } diff --git a/Content.Shared/Administration/Components/SharedKillSignComponent.cs b/Content.Shared/Administration/Components/SharedKillSignComponent.cs index 2e6d54ca88..9a95454f72 100644 --- a/Content.Shared/Administration/Components/SharedKillSignComponent.cs +++ b/Content.Shared/Administration/Components/SharedKillSignComponent.cs @@ -1,5 +1,8 @@ -namespace Content.Shared.Administration.Components; +using Robust.Shared.GameStates; +namespace Content.Shared.Administration.Components; + +[NetworkedComponent] public abstract partial class SharedKillSignComponent : Component { diff --git a/Content.Shared/Administration/JobWhitelistsEuiState.cs b/Content.Shared/Administration/JobWhitelistsEuiState.cs new file mode 100644 index 0000000000..e993ab3aa5 --- /dev/null +++ b/Content.Shared/Administration/JobWhitelistsEuiState.cs @@ -0,0 +1,35 @@ +using Content.Shared.Eui; +using Content.Shared.Roles; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; + +namespace Content.Shared.Administration; + +[Serializable, NetSerializable] +public sealed class JobWhitelistsEuiState : EuiStateBase +{ + public string PlayerName; + public HashSet> Whitelists; + + public JobWhitelistsEuiState(string playerName, HashSet> whitelists) + { + PlayerName = playerName; + Whitelists = whitelists; + } +} + +/// +/// Tries to add or remove a whitelist of a job for a player. +/// +[Serializable, NetSerializable] +public sealed class SetJobWhitelistedMessage : EuiMessageBase +{ + public ProtoId Job; + public bool Whitelisting; + + public SetJobWhitelistedMessage(ProtoId job, bool whitelisting) + { + Job = job; + Whitelisting = whitelisting; + } +} diff --git a/Content.Shared/Alert/AlertCategory.cs b/Content.Shared/Alert/AlertCategory.cs index 7450f585a4..57a3e40f70 100644 --- a/Content.Shared/Alert/AlertCategory.cs +++ b/Content.Shared/Alert/AlertCategory.cs @@ -10,6 +10,7 @@ public enum AlertCategory Breathing, Buckled, Health, + Mood, Internals, Stamina, Piloting, diff --git a/Content.Shared/Alert/AlertType.cs b/Content.Shared/Alert/AlertType.cs index dc323dc64a..bd8c1dbe25 100644 --- a/Content.Shared/Alert/AlertType.cs +++ b/Content.Shared/Alert/AlertType.cs @@ -25,6 +25,22 @@ public enum AlertType : byte HumanHealth, BorgBattery, BorgBatteryNone, + + // Mood + Bleeding, + Insane, + Horrible, + Terrible, + Bad, + Meh, + Neutral, + Good, + Great, + Exceptional, + Perfect, + MoodDead, + CultBuffed, + PilotingShuttle, Peckish, Starving, @@ -55,6 +71,8 @@ public enum AlertType : byte BorgCrit, BorgDead, Offer, + ShadowkinPower, + Deflecting, } } diff --git a/Content.Shared/Announcements/Prototypes/AnnouncerPrototype.cs b/Content.Shared/Announcements/Prototypes/AnnouncerPrototype.cs index 42db148df9..1cb9ba085f 100644 --- a/Content.Shared/Announcements/Prototypes/AnnouncerPrototype.cs +++ b/Content.Shared/Announcements/Prototypes/AnnouncerPrototype.cs @@ -7,7 +7,7 @@ namespace Content.Shared.Announcements.Prototypes; /// Defines an announcer and their announcement file paths /// [Prototype("announcer")] -public sealed class AnnouncerPrototype : IPrototype +public sealed partial class AnnouncerPrototype : IPrototype { [IdDataField] public string ID { get; } = default!; diff --git a/Content.Shared/Anomaly/SharedAnomalySystem.cs b/Content.Shared/Anomaly/SharedAnomalySystem.cs index da1d31c6ff..ccc8b7bc5c 100644 --- a/Content.Shared/Anomaly/SharedAnomalySystem.cs +++ b/Content.Shared/Anomaly/SharedAnomalySystem.cs @@ -368,14 +368,14 @@ public override void Update(float frameTime) /// /// Gets random points around the anomaly based on the given parameters. /// - public List? GetSpawningPoints(EntityUid uid, float stability, float severity, AnomalySpawnSettings settings, float powerModifier = 1f) + public List? GetSpawningPoints(EntityUid uid, float stability, float severity, AnomalySpawnSettings settings, float powerModifier = 1f, float minAmountOffset = 0, float maxAmountOffset = 0) { var xform = Transform(uid); if (!TryComp(xform.GridUid, out var grid)) return null; - var amount = (int) (MathHelper.Lerp(settings.MinAmount, settings.MaxAmount, severity * stability * powerModifier) + 0.5f); + var amount = (int) MathF.Round(MathHelper.Lerp(settings.MinAmount + minAmountOffset, settings.MaxAmount + maxAmountOffset, severity * stability * powerModifier) + 0.5f); var localpos = xform.Coordinates.Position; var tilerefs = grid.GetLocalTilesIntersecting( diff --git a/Content.Shared/Antag/AntagAcceptability.cs b/Content.Shared/Antag/AntagAcceptability.cs index 98abe713eb..f56be97503 100644 --- a/Content.Shared/Antag/AntagAcceptability.cs +++ b/Content.Shared/Antag/AntagAcceptability.cs @@ -20,3 +20,16 @@ public enum AntagAcceptability All } +public enum AntagSelectionTime : byte +{ + /// + /// Antag roles are assigned before players are assigned jobs and spawned in. + /// This prevents antag selection from happening if the round is on-going. + /// + PrePlayerSpawn, + + /// + /// Antag roles get assigned after players have been assigned jobs and have spawned in. + /// + PostPlayerSpawn +} diff --git a/Content.Shared/Arachne/ArachneComponent.cs b/Content.Shared/Arachne/ArachneComponent.cs deleted file mode 100644 index 04c369cc45..0000000000 --- a/Content.Shared/Arachne/ArachneComponent.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Robust.Shared.GameStates; - -namespace Content.Shared.Arachne -{ - [RegisterComponent, NetworkedComponent] - public sealed partial class ArachneComponent : Component - { - [DataField("cocoonDelay")] - public float CocoonDelay = 12f; - - [DataField("cocoonKnockdownMultiplier")] - public float CocoonKnockdownMultiplier = 0.5f; - - /// - /// Blood reagent required to web up a mob. - /// - - [DataField("webBloodReagent")] - public string WebBloodReagent = "Blood"; - } -} diff --git a/Content.Shared/Arachne/Events.cs b/Content.Shared/Arachne/Events.cs deleted file mode 100644 index 02001286ac..0000000000 --- a/Content.Shared/Arachne/Events.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Robust.Shared.Map; -using Robust.Shared.Serialization; -using Content.Shared.DoAfter; - -namespace Content.Shared.Arachne -{ - [Serializable, NetSerializable] - public sealed partial class ArachneCocoonDoAfterEvent : SimpleDoAfterEvent - { - } -} diff --git a/Content.Shared/Arachne/WebComponent.cs b/Content.Shared/Arachne/WebComponent.cs deleted file mode 100644 index c8284f3943..0000000000 --- a/Content.Shared/Arachne/WebComponent.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Robust.Shared.GameStates; - -namespace Content.Shared.Arachne -{ - [RegisterComponent, NetworkedComponent] - public sealed partial class WebComponent : Component - {} -} diff --git a/Content.Shared/Armor/AllowSuitStorageComponent.cs b/Content.Shared/Armor/AllowSuitStorageComponent.cs new file mode 100644 index 0000000000..aa7bce1c87 --- /dev/null +++ b/Content.Shared/Armor/AllowSuitStorageComponent.cs @@ -0,0 +1,10 @@ +namespace Content.Shared.Armor; + +/// +/// Used on outerclothing to allow use of suit storage +/// +[RegisterComponent] +public sealed partial class AllowSuitStorageComponent : Component +{ + +} diff --git a/Content.Shared/Atmos/AtmosDirection.cs b/Content.Shared/Atmos/AtmosDirection.cs index 09ba521aa9..a8155ef88d 100644 --- a/Content.Shared/Atmos/AtmosDirection.cs +++ b/Content.Shared/Atmos/AtmosDirection.cs @@ -104,15 +104,14 @@ public static Angle ToAngle(this AtmosDirection direction) { return direction switch { - AtmosDirection.East => Angle.FromDegrees(90), - AtmosDirection.North => Angle.FromDegrees(180), - AtmosDirection.West => Angle.FromDegrees(270), - AtmosDirection.South => Angle.FromDegrees(0), - - AtmosDirection.NorthEast => Angle.FromDegrees(135), - AtmosDirection.NorthWest => Angle.FromDegrees(205), - AtmosDirection.SouthWest => Angle.FromDegrees(315), - AtmosDirection.SouthEast => Angle.FromDegrees(45), + AtmosDirection.South => Angle.Zero, + AtmosDirection.East => new Angle(MathHelper.PiOver2), + AtmosDirection.North => new Angle(Math.PI), + AtmosDirection.West => new Angle(-MathHelper.PiOver2), + AtmosDirection.NorthEast => new Angle(Math.PI*3/4), + AtmosDirection.NorthWest => new Angle(-Math.PI*3/4), + AtmosDirection.SouthWest => new Angle(-MathHelper.PiOver4), + AtmosDirection.SouthEast => new Angle(MathHelper.PiOver4), _ => throw new ArgumentOutOfRangeException(nameof(direction), $"It was {direction}."), }; diff --git a/Content.Shared/Atmos/Components/GasAnalyzerComponent.cs b/Content.Shared/Atmos/Components/GasAnalyzerComponent.cs index 51ae8cc740..dec9516c01 100644 --- a/Content.Shared/Atmos/Components/GasAnalyzerComponent.cs +++ b/Content.Shared/Atmos/Components/GasAnalyzerComponent.cs @@ -56,13 +56,15 @@ public struct GasMixEntry /// Name of the tab in the UI /// public readonly string Name; + public readonly float Volume; public readonly float Pressure; public readonly float Temperature; public readonly GasEntry[]? Gases; - public GasMixEntry(string name, float pressure, float temperature, GasEntry[]? gases = null) + public GasMixEntry(string name, float volume, float pressure, float temperature, GasEntry[]? gases = null) { Name = name; + Volume = volume; Pressure = pressure; Temperature = temperature; Gases = gases; diff --git a/Content.Shared/Atmos/Components/GasTileOverlayComponent.cs b/Content.Shared/Atmos/Components/GasTileOverlayComponent.cs index e72a1d6758..2c3149b11a 100644 --- a/Content.Shared/Atmos/Components/GasTileOverlayComponent.cs +++ b/Content.Shared/Atmos/Components/GasTileOverlayComponent.cs @@ -1,7 +1,6 @@ using Robust.Shared.GameStates; using Robust.Shared.Serialization; using Robust.Shared.Timing; -using Robust.Shared.Utility; namespace Content.Shared.Atmos.Components; @@ -24,55 +23,47 @@ public sealed partial class GasTileOverlayComponent : Component public GameTick ForceTick { get; set; } } - [Serializable, NetSerializable] -public sealed class GasTileOverlayState : ComponentState, IComponentDeltaState +public sealed class GasTileOverlayState(Dictionary chunks) : ComponentState { - public readonly Dictionary Chunks; - public bool FullState => AllChunks == null; - - // required to infer deleted/missing chunks for delta states - public HashSet? AllChunks; + public readonly Dictionary Chunks = chunks; +} - public GasTileOverlayState(Dictionary chunks) - { - Chunks = chunks; - } +[Serializable, NetSerializable] +public sealed class GasTileOverlayDeltaState( + Dictionary modifiedChunks, + HashSet allChunks) + : ComponentState, IComponentDeltaState +{ + public readonly Dictionary ModifiedChunks = modifiedChunks; + public readonly HashSet AllChunks = allChunks; - public void ApplyToFullState(IComponentState fullState) + public void ApplyToFullState(GasTileOverlayState state) { - DebugTools.Assert(!FullState); - var state = (GasTileOverlayState) fullState; - DebugTools.Assert(state.FullState); - foreach (var key in state.Chunks.Keys) { - if (!AllChunks!.Contains(key)) + if (!AllChunks.Contains(key)) state.Chunks.Remove(key); } - foreach (var (chunk, data) in Chunks) + foreach (var (chunk, data) in ModifiedChunks) { state.Chunks[chunk] = new(data); } } - public IComponentState CreateNewFullState(IComponentState fullState) + public GasTileOverlayState CreateNewFullState(GasTileOverlayState state) { - DebugTools.Assert(!FullState); - var state = (GasTileOverlayState) fullState; - DebugTools.Assert(state.FullState); - - var chunks = new Dictionary(state.Chunks.Count); + var chunks = new Dictionary(AllChunks.Count); - foreach (var (chunk, data) in Chunks) + foreach (var (chunk, data) in ModifiedChunks) { chunks[chunk] = new(data); } foreach (var (chunk, data) in state.Chunks) { - if (AllChunks!.Contains(chunk)) + if (AllChunks.Contains(chunk)) chunks.TryAdd(chunk, new(data)); } diff --git a/Content.Shared/Atmos/Consoles/Components/AtmosAlertsComputerComponent.cs b/Content.Shared/Atmos/Consoles/Components/AtmosAlertsComputerComponent.cs new file mode 100644 index 0000000000..d64c8907af --- /dev/null +++ b/Content.Shared/Atmos/Consoles/Components/AtmosAlertsComputerComponent.cs @@ -0,0 +1,235 @@ +using Content.Shared.Atmos.Consoles; +using Content.Shared.Atmos.Monitor; +using Robust.Shared.GameStates; +using Robust.Shared.Map; +using Robust.Shared.Serialization; + +namespace Content.Shared.Atmos.Components; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +[Access(typeof(SharedAtmosAlertsComputerSystem))] +public sealed partial class AtmosAlertsComputerComponent : Component +{ + /// + /// The current entity of interest (selected via the console UI) + /// + [ViewVariables] + public NetEntity? FocusDevice; + + /// + /// A list of all the atmos devices that will be used to populate the nav map + /// + [ViewVariables, AutoNetworkedField] + public HashSet AtmosDevices = new(); + + /// + /// A list of all the air alarms that have had their alerts silenced on this particular console + /// + [ViewVariables, AutoNetworkedField] + public HashSet SilencedDevices = new(); +} + +[Serializable, NetSerializable] +public struct AtmosAlertsDeviceNavMapData +{ + /// + /// The entity in question + /// + public NetEntity NetEntity; + + /// + /// Location of the entity + /// + public NetCoordinates NetCoordinates; + + /// + /// Used to determine what map icons to use + /// + public AtmosAlertsComputerGroup Group; + + /// + /// Populate the atmos monitoring console nav map with a single entity + /// + public AtmosAlertsDeviceNavMapData(NetEntity netEntity, NetCoordinates netCoordinates, AtmosAlertsComputerGroup group) + { + NetEntity = netEntity; + NetCoordinates = netCoordinates; + Group = group; + } +} + +[Serializable, NetSerializable] +public struct AtmosAlertsFocusDeviceData +{ + /// + /// Focus entity + /// + public NetEntity NetEntity; + + /// + /// Temperature (K) and related alert state + /// + public (float, AtmosAlarmType) TemperatureData; + + /// + /// Pressure (kPA) and related alert state + /// + public (float, AtmosAlarmType) PressureData; + + /// + /// Moles, percentage, and related alert state, for all detected gases + /// + public Dictionary GasData; + + /// + /// Populates the atmos monitoring console focus entry with atmospheric data + /// + public AtmosAlertsFocusDeviceData + (NetEntity netEntity, + (float, AtmosAlarmType) temperatureData, + (float, AtmosAlarmType) pressureData, + Dictionary gasData) + { + NetEntity = netEntity; + TemperatureData = temperatureData; + PressureData = pressureData; + GasData = gasData; + } +} + +[Serializable, NetSerializable] +public sealed class AtmosAlertsComputerBoundInterfaceState : BoundUserInterfaceState +{ + /// + /// A list of all air alarms + /// + public AtmosAlertsComputerEntry[] AirAlarms; + + /// + /// A list of all fire alarms + /// + public AtmosAlertsComputerEntry[] FireAlarms; + + /// + /// Data for the UI focus (if applicable) + /// + public AtmosAlertsFocusDeviceData? FocusData; + + /// + /// Sends data from the server to the client to populate the atmos monitoring console UI + /// + public AtmosAlertsComputerBoundInterfaceState(AtmosAlertsComputerEntry[] airAlarms, AtmosAlertsComputerEntry[] fireAlarms, AtmosAlertsFocusDeviceData? focusData) + { + AirAlarms = airAlarms; + FireAlarms = fireAlarms; + FocusData = focusData; + } +} + +[Serializable, NetSerializable] +public struct AtmosAlertsComputerEntry +{ + /// + /// The entity in question + /// + public NetEntity NetEntity; + + /// + /// Location of the entity + /// + public NetCoordinates Coordinates; + + /// + /// The type of entity + /// + public AtmosAlertsComputerGroup Group; + + /// + /// Current alarm state + /// + public AtmosAlarmType AlarmState; + + /// + /// Localised device name + /// + public string EntityName; + + /// + /// Device network address + /// + public string Address; + + /// + /// Used to populate the atmos monitoring console UI with data from a single air alarm + /// + public AtmosAlertsComputerEntry + (NetEntity entity, + NetCoordinates coordinates, + AtmosAlertsComputerGroup group, + AtmosAlarmType alarmState, + string entityName, + string address) + { + NetEntity = entity; + Coordinates = coordinates; + Group = group; + AlarmState = alarmState; + EntityName = entityName; + Address = address; + } +} + +[Serializable, NetSerializable] +public sealed class AtmosAlertsComputerFocusChangeMessage : BoundUserInterfaceMessage +{ + public NetEntity? FocusDevice; + + /// + /// Used to inform the server that the specified focus for the atmos monitoring console has been changed by the client + /// + public AtmosAlertsComputerFocusChangeMessage(NetEntity? focusDevice) + { + FocusDevice = focusDevice; + } +} + +[Serializable, NetSerializable] +public sealed class AtmosAlertsComputerDeviceSilencedMessage : BoundUserInterfaceMessage +{ + public NetEntity AtmosDevice; + public bool SilenceDevice = true; + + /// + /// Used to inform the server that the client has silenced alerts from the specified device to this atmos monitoring console + /// + public AtmosAlertsComputerDeviceSilencedMessage(NetEntity atmosDevice, bool silenceDevice = true) + { + AtmosDevice = atmosDevice; + SilenceDevice = silenceDevice; + } +} + +/// +/// List of all the different atmos device groups +/// +public enum AtmosAlertsComputerGroup +{ + Invalid, + AirAlarm, + FireAlarm, +} + +[NetSerializable, Serializable] +public enum AtmosAlertsComputerVisuals +{ + ComputerLayerScreen, +} + +/// +/// UI key associated with the atmos monitoring console +/// +[Serializable, NetSerializable] +public enum AtmosAlertsComputerUiKey +{ + Key +} diff --git a/Content.Shared/Atmos/Consoles/Components/AtmosAlertsDeviceComponent.cs b/Content.Shared/Atmos/Consoles/Components/AtmosAlertsDeviceComponent.cs new file mode 100644 index 0000000000..881d60b084 --- /dev/null +++ b/Content.Shared/Atmos/Consoles/Components/AtmosAlertsDeviceComponent.cs @@ -0,0 +1,14 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Atmos.Components; + +[RegisterComponent, NetworkedComponent] +[Access([])] +public sealed partial class AtmosAlertsDeviceComponent : Component +{ + /// + /// The group that the entity belongs to + /// + [DataField, ViewVariables] + public AtmosAlertsComputerGroup Group; +} diff --git a/Content.Shared/Atmos/Consoles/SharedAtmosAlertsComputerSystem.cs b/Content.Shared/Atmos/Consoles/SharedAtmosAlertsComputerSystem.cs new file mode 100644 index 0000000000..7e2b2b0467 --- /dev/null +++ b/Content.Shared/Atmos/Consoles/SharedAtmosAlertsComputerSystem.cs @@ -0,0 +1,24 @@ +using Content.Shared.Atmos.Components; + +namespace Content.Shared.Atmos.Consoles; + +public abstract partial class SharedAtmosAlertsComputerSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnDeviceSilencedMessage); + } + + private void OnDeviceSilencedMessage(EntityUid uid, AtmosAlertsComputerComponent component, AtmosAlertsComputerDeviceSilencedMessage args) + { + if (args.SilenceDevice) + component.SilencedDevices.Add(args.AtmosDevice); + + else + component.SilencedDevices.Remove(args.AtmosDevice); + + Dirty(uid, component); + } +} diff --git a/Content.Shared/Atmos/EntitySystems/SharedGasTileOverlaySystem.cs b/Content.Shared/Atmos/EntitySystems/SharedGasTileOverlaySystem.cs index f468724db3..8e7dfdedaf 100644 --- a/Content.Shared/Atmos/EntitySystems/SharedGasTileOverlaySystem.cs +++ b/Content.Shared/Atmos/EntitySystems/SharedGasTileOverlaySystem.cs @@ -55,7 +55,7 @@ private void OnGetState(EntityUid uid, GasTileOverlayComponent component, ref Co data[index] = chunk; } - args.State = new GasTileOverlayState(data) { AllChunks = new(component.Chunks.Keys) }; + args.State = new GasTileOverlayDeltaState(data, new(component.Chunks.Keys)); } public static Vector2i GetGasChunkIndices(Vector2i indices) diff --git a/Content.Server/Atmos/GasMixture.cs b/Content.Shared/Atmos/GasMixture.cs similarity index 98% rename from Content.Server/Atmos/GasMixture.cs rename to Content.Shared/Atmos/GasMixture.cs index 3d73a4d0b1..a676ed6720 100644 --- a/Content.Server/Atmos/GasMixture.cs +++ b/Content.Shared/Atmos/GasMixture.cs @@ -1,13 +1,12 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.CompilerServices; -using Content.Server.Atmos.Reactions; -using Content.Shared.Atmos; using Content.Shared.Atmos.EntitySystems; +using Content.Shared.Atmos.Reactions; using Robust.Shared.Serialization; using Robust.Shared.Utility; -namespace Content.Server.Atmos +namespace Content.Shared.Atmos { /// /// A general-purpose, variable volume gas mixture. diff --git a/Content.Shared/Atmos/GasMixtureStringRepresentation.cs b/Content.Shared/Atmos/GasMixtureStringRepresentation.cs new file mode 100644 index 0000000000..942b2bdc67 --- /dev/null +++ b/Content.Shared/Atmos/GasMixtureStringRepresentation.cs @@ -0,0 +1,16 @@ +namespace Content.Shared.Atmos; + +public readonly record struct GasMixtureStringRepresentation(float TotalMoles, float Temperature, float Pressure, Dictionary MolesPerGas) : IFormattable +{ + public override string ToString() + { + return $"{Temperature}K {Pressure} kPa"; + } + + public string ToString(string? format, IFormatProvider? formatProvider) + { + return ToString(); + } + + public static implicit operator string(GasMixtureStringRepresentation rep) => rep.ToString(); +} diff --git a/Content.Shared/Atmos/GetFireProtectionEvent.cs b/Content.Shared/Atmos/GetFireProtectionEvent.cs new file mode 100644 index 0000000000..e243295449 --- /dev/null +++ b/Content.Shared/Atmos/GetFireProtectionEvent.cs @@ -0,0 +1,33 @@ +using Content.Shared.Inventory; + +namespace Content.Shared.Atmos; + +/// +/// Raised on a burning entity to check its fire protection. +/// Damage taken is multiplied by the final amount, but not temperature. +/// TemperatureProtection is needed for that. +/// +[ByRefEvent] +public sealed class GetFireProtectionEvent : EntityEventArgs, IInventoryRelayEvent +{ + public SlotFlags TargetSlots { get; } = ~SlotFlags.POCKET; + + /// + /// What to multiply the fire damage by. + /// If this is 0 then it's ignored + /// + public float Multiplier; + + public GetFireProtectionEvent() + { + Multiplier = 1f; + } + + /// + /// Reduce fire damage taken by a percentage. + /// + public void Reduce(float by) + { + Multiplier -= by; + } +} diff --git a/Content.Shared/Atmos/Reactions/GasReactionEnums.cs b/Content.Shared/Atmos/Reactions/GasReactionEnums.cs new file mode 100644 index 0000000000..73b8998d40 --- /dev/null +++ b/Content.Shared/Atmos/Reactions/GasReactionEnums.cs @@ -0,0 +1,14 @@ +namespace Content.Shared.Atmos.Reactions; + +[Flags] +public enum ReactionResult : byte +{ + NoReaction = 0, + Reacting = 1, + StopReactions = 2, +} + +public enum GasReaction : byte +{ + Fire = 0, +} diff --git a/Content.Shared/Audio/Jukebox/JukeboxComponent.cs b/Content.Shared/Audio/Jukebox/JukeboxComponent.cs new file mode 100644 index 0000000000..f9bb385f52 --- /dev/null +++ b/Content.Shared/Audio/Jukebox/JukeboxComponent.cs @@ -0,0 +1,80 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; + +namespace Content.Shared.Audio.Jukebox; + +[NetworkedComponent, RegisterComponent, AutoGenerateComponentState(true)] +[Access(typeof(SharedJukeboxSystem))] +public sealed partial class JukeboxComponent : Component +{ + [DataField, AutoNetworkedField] + public ProtoId? SelectedSongId; + + [DataField, AutoNetworkedField] + public EntityUid? AudioStream; + + /// + /// RSI state for the jukebox being on. + /// + [DataField] + public string? OnState; + + /// + /// RSI state for the jukebox being on. + /// + [DataField] + public string? OffState; + + /// + /// RSI state for the jukebox track being selected. + /// + [DataField] + public string? SelectState; + + [ViewVariables] + public bool Selecting; + + [ViewVariables] + public float SelectAccumulator; +} + +[Serializable, NetSerializable] +public sealed class JukeboxPlayingMessage : BoundUserInterfaceMessage; + +[Serializable, NetSerializable] +public sealed class JukeboxPauseMessage : BoundUserInterfaceMessage; + +[Serializable, NetSerializable] +public sealed class JukeboxStopMessage : BoundUserInterfaceMessage; + +[Serializable, NetSerializable] +public sealed class JukeboxSelectedMessage(ProtoId songId) : BoundUserInterfaceMessage +{ + public ProtoId SongId { get; } = songId; +} + +[Serializable, NetSerializable] +public sealed class JukeboxSetTimeMessage(float songTime) : BoundUserInterfaceMessage +{ + public float SongTime { get; } = songTime; +} + +[Serializable, NetSerializable] +public enum JukeboxVisuals : byte +{ + VisualState +} + +[Serializable, NetSerializable] +public enum JukeboxVisualState : byte +{ + On, + Off, + Select, +} + +public enum JukeboxVisualLayers : byte +{ + Base +} diff --git a/Content.Shared/Audio/Jukebox/JukeboxPrototype.cs b/Content.Shared/Audio/Jukebox/JukeboxPrototype.cs new file mode 100644 index 0000000000..ad690ef497 --- /dev/null +++ b/Content.Shared/Audio/Jukebox/JukeboxPrototype.cs @@ -0,0 +1,23 @@ +using Robust.Shared.Audio; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Audio.Jukebox; + +/// +/// Soundtrack that's visible on the jukebox list. +/// +[Prototype] +public sealed partial class JukeboxPrototype : IPrototype +{ + [IdDataField] + public string ID { get; } = string.Empty; + + /// + /// User friendly name to use in UI. + /// + [DataField(required: true)] + public string Name = string.Empty; + + [DataField(required: true)] + public SoundPathSpecifier Path = default!; +} diff --git a/Content.Shared/Audio/Jukebox/JukeboxUi.cs b/Content.Shared/Audio/Jukebox/JukeboxUi.cs new file mode 100644 index 0000000000..bf1fc3d5d2 --- /dev/null +++ b/Content.Shared/Audio/Jukebox/JukeboxUi.cs @@ -0,0 +1,11 @@ +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; + +namespace Content.Shared.Audio.Jukebox; + + +[Serializable, NetSerializable] +public enum JukeboxUiKey : byte +{ + Key, +} diff --git a/Content.Shared/Audio/Jukebox/SharedJukeboxSystem.cs b/Content.Shared/Audio/Jukebox/SharedJukeboxSystem.cs new file mode 100644 index 0000000000..1a8f9cb3bb --- /dev/null +++ b/Content.Shared/Audio/Jukebox/SharedJukeboxSystem.cs @@ -0,0 +1,8 @@ +using Robust.Shared.Audio.Systems; + +namespace Content.Shared.Audio.Jukebox; + +public abstract class SharedJukeboxSystem : EntitySystem +{ + [Dependency] protected readonly SharedAudioSystem Audio = default!; +} diff --git a/Content.Shared/Bed/Sleep/SharedSleepingSystem.cs b/Content.Shared/Bed/Sleep/SharedSleepingSystem.cs index c6248c88f7..aa5578a3a9 100644 --- a/Content.Shared/Bed/Sleep/SharedSleepingSystem.cs +++ b/Content.Shared/Bed/Sleep/SharedSleepingSystem.cs @@ -31,6 +31,8 @@ public override void Initialize() private void OnMapInit(EntityUid uid, SleepingComponent component, MapInitEvent args) { + component.SleepingSince = _gameTiming.CurTime; + var ev = new SleepStateChangedEvent(true); RaiseLocalEvent(uid, ev); _blindableSystem.UpdateIsBlind(uid); @@ -43,7 +45,10 @@ private void OnMapInit(EntityUid uid, SleepingComponent component, MapInitEvent private void OnShutdown(EntityUid uid, SleepingComponent component, ComponentShutdown args) { _actionsSystem.RemoveAction(uid, component.WakeAction); - var ev = new SleepStateChangedEvent(false); + var ev = new SleepStateChangedEvent(false) + { + TimeSlept = _gameTiming.CurTime - component.SleepingSince + }; RaiseLocalEvent(uid, ev); _blindableSystem.UpdateIsBlind(uid); } @@ -85,6 +90,11 @@ public sealed class SleepStateChangedEvent : EntityEventArgs { public bool FellAsleep = false; + /// + /// The amount of time this entity slept for. Null if is true. + /// + public TimeSpan? TimeSlept; + public SleepStateChangedEvent(bool fellAsleep) { FellAsleep = fellAsleep; diff --git a/Content.Shared/Bed/Sleep/SleepingComponent.cs b/Content.Shared/Bed/Sleep/SleepingComponent.cs index cd468440f4..e87aca2203 100644 --- a/Content.Shared/Bed/Sleep/SleepingComponent.cs +++ b/Content.Shared/Bed/Sleep/SleepingComponent.cs @@ -28,4 +28,10 @@ public sealed partial class SleepingComponent : Component public TimeSpan CoolDownEnd; [DataField("wakeAction")] public EntityUid? WakeAction; + + /// + /// The moment this entity went to sleep. Initialized on MapInit. + /// + [DataField] + public TimeSpan SleepingSince; } diff --git a/Content.Shared/Body/Events/AmputateAttemptEvent.cs b/Content.Shared/Body/Events/AmputateAttemptEvent.cs new file mode 100644 index 0000000000..b71a0407bf --- /dev/null +++ b/Content.Shared/Body/Events/AmputateAttemptEvent.cs @@ -0,0 +1,7 @@ +namespace Content.Shared.Body.Events; + +/// +/// Raised on an entity when attempting to remove a body part. +/// +[ByRefEvent] +public readonly record struct AmputateAttemptEvent(EntityUid Part); diff --git a/Content.Shared/Body/Organ/DebrainedComponent.cs b/Content.Shared/Body/Organ/DebrainedComponent.cs new file mode 100644 index 0000000000..12574bddcc --- /dev/null +++ b/Content.Shared/Body/Organ/DebrainedComponent.cs @@ -0,0 +1,7 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Body.Organ; + +[RegisterComponent] +public sealed partial class DebrainedComponent : Component; +// TODO: Add a timer to kill the entity if they don't get a new brain in time. diff --git a/Content.Shared/Body/Organ/EarsComponent.cs b/Content.Shared/Body/Organ/EarsComponent.cs new file mode 100644 index 0000000000..8041438729 --- /dev/null +++ b/Content.Shared/Body/Organ/EarsComponent.cs @@ -0,0 +1,6 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Body.Organ; + +[RegisterComponent] +public sealed partial class EarsComponent : Component; diff --git a/Content.Shared/Body/Organ/EyesComponent.cs b/Content.Shared/Body/Organ/EyesComponent.cs new file mode 100644 index 0000000000..55be5f1a9c --- /dev/null +++ b/Content.Shared/Body/Organ/EyesComponent.cs @@ -0,0 +1,6 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Body.Organ; + +[RegisterComponent] +public sealed partial class EyesComponent : Component; diff --git a/Content.Shared/Body/Organ/HeartComponent.cs b/Content.Shared/Body/Organ/HeartComponent.cs new file mode 100644 index 0000000000..fc4def945e --- /dev/null +++ b/Content.Shared/Body/Organ/HeartComponent.cs @@ -0,0 +1,6 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Body.Organ; + +[RegisterComponent] +public sealed partial class HeartComponent : Component; diff --git a/Content.Shared/Body/Organ/LiverComponent.cs b/Content.Shared/Body/Organ/LiverComponent.cs new file mode 100644 index 0000000000..23021bea31 --- /dev/null +++ b/Content.Shared/Body/Organ/LiverComponent.cs @@ -0,0 +1,6 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Body.Organ; + +[RegisterComponent] +public sealed partial class LiverComponent : Component; diff --git a/Content.Shared/Body/Organ/MarkingContainerComponent.cs b/Content.Shared/Body/Organ/MarkingContainerComponent.cs new file mode 100644 index 0000000000..0583258dc2 --- /dev/null +++ b/Content.Shared/Body/Organ/MarkingContainerComponent.cs @@ -0,0 +1,15 @@ +// This is a uh, very shitty copout to not wanting to modify the prototypes for felinids, and entities at large so they have ears. +// I will do that at some point, for now I just want the funny surgery to work lol. +using Robust.Shared.GameStates; +using Content.Shared.Humanoid.Markings; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Shared.Body.Organ; + +[RegisterComponent, NetworkedComponent] +public sealed partial class MarkingContainerComponent : Component +{ + [DataField(required: true, customTypeSerializer: typeof(PrototypeIdSerializer))] + public string Marking = default!; + +} diff --git a/Content.Shared/Body/Organ/OrganComponent.cs b/Content.Shared/Body/Organ/OrganComponent.cs index 3048927b5f..c7212cbec3 100644 --- a/Content.Shared/Body/Organ/OrganComponent.cs +++ b/Content.Shared/Body/Organ/OrganComponent.cs @@ -1,16 +1,34 @@ using Content.Shared.Body.Systems; using Robust.Shared.Containers; using Robust.Shared.GameStates; +using Content.Shared.Medical.Surgery.Tools; namespace Content.Shared.Body.Organ; [RegisterComponent, NetworkedComponent, AutoGenerateComponentState] [Access(typeof(SharedBodySystem))] -public sealed partial class OrganComponent : Component +public sealed partial class OrganComponent : Component, ISurgeryToolComponent { /// /// Relevant body this organ is attached to. /// [DataField, AutoNetworkedField] public EntityUid? Body; + + /// + /// Shitcodey solution to not being able to know what name corresponds to each organ's slot ID + /// without referencing the prototype or hardcoding. + /// + + [DataField] + public string SlotId = ""; + + [DataField] + public string ToolName { get; set; } = "An organ"; + + /// + /// If true, the organ will not heal an entity when transplanted into them. + /// + [DataField, AutoNetworkedField] + public bool? Used { get; set; } } diff --git a/Content.Shared/Body/Organ/TailComponent.cs b/Content.Shared/Body/Organ/TailComponent.cs new file mode 100644 index 0000000000..3cd8da87b5 --- /dev/null +++ b/Content.Shared/Body/Organ/TailComponent.cs @@ -0,0 +1,6 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Body.Organ; + +[RegisterComponent] +public sealed partial class TailComponent : Component; diff --git a/Content.Shared/Body/Part/BodyPartAppearanceComponent.cs b/Content.Shared/Body/Part/BodyPartAppearanceComponent.cs new file mode 100644 index 0000000000..1769d68ec7 --- /dev/null +++ b/Content.Shared/Body/Part/BodyPartAppearanceComponent.cs @@ -0,0 +1,45 @@ +using Content.Shared.Humanoid; +using Content.Shared.Humanoid.Prototypes; +using Content.Shared.Humanoid.Markings; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; +using Robust.Shared.GameStates; + +namespace Content.Shared.Body.Part; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)] +public sealed partial class BodyPartAppearanceComponent : Component +{ + /// + /// HumanoidVisualLayer type for this body part. + /// + [DataField, AutoNetworkedField] + public HumanoidVisualLayers Type { get; set; } + + /// + /// Relevant markings for this body part that will be applied on attachment. + /// + [DataField, AutoNetworkedField] + public Dictionary> Markings = new(); + + /// + /// ID of this custom base layer. Must be a . + /// I don't actually know if these serializer props are necessary. I just lifted this from MS14 lol. + /// + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer)), AutoNetworkedField] + public string? ID { get; set; } + + /// + /// Color of this custom base layer. Null implies skin colour if the corresponding is set to match skin. + /// + [DataField, AutoNetworkedField] + public Color? Color { get; set; } + + /// + /// Color of this custom base eye layer. Null implies eye colour if the corresponding is set to match skin. + /// + [DataField, AutoNetworkedField] + public Color? EyeColor { get; set; } + + [DataField, AutoNetworkedField] + public EntityUid? OriginalBody { get; set; } +} diff --git a/Content.Shared/Body/Part/BodyPartComponent.cs b/Content.Shared/Body/Part/BodyPartComponent.cs index c4e65c06a3..2a93f6aed2 100644 --- a/Content.Shared/Body/Part/BodyPartComponent.cs +++ b/Content.Shared/Body/Part/BodyPartComponent.cs @@ -1,5 +1,10 @@ using Content.Shared.Body.Components; using Content.Shared.Body.Systems; +using Content.Shared.Containers.ItemSlots; +using Content.Shared.Damage; +using Content.Shared.FixedPoint; +using Content.Shared.Medical.Surgery.Tools; +using Content.Shared.Targeting; using Robust.Shared.Containers; using Robust.Shared.GameStates; using Robust.Shared.Serialization; @@ -8,7 +13,7 @@ namespace Content.Shared.Body.Part; [RegisterComponent, NetworkedComponent, AutoGenerateComponentState] [Access(typeof(SharedBodySystem))] -public sealed partial class BodyPartComponent : Component +public sealed partial class BodyPartComponent : Component, ISurgeryToolComponent { // Need to set this on container changes as it may be several transform parents up the hierarchy. /// @@ -17,6 +22,12 @@ public sealed partial class BodyPartComponent : Component [DataField, AutoNetworkedField] public EntityUid? Body; + [DataField, AutoNetworkedField] + public EntityUid? OriginalBody; + + [DataField, AutoNetworkedField] + public BodyPartSlot? ParentSlot; + [DataField, AutoNetworkedField] public BodyPartType PartType = BodyPartType.Other; @@ -28,9 +39,23 @@ public sealed partial class BodyPartComponent : Component [DataField("vital"), AutoNetworkedField] public bool IsVital; + /// + /// Amount of damage to deal when the part gets removed. + /// Only works if IsVital is true. + /// + [DataField, AutoNetworkedField] + public FixedPoint2 VitalDamage = 100; + + [DataField, AutoNetworkedField] public BodyPartSymmetry Symmetry = BodyPartSymmetry.None; + [DataField] + public string ToolName { get; set; } = "A body part"; + + [DataField, AutoNetworkedField] + public bool? Used { get; set; } = null; + /// /// Child body parts attached to this body part. /// @@ -43,6 +68,90 @@ public sealed partial class BodyPartComponent : Component [DataField, AutoNetworkedField] public Dictionary Organs = new(); + /// + /// What's the max health this body part can have? + /// + [DataField] + public float MinIntegrity; + + /// + /// Whether this body part is enabled or not. + /// + [DataField, AutoNetworkedField] + public bool Enabled = true; + + /// + /// Whether this body part can be enabled or not. Used for non-functional prosthetics. + /// + [DataField] + public bool CanEnable = true; + + /// + /// How long it takes to run another self heal tick on the body part. + /// + [DataField] + public float HealingTime = 30; + + /// + /// How long it has been since the last self heal tick on the body part. + /// + public float HealingTimer; + + /// + /// How much health to heal on the body part per tick. + /// + [DataField] + public float SelfHealingAmount = 5; + + /// + /// The name of the container for this body part. Used in insertion surgeries. + /// + [DataField] + public string ContainerName { get; set; } = "part_slot"; + + /// + /// The slot for item insertion. + /// + [DataField, AutoNetworkedField] + public ItemSlot ItemInsertionSlot = new(); + + + /// + /// Current species. Dictates things like body part sprites. + /// + [DataField, AutoNetworkedField] + public string Species { get; set; } = ""; + + /// + /// The total damage that has to be dealt to a body part + /// to make possible severing it. + /// + [DataField, AutoNetworkedField] + public float SeverIntegrity = 90; + + /// + /// The ID of the base layer for this body part. + /// + [DataField, AutoNetworkedField] + public string? BaseLayerId; + + /// + /// On what TargetIntegrity we should re-enable the part. + /// + [DataField, AutoNetworkedField] + public TargetIntegrity EnableIntegrity = TargetIntegrity.ModeratelyWounded; + + [DataField, AutoNetworkedField] + public Dictionary IntegrityThresholds = new() + { + { TargetIntegrity.CriticallyWounded, 90 }, + { TargetIntegrity.HeavilyWounded, 75 }, + { TargetIntegrity.ModeratelyWounded, 60 }, + { TargetIntegrity.SomewhatWounded, 40}, + { TargetIntegrity.LightlyWounded, 20 }, + { TargetIntegrity.Healthy, 10 }, + }; + /// /// These are only for VV/Debug do not use these for gameplay/systems /// diff --git a/Content.Shared/Body/Part/BodyPartEvents.cs b/Content.Shared/Body/Part/BodyPartEvents.cs index 0d8d2c8a26..9872b09200 100644 --- a/Content.Shared/Body/Part/BodyPartEvents.cs +++ b/Content.Shared/Body/Part/BodyPartEvents.cs @@ -1,7 +1,27 @@ +using Content.Shared.Humanoid; + namespace Content.Shared.Body.Part; [ByRefEvent] public readonly record struct BodyPartAddedEvent(string Slot, Entity Part); +// Kind of a clone of the above for surgical reattachment specifically. +[ByRefEvent] +public readonly record struct BodyPartAttachedEvent(Entity Part); + [ByRefEvent] public readonly record struct BodyPartRemovedEvent(string Slot, Entity Part); + +// Kind of a clone of the above for any instances where we call DropPart(), reasoning being that RemovedEvent fires off +// a lot more often than what I'd like due to PVS. +[ByRefEvent] +public readonly record struct BodyPartDroppedEvent(Entity Part); + +[ByRefEvent] +public readonly record struct BodyPartEnableChangedEvent(bool Enabled); + +[ByRefEvent] +public readonly record struct BodyPartEnabledEvent(Entity Part); + +[ByRefEvent] +public readonly record struct BodyPartDisabledEvent(Entity Part); diff --git a/Content.Shared/Body/Prototypes/BodyPrototypeSerializer.cs b/Content.Shared/Body/Prototypes/BodyPrototypeSerializer.cs index e2b54bf951..ae09976704 100644 --- a/Content.Shared/Body/Prototypes/BodyPrototypeSerializer.cs +++ b/Content.Shared/Body/Prototypes/BodyPrototypeSerializer.cs @@ -182,7 +182,7 @@ public BodyPrototype Read(ISerializationManager serializationManager, MappingDat foreach (var (slotId, (part, connections, organs)) in allConnections) { - var slot = new BodyPrototypeSlot(part != null ? new EntProtoId(part) : null!, connections ?? new HashSet(), organs ?? new Dictionary()); + var slot = new BodyPrototypeSlot(part, connections ?? new HashSet(), organs ?? new Dictionary()); slots.Add(slotId, slot); } diff --git a/Content.Shared/Body/Prototypes/MetabolismGroupPrototype.cs b/Content.Shared/Body/Prototypes/MetabolismGroupPrototype.cs index 162b5f2d6c..82f4e70ce0 100644 --- a/Content.Shared/Body/Prototypes/MetabolismGroupPrototype.cs +++ b/Content.Shared/Body/Prototypes/MetabolismGroupPrototype.cs @@ -1,4 +1,4 @@ -using Robust.Shared.Prototypes; +using Robust.Shared.Prototypes; namespace Content.Shared.Body.Prototypes { @@ -7,5 +7,11 @@ public sealed partial class MetabolismGroupPrototype : IPrototype { [IdDataField] public string ID { get; private set; } = default!; + + [DataField("name", required: true)] + private LocId Name { get; set; } + + [ViewVariables(VVAccess.ReadOnly)] + public string LocalizedName => Loc.GetString(Name); } } diff --git a/Content.Shared/Body/Prototypes/MetabolizerTypePrototype.cs b/Content.Shared/Body/Prototypes/MetabolizerTypePrototype.cs index c840983ca0..5273ac722b 100644 --- a/Content.Shared/Body/Prototypes/MetabolizerTypePrototype.cs +++ b/Content.Shared/Body/Prototypes/MetabolizerTypePrototype.cs @@ -1,4 +1,4 @@ -using Robust.Shared.Prototypes; +using Robust.Shared.Prototypes; namespace Content.Shared.Body.Prototypes { @@ -9,6 +9,9 @@ public sealed partial class MetabolizerTypePrototype : IPrototype public string ID { get; private set; } = default!; [DataField("name", required: true)] - public string Name { get; private set; } = default!; + private LocId Name { get; set; } + + [ViewVariables(VVAccess.ReadOnly)] + public string LocalizedName => Loc.GetString(Name); } } diff --git a/Content.Shared/Body/Systems/SharedBodySystem.Body.cs b/Content.Shared/Body/Systems/SharedBodySystem.Body.cs index 1a35afdbe0..f309cc238c 100644 --- a/Content.Shared/Body/Systems/SharedBodySystem.Body.cs +++ b/Content.Shared/Body/Systems/SharedBodySystem.Body.cs @@ -4,17 +4,25 @@ using Content.Shared.Body.Organ; using Content.Shared.Body.Part; using Content.Shared.Body.Prototypes; +using Content.Shared.Containers.ItemSlots; +using Content.Shared.Damage; using Content.Shared.DragDrop; +using Content.Shared.FixedPoint; using Content.Shared.Gibbing.Components; using Content.Shared.Gibbing.Events; using Content.Shared.Gibbing.Systems; +using Content.Shared.Humanoid; +using Content.Shared.Humanoid.Events; using Content.Shared.Inventory; +using Content.Shared.Rejuvenate; +using Content.Shared.Standing; +using Content.Shared.Targeting; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Containers; using Robust.Shared.Map; using Robust.Shared.Utility; - +using Robust.Shared.Timing; namespace Content.Shared.Body.Systems; public partial class SharedBodySystem @@ -27,9 +35,10 @@ public partial class SharedBodySystem */ [Dependency] private readonly InventorySystem _inventory = default!; + [Dependency] private readonly ItemSlotsSystem _slots = default!; [Dependency] private readonly GibbingSystem _gibbingSystem = default!; [Dependency] private readonly SharedAudioSystem _audioSystem = default!; - + [Dependency] private readonly IGameTiming _gameTiming = default!; private const float GibletLaunchImpulse = 8; private const float GibletLaunchImpulseVariance = 3; @@ -42,6 +51,8 @@ private void InitializeBody() SubscribeLocalEvent(OnBodyInit); SubscribeLocalEvent(OnBodyMapInit); SubscribeLocalEvent(OnBodyCanDrag); + SubscribeLocalEvent(OnStandAttempt); + SubscribeLocalEvent(OnProfileLoadFinished); } private void OnBodyInserted(Entity ent, ref EntInsertedIntoContainerMessage args) @@ -115,11 +126,11 @@ private void MapInitBody(EntityUid bodyEntity, BodyPrototype prototype) var rootPartUid = SpawnInContainerOrDrop(protoRoot.Part, bodyEntity, BodyRootContainerId); var rootPart = Comp(rootPartUid); rootPart.Body = bodyEntity; + rootPart.OriginalBody = bodyEntity; Dirty(rootPartUid, rootPart); - // Setup the rest of the body entities. SetupOrgans((rootPartUid, rootPart), protoRoot.Organs); - MapInitParts(rootPartUid, prototype); + MapInitParts(rootPartUid, rootPart, prototype); } private void OnBodyCanDrag(Entity ent, ref CanDragEvent args) @@ -127,10 +138,16 @@ private void OnBodyCanDrag(Entity ent, ref CanDragEvent args) args.Handled = true; } + private void OnStandAttempt(Entity ent, ref StandAttemptEvent args) + { + if (ent.Comp.LegEntities.Count == 0) + args.Cancel(); + } + /// /// Sets up all of the relevant body parts for a particular body entity and root part. /// - private void MapInitParts(EntityUid rootPartId, BodyPrototype prototype) + private void MapInitParts(EntityUid rootPartId, BodyPartComponent rootPart, BodyPrototype prototype) { // Start at the root part and traverse the body graph, setting up parts as we go. // Basic BFS pathfind. @@ -168,6 +185,9 @@ private void MapInitParts(EntityUid rootPartId, BodyPrototype prototype) var childPartComponent = Comp(childPart); var partSlot = CreatePartSlot(parentEntity, connection, childPartComponent.PartType, parentPartComponent); + childPartComponent.ParentSlot = partSlot; + childPartComponent.OriginalBody = rootPart.Body; + Dirty(childPart, childPartComponent); var cont = Containers.GetContainer(parentEntity, GetPartSlotContainerId(connection)); if (partSlot is null || !Containers.Insert(childPart, cont)) @@ -233,11 +253,11 @@ public IEnumerable GetBodyContainers( { if (id is null || !Resolve(id.Value, ref body, logMissing: false) + || body is null + || body.RootContainer == default || body.RootContainer.ContainedEntity is null || !Resolve(body.RootContainer.ContainedEntity.Value, ref rootPart)) - { yield break; - } foreach (var child in GetBodyPartChildren(body.RootContainer.ContainedEntity.Value, rootPart)) { @@ -291,7 +311,9 @@ public virtual HashSet GibBody( Vector2? splatDirection = null, float splatModifier = 1, Angle splatCone = default, - SoundSpecifier? gibSoundOverride = null) + SoundSpecifier? gibSoundOverride = null, + GibType gib = GibType.Gib, + GibContentsOption contents = GibContentsOption.Drop) { var gibs = new HashSet(); @@ -308,9 +330,9 @@ public virtual HashSet GibBody( foreach (var part in parts) { - _gibbingSystem.TryGibEntityWithRef(bodyId, part.Id, GibType.Gib, GibContentsOption.Skip, ref gibs, - playAudio: false, launchGibs:true, launchDirection:splatDirection, launchImpulse: GibletLaunchImpulse * splatModifier, - launchImpulseVariance:GibletLaunchImpulseVariance, launchCone: splatCone); + _gibbingSystem.TryGibEntityWithRef(bodyId, part.Id, gib, contents, ref gibs, + playAudio: false, launchGibs: true, launchDirection: splatDirection, launchImpulse: GibletLaunchImpulse * splatModifier, + launchImpulseVariance: GibletLaunchImpulseVariance, launchCone: splatCone); if (!gibOrgans) continue; @@ -319,7 +341,7 @@ public virtual HashSet GibBody( { _gibbingSystem.TryGibEntityWithRef(bodyId, organ.Id, GibType.Drop, GibContentsOption.Skip, ref gibs, playAudio: false, launchImpulse: GibletLaunchImpulse * splatModifier, - launchImpulseVariance:GibletLaunchImpulseVariance, launchCone: splatCone); + launchImpulseVariance: GibletLaunchImpulseVariance, launchCone: splatCone); } } if (TryComp(bodyId, out var inventory)) @@ -333,4 +355,57 @@ public virtual HashSet GibBody( _audioSystem.PlayPredicted(gibSoundOverride, Transform(bodyId).Coordinates, null); return gibs; } + + public virtual HashSet GibPart( + EntityUid partId, + BodyPartComponent? part = null, + bool launchGibs = true, + Vector2? splatDirection = null, + float splatModifier = 1, + Angle splatCone = default, + SoundSpecifier? gibSoundOverride = null) + { + var gibs = new HashSet(); + + if (!Resolve(partId, ref part, logMissing: false)) + return gibs; + + if (part.Body is { } bodyEnt) + { + RemovePartChildren((partId, part), bodyEnt); + foreach (var organ in GetPartOrgans(partId, part)) + { + _gibbingSystem.TryGibEntityWithRef(bodyEnt, organ.Id, GibType.Drop, GibContentsOption.Skip, + ref gibs, playAudio: false, launchImpulse: GibletLaunchImpulse * splatModifier, + launchImpulseVariance: GibletLaunchImpulseVariance, launchCone: splatCone); + } + var ev = new BodyPartDroppedEvent((partId, part)); + RaiseLocalEvent(bodyEnt, ref ev); + } + + _gibbingSystem.TryGibEntityWithRef(partId, partId, GibType.Gib, GibContentsOption.Drop, ref gibs, + playAudio: true, launchGibs: true, launchDirection: splatDirection, launchImpulse: GibletLaunchImpulse * splatModifier, + launchImpulseVariance: GibletLaunchImpulseVariance, launchCone: splatCone); + + + if (HasComp(partId)) + { + foreach (var item in _inventory.GetHandOrInventoryEntities(partId)) + { + SharedTransform.AttachToGridOrMap(item); + gibs.Add(item); + } + } + _audioSystem.PlayPredicted(gibSoundOverride, Transform(partId).Coordinates, null); + return gibs; + } + + private void OnProfileLoadFinished(EntityUid uid, BodyComponent component, ProfileLoadFinishedEvent args) + { + if (!HasComp(uid) + || TerminatingOrDeleted(uid)) + + foreach (var part in GetBodyChildren(uid, component)) + EnsureComp(part.Id); + } } diff --git a/Content.Shared/Body/Systems/SharedBodySystem.Organs.cs b/Content.Shared/Body/Systems/SharedBodySystem.Organs.cs index efabebfc85..e006d0feeb 100644 --- a/Content.Shared/Body/Systems/SharedBodySystem.Organs.cs +++ b/Content.Shared/Body/Systems/SharedBodySystem.Organs.cs @@ -212,4 +212,15 @@ public bool TryGetBodyOrganComponents( comps = null; return false; } + + public bool TrySetOrganUsed(EntityUid organId, bool used, OrganComponent? organ = null) + { + if (!Resolve(organId, ref organ) + || organ.Used == true) + return false; + + organ.Used = true; + Dirty(organId, organ); + return true; + } } diff --git a/Content.Shared/Body/Systems/SharedBodySystem.PartAppearance.cs b/Content.Shared/Body/Systems/SharedBodySystem.PartAppearance.cs new file mode 100644 index 0000000000..50b9fb8c07 --- /dev/null +++ b/Content.Shared/Body/Systems/SharedBodySystem.PartAppearance.cs @@ -0,0 +1,198 @@ +using System.Diagnostics; +using System.Linq; +using Content.Shared.Body.Components; +using Content.Shared.Body.Part; +using Content.Shared.Humanoid; +using Content.Shared.Humanoid.Markings; +using Content.Shared.Humanoid.Prototypes; +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; + +namespace Content.Shared.Body.Systems; +public partial class SharedBodySystem +{ + [Dependency] private readonly SharedHumanoidAppearanceSystem _humanoid = default!; + [Dependency] private readonly MarkingManager _markingManager = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + private void InitializePartAppearances() + { + base.Initialize(); + + SubscribeLocalEvent(OnPartAppearanceStartup); + SubscribeLocalEvent(HandleState); + SubscribeLocalEvent(OnPartAttachedToBody); + SubscribeLocalEvent(OnPartDroppedFromBody); + } + + private void OnPartAppearanceStartup(EntityUid uid, BodyPartAppearanceComponent component, ComponentStartup args) + { + if (!TryComp(uid, out BodyPartComponent? part)) + return; + + if (part.OriginalBody == null + || TerminatingOrDeleted(part.OriginalBody.Value) + || !TryComp(part.OriginalBody.Value, out HumanoidAppearanceComponent? bodyAppearance) + || part.ToHumanoidLayers() is not { } relevantLayer) + { + component.ID = part.BaseLayerId; + return; + } + + var customLayers = bodyAppearance.CustomBaseLayers; + var spriteLayers = bodyAppearance.BaseLayers; + component.Type = relevantLayer; + component.OriginalBody = part.OriginalBody.Value; + + part.Species = bodyAppearance.Species; + + if (customLayers.ContainsKey(component.Type)) + { + component.ID = customLayers[component.Type].Id; + component.Color = customLayers[component.Type].Color; + } + else if (spriteLayers.ContainsKey(component.Type)) + { + component.ID = spriteLayers[component.Type].ID; + component.Color = bodyAppearance.SkinColor; + } + else + { + component.ID = CreateIdFromPart(bodyAppearance, relevantLayer); + component.Color = bodyAppearance.SkinColor; + } + + // I HATE HARDCODED CHECKS I HATE HARDCODED CHECKS I HATE HARDCODED CHECKS + if (part.PartType == BodyPartType.Head) + component.EyeColor = bodyAppearance.EyeColor; + + var markingsByLayer = new Dictionary>(); + + foreach (var layer in HumanoidVisualLayersExtension.Sublayers(relevantLayer)) + { + var category = MarkingCategoriesConversion.FromHumanoidVisualLayers(layer); + if (bodyAppearance.MarkingSet.Markings.TryGetValue(category, out var markingList)) + markingsByLayer[layer] = markingList.Select(m => new Marking(m.MarkingId, m.MarkingColors.ToList())).ToList(); + } + + component.Markings = markingsByLayer; + } + + private string? CreateIdFromPart(HumanoidAppearanceComponent bodyAppearance, HumanoidVisualLayers part) + { + var speciesProto = _prototypeManager.Index(bodyAppearance.Species); + var baseSprites = _prototypeManager.Index(speciesProto.SpriteSet); + + if (!baseSprites.Sprites.ContainsKey(part)) + return null; + + return HumanoidVisualLayersExtension.GetSexMorph(part, bodyAppearance.Sex, baseSprites.Sprites[part]); + } + + public void ModifyMarkings(EntityUid uid, + Entity partAppearance, + HumanoidAppearanceComponent bodyAppearance, + HumanoidVisualLayers targetLayer, + string markingId, + bool remove = false) + { + + if (!Resolve(partAppearance, ref partAppearance.Comp)) + return; + + if (!remove) + { + + if (!_markingManager.Markings.TryGetValue(markingId, out var prototype)) + return; + + var markingColors = MarkingColoring.GetMarkingLayerColors( + prototype, + bodyAppearance.SkinColor, + bodyAppearance.EyeColor, + bodyAppearance.MarkingSet + ); + + var marking = new Marking(markingId, markingColors); + + _humanoid.SetLayerVisibility(uid, targetLayer, true, true, bodyAppearance); + _humanoid.AddMarking(uid, markingId, markingColors, true, true, bodyAppearance); + if (!partAppearance.Comp.Markings.ContainsKey(targetLayer)) + partAppearance.Comp.Markings[targetLayer] = new List(); + + partAppearance.Comp.Markings[targetLayer].Add(marking); + } + //else + //RemovePartMarkings(uid, component, bodyAppearance); + } + + private void HandleState(EntityUid uid, BodyPartAppearanceComponent component, ref AfterAutoHandleStateEvent args) => + ApplyPartMarkings(uid, component); + + private void OnPartAttachedToBody(EntityUid uid, BodyComponent component, ref BodyPartAttachedEvent args) + { + if (!TryComp(args.Part, out BodyPartAppearanceComponent? partAppearance) + || !TryComp(uid, out HumanoidAppearanceComponent? bodyAppearance)) + return; + + if (partAppearance.ID != null) + _humanoid.SetBaseLayerId(uid, partAppearance.Type, partAppearance.ID, sync: true, bodyAppearance); + + UpdateAppearance(uid, partAppearance); + } + + private void OnPartDroppedFromBody(EntityUid uid, BodyComponent component, ref BodyPartDroppedEvent args) + { + if (TerminatingOrDeleted(uid) + || TerminatingOrDeleted(args.Part) + || !TryComp(uid, out HumanoidAppearanceComponent? bodyAppearance)) + return; + + // We check for this conditional here since some entities may not have a profile... If they dont + // have one, and their part is gibbed, the markings will not be removed or applied properly. + if (!HasComp(args.Part)) + EnsureComp(args.Part); + + if (TryComp(args.Part, out var partAppearance)) + RemoveAppearance(uid, partAppearance, args.Part); + } + + protected void UpdateAppearance(EntityUid target, + BodyPartAppearanceComponent component) + { + if (!TryComp(target, out HumanoidAppearanceComponent? bodyAppearance)) + return; + + if (component.EyeColor != null) + bodyAppearance.EyeColor = component.EyeColor.Value; + + if (component.Color != null) + _humanoid.SetBaseLayerColor(target, component.Type, component.Color, true, bodyAppearance); + + _humanoid.SetLayerVisibility(target, component.Type, true, true, bodyAppearance); + + foreach (var (visualLayer, markingList) in component.Markings) + { + _humanoid.SetLayerVisibility(target, visualLayer, true, true, bodyAppearance); + foreach (var marking in markingList) + _humanoid.AddMarking(target, marking.MarkingId, marking.MarkingColors, false, true, bodyAppearance); + } + + Dirty(target, bodyAppearance); + } + + protected void RemoveAppearance(EntityUid entity, BodyPartAppearanceComponent component, EntityUid partEntity) + { + if (!TryComp(entity, out HumanoidAppearanceComponent? bodyAppearance)) + return; + + foreach (var (visualLayer, markingList) in component.Markings) + { + _humanoid.SetLayerVisibility(entity, visualLayer, false, true, bodyAppearance); + } + RemoveBodyMarkings(entity, component, bodyAppearance); + } + + protected abstract void ApplyPartMarkings(EntityUid target, BodyPartAppearanceComponent component); + + protected abstract void RemoveBodyMarkings(EntityUid target, BodyPartAppearanceComponent partAppearance, HumanoidAppearanceComponent bodyAppearance); +} diff --git a/Content.Shared/Body/Systems/SharedBodySystem.Parts.cs b/Content.Shared/Body/Systems/SharedBodySystem.Parts.cs index ee79faa0b8..be03fa6f40 100644 --- a/Content.Shared/Body/Systems/SharedBodySystem.Parts.cs +++ b/Content.Shared/Body/Systems/SharedBodySystem.Parts.cs @@ -1,28 +1,55 @@ -using System.Diagnostics.CodeAnalysis; -using System.Linq; using Content.Shared.Body.Components; using Content.Shared.Body.Events; using Content.Shared.Body.Organ; using Content.Shared.Body.Part; using Content.Shared.Damage; using Content.Shared.Damage.Prototypes; +using Content.Shared.Humanoid; +using Content.Shared.Inventory; using Content.Shared.Movement.Components; +using Content.Shared.Random; +using Content.Shared.Targeting.Events; using Robust.Shared.Containers; using Robust.Shared.Utility; +using System.Diagnostics.CodeAnalysis; +using System.Linq; namespace Content.Shared.Body.Systems; public partial class SharedBodySystem { + [Dependency] private readonly RandomHelperSystem _randomHelper = default!; + [Dependency] private readonly InventorySystem _inventorySystem = default!; private void InitializeParts() { // TODO: This doesn't handle comp removal on child ents. // If you modify this also see the Body partial for root parts. + SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnBodyPartRemove); SubscribeLocalEvent(OnBodyPartInserted); SubscribeLocalEvent(OnBodyPartRemoved); + SubscribeLocalEvent(OnAmputateAttempt); + SubscribeLocalEvent(OnPartEnableChanged); } + private void OnMapInit(Entity ent, ref MapInitEvent args) + { + if (ent.Comp.PartType == BodyPartType.Torso) + { + // For whatever reason this slot is initialized properly on the server, but not on the client. + // This seems to be an issue due to wiz-merge, on my old branch it was properly instantiating + // ItemInsertionSlot's container on both ends. It does show up properly on ItemSlotsComponent though. + _slots.AddItemSlot(ent, ent.Comp.ContainerName, ent.Comp.ItemInsertionSlot); + Dirty(ent, ent.Comp); + } + } + + private void OnBodyPartRemove(Entity ent, ref ComponentRemove args) + { + if (ent.Comp.PartType == BodyPartType.Torso) + _slots.RemoveItemSlot(ent, ent.Comp.ItemInsertionSlot); + } private void OnBodyPartInserted(Entity ent, ref EntInsertedIntoContainerMessage args) { // Body part inserted into another body part. @@ -47,12 +74,12 @@ private void OnBodyPartRemoved(Entity ent, ref EntRemovedFrom // Body part removed from another body part. var removedUid = args.Entity; var slotId = args.Container.ID; - DebugTools.Assert(!TryComp(removedUid, out BodyPartComponent? b) || b.Body == ent.Comp.Body); DebugTools.Assert(!TryComp(removedUid, out OrganComponent? o) || o.Body == ent.Comp.Body); if (TryComp(removedUid, out BodyPartComponent? part) && part.Body is not null) { + CheckBodyPart((removedUid, part), GetTargetBodyPart(part), true); RemovePart(part.Body.Value, (removedUid, part), slotId); RecursiveBodyUpdate((removedUid, part), null); } @@ -93,6 +120,8 @@ private void RecursiveBodyUpdate(Entity ent, EntityUid? bodyU } } + // The code for RemovePartEffect() should live here, because it literally is the point of this recursive function. + // But the debug asserts at the top plus existing tests need refactoring for this. So we'll be lazy. foreach (var slotId in ent.Comp.Children.Keys) { if (!Containers.TryGetContainer(ent, GetPartSlotContainerId(slotId), out var container)) @@ -116,7 +145,6 @@ protected virtual void AddPart( var ev = new BodyPartAddedEvent(slotId, partEnt); RaiseLocalEvent(bodyEnt, ref ev); - AddLeg(partEnt, bodyEnt); } @@ -127,15 +155,37 @@ protected virtual void RemovePart( { Resolve(bodyEnt, ref bodyEnt.Comp, logMissing: false); Dirty(partEnt, partEnt.Comp); - partEnt.Comp.Body = null; + partEnt.Comp.ParentSlot = null; + partEnt.Comp.OriginalBody = partEnt.Comp.Body; var ev = new BodyPartRemovedEvent(slotId, partEnt); RaiseLocalEvent(bodyEnt, ref ev); - RemoveLeg(partEnt, bodyEnt); + RemovePartEffect(partEnt, bodyEnt); PartRemoveDamage(bodyEnt, partEnt); } + protected virtual void DropPart(Entity partEnt) + { + ChangeSlotState(partEnt, true); + // I don't know if this can cause issues, since any part that's being detached HAS to have a Body. + // though I really just want the compiler to shut the fuck up. + var body = partEnt.Comp.Body.GetValueOrDefault(); + if (TryComp(partEnt, out TransformComponent? transform) && _gameTiming.IsFirstTimePredicted) + { + var enableEvent = new BodyPartEnableChangedEvent(false); + RaiseLocalEvent(partEnt, ref enableEvent); + var droppedEvent = new BodyPartDroppedEvent(partEnt); + RaiseLocalEvent(body, ref droppedEvent); + SharedTransform.AttachToGridOrMap(partEnt, transform); + _randomHelper.RandomOffset(partEnt, 0.5f); + } + + } + + private void OnAmputateAttempt(Entity partEnt, ref AmputateAttemptEvent args) => + DropPart(partEnt); + private void AddLeg(Entity legEnt, Entity bodyEnt) { if (!Resolve(bodyEnt, ref bodyEnt.Comp, logMissing: false)) @@ -159,11 +209,41 @@ private void RemoveLeg(Entity legEnt, Entity bodyEnt.Comp.LegEntities.Remove(legEnt); UpdateMovementSpeed(bodyEnt); Dirty(bodyEnt, bodyEnt.Comp); + Standing.Down(bodyEnt); + } + } + + // TODO: Refactor this crap. + private void RemovePartEffect(Entity partEnt, Entity bodyEnt) + { + if (TerminatingOrDeleted(bodyEnt) + || !Resolve(bodyEnt, ref bodyEnt.Comp, logMissing: false)) + return; + + RemovePartChildren(partEnt, bodyEnt, bodyEnt.Comp); + } + + protected void RemovePartChildren(Entity partEnt, EntityUid bodyEnt, BodyComponent? body = null) + { + if (!Resolve(bodyEnt, ref body, logMissing: false)) + return; - if (!bodyEnt.Comp.LegEntities.Any()) + if (partEnt.Comp.Children.Any()) + { + foreach (var slotId in partEnt.Comp.Children.Keys) { - Standing.Down(bodyEnt); + if (Containers.TryGetContainer(partEnt, GetPartSlotContainerId(slotId), out var container) && + container is ContainerSlot slot && + slot.ContainedEntity is { } childEntity && + TryComp(childEntity, out BodyPartComponent? childPart)) + { + var ev = new BodyPartEnableChangedEvent(false); + RaiseLocalEvent(childEntity, ref ev); + DropPart((childEntity, childPart)); + } } + + Dirty(bodyEnt, body); } } @@ -177,9 +257,94 @@ private void PartRemoveDamage(Entity bodyEnt, Entity("Bloodloss"), 300); - Damageable.TryChangeDamage(bodyEnt, damage); + var damage = new DamageSpecifier(Prototypes.Index("Bloodloss"), partEnt.Comp.VitalDamage); + Damageable.TryChangeDamage(bodyEnt, damage, partMultiplier: 0f); + } + } + + private void OnPartEnableChanged(Entity partEnt, ref BodyPartEnableChangedEvent args) + { + if (!partEnt.Comp.CanEnable && args.Enabled) + return; + + partEnt.Comp.Enabled = args.Enabled; + Dirty(partEnt, partEnt.Comp); + + if (args.Enabled) + EnablePart(partEnt); + else + DisablePart(partEnt); + } + + private void EnablePart(Entity partEnt) + { + if (!TryComp(partEnt.Comp.Body, out BodyComponent? body)) + return; + + // I hate having to hardcode these checks so much. + if (partEnt.Comp.PartType == BodyPartType.Leg) + AddLeg(partEnt, (partEnt.Comp.Body.Value, body)); + + if (partEnt.Comp.PartType == BodyPartType.Arm) + { + var hand = GetBodyChildrenOfType(partEnt.Comp.Body.Value, BodyPartType.Hand, symmetry: partEnt.Comp.Symmetry).FirstOrDefault(); + if (hand != default) + { + var ev = new BodyPartEnabledEvent(hand); + RaiseLocalEvent(partEnt.Comp.Body.Value, ref ev); + } + } + + if (partEnt.Comp.PartType == BodyPartType.Hand) + { + var ev = new BodyPartEnabledEvent(partEnt); + RaiseLocalEvent(partEnt.Comp.Body.Value, ref ev); + } + } + + /// + /// This function handles disabling or enabling equipment slots when an entity is + /// missing all of a given part type, or they get one added to them. + /// It is called right before dropping a part, or right after adding one. + /// + public void ChangeSlotState(Entity partEnt, bool disable) + { + if (partEnt.Comp.Body is not null + && GetBodyPartCount(partEnt.Comp.Body.Value, partEnt.Comp.PartType) == 1 + && TryGetPartSlotContainerName(partEnt.Comp.PartType, out var containerNames)) + { + foreach (var containerName in containerNames) + { + _inventorySystem.SetSlotStatus(partEnt.Comp.Body.Value, containerName, disable); + var ev = new RefreshInventorySlotsEvent(containerName); + RaiseLocalEvent(partEnt.Comp.Body.Value, ev); + } + } + + } + + private void DisablePart(Entity partEnt) + { + if (!TryComp(partEnt.Comp.Body, out BodyComponent? body)) + return; + + if (partEnt.Comp.PartType == BodyPartType.Leg) + RemoveLeg(partEnt, (partEnt.Comp.Body.Value, body)); + + if (partEnt.Comp.PartType == BodyPartType.Arm) + { + var hand = GetBodyChildrenOfType(partEnt.Comp.Body.Value, BodyPartType.Hand, symmetry: partEnt.Comp.Symmetry).FirstOrDefault(); + if (hand != default) + { + var ev = new BodyPartDisabledEvent(hand); + RaiseLocalEvent(partEnt.Comp.Body.Value, ref ev); + } + } + + if (partEnt.Comp.PartType == BodyPartType.Hand) + { + var ev = new BodyPartDisabledEvent(partEnt); + RaiseLocalEvent(partEnt.Comp.Body.Value, ref ev); } } @@ -438,12 +603,21 @@ public bool AttachPart( return false; } + if (!Containers.TryGetContainer(parentPartId, GetPartSlotContainerId(slot.Id), out var container)) { DebugTools.Assert($"Unable to find body slot {slot.Id} for {ToPrettyString(parentPartId)}"); return false; } + part.ParentSlot = slot; + + if (TryComp(parentPart.Body, out HumanoidAppearanceComponent? bodyAppearance) + && !HasComp(partId) + && !TerminatingOrDeleted(parentPartId) + && !TerminatingOrDeleted(partId)) // Saw some exceptions involving these due to the spawn menu. + EnsureComp(partId); + return Containers.Insert(partId, container); } @@ -656,11 +830,12 @@ public bool BodyHasChild( public IEnumerable<(EntityUid Id, BodyPartComponent Component)> GetBodyChildrenOfType( EntityUid bodyId, BodyPartType type, - BodyComponent? body = null) + BodyComponent? body = null, + BodyPartSymmetry? symmetry = null) { foreach (var part in GetBodyChildren(bodyId, body)) { - if (part.Component.PartType == type) + if (part.Component.PartType == type && (symmetry == null || part.Component.Symmetry == symmetry)) yield return part; } } @@ -722,6 +897,49 @@ public bool TryGetBodyPartOrganComponents( return false; } + /// + /// Tries to get a list of ValueTuples of EntityUid and OrganComponent on each organ + /// in the given part. + /// + /// The part entity id to check on. + /// The type of component to check for. + /// The part to check for organs on. + /// The organs found on the body part. + /// Whether any were found. + /// + /// This method is somewhat of a copout to the fact that we can't use reflection to generically + /// get the type of component on runtime due to sandboxing. So we simply do a HasComp check for each organ. + /// + public bool TryGetBodyPartOrgans( + EntityUid uid, + Type type, + [NotNullWhen(true)] out List<(EntityUid Id, OrganComponent Organ)>? organs, + BodyPartComponent? part = null) + { + if (!Resolve(uid, ref part)) + { + organs = null; + return false; + } + + var list = new List<(EntityUid Id, OrganComponent Organ)>(); + + foreach (var organ in GetPartOrgans(uid, part)) + { + if (HasComp(organ.Id, type)) + list.Add((organ.Id, organ.Component)); + } + + if (list.Count != 0) + { + organs = list; + return true; + } + + organs = null; + return false; + } + /// /// Gets the parent body part and all immediate child body parts for the partId. /// @@ -790,5 +1008,31 @@ public bool TryGetBodyPartAdjacentPartsComponents( return false; } + private bool TryGetPartSlotContainerName(BodyPartType partType, out HashSet containerNames) + { + containerNames = partType switch + { + BodyPartType.Arm => new() { "gloves" }, + BodyPartType.Leg => new() { "shoes" }, + BodyPartType.Head => new() { "eyes", "ears", "head", "mask" }, + _ => new() + }; + return containerNames.Count > 0; + } + + public int GetBodyPartCount(EntityUid bodyId, BodyPartType partType, BodyComponent? body = null) + { + if (!Resolve(bodyId, ref body, logMissing: false)) + return 0; + + int count = 0; + foreach (var part in GetBodyChildren(bodyId, body)) + { + if (part.Component.PartType == partType) + count++; + } + return count; + } + #endregion } diff --git a/Content.Shared/Body/Systems/SharedBodySystem.Targeting.cs b/Content.Shared/Body/Systems/SharedBodySystem.Targeting.cs new file mode 100644 index 0000000000..9a7c9bc889 --- /dev/null +++ b/Content.Shared/Body/Systems/SharedBodySystem.Targeting.cs @@ -0,0 +1,503 @@ +using Content.Shared.Body.Components; +using Content.Shared.Body.Part; +using Content.Shared.Damage; +using Content.Shared.Damage.Prototypes; +using Content.Shared.FixedPoint; +using Content.Shared.IdentityManagement; +using Content.Shared.Medical.Surgery.Steps.Parts; +using Content.Shared.Mobs.Components; +using Content.Shared.Mobs.Systems; +using Content.Shared.Popups; +using Content.Shared.Standing; +using Content.Shared.Targeting; +using Content.Shared.Targeting.Events; +using Robust.Shared.CPUJob.JobQueues; +using Robust.Shared.CPUJob.JobQueues.Queues; +using Robust.Shared.Network; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; +using Robust.Shared.Timing; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Content.Shared.Body.Systems; + +public partial class SharedBodySystem +{ + [Dependency] private readonly INetManager _net = default!; + [Dependency] private readonly MobStateSystem _mobState = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly DamageableSystem _damageable = default!; + + [Dependency] private readonly SharedPopupSystem _popup = default!; + private readonly string[] _severingDamageTypes = { "Slash", "Pierce", "Blunt" }; + private const double IntegrityJobTime = 0.005; + private readonly JobQueue _integrityJobQueue = new(IntegrityJobTime); + public sealed class IntegrityJob : Job + { + private readonly SharedBodySystem _self; + private readonly Entity _ent; + public IntegrityJob(SharedBodySystem self, Entity ent, double maxTime, CancellationToken cancellation = default) : base(maxTime, cancellation) + { + _self = self; + _ent = ent; + } + + public IntegrityJob(SharedBodySystem self, Entity ent, double maxTime, IStopwatch stopwatch, CancellationToken cancellation = default) : base(maxTime, stopwatch, cancellation) + { + _self = self; + _ent = ent; + } + + protected override Task Process() + { + _self.ProcessIntegrityTick(_ent); + + return Task.FromResult(null); + } + } + + private EntityQuery _queryTargeting; + private void InitializeIntegrityQueue() + { + _queryTargeting = GetEntityQuery(); + SubscribeLocalEvent(OnBeforeDamageChanged); + SubscribeLocalEvent(OnBodyDamageModify); + SubscribeLocalEvent(OnPartDamageModify); + SubscribeLocalEvent(OnDamageChanged); + } + + private void ProcessIntegrityTick(Entity entity) + { + if (!TryComp(entity, out var damageable)) + return; + + var damage = damageable.TotalDamage; + + if (entity.Comp is { Body: { } body } + && damage > entity.Comp.MinIntegrity + && damage <= entity.Comp.IntegrityThresholds[TargetIntegrity.HeavilyWounded] + && _queryTargeting.HasComp(body) + && !_mobState.IsDead(body)) + _damageable.TryChangeDamage(entity, GetHealingSpecifier(entity), canSever: false, targetPart: GetTargetBodyPart(entity)); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + _integrityJobQueue.Process(); + + if (!_timing.IsFirstTimePredicted) + return; + + using var query = EntityQueryEnumerator(); + while (query.MoveNext(out var ent, out var part)) + { + part.HealingTimer += frameTime; + + if (part.HealingTimer >= part.HealingTime) + { + part.HealingTimer = 0; + _integrityJobQueue.EnqueueJob(new IntegrityJob(this, (ent, part), IntegrityJobTime)); + } + } + } + + private void OnBeforeDamageChanged(Entity ent, ref BeforeDamageChangedEvent args) + { + // If our target has a TargetingComponent, that means they will take limb damage + // And if their attacker also has one, then we use that part. + if (_queryTargeting.TryComp(ent, out var targetEnt)) + { + var damage = args.Damage; + TargetBodyPart? targetPart = null; + + if (args.TargetPart != null) + { + targetPart = args.TargetPart; + } + else if (args.Origin.HasValue && _queryTargeting.TryComp(args.Origin.Value, out var targeter)) + { + targetPart = targeter.Target; + // If the target is Torso then have a 33% chance to hit another part + if (targetPart.Value == TargetBodyPart.Torso) + { + var additionalPart = GetRandomPartSpread(_random, 10); + targetPart = targetPart.Value | additionalPart; + } + } + else + { + // If there's an origin in this case, that means it comes from an entity without TargetingComponent, + // such as an animal, so we attack a random part. + if (args.Origin.HasValue) + { + targetPart = GetRandomBodyPart(ent, targetEnt); + } + // Otherwise we damage all parts equally (barotrauma, explosions, etc). + else if (damage != null) + { + // Division by 2 cuz damaging all parts by the same damage by default is too much. + damage /= 2; + targetPart = TargetBodyPart.All; + } + } + + if (targetPart == null) + return; + + if (!TryChangePartDamage(ent, args.Damage, args.CanSever, args.CanEvade, args.PartMultiplier, targetPart.Value) + && args.CanEvade) + { + if (_net.IsServer) + _popup.PopupEntity(Loc.GetString("surgery-part-damage-evaded", ("user", Identity.Entity(ent, EntityManager))), ent); + + args.Evaded = true; + } + } + } + + private void OnBodyDamageModify(Entity bodyEnt, ref DamageModifyEvent args) + { + if (args.TargetPart != null) + { + var (targetType, _) = ConvertTargetBodyPart(args.TargetPart.Value); + args.Damage = args.Damage * GetPartDamageModifier(targetType); + } + } + + private void OnPartDamageModify(Entity partEnt, ref DamageModifyEvent args) + { + if (partEnt.Comp.Body != null + && TryComp(partEnt.Comp.Body.Value, out DamageableComponent? damageable) + && damageable.DamageModifierSetId != null + && _prototypeManager.TryIndex(damageable.DamageModifierSetId, out var modifierSet)) + // TODO: We need to add a check to see if the given armor covers this part to cancel or not. + args.Damage = DamageSpecifier.ApplyModifierSet(args.Damage, modifierSet); + + if (_prototypeManager.TryIndex("PartDamage", out var partModifierSet)) + args.Damage = DamageSpecifier.ApplyModifierSet(args.Damage, partModifierSet); + + args.Damage = args.Damage * GetPartDamageModifier(partEnt.Comp.PartType); + } + + private bool TryChangePartDamage(EntityUid entity, + DamageSpecifier damage, + bool canSever, + bool canEvade, + float partMultiplier, + TargetBodyPart targetParts) + { + var landed = false; + var targets = SharedTargetingSystem.GetValidParts(); + foreach (var target in targets) + { + if (!targetParts.HasFlag(target)) + continue; + + var (targetType, targetSymmetry) = ConvertTargetBodyPart(target); + if (GetBodyChildrenOfType(entity, targetType, symmetry: targetSymmetry) is { } part) + { + if (canEvade && TryEvadeDamage(entity, GetEvadeChance(targetType))) + continue; + + var damageResult = _damageable.TryChangeDamage(part.FirstOrDefault().Id, damage * partMultiplier, canSever: canSever); + if (damageResult != null && damageResult.GetTotal() != 0) + landed = true; + } + } + + return landed; + } + + private void OnDamageChanged(Entity partEnt, ref DamageChangedEvent args) + { + if (!TryComp(partEnt, out var damageable)) + return; + + var severed = false; + var partIdSlot = GetParentPartAndSlotOrNull(partEnt)?.Slot; + var delta = args.DamageDelta; + + if (args.CanSever + && partIdSlot is not null + && delta != null + && !HasComp(partEnt) + && !partEnt.Comp.Enabled + && damageable.TotalDamage >= partEnt.Comp.SeverIntegrity + && _severingDamageTypes.Any(damageType => delta.DamageDict.TryGetValue(damageType, out var value) && value > 0)) + severed = true; + + CheckBodyPart(partEnt, GetTargetBodyPart(partEnt), severed, damageable); + + if (severed) + DropPart(partEnt); + + Dirty(partEnt, partEnt.Comp); + } + + /// + /// Gets the random body part rolling a number between 1 and 9, and returns + /// Torso if the result is 9 or more. The higher torsoWeight is, the higher chance to return it. + /// By default, the chance to return Torso is 50%. + /// + private static TargetBodyPart GetRandomPartSpread(IRobustRandom random, ushort torsoWeight = 9) + { + const int targetPartsAmount = 9; + // 5 = amount of target parts except Torso + return random.Next(1, targetPartsAmount + torsoWeight) switch + { + 1 => TargetBodyPart.Head, + 2 => TargetBodyPart.RightArm, + 3 => TargetBodyPart.RightHand, + 4 => TargetBodyPart.LeftArm, + 5 => TargetBodyPart.LeftHand, + 6 => TargetBodyPart.RightLeg, + 7 => TargetBodyPart.RightFoot, + 8 => TargetBodyPart.LeftLeg, + 9 => TargetBodyPart.LeftFoot, + _ => TargetBodyPart.Torso, + }; + } + + public TargetBodyPart? GetRandomBodyPart(EntityUid uid, TargetingComponent? target = null) + { + if (!Resolve(uid, ref target)) + return null; + + var totalWeight = target.TargetOdds.Values.Sum(); + var randomValue = _random.NextFloat() * totalWeight; + + foreach (var (part, weight) in target.TargetOdds) + { + if (randomValue <= weight) + return part; + randomValue -= weight; + } + + return TargetBodyPart.Torso; // Default to torso if something goes wrong + } + + /// + /// This should be called after body part damage was changed. + /// + protected void CheckBodyPart( + Entity partEnt, + TargetBodyPart? targetPart, + bool severed, + DamageableComponent? damageable = null) + { + if (!Resolve(partEnt, ref damageable)) + return; + + var integrity = damageable.TotalDamage; + + // KILL the body part + if (partEnt.Comp.Enabled && integrity >= partEnt.Comp.IntegrityThresholds[TargetIntegrity.CriticallyWounded]) + { + var ev = new BodyPartEnableChangedEvent(false); + RaiseLocalEvent(partEnt, ref ev); + } + + // LIVE the body part + if (!partEnt.Comp.Enabled && integrity <= partEnt.Comp.IntegrityThresholds[partEnt.Comp.EnableIntegrity] && !severed) + { + var ev = new BodyPartEnableChangedEvent(true); + RaiseLocalEvent(partEnt, ref ev); + } + + if (_queryTargeting.TryComp(partEnt.Comp.Body, out var targeting) + && HasComp(partEnt.Comp.Body)) + { + var newIntegrity = GetIntegrityThreshold(partEnt.Comp, integrity.Float(), severed); + // We need to check if the part is dead to prevent the UI from showing dead parts as alive. + if (targetPart is not null && + targeting.BodyStatus.ContainsKey(targetPart.Value) && + targeting.BodyStatus[targetPart.Value] != TargetIntegrity.Dead) + { + targeting.BodyStatus[targetPart.Value] = newIntegrity; + if (targetPart.Value == TargetBodyPart.Torso) + targeting.BodyStatus[TargetBodyPart.Groin] = newIntegrity; + + Dirty(partEnt.Comp.Body.Value, targeting); + } + // Revival events are handled by the server, so we end up being locked to a network event. + // I hope you like the _net.IsServer, Remuchi :) + if (_net.IsServer) + RaiseNetworkEvent(new TargetIntegrityChangeEvent(GetNetEntity(partEnt.Comp.Body.Value)), partEnt.Comp.Body.Value); + } + } + + /// + /// Gets the integrity of all body parts in the entity. + /// + public Dictionary GetBodyPartStatus(EntityUid entityUid) + { + var result = new Dictionary(); + + if (!TryComp(entityUid, out var body)) + return result; + + foreach (var part in SharedTargetingSystem.GetValidParts()) + { + result[part] = TargetIntegrity.Severed; + } + + foreach (var partComponent in GetBodyChildren(entityUid, body)) + { + var targetBodyPart = GetTargetBodyPart(partComponent.Component.PartType, partComponent.Component.Symmetry); + + if (targetBodyPart != null && TryComp(partComponent.Id, out var damageable)) + result[targetBodyPart.Value] = GetIntegrityThreshold(partComponent.Component, damageable.TotalDamage.Float(), false); + } + + // Hardcoded shitcode for Groin :) + result[TargetBodyPart.Groin] = result[TargetBodyPart.Torso]; + + return result; + } + + public TargetBodyPart? GetTargetBodyPart(Entity part) => GetTargetBodyPart(part.Comp.PartType, part.Comp.Symmetry); + public TargetBodyPart? GetTargetBodyPart(BodyPartComponent part) => GetTargetBodyPart(part.PartType, part.Symmetry); + + /// + /// Converts Enums from BodyPartType to their Targeting system equivalent. + /// + public TargetBodyPart? GetTargetBodyPart(BodyPartType type, BodyPartSymmetry symmetry) + { + return (type, symmetry) switch + { + (BodyPartType.Head, _) => TargetBodyPart.Head, + (BodyPartType.Torso, _) => TargetBodyPart.Torso, + (BodyPartType.Arm, BodyPartSymmetry.Left) => TargetBodyPart.LeftArm, + (BodyPartType.Arm, BodyPartSymmetry.Right) => TargetBodyPart.RightArm, + (BodyPartType.Hand, BodyPartSymmetry.Left) => TargetBodyPart.LeftHand, + (BodyPartType.Hand, BodyPartSymmetry.Right) => TargetBodyPart.RightHand, + (BodyPartType.Leg, BodyPartSymmetry.Left) => TargetBodyPart.LeftLeg, + (BodyPartType.Leg, BodyPartSymmetry.Right) => TargetBodyPart.RightLeg, + (BodyPartType.Foot, BodyPartSymmetry.Left) => TargetBodyPart.LeftFoot, + (BodyPartType.Foot, BodyPartSymmetry.Right) => TargetBodyPart.RightFoot, + _ => null + }; + } + + /// + /// Converts Enums from Targeting system to their BodyPartType equivalent. + /// + public (BodyPartType Type, BodyPartSymmetry Symmetry) ConvertTargetBodyPart(TargetBodyPart targetPart) + { + return targetPart switch + { + TargetBodyPart.Head => (BodyPartType.Head, BodyPartSymmetry.None), + TargetBodyPart.Torso => (BodyPartType.Torso, BodyPartSymmetry.None), + TargetBodyPart.Groin => (BodyPartType.Torso, BodyPartSymmetry.None), // TODO: Groin is not a part type yet + TargetBodyPart.LeftArm => (BodyPartType.Arm, BodyPartSymmetry.Left), + TargetBodyPart.LeftHand => (BodyPartType.Hand, BodyPartSymmetry.Left), + TargetBodyPart.RightArm => (BodyPartType.Arm, BodyPartSymmetry.Right), + TargetBodyPart.RightHand => (BodyPartType.Hand, BodyPartSymmetry.Right), + TargetBodyPart.LeftLeg => (BodyPartType.Leg, BodyPartSymmetry.Left), + TargetBodyPart.LeftFoot => (BodyPartType.Foot, BodyPartSymmetry.Left), + TargetBodyPart.RightLeg => (BodyPartType.Leg, BodyPartSymmetry.Right), + TargetBodyPart.RightFoot => (BodyPartType.Foot, BodyPartSymmetry.Right), + _ => (BodyPartType.Torso, BodyPartSymmetry.None) + }; + + } + + public DamageSpecifier GetHealingSpecifier(BodyPartComponent part) + { + var damage = new DamageSpecifier() + { + DamageDict = new Dictionary() + { + { "Blunt", -part.SelfHealingAmount }, + { "Slash", -part.SelfHealingAmount }, + { "Piercing", -part.SelfHealingAmount }, + { "Heat", -part.SelfHealingAmount }, + { "Cold", -part.SelfHealingAmount }, + { "Shock", -part.SelfHealingAmount }, + { "Caustic", -part.SelfHealingAmount * 0.1}, // not much caustic healing + } + }; + + return damage; + } + + /// + /// Fetches the damage multiplier for part integrity based on part types. + /// + /// TODO: Serialize this per body part. + public static float GetPartDamageModifier(BodyPartType partType) + { + return partType switch + { + BodyPartType.Head => 0.5f, // 50% damage, necks are hard to cut + BodyPartType.Torso => 1.0f, // 100% damage + BodyPartType.Arm => 0.7f, // 70% damage + BodyPartType.Hand => 0.7f, // 70% damage + BodyPartType.Leg => 0.7f, // 70% damage + BodyPartType.Foot => 0.7f, // 70% damage + _ => 0.5f + }; + } + + /// + /// Fetches the TargetIntegrity equivalent of the current integrity value for the body part. + /// + public static TargetIntegrity GetIntegrityThreshold(BodyPartComponent component, float integrity, bool severed) + { + if (severed) + return TargetIntegrity.Severed; + else if (!component.Enabled) + return TargetIntegrity.Disabled; + + var targetIntegrity = TargetIntegrity.Healthy; + foreach (var threshold in component.IntegrityThresholds) + { + if (integrity <= threshold.Value) + targetIntegrity = threshold.Key; + } + + return targetIntegrity; + } + + /// + /// Fetches the chance to evade integrity damage for a body part. + /// Used when the entity is not dead, laying down, or incapacitated. + /// + public static float GetEvadeChance(BodyPartType partType) + { + return partType switch + { + BodyPartType.Head => 0.70f, // 70% chance to evade + BodyPartType.Arm => 0.20f, // 20% chance to evade + BodyPartType.Hand => 0.20f, // 20% chance to evade + BodyPartType.Leg => 0.20f, // 20% chance to evade + BodyPartType.Foot => 0.20f, // 20% chance to evade + BodyPartType.Torso => 0f, // 0% chance to evade + _ => 0f + }; + } + + public bool CanEvadeDamage(EntityUid uid) + { + if (!TryComp(uid, out var mobState) + || !TryComp(uid, out var standingState) + || _mobState.IsCritical(uid, mobState) + || _mobState.IsDead(uid, mobState) + || standingState.CurrentState == StandingState.Lying) + return false; + + return true; + } + + public bool TryEvadeDamage(EntityUid uid, float evadeChance) + { + if (!CanEvadeDamage(uid)) + return false; + + return _random.NextFloat() < evadeChance; + } + +} diff --git a/Content.Shared/Body/Systems/SharedBodySystem.cs b/Content.Shared/Body/Systems/SharedBodySystem.cs index a45966fcc3..013e302633 100644 --- a/Content.Shared/Body/Systems/SharedBodySystem.cs +++ b/Content.Shared/Body/Systems/SharedBodySystem.cs @@ -28,7 +28,7 @@ public abstract partial class SharedBodySystem : EntitySystem /// public const string OrganSlotContainerIdPrefix = "body_organ_slot_"; - [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly IGameTiming _timing = default!; [Dependency] protected readonly IPrototypeManager Prototypes = default!; [Dependency] protected readonly DamageableSystem Damageable = default!; [Dependency] protected readonly MovementSpeedModifierSystem Movement = default!; @@ -42,6 +42,9 @@ public override void Initialize() InitializeBody(); InitializeParts(); + // To try and mitigate the server load due to integrity checks, we set up a Job Queue. + InitializeIntegrityQueue(); + InitializePartAppearances(); } /// diff --git a/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs b/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs index bc039c03e6..c07d90b3a2 100644 --- a/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs +++ b/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs @@ -61,7 +61,7 @@ private void OnBuckleMove(EntityUid uid, BuckleComponent component, ref MoveEven return; var strapPosition = Transform(strapUid).Coordinates; - if (ev.NewPosition.InRange(EntityManager, _transform, strapPosition, strapComp.MaxBuckleDistance)) + if (ev.NewPosition.EntityId.IsValid() && ev.NewPosition.InRange(EntityManager, _transform, strapPosition, strapComp.MaxBuckleDistance)) return; TryUnbuckle(uid, uid, true, component); diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index ff2a915b1b..603855679c 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -1,6 +1,8 @@ +using Content.Shared.Maps; using Content.Shared.Supermatter.Components; using Robust.Shared; using Robust.Shared.Configuration; +using Robust.Shared.Physics.Components; namespace Content.Shared.CCVar { @@ -178,34 +180,43 @@ public static readonly CVarDef /// Minimum time between Basic station events in seconds /// public static readonly CVarDef // 5 Minutes - GameEventsBasicMinimumTime = CVarDef.Create("game.events_basic_minimum_time", 300, CVar.SERVERONLY); + GameEventsBasicMinimumTime = CVarDef.Create("game.events_basic_minimum_time", 300, CVar.SERVERONLY | CVar.ARCHIVE); /// /// Maximum time between Basic station events in seconds /// public static readonly CVarDef // 25 Minutes - GameEventsBasicMaximumTime = CVarDef.Create("game.events_basic_maximum_time", 1500, CVar.SERVERONLY); + GameEventsBasicMaximumTime = CVarDef.Create("game.events_basic_maximum_time", 1500, CVar.SERVERONLY | CVar.ARCHIVE); /// /// Minimum time between Ramping station events in seconds /// public static readonly CVarDef // 4 Minutes - GameEventsRampingMinimumTime = CVarDef.Create("game.events_ramping_minimum_time", 240, CVar.SERVERONLY); + GameEventsRampingMinimumTime = CVarDef.Create("game.events_ramping_minimum_time", 240, CVar.SERVERONLY | CVar.ARCHIVE); /// /// Maximum time between Ramping station events in seconds /// public static readonly CVarDef // 12 Minutes - GameEventsRampingMaximumTime = CVarDef.Create("game.events_ramping_maximum_time", 720, CVar.SERVERONLY); + GameEventsRampingMaximumTime = CVarDef.Create("game.events_ramping_maximum_time", 720, CVar.SERVERONLY | CVar.ARCHIVE); /// - /// + /// Minimum time between Oscillating station events in seconds. This is the bare minimum which will never be violated, unlike with ramping events. + /// + public static readonly CVarDef // 40 seconds + GameEventsOscillatingMinimumTime = CVarDef.Create("game.events_oscillating_minimum_time", 40, CVar.SERVERONLY | CVar.ARCHIVE); + + /// + /// Time between Oscillating station events in seconds at 1x chaos level. Events may occur at larger intervals if current chaos is lower than that. + /// + public static readonly CVarDef // 20 Minutes - which constitutes a minimum of 120 seconds between events in Irregular and 280 seconds in Extended Irregular + GameEventsOscillatingAverageTime = CVarDef.Create("game.events_oscillating_average_time", 1200, CVar.SERVERONLY | CVar.ARCHIVE); /// /// Controls the maximum number of character slots a player is allowed to have. /// public static readonly CVarDef - GameMaxCharacterSlots = CVarDef.Create("game.maxcharacterslots", 10, CVar.ARCHIVE | CVar.SERVERONLY); + GameMaxCharacterSlots = CVarDef.Create("game.maxcharacterslots", 30, CVar.ARCHIVE | CVar.SERVERONLY); /// /// Controls the game map prototype to load. SS14 stores these prototypes in Prototypes/Maps. @@ -213,6 +224,13 @@ public static readonly CVarDef public static readonly CVarDef GameMap = CVarDef.Create("game.map", string.Empty, CVar.SERVERONLY); + /// + /// If roles should be restricted based on whether or not they are whitelisted. + /// + public static readonly CVarDef + GameRoleWhitelist = CVarDef.Create("game.role_whitelist", true, CVar.SERVER | CVar.REPLICATED); + + /// /// Controls whether to use world persistence or not. /// @@ -367,7 +385,7 @@ public static readonly CVarDef /// How many points a character should start with. /// public static readonly CVarDef GameTraitsDefaultPoints = - CVarDef.Create("game.traits_default_points", 5, CVar.REPLICATED); + CVarDef.Create("game.traits_default_points", 10, CVar.REPLICATED); /// @@ -395,12 +413,11 @@ public static readonly CVarDef public static readonly CVarDef GameAutoEatDrinks = CVarDef.Create("game.auto_eat_drinks", false, CVar.REPLICATED); - /// - /// When true, you have to press the change speed button to sprint. + /// Whether item slots, such as power cell slots or AME fuel cell slots, should support quick swap if it is not otherwise specified in their YAML prototype. /// - public static readonly CVarDef GamePressToSprint = - CVarDef.Create("game.press_to_sprint", true, CVar.REPLICATED); + public static readonly CVarDef AllowSlotQuickSwap = + CVarDef.Create("game.slot_quick_swap", false, CVar.REPLICATED); #if EXCEPTION_TOLERANCE /// @@ -466,6 +483,12 @@ public static readonly CVarDef public static readonly CVarDef AnnouncerVolume = CVarDef.Create("announcer.volume", 0.5f, CVar.ARCHIVE | CVar.CLIENTONLY); + /// + /// Disables multiple announcement sounds from playing at once + /// + public static readonly CVarDef AnnouncerDisableMultipleSounds = + CVarDef.Create("announcer.disable_multiple_sounds", false, CVar.ARCHIVE | CVar.CLIENTONLY); + /* * Queue @@ -537,91 +560,6 @@ public static readonly CVarDef public static readonly CVarDef DiscordAuthApiKey = CVarDef.Create("discord.auth_api_key", "", CVar.SERVERONLY | CVar.CONFIDENTIAL); - - /* - * Suspicion - */ - - public static readonly CVarDef SuspicionMinPlayers = - CVarDef.Create("suspicion.min_players", 5); - - public static readonly CVarDef SuspicionMinTraitors = - CVarDef.Create("suspicion.min_traitors", 2); - - public static readonly CVarDef SuspicionPlayersPerTraitor = - CVarDef.Create("suspicion.players_per_traitor", 6); - - public static readonly CVarDef SuspicionStartingBalance = - CVarDef.Create("suspicion.starting_balance", 20); - - public static readonly CVarDef SuspicionMaxTimeSeconds = - CVarDef.Create("suspicion.max_time_seconds", 300); - - /* - * Traitor - */ - - public static readonly CVarDef TraitorMinPlayers = - CVarDef.Create("traitor.min_players", 5); - - public static readonly CVarDef TraitorMaxTraitors = - CVarDef.Create("traitor.max_traitors", 12); // Assuming average server maxes somewhere from like 50-80 people - - public static readonly CVarDef TraitorPlayersPerTraitor = - CVarDef.Create("traitor.players_per_traitor", 10); - - public static readonly CVarDef TraitorCodewordCount = - CVarDef.Create("traitor.codeword_count", 4); - - public static readonly CVarDef TraitorStartingBalance = - CVarDef.Create("traitor.starting_balance", 20); - - public static readonly CVarDef TraitorMaxDifficulty = - CVarDef.Create("traitor.max_difficulty", 5); - - public static readonly CVarDef TraitorMaxPicks = - CVarDef.Create("traitor.max_picks", 20); - - public static readonly CVarDef TraitorStartDelay = - CVarDef.Create("traitor.start_delay", 4f * 60f); - - public static readonly CVarDef TraitorStartDelayVariance = - CVarDef.Create("traitor.start_delay_variance", 3f * 60f); - - /* - * TraitorDeathMatch - */ - - public static readonly CVarDef TraitorDeathMatchStartingBalance = - CVarDef.Create("traitordm.starting_balance", 20); - - /* - * Zombie - */ - - public static readonly CVarDef ZombieMinPlayers = - CVarDef.Create("zombie.min_players", 20); - - /* - * Pirates - */ - - public static readonly CVarDef PiratesMinPlayers = - CVarDef.Create("pirates.min_players", 25); - - public static readonly CVarDef PiratesMaxOps = - CVarDef.Create("pirates.max_pirates", 6); - - public static readonly CVarDef PiratesPlayersPerOp = - CVarDef.Create("pirates.players_per_pirate", 5); - - /* - * Nukeops - */ - - public static readonly CVarDef NukeopsSpawnGhostRoles = - CVarDef.Create("nukeops.spawn_ghost_roles", false); - /* * Tips */ @@ -654,6 +592,12 @@ public static readonly CVarDef public static readonly CVarDef LoginTipsDataset = CVarDef.Create("tips.login_dataset", "Tips"); + /// + /// The chance for Tippy to replace a normal tip message. + /// + public static readonly CVarDef TipsTippyChance = + CVarDef.Create("tips.tippy_chance", 0.01f); + /* * Console */ @@ -842,7 +786,6 @@ public static readonly CVarDef public static readonly CVarDef OfferModeIndicatorsPointShow = CVarDef.Create("hud.offer_mode_indicators_point_show", true, CVar.ARCHIVE | CVar.CLIENTONLY); - public static readonly CVarDef LoocAboveHeadShow = CVarDef.Create("hud.show_looc_above_head", true, CVar.ARCHIVE | CVar.CLIENTONLY); @@ -931,19 +874,43 @@ public static readonly CVarDef /// Default severity for role bans /// public static readonly CVarDef RoleBanDefaultSeverity = - CVarDef.Create("admin.role_ban_default_severity", "medium", CVar.ARCHIVE | CVar.SERVER); + CVarDef.Create("admin.role_ban_default_severity", "medium", CVar.ARCHIVE | CVar.SERVER | CVar.REPLICATED); /// /// Default severity for department bans /// public static readonly CVarDef DepartmentBanDefaultSeverity = - CVarDef.Create("admin.department_ban_default_severity", "medium", CVar.ARCHIVE | CVar.SERVER); + CVarDef.Create("admin.department_ban_default_severity", "medium", CVar.ARCHIVE | CVar.SERVER | CVar.REPLICATED); /// /// Default severity for server bans /// public static readonly CVarDef ServerBanDefaultSeverity = - CVarDef.Create("admin.server_ban_default_severity", "High", CVar.ARCHIVE | CVar.SERVER); + CVarDef.Create("admin.server_ban_default_severity", "High", CVar.ARCHIVE | CVar.SERVER | CVar.REPLICATED); + + /// + /// Whether a server ban will ban the player's ip by default. + /// + public static readonly CVarDef ServerBanIpBanDefault = + CVarDef.Create("admin.server_ban_ip_ban_default", true, CVar.ARCHIVE | CVar.SERVER | CVar.REPLICATED); + + /// + /// Whether a server ban will ban the player's hardware id by default. + /// + public static readonly CVarDef ServerBanHwidBanDefault = + CVarDef.Create("admin.server_ban_hwid_ban_default", true, CVar.ARCHIVE | CVar.SERVER | CVar.REPLICATED); + + /// + /// Whether to use details from last connection for ip/hwid in the BanPanel. + /// + public static readonly CVarDef ServerBanUseLastDetails = + CVarDef.Create("admin.server_ban_use_last_details", true, CVar.ARCHIVE | CVar.SERVER | CVar.REPLICATED); + + /// + /// Whether to erase a player's chat messages and their entity from the game when banned. + /// + public static readonly CVarDef ServerBanErasePlayer = + CVarDef.Create("admin.server_ban_erase_player", false, CVar.ARCHIVE | CVar.SERVER | CVar.REPLICATED); /// /// Minimum explosion intensity to create an admin alert message. -1 to disable the alert. @@ -1088,6 +1055,13 @@ public static readonly CVarDef public static readonly CVarDef ExplosionSingleTickAreaLimit = CVarDef.Create("explosion.single_tick_area_limit", 400, CVar.SERVERONLY); + /// + /// Whether or not explosions are allowed to create tiles that have + /// set to true. + /// + public static readonly CVarDef ExplosionCanCreateVacuum = + CVarDef.Create("explosion.can_create_vacuum", true, CVar.SERVERONLY); + /* * Radiation */ @@ -1570,7 +1544,7 @@ public static readonly CVarDef /// Whether the arrivals shuttle is enabled. /// public static readonly CVarDef ArrivalsShuttles = - CVarDef.Create("shuttle.arrivals", false, CVar.SERVERONLY); + CVarDef.Create("shuttle.arrivals", true, CVar.SERVERONLY); /// /// The map to use for the arrivals station. @@ -1584,6 +1558,18 @@ public static readonly CVarDef public static readonly CVarDef ArrivalsCooldown = CVarDef.Create("shuttle.arrivals_cooldown", 50f, CVar.SERVERONLY); + /// + /// Time it takes the shuttle to spin up it's hyper drive and jump + /// + public static readonly CVarDef ArrivalsStartupTime= + CVarDef.Create("shuttle.arrivals_startup_time", 5.5f, CVar.SERVERONLY); + + /// + /// Time spent in hyperspace + /// + public static readonly CVarDef ArrivalsHyperspaceTime = + CVarDef.Create("shuttle.arrivals_hyperspace_time", 20f, CVar.SERVERONLY); + /// /// Are players allowed to return on the arrivals shuttle. /// @@ -1596,6 +1582,49 @@ public static readonly CVarDef public static readonly CVarDef GridFill = CVarDef.Create("shuttle.grid_fill", true, CVar.SERVERONLY); + /// + /// Whether to automatically preloading grids by GridPreloaderSystem + /// + public static readonly CVarDef PreloadGrids = + CVarDef.Create("shuttle.preload_grids", true, CVar.SERVERONLY); + + /// + /// How long the warmup time before FTL start should be. + /// + public static readonly CVarDef FTLStartupTime = + CVarDef.Create("shuttle.startup_time", 5.5f, CVar.SERVERONLY); + + /// + /// How long a shuttle spends in FTL. + /// + public static readonly CVarDef FTLTravelTime = + CVarDef.Create("shuttle.travel_time", 20f, CVar.SERVERONLY); + + /// + /// How long the final stage of FTL before arrival should be. + /// + public static readonly CVarDef FTLArrivalTime = + CVarDef.Create("shuttle.arrival_time", 5f, CVar.SERVERONLY); + + /// + /// How much time needs to pass before a shuttle can FTL again. + /// + public static readonly CVarDef FTLCooldown = + CVarDef.Create("shuttle.cooldown", 10f, CVar.SERVERONLY); + + /// + /// The maximum a grid can have before it becomes unable to FTL. + /// Any value equal to or less than zero will disable this check. + /// + public static readonly CVarDef FTLMassLimit = + CVarDef.Create("shuttle.mass_limit", 300f, CVar.SERVERONLY); + + /// + /// How long to knock down entities for if they aren't buckled when FTL starts and stops. + /// + public static readonly CVarDef HyperspaceKnockdownTime = + CVarDef.Create("shuttle.hyperspace_knockdown_time", 5f, CVar.SERVERONLY); + /* * Emergency */ @@ -1620,6 +1649,7 @@ public static readonly CVarDef /// /// The minimum time for the emergency shuttle to arrive at centcomm. + /// Actual minimum travel time cannot be less than /// public static readonly CVarDef EmergencyShuttleMinTransitTime = CVarDef.Create("shuttle.emergency_transit_time_min", 90f, CVar.SERVERONLY); @@ -1678,16 +1708,63 @@ public static readonly CVarDef public static readonly CVarDef CrewManifestUnsecure = CVarDef.Create("crewmanifest.unsecure", true, CVar.REPLICATED); - /* - * Biomass - */ + #region Cloning /// - /// Enabled: Cloning has 70% cost and reclaimer will refuse to reclaim corpses with souls. (For LRP). - /// Disabled: Cloning has full biomass cost and reclaimer can reclaim corpses with souls. (Playtested and balanced for MRP+). + /// How much should the cost to clone an entity be multiplied by. /// - public static readonly CVarDef BiomassEasyMode = - CVarDef.Create("biomass.easy_mode", false, CVar.SERVERONLY); + public static readonly CVarDef CloningBiomassCostMultiplier = + CVarDef.Create("cloning.biomass_cost_multiplier", 1f, CVar.SERVERONLY); + + /// + /// Whether or not the Biomass Reclaimer is allowed to roundremove bodies with a soul. + /// + public static readonly CVarDef CloningReclaimSouledBodies = + CVarDef.Create("cloning.reclaim_souled_bodies", true, CVar.SERVERONLY); + + /// + /// Controls whether or not Metempsychosis will potentially give people a sex change. + /// + public static readonly CVarDef CloningPreserveSex = + CVarDef.Create("cloning.preserve_sex", false, CVar.SERVERONLY); + + /// + /// Controls whether or not Metempsychosis preserves Pronouns when reincarnating people. + /// + public static readonly CVarDef CloningPreserveGender = + CVarDef.Create("cloning.preserve_gender", true, CVar.SERVERONLY); + + /// + /// Controls whether or not Metempsychosis preserves Age. + /// + public static readonly CVarDef CloningPreserveAge = + CVarDef.Create("cloning.preserve_age", false, CVar.SERVERONLY); + + /// + /// Controls whether or not Metempsychosis preserves height. + /// + public static readonly CVarDef CloningPreserveHeight = + CVarDef.Create("cloning.preserve_height", false, CVar.SERVERONLY); + + /// + /// Controls whether or not Metempsychosis preserves width. + /// + public static readonly CVarDef CloningPreserveWidth = + CVarDef.Create("cloning.preserve_width", false, CVar.SERVERONLY); + + /// + /// Controls whether or not Metempsychosis preserves Names. EG: Are you actually a new person? + /// + public static readonly CVarDef CloningPreserveName = + CVarDef.Create("cloning.preserve_name", true, CVar.SERVERONLY); + + /// + /// Controls whether or not Metempsychosis preserves Flavor Text. + /// + public static readonly CVarDef CloningPreserveFlavorText = + CVarDef.Create("cloning.preserve_flavor_text", true, CVar.SERVERONLY); + + #endregion /* * Anomaly @@ -1728,6 +1805,9 @@ public static readonly CVarDef public static readonly CVarDef ViewportWidth = CVarDef.Create("viewport.width", 21, CVar.CLIENTONLY | CVar.ARCHIVE); + public static readonly CVarDef ViewportVerticalFit = + CVarDef.Create("viewport.vertical_fit", true, CVar.CLIENTONLY | CVar.ARCHIVE); + /* * FOV */ @@ -1795,6 +1875,12 @@ public static readonly CVarDef public static readonly CVarDef AccessibilityColorblindFriendly = CVarDef.Create("accessibility.colorblind_friendly", false, CVar.CLIENTONLY | CVar.ARCHIVE); + /// + /// Disables all vision filters for species like Vulpkanin or Harpies. There are good reasons someone might want to disable these. + /// + public static readonly CVarDef NoVisionFilters = + CVarDef.Create("accessibility.no_vision_filters", false, CVar.CLIENTONLY | CVar.ARCHIVE); + /* * CHAT */ @@ -1915,6 +2001,12 @@ public static readonly CVarDef public static readonly CVarDef ICShowSSDIndicator = CVarDef.Create("ic.show_ssd_indicator", true, CVar.CLIENTONLY); + /// + /// Allow Ethereal Ent to PassThrough Walls/Objects while in Ethereal. + /// + public static readonly CVarDef EtherealPassThrough = + CVarDef.Create("ic.EtherealPassThrough", false, CVar.SERVER); + /* * Salvage */ @@ -1970,13 +2062,13 @@ public static readonly CVarDef */ /// - /// Time that players have to wait before rules can be accepted. + /// Time that players have to wait before rules can be accepted. /// public static readonly CVarDef RulesWaitTime = - CVarDef.Create("rules.time", 60f, CVar.SERVER | CVar.REPLICATED); + CVarDef.Create("rules.time", 10f, CVar.SERVER | CVar.REPLICATED); /// - /// Don't show rules to localhost/loopback interface. + /// Don't show rules to localhost/loopback interface. /// public static readonly CVarDef RulesExemptLocal = CVarDef.Create("rules.exempt_local", true, CVar.SERVERONLY); @@ -2023,6 +2115,12 @@ public static readonly CVarDef public static readonly CVarDef ToggleWalk = CVarDef.Create("control.toggle_walk", false, CVar.CLIENTONLY | CVar.ARCHIVE); + /// + /// Whether the player mob is walking by default instead of running. + /// + public static readonly CVarDef DefaultWalk = + CVarDef.Create("control.default_walk", true, CVar.CLIENT | CVar.REPLICATED | CVar.ARCHIVE); + /* * STORAGE */ @@ -2226,6 +2324,14 @@ public static readonly CVarDef public static readonly CVarDef ReplayAutoRecordTempDir = CVarDef.Create("replay.auto_record_temp_dir", "", CVar.SERVERONLY); + + /// + /// The amount of time between NPC Silicons draining their battery in seconds. + /// + public static readonly CVarDef SiliconNpcUpdateTime = + CVarDef.Create("silicon.npcupdatetime", 1.5f, CVar.SERVERONLY); + + /* * Miscellaneous */ @@ -2233,6 +2339,10 @@ public static readonly CVarDef public static readonly CVarDef GatewayGeneratorEnabled = CVarDef.Create("gateway.generator_enabled", true); + // Clippy! + public static readonly CVarDef TippyEntity = + CVarDef.Create("tippy.entity", "Tippy", CVar.SERVER | CVar.REPLICATED); + /* * DEBUG */ @@ -2280,6 +2390,15 @@ public static readonly CVarDef public static readonly CVarDef HeightAdjustModifiesZoom = CVarDef.Create("heightadjust.modifies_zoom", false, CVar.SERVERONLY); + /// + /// Whether height & width sliders adjust a player's bloodstream volume. + /// + /// + /// This can be configured more precisely by modifying BloodstreamAffectedByMassComponent. + /// + public static readonly CVarDef HeightAdjustModifiesBloodstream = + CVarDef.Create("heightadjust.modifies_bloodstream", true, CVar.SERVERONLY); + /// /// Enables station goals /// @@ -2291,7 +2410,7 @@ public static readonly CVarDef /// public static readonly CVarDef StationGoalsChance = CVarDef.Create("game.station_goals_chance", 0.1f, CVar.SERVERONLY); - + #region CPR System /// @@ -2338,7 +2457,7 @@ public static readonly CVarDef /// public static readonly CVarDef CPRAirlossReductionMultiplier = CVarDef.Create("cpr.airloss_reduction_multiplier", 1f, CVar.REPLICATED | CVar.SERVER); - + #endregion #region Contests System @@ -2381,6 +2500,12 @@ public static readonly CVarDef public static readonly CVarDef DoMindContests = CVarDef.Create("contests.do_mind_contests", true, CVar.REPLICATED | CVar.SERVER); + /// + /// Toggles all MoodContest functions. All mood contests output 1f when false. + /// + public static readonly CVarDef DoMoodContests = + CVarDef.Create("contests.do_mood_contests", true, CVar.REPLICATED | CVar.SERVER); + /// /// The maximum amount that Mass Contests can modify a physics multiplier, given as a +/- percentage /// Default of 0.25f outputs between * 0.75f and 1.25f @@ -2438,5 +2563,106 @@ public static readonly CVarDef CVarDef.Create("supermatter.rads_modifier", 1f, CVar.SERVER); #endregion + + #region Mood System + + public static readonly CVarDef MoodEnabled = + CVarDef.Create("mood.enabled", true, CVar.SERVER); + + public static readonly CVarDef MoodIncreasesSpeed = + CVarDef.Create("mood.increases_speed", true, CVar.SERVER); + + public static readonly CVarDef MoodDecreasesSpeed = + CVarDef.Create("mood.decreases_speed", true, CVar.SERVER); + + public static readonly CVarDef MoodModifiesThresholds = + CVarDef.Create("mood.modify_thresholds", false, CVar.SERVER); + + #endregion + + #region Lying Down System + + public static readonly CVarDef AutoGetUp = + CVarDef.Create("rest.auto_get_up", true, CVar.CLIENT | CVar.ARCHIVE | CVar.REPLICATED); + + public static readonly CVarDef HoldLookUp = + CVarDef.Create("rest.hold_look_up", false, CVar.CLIENT | CVar.ARCHIVE); + + /// + /// When true, players can choose to crawl under tables while laying down, using the designated keybind. + /// + public static readonly CVarDef CrawlUnderTables = + CVarDef.Create("rest.crawlundertables", true, CVar.SERVER | CVar.ARCHIVE); + + #endregion + + #region Material Reclaimer + + /// + /// Whether or not a Material Reclaimer is allowed to eat people when emagged. + /// + public static readonly CVarDef ReclaimerAllowGibbing = + CVarDef.Create("reclaimer.allow_gibbing", true, CVar.SERVER); + + #endregion + + #region Jetpack System + + /// + /// When true, Jetpacks can be enabled anywhere, even in gravity. + /// + public static readonly CVarDef JetpackEnableAnywhere = + CVarDef.Create("jetpack.enable_anywhere", false, CVar.REPLICATED); + + /// + /// When true, jetpacks can be enabled on grids that have zero gravity. + /// + public static readonly CVarDef JetpackEnableInNoGravity = + CVarDef.Create("jetpack.enable_in_no_gravity", true, CVar.REPLICATED); + + #endregion + + #region GhostRespawn + + public static readonly CVarDef GhostRespawnTime = + CVarDef.Create("ghost.respawn_time", 15d, CVar.SERVERONLY); + + public static readonly CVarDef GhostRespawnMaxPlayers = + CVarDef.Create("ghost.respawn_max_players", 40, CVar.SERVERONLY); + + public static readonly CVarDef GhostAllowSameCharacter = + CVarDef.Create("ghost.allow_same_character", false, CVar.SERVERONLY); + + #endregion + + #region Surgery + + public static readonly CVarDef CanOperateOnSelf = + CVarDef.Create("surgery.can_operate_on_self", false, CVar.SERVERONLY); + + #endregion + /// + /// Set to true to disable parallel processing in the pow3r solver. + /// + public static readonly CVarDef DebugPow3rDisableParallel = + CVarDef.Create("debug.pow3r_disable_parallel", true, CVar.SERVERONLY); + + /* + * AUTOVOTE SYSTEM + */ + + /// Enables the automatic voting system. + public static readonly CVarDef AutoVoteEnabled = + CVarDef.Create("vote.autovote_enabled", false, CVar.SERVERONLY); + + /// Automatically starts a map vote when returning to the lobby. + /// Requires auto voting to be enabled. + public static readonly CVarDef MapAutoVoteEnabled = + CVarDef.Create("vote.map_autovote_enabled", true, CVar.SERVERONLY); + + /// Automatically starts a gamemode vote when returning to the lobby. + /// Requires auto voting to be enabled. + public static readonly CVarDef PresetAutoVoteEnabled = + CVarDef.Create("vote.preset_autovote_enabled", true, CVar.SERVERONLY); } } diff --git a/Content.Shared/Cargo/CargoOrderData.cs b/Content.Shared/Cargo/CargoOrderData.cs index a6d5fb0a18..ce05d92236 100644 --- a/Content.Shared/Cargo/CargoOrderData.cs +++ b/Content.Shared/Cargo/CargoOrderData.cs @@ -1,48 +1,62 @@ using Robust.Shared.Serialization; -using Content.Shared.Access.Components; using System.Text; namespace Content.Shared.Cargo { - [NetSerializable, Serializable] - public sealed class CargoOrderData + [DataDefinition, NetSerializable, Serializable] + public sealed partial class CargoOrderData { /// /// Price when the order was added. /// + [DataField] public int Price; /// /// A unique (arbitrary) ID which identifies this order. /// - public readonly int OrderId; + [DataField] + public int OrderId { get; private set; } /// /// Prototype Id for the item to be created /// - public readonly string ProductId; + [DataField] + public string ProductId { get; private set; } + + /// + /// Prototype Name + /// + [DataField] + public string ProductName { get; private set; } /// /// The number of items in the order. Not readonly, as it might change /// due to caps on the amount of orders that can be placed. /// + [DataField] public int OrderQuantity; /// /// How many instances of this order that we've already dispatched /// + [DataField] public int NumDispatched = 0; - public readonly string Requester; + [DataField] + public string Requester { get; private set; } // public String RequesterRank; // TODO Figure out how to get Character ID card data // public int RequesterId; - public readonly string Reason; + [DataField] + public string Reason { get; private set; } public bool Approved => Approver is not null; + [DataField] public string? Approver; - public CargoOrderData(int orderId, string productId, int price, int amount, string requester, string reason) + public CargoOrderData(int orderId, string productId, string productName, int price, int amount, string requester, string reason) { OrderId = orderId; ProductId = productId; + ProductName = productName; Price = price; OrderQuantity = amount; Requester = requester; diff --git a/Content.Shared/Cargo/Components/CargoOrderConsoleComponent.cs b/Content.Shared/Cargo/Components/CargoOrderConsoleComponent.cs index a7d1f53175..873e9bb7b9 100644 --- a/Content.Shared/Cargo/Components/CargoOrderConsoleComponent.cs +++ b/Content.Shared/Cargo/Components/CargoOrderConsoleComponent.cs @@ -1,6 +1,8 @@ using Content.Shared.Cargo.Prototypes; using Robust.Shared.Audio; using Robust.Shared.GameStates; +using Content.Shared.Radio; +using Robust.Shared.Prototypes; namespace Content.Shared.Cargo.Components; @@ -21,5 +23,11 @@ public sealed partial class CargoOrderConsoleComponent : Component /// [DataField, ViewVariables(VVAccess.ReadWrite)] public List AllowedGroups = new() { "market" }; + + /// + /// Radio channel on which order approval announcements are transmitted + /// + [DataField, ViewVariables(VVAccess.ReadWrite)] + public ProtoId AnnouncementChannel = "Supply"; } diff --git a/Content.Shared/Cargo/Components/CashComponent.cs b/Content.Shared/Cargo/Components/CashComponent.cs index 10a47ff803..89af094e04 100644 --- a/Content.Shared/Cargo/Components/CashComponent.cs +++ b/Content.Shared/Cargo/Components/CashComponent.cs @@ -3,7 +3,7 @@ namespace Content.Shared.Cargo.Components; /// -/// Can be inserted into a to increase the station's bank account. +/// Can be inserted into a to increase the station's bank account. /// [RegisterComponent, NetworkedComponent] public sealed partial class CashComponent : Component diff --git a/Content.Shared/Cargo/Components/SharedCargoTelepadComponent.cs b/Content.Shared/Cargo/Components/SharedCargoTelepadComponent.cs index 911ea41cca..e69e83d635 100644 --- a/Content.Shared/Cargo/Components/SharedCargoTelepadComponent.cs +++ b/Content.Shared/Cargo/Components/SharedCargoTelepadComponent.cs @@ -1,3 +1,4 @@ +using Content.Shared.Construction.Prototypes; using Content.Shared.DeviceLinking; using Robust.Shared.Audio; using Robust.Shared.GameStates; @@ -12,30 +13,51 @@ namespace Content.Shared.Cargo.Components; [RegisterComponent, NetworkedComponent, Access(typeof(SharedCargoSystem))] public sealed partial class CargoTelepadComponent : Component { + [DataField] + public List CurrentOrders = new(); + /// - /// The actual amount of time it takes to teleport from the telepad + /// The base amount of time it takes to teleport from the telepad /// - [DataField("delay"), ViewVariables(VVAccess.ReadWrite)] + [DataField] + public float BaseDelay = 10f; + + /// + /// The actual amount of time it takes to teleport from the telepad + /// + [DataField] public float Delay = 10f; /// - /// How much time we've accumulated until next teleport. + /// The machine part that affects + /// + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer)), ViewVariables(VVAccess.ReadWrite)] + public string MachinePartTeleportDelay = "Capacitor"; + + /// + /// A multiplier applied to for each level of + /// + [DataField] + public float PartRatingTeleportDelay = 0.8f; + + /// + /// How much time we've accumulated until next teleport. /// - [DataField("accumulator"), ViewVariables(VVAccess.ReadWrite)] + [DataField] public float Accumulator; - [DataField("currentState")] + [DataField] public CargoTelepadState CurrentState = CargoTelepadState.Unpowered; - [DataField("teleportSound")] + [DataField] public SoundSpecifier TeleportSound = new SoundPathSpecifier("/Audio/Machines/phasein.ogg"); /// /// The paper-type prototype to spawn with the order information. /// - [DataField("printerOutput", customTypeSerializer: typeof(PrototypeIdSerializer)), ViewVariables(VVAccess.ReadWrite)] + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer)), ViewVariables(VVAccess.ReadWrite)] public string PrinterOutput = "PaperCargoInvoice"; - [DataField("receiverPort", customTypeSerializer: typeof(PrototypeIdSerializer)), ViewVariables(VVAccess.ReadWrite)] + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer)), ViewVariables(VVAccess.ReadWrite)] public string ReceiverPort = "OrderReceiver"; } diff --git a/Content.Shared/Cargo/Prototypes/CargoProductPrototype.cs b/Content.Shared/Cargo/Prototypes/CargoProductPrototype.cs index 1d0ca8abdb..af2f6613d6 100644 --- a/Content.Shared/Cargo/Prototypes/CargoProductPrototype.cs +++ b/Content.Shared/Cargo/Prototypes/CargoProductPrototype.cs @@ -1,4 +1,4 @@ -using Robust.Shared.Prototypes; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array; using Robust.Shared.Utility; diff --git a/Content.Shared/Chapel/SacrificeDoAfterEvent.cs b/Content.Shared/Chapel/SacrificeDoAfterEvent.cs index 444871a2fc..f730bab883 100644 --- a/Content.Shared/Chapel/SacrificeDoAfterEvent.cs +++ b/Content.Shared/Chapel/SacrificeDoAfterEvent.cs @@ -2,8 +2,6 @@ using Content.Shared.DoAfter; namespace Content.Shared.Chapel; -[Serializable, NetSerializable] -public sealed partial class SacrificeDoAfterEvent : SimpleDoAfterEvent -{ -} +[Serializable, NetSerializable] +public sealed partial class SacrificeDoAfterEvent : SimpleDoAfterEvent { } diff --git a/Content.Shared/Chapel/SacrificialAltarComponent.cs b/Content.Shared/Chapel/SacrificialAltarComponent.cs new file mode 100644 index 0000000000..c3db338861 --- /dev/null +++ b/Content.Shared/Chapel/SacrificialAltarComponent.cs @@ -0,0 +1,48 @@ +using Content.Shared.Random; +using Content.Shared.DoAfter; +using Robust.Shared.Audio; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Chapel; + +/// +/// Altar that lets you sacrifice psionics to lower glimmer by a large amount. +/// +[RegisterComponent, NetworkedComponent, Access(typeof(SharedSacrificialAltarSystem))] +public sealed partial class SacrificialAltarComponent : Component +{ + /// + /// DoAfter for an active sacrifice. + /// + [DataField] + public DoAfterId? DoAfter; + + /// + /// How long it takes to sacrifice someone once they die. + /// This is the window to interrupt a sacrifice if you want glimmer to stay high, or need the psionic to be revived. + /// + [DataField] + public TimeSpan SacrificeTime = TimeSpan.FromSeconds(8.35); + + [DataField] + public SoundSpecifier SacrificeSound = new SoundPathSpecifier("/Audio/Psionics/heartbeat_fast.ogg"); + + [DataField] + public EntityUid? SacrificeStream; + + /// + /// Base amount to reduce glimmer by, multiplied by the victim's Amplification stat. + /// + [DataField] + public float GlimmerReduction = 25; + + [DataField] + public List>? RewardPool; + + /// + /// The base chance to generate an item of power, multiplied by the victim's Dampening stat. + /// + [DataField] + public float BaseItemChance = 0.1f; +} diff --git a/Content.Shared/Chapel/SharedSacrificialAltarSystem.cs b/Content.Shared/Chapel/SharedSacrificialAltarSystem.cs new file mode 100644 index 0000000000..92df7e0f6b --- /dev/null +++ b/Content.Shared/Chapel/SharedSacrificialAltarSystem.cs @@ -0,0 +1,61 @@ +using System.Linq; +using Content.Shared.Buckle.Components; +using Content.Shared.DoAfter; +using Content.Shared.Examine; +using Content.Shared.Verbs; + +namespace Content.Shared.Chapel; + +public abstract partial class SharedSacrificialAltarSystem : EntitySystem +{ + [Dependency] protected readonly SharedDoAfterSystem DoAfter = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnExamined); + SubscribeLocalEvent(OnUnstrapped); + SubscribeLocalEvent>(OnGetVerbs); + } + + private void OnExamined(Entity ent, ref ExaminedEvent args) + { + args.PushMarkup(Loc.GetString("altar-examine")); + } + + private void OnUnstrapped(Entity ent, ref BuckleChangeEvent args) + { + if (ent.Comp.DoAfter is not { } id) + return; + + DoAfter.Cancel(id); + ent.Comp.DoAfter = null; + } + + private void OnGetVerbs(Entity ent, ref GetVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract || ent.Comp.DoAfter != null + || !TryComp(ent, out var strap) + || GetFirstBuckled(strap) is not { } target) + return; + + var user = args.User; + args.Verbs.Add(new AlternativeVerb() + { + Act = () => AttemptSacrifice(ent, user, target), + Text = Loc.GetString("altar-sacrifice-verb"), + Priority = 2 + }); + } + + private EntityUid? GetFirstBuckled(StrapComponent strap) + { + if (strap.BuckledEntities.Count <= 0) + return null; + + return strap.BuckledEntities.First(); + } + + protected virtual void AttemptSacrifice(Entity ent, EntityUid user, EntityUid target) { } +} diff --git a/Content.Shared/Chat/ChatChannel.cs b/Content.Shared/Chat/ChatChannel.cs index d87fcbc075..e3810342b6 100644 --- a/Content.Shared/Chat/ChatChannel.cs +++ b/Content.Shared/Chat/ChatChannel.cs @@ -3,7 +3,7 @@ namespace Content.Shared.Chat /// /// Represents chat channels that the player can filter chat tabs by. /// - [Flags] + [Flags, Serializable] public enum ChatChannel : ushort { None = 0, diff --git a/Content.Shared/Chat/EmotesEvents.cs b/Content.Shared/Chat/EmotesEvents.cs new file mode 100644 index 0000000000..4479f8b2ab --- /dev/null +++ b/Content.Shared/Chat/EmotesEvents.cs @@ -0,0 +1,11 @@ +using Content.Shared.Chat.Prototypes; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; + +namespace Content.Shared.Chat; + +[Serializable, NetSerializable] +public sealed class PlayEmoteMessage(ProtoId protoId) : EntityEventArgs +{ + public readonly ProtoId ProtoId = protoId; +} diff --git a/Content.Shared/Chat/Prototypes/EmotePrototype.cs b/Content.Shared/Chat/Prototypes/EmotePrototype.cs index 08f209d28d..7ee958ee6a 100644 --- a/Content.Shared/Chat/Prototypes/EmotePrototype.cs +++ b/Content.Shared/Chat/Prototypes/EmotePrototype.cs @@ -1,11 +1,13 @@ +using Content.Shared.Whitelist; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; +using Robust.Shared.Utility; namespace Content.Shared.Chat.Prototypes; /// /// IC emotes (scream, smile, clapping, etc). -/// Entities can activate emotes by chat input or code. +/// Entities can activate emotes by chat input, radial or code. /// [Prototype("emote")] public sealed partial class EmotePrototype : IPrototype @@ -13,18 +15,50 @@ public sealed partial class EmotePrototype : IPrototype [IdDataField] public string ID { get; private set; } = default!; + /// + /// Localization string for the emote name. Displayed in the radial UI. + /// + [DataField(required: true)] + public string Name = default!; + + /// + /// Determines if emote available to all by default + /// check comes after this setting + /// can ignore this setting + /// + [DataField] + public bool Available = true; + /// /// Different emote categories may be handled by different systems. /// Also may be used for filtering. /// - [DataField("category")] + [DataField] public EmoteCategory Category = EmoteCategory.General; + /// + /// An icon used to visually represent the emote in radial UI. + /// + [DataField] + public SpriteSpecifier Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/Actions/scream.png")); + + /// + /// Determines conditions to this emote be available to use + /// + [DataField] + public EntityWhitelist? Whitelist; + + /// + /// Determines conditions to this emote be unavailable to use + /// + [DataField] + public EntityWhitelist? Blacklist; + /// /// Collection of words that will be sent to chat if emote activates. /// Will be picked randomly from list. /// - [DataField("chatMessages")] + [DataField] public List ChatMessages = new(); /// @@ -32,7 +66,7 @@ public sealed partial class EmotePrototype : IPrototype /// When typed into players chat they will activate emote event. /// All words should be unique across all emote prototypes. /// - [DataField("chatTriggers")] + [DataField] public HashSet ChatTriggers = new(); } diff --git a/Content.Shared/Chat/Prototypes/EmoteSoundsPrototype.cs b/Content.Shared/Chat/Prototypes/EmoteSoundsPrototype.cs index c9a78e7d6d..2b7064c1e9 100644 --- a/Content.Shared/Chat/Prototypes/EmoteSoundsPrototype.cs +++ b/Content.Shared/Chat/Prototypes/EmoteSoundsPrototype.cs @@ -1,5 +1,6 @@ using Robust.Shared.Audio; using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary; namespace Content.Shared.Chat.Prototypes; @@ -8,8 +9,8 @@ namespace Content.Shared.Chat.Prototypes; /// Sounds collection for each . /// Different entities may use different sounds collections. /// -[Prototype("emoteSounds")] -public sealed partial class EmoteSoundsPrototype : IPrototype +[Prototype("emoteSounds"), Serializable, NetSerializable] +public sealed class EmoteSoundsPrototype : IPrototype { [IdDataField] public string ID { get; private set; } = default!; diff --git a/Content.Shared/Chat/SharedChatEvents.cs b/Content.Shared/Chat/SharedChatEvents.cs new file mode 100644 index 0000000000..6d5c435e05 --- /dev/null +++ b/Content.Shared/Chat/SharedChatEvents.cs @@ -0,0 +1,24 @@ +using Content.Shared.Speech; +using Robust.Shared.Prototypes; +using Content.Shared.Inventory; + +namespace Content.Shared.Chat; + +/// +/// This event should be sent everytime an entity talks (Radio, local chat, etc...). +/// The event is sent to both the entity itself, and all clothing (For stuff like voice masks). +/// +public sealed class TransformSpeakerSpeechEvent : EntityEventArgs, IInventoryRelayEvent +{ + public SlotFlags TargetSlots { get; } = SlotFlags.WITHOUT_POCKET; + public EntityUid Sender; + public string? VoiceName; + public ProtoId? SpeechVerb; + + public TransformSpeakerSpeechEvent(EntityUid sender, string? name = null) + { + Sender = sender; + VoiceName = name; + SpeechVerb = null; + } +} diff --git a/Content.Shared/Chat/SharedChatSystem.cs b/Content.Shared/Chat/SharedChatSystem.cs index c48d70405f..14cb37d18b 100644 --- a/Content.Shared/Chat/SharedChatSystem.cs +++ b/Content.Shared/Chat/SharedChatSystem.cs @@ -70,19 +70,25 @@ public SpeechVerbPrototype GetSpeechVerb(EntityUid source, string message, Speec if (!Resolve(source, ref speech, false)) return _prototypeManager.Index(DefaultSpeechVerb); + var evt = new TransformSpeakerSpeechEvent(source); + RaiseLocalEvent(source, evt); + + SpeechVerbPrototype? speechProto = null; + if (evt.SpeechVerb != null && _prototypeManager.TryIndex(evt.SpeechVerb, out var evntProto)) + speechProto = evntProto; + // check for a suffix-applicable speech verb - SpeechVerbPrototype? current = null; foreach (var (str, id) in speech.SuffixSpeechVerbs) { var proto = _prototypeManager.Index(id); - if (message.EndsWith(Loc.GetString(str)) && proto.Priority >= (current?.Priority ?? 0)) + if (message.EndsWith(Loc.GetString(str)) && proto.Priority >= (speechProto?.Priority ?? 0)) { - current = proto; + speechProto = proto; } } // if no applicable suffix verb return the normal one used by the entity - return current ?? _prototypeManager.Index(speech.SpeechVerb); + return speechProto ?? _prototypeManager.Index(speech.SpeechVerb); } /// @@ -154,10 +160,16 @@ public string SanitizeMessageCapital(string message) if (string.IsNullOrEmpty(message)) return message; // Capitalize first letter - message = char.ToUpper(message[0]) + message.Remove(0, 1); + message = OopsConcat(char.ToUpper(message[0]).ToString(), message.Remove(0, 1)); return message; } + private static string OopsConcat(string a, string b) + { + // This exists to prevent Roslyn being clever and compiling something that fails sandbox checks. + return a + b; + } + public string SanitizeMessageCapitalizeTheWordI(string message, string theWordI = "i") { if (string.IsNullOrEmpty(message)) diff --git a/Content.Shared/Chat/TypingIndicator/SharedTypingIndicatorSystem.cs b/Content.Shared/Chat/TypingIndicator/SharedTypingIndicatorSystem.cs index 41ed4b1b2b..81ebcfb108 100644 --- a/Content.Shared/Chat/TypingIndicator/SharedTypingIndicatorSystem.cs +++ b/Content.Shared/Chat/TypingIndicator/SharedTypingIndicatorSystem.cs @@ -1,5 +1,4 @@ -using Content.Shared.Clothing.Components; -using Content.Shared.Inventory.Events; +using Content.Shared.Clothing; namespace Content.Shared.Chat.TypingIndicator; @@ -17,25 +16,21 @@ public abstract class SharedTypingIndicatorSystem : EntitySystem public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnGotEquipped); - SubscribeLocalEvent(OnGotUnequipped); + SubscribeLocalEvent(OnGotEquipped); + SubscribeLocalEvent(OnGotUnequipped); } - private void OnGotEquipped(EntityUid uid, TypingIndicatorClothingComponent component, GotEquippedEvent args) + private void OnGotEquipped(EntityUid uid, TypingIndicatorClothingComponent component, ClothingGotEquippedEvent args) { - if (!TryComp(uid, out var clothing) || - !TryComp(args.Equipee, out var indicator)) + if (!TryComp(args.Wearer, out var indicator)) return; - var isCorrectSlot = clothing.Slots.HasFlag(args.SlotFlags); - if (!isCorrectSlot) return; - indicator.Prototype = component.Prototype; } - private void OnGotUnequipped(EntityUid uid, TypingIndicatorClothingComponent component, GotUnequippedEvent args) + private void OnGotUnequipped(EntityUid uid, TypingIndicatorClothingComponent component, ClothingGotUnequippedEvent args) { - if (!TryComp(args.Equipee, out var indicator)) + if (!TryComp(args.Wearer, out var indicator)) return; indicator.Prototype = SharedTypingIndicatorSystem.InitialIndicatorId; diff --git a/Content.Shared/Chat/V2/Repository/Types.cs b/Content.Shared/Chat/V2/Repository/Types.cs new file mode 100644 index 0000000000..59acb849d4 --- /dev/null +++ b/Content.Shared/Chat/V2/Repository/Types.cs @@ -0,0 +1,60 @@ +using System.Linq; +using System.Runtime.InteropServices; +using Robust.Shared.Network; +using Robust.Shared.Serialization; + +namespace Content.Shared.Chat.V2.Repository; + +/// +/// The record associated with a specific chat event. +/// +public struct ChatRecord(string userName, NetUserId userId, IChatEvent storedEvent, string entityName) +{ + public string UserName = userName; + public NetUserId UserId = userId; + public string EntityName = entityName; + public IChatEvent StoredEvent = storedEvent; +} + +/// +/// Notifies that a chat message has been created. +/// +/// +[Serializable, NetSerializable] +public sealed class MessageCreatedEvent(IChatEvent ev) : EntityEventArgs +{ + public IChatEvent Event = ev; +} + +/// +/// Notifies that a chat message has been changed. +/// +/// +/// +[Serializable, NetSerializable] +public sealed class MessagePatchedEvent(uint id, string newMessage) : EntityEventArgs +{ + public uint MessageId = id; + public string NewMessage = newMessage; +} + +/// +/// Notifies that a chat message has been deleted. +/// +/// +[Serializable, NetSerializable] +public sealed class MessageDeletedEvent(uint id) : EntityEventArgs +{ + public uint MessageId = id; +} + +/// +/// Notifies that a player's messages have been nuked. +/// +/// +[Serializable, NetSerializable] +public sealed class MessagesNukedEvent(List set) : EntityEventArgs +{ + public uint[] MessageIds = CollectionsMarshal.AsSpan(set).ToArray(); +} + diff --git a/Content.Shared/Chat/V2/Types.cs b/Content.Shared/Chat/V2/Types.cs new file mode 100644 index 0000000000..50e5a53ab5 --- /dev/null +++ b/Content.Shared/Chat/V2/Types.cs @@ -0,0 +1,94 @@ +namespace Content.Shared.Chat.V2; + +/// +/// The types of messages that can be sent, validated and processed via user input that are covered by Chat V2. +/// +public enum MessageType : byte +{ + #region Player-sendable types + + /// + /// Chat for announcements like CentCom telling you to stop sending them memes. + /// + Announcement, + /// + /// Chat that ghosts use to complain about being gibbed. + /// + DeadChat, + /// + /// Chat that mimes use to evade their vow. + /// + Emote, + /// + /// Chat that players use to make lame jokes to people nearby. + /// + Local, + /// + /// Chat that players use to complain about shitsec/admins/antags/balance/etc. + /// + Looc, + /// + /// Chat that players use to say "HELP MAINT", or plead to call the shuttle because a beaker spilled. + /// + /// This does not tell you what radio channel has been chatted on! + Radio, + /// + /// Chat that is used exclusively by syndie tots to collaborate on whatever tots do. + /// + Whisper, + + #endregion + + #region Non-player-sendable types + + /// + /// Chat that is sent to exactly one player; almost exclusively used for admemes and prayer responses. + /// + Subtle, + /// + /// Chat that is sent by automata, like when a vending machine thanks you for your unwise purchases. + /// + Background, + + #endregion +} + +/// +/// Defines a chat event that can be stored in a chat repository. +/// +public interface IChatEvent +{ + /// + /// The sender of the chat message. + /// + public EntityUid Sender + { + get; + } + + /// + /// The ID of the message. This is overwritten when saved into a repository. + /// + public uint Id + { + get; + set; + } + + /// + /// The sent message. + /// + public string Message + { + get; + set; + } + + /// + /// The type of sent message. + /// + public MessageType Type + { + get; + } +} diff --git a/Content.Shared/Chemistry/Components/InjectorComponent.cs b/Content.Shared/Chemistry/Components/InjectorComponent.cs index 437c5e327d..c583ee6664 100644 --- a/Content.Shared/Chemistry/Components/InjectorComponent.cs +++ b/Content.Shared/Chemistry/Components/InjectorComponent.cs @@ -24,7 +24,8 @@ public sealed partial class InjectorDoAfterEvent : SimpleDoAfterEvent [RegisterComponent, NetworkedComponent, AutoGenerateComponentState] public sealed partial class InjectorComponent : Component { - public const string SolutionName = "injector"; + [DataField] + public string SolutionName = "injector"; /// /// Whether or not the injector is able to draw from containers or if it's a single use diff --git a/Content.Shared/Chemistry/Components/MixableSolutionComponent.cs b/Content.Shared/Chemistry/Components/MixableSolutionComponent.cs new file mode 100644 index 0000000000..2b1c140aa6 --- /dev/null +++ b/Content.Shared/Chemistry/Components/MixableSolutionComponent.cs @@ -0,0 +1,13 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Chemistry.Components; + +[RegisterComponent, NetworkedComponent] +public sealed partial class MixableSolutionComponent : Component +{ + /// + /// Solution name which can be mixed with methods such as blessing + /// + [DataField, ViewVariables(VVAccess.ReadWrite)] + public string Solution = "default"; +} diff --git a/Content.Shared/Chemistry/Components/ReagentTankComponent.cs b/Content.Shared/Chemistry/Components/ReagentTankComponent.cs new file mode 100644 index 0000000000..3aa1756cf9 --- /dev/null +++ b/Content.Shared/Chemistry/Components/ReagentTankComponent.cs @@ -0,0 +1,22 @@ +using Content.Shared.FixedPoint; +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; + +namespace Content.Shared.Chemistry.Components; + +[RegisterComponent, NetworkedComponent] +public sealed partial class ReagentTankComponent : Component +{ + [DataField, ViewVariables(VVAccess.ReadWrite)] + public FixedPoint2 TransferAmount { get; set; } = FixedPoint2.New(10); + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public ReagentTankType TankType { get; set; } = ReagentTankType.Unspecified; +} + +[Serializable, NetSerializable] +public enum ReagentTankType : byte +{ + Unspecified, + Fuel +} diff --git a/Content.Shared/Chemistry/Components/Solution.cs b/Content.Shared/Chemistry/Components/Solution.cs index 1c24c860dd..f16cf4a80f 100644 --- a/Content.Shared/Chemistry/Components/Solution.cs +++ b/Content.Shared/Chemistry/Components/Solution.cs @@ -6,6 +6,7 @@ using Robust.Shared.Utility; using System.Collections; using System.Linq; +using Content.Shared.Chemistry.Components.SolutionManager; namespace Content.Shared.Chemistry.Components { @@ -48,13 +49,6 @@ public sealed partial class Solution : IEnumerable, ISerializat [DataField("canReact")] public bool CanReact { get; set; } = true; - /// - /// If reactions can occur via mixing. - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("canMix")] - public bool CanMix { get; set; } = false; - /// /// Volume needed to fill this container. /// diff --git a/Content.Shared/Chemistry/EntitySystems/RehydratableSystem.cs b/Content.Shared/Chemistry/EntitySystems/RehydratableSystem.cs index e260280ae4..36ca791290 100644 --- a/Content.Shared/Chemistry/EntitySystems/RehydratableSystem.cs +++ b/Content.Shared/Chemistry/EntitySystems/RehydratableSystem.cs @@ -1,12 +1,14 @@ using Content.Shared.Chemistry.Components; using Content.Shared.FixedPoint; using Content.Shared.Popups; +using Robust.Shared.Network; using Robust.Shared.Random; namespace Content.Shared.Chemistry.EntitySystems; public sealed class RehydratableSystem : EntitySystem { + [Dependency] private readonly INetManager _net = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly SharedSolutionContainerSystem _solutions = default!; @@ -31,6 +33,9 @@ private void OnSolutionChange(Entity ent, ref SolutionCon // Try not to make this public if you can help it. private void Expand(Entity ent) { + if (_net.IsClient) + return; + var (uid, comp) = ent; var randomMob = _random.Pick(comp.PossibleSpawns); diff --git a/Content.Shared/Chemistry/EntitySystems/SharedInjectorSystem.cs b/Content.Shared/Chemistry/EntitySystems/SharedInjectorSystem.cs index 6c43c1d5f0..1620344652 100644 --- a/Content.Shared/Chemistry/EntitySystems/SharedInjectorSystem.cs +++ b/Content.Shared/Chemistry/EntitySystems/SharedInjectorSystem.cs @@ -113,7 +113,7 @@ private void Toggle(Entity injector, EntityUid user) if (injector.Comp.InjectOnly) return; - if (!SolutionContainers.TryGetSolution(injector.Owner, InjectorComponent.SolutionName, out var solEnt, out var solution)) + if (!SolutionContainers.TryGetSolution(injector.Owner, injector.Comp.SolutionName, out var solEnt, out var solution)) return; string msg; diff --git a/Content.Shared/Chemistry/EntitySystems/SharedSolutionContainerSystem.Capabilities.cs b/Content.Shared/Chemistry/EntitySystems/SharedSolutionContainerSystem.Capabilities.cs index 0d4912a504..ce0cfab002 100644 --- a/Content.Shared/Chemistry/EntitySystems/SharedSolutionContainerSystem.Capabilities.cs +++ b/Content.Shared/Chemistry/EntitySystems/SharedSolutionContainerSystem.Capabilities.cs @@ -78,31 +78,15 @@ public bool TryGetFitsInDispenser(Entity container, [NotNullWhen(true)] out Entity? solution) + public bool TryGetMixableSolution(Entity entity, [NotNullWhen(true)] out Entity? soln, [NotNullWhen(true)] out Solution? solution) { - var getMixableSolutionAttempt = new GetMixableSolutionAttemptEvent(container); - RaiseLocalEvent(container, ref getMixableSolutionAttempt); - if (getMixableSolutionAttempt.MixedSolution != null) - { - solution = getMixableSolutionAttempt.MixedSolution; - return true; - } - - if (!Resolve(container, ref container.Comp, false)) + if (!Resolve(entity, ref entity.Comp1, logMissing: false)) { - solution = default!; + (soln, solution) = (default!, null); return false; } - var tryGetSolution = EnumerateSolutions(container).FirstOrNull(x => x.Solution.Comp.Solution.CanMix); - if (tryGetSolution.HasValue) - { - solution = tryGetSolution.Value.Solution; - return true; - } - - solution = default!; - return false; + return TryGetSolution((entity.Owner, entity.Comp2), entity.Comp1.Solution, out soln, out solution); } #endregion Solution Accessors diff --git a/Content.Shared/Chemistry/EntitySystems/SharedSolutionContainerSystem.cs b/Content.Shared/Chemistry/EntitySystems/SharedSolutionContainerSystem.cs index e74c146380..5e58a0944a 100644 --- a/Content.Shared/Chemistry/EntitySystems/SharedSolutionContainerSystem.cs +++ b/Content.Shared/Chemistry/EntitySystems/SharedSolutionContainerSystem.cs @@ -11,10 +11,13 @@ using Robust.Shared.Utility; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Numerics; using System.Runtime.CompilerServices; using System.Text; using Content.Shared.Hands.Components; using Content.Shared.Hands.EntitySystems; +using Robust.Shared.Map; +using Robust.Shared.Network; using Dependency = Robust.Shared.IoC.DependencyAttribute; namespace Content.Shared.Chemistry.EntitySystems; @@ -57,7 +60,8 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem [Dependency] protected readonly SharedAppearanceSystem AppearanceSystem = default!; [Dependency] protected readonly SharedHandsSystem Hands = default!; [Dependency] protected readonly SharedContainerSystem ContainerSystem = default!; - [Dependency] protected readonly MetaDataSystem MetaData = default!; + [Dependency] protected readonly MetaDataSystem MetaDataSys = default!; + [Dependency] protected readonly INetManager NetManager = default!; public override void Initialize() { @@ -66,13 +70,18 @@ public override void Initialize() InitializeRelays(); SubscribeLocalEvent(OnComponentInit); - SubscribeLocalEvent(OnComponentStartup); - SubscribeLocalEvent(OnComponentShutdown); - - SubscribeLocalEvent(OnComponentInit); - + SubscribeLocalEvent(OnSolutionStartup); + SubscribeLocalEvent(OnSolutionShutdown); + SubscribeLocalEvent(OnContainerManagerInit); SubscribeLocalEvent(OnExamineSolution); SubscribeLocalEvent>(OnSolutionExaminableVerb); + SubscribeLocalEvent(OnMapInit); + + if (NetManager.IsServer) + { + SubscribeLocalEvent(OnContainerManagerShutdown); + SubscribeLocalEvent(OnContainedSolutionShutdown); + } } @@ -121,8 +130,14 @@ public bool ResolveSolution(Entity container /// The name of the solution entity to fetch. /// Returns the solution entity that was fetched. /// Returns the solution state of the solution entity that was fetched. + /// /// Should we print an error if the solution specified by name is missing /// - public bool TryGetSolution(Entity container, string? name, [NotNullWhen(true)] out Entity? entity, [NotNullWhen(true)] out Solution? solution) + public bool TryGetSolution( + Entity container, + string? name, + [NotNullWhen(true)] out Entity? entity, + [NotNullWhen(true)] out Solution? solution, + bool errorOnMissing = false) { if (!TryGetSolution(container, name, out entity)) { @@ -135,7 +150,11 @@ public bool TryGetSolution(Entity container, } /// - public bool TryGetSolution(Entity container, string? name, [NotNullWhen(true)] out Entity? entity) + public bool TryGetSolution( + Entity container, + string? name, + [NotNullWhen(true)] out Entity? entity, + bool errorOnMissing = false) { if (TryComp(container, out BlockSolutionAccessComponent? blocker)) { @@ -155,12 +174,18 @@ solutionSlot.ContainedEntity is { } containedSolution else { entity = null; + if (!errorOnMissing) + return false; + Log.Error($"{ToPrettyString(container)} does not have a solution with ID: {name}"); return false; } if (!TryComp(uid, out SolutionComponent? comp)) { entity = null; + if (!errorOnMissing) + return false; + Log.Error($"{ToPrettyString(container)} does not have a solution with ID: {name}"); return false; } @@ -171,13 +196,18 @@ solutionSlot.ContainedEntity is { } containedSolution /// /// Version of TryGetSolution that doesn't take or return an entity. /// Used for prototypes and with old code parity. - public bool TryGetSolution(SolutionContainerManagerComponent container, string name, [NotNullWhen(true)] out Solution? solution) + public bool TryGetSolution(SolutionContainerManagerComponent container, + string name, + [NotNullWhen(true)] out Solution? solution, + bool errorOnMissing = false) { solution = null; - if (container.Solutions == null) + if (container.Solutions != null) + return container.Solutions.TryGetValue(name, out solution); + if (!errorOnMissing) return false; - - return container.Solutions.TryGetValue(name, out solution); + Log.Error($"{container} does not have a solution with ID: {name}"); + return false; } public IEnumerable<(string? Name, Entity Solution)> EnumerateSolutions(Entity container, bool includeSelf = true) @@ -703,17 +733,17 @@ private void OnComponentInit(Entity entity, ref ComponentInit entity.Comp.Solution.ValidateSolution(); } - private void OnComponentStartup(Entity entity, ref ComponentStartup args) + private void OnSolutionStartup(Entity entity, ref ComponentStartup args) { UpdateChemicals(entity); } - private void OnComponentShutdown(Entity entity, ref ComponentShutdown args) + private void OnSolutionShutdown(Entity entity, ref ComponentShutdown args) { RemoveAllSolution(entity); } - private void OnComponentInit(Entity entity, ref ComponentInit args) + private void OnContainerManagerInit(Entity entity, ref ComponentInit args) { if (entity.Comp.Containers is not { Count: > 0 } containers) return; @@ -733,7 +763,7 @@ private void OnExamineSolution(Entity entity, ref E return; } - if (!CanSeeHiddenSolution(entity,args.Examiner)) + if (!CanSeeHiddenSolution(entity, args.Examiner)) return; var primaryReagent = solution.GetPrimaryReagentId(); @@ -832,7 +862,7 @@ private void OnSolutionExaminableVerb(Entity entity return; } - if (!CanSeeHiddenSolution(entity,args.User)) + if (!CanSeeHiddenSolution(entity, args.User)) return; var target = args.Target; @@ -881,6 +911,9 @@ private FormattedMessage GetSolutionExamine(Solution solution) , ("amount", quantity))); } + msg.PushNewline(); + msg.AddMarkup(Loc.GetString("scannable-solution-temperature", ("temperature", Math.Round(solution.Temperature)))); + return msg; } @@ -901,5 +934,273 @@ private bool CanSeeHiddenSolution(Entity entity, En return true; } + private void OnMapInit(Entity entity, ref MapInitEvent args) + { + EnsureAllSolutions(entity); + } + + private void OnContainerManagerShutdown(Entity entity, ref ComponentShutdown args) + { + foreach (var name in entity.Comp.Containers) + { + if (ContainerSystem.TryGetContainer(entity, $"solution@{name}", out var solutionContainer)) + ContainerSystem.ShutdownContainer(solutionContainer); + } + entity.Comp.Containers.Clear(); + } + + private void OnContainedSolutionShutdown(Entity entity, ref ComponentShutdown args) + { + if (TryComp(entity.Comp.Container, out SolutionContainerManagerComponent? container)) + { + container.Containers.Remove(entity.Comp.ContainerName); + Dirty(entity.Comp.Container, container); + } + + if (ContainerSystem.TryGetContainer(entity, $"solution@{entity.Comp.ContainerName}", out var solutionContainer)) + ContainerSystem.ShutdownContainer(solutionContainer); + } + #endregion Event Handlers + + public bool EnsureSolution( + Entity entity, + string name, + [NotNullWhen(true)]out Solution? solution, + FixedPoint2 maxVol = default) + { + return EnsureSolution(entity, name, maxVol, null, out _, out solution); + } + + public bool EnsureSolution( + Entity entity, + string name, + out bool existed, + [NotNullWhen(true)]out Solution? solution, + FixedPoint2 maxVol = default) + { + return EnsureSolution(entity, name, maxVol, null, out existed, out solution); + } + + public bool EnsureSolution( + Entity entity, + string name, + FixedPoint2 maxVol, + Solution? prototype, + out bool existed, + [NotNullWhen(true)] out Solution? solution) + { + solution = null; + existed = false; + + var (uid, meta) = entity; + if (!Resolve(uid, ref meta)) + throw new InvalidOperationException("Attempted to ensure solution on invalid entity."); + var manager = EnsureComp(uid); + if (meta.EntityLifeStage >= EntityLifeStage.MapInitialized) + { + EnsureSolutionEntity((uid, manager), name, out existed, + out var solEnt, maxVol, prototype); + solution = solEnt!.Value.Comp.Solution; + return true; + } + solution = EnsureSolutionPrototype((uid, manager), name, maxVol, prototype, out existed); + return true; + } + + public void EnsureAllSolutions(Entity entity) + { + if (NetManager.IsClient) + return; + + if (entity.Comp.Solutions is not { } prototypes) + return; + + foreach (var (name, prototype) in prototypes) + { + EnsureSolutionEntity((entity.Owner, entity.Comp), name, out _, out _, prototype.MaxVolume, prototype); + } + + entity.Comp.Solutions = null; + Dirty(entity); + } + + public bool EnsureSolutionEntity( + Entity entity, + string name, + [NotNullWhen(true)] out Entity? solutionEntity, + FixedPoint2 maxVol = default) => + EnsureSolutionEntity(entity, name, out _, out solutionEntity, maxVol); + + public bool EnsureSolutionEntity( + Entity entity, + string name, + out bool existed, + [NotNullWhen(true)] out Entity? solutionEntity, + FixedPoint2 maxVol = default, + Solution? prototype = null + ) + { + existed = true; + solutionEntity = null; + + var (uid, container) = entity; + + var solutionSlot = ContainerSystem.EnsureContainer(uid, $"solution@{name}", out existed); + if (!Resolve(uid, ref container, logMissing: false)) + { + existed = false; + container = AddComp(uid); + container.Containers.Add(name); + if (NetManager.IsClient) + return false; + } + else if (!existed) + { + container.Containers.Add(name); + Dirty(uid, container); + } + + var needsInit = false; + SolutionComponent solutionComp; + if (solutionSlot.ContainedEntity is not { } solutionId) + { + if (NetManager.IsClient) + return false; + prototype ??= new() { MaxVolume = maxVol }; + prototype.Name = name; + (solutionId, solutionComp, _) = SpawnSolutionUninitialized(solutionSlot, name, maxVol, prototype); + existed = false; + needsInit = true; + Dirty(uid, container); + } + else + { + solutionComp = Comp(solutionId); + DebugTools.Assert(TryComp(solutionId, out ContainedSolutionComponent? relation) && relation.Container == uid && relation.ContainerName == name); + DebugTools.Assert(solutionComp.Solution.Name == name); + + var solution = solutionComp.Solution; + solution.MaxVolume = FixedPoint2.Max(solution.MaxVolume, maxVol); + + // Depending on MapInitEvent order some systems can ensure solution empty solutions and conflict with the prototype solutions. + // We want the reagents from the prototype to exist even if something else already created the solution. + if (prototype is { Volume.Value: > 0 }) + solution.AddSolution(prototype, PrototypeManager); + + Dirty(solutionId, solutionComp); + } + + if (needsInit) + EntityManager.InitializeAndStartEntity(solutionId, Transform(solutionId).MapID); + solutionEntity = (solutionId, solutionComp); + return true; + } + + private Solution EnsureSolutionPrototype(Entity entity, string name, FixedPoint2 maxVol, Solution? prototype, out bool existed) + { + existed = true; + + var (uid, container) = entity; + if (!Resolve(uid, ref container, logMissing: false)) + { + container = AddComp(uid); + existed = false; + } + + if (container.Solutions is null) + container.Solutions = new(SolutionContainerManagerComponent.DefaultCapacity); + + if (!container.Solutions.TryGetValue(name, out var solution)) + { + solution = prototype ?? new() { Name = name, MaxVolume = maxVol }; + container.Solutions.Add(name, solution); + existed = false; + } + else + solution.MaxVolume = FixedPoint2.Max(solution.MaxVolume, maxVol); + + Dirty(uid, container); + return solution; + } + + private Entity SpawnSolutionUninitialized(ContainerSlot container, string name, FixedPoint2 maxVol, Solution prototype) + { + var coords = new EntityCoordinates(container.Owner, Vector2.Zero); + var uid = EntityManager.CreateEntityUninitialized(null, coords, null); + + var solution = new SolutionComponent() { Solution = prototype }; + AddComp(uid, solution); + + var relation = new ContainedSolutionComponent() { Container = container.Owner, ContainerName = name }; + AddComp(uid, relation); + + MetaDataSys.SetEntityName(uid, $"solution - {name}"); + ContainerSystem.Insert(uid, container, force: true); + + return (uid, solution, relation); + } + + public void AdjustDissolvedReagent( + Entity dissolvedSolution, + FixedPoint2 volume, + ReagentId reagent, + float concentrationChange) + { + if (concentrationChange == 0) + return; + var dissolvedSol = dissolvedSolution.Comp.Solution; + var amtChange = + GetReagentQuantityFromConcentration(dissolvedSolution, volume, MathF.Abs(concentrationChange)); + if (concentrationChange > 0) + { + dissolvedSol.AddReagent(reagent, amtChange); + } + else + { + dissolvedSol.RemoveReagent(reagent,amtChange); + } + UpdateChemicals(dissolvedSolution); + } + + public FixedPoint2 GetReagentQuantityFromConcentration(Entity dissolvedSolution, + FixedPoint2 volume,float concentration) + { + var dissolvedSol = dissolvedSolution.Comp.Solution; + if (volume == 0 + || dissolvedSol.Volume == 0) + return 0; + return concentration * volume; + } + + public float GetReagentConcentration(Entity dissolvedSolution, + FixedPoint2 volume, ReagentId dissolvedReagent) + { + var dissolvedSol = dissolvedSolution.Comp.Solution; + if (volume == 0 + || dissolvedSol.Volume == 0 + || !dissolvedSol.TryGetReagentQuantity(dissolvedReagent, out var dissolvedVol)) + return 0; + return (float)dissolvedVol / volume.Float(); + } + + public FixedPoint2 ClampReagentAmountByConcentration( + Entity dissolvedSolution, + FixedPoint2 volume, + ReagentId dissolvedReagent, + FixedPoint2 dissolvedReagentAmount, + float maxConcentration = 1f) + { + var dissolvedSol = dissolvedSolution.Comp.Solution; + if (volume == 0 + || dissolvedSol.Volume == 0 + || !dissolvedSol.TryGetReagentQuantity(dissolvedReagent, out var dissolvedVol)) + return 0; + volume *= maxConcentration; + dissolvedVol += dissolvedReagentAmount; + var overflow = volume - dissolvedVol; + if (overflow < 0) + dissolvedReagentAmount += overflow; + return dissolvedReagentAmount; + } } diff --git a/Content.Shared/Chemistry/EntitySystems/SolutionTransferSystem.cs b/Content.Shared/Chemistry/EntitySystems/SolutionTransferSystem.cs index 34a64d0edb..b12778262c 100644 --- a/Content.Shared/Chemistry/EntitySystems/SolutionTransferSystem.cs +++ b/Content.Shared/Chemistry/EntitySystems/SolutionTransferSystem.cs @@ -17,7 +17,6 @@ namespace Content.Shared.Chemistry.EntitySystems; /// public sealed class SolutionTransferSystem : EntitySystem { - [Dependency] private readonly INetManager _net = default!; [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly SharedSolutionContainerSystem _solution = default!; @@ -42,7 +41,7 @@ private void OnTransferAmountSetValueMessage(Entity e var newTransferAmount = FixedPoint2.Clamp(message.Value, ent.Comp.MinimumTransferAmount, ent.Comp.MaximumTransferAmount); ent.Comp.TransferAmount = newTransferAmount; - if (message.Session.AttachedEntity is { Valid: true } user) + if (message.Actor is { Valid: true } user) _popup.PopupClient(Loc.GetString("comp-solution-transfer-set-amount", ("amount", newTransferAmount)), ent, user); } @@ -53,10 +52,9 @@ private void AddSetTransferVerbs(Entity ent, ref GetV if (!args.CanAccess || !args.CanInteract || !comp.CanChangeTransferAmount || args.Hands == null) return; - if (!TryComp(args.User, out var actor)) - return; - // Custom transfer verb + var @event = args; + args.Verbs.Add(new AlternativeVerb() { Text = Loc.GetString("comp-solution-transfer-verb-custom-amount"), @@ -64,8 +62,7 @@ private void AddSetTransferVerbs(Entity ent, ref GetV // TODO: remove server check when bui prediction is a thing Act = () => { - if (_net.IsServer) - _ui.TryOpen(uid, TransferAmountUiKey.Key, actor.PlayerSession); + _ui.OpenUi(uid, TransferAmountUiKey.Key, @event.User); }, Priority = 1 }); diff --git a/Content.Shared/Chemistry/Reaction/ReactionMixerComponent.cs b/Content.Shared/Chemistry/Reaction/ReactionMixerComponent.cs index ede73c4969..118f224061 100644 --- a/Content.Shared/Chemistry/Reaction/ReactionMixerComponent.cs +++ b/Content.Shared/Chemistry/Reaction/ReactionMixerComponent.cs @@ -25,6 +25,3 @@ public sealed partial class ReactionMixerComponent : Component public record struct MixingAttemptEvent(EntityUid Mixed, bool Cancelled = false); public readonly record struct AfterMixingEvent(EntityUid Mixed, EntityUid Mixer); - -[ByRefEvent] -public record struct GetMixableSolutionAttemptEvent(EntityUid Mixed, Entity? MixedSolution = null); diff --git a/Content.Shared/Chemistry/Reagent/ReagentPrototype.cs b/Content.Shared/Chemistry/Reagent/ReagentPrototype.cs index 5d6d9d2120..df1b1aa20b 100644 --- a/Content.Shared/Chemistry/Reagent/ReagentPrototype.cs +++ b/Content.Shared/Chemistry/Reagent/ReagentPrototype.cs @@ -104,6 +104,13 @@ public sealed partial class ReagentPrototype : IPrototype, IInheritingPrototype [DataField] public bool Slippery; + /// + /// How easily this reagent becomes fizzy when aggitated. + /// 0 - completely flat, 1 - fizzes up when nudged. + /// + [DataField] + public float Fizziness; + /// /// How much reagent slows entities down if it's part of a puddle. /// 0 - no slowdown; 1 - can't move. diff --git a/Content.Shared/Chemistry/SharedReagentDispenser.cs b/Content.Shared/Chemistry/SharedReagentDispenser.cs index 2b9c318c58..5de3f6cae3 100644 --- a/Content.Shared/Chemistry/SharedReagentDispenser.cs +++ b/Content.Shared/Chemistry/SharedReagentDispenser.cs @@ -20,6 +20,46 @@ public ReagentDispenserSetDispenseAmountMessage(ReagentDispenserDispenseAmount a { ReagentDispenserDispenseAmount = amount; } + + /// + /// Create a new instance from interpreting a String as an integer, + /// throwing an exception if it is unable to parse. + /// + public ReagentDispenserSetDispenseAmountMessage(String s) + { + switch (s) + { + case "1": + ReagentDispenserDispenseAmount = ReagentDispenserDispenseAmount.U1; + break; + case "5": + ReagentDispenserDispenseAmount = ReagentDispenserDispenseAmount.U5; + break; + case "10": + ReagentDispenserDispenseAmount = ReagentDispenserDispenseAmount.U10; + break; + case "15": + ReagentDispenserDispenseAmount = ReagentDispenserDispenseAmount.U15; + break; + case "20": + ReagentDispenserDispenseAmount = ReagentDispenserDispenseAmount.U20; + break; + case "25": + ReagentDispenserDispenseAmount = ReagentDispenserDispenseAmount.U25; + break; + case "30": + ReagentDispenserDispenseAmount = ReagentDispenserDispenseAmount.U30; + break; + case "50": + ReagentDispenserDispenseAmount = ReagentDispenserDispenseAmount.U50; + break; + case "100": + ReagentDispenserDispenseAmount = ReagentDispenserDispenseAmount.U100; + break; + default: + throw new Exception($"Cannot convert the string `{s}` into a valid ReagentDispenser DispenseAmount"); + } + } } [Serializable, NetSerializable] @@ -52,20 +92,30 @@ public enum ReagentDispenserDispenseAmount U100 = 100, } + [Serializable, NetSerializable] + public sealed class ReagentInventoryItem(string storageSlotId, string reagentLabel, string storedAmount, Color reagentColor) + { + public string StorageSlotId = storageSlotId; + public string ReagentLabel = reagentLabel; + public string StoredAmount = storedAmount; + public Color ReagentColor = reagentColor; + } + [Serializable, NetSerializable] public sealed class ReagentDispenserBoundUserInterfaceState : BoundUserInterfaceState { public readonly ContainerInfo? OutputContainer; public readonly NetEntity? OutputContainerEntity; + /// /// A list of the reagents which this dispenser can dispense. /// - public readonly List>> Inventory; + public readonly List Inventory; public readonly ReagentDispenserDispenseAmount SelectedDispenseAmount; - public ReagentDispenserBoundUserInterfaceState(ContainerInfo? outputContainer, NetEntity? outputContainerEntity, List>> inventory, ReagentDispenserDispenseAmount selectedDispenseAmount) + public ReagentDispenserBoundUserInterfaceState(ContainerInfo? outputContainer, NetEntity? outputContainerEntity, List inventory, ReagentDispenserDispenseAmount selectedDispenseAmount) { OutputContainer = outputContainer; OutputContainerEntity = outputContainerEntity; diff --git a/Content.Shared/Climbing/Systems/BonkSystem.cs b/Content.Shared/Climbing/Systems/BonkSystem.cs index 08ca63368a..c7c89a3b7f 100644 --- a/Content.Shared/Climbing/Systems/BonkSystem.cs +++ b/Content.Shared/Climbing/Systems/BonkSystem.cs @@ -108,17 +108,16 @@ private bool TryStartBonk(EntityUid uid, EntityUid user, EntityUid climber, Bonk { BreakOnTargetMove = true, BreakOnUserMove = true, - BreakOnDamage = true + BreakOnDamage = true, + DuplicateCondition = DuplicateConditions.SameTool | DuplicateConditions.SameTarget }; - _doAfter.TryStartDoAfter(doAfterArgs); - - return true; + return _doAfter.TryStartDoAfter(doAfterArgs); } - private void OnAttemptClimb(EntityUid uid, BonkableComponent component, AttemptClimbEvent args) + private void OnAttemptClimb(EntityUid uid, BonkableComponent component, ref AttemptClimbEvent args) { - if (args.Cancelled || !HasComp(args.Climber) || !HasComp(args.User)) + if (args.Cancelled) return; if (TryStartBonk(uid, args.User, args.Climber, component)) diff --git a/Content.Shared/Climbing/Systems/ClimbSystem.cs b/Content.Shared/Climbing/Systems/ClimbSystem.cs index 521f5ace99..c570a821a6 100644 --- a/Content.Shared/Climbing/Systems/ClimbSystem.cs +++ b/Content.Shared/Climbing/Systems/ClimbSystem.cs @@ -228,7 +228,8 @@ public bool TryClimb( { BreakOnTargetMove = true, BreakOnUserMove = true, - BreakOnDamage = true + BreakOnDamage = true, + DuplicateCondition = DuplicateConditions.SameTool | DuplicateConditions.SameTarget }; _audio.PlayPredicted(comp.StartClimbSound, climbable, user); @@ -473,6 +474,11 @@ public void ForciblySetClimbing(EntityUid uid, EntityUid climbable, ClimbingComp Climb(uid, uid, climbable, true, component); } + public void ForciblyStopClimbing(EntityUid uid, ClimbingComponent? climbing = null, FixturesComponent? fixtures = null) + { + StopClimb(uid, climbing, fixtures); + } + private void OnBuckleChange(EntityUid uid, ClimbingComponent component, ref BuckleChangeEvent args) { if (!args.Buckling) diff --git a/Content.Shared/Cloning/CloningPodComponent.cs b/Content.Shared/Cloning/CloningPodComponent.cs index 88d587c145..c9a6fd4500 100644 --- a/Content.Shared/Cloning/CloningPodComponent.cs +++ b/Content.Shared/Cloning/CloningPodComponent.cs @@ -1,5 +1,7 @@ +using Content.Shared.Construction.Prototypes; using Content.Shared.DeviceLinking; using Content.Shared.Materials; +using Content.Shared.Random; using Robust.Shared.Audio; using Robust.Shared.Containers; using Robust.Shared.Prototypes; @@ -17,11 +19,14 @@ public sealed partial class CloningPodComponent : Component public ContainerSlot BodyContainer = default!; /// - /// How long the cloning has been going on for. + /// How long the cloning has been going on for /// [ViewVariables] public float CloningProgress = 0; + [DataField] + public float BiomassCostMultiplier = 1; + [ViewVariables] public int UsedBiomass = 70; @@ -29,44 +34,142 @@ public sealed partial class CloningPodComponent : Component public bool FailedClone = false; /// - /// The material that is used to clone entities. + /// The material that is used to clone entities /// - [DataField("requiredMaterial"), ViewVariables(VVAccess.ReadWrite)] + [DataField] public ProtoId RequiredMaterial = "Biomass"; /// - /// The current amount of time it takes to clone a body + /// The multiplier for cloning duration + /// + [DataField] + public float PartRatingSpeedMultiplier = 0.75f; + + /// + /// The machine part that affects cloning speed + /// + [DataField] + public ProtoId MachinePartCloningSpeed = "Manipulator"; + + /// + /// The current amount of time it takes to clone a body /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public float CloningTime = 30f; /// - /// The mob to spawn on emag + /// The mob to spawn on emag /// - [DataField("mobSpawnId"), ViewVariables(VVAccess.ReadWrite)] + [DataField] public EntProtoId MobSpawnId = "MobAbomination"; /// - /// Emag sound effects. + /// Emag sound effects /// - [DataField("sparkSound")] + [DataField] public SoundSpecifier SparkSound = new SoundCollectionSpecifier("sparks") { Params = AudioParams.Default.WithVolume(8), }; // TODO: Remove this from here when cloning and/or zombies are refactored - [DataField("screamSound")] + [DataField] public SoundSpecifier ScreamSound = new SoundCollectionSpecifier("ZombieScreams") { Params = AudioParams.Default.WithVolume(4), }; + /// + /// The machine part that affects how much biomass is needed to clone a body. + /// + [DataField] + public float PartRatingMaterialMultiplier = 0.85f; + + /// + /// The machine part that decreases the amount of material needed for cloning + /// + [DataField] + public ProtoId MachinePartMaterialUse = "MatterBin"; + [ViewVariables(VVAccess.ReadWrite)] public CloningPodStatus Status; [ViewVariables] public EntityUid? ConnectedConsole; + + /// + /// Tracks whether a Cloner is actively cloning someone + /// + [ViewVariables(VVAccess.ReadWrite)] + public bool ActivelyCloning; + + /// + /// Controls whether a Cloning Pod will add genetic damage to a clone, scaling as the body's crit threshold + 1 + the genetic damage of the body to be cloned + /// + [DataField] + public bool DoGeneticDamage = true; + + /// + /// How much should the cloning pod adjust the hunger of an entity by + /// + [DataField] + public float HungerAdjustment = 50; + + /// + /// How much should the cloning pod adjust the thirst of an entity by + /// + [DataField] + public float ThirstAdjustment = 50; + + /// + /// How much time should the cloning pod give an entity the durnk condition, in seconds + /// + [DataField] + public float DrunkTimer = 300; + + #region Metempsychosis + + /// + /// Controls whether a cloning machine performs the Metempsychosis functions, EG: Is this a Cloner or a Metem Machine? + /// Metempsychosis refers to the metaphysical process of Reincarnation. + /// + /// + /// A Machine with this enabled will essentially create a random new character instead of creating a living version of the old character. + /// Although, the specifics of how much of the new body is a "new character" is highly adjustable in server configuration. + /// + [DataField] + public bool DoMetempsychosis; + + /// + /// How much should each point of Karma decrease the odds of reincarnating as a humanoid + /// + [DataField] + public float KarmaOffset = 0.5f; + + /// + /// The base chances for a Metem Machine to produce a Humanoid. + /// > 1 has a chance of acting like a true Cloner. + /// On a successful roll, produces a random Humanoid. + /// A failed roll poduces a random NonHumanoid. + /// + [DataField] + public float HumanoidBaseChance = 1; + + /// + /// The proto that the Metem Machine picks a random Humanoid from + /// + [ValidatePrototypeId] + [DataField] + public string MetempsychoticHumanoidPool = "MetempsychoticHumanoidPool"; + + /// + /// The proto that the Metem Machine picks a random Non-Humanoid from + /// + [ValidatePrototypeId] + [DataField] + public string MetempsychoticNonHumanoidPool = "MetempsychoticNonhumanoidPool"; + + #endregion } [Serializable, NetSerializable] @@ -84,20 +187,11 @@ public enum CloningPodStatus : byte NoMind } -/// -/// Raised after a new mob got spawned when cloning a humanoid -/// -[ByRefEvent] -public struct CloningEvent +[Serializable, NetSerializable] +public enum ForcedMetempsychosisType : byte { - public bool NameHandled = false; - - public readonly EntityUid Source; - public readonly EntityUid Target; - - public CloningEvent(EntityUid source, EntityUid target) - { - Source = source; - Target = target; - } + None, + Clone, + RandomHumanoid, + RandomNonHumanoid } diff --git a/Content.Shared/Cloning/CloningSystem.Events.cs b/Content.Shared/Cloning/CloningSystem.Events.cs new file mode 100644 index 0000000000..a29310d45b --- /dev/null +++ b/Content.Shared/Cloning/CloningSystem.Events.cs @@ -0,0 +1,58 @@ +namespace Content.Shared.Cloning; + +/// +/// Raised after a new mob got spawned when cloning a humanoid +/// +[ByRefEvent] +public struct CloningEvent +{ + public bool NameHandled = false; + + public readonly EntityUid Source; + public readonly EntityUid Target; + + public CloningEvent(EntityUid source, EntityUid target) + { + Source = source; + Target = target; + } +} + +/// +/// Raised on a corpse being subjected to forced reincarnation(Metempsychosis). +/// Allowing for innate effects from the mob to influence the reincarnation. +/// +[ByRefEvent] +public struct ReincarnatingEvent +{ + public bool OverrideChance; + public bool NeverTrulyClone; + public ForcedMetempsychosisType ForcedType = ForcedMetempsychosisType.None; + public readonly EntityUid OldBody; + public float ReincarnationChanceModifier = 1; + public float ReincarnationChances; + public ReincarnatingEvent(EntityUid oldBody, float reincarnationChances) + { + OldBody = oldBody; + ReincarnationChances = reincarnationChances; + } +} + +/// +/// Raised on a corpse that someone is attempting to clone, but before the process actually begins. +/// Allows for Entities to influence whether the cloning can begin in the first place, either by canceling it, or modifying the cost. +/// +[ByRefEvent] +public struct AttemptCloningEvent +{ + public bool Cancelled; + public bool DoMetempsychosis; + public EntityUid CloningPod; + public string? CloningFailMessage; + public float CloningCostMultiplier = 1; + public AttemptCloningEvent(EntityUid cloningPod, bool doMetempsychosis) + { + DoMetempsychosis = doMetempsychosis; + CloningPod = cloningPod; + } +} diff --git a/Content.Shared/Clothing/ClothingEvents.cs b/Content.Shared/Clothing/ClothingEvents.cs index 1dcce2402a..1e74b4b56c 100644 --- a/Content.Shared/Clothing/ClothingEvents.cs +++ b/Content.Shared/Clothing/ClothingEvents.cs @@ -1,20 +1,14 @@ using Content.Shared.Actions; +using Content.Shared.Clothing.Components; namespace Content.Shared.Clothing; -/// -/// Raised directed at a piece of clothing to get the set of layers to show on the wearer's sprite -/// -public sealed class GetEquipmentVisualsEvent : EntityEventArgs +/// Raised directed at a piece of clothing to get the set of layers to show on the wearer's sprite +public sealed class GetEquipmentVisualsEvent(EntityUid equipee, string slot) : EntityEventArgs { - /// - /// Entity that is wearing the item. - /// - public readonly EntityUid Equipee; - - public readonly string Slot; - + public readonly EntityUid Equipee = equipee; + public readonly string Slot = slot; /// /// The layers that will be added to the entity that is wearing this item. /// @@ -22,12 +16,6 @@ public sealed class GetEquipmentVisualsEvent : EntityEventArgs /// Note that the actual ordering of the layers depends on the order in which they are added to this list; /// public List<(string, PrototypeLayerData)> Layers = new(); - - public GetEquipmentVisualsEvent(EntityUid equipee, string slot) - { - Equipee = equipee; - Slot = slot; - } } /// @@ -36,26 +24,12 @@ public GetEquipmentVisualsEvent(EntityUid equipee, string slot) /// /// Useful for systems/components that modify the visual layers that an item adds to a player. (e.g. RGB memes) /// -public sealed class EquipmentVisualsUpdatedEvent : EntityEventArgs +public sealed class EquipmentVisualsUpdatedEvent(EntityUid equipee, string slot, HashSet revealedLayers) : EntityEventArgs { - /// - /// Entity that is wearing the item. - /// - public readonly EntityUid Equipee; - - public readonly string Slot; - - /// - /// The layers that this item is now revealing. - /// - public HashSet RevealedLayers; - - public EquipmentVisualsUpdatedEvent(EntityUid equipee, string slot, HashSet revealedLayers) - { - Equipee = equipee; - Slot = slot; - RevealedLayers = revealedLayers; - } + public readonly EntityUid Equipee = equipee; + public readonly string Slot = slot; + /// The layers that this item is now revealing. + public HashSet RevealedLayers = revealedLayers; } public sealed partial class ToggleMaskEvent : InstantActionEvent { } @@ -71,3 +45,31 @@ public sealed partial class ToggleMaskEvent : InstantActionEvent { } /// [ByRefEvent] public readonly record struct WearerMaskToggledEvent(bool IsToggled); + +/// +/// Raised on the clothing entity when it is equipped to a valid slot, +/// as determined by . +/// +[ByRefEvent] +public readonly record struct ClothingGotEquippedEvent(EntityUid Wearer, ClothingComponent Clothing); + +/// +/// Raised on the clothing entity when it is unequipped from a valid slot, +/// as determined by . +/// +[ByRefEvent] +public readonly record struct ClothingGotUnequippedEvent(EntityUid Wearer, ClothingComponent Clothing); + +/// +/// Raised on an entity when they equip a clothing item to a valid slot, +/// as determined by . +/// +[ByRefEvent] +public readonly record struct ClothingDidEquippedEvent(Entity Clothing); + +/// +/// Raised on an entity when they unequip a clothing item from a valid slot, +/// as determined by . +/// +[ByRefEvent] +public readonly record struct ClothingDidUnequippedEvent(Entity Clothing); diff --git a/Content.Shared/Clothing/Components/ClothingComponent.cs b/Content.Shared/Clothing/Components/ClothingComponent.cs index 0f4c7f68bf..6d7226e767 100644 --- a/Content.Shared/Clothing/Components/ClothingComponent.cs +++ b/Content.Shared/Clothing/Components/ClothingComponent.cs @@ -66,6 +66,9 @@ public sealed partial class ClothingComponent : Component [DataField("unisexMask")] public ClothingMask UnisexMask = ClothingMask.UniformFull; + /// + /// Name of the inventory slot the clothing is in. + /// public string? InSlot; [DataField, ViewVariables(VVAccess.ReadWrite)] diff --git a/Content.Shared/Clothing/Components/FireProtectionComponent.cs b/Content.Shared/Clothing/Components/FireProtectionComponent.cs new file mode 100644 index 0000000000..cafa6e5359 --- /dev/null +++ b/Content.Shared/Clothing/Components/FireProtectionComponent.cs @@ -0,0 +1,17 @@ +using Content.Shared.Clothing.EntitySystems; + +namespace Content.Shared.Clothing.Components; + +/// +/// Makes this clothing reduce fire damage when worn. +/// +[RegisterComponent, Access(typeof(FireProtectionSystem))] +public sealed partial class FireProtectionComponent : Component +{ + /// + /// Percentage to reduce fire damage by, subtracted not multiplicative. + /// 0.25 means 25% less fire damage. + /// + [DataField(required: true)] + public float Reduction; +} diff --git a/Content.Shared/Clothing/Components/HideLayerClothingComponent.cs b/Content.Shared/Clothing/Components/HideLayerClothingComponent.cs new file mode 100644 index 0000000000..ac3d9b9789 --- /dev/null +++ b/Content.Shared/Clothing/Components/HideLayerClothingComponent.cs @@ -0,0 +1,24 @@ +using Content.Shared.Humanoid; +using Robust.Shared.GameStates; + +namespace Content.Shared.Clothing.Components; + +/// +/// This is used for a clothing item that hides an appearance layer. +/// The entity's HumanoidAppearance component must have the corresponding hideLayerOnEquip value. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class HideLayerClothingComponent : Component +{ + /// + /// The appearance layer to hide. + /// + [DataField] + public HashSet Slots = new(); + + /// + /// If true, the layer will only hide when the item is in a toggled state (e.g. masks) + /// + [DataField] + public bool HideOnToggle = false; +} diff --git a/Content.Shared/Clothing/Components/LoadoutComponent.cs b/Content.Shared/Clothing/Components/LoadoutComponent.cs index a3c6efe2a0..612286e6e6 100644 --- a/Content.Shared/Clothing/Components/LoadoutComponent.cs +++ b/Content.Shared/Clothing/Components/LoadoutComponent.cs @@ -1,16 +1,15 @@ using Content.Shared.Roles; using Robust.Shared.GameStates; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; +using Robust.Shared.Prototypes; namespace Content.Shared.Clothing.Components; [RegisterComponent, NetworkedComponent, AutoGenerateComponentState] public sealed partial class LoadoutComponent : Component { - /// /// A list of starting gears, of which one will be given. /// All elements are weighted the same in the list. - /// - [DataField("prototypes", required: true, customTypeSerializer: typeof(PrototypeIdListSerializer)), AutoNetworkedField] - public List? Prototypes; + [DataField("prototypes")] + [AutoNetworkedField] + public List>? StartingGear; } diff --git a/Content.Shared/Clothing/EntitySystems/ClothingSystem.cs b/Content.Shared/Clothing/EntitySystems/ClothingSystem.cs index b407b867f7..9e3f917e96 100644 --- a/Content.Shared/Clothing/EntitySystems/ClothingSystem.cs +++ b/Content.Shared/Clothing/EntitySystems/ClothingSystem.cs @@ -6,31 +6,18 @@ using Content.Shared.Inventory; using Content.Shared.Inventory.Events; using Content.Shared.Item; -using Content.Shared.Tag; -using Robust.Shared.GameStates; -using System.Linq; using Robust.Shared.Containers; +using Robust.Shared.GameStates; namespace Content.Shared.Clothing.EntitySystems; public abstract class ClothingSystem : EntitySystem { [Dependency] private readonly SharedItemSystem _itemSys = default!; + [Dependency] private readonly SharedContainerSystem _containerSys = default!; [Dependency] private readonly SharedHumanoidAppearanceSystem _humanoidSystem = default!; - [Dependency] private readonly TagSystem _tagSystem = default!; [Dependency] private readonly InventorySystem _invSystem = default!; [Dependency] private readonly SharedHandsSystem _handsSystem = default!; - [Dependency] private readonly SharedContainerSystem _containerSys = default!; - - [ValidatePrototypeId] - private const string HairTag = "HidesHair"; - - [ValidatePrototypeId] - private const string NoseTag = "HidesNose"; - - [ValidatePrototypeId] - - private const string BeardTag = "HidesBeard"; public override void Initialize() { @@ -101,61 +88,71 @@ private void QuickEquip( } } - private void ToggleVisualLayer(EntityUid equipee, HumanoidVisualLayers layer, string tag) + private void ToggleVisualLayers(EntityUid equipee, HashSet layers, HashSet appearanceLayers) { - InventorySystem.InventorySlotEnumerator enumerator = _invSystem.GetSlotEnumerator(equipee, SlotFlags.HEAD ^ SlotFlags.MASK); - bool shouldLayerShow = true; - - while (enumerator.NextItem(out EntityUid item)) + foreach (HumanoidVisualLayers layer in layers) { - if (_tagSystem.HasTag(item, tag)) + if (!appearanceLayers.Contains(layer)) + break; + + InventorySystem.InventorySlotEnumerator enumerator = _invSystem.GetSlotEnumerator(equipee); + + bool shouldLayerShow = true; + while (enumerator.NextItem(out EntityUid item)) { - if (tag == NoseTag || tag == BeardTag) // Special check for NoseTag or BeardTag, due to masks being toggleable + if (TryComp(item, out HideLayerClothingComponent? comp)) { - if (TryComp(item, out MaskComponent? mask) && TryComp(item, out ClothingComponent? clothing)) + if (comp.Slots.Contains(layer)) { - if (clothing.EquippedPrefix != mask.EquippedPrefix) + //Checks for mask toggling. TODO: Make a generic system for this + if (comp.HideOnToggle && TryComp(item, out MaskComponent? mask) && TryComp(item, out ClothingComponent? clothing)) + { + if (clothing.EquippedPrefix != mask.EquippedPrefix) + { + shouldLayerShow = false; + break; + } + } + else { shouldLayerShow = false; break; } } - else - { - shouldLayerShow = false; - break; - } - } - else - { - shouldLayerShow = false; - break; } } + _humanoidSystem.SetLayerVisibility(equipee, layer, shouldLayerShow); } - _humanoidSystem.SetLayerVisibility(equipee, layer, shouldLayerShow); } protected virtual void OnGotEquipped(EntityUid uid, ClothingComponent component, GotEquippedEvent args) { component.InSlot = args.Slot; - if ((new string[] { "head" }).Contains(args.Slot) && _tagSystem.HasTag(args.Equipment, HairTag)) - ToggleVisualLayer(args.Equipee, HumanoidVisualLayers.Hair, HairTag); - if ((new string[] { "mask", "head" }).Contains(args.Slot) && _tagSystem.HasTag(args.Equipment, NoseTag)) - ToggleVisualLayer(args.Equipee, HumanoidVisualLayers.Snout, NoseTag); - if ((new string[] { "mask", "head" }).Contains(args.Slot) && _tagSystem.HasTag(args.Equipment, BeardTag)) - ToggleVisualLayer(args.Equipee, HumanoidVisualLayers.FacialHair, BeardTag); + CheckEquipmentForLayerHide(args.Equipment, args.Equipee); + + if ((component.Slots & args.SlotFlags) != SlotFlags.NONE) + { + var gotEquippedEvent = new ClothingGotEquippedEvent(args.Equipee, component); + RaiseLocalEvent(uid, ref gotEquippedEvent); + + var didEquippedEvent = new ClothingDidEquippedEvent((uid, component)); + RaiseLocalEvent(args.Equipee, ref didEquippedEvent); + } } protected virtual void OnGotUnequipped(EntityUid uid, ClothingComponent component, GotUnequippedEvent args) { + if ((component.Slots & args.SlotFlags) != SlotFlags.NONE) + { + var gotUnequippedEvent = new ClothingGotUnequippedEvent(args.Equipee, component); + RaiseLocalEvent(uid, ref gotUnequippedEvent); + + var didUnequippedEvent = new ClothingDidUnequippedEvent((uid, component)); + RaiseLocalEvent(args.Equipee, ref didUnequippedEvent); + } + component.InSlot = null; - if ((new string[] { "head" }).Contains(args.Slot) && _tagSystem.HasTag(args.Equipment, HairTag)) - ToggleVisualLayer(args.Equipee, HumanoidVisualLayers.Hair, HairTag); - if ((new string[] { "mask", "head" }).Contains(args.Slot) && _tagSystem.HasTag(args.Equipment, NoseTag)) - ToggleVisualLayer(args.Equipee, HumanoidVisualLayers.Snout, NoseTag); - if ((new string[] { "mask", "head" }).Contains(args.Slot) && _tagSystem.HasTag(args.Equipment, BeardTag)) - ToggleVisualLayer(args.Equipee, HumanoidVisualLayers.FacialHair, BeardTag); + CheckEquipmentForLayerHide(args.Equipment, args.Equipee); } private void OnGetState(EntityUid uid, ClothingComponent component, ref ComponentGetState args) @@ -166,15 +163,20 @@ private void OnGetState(EntityUid uid, ClothingComponent component, ref Componen private void OnHandleState(EntityUid uid, ClothingComponent component, ref ComponentHandleState args) { if (args.Current is ClothingComponentState state) + { SetEquippedPrefix(uid, state.EquippedPrefix, component); + if (component.InSlot != null && _containerSys.TryGetContainingContainer(uid, out var container)) + { + CheckEquipmentForLayerHide(uid, container.Owner); + } + } } private void OnMaskToggled(Entity ent, ref ItemMaskToggledEvent args) { //TODO: sprites for 'pulled down' state. defaults to invisible due to no sprite with this prefix SetEquippedPrefix(ent, args.IsToggled ? args.equippedPrefix : null, ent); - ToggleVisualLayer(args.Wearer, HumanoidVisualLayers.Snout, NoseTag); - ToggleVisualLayer(args.Wearer, HumanoidVisualLayers.FacialHair, BeardTag); + CheckEquipmentForLayerHide(ent.Owner, args.Wearer); } private void OnPickedUp(Entity ent, ref GettingPickedUpAttemptEvent args) @@ -205,6 +207,12 @@ private void OnUnequipDoAfter(Entity ent, ref ClothingUnequip _handsSystem.TryPickup(args.User, ent); } + private void CheckEquipmentForLayerHide(EntityUid equipment, EntityUid equipee) + { + if (TryComp(equipment, out HideLayerClothingComponent? clothesComp) && TryComp(equipee, out HumanoidAppearanceComponent? appearanceComp)) + ToggleVisualLayers(equipee, clothesComp.Slots, appearanceComp.HideLayersOnEquip); + } + #region Public API public void SetEquippedPrefix(EntityUid uid, string? prefix, ClothingComponent? clothing = null) diff --git a/Content.Shared/Clothing/EntitySystems/FireProtectionSystem.cs b/Content.Shared/Clothing/EntitySystems/FireProtectionSystem.cs new file mode 100644 index 0000000000..6f80bc0588 --- /dev/null +++ b/Content.Shared/Clothing/EntitySystems/FireProtectionSystem.cs @@ -0,0 +1,23 @@ +using Content.Shared.Atmos; +using Content.Shared.Clothing.Components; +using Content.Shared.Inventory; + +namespace Content.Shared.Clothing.EntitySystems; + +/// +/// Handles reducing fire damage when wearing clothing with . +/// +public sealed class FireProtectionSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent>(OnGetProtection); + } + + private void OnGetProtection(Entity ent, ref InventoryRelayedEvent args) + { + args.Args.Reduce(ent.Comp.Reduction); + } +} diff --git a/Content.Shared/Clothing/EntitySystems/MaskSystem.cs b/Content.Shared/Clothing/EntitySystems/MaskSystem.cs index aab2a172dc..2a4383279a 100644 --- a/Content.Shared/Clothing/EntitySystems/MaskSystem.cs +++ b/Content.Shared/Clothing/EntitySystems/MaskSystem.cs @@ -42,12 +42,10 @@ private void OnToggleMask(Entity ent, ref ToggleMaskEvent args) return; mask.IsToggled ^= true; - _actionSystem.SetToggled(mask.ToggleActionEntity, mask.IsToggled); - if (mask.IsToggled) - _popupSystem.PopupEntity(Loc.GetString("action-mask-pull-down-popup-message", ("mask", uid)), args.Performer, args.Performer); - else - _popupSystem.PopupEntity(Loc.GetString("action-mask-pull-up-popup-message", ("mask", uid)), args.Performer, args.Performer); + var dir = mask.IsToggled ? "down" : "up"; + var msg = $"action-mask-pull-{dir}-popup-message"; + _popupSystem.PopupClient(Loc.GetString(msg, ("mask", uid)), args.Performer, args.Performer); ToggleMaskComponents(uid, mask, args.Performer, mask.EquippedPrefix); } @@ -55,18 +53,22 @@ private void OnToggleMask(Entity ent, ref ToggleMaskEvent args) // set to untoggled when unequipped, so it isn't left in a 'pulled down' state private void OnGotUnequipped(EntityUid uid, MaskComponent mask, GotUnequippedEvent args) { - if (mask.ToggleActionEntity == null) + if (!mask.IsToggled) return; mask.IsToggled = false; - Dirty(uid, mask); - _actionSystem.SetToggled(mask.ToggleActionEntity, mask.IsToggled); - ToggleMaskComponents(uid, mask, args.Equipee, mask.EquippedPrefix, true); } + /// + /// Called after setting IsToggled, raises events and dirties. + /// private void ToggleMaskComponents(EntityUid uid, MaskComponent mask, EntityUid wearer, string? equippedPrefix = null, bool isEquip = false) { + Dirty(uid, mask); + if (mask.ToggleActionEntity is {} action) + _actionSystem.SetToggled(action, mask.IsToggled); + var maskEv = new ItemMaskToggledEvent(wearer, equippedPrefix, mask.IsToggled, isEquip); RaiseLocalEvent(uid, ref maskEv); diff --git a/Content.Shared/Clothing/EntitySystems/SkatesSystem.cs b/Content.Shared/Clothing/EntitySystems/SkatesSystem.cs index 7d748a67a4..bbb640bd98 100644 --- a/Content.Shared/Clothing/EntitySystems/SkatesSystem.cs +++ b/Content.Shared/Clothing/EntitySystems/SkatesSystem.cs @@ -17,34 +17,28 @@ public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnGotEquipped); - SubscribeLocalEvent(OnGotUnequipped); + SubscribeLocalEvent(OnGotEquipped); + SubscribeLocalEvent(OnGotUnequipped); } /// /// When item is unequipped from the shoe slot, friction, aceleration and collide on impact return to default settings. /// - public void OnGotUnequipped(EntityUid uid, SkatesComponent component, GotUnequippedEvent args) + public void OnGotUnequipped(EntityUid uid, SkatesComponent component, ClothingGotUnequippedEvent args) { - if (!TryComp(args.Equipee, out MovementSpeedModifierComponent? speedModifier)) + if (!TryComp(args.Wearer, out MovementSpeedModifierComponent? speedModifier)) return; - if (args.Slot == "shoes") - { - _move.ChangeFriction(args.Equipee, MovementSpeedModifierComponent.DefaultFriction, MovementSpeedModifierComponent.DefaultFrictionNoInput, MovementSpeedModifierComponent.DefaultAcceleration, speedModifier); - _impact.ChangeCollide(args.Equipee, component.DefaultMinimumSpeed, component.DefaultStunSeconds, component.DefaultDamageCooldown, component.DefaultSpeedDamage); - } + _move.ChangeFriction(args.Wearer, MovementSpeedModifierComponent.DefaultFriction, MovementSpeedModifierComponent.DefaultFrictionNoInput, MovementSpeedModifierComponent.DefaultAcceleration, speedModifier); + _impact.ChangeCollide(args.Wearer, component.DefaultMinimumSpeed, component.DefaultStunSeconds, component.DefaultDamageCooldown, component.DefaultSpeedDamage); } /// /// When item is equipped into the shoe slot, friction, acceleration and collide on impact are adjusted. /// - private void OnGotEquipped(EntityUid uid, SkatesComponent component, GotEquippedEvent args) + private void OnGotEquipped(EntityUid uid, SkatesComponent component, ClothingGotEquippedEvent args) { - if (args.Slot == "shoes") - { - _move.ChangeFriction(args.Equipee, component.Friction, component.FrictionNoInput, component.Acceleration); - _impact.ChangeCollide(args.Equipee, component.MinimumSpeed, component.StunSeconds, component.DamageCooldown, component.SpeedDamage); - } + _move.ChangeFriction(args.Wearer, component.Friction, component.FrictionNoInput, component.Acceleration); + _impact.ChangeCollide(args.Wearer, component.MinimumSpeed, component.StunSeconds, component.DamageCooldown, component.SpeedDamage); } } diff --git a/Content.Shared/Clothing/Loadouts/Prototypes/LoadoutCategoryPrototype.cs b/Content.Shared/Clothing/Loadouts/Prototypes/LoadoutCategoryPrototype.cs index 445cbc10e6..2d008e437d 100644 --- a/Content.Shared/Clothing/Loadouts/Prototypes/LoadoutCategoryPrototype.cs +++ b/Content.Shared/Clothing/Loadouts/Prototypes/LoadoutCategoryPrototype.cs @@ -6,9 +6,15 @@ namespace Content.Shared.Clothing.Loadouts.Prototypes; /// /// A prototype defining a valid category for s to go into. /// -[Prototype("loadoutCategory")] -public sealed class LoadoutCategoryPrototype : IPrototype +[Prototype] +public sealed partial class LoadoutCategoryPrototype : IPrototype { [IdDataField] public string ID { get; } = default!; + + [DataField] + public bool Root; + + [DataField] + public List> SubCategories = new(); } diff --git a/Content.Shared/Clothing/Loadouts/Prototypes/LoadoutPrototype.cs b/Content.Shared/Clothing/Loadouts/Prototypes/LoadoutPrototype.cs index 9ca575fa72..19c2bf59f1 100644 --- a/Content.Shared/Clothing/Loadouts/Prototypes/LoadoutPrototype.cs +++ b/Content.Shared/Clothing/Loadouts/Prototypes/LoadoutPrototype.cs @@ -7,8 +7,8 @@ namespace Content.Shared.Clothing.Loadouts.Prototypes; -[Prototype("loadout")] -public sealed class LoadoutPrototype : IPrototype +[Prototype] +public sealed partial class LoadoutPrototype : IPrototype { /// /// Formatted like "Loadout[Department/ShortHeadName][CommonClothingSlot][SimplifiedClothingId]", example: "LoadoutScienceOuterLabcoatSeniorResearcher" @@ -16,22 +16,12 @@ public sealed class LoadoutPrototype : IPrototype [IdDataField] public string ID { get; } = default!; - /// - /// Which tab category to put this under - /// [DataField, ValidatePrototypeId] - public string Category = "Uncategorized"; - - /// - /// The item to give - /// - [DataField(customTypeSerializer: typeof(PrototypeIdListSerializer), required: true)] - public List Items = new(); + public ProtoId Category = "Uncategorized"; + [DataField(required: true)] + public List> Items = new(); - /// - /// The point cost of this loadout - /// [DataField] public int Cost = 1; @@ -41,7 +31,6 @@ public sealed class LoadoutPrototype : IPrototype [DataField] public bool Exclusive; - [DataField] public List Requirements = new(); } diff --git a/Content.Shared/Clothing/Loadouts/Systems/LoadoutSystem.cs b/Content.Shared/Clothing/Loadouts/Systems/LoadoutSystem.cs index e7a0eef80e..7ee0676f9e 100644 --- a/Content.Shared/Clothing/Loadouts/Systems/LoadoutSystem.cs +++ b/Content.Shared/Clothing/Loadouts/Systems/LoadoutSystem.cs @@ -1,3 +1,4 @@ +using System.Linq; using Content.Shared.Clothing.Components; using Content.Shared.Clothing.Loadouts.Prototypes; using Content.Shared.Customization.Systems; @@ -30,11 +31,12 @@ public override void Initialize() private void OnMapInit(EntityUid uid, LoadoutComponent component, MapInitEvent args) { - if (component.Prototypes == null) + if (component.StartingGear is null + || component.StartingGear.Count <= 0) return; - var proto = _prototype.Index(_random.Pick(component.Prototypes)); - _station.EquipStartingGear(uid, proto, null); + var proto = _prototype.Index(_random.Pick(component.StartingGear)); + _station.EquipStartingGear(uid, proto); } @@ -69,7 +71,7 @@ public List ApplyCharacterLoadout(EntityUid uid, JobPrototype job, Hu if (!_characterRequirements.CheckRequirementsValid( - loadoutProto.Requirements, job, profile, playTimes, whitelisted, + loadoutProto.Requirements, job, profile, playTimes, whitelisted, loadoutProto, EntityManager, _prototype, _configuration, out _)) continue; @@ -78,12 +80,13 @@ public List ApplyCharacterLoadout(EntityUid uid, JobPrototype job, Hu // Spawn the loadout items var spawned = EntityManager.SpawnEntities( EntityManager.GetComponent(uid).Coordinates.ToMap(EntityManager), - loadoutProto.Items!); + loadoutProto.Items.Select(p => (string?) p.ToString()).ToList()); // Dumb cast foreach (var item in spawned) { - if (EntityManager.TryGetComponent(item, out var clothingComp) && - _inventory.TryGetSlots(uid, out var slotDefinitions)) + if (EntityManager.TryGetComponent(item, out var clothingComp) + && _characterRequirements.CanEntityWearItem(uid, item) + && _inventory.TryGetSlots(uid, out var slotDefinitions)) { var deleted = false; foreach (var curSlot in slotDefinitions) diff --git a/Content.Shared/Cocoon/CocoonComponent.cs b/Content.Shared/Cocoon/CocoonComponent.cs new file mode 100644 index 0000000000..66ba6e6dd3 --- /dev/null +++ b/Content.Shared/Cocoon/CocoonComponent.cs @@ -0,0 +1,14 @@ +namespace Content.Shared.Cocoon +{ + [RegisterComponent] + public sealed partial class CocoonComponent : Component + { + public string? OldAccent; + + public EntityUid? Victim; + + [DataField("damagePassthrough")] + public float DamagePassthrough = 0.5f; + + } +} diff --git a/Content.Shared/Cocoon/CocoonDoAfterEvent.cs b/Content.Shared/Cocoon/CocoonDoAfterEvent.cs new file mode 100644 index 0000000000..0b9049e989 --- /dev/null +++ b/Content.Shared/Cocoon/CocoonDoAfterEvent.cs @@ -0,0 +1,15 @@ +using Robust.Shared.Serialization; +using Content.Shared.DoAfter; + +namespace Content.Shared.Cocoon +{ + [Serializable, NetSerializable] + public sealed partial class CocoonDoAfterEvent : SimpleDoAfterEvent + { + } + + [Serializable, NetSerializable] + public sealed partial class UnCocoonDoAfterEvent : SimpleDoAfterEvent + { + } +} diff --git a/Content.Shared/Cocoon/CocoonerComponent.cs b/Content.Shared/Cocoon/CocoonerComponent.cs new file mode 100644 index 0000000000..17cce97309 --- /dev/null +++ b/Content.Shared/Cocoon/CocoonerComponent.cs @@ -0,0 +1,14 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Cocoon +{ + [RegisterComponent, NetworkedComponent] + public sealed partial class CocoonerComponent : Component + { + [DataField("cocoonDelay")] + public float CocoonDelay = 12f; + + [DataField("cocoonKnockdownMultiplier")] + public float CocoonKnockdownMultiplier = 0.5f; + } +} diff --git a/Content.Shared/CombatMode/DisarmedEvent.cs b/Content.Shared/CombatMode/DisarmedEvent.cs index 653529fe02..f49b25f8b4 100644 --- a/Content.Shared/CombatMode/DisarmedEvent.cs +++ b/Content.Shared/CombatMode/DisarmedEvent.cs @@ -16,5 +16,10 @@ public sealed class DisarmedEvent : HandledEntityEventArgs /// Probability for push/knockdown. /// public float PushProbability { get; init; } + + /// + /// Potential stamina damage if this disarm results in a shove. + /// + public float StaminaDamage { get; init; } } } diff --git a/Content.Shared/Construction/MachinePartSystem.cs b/Content.Shared/Construction/MachinePartSystem.cs index 1a19040b41..01db7fbade 100644 --- a/Content.Shared/Construction/MachinePartSystem.cs +++ b/Content.Shared/Construction/MachinePartSystem.cs @@ -21,6 +21,7 @@ public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnMachineBoardExamined); + SubscribeLocalEvent(OnMachinePartExamined); } private void OnMachineBoardExamined(EntityUid uid, MachineBoardComponent component, ExaminedEvent args) @@ -61,6 +62,20 @@ private void OnMachineBoardExamined(EntityUid uid, MachineBoardComponent compone } } + private void OnMachinePartExamined(EntityUid uid, MachinePartComponent component, ExaminedEvent args) + { + if (!args.IsInDetailsRange) + return; + + using (args.PushGroup(nameof(MachinePartComponent))) + { + args.PushMarkup(Loc.GetString("machine-part-component-on-examine-rating-text", + ("rating", component.Rating))); + args.PushMarkup(Loc.GetString("machine-part-component-on-examine-type-text", ("type", + Loc.GetString(_prototype.Index(component.PartType).Name)))); + } + } + public Dictionary GetMachineBoardMaterialCost(Entity entity, int coefficient = 1) { var (_, comp) = entity; diff --git a/Content.Shared/Containers/ContainerFillComponent.cs b/Content.Shared/Containers/ContainerFillComponent.cs index 8c63cbc66a..7ce5fa8850 100644 --- a/Content.Shared/Containers/ContainerFillComponent.cs +++ b/Content.Shared/Containers/ContainerFillComponent.cs @@ -1,4 +1,5 @@ using Content.Shared.Storage; +using Content.Shared.Storage.Components; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.Manager; using Robust.Shared.Serialization.Markdown.Mapping; diff --git a/Content.Shared/Containers/ItemSlot/ItemSlotsComponent.cs b/Content.Shared/Containers/ItemSlot/ItemSlotsComponent.cs index 42e7f721b3..ba8a9a934e 100644 --- a/Content.Shared/Containers/ItemSlot/ItemSlotsComponent.cs +++ b/Content.Shared/Containers/ItemSlot/ItemSlotsComponent.cs @@ -214,6 +214,7 @@ public ItemSlot(ItemSlot other) /// /// If the user interacts with an entity with an already-filled item slot, should they attempt to swap out the item? + /// If set to null, will be deduced based on the relevant config variable. /// /// /// Useful for things like chem dispensers, but undesirable for things like the ID card console, where you @@ -221,7 +222,7 @@ public ItemSlot(ItemSlot other) /// [DataField] [Access(typeof(ItemSlotsSystem), Other = AccessPermissions.ReadWriteExecute)] - public bool Swap = true; + public bool? Swap = null; public string? ID => ContainerSlot?.ID; diff --git a/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs b/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs index 9cb21e882e..cb6b2a747b 100644 --- a/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs +++ b/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs @@ -1,6 +1,7 @@ using System.Diagnostics.CodeAnalysis; using Content.Shared.ActionBlocker; using Content.Shared.Administration.Logs; +using Content.Shared.CCVar; using Content.Shared.Database; using Content.Shared.Destructible; using Content.Shared.Hands.Components; @@ -10,6 +11,7 @@ using Content.Shared.Popups; using Content.Shared.Verbs; using Robust.Shared.Audio.Systems; +using Robust.Shared.Configuration; using Robust.Shared.Containers; using Robust.Shared.GameStates; using Robust.Shared.Utility; @@ -27,11 +29,14 @@ public sealed class ItemSlotsSystem : EntitySystem { [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; + [Dependency] private readonly IConfigurationManager _config = default!; [Dependency] private readonly SharedContainerSystem _containers = default!; [Dependency] private readonly SharedPopupSystem _popupSystem = default!; [Dependency] private readonly SharedHandsSystem _handsSystem = default!; [Dependency] private readonly SharedAudioSystem _audioSystem = default!; + private bool _defaultQuickSwap; + public override void Initialize() { base.Initialize(); @@ -53,6 +58,8 @@ public override void Initialize() SubscribeLocalEvent(HandleItemSlotsState); SubscribeLocalEvent(HandleButtonPressed); + + _config.OnValueChanged(CCVars.AllowSlotQuickSwap, b => _defaultQuickSwap = b, true); } #region ComponentManagement @@ -202,7 +209,7 @@ private void OnInteractUsing(EntityUid uid, ItemSlotsComponent itemSlots, Intera if (!slot.InsertOnInteract) continue; - if (!CanInsert(uid, args.Used, args.User, slot, swap: slot.Swap, popup: args.User)) + if (!CanInsert(uid, args.Used, args.User, slot, swap: slot.Swap ?? _defaultQuickSwap, popup: args.User)) continue; // Drop the held item onto the floor. Return if the user cannot drop. @@ -626,9 +633,9 @@ private void HandleButtonPressed(EntityUid uid, ItemSlotsComponent component, It return; if (args.TryEject && slot.HasItem) - TryEjectToHands(uid, slot, args.Session.AttachedEntity, false); - else if (args.TryInsert && !slot.HasItem && args.Session.AttachedEntity is EntityUid user) - TryInsertFromHand(uid, slot, user); + TryEjectToHands(uid, slot, args.Actor, true); + else if (args.TryInsert && !slot.HasItem) + TryInsertFromHand(uid, slot, args.Actor); } #endregion diff --git a/Content.Shared/Content.Shared.csproj b/Content.Shared/Content.Shared.csproj index 9752bfcfe2..4cca399b28 100644 --- a/Content.Shared/Content.Shared.csproj +++ b/Content.Shared/Content.Shared.csproj @@ -23,9 +23,6 @@ false - - - diff --git a/Content.Shared/Contests/ContestsSystem.Utilities.cs b/Content.Shared/Contests/ContestsSystem.Utilities.cs new file mode 100644 index 0000000000..2bcb6dcc32 --- /dev/null +++ b/Content.Shared/Contests/ContestsSystem.Utilities.cs @@ -0,0 +1,283 @@ +using Content.Shared.CCVar; +using Robust.Shared.Serialization; + +namespace Content.Shared.Contests; +public sealed partial class ContestsSystem +{ + /// + /// Clamp a contest to a Range of [Epsilon, 32bit integer limit]. This exists to make sure contests are always "Safe" to divide by. + /// + private float ContestClamp(float input) + { + return Math.Clamp(input, float.Epsilon, float.MaxValue); + } + + /// + /// Shorthand for checking if clamp overrides are allowed, and the bypass is used by a contest. + /// + private bool ContestClampOverride(bool bypassClamp) + { + return _cfg.GetCVar(CCVars.AllowClampOverride) && bypassClamp; + } + + /// + /// Constructor for feeding options from a given set of ContestArgs into the ContestsSystem. + /// Just multiply by this and give it a user EntityUid and a ContestArgs variable. That's all you need to know. + /// + public float ContestConstructor(EntityUid user, ContestArgs args) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem)) + return 1; + + if (!args.DoEveryInteraction) + return args.DoMassInteraction ? ((args.MassDisadvantage + ? MassContest(user, args.MassBypassClamp, args.MassRangeModifier) + : 1 / MassContest(user, args.MassBypassClamp, args.MassRangeModifier)) + + args.MassOffset) + : 1 + * (args.DoStaminaInteraction ? ((args.StaminaDisadvantage + ? StaminaContest(user, args.StaminaBypassClamp, args.StaminaRangeModifier) + : 1 / StaminaContest(user, args.StaminaBypassClamp, args.StaminaRangeModifier)) + + args.StaminaOffset) + : 1) + * (args.DoHealthInteraction ? ((args.HealthDisadvantage + ? HealthContest(user, args.HealthBypassClamp, args.HealthRangeModifier) + : 1 / HealthContest(user, args.HealthBypassClamp, args.HealthRangeModifier)) + + args.HealthOffset) + : 1) + * (args.DoMindInteraction ? ((args.MindDisadvantage + ? MindContest(user, args.MindBypassClamp, args.MindRangeModifier) + : 1 / MindContest(user, args.MindBypassClamp, args.MindRangeModifier)) + + args.MindOffset) + : 1) + * (args.DoMoodInteraction ? ((args.MoodDisadvantage + ? MoodContest(user, args.MoodBypassClamp, args.MoodRangeModifier) + : 1 / MoodContest(user, args.MoodBypassClamp, args.MoodRangeModifier)) + + args.MoodOffset) + : 1); + + var everyContest = EveryContest(user, + args.MassBypassClamp, + args.StaminaBypassClamp, + args.HealthBypassClamp, + args.MindBypassClamp, + args.MoodBypassClamp, + args.MassRangeModifier, + args.StaminaRangeModifier, + args.HealthRangeModifier, + args.MindRangeModifier, + args.MoodRangeModifier, + args.EveryMassWeight, + args.EveryStaminaWeight, + args.EveryHealthWeight, + args.EveryMindWeight, + args.EveryMoodWeight, + args.EveryInteractionSumOrMultiply); + + return !args.EveryDisadvantage ? everyContest : 1 / everyContest; + } +} + +[Serializable, NetSerializable, DataDefinition] +public sealed partial class ContestArgs +{ + /// + /// Controls whether this melee weapon allows for mass to factor into damage. + /// + [DataField] + public bool DoMassInteraction; + + /// + /// When true, mass provides a disadvantage. + /// + [DataField] + public bool MassDisadvantage; + + /// + /// When true, mass contests ignore clamp limitations for a melee weapon. + /// + [DataField] + public bool MassBypassClamp; + + /// + /// Multiplies the acceptable range of outputs provided by mass contests for melee. + /// + [DataField] + public float MassRangeModifier = 1; + + /// + /// The output of a mass contest is increased by this amount. + /// + [DataField] + public float MassOffset; + + /// + /// Controls whether this melee weapon allows for stamina to factor into damage. + /// + [DataField] + public bool DoStaminaInteraction; + + /// + /// When true, stamina provides a disadvantage. + /// + [DataField] + public bool StaminaDisadvantage; + + /// + /// When true, stamina contests ignore clamp limitations for a melee weapon. + /// + [DataField] + public bool StaminaBypassClamp; + + /// + /// Multiplies the acceptable range of outputs provided by mass contests for melee. + /// + [DataField] + public float StaminaRangeModifier = 1; + + /// + /// The output of a stamina contest is increased by this amount. + /// + [DataField] + public float StaminaOffset; + + /// + /// Controls whether this melee weapon allows for health to factor into damage. + /// + [DataField] + public bool DoHealthInteraction; + + /// + /// When true, health contests provide a disadvantage. + /// + [DataField] + public bool HealthDisadvantage; + + /// + /// When true, health contests ignore clamp limitations for a melee weapon. + /// + [DataField] + public bool HealthBypassClamp; + + /// + /// Multiplies the acceptable range of outputs provided by mass contests for melee. + /// + [DataField] + public float HealthRangeModifier = 1; + + /// + /// The output of health contests is increased by this amount. + /// + [DataField] + public float HealthOffset; + + /// + /// Controls whether this melee weapon allows for psychic casting stats to factor into damage. + /// + [DataField] + public bool DoMindInteraction; + + /// + /// When true, high psychic casting stats provide a disadvantage. + /// + [DataField] + public bool MindDisadvantage; + + /// + /// When true, mind contests ignore clamp limitations for a melee weapon. + /// + [DataField] + public bool MindBypassClamp; + + /// + /// Multiplies the acceptable range of outputs provided by mind contests for melee. + /// + [DataField] + public float MindRangeModifier = 1; + + /// + /// The output of a mind contest is increased by this amount. + /// + [DataField] + public float MindOffset; + + /// + /// Controls whether this melee weapon allows mood to factor into damage. + /// + [DataField] + public bool DoMoodInteraction; + + /// + /// When true, mood provides a disadvantage. + /// + [DataField] + public bool MoodDisadvantage; + + /// + /// When true, mood contests ignore clamp limitations for a melee weapon. + /// + [DataField] + public bool MoodBypassClamp; + + /// + /// Multiplies the acceptable range of outputs provided by mood contests for melee. + /// + [DataField] + public float MoodRangeModifier = 1; + + /// + /// The output of mood contests is increased by this amount. + /// + [DataField] + public float MoodOffset; + + /// + /// Enables the EveryContest interaction for a melee weapon. + /// IF YOU PUT THIS ON ANY WEAPON OTHER THAN AN ADMEME, I WILL COME TO YOUR HOUSE AND SEND YOU TO MEET YOUR CREATOR WHEN THE PLAYERS COMPLAIN. + /// + [DataField] + public bool DoEveryInteraction; + + /// + /// When true, EveryContest provides a disadvantage. + /// + [DataField] + public bool EveryDisadvantage; + + /// + /// How much Mass is considered for an EveryContest. + /// + [DataField] + public float EveryMassWeight = 1; + + /// + /// How much Stamina is considered for an EveryContest. + /// + [DataField] + public float EveryStaminaWeight = 1; + + /// + /// How much Health is considered for an EveryContest. + /// + [DataField] + public float EveryHealthWeight = 1; + + /// + /// How much psychic casting stats are considered for an EveryContest. + /// + [DataField] + public float EveryMindWeight = 1; + + /// + /// How much mood is considered for an EveryContest. + /// + [DataField] + public float EveryMoodWeight = 1; + + /// + /// When true, the EveryContest sums the results of all contests rather than multiplying them, + /// probably giving you a very, very, very large multiplier... + /// + [DataField] + public bool EveryInteractionSumOrMultiply; +} diff --git a/Content.Shared/Contests/ContestsSystem.cs b/Content.Shared/Contests/ContestsSystem.cs index 47924f4d79..454d17a5d8 100644 --- a/Content.Shared/Contests/ContestsSystem.cs +++ b/Content.Shared/Contests/ContestsSystem.cs @@ -1,330 +1,477 @@ +using Content.Shared.Abilities.Psionics; using Content.Shared.CCVar; using Content.Shared.Damage; using Content.Shared.Damage.Components; +using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; +using Content.Shared.Mood; using Robust.Shared.Configuration; using Robust.Shared.Physics.Components; -namespace Content.Shared.Contests +namespace Content.Shared.Contests; + +public sealed partial class ContestsSystem : EntitySystem { - public sealed partial class ContestsSystem : EntitySystem + [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly MobThresholdSystem _mobThreshold = default!; + + /// + /// The presumed average mass of a player entity + /// Defaulted to the average mass of an adult human + /// + private const float AverageMass = 71f; + + /// + /// The presumed average sum of a Psionic's Baseline Amplification and Baseline Dampening. + /// Since Baseline casting stats are a random value between 0.4 and 1.2, this is defaulted to 0.8 + 0.8. + /// + private const float AveragePsionicPotential = 1.6f; + + #region Mass Contests + /// + /// Outputs the ratio of mass between a performer and the average human mass + /// + /// + /// bypassClamp is a deprecated input intended for supporting legacy Nyanotrasen systems. Do not use it if you don't know what you're doing. + /// + public float MassContest(EntityUid performerUid, bool bypassClamp = false, float rangeFactor = 1f, float otherMass = AverageMass) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem) + || !_cfg.GetCVar(CCVars.DoMassContests) + || !TryComp(performerUid, out var performerPhysics) + || performerPhysics.Mass == 0) + return 1f; + + return ContestClamp(ContestClampOverride(bypassClamp) + ? performerPhysics.Mass / otherMass + : Math.Clamp(performerPhysics.Mass / otherMass, + 1 - _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor, + 1 + _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor)); + } + + /// + /// + /// MaybeMassContest, in case your entity doesn't exist + /// + public float MassContest(EntityUid? performerUid, bool bypassClamp = false, float rangeFactor = 1f, float otherMass = AverageMass) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem) + || !_cfg.GetCVar(CCVars.DoMassContests) + || performerUid is null) + return 1f; + + return MassContest(performerUid.Value, bypassClamp, rangeFactor, otherMass); + } + + /// + /// Outputs the ratio of mass between a performer and the average human mass + /// If a function already has the performer's physics component, this is faster + /// + /// + /// bypassClamp is a deprecated input intended for supporting legacy Nyanotrasen systems. Do not use it if you don't know what you're doing. + /// + public float MassContest(PhysicsComponent performerPhysics, bool bypassClamp = false, float rangeFactor = 1f, float otherMass = AverageMass) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem) + || !_cfg.GetCVar(CCVars.DoMassContests) + || performerPhysics.Mass == 0) + return 1f; + + return ContestClamp(ContestClampOverride(bypassClamp) + ? performerPhysics.Mass / otherMass + : Math.Clamp(performerPhysics.Mass / otherMass, + 1 - _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor, + 1 + _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor)); + } + + /// + /// Outputs the ratio of mass between a performer and a target, accepts either EntityUids or PhysicsComponents in any combination + /// If you have physics components already in your function, use instead + /// + /// + /// bypassClamp is a deprecated input intended for supporting legacy Nyanotrasen systems. Do not use it if you don't know what you're doing. + /// + public float MassContest(EntityUid performerUid, EntityUid targetUid, bool bypassClamp = false, float rangeFactor = 1f) { - [Dependency] private readonly IConfigurationManager _cfg = default!; - [Dependency] private readonly MobThresholdSystem _mobThreshold = default!; - - /// - /// The presumed average mass of a player entity - /// Defaulted to the average mass of an adult human - /// - private const float AverageMass = 71f; - - #region Mass Contests - /// - /// Outputs the ratio of mass between a performer and the average human mass - /// - /// Uid of Performer - public float MassContest(EntityUid performerUid, bool bypassClamp = false, float rangeFactor = 1f, float otherMass = AverageMass) - { - if (!_cfg.GetCVar(CCVars.DoContestsSystem) - || !_cfg.GetCVar(CCVars.DoMassContests) - || !TryComp(performerUid, out var performerPhysics) - || performerPhysics.Mass == 0) - return 1f; - - return _cfg.GetCVar(CCVars.AllowClampOverride) && bypassClamp - ? performerPhysics.Mass / otherMass - : Math.Clamp(performerPhysics.Mass / otherMass, - 1 - _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor, - 1 + _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor); - } - - /// - /// - /// MaybeMassContest, in case your entity doesn't exist - /// - public float MassContest(EntityUid? performerUid, bool bypassClamp = false, float rangeFactor = 1f, float otherMass = AverageMass) - { - if (!_cfg.GetCVar(CCVars.DoContestsSystem) - || !_cfg.GetCVar(CCVars.DoMassContests) - || performerUid is null) - return 1f; - - return MassContest(performerUid.Value, bypassClamp, rangeFactor, otherMass); - } - - /// - /// Outputs the ratio of mass between a performer and the average human mass - /// If a function already has the performer's physics component, this is faster - /// - /// - public float MassContest(PhysicsComponent performerPhysics, bool bypassClamp = false, float rangeFactor = 1f, float otherMass = AverageMass) - { - if (!_cfg.GetCVar(CCVars.DoContestsSystem) - || !_cfg.GetCVar(CCVars.DoMassContests) - || performerPhysics.Mass == 0) - return 1f; - - return _cfg.GetCVar(CCVars.AllowClampOverride) && bypassClamp - ? performerPhysics.Mass / otherMass - : Math.Clamp(performerPhysics.Mass / otherMass, - 1 - _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor, - 1 + _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor); - } - - /// - /// Outputs the ratio of mass between a performer and a target, accepts either EntityUids or PhysicsComponents in any combination - /// If you have physics components already in your function, use instead - /// - /// - /// - public float MassContest(EntityUid performerUid, EntityUid targetUid, bool bypassClamp = false, float rangeFactor = 1f) - { - if (!_cfg.GetCVar(CCVars.DoContestsSystem) - || !_cfg.GetCVar(CCVars.DoMassContests) - || !TryComp(performerUid, out var performerPhysics) - || !TryComp(targetUid, out var targetPhysics) - || performerPhysics.Mass == 0 - || targetPhysics.InvMass == 0) - return 1f; - - return _cfg.GetCVar(CCVars.AllowClampOverride) && bypassClamp - ? performerPhysics.Mass * targetPhysics.InvMass - : Math.Clamp(performerPhysics.Mass * targetPhysics.InvMass, - 1 - _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor, - 1 + _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor); - } - - /// - public float MassContest(EntityUid performerUid, PhysicsComponent targetPhysics, bool bypassClamp = false, float rangeFactor = 1f) - { - if (!_cfg.GetCVar(CCVars.DoContestsSystem) - || !_cfg.GetCVar(CCVars.DoMassContests) - || !TryComp(performerUid, out var performerPhysics) - || performerPhysics.Mass == 0 - || targetPhysics.InvMass == 0) - return 1f; - - return _cfg.GetCVar(CCVars.AllowClampOverride) && bypassClamp - ? performerPhysics.Mass * targetPhysics.InvMass - : Math.Clamp(performerPhysics.Mass * targetPhysics.InvMass, - 1 - _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor, - 1 + _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor); - } - - /// - public float MassContest(PhysicsComponent performerPhysics, EntityUid targetUid, bool bypassClamp = false, float rangeFactor = 1f) - { - if (!_cfg.GetCVar(CCVars.DoContestsSystem) - || !_cfg.GetCVar(CCVars.DoMassContests) - || !TryComp(targetUid, out var targetPhysics) - || performerPhysics.Mass == 0 - || targetPhysics.InvMass == 0) - return 1f; - - return _cfg.GetCVar(CCVars.AllowClampOverride) && bypassClamp - ? performerPhysics.Mass * targetPhysics.InvMass - : Math.Clamp(performerPhysics.Mass * targetPhysics.InvMass, - 1 - _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor, - 1 + _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor); - } - - /// - public float MassContest(PhysicsComponent performerPhysics, PhysicsComponent targetPhysics, bool bypassClamp = false, float rangeFactor = 1f) - { - if (!_cfg.GetCVar(CCVars.DoContestsSystem) - || !_cfg.GetCVar(CCVars.DoMassContests) - || performerPhysics.Mass == 0 - || targetPhysics.InvMass == 0) - return 1f; - - return _cfg.GetCVar(CCVars.AllowClampOverride) && bypassClamp - ? performerPhysics.Mass * targetPhysics.InvMass - : Math.Clamp(performerPhysics.Mass * targetPhysics.InvMass, - 1 - _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor, - 1 + _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor); - } - - #endregion - #region Stamina Contests - - public float StaminaContest(EntityUid performer, bool bypassClamp = false, float rangeFactor = 1f) - { - if (!_cfg.GetCVar(CCVars.DoContestsSystem) - || !_cfg.GetCVar(CCVars.DoStaminaContests) - || !TryComp(performer, out var perfStamina) - || perfStamina.StaminaDamage == 0) - return 1f; - - return _cfg.GetCVar(CCVars.AllowClampOverride) && bypassClamp - ? 1 - perfStamina.StaminaDamage / perfStamina.CritThreshold - : 1 - Math.Clamp(perfStamina.StaminaDamage / perfStamina.CritThreshold, 0, 0.25f * rangeFactor); - } - - public float StaminaContest(StaminaComponent perfStamina, bool bypassClamp = false, float rangeFactor = 1f) - { - if (!_cfg.GetCVar(CCVars.DoContestsSystem) - || !_cfg.GetCVar(CCVars.DoStaminaContests)) - return 1f; - - return _cfg.GetCVar(CCVars.AllowClampOverride) && bypassClamp - ? 1 - perfStamina.StaminaDamage / perfStamina.CritThreshold - : 1 - Math.Clamp(perfStamina.StaminaDamage / perfStamina.CritThreshold, 0, 0.25f * rangeFactor); - } - - public float StaminaContest(EntityUid performer, EntityUid target, bool bypassClamp = false, float rangeFactor = 1f) - { - if (!_cfg.GetCVar(CCVars.DoContestsSystem) - || !_cfg.GetCVar(CCVars.DoStaminaContests) - || !TryComp(performer, out var perfStamina) - || !TryComp(target, out var targetStamina)) - return 1f; - - return _cfg.GetCVar(CCVars.AllowClampOverride) && bypassClamp - ? (1 - perfStamina.StaminaDamage / perfStamina.CritThreshold) - / (1 - targetStamina.StaminaDamage / targetStamina.CritThreshold) - : (1 - Math.Clamp(perfStamina.StaminaDamage / perfStamina.CritThreshold, 0, 0.25f * rangeFactor)) - / (1 - Math.Clamp(targetStamina.StaminaDamage / targetStamina.CritThreshold, 0, 0.25f * rangeFactor)); - } - - #endregion - - #region Health Contests - - public float HealthContest(EntityUid performer, bool bypassClamp = false, float rangeFactor = 1f) - { - if (!_cfg.GetCVar(CCVars.DoContestsSystem) - || !_cfg.GetCVar(CCVars.DoHealthContests) - || !TryComp(performer, out var damage) - || !_mobThreshold.TryGetThresholdForState(performer, Mobs.MobState.Critical, out var threshold)) - return 1f; - - return _cfg.GetCVar(CCVars.AllowClampOverride) && bypassClamp - ? 1 - damage.TotalDamage.Float() / threshold.Value.Float() - : 1 - Math.Clamp(damage.TotalDamage.Float() / threshold.Value.Float(), 0, 0.25f * rangeFactor); - } - - public float HealthContest(EntityUid performer, EntityUid target, bool bypassClamp = false, float rangeFactor = 1f) - { - if (!_cfg.GetCVar(CCVars.DoContestsSystem) - || !_cfg.GetCVar(CCVars.DoHealthContests) - || !TryComp(performer, out var perfDamage) - || !TryComp(target, out var targetDamage) - || !_mobThreshold.TryGetThresholdForState(performer, Mobs.MobState.Critical, out var perfThreshold) - || !_mobThreshold.TryGetThresholdForState(target, Mobs.MobState.Critical, out var targetThreshold)) - return 1f; - - return _cfg.GetCVar(CCVars.AllowClampOverride) && bypassClamp - ? (1 - perfDamage.TotalDamage.Float() / perfThreshold.Value.Float()) - / (1 - targetDamage.TotalDamage.Float() / targetThreshold.Value.Float()) - : (1 - Math.Clamp(perfDamage.TotalDamage.Float() / perfThreshold.Value.Float(), 0, 0.25f * rangeFactor)) - / (1 - Math.Clamp(targetDamage.TotalDamage.Float() / targetThreshold.Value.Float(), 0, 0.25f * rangeFactor)); - } - #endregion - - #region Mind Contests - - /// - /// These cannot be implemented until AFTER the psychic refactor, but can still be factored into other systems before that point. - /// Same rule here as other Contest functions, simply multiply or divide by the function. - /// - /// - /// - /// - /// - public float MindContest(EntityUid performer, bool bypassClamp = false, float rangeFactor = 1f) - { - if (!_cfg.GetCVar(CCVars.DoContestsSystem) - || !_cfg.GetCVar(CCVars.DoMindContests)) - return 1f; + if (!_cfg.GetCVar(CCVars.DoContestsSystem) + || !_cfg.GetCVar(CCVars.DoMassContests) + || !TryComp(performerUid, out var performerPhysics) + || !TryComp(targetUid, out var targetPhysics) + || performerPhysics.Mass == 0 + || targetPhysics.InvMass == 0) + return 1f; + + return ContestClamp(ContestClampOverride(bypassClamp) + ? performerPhysics.Mass * targetPhysics.InvMass + : Math.Clamp(performerPhysics.Mass * targetPhysics.InvMass, + 1 - _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor, + 1 + _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor)); + } + /// + public float MassContest(EntityUid performerUid, PhysicsComponent targetPhysics, bool bypassClamp = false, float rangeFactor = 1f) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem) + || !_cfg.GetCVar(CCVars.DoMassContests) + || !TryComp(performerUid, out var performerPhysics) + || performerPhysics.Mass == 0 + || targetPhysics.InvMass == 0) return 1f; - } - public float MindContest(EntityUid performer, EntityUid target, bool bypassClamp = false, float rangeFactor = 1f) - { - if (!_cfg.GetCVar(CCVars.DoContestsSystem) - || !_cfg.GetCVar(CCVars.DoMindContests)) - return 1f; + return ContestClamp(ContestClampOverride(bypassClamp) + ? performerPhysics.Mass * targetPhysics.InvMass + : Math.Clamp(performerPhysics.Mass * targetPhysics.InvMass, + 1 - _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor, + 1 + _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor)); + } + /// + public float MassContest(PhysicsComponent performerPhysics, EntityUid targetUid, bool bypassClamp = false, float rangeFactor = 1f) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem) + || !_cfg.GetCVar(CCVars.DoMassContests) + || !TryComp(targetUid, out var targetPhysics) + || performerPhysics.Mass == 0 + || targetPhysics.InvMass == 0) return 1f; - } - - #endregion - - #region EVERY CONTESTS - - public float EveryContest( - EntityUid performer, - bool bypassClampMass = false, - bool bypassClampStamina = false, - bool bypassClampHealth = false, - bool bypassClampMind = false, - float rangeFactorMass = 1f, - float rangeFactorStamina = 1f, - float rangeFactorHealth = 1f, - float rangeFactorMind = 1f, - float weightMass = 1f, - float weightStamina = 1f, - float weightHealth = 1f, - float weightMind = 1f, - bool sumOrMultiply = false) - { - if (!_cfg.GetCVar(CCVars.DoContestsSystem)) - return 1f; - - var weightTotal = weightMass + weightStamina + weightHealth + weightMind; - var massMultiplier = weightMass / weightTotal; - var staminaMultiplier = weightStamina / weightTotal; - var healthMultiplier = weightHealth / weightTotal; - var mindMultiplier = weightMind / weightTotal; - - return sumOrMultiply - ? MassContest(performer, bypassClampMass, rangeFactorMass) * massMultiplier - + StaminaContest(performer, bypassClampStamina, rangeFactorStamina) * staminaMultiplier - + HealthContest(performer, bypassClampHealth, rangeFactorHealth) * healthMultiplier - + MindContest(performer, bypassClampMind, rangeFactorMind) * mindMultiplier - : MassContest(performer, bypassClampMass, rangeFactorMass) * massMultiplier - * StaminaContest(performer, bypassClampStamina, rangeFactorStamina) * staminaMultiplier - * HealthContest(performer, bypassClampHealth, rangeFactorHealth) * healthMultiplier - * MindContest(performer, bypassClampMind, rangeFactorMind) * mindMultiplier; - } - - public float EveryContest( - EntityUid performer, - EntityUid target, - bool bypassClampMass = false, - bool bypassClampStamina = false, - bool bypassClampHealth = false, - bool bypassClampMind = false, - float rangeFactorMass = 1f, - float rangeFactorStamina = 1f, - float rangeFactorHealth = 1f, - float rangeFactorMind = 1f, - float weightMass = 1f, - float weightStamina = 1f, - float weightHealth = 1f, - float weightMind = 1f, - bool sumOrMultiply = false) - { - if (!_cfg.GetCVar(CCVars.DoContestsSystem)) - return 1f; - - var weightTotal = weightMass + weightStamina + weightHealth + weightMind; - var massMultiplier = weightMass / weightTotal; - var staminaMultiplier = weightStamina / weightTotal; - var healthMultiplier = weightHealth / weightTotal; - var mindMultiplier = weightMind / weightTotal; - - return sumOrMultiply - ? MassContest(performer, target, bypassClampMass, rangeFactorMass) * massMultiplier - + StaminaContest(performer, target, bypassClampStamina, rangeFactorStamina) * staminaMultiplier - + HealthContest(performer, target, bypassClampHealth, rangeFactorHealth) * healthMultiplier - + MindContest(performer, target, bypassClampMind, rangeFactorMind) * mindMultiplier - : MassContest(performer, target, bypassClampMass, rangeFactorMass) * massMultiplier - * StaminaContest(performer, target, bypassClampStamina, rangeFactorStamina) * staminaMultiplier - * HealthContest(performer, target, bypassClampHealth, rangeFactorHealth) * healthMultiplier - * MindContest(performer, target, bypassClampMind, rangeFactorMind) * mindMultiplier; - } - #endregion + + return ContestClamp(ContestClampOverride(bypassClamp) + ? performerPhysics.Mass * targetPhysics.InvMass + : Math.Clamp(performerPhysics.Mass * targetPhysics.InvMass, + 1 - _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor, + 1 + _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor)); + } + + /// + public float MassContest(PhysicsComponent performerPhysics, PhysicsComponent targetPhysics, bool bypassClamp = false, float rangeFactor = 1f) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem) + || !_cfg.GetCVar(CCVars.DoMassContests) + || performerPhysics.Mass == 0 + || targetPhysics.InvMass == 0) + return 1f; + + return ContestClamp(ContestClampOverride(bypassClamp) + ? performerPhysics.Mass * targetPhysics.InvMass + : Math.Clamp(performerPhysics.Mass * targetPhysics.InvMass, + 1 - _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor, + 1 + _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor)); + } + + #endregion + #region Stamina Contests + + /// + /// Outputs 1 minus the percentage of an Entity's Stamina, with a Range of [Epsilon, 1 - 0.25 * rangeFactor], or a range of [Epsilon, 1 - Epsilon] if bypassClamp is true. + /// This will never return a value >1. + /// + /// + /// bypassClamp is a deprecated input intended for supporting legacy Nyanotrasen systems. Do not use it if you don't know what you're doing. + /// + public float StaminaContest(EntityUid performer, bool bypassClamp = false, float rangeFactor = 1f) + { + if (!TryComp(performer, out var perfStamina) + || perfStamina.StaminaDamage == 0) + return 1f; + + return StaminaContest(perfStamina, bypassClamp, rangeFactor); + } + + /// + public float StaminaContest(StaminaComponent perfStamina, bool bypassClamp = false, float rangeFactor = 1f) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem) + || !_cfg.GetCVar(CCVars.DoStaminaContests)) + return 1f; + + return ContestClamp(ContestClampOverride(bypassClamp) + ? 1 - perfStamina.StaminaDamage / perfStamina.CritThreshold + : 1 - Math.Clamp(perfStamina.StaminaDamage / perfStamina.CritThreshold, 0, 0.25f * rangeFactor)); + } + + /// + /// Outputs the ratio of percentage of an Entity's Stamina and a Target Entity's Stamina, with a Range of [Epsilon, 0.25 * rangeFactor], or a range of [Epsilon, +inf] if bypassClamp is true. + /// This does NOT produce the same kind of outputs as a Single-Entity StaminaContest. 2Entity StaminaContest returns the product of two Solo Stamina Contests, and so its values can be very strange. + /// + /// + /// bypassClamp is a deprecated input intended for supporting legacy Nyanotrasen systems. Do not use it if you don't know what you're doing. + /// + public float StaminaContest(EntityUid performer, EntityUid target, bool bypassClamp = false, float rangeFactor = 1f) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem) + || !_cfg.GetCVar(CCVars.DoStaminaContests) + || !TryComp(performer, out var perfStamina) + || !TryComp(target, out var targetStamina)) + return 1f; + + return ContestClamp(ContestClampOverride(bypassClamp) + ? (1 - perfStamina.StaminaDamage / perfStamina.CritThreshold) + / (1 - targetStamina.StaminaDamage / targetStamina.CritThreshold) + : (1 - Math.Clamp(perfStamina.StaminaDamage / perfStamina.CritThreshold, 0, 0.25f * rangeFactor)) + / (1 - Math.Clamp(targetStamina.StaminaDamage / targetStamina.CritThreshold, 0, 0.25f * rangeFactor))); + } + + #endregion + + #region Health Contests + + /// + /// Outputs 1 minus the percentage of an Entity's Health, with a Range of [Epsilon, 1 - 0.25 * rangeFactor], or a range of [Epsilon, 1 - Epsilon] if bypassClamp is true. + /// This will never return a value >1. + /// + /// + /// bypassClamp is a deprecated input intended for supporting legacy Nyanotrasen systems. Do not use it if you don't know what you're doing. + /// + public float HealthContest(EntityUid performer, bool bypassClamp = false, float rangeFactor = 1f) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem) + || !_cfg.GetCVar(CCVars.DoHealthContests) + || !TryComp(performer, out var damage) + || !TryComp(performer, out var thresholdsComponent) + || !_mobThreshold.TryGetThresholdForState(performer, Mobs.MobState.Critical, out var threshold, thresholdsComponent)) + return 1f; + + return ContestClamp(ContestClampOverride(bypassClamp) + ? 1 - damage.TotalDamage.Float() / threshold.Value.Float() + : 1 - Math.Clamp(damage.TotalDamage.Float() / threshold.Value.Float(), 0, 0.25f * rangeFactor)); + } + + /// + /// Outputs the ratio of percentage of an Entity's Health and a Target Entity's Health, with a Range of [Epsilon, 0.25 * rangeFactor], or a range of [Epsilon, +inf] if bypassClamp is true. + /// This does NOT produce the same kind of outputs as a Single-Entity HealthContest. 2Entity HealthContest returns the product of two Solo Health Contests, and so its values can be very strange. + /// + /// + /// bypassClamp is a deprecated input intended for supporting legacy Nyanotrasen systems. Do not use it if you don't know what you're doing. + /// + public float HealthContest(EntityUid performer, EntityUid target, bool bypassClamp = false, float rangeFactor = 1f) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem) + || !_cfg.GetCVar(CCVars.DoHealthContests) + || !TryComp(performer, out var perfDamage) + || !TryComp(target, out var targetDamage) + || !TryComp(performer, out var perfThresholdComp) + || !TryComp(target, out var targetThresholdComp) + || !_mobThreshold.TryGetThresholdForState(performer, Mobs.MobState.Critical, out var perfThreshold, perfThresholdComp) + || !_mobThreshold.TryGetThresholdForState(target, Mobs.MobState.Critical, out var targetThreshold, targetThresholdComp)) + return 1f; + + return ContestClamp(ContestClampOverride(bypassClamp) + ? (1 - perfDamage.TotalDamage.Float() / perfThreshold.Value.Float()) + / (1 - targetDamage.TotalDamage.Float() / targetThreshold.Value.Float()) + : (1 - Math.Clamp(perfDamage.TotalDamage.Float() / perfThreshold.Value.Float(), 0, 0.25f * rangeFactor)) + / (1 - Math.Clamp(targetDamage.TotalDamage.Float() / targetThreshold.Value.Float(), 0, 0.25f * rangeFactor))); + } + #endregion + + #region Mind Contests + + /// + /// Returns the ratio of casting stats between a performer and the presumed average latent psionic. + /// Uniquely among Contests, not being Psionic is not a failure condition, and is instead a variable. + /// If you do not have a PsionicComponent, your combined casting stats are assumed to be 0.1f + /// + /// + /// This can produce some truly astounding modifiers, so be ready to meet god if you bypass the clamp. + /// By bypassing this function's clamp you hereby agree to forfeit your soul to VMSolidus should unintended bugs occur. + /// + public float MindContest(EntityUid performer, bool bypassClamp = false, float rangeFactor = 1f, float otherPsion = AveragePsionicPotential) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem) + || !_cfg.GetCVar(CCVars.DoMindContests)) + return 1f; + + var performerPotential = TryComp(performer, out var performerPsionic) + ? performerPsionic.CurrentAmplification + performerPsionic.CurrentDampening + : 0.1f; + + if (performerPotential == otherPsion) + return 1f; + + return ContestClamp(ContestClampOverride(bypassClamp) + ? performerPotential / otherPsion + : Math.Clamp(performerPotential / otherPsion, + 1 - _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor, + 1 + _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor)); + } + + /// + /// Returns the ratio of casting stats between a performer and a target. + /// Like with single-Uid MindContests, if an entity does not possess a PsionicComponent, its casting stats are assumed to be 0.1f. + /// + /// + /// This can produce some truly astounding modifiers, so be ready to meet god if you bypass the clamp. + /// By bypassing this function's clamp you hereby agree to forfeit your soul to VMSolidus should unintended bugs occur. + /// + public float MindContest(EntityUid performer, EntityUid target, bool bypassClamp = false, float rangeFactor = 1f) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem) + || !_cfg.GetCVar(CCVars.DoMindContests)) + return 1f; + + var performerPotential = TryComp(performer, out var performerPsionic) + ? performerPsionic.CurrentAmplification + performerPsionic.CurrentDampening + : 0.1f; + + var targetPotential = TryComp(target, out var targetPsionic) + ? targetPsionic.CurrentAmplification + targetPsionic.CurrentDampening + : 0.1f; + + if (performerPotential == targetPotential) + return 1f; + + return ContestClamp(ContestClampOverride(bypassClamp) + ? performerPotential / targetPotential + : Math.Clamp(performerPotential / targetPotential, + 1 - _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor, + 1 + _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor)); + } + + #endregion + + #region Mood Contests + + /// + /// Outputs the ratio of an Entity's mood level and its Neutral Mood threshold. + /// + /// + /// bypassClamp is a deprecated input intended for supporting legacy Nyanotrasen systems. Do not use it if you don't know what you're doing. + /// + public float MoodContest(EntityUid performer, bool bypassClamp = false, float rangeFactor = 1f) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem) + || !_cfg.GetCVar(CCVars.DoMoodContests) + || !TryComp(performer, out var mood)) + return 1f; + + return ContestClamp(ContestClampOverride(bypassClamp) + ? mood.CurrentMoodLevel / mood.NeutralMoodThreshold + : Math.Clamp(mood.CurrentMoodLevel / mood.NeutralMoodThreshold, + 1 - _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor, + 1 + _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor)); + } + + /// + /// Outputs the ratio of mood level between two Entities. + /// + /// + /// bypassClamp is a deprecated input intended for supporting legacy Nyanotrasen systems. Do not use it if you don't know what you're doing. + /// + public float MoodContest(EntityUid performer, EntityUid target, bool bypassClamp = false, float rangeFactor = 1f) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem) + || !_cfg.GetCVar(CCVars.DoMoodContests) + || !TryComp(performer, out var performerMood) + || !TryComp(target, out var targetMood)) + return 1f; + + return ContestClamp(ContestClampOverride(bypassClamp) + ? performerMood.CurrentMoodLevel / targetMood.CurrentMoodLevel + : Math.Clamp(performerMood.CurrentMoodLevel / targetMood.CurrentMoodLevel, + 1 - _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor, + 1 + _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor)); + } + + #endregion + + #region EVERY CONTESTS + + /// + /// EveryContest takes either the Sum or Product of all existing contests, for if you want to just check if somebody is absolutely fucked up. + /// + /// + /// If it's not immediately obvious that a function with 16 optional inputs is a joke, please take a step back and re-evaluate why you're using this function. + /// All prior warnings also apply here. Bypass the clamps at your own risk. By calling this function in your system, you hereby agree to forfeit your soul to VMSolidus if bugs occur. + /// + public float EveryContest( + EntityUid performer, + bool bypassClampMass = false, + bool bypassClampStamina = false, + bool bypassClampHealth = false, + bool bypassClampMind = false, + bool bypassClampMood = false, + float rangeFactorMass = 1f, + float rangeFactorStamina = 1f, + float rangeFactorHealth = 1f, + float rangeFactorMind = 1f, + float rangeFactorMood = 1f, + float weightMass = 1f, + float weightStamina = 1f, + float weightHealth = 1f, + float weightMind = 1f, + float weightMood = 1f, + bool sumOrMultiply = false) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem)) + return 1f; + + var weightTotal = weightMass + weightStamina + weightHealth + weightMind + weightMood; + var massMultiplier = weightMass / weightTotal; + var staminaMultiplier = weightStamina / weightTotal; + var healthMultiplier = weightHealth / weightTotal; + var mindMultiplier = weightMind / weightTotal; + var moodMultiplier = weightMood / weightTotal; + + return sumOrMultiply + ? MassContest(performer, bypassClampMass, rangeFactorMass) * massMultiplier + + StaminaContest(performer, bypassClampStamina, rangeFactorStamina) * staminaMultiplier + + HealthContest(performer, bypassClampHealth, rangeFactorHealth) * healthMultiplier + + MindContest(performer, bypassClampMind, rangeFactorMind) * mindMultiplier + + MoodContest(performer, bypassClampMood, rangeFactorMood) * moodMultiplier + : ContestClamp(MassContest(performer, bypassClampMass, rangeFactorMass) * massMultiplier + * StaminaContest(performer, bypassClampStamina, rangeFactorStamina) * staminaMultiplier + * HealthContest(performer, bypassClampHealth, rangeFactorHealth) * healthMultiplier + * MindContest(performer, bypassClampMind, rangeFactorMind) * mindMultiplier + * MoodContest(performer, bypassClampMood, rangeFactorMood) * moodMultiplier); + } + + /// + /// EveryContest takes either the Sum or Product of all existing contests, for if you want to just check if somebody is absolutely fucked up. + /// + /// + /// If it's not immediately obvious that a function with 16 optional inputs is a joke, please take a step back and re-evaluate why you're using this function. + /// All prior warnings also apply here. Bypass the clamps at your own risk. By calling this function in your system, you hereby agree to forfeit your soul to VMSolidus if bugs occur. + /// + public float EveryContest( + EntityUid performer, + EntityUid target, + bool bypassClampMass = false, + bool bypassClampStamina = false, + bool bypassClampHealth = false, + bool bypassClampMind = false, + bool bypassClampMood = false, + float rangeFactorMass = 1f, + float rangeFactorStamina = 1f, + float rangeFactorHealth = 1f, + float rangeFactorMind = 1f, + float rangeFactorMood = 1f, + float weightMass = 1f, + float weightStamina = 1f, + float weightHealth = 1f, + float weightMind = 1f, + float weightMood = 1f, + bool sumOrMultiply = false) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem)) + return 1f; + + var weightTotal = weightMass + weightStamina + weightHealth + weightMind + weightMood; + var massMultiplier = weightMass / weightTotal; + var staminaMultiplier = weightStamina / weightTotal; + var healthMultiplier = weightHealth / weightTotal; + var mindMultiplier = weightMind / weightTotal; + var moodMultiplier = weightMood / weightTotal; + + return sumOrMultiply + ? MassContest(performer, target, bypassClampMass, rangeFactorMass) * massMultiplier + + StaminaContest(performer, target, bypassClampStamina, rangeFactorStamina) * staminaMultiplier + + HealthContest(performer, target, bypassClampHealth, rangeFactorHealth) * healthMultiplier + + MindContest(performer, target, bypassClampMind, rangeFactorMind) * mindMultiplier + + MoodContest(performer, target, bypassClampMood, rangeFactorMood) * moodMultiplier + : ContestClamp(MassContest(performer, target, bypassClampMass, rangeFactorMass) * massMultiplier + * StaminaContest(performer, target, bypassClampStamina, rangeFactorStamina) * staminaMultiplier + * HealthContest(performer, target, bypassClampHealth, rangeFactorHealth) * healthMultiplier + * MindContest(performer, target, bypassClampMind, rangeFactorMind) * mindMultiplier + * MoodContest(performer, target, bypassClampMood, rangeFactorMood) * moodMultiplier); } + #endregion } diff --git a/Content.Shared/CriminalRecords/Components/CriminalRecordsHackerComponent.cs b/Content.Shared/CriminalRecords/Components/CriminalRecordsHackerComponent.cs new file mode 100644 index 0000000000..189a387a5d --- /dev/null +++ b/Content.Shared/CriminalRecords/Components/CriminalRecordsHackerComponent.cs @@ -0,0 +1,31 @@ +using Content.Shared.CriminalRecords.Systems; +using Content.Shared.Dataset; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.CriminalRecords.Components; + +/// +/// Lets the user hack a criminal records console, once. +/// Everyone is set to wanted with a randomly picked reason. +/// +[RegisterComponent, NetworkedComponent, Access(typeof(SharedCriminalRecordsHackerSystem))] +public sealed partial class CriminalRecordsHackerComponent : Component +{ + /// + /// How long the doafter is for hacking it. + /// + public TimeSpan Delay = TimeSpan.FromSeconds(20); + + /// + /// Dataset of random reasons to use. + /// + [DataField] + public ProtoId Reasons = "CriminalRecordsWantedReasonPlaceholders"; + + /// + /// Announcement made after the console is hacked. + /// + [DataField] + public LocId Announcement = "ninja-criminal-records-hack-announcement"; +} diff --git a/Content.Shared/CriminalRecords/Systems/SharedCriminalRecordsHackerSystem.cs b/Content.Shared/CriminalRecords/Systems/SharedCriminalRecordsHackerSystem.cs new file mode 100644 index 0000000000..a626db19e5 --- /dev/null +++ b/Content.Shared/CriminalRecords/Systems/SharedCriminalRecordsHackerSystem.cs @@ -0,0 +1,50 @@ +using Content.Shared.CriminalRecords.Components; +using Content.Shared.DoAfter; +using Content.Shared.Interaction; +using Content.Shared.Ninja.Systems; +using Robust.Shared.Serialization; + +namespace Content.Shared.CriminalRecords.Systems; + +public abstract class SharedCriminalRecordsHackerSystem : EntitySystem +{ + [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + [Dependency] private readonly SharedNinjaGlovesSystem _gloves = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnBeforeInteractHand); + } + + private void OnBeforeInteractHand(Entity ent, ref BeforeInteractHandEvent args) + { + // TODO: generic event + if (args.Handled || !_gloves.AbilityCheck(ent, args, out var target)) + return; + + if (!HasComp(target)) + return; + + var doAfterArgs = new DoAfterArgs(EntityManager, ent, ent.Comp.Delay, new CriminalRecordsHackDoAfterEvent(), target: target, used: ent, eventTarget: ent) + { + BreakOnDamage = true, + BreakOnTargetMove = true, + BreakOnUserMove = true, + BreakOnWeightlessMove = true, + MovementThreshold = 0.5f, + }; + + _doAfter.TryStartDoAfter(doAfterArgs); + args.Handled = true; + } +} + +/// +/// Raised on the user when the doafter completes. +/// +[Serializable, NetSerializable] +public sealed partial class CriminalRecordsHackDoAfterEvent : SimpleDoAfterEvent +{ +} diff --git a/Content.Shared/Cuffs/SharedCuffableSystem.cs b/Content.Shared/Cuffs/SharedCuffableSystem.cs index b47fa08bc7..9777b23988 100644 --- a/Content.Shared/Cuffs/SharedCuffableSystem.cs +++ b/Content.Shared/Cuffs/SharedCuffableSystem.cs @@ -7,6 +7,7 @@ using Content.Shared.Contests; using Content.Shared.Cuffs.Components; using Content.Shared.Database; +using Content.Shared.Flight; using Content.Shared.DoAfter; using Content.Shared.Hands; using Content.Shared.Hands.Components; @@ -28,6 +29,7 @@ using Content.Shared.Verbs; using Content.Shared.Weapons.Melee.Events; using Robust.Shared.Audio.Systems; +using Content.Shared.Mood; using Robust.Shared.Containers; using Robust.Shared.Network; using Robust.Shared.Player; @@ -174,9 +176,15 @@ public void UpdateCuffState(EntityUid uid, CuffableComponent component) _actionBlocker.UpdateCanMove(uid); if (component.CanStillInteract) + { _alerts.ClearAlert(uid, AlertType.Handcuffed); + RaiseLocalEvent(uid, new MoodRemoveEffectEvent("Handcuffed")); + } else + { _alerts.ShowAlert(uid, AlertType.Handcuffed); + RaiseLocalEvent(uid, new MoodEffectEvent("Handcuffed")); + } var ev = new CuffedStateChangeEvent(); RaiseLocalEvent(uid, ref ev); @@ -472,6 +480,13 @@ public bool TryCuffing(EntityUid user, EntityUid target, EntityUid handcuff, Han return true; } + if (TryComp(target, out var flight) && flight.On) + { + _popup.PopupClient(Loc.GetString("handcuff-component-target-flying-error", + ("targetName", Identity.Name(target, EntityManager, user))), user, user); + return true; + } + var cuffTime = handcuffComponent.CuffTime; if (HasComp(target)) @@ -724,4 +739,4 @@ private sealed partial class AddCuffDoAfterEvent : SimpleDoAfterEvent { } } -} +} \ No newline at end of file diff --git a/Content.Shared/Customization/Systems/CharacterRequirements.Job.cs b/Content.Shared/Customization/Systems/CharacterRequirements.Job.cs index fe44f2ccc0..f9a060b738 100644 --- a/Content.Shared/Customization/Systems/CharacterRequirements.Job.cs +++ b/Content.Shared/Customization/Systems/CharacterRequirements.Job.cs @@ -24,9 +24,9 @@ public sealed partial class CharacterJobRequirement : CharacterRequirement public List> Jobs; public override bool IsValid(JobPrototype job, HumanoidCharacterProfile profile, - Dictionary playTimes, bool whitelisted, + Dictionary playTimes, bool whitelisted, IPrototype prototype, IEntityManager entityManager, IPrototypeManager prototypeManager, IConfigurationManager configManager, - out FormattedMessage? reason) + out FormattedMessage? reason, int depth = 0) { var jobs = new List(); @@ -70,9 +70,9 @@ public sealed partial class CharacterDepartmentRequirement : CharacterRequiremen public List> Departments; public override bool IsValid(JobPrototype job, HumanoidCharacterProfile profile, - Dictionary playTimes, bool whitelisted, + Dictionary playTimes, bool whitelisted, IPrototype prototype, IEntityManager entityManager, IPrototypeManager prototypeManager, IConfigurationManager configManager, - out FormattedMessage? reason) + out FormattedMessage? reason, int depth = 0) { var departments = new List(); @@ -112,9 +112,9 @@ public sealed partial class CharacterDepartmentTimeRequirement : CharacterRequir public ProtoId Department; public override bool IsValid(JobPrototype job, HumanoidCharacterProfile profile, - Dictionary playTimes, bool whitelisted, + Dictionary playTimes, bool whitelisted, IPrototype prototype, IEntityManager entityManager, IPrototypeManager prototypeManager, IConfigurationManager configManager, - out FormattedMessage? reason) + out FormattedMessage? reason, int depth = 0) { // Disable the requirement if the role timers are disabled if (!configManager.GetCVar(CCVars.GameRoleTimers)) @@ -178,9 +178,9 @@ public sealed partial class CharacterOverallTimeRequirement : CharacterRequireme public TimeSpan Max = TimeSpan.MaxValue; public override bool IsValid(JobPrototype job, HumanoidCharacterProfile profile, - Dictionary playTimes, bool whitelisted, + Dictionary playTimes, bool whitelisted, IPrototype prototype, IEntityManager entityManager, IPrototypeManager prototypeManager, IConfigurationManager configManager, - out FormattedMessage? reason) + out FormattedMessage? reason, int depth = 0) { // Disable the requirement if the role timers are disabled if (!configManager.GetCVar(CCVars.GameRoleTimers)) @@ -234,9 +234,9 @@ public sealed partial class CharacterPlaytimeRequirement : CharacterRequirement public ProtoId Tracker; public override bool IsValid(JobPrototype job, HumanoidCharacterProfile profile, - Dictionary playTimes, bool whitelisted, + Dictionary playTimes, bool whitelisted, IPrototype prototype, IEntityManager entityManager, IPrototypeManager prototypeManager, IConfigurationManager configManager, - out FormattedMessage? reason) + out FormattedMessage? reason, int depth = 0) { // Disable the requirement if the role timers are disabled if (!configManager.GetCVar(CCVars.GameRoleTimers)) diff --git a/Content.Shared/Customization/Systems/CharacterRequirements.Logic.cs b/Content.Shared/Customization/Systems/CharacterRequirements.Logic.cs new file mode 100644 index 0000000000..397e74bc8b --- /dev/null +++ b/Content.Shared/Customization/Systems/CharacterRequirements.Logic.cs @@ -0,0 +1,150 @@ +using System.Linq; +using Content.Shared.Preferences; +using Content.Shared.Roles; +using JetBrains.Annotations; +using Robust.Shared.Configuration; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; +using Robust.Shared.Utility; + +namespace Content.Shared.Customization.Systems; + + +/// +/// Requires all of the requirements to be true +/// +[UsedImplicitly] +[Serializable, NetSerializable] +public sealed partial class CharacterLogicAndRequirement : CharacterRequirement +{ + [DataField] + public List Requirements { get; private set; } = new(); + + public override bool IsValid(JobPrototype job, HumanoidCharacterProfile profile, + Dictionary playTimes, bool whitelisted, IPrototype prototype, + IEntityManager entityManager, IPrototypeManager prototypeManager, IConfigurationManager configManager, + out FormattedMessage? reason, int depth = 0) + { + var succeeded = entityManager.EntitySysManager.GetEntitySystem() + .CheckRequirementsValid(Requirements, job, profile, playTimes, whitelisted, prototype, entityManager, + prototypeManager, configManager, out var reasons, depth + 1); + + if (reasons.Count == 0) + { + reason = null; + return succeeded; + } + + reason = new FormattedMessage(); + foreach (var message in reasons) + reason.AddMessage(FormattedMessage.FromMarkup( + Loc.GetString("character-logic-and-requirement-listprefix", ("indent", new string(' ', depth * 2))) + message.ToMarkup())); + reason = FormattedMessage.FromMarkup(Loc.GetString("character-logic-and-requirement", + ("inverted", Inverted), ("options", reason.ToMarkup()))); + + return succeeded; + } +} + +/// +/// Requires any of the requirements to be true +/// +[UsedImplicitly] +[Serializable, NetSerializable] +public sealed partial class CharacterLogicOrRequirement : CharacterRequirement +{ + [DataField] + public List Requirements { get; private set; } = new(); + + public override bool IsValid(JobPrototype job, HumanoidCharacterProfile profile, + Dictionary playTimes, bool whitelisted, IPrototype prototype, + IEntityManager entityManager, IPrototypeManager prototypeManager, IConfigurationManager configManager, + out FormattedMessage? reason, int depth = 0) + { + var succeeded = false; + var reasons = new List(); + var characterRequirements = entityManager.EntitySysManager.GetEntitySystem(); + + foreach (var requirement in Requirements) + { + if (characterRequirements.CheckRequirementValid(requirement, job, profile, playTimes, whitelisted, prototype, + entityManager, prototypeManager, configManager, out var raisin, depth + 1)) + { + succeeded = true; + break; + } + + if (raisin != null) + reasons.Add(raisin); + } + + if (reasons.Count == 0) + { + reason = null; + return succeeded; + } + + reason = new FormattedMessage(); + foreach (var message in reasons) + reason.AddMessage(FormattedMessage.FromMarkup( + Loc.GetString("character-logic-or-requirement-listprefix", ("indent", new string(' ', depth * 2))) + message.ToMarkup())); + reason = FormattedMessage.FromMarkup(Loc.GetString("character-logic-or-requirement", + ("inverted", Inverted), ("options", reason.ToMarkup()))); + + return succeeded; + } +} + +/// +/// Requires only one of the requirements to be true +/// +[UsedImplicitly] +[Serializable, NetSerializable] +public sealed partial class CharacterLogicXorRequirement : CharacterRequirement +{ + [DataField] + public List Requirements { get; private set; } = new(); + + public override bool IsValid(JobPrototype job, HumanoidCharacterProfile profile, + Dictionary playTimes, bool whitelisted, IPrototype prototype, + IEntityManager entityManager, IPrototypeManager prototypeManager, IConfigurationManager configManager, + out FormattedMessage? reason, int depth = 0) + { + var reasons = new List(); + var succeeded = false; + var characterRequirements = entityManager.EntitySysManager.GetEntitySystem(); + + foreach (var requirement in Requirements) + { + if (characterRequirements.CheckRequirementValid(requirement, job, profile, playTimes, whitelisted, prototype, + entityManager, prototypeManager, configManager, out var raisin, depth + 1)) + { + if (succeeded) + { + succeeded = false; + break; + } + + succeeded = true; + } + + if (raisin != null) + reasons.Add(raisin); + } + + if (reasons.Count == 0) + { + reason = null; + return succeeded; + } + + reason = new FormattedMessage(); + foreach (var message in reasons) + reason.AddMessage(FormattedMessage.FromMarkup( + Loc.GetString("character-logic-xor-requirement-listprefix", ("indent", new string(' ', depth * 2))) + message.ToMarkup())); + reason = FormattedMessage.FromMarkup(Loc.GetString("character-logic-xor-requirement", + ("inverted", Inverted), ("options", reason.ToMarkup()))); + + return succeeded; + } +} diff --git a/Content.Shared/Customization/Systems/CharacterRequirements.Profile.cs b/Content.Shared/Customization/Systems/CharacterRequirements.Profile.cs index aaeae107b9..568a7f1b61 100644 --- a/Content.Shared/Customization/Systems/CharacterRequirements.Profile.cs +++ b/Content.Shared/Customization/Systems/CharacterRequirements.Profile.cs @@ -1,11 +1,15 @@ using System.Linq; using Content.Shared.Clothing.Loadouts.Prototypes; +using Content.Shared.Humanoid; using Content.Shared.Humanoid.Prototypes; using Content.Shared.Preferences; +using Content.Shared.Prototypes; using Content.Shared.Roles; using Content.Shared.Traits; using JetBrains.Annotations; using Robust.Shared.Configuration; +using Robust.Shared.Enums; +using Robust.Shared.Physics; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; using Robust.Shared.Utility; @@ -27,9 +31,9 @@ public sealed partial class CharacterAgeRequirement : CharacterRequirement public int Max; public override bool IsValid(JobPrototype job, HumanoidCharacterProfile profile, - Dictionary playTimes, bool whitelisted, + Dictionary playTimes, bool whitelisted, IPrototype prototype, IEntityManager entityManager, IPrototypeManager prototypeManager, IConfigurationManager configManager, - out FormattedMessage? reason) + out FormattedMessage? reason, int depth = 0) { reason = FormattedMessage.FromMarkup(Loc.GetString("character-age-requirement", ("inverted", Inverted), ("min", Min), ("max", Max))); @@ -38,46 +42,46 @@ public override bool IsValid(JobPrototype job, HumanoidCharacterProfile profile, } /// -/// Requires the profile to use either a Backpack, Satchel, or Duffelbag +/// Requires the profile to be a certain gender /// [UsedImplicitly] [Serializable, NetSerializable] -public sealed partial class CharacterBackpackTypeRequirement : CharacterRequirement +public sealed partial class CharacterGenderRequirement : CharacterRequirement { [DataField(required: true)] - public BackpackPreference Preference; + public Gender Gender; public override bool IsValid(JobPrototype job, HumanoidCharacterProfile profile, - Dictionary playTimes, bool whitelisted, + Dictionary playTimes, bool whitelisted, IPrototype prototype, IEntityManager entityManager, IPrototypeManager prototypeManager, IConfigurationManager configManager, - out FormattedMessage? reason) + out FormattedMessage? reason, int depth = 0) { - reason = FormattedMessage.FromMarkup(Loc.GetString("character-backpack-type-requirement", + reason = FormattedMessage.FromMarkup(Loc.GetString("character-gender-requirement", ("inverted", Inverted), - ("type", Loc.GetString($"humanoid-profile-editor-preference-{Preference.ToString().ToLower()}")))); - return profile.Backpack == Preference; + ("gender", Loc.GetString($"humanoid-profile-editor-pronouns-{Gender.ToString().ToLower()}-text")))); + return profile.Gender == Gender; } } /// -/// Requires the profile to use either Jumpsuits or Jumpskirts +/// Requires the profile to be a certain sex /// [UsedImplicitly] [Serializable, NetSerializable] -public sealed partial class CharacterClothingPreferenceRequirement : CharacterRequirement +public sealed partial class CharacterSexRequirement : CharacterRequirement { [DataField(required: true)] - public ClothingPreference Preference; + public Sex Sex; public override bool IsValid(JobPrototype job, HumanoidCharacterProfile profile, - Dictionary playTimes, bool whitelisted, + Dictionary playTimes, bool whitelisted, IPrototype prototype, IEntityManager entityManager, IPrototypeManager prototypeManager, IConfigurationManager configManager, - out FormattedMessage? reason) + out FormattedMessage? reason, int depth = 0) { - reason = FormattedMessage.FromMarkup(Loc.GetString("character-clothing-preference-requirement", + reason = FormattedMessage.FromMarkup(Loc.GetString("character-sex-requirement", ("inverted", Inverted), - ("preference", Loc.GetString($"humanoid-profile-editor-preference-{Preference.ToString().ToLower()}")))); - return profile.Clothing == Preference; + ("sex", Loc.GetString($"humanoid-profile-editor-sex-{Sex.ToString().ToLower()}-text")))); + return profile.Sex == Sex; } } @@ -92,9 +96,9 @@ public sealed partial class CharacterSpeciesRequirement : CharacterRequirement public List> Species; public override bool IsValid(JobPrototype job, HumanoidCharacterProfile profile, - Dictionary playTimes, bool whitelisted, + Dictionary playTimes, bool whitelisted, IPrototype prototype, IEntityManager entityManager, IPrototypeManager prototypeManager, IConfigurationManager configManager, - out FormattedMessage? reason) + out FormattedMessage? reason, int depth = 0) { const string color = "green"; reason = FormattedMessage.FromMarkup(Loc.GetString("character-species-requirement", @@ -106,6 +110,124 @@ public override bool IsValid(JobPrototype job, HumanoidCharacterProfile profile, } } +/// +/// Requires the profile to be within a certain height range +/// +[UsedImplicitly] +[Serializable, NetSerializable] +public sealed partial class CharacterHeightRequirement : CharacterRequirement +{ + /// + /// The minimum height of the profile in centimeters + /// + [DataField] + public float Min = int.MinValue; + + /// + /// The maximum height of the profile in centimeters + /// + [DataField] + public float Max = int.MaxValue; + + public override bool IsValid(JobPrototype job, HumanoidCharacterProfile profile, + Dictionary playTimes, bool whitelisted, IPrototype prototype, + IEntityManager entityManager, IPrototypeManager prototypeManager, IConfigurationManager configManager, + out FormattedMessage? reason, int depth = 0) + { + const string color = "yellow"; + var species = prototypeManager.Index(profile.Species); + + reason = FormattedMessage.FromMarkup(Loc.GetString("character-height-requirement", + ("inverted", Inverted), ("color", color), ("min", Min), ("max", Max))); + + var height = profile.Height * species.AverageHeight; + return height >= Min && height <= Max; + } +} + +/// +/// Requires the profile to be within a certain width range +/// +[UsedImplicitly] +[Serializable, NetSerializable] +public sealed partial class CharacterWidthRequirement : CharacterRequirement +{ + /// + /// The minimum width of the profile in centimeters + /// + [DataField] + public float Min = int.MinValue; + + /// + /// The maximum width of the profile in centimeters + /// + [DataField] + public float Max = int.MaxValue; + + public override bool IsValid(JobPrototype job, HumanoidCharacterProfile profile, + Dictionary playTimes, bool whitelisted, IPrototype prototype, + IEntityManager entityManager, IPrototypeManager prototypeManager, IConfigurationManager configManager, + out FormattedMessage? reason, int depth = 0) + { + const string color = "yellow"; + var species = prototypeManager.Index(profile.Species); + + reason = FormattedMessage.FromMarkup(Loc.GetString("character-width-requirement", + ("inverted", Inverted), ("color", color), ("min", Min), ("max", Max))); + + var width = profile.Width * species.AverageWidth; + return width >= Min && width <= Max; + } +} + +/// +/// Requires the profile to be within a certain weight range +/// +[UsedImplicitly] +[Serializable, NetSerializable] +public sealed partial class CharacterWeightRequirement : CharacterRequirement +{ + /// + /// Minimum weight of the profile in kilograms + /// + [DataField] + public float Min = int.MinValue; + + /// + /// Maximum weight of the profile in kilograms + /// + [DataField] + public float Max = int.MaxValue; + + public override bool IsValid(JobPrototype job, HumanoidCharacterProfile profile, + Dictionary playTimes, bool whitelisted, IPrototype prototype, + IEntityManager entityManager, IPrototypeManager prototypeManager, IConfigurationManager configManager, + out FormattedMessage? reason, int depth = 0) + { + const string color = "green"; + var species = prototypeManager.Index(profile.Species); + prototypeManager.Index(species.Prototype).TryGetComponent(out var fixture); + + if (fixture == null) + { + reason = null; + return false; + } + + var weight = MathF.Round( + MathF.PI * MathF.Pow( + fixture.Fixtures["fix1"].Shape.Radius + * ((profile.Width + profile.Height) / 2), 2) + * fixture.Fixtures["fix1"].Density); + + reason = FormattedMessage.FromMarkup(Loc.GetString("character-weight-requirement", + ("inverted", Inverted), ("color", color), ("min", Min), ("max", Max))); + + return weight >= Min && weight <= Max; + } +} + + /// /// Requires the profile to have one of the specified traits /// @@ -117,9 +239,9 @@ public sealed partial class CharacterTraitRequirement : CharacterRequirement public List> Traits; public override bool IsValid(JobPrototype job, HumanoidCharacterProfile profile, - Dictionary playTimes, bool whitelisted, + Dictionary playTimes, bool whitelisted, IPrototype prototype, IEntityManager entityManager, IPrototypeManager prototypeManager, IConfigurationManager configManager, - out FormattedMessage? reason) + out FormattedMessage? reason, int depth = 0) { const string color = "lightblue"; reason = FormattedMessage.FromMarkup(Loc.GetString("character-trait-requirement", @@ -142,9 +264,9 @@ public sealed partial class CharacterLoadoutRequirement : CharacterRequirement public List> Loadouts; public override bool IsValid(JobPrototype job, HumanoidCharacterProfile profile, - Dictionary playTimes, bool whitelisted, + Dictionary playTimes, bool whitelisted, IPrototype prototype, IEntityManager entityManager, IPrototypeManager prototypeManager, IConfigurationManager configManager, - out FormattedMessage? reason) + out FormattedMessage? reason, int depth = 0) { const string color = "lightblue"; reason = FormattedMessage.FromMarkup(Loc.GetString("character-loadout-requirement", @@ -155,3 +277,37 @@ public override bool IsValid(JobPrototype job, HumanoidCharacterProfile profile, return Loadouts.Any(l => profile.LoadoutPreferences.Contains(l.ToString())); } } + +/// +/// Requires the profile to not have any more than X of the specified traits, loadouts, etc, in a group +/// +[UsedImplicitly] +[Serializable, NetSerializable] +public sealed partial class CharacterItemGroupRequirement : CharacterRequirement +{ + [DataField(required: true)] + public ProtoId Group; + + public override bool IsValid(JobPrototype job, HumanoidCharacterProfile profile, + Dictionary playTimes, bool whitelisted, IPrototype prototype, + IEntityManager entityManager, IPrototypeManager prototypeManager, IConfigurationManager configManager, + out FormattedMessage? reason, int depth = 0) + { + var group = prototypeManager.Index(Group); + + // Get the count of items in the group that are in the profile + var items = group.Items.Select(item => item.TryGetValue(profile, prototypeManager, out _) ? item.ID : null).Where(id => id != null).ToList(); + var count = items.Count; + + // If prototype is selected, remove one from the count + if (items.ToList().Contains(prototype.ID)) + count--; + + reason = FormattedMessage.FromMarkup(Loc.GetString("character-item-group-requirement", + ("inverted", Inverted), + ("group", Loc.GetString($"character-item-group-{Group}")), + ("max", group.MaxItems))); + + return count < group.MaxItems; + } +} diff --git a/Content.Shared/Customization/Systems/CharacterRequirements.Whitelist.cs b/Content.Shared/Customization/Systems/CharacterRequirements.Whitelist.cs index 56465251cd..78a520ca8d 100644 --- a/Content.Shared/Customization/Systems/CharacterRequirements.Whitelist.cs +++ b/Content.Shared/Customization/Systems/CharacterRequirements.Whitelist.cs @@ -6,6 +6,7 @@ using Robust.Shared.Prototypes; using Robust.Shared.Serialization; using Robust.Shared.Utility; +using Robust.Shared.Network; namespace Content.Shared.Customization.Systems; @@ -18,9 +19,9 @@ namespace Content.Shared.Customization.Systems; public sealed partial class CharacterWhitelistRequirement : CharacterRequirement { public override bool IsValid(JobPrototype job, HumanoidCharacterProfile profile, - Dictionary playTimes, bool whitelisted, + Dictionary playTimes, bool whitelisted, IPrototype prototype, IEntityManager entityManager, IPrototypeManager prototypeManager, IConfigurationManager configManager, - out FormattedMessage? reason) + out FormattedMessage? reason, int depth = 0) { reason = FormattedMessage.FromMarkup(Loc.GetString("character-whitelist-requirement", ("inverted", Inverted))); return !configManager.GetCVar(CCVars.WhitelistEnabled) || whitelisted; diff --git a/Content.Shared/Customization/Systems/CharacterRequirements.cs b/Content.Shared/Customization/Systems/CharacterRequirements.cs index b347c9787a..2bf24700a6 100644 --- a/Content.Shared/Customization/Systems/CharacterRequirements.cs +++ b/Content.Shared/Customization/Systems/CharacterRequirements.cs @@ -8,7 +8,7 @@ namespace Content.Shared.Customization.Systems; - +// ReSharper disable InvalidXmlDocComment [ImplicitDataDefinitionForInheritors, MeansImplicitUse] [Serializable, NetSerializable] public abstract partial class CharacterRequirement @@ -31,9 +31,11 @@ public abstract bool IsValid( HumanoidCharacterProfile profile, Dictionary playTimes, bool whitelisted, + IPrototype prototype, IEntityManager entityManager, IPrototypeManager prototypeManager, IConfigurationManager configManager, - out FormattedMessage? reason + out FormattedMessage? reason, + int depth = 0 ); } diff --git a/Content.Shared/Customization/Systems/CharacterRequirementsSystem.cs b/Content.Shared/Customization/Systems/CharacterRequirementsSystem.cs index 521c4f186a..9becb640d1 100644 --- a/Content.Shared/Customization/Systems/CharacterRequirementsSystem.cs +++ b/Content.Shared/Customization/Systems/CharacterRequirementsSystem.cs @@ -1,4 +1,6 @@ +using System.Linq; using System.Text; +using Content.Shared.Inventory; using Content.Shared.Preferences; using Content.Shared.Roles; using Robust.Shared.Configuration; @@ -10,10 +12,28 @@ namespace Content.Shared.Customization.Systems; public sealed class CharacterRequirementsSystem : EntitySystem { + [Dependency] private readonly InventorySystem _inventory = default!; + + + public bool CheckRequirementValid(CharacterRequirement requirement, JobPrototype job, + HumanoidCharacterProfile profile, Dictionary playTimes, bool whitelisted, IPrototype prototype, + IEntityManager entityManager, IPrototypeManager prototypeManager, IConfigurationManager configManager, + out FormattedMessage? reason, int depth = 0) + { + // Return false if the requirement is invalid and not inverted + // If it's inverted return false when it's valid + return + !requirement.IsValid(job, profile, playTimes, whitelisted, prototype, + entityManager, prototypeManager, configManager, + out reason, depth) + ? requirement.Inverted + : !requirement.Inverted; + } + public bool CheckRequirementsValid(List requirements, JobPrototype job, - HumanoidCharacterProfile profile, Dictionary playTimes, bool whitelisted, + HumanoidCharacterProfile profile, Dictionary playTimes, bool whitelisted, IPrototype prototype, IEntityManager entityManager, IPrototypeManager prototypeManager, IConfigurationManager configManager, - out List reasons) + out List reasons, int depth = 0) { reasons = new List(); var valid = true; @@ -22,9 +42,9 @@ public bool CheckRequirementsValid(List requirements, JobP { // Set valid to false if the requirement is invalid and not inverted // If it's inverted set valid to false when it's valid - if (!requirement.IsValid(job, profile, playTimes, whitelisted, + if (!requirement.IsValid(job, profile, playTimes, whitelisted, prototype, entityManager, prototypeManager, configManager, - out var reason)) + out var reason, depth)) { if (valid) valid = requirement.Inverted; @@ -48,11 +68,7 @@ public bool CheckRequirementsValid(List requirements, JobP /// public FormattedMessage GetRequirementsText(List reasons) { - var text = new StringBuilder(); - foreach (var reason in reasons) - text.Append($"\n{reason.ToMarkup()}"); - - return FormattedMessage.FromMarkup(text.ToString().Trim()); + return FormattedMessage.FromMarkup(GetRequirementsMarkup(reasons)); } /// @@ -66,4 +82,16 @@ public string GetRequirementsMarkup(List reasons) return text.ToString().Trim(); } + + + /// + /// Returns true if the given dummy can equip the given item. + /// Does not care if items are already in equippable slots, and ignores pockets. + /// + public bool CanEntityWearItem(EntityUid dummy, EntityUid clothing) + { + return _inventory.TryGetSlots(dummy, out var slots) + && slots.Where(slot => !slot.SlotFlags.HasFlag(SlotFlags.POCKET)) + .Any(slot => _inventory.CanEquip(dummy, clothing, slot.Name, out _)); + } } diff --git a/Content.Shared/Damage/Components/StaminaComponent.cs b/Content.Shared/Damage/Components/StaminaComponent.cs index 5a2fba4970..b78fe97809 100644 --- a/Content.Shared/Damage/Components/StaminaComponent.cs +++ b/Content.Shared/Damage/Components/StaminaComponent.cs @@ -39,6 +39,13 @@ public sealed partial class StaminaComponent : Component [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField] public float CritThreshold = 100f; + /// + /// A dictionary of active stamina drains, with the key being the source of the drain, + /// DrainRate how much it changes per tick, and ModifiesSpeed if it should slow down the user. + /// + [DataField, AutoNetworkedField] + public Dictionary ActiveDrains = new(); + /// /// How long will this mob be stunned for? /// @@ -51,4 +58,16 @@ public sealed partial class StaminaComponent : Component [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoNetworkedField] [AutoPausedField] public TimeSpan NextUpdate = TimeSpan.Zero; -} + + /// + /// Minimum factor of the crit threshold that the mob must receive in stamina damage in order to start slowing down. + /// + [DataField, AutoNetworkedField] + public float SlowdownThresholdFactor = 0.5f; + + /// + /// Speed multiplier for entities that are slowed down due to low stamina. Multiplied by how close the mob is to stamcrit. + /// + [DataField, AutoNetworkedField] + public float SlowdownMultiplier = 0.75f; +} \ No newline at end of file diff --git a/Content.Shared/Damage/Prototypes/DamageGroupPrototype.cs b/Content.Shared/Damage/Prototypes/DamageGroupPrototype.cs index 807f143708..bb5aea3a38 100644 --- a/Content.Shared/Damage/Prototypes/DamageGroupPrototype.cs +++ b/Content.Shared/Damage/Prototypes/DamageGroupPrototype.cs @@ -17,6 +17,12 @@ public sealed partial class DamageGroupPrototype : IPrototype { [IdDataField] public string ID { get; } = default!; + [DataField(required: true)] + private LocId Name { get; set; } + + [ViewVariables(VVAccess.ReadOnly)] + public string LocalizedName => Loc.GetString(Name); + [DataField("damageTypes", required: true, customTypeSerializer: typeof(PrototypeIdListSerializer))] public List DamageTypes { get; private set; } = default!; } diff --git a/Content.Shared/Damage/Prototypes/DamageModifierSetPrototype.cs b/Content.Shared/Damage/Prototypes/DamageModifierSetPrototype.cs index 99e13ee284..a50856b044 100644 --- a/Content.Shared/Damage/Prototypes/DamageModifierSetPrototype.cs +++ b/Content.Shared/Damage/Prototypes/DamageModifierSetPrototype.cs @@ -10,7 +10,7 @@ namespace Content.Shared.Damage.Prototypes /// just want normal data to be deserialized. /// [Prototype("damageModifierSet")] - public sealed class DamageModifierSetPrototype : DamageModifierSet, IPrototype + public sealed partial class DamageModifierSetPrototype : DamageModifierSet, IPrototype { [ViewVariables] [IdDataField] diff --git a/Content.Shared/Damage/Prototypes/DamageTypePrototype.cs b/Content.Shared/Damage/Prototypes/DamageTypePrototype.cs index cde7a8617f..a1ae23ef67 100644 --- a/Content.Shared/Damage/Prototypes/DamageTypePrototype.cs +++ b/Content.Shared/Damage/Prototypes/DamageTypePrototype.cs @@ -11,6 +11,12 @@ public sealed partial class DamageTypePrototype : IPrototype [IdDataField] public string ID { get; private set; } = default!; + [DataField(required: true)] + private LocId Name { get; set; } + + [ViewVariables(VVAccess.ReadOnly)] + public string LocalizedName => Loc.GetString(Name); + /// /// The price for each 1% damage reduction in armors /// diff --git a/Content.Shared/Damage/Systems/DamageExamineSystem.cs b/Content.Shared/Damage/Systems/DamageExamineSystem.cs index 8273719110..fd1f191334 100644 --- a/Content.Shared/Damage/Systems/DamageExamineSystem.cs +++ b/Content.Shared/Damage/Systems/DamageExamineSystem.cs @@ -1,8 +1,10 @@ using Content.Shared.Damage.Components; using Content.Shared.Damage.Events; +using Content.Shared.Damage.Prototypes; using Content.Shared.Examine; using Content.Shared.FixedPoint; using Content.Shared.Verbs; +using Robust.Shared.Prototypes; using Robust.Shared.Utility; namespace Content.Shared.Damage.Systems; @@ -10,6 +12,7 @@ namespace Content.Shared.Damage.Systems; public sealed class DamageExamineSystem : EntitySystem { [Dependency] private readonly ExamineSystemShared _examine = default!; + [Dependency] private readonly IPrototypeManager _prototype = default!; public override void Initialize() { @@ -66,7 +69,7 @@ private FormattedMessage GetDamageExamine(DamageSpecifier damageSpecifier, strin if (damage.Value != FixedPoint2.Zero) { msg.PushNewline(); - msg.AddMarkup(Loc.GetString("damage-value", ("type", damage.Key), ("amount", damage.Value))); + msg.AddMarkup(Loc.GetString("damage-value", ("type", _prototype.Index(damage.Key).LocalizedName), ("amount", damage.Value))); } } diff --git a/Content.Shared/Damage/Systems/DamageableSystem.cs b/Content.Shared/Damage/Systems/DamageableSystem.cs index 4aaf380c47..4d08735d81 100644 --- a/Content.Shared/Damage/Systems/DamageableSystem.cs +++ b/Content.Shared/Damage/Systems/DamageableSystem.cs @@ -6,11 +6,14 @@ using Content.Shared.Mind.Components; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; +using Content.Shared.Body.Systems; using Content.Shared.Radiation.Events; using Content.Shared.Rejuvenate; +using Content.Shared.Targeting; using Robust.Shared.GameStates; using Robust.Shared.Network; using Robust.Shared.Prototypes; +using Robust.Shared.Random; using Robust.Shared.Utility; namespace Content.Shared.Damage @@ -22,10 +25,12 @@ public sealed class DamageableSystem : EntitySystem [Dependency] private readonly INetManager _netMan = default!; [Dependency] private readonly MobThresholdSystem _mobThreshold = default!; + [Dependency] private readonly SharedBodySystem _body = default!; + [Dependency] private readonly IRobustRandom _random = default!; private EntityQuery _appearanceQuery; private EntityQuery _damageableQuery; private EntityQuery _mindContainerQuery; - + private EntityQuery _targetingQuery; public override void Initialize() { SubscribeLocalEvent(DamageableInit); @@ -37,6 +42,7 @@ public override void Initialize() _appearanceQuery = GetEntityQuery(); _damageableQuery = GetEntityQuery(); _mindContainerQuery = GetEntityQuery(); + _targetingQuery = GetEntityQuery(); } /// @@ -98,7 +104,8 @@ public void SetDamage(EntityUid uid, DamageableComponent damageable, DamageSpeci /// The damage changed event is used by other systems, such as damage thresholds. /// public void DamageChanged(EntityUid uid, DamageableComponent component, DamageSpecifier? damageDelta = null, - bool interruptsDoAfters = true, EntityUid? origin = null) + bool interruptsDoAfters = true, EntityUid? origin = null, bool? canSever = null) + { component.Damage.GetDamagePerGroup(_prototypeManager, component.DamagePerGroup); component.TotalDamage = component.Damage.GetTotal(); @@ -109,7 +116,7 @@ public void DamageChanged(EntityUid uid, DamageableComponent component, DamageSp var data = new DamageVisualizerGroupData(component.DamagePerGroup.Keys.ToList()); _appearance.SetData(uid, DamageVisualizerKeys.DamageUpdateGroups, data, appearance); } - RaiseLocalEvent(uid, new DamageChangedEvent(component, damageDelta, interruptsDoAfters, origin)); + RaiseLocalEvent(uid, new DamageChangedEvent(component, damageDelta, interruptsDoAfters, origin, canSever ?? true)); } /// @@ -125,7 +132,8 @@ public void DamageChanged(EntityUid uid, DamageableComponent component, DamageSp /// null if the user had no applicable components that can take damage. /// public DamageSpecifier? TryChangeDamage(EntityUid? uid, DamageSpecifier damage, bool ignoreResistances = false, - bool interruptsDoAfters = true, DamageableComponent? damageable = null, EntityUid? origin = null) + bool interruptsDoAfters = true, DamageableComponent? damageable = null, EntityUid? origin = null, + bool? canSever = true, bool? canEvade = false, float? partMultiplier = 1.00f, TargetBodyPart? targetPart = null) { if (!uid.HasValue || !_damageableQuery.Resolve(uid.Value, ref damageable, false)) { @@ -138,10 +146,10 @@ public void DamageChanged(EntityUid uid, DamageableComponent component, DamageSp return damage; } - var before = new BeforeDamageChangedEvent(damage, origin); + var before = new BeforeDamageChangedEvent(damage, origin, targetPart, canSever ?? true, canEvade ?? false, partMultiplier ?? 1.00f); RaiseLocalEvent(uid.Value, ref before); - if (before.Cancelled) + if (before.Cancelled || before.Evaded) return null; // Apply resistances @@ -152,10 +160,10 @@ public void DamageChanged(EntityUid uid, DamageableComponent component, DamageSp { // TODO DAMAGE PERFORMANCE // use a local private field instead of creating a new dictionary here.. + // TODO: We need to add a check to see if the given armor covers the targeted part (if any) to modify or not. damage = DamageSpecifier.ApplyModifierSet(damage, modifierSet); } - - var ev = new DamageModifyEvent(damage, origin); + var ev = new DamageModifyEvent(damage, origin, targetPart); RaiseLocalEvent(uid.Value, ev); damage = ev.Damage; @@ -187,7 +195,7 @@ public void DamageChanged(EntityUid uid, DamageableComponent component, DamageSp } if (delta.DamageDict.Count > 0) - DamageChanged(uid.Value, damageable, delta, interruptsDoAfters, origin); + DamageChanged(uid.Value, damageable, delta, interruptsDoAfters, origin, canSever); return delta; } @@ -257,6 +265,11 @@ private void OnRejuvenate(EntityUid uid, DamageableComponent component, Rejuvena TryComp(uid, out var thresholds); _mobThreshold.SetAllowRevives(uid, true, thresholds); // do this so that the state changes when we set the damage SetAllDamage(uid, component, 0); + // Shitmed Start + if (HasComp(uid)) + foreach (var part in _body.GetBodyChildren(uid)) + RaiseLocalEvent(part.Id, new RejuvenateEvent()); + // Shitmed End _mobThreshold.SetAllowRevives(uid, false, thresholds); } @@ -286,7 +299,15 @@ private void DamageableHandleState(EntityUid uid, DamageableComponent component, /// Raised before damage is done, so stuff can cancel it if necessary. /// [ByRefEvent] - public record struct BeforeDamageChangedEvent(DamageSpecifier Damage, EntityUid? Origin = null, bool Cancelled = false); + public record struct BeforeDamageChangedEvent( + DamageSpecifier Damage, + EntityUid? Origin = null, + TargetBodyPart? TargetPart = null, + bool CanSever = true, + bool CanEvade = false, + float PartMultiplier = 1.00f, + bool Evaded = false, + bool Cancelled = false); /// /// Raised on an entity when damage is about to be dealt, @@ -299,16 +320,17 @@ public sealed class DamageModifyEvent : EntityEventArgs, IInventoryRelayEvent { // Whenever locational damage is a thing, this should just check only that bit of armour. public SlotFlags TargetSlots { get; } = ~SlotFlags.POCKET; - public readonly DamageSpecifier OriginalDamage; public DamageSpecifier Damage; public EntityUid? Origin; + public readonly TargetBodyPart? TargetPart; - public DamageModifyEvent(DamageSpecifier damage, EntityUid? origin = null) + public DamageModifyEvent(DamageSpecifier damage, EntityUid? origin = null, TargetBodyPart? targetPart = null) { OriginalDamage = damage; Damage = damage; Origin = origin; + TargetPart = targetPart; } } @@ -347,11 +369,17 @@ public sealed class DamageChangedEvent : EntityEventArgs /// public readonly EntityUid? Origin; - public DamageChangedEvent(DamageableComponent damageable, DamageSpecifier? damageDelta, bool interruptsDoAfters, EntityUid? origin) + /// + /// Can this damage event sever parts? + /// + public readonly bool CanSever; + + public DamageChangedEvent(DamageableComponent damageable, DamageSpecifier? damageDelta, bool interruptsDoAfters, EntityUid? origin, bool canSever = true) { Damageable = damageable; DamageDelta = damageDelta; Origin = origin; + CanSever = canSever; if (DamageDelta == null) return; diff --git a/Content.Shared/Damage/Systems/StaminaSystem.Slowdown.cs b/Content.Shared/Damage/Systems/StaminaSystem.Slowdown.cs new file mode 100644 index 0000000000..cab693dc24 --- /dev/null +++ b/Content.Shared/Damage/Systems/StaminaSystem.Slowdown.cs @@ -0,0 +1,23 @@ +using Content.Shared.Damage.Components; +using Content.Shared.Movement.Systems; + +namespace Content.Shared.Damage.Systems; + +public sealed partial class StaminaSystem +{ + private void InitializeSlowdown() + { + SubscribeLocalEvent(OnRefreshModifiers); + } + + private void OnRefreshModifiers(Entity ent, ref RefreshMovementSpeedModifiersEvent args) + { + var damage = ent.Comp.StaminaDamage; + var threshold = ent.Comp.SlowdownThresholdFactor * ent.Comp.CritThreshold; + if (damage < threshold) + return; + + var factor = ent.Comp.SlowdownMultiplier * damage / ent.Comp.CritThreshold; + args.ModifySpeed(factor, factor); + } +} diff --git a/Content.Shared/Damage/Systems/StaminaSystem.cs b/Content.Shared/Damage/Systems/StaminaSystem.cs index 54a88205b2..e4840a6630 100644 --- a/Content.Shared/Damage/Systems/StaminaSystem.cs +++ b/Content.Shared/Damage/Systems/StaminaSystem.cs @@ -7,6 +7,7 @@ using Content.Shared.Database; using Content.Shared.Effects; using Content.Shared.IdentityManagement; +using Content.Shared.Movement.Systems; using Content.Shared.Popups; using Content.Shared.Projectiles; using Content.Shared.Rejuvenate; @@ -36,6 +37,7 @@ public sealed partial class StaminaSystem : EntitySystem [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly SharedStunSystem _stunSystem = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly MovementSpeedModifierSystem _movementSpeed = default!; /// /// How much of a buffer is there between the stun duration and when stuns can be re-applied. @@ -47,6 +49,7 @@ public override void Initialize() base.Initialize(); InitializeModifier(); + InitializeSlowdown(); SubscribeLocalEvent(OnStartup); SubscribeLocalEvent(OnShutdown); @@ -116,14 +119,11 @@ private void OnRejuvenate(EntityUid uid, StaminaComponent component, RejuvenateE private void OnDisarmed(EntityUid uid, StaminaComponent component, DisarmedEvent args) { - if (args.Handled || !_random.Prob(args.PushProbability)) + // Note: we do not run _random.Prob here because SharedWeaponSystem already runs it. + if (args.Handled || component.Critical) return; - if (component.Critical) - return; - - var damage = args.PushProbability * component.CritThreshold; - TakeStaminaDamage(uid, damage, component, source: args.Source); + TakeStaminaDamage(uid, args.StaminaDamage, component, source: args.Source); // We need a better method of getting if the entity is going to resist stam damage, both this and the lines in the foreach at the end of OnHit() are awful if (!component.Critical) @@ -258,7 +258,7 @@ public bool TryTakeStamina(EntityUid uid, float value, StaminaComponent? compone } public void TakeStaminaDamage(EntityUid uid, float value, StaminaComponent? component = null, - EntityUid? source = null, EntityUid? with = null, bool visual = true, SoundSpecifier? sound = null) + EntityUid? source = null, EntityUid? with = null, bool visual = true, SoundSpecifier? sound = null, bool? allowsSlowdown = true) { if (!Resolve(uid, ref component, false) || value == 0) @@ -284,16 +284,8 @@ public void TakeStaminaDamage(EntityUid uid, float value, StaminaComponent? comp if (component.NextUpdate < nextUpdate) component.NextUpdate = nextUpdate; } - - var slowdownThreshold = component.CritThreshold / 2f; - - // If we go above n% then apply slowdown - if (oldDamage < slowdownThreshold && - component.StaminaDamage > slowdownThreshold) - { - _stunSystem.TrySlowdown(uid, TimeSpan.FromSeconds(3), true, 0.8f, 0.8f); - } - + if (allowsSlowdown == true) + _movementSpeed.RefreshMovementSpeedModifiers(uid); SetStaminaAlert(uid, component); if (!component.Critical) @@ -336,27 +328,51 @@ public void TakeStaminaDamage(EntityUid uid, float value, StaminaComponent? comp } } + public void ToggleStaminaDrain(EntityUid target, float drainRate, bool enabled, bool modifiesSpeed, EntityUid? source = null) + { + if (!TryComp(target, out var stamina)) + return; + + // If theres no source, we assume its the target that caused the drain. + var actualSource = source ?? target; + + if (enabled) + { + stamina.ActiveDrains[actualSource] = (drainRate, modifiesSpeed); + EnsureComp(target); + } + else + stamina.ActiveDrains.Remove(actualSource); + + Dirty(target, stamina); + } + public override void Update(float frameTime) { base.Update(frameTime); - if (!_timing.IsFirstTimePredicted) return; var stamQuery = GetEntityQuery(); var query = EntityQueryEnumerator(); var curTime = _timing.CurTime; - while (query.MoveNext(out var uid, out _)) { // Just in case we have active but not stamina we'll check and account for it. if (!stamQuery.TryGetComponent(uid, out var comp) || - comp.StaminaDamage <= 0f && !comp.Critical) + comp.StaminaDamage <= 0f && !comp.Critical && comp.ActiveDrains.Count == 0) { RemComp(uid); continue; } - + if (comp.ActiveDrains.Count > 0) + foreach (var (source, (drainRate, modifiesSpeed)) in comp.ActiveDrains) + TakeStaminaDamage(uid, + drainRate * frameTime, + comp, + source: source, + visual: false, + allowsSlowdown: modifiesSpeed); // Shouldn't need to consider paused time as we're only iterating non-paused stamina components. var nextUpdate = comp.NextUpdate; @@ -371,18 +387,18 @@ public override void Update(float frameTime) } comp.NextUpdate += TimeSpan.FromSeconds(1f); - TakeStaminaDamage(uid, -comp.Decay, comp); - Dirty(comp); + // If theres no active drains, recover stamina. + if (comp.ActiveDrains.Count == 0) + TakeStaminaDamage(uid, -comp.Decay, comp); + + Dirty(uid, comp); } } private void EnterStamCrit(EntityUid uid, StaminaComponent? component = null) { - if (!Resolve(uid, ref component) || - component.Critical) - { + if (!Resolve(uid, ref component) || component.Critical) return; - } // To make the difference between a stun and a stamcrit clear // TODO: Mask? @@ -391,7 +407,6 @@ private void EnterStamCrit(EntityUid uid, StaminaComponent? component = null) component.StaminaDamage = component.CritThreshold; _stunSystem.TryParalyze(uid, component.StunTime, true); - // Give them buffer before being able to be re-stunned component.NextUpdate = _timing.CurTime + component.StunTime + StamCritBufferTime; EnsureComp(uid); @@ -401,17 +416,14 @@ private void EnterStamCrit(EntityUid uid, StaminaComponent? component = null) private void ExitStamCrit(EntityUid uid, StaminaComponent? component = null) { - if (!Resolve(uid, ref component) || - !component.Critical) - { + if (!Resolve(uid, ref component) || !component.Critical) return; - } component.Critical = false; - component.StaminaDamage = 0f; + component.StaminaDamage = component.CritThreshold - float.Epsilon; // Yea, standing up after fainting from fatigue... not exactly easy component.NextUpdate = _timing.CurTime; + _movementSpeed.RefreshMovementSpeedModifiers(uid); SetStaminaAlert(uid, component); - RemComp(uid); Dirty(component); _adminLogger.Add(LogType.Stamina, LogImpact.Low, $"{ToPrettyString(uid):user} recovered from stamina crit"); } @@ -421,4 +433,4 @@ private void ExitStamCrit(EntityUid uid, StaminaComponent? component = null) /// Raised before stamina damage is dealt to allow other systems to cancel it. /// [ByRefEvent] -public record struct BeforeStaminaDamageEvent(float Value, bool Cancelled = false); +public record struct BeforeStaminaDamageEvent(float Value, bool Cancelled = false); \ No newline at end of file diff --git a/Content.Shared/Decals/DecalGridComponent.cs b/Content.Shared/Decals/DecalGridComponent.cs index 8ac05cb280..67a9c03769 100644 --- a/Content.Shared/Decals/DecalGridComponent.cs +++ b/Content.Shared/Decals/DecalGridComponent.cs @@ -62,46 +62,37 @@ public record DecalGridChunkCollection(Dictionary ChunkCol } [Serializable, NetSerializable] - public sealed class DecalGridState : ComponentState, IComponentDeltaState + public sealed class DecalGridState(Dictionary chunks) : ComponentState { - public Dictionary Chunks; - public bool FullState => AllChunks == null; - - // required to infer deleted/missing chunks for delta states - public HashSet? AllChunks; + public Dictionary Chunks = chunks; + } - public DecalGridState(Dictionary chunks) - { - Chunks = chunks; - } + [Serializable, NetSerializable] + public sealed class DecalGridDeltaState(Dictionary modifiedChunks, HashSet allChunks) + : ComponentState, IComponentDeltaState + { + public Dictionary ModifiedChunks = modifiedChunks; + public HashSet AllChunks = allChunks; - public void ApplyToFullState(IComponentState fullState) + public void ApplyToFullState(DecalGridState state) { - DebugTools.Assert(!FullState); - var state = (DecalGridState) fullState; - DebugTools.Assert(state.FullState); - foreach (var key in state.Chunks.Keys) { if (!AllChunks!.Contains(key)) state.Chunks.Remove(key); } - foreach (var (chunk, data) in Chunks) + foreach (var (chunk, data) in ModifiedChunks) { state.Chunks[chunk] = new(data); } } - public IComponentState CreateNewFullState(IComponentState fullState) + public DecalGridState CreateNewFullState(DecalGridState state) { - DebugTools.Assert(!FullState); - var state = (DecalGridState) fullState; - DebugTools.Assert(state.FullState); - var chunks = new Dictionary(state.Chunks.Count); - foreach (var (chunk, data) in Chunks) + foreach (var (chunk, data) in ModifiedChunks) { chunks[chunk] = new(data); } diff --git a/Content.Shared/Decals/SharedDecalSystem.cs b/Content.Shared/Decals/SharedDecalSystem.cs index 76fa9d64db..9dbef60f0d 100644 --- a/Content.Shared/Decals/SharedDecalSystem.cs +++ b/Content.Shared/Decals/SharedDecalSystem.cs @@ -49,7 +49,7 @@ private void OnGetState(EntityUid uid, DecalGridComponent component, ref Compone data[index] = chunk; } - args.State = new DecalGridState(data) { AllChunks = new(component.ChunkCollection.ChunkCollection.Keys) }; + args.State = new DecalGridDeltaState(data, new(component.ChunkCollection.ChunkCollection.Keys)); } private void OnGridInitialize(GridInitializeEvent msg) diff --git a/Content.Shared/DeltaV/Abilities/UltraVisionComponent.cs b/Content.Shared/DeltaV/Abilities/UltraVisionComponent.cs deleted file mode 100644 index 5f631c54f2..0000000000 --- a/Content.Shared/DeltaV/Abilities/UltraVisionComponent.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Robust.Shared.GameStates; -namespace Content.Shared.Abilities; - -[RegisterComponent] -[NetworkedComponent] - -public sealed partial class UltraVisionComponent : Component -{} diff --git a/Content.Shared/DeltaV/CCVars/DCCVars.cs b/Content.Shared/DeltaV/CCVars/DCCVars.cs index 2028397ffe..89a14275be 100644 --- a/Content.Shared/DeltaV/CCVars/DCCVars.cs +++ b/Content.Shared/DeltaV/CCVars/DCCVars.cs @@ -15,10 +15,4 @@ public sealed class DCCVars /// public static readonly CVarDef RoundEndPacifist = CVarDef.Create("game.round_end_pacifist", false, CVar.SERVERONLY); - - /// - /// Disables all vision filters for species like Vulpkanin or Harpies. There are good reasons someone might want to disable these. - /// - public static readonly CVarDef NoVisionFilters = - CVarDef.Create("accessibility.no_vision_filters", false, CVar.CLIENTONLY | CVar.ARCHIVE); } diff --git a/Content.Shared/DeltaV/CartridgeLoader/Cartridges/MailMetricUiState.cs b/Content.Shared/DeltaV/CartridgeLoader/Cartridges/MailMetricUiState.cs new file mode 100644 index 0000000000..69506f5d0c --- /dev/null +++ b/Content.Shared/DeltaV/CartridgeLoader/Cartridges/MailMetricUiState.cs @@ -0,0 +1,50 @@ +using Content.Shared.Security; +using Robust.Shared.Serialization; + +namespace Content.Shared.CartridgeLoader.Cartridges; + +[Serializable, NetSerializable] +public sealed class MailMetricUiState : BoundUserInterfaceState +{ + public readonly MailStats Metrics; + public int UnopenedMailCount { get; } + public int TotalMail { get; } + public double SuccessRate { get; } + + public MailMetricUiState(MailStats metrics, int unopenedMailCount) + { + Metrics = metrics; + UnopenedMailCount = unopenedMailCount; + TotalMail = metrics.TotalMail(unopenedMailCount); + SuccessRate = metrics.SuccessRate(unopenedMailCount); + } +} + +[DataDefinition] +[Serializable, NetSerializable] +public partial record struct MailStats +{ + public int Earnings { get; init; } + public int DamagedLosses { get; init; } + public int ExpiredLosses { get; init; } + public int TamperedLosses { get; init; } + public int OpenedCount { get; init; } + public int DamagedCount { get; init; } + public int ExpiredCount { get; init; } + public int TamperedCount { get; init; } + + public readonly int TotalMail(int unopenedCount) + { + return OpenedCount + unopenedCount; + } + + public readonly int TotalIncome => Earnings + DamagedLosses + ExpiredLosses + TamperedLosses; + + public readonly double SuccessRate(int unopenedCount) + { + var totalMail = TotalMail(unopenedCount); + return (totalMail > 0) + ? Math.Round((double)OpenedCount / totalMail * 100, 2) + : 0; + } +} \ No newline at end of file diff --git a/Content.Shared/DeltaV/GlimmerWisp/Events.cs b/Content.Shared/DeltaV/GlimmerWisp/Events.cs new file mode 100644 index 0000000000..10d985193b --- /dev/null +++ b/Content.Shared/DeltaV/GlimmerWisp/Events.cs @@ -0,0 +1,5 @@ +using Content.Shared.DoAfter; +using Robust.Shared.Serialization; + +[NetSerializable, Serializable] +public sealed partial class LifeDrainDoAfterEvent : SimpleDoAfterEvent; diff --git a/Content.Shared/DeviceLinking/DevicePortPrototype.cs b/Content.Shared/DeviceLinking/DevicePortPrototype.cs index c0a419ee65..e3421dda9d 100644 --- a/Content.Shared/DeviceLinking/DevicePortPrototype.cs +++ b/Content.Shared/DeviceLinking/DevicePortPrototype.cs @@ -29,13 +29,13 @@ public abstract class DevicePortPrototype [Prototype("sinkPort")] [Serializable, NetSerializable] -public sealed class SinkPortPrototype : DevicePortPrototype, IPrototype +public sealed partial class SinkPortPrototype : DevicePortPrototype, IPrototype { } [Prototype("sourcePort")] [Serializable, NetSerializable] -public sealed class SourcePortPrototype : DevicePortPrototype, IPrototype +public sealed partial class SourcePortPrototype : DevicePortPrototype, IPrototype { /// /// This is a set of sink ports that this source port will attempt to link to when using the diff --git a/Content.Shared/DeviceNetwork/Systems/SharedNetworkConfiguratorSystem.cs b/Content.Shared/DeviceNetwork/Systems/SharedNetworkConfiguratorSystem.cs index 4aa9db199d..53928f83a9 100644 --- a/Content.Shared/DeviceNetwork/Systems/SharedNetworkConfiguratorSystem.cs +++ b/Content.Shared/DeviceNetwork/Systems/SharedNetworkConfiguratorSystem.cs @@ -1,10 +1,23 @@ using Content.Shared.Actions; +using Content.Shared.DeviceNetwork.Components; +using Content.Shared.UserInterface; using Robust.Shared.Serialization; namespace Content.Shared.DeviceNetwork.Systems; public abstract class SharedNetworkConfiguratorSystem : EntitySystem { + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnUiOpenAttempt); + } + + private void OnUiOpenAttempt(EntityUid uid, NetworkConfiguratorComponent configurator, ActivatableUIOpenAttemptEvent args) + { + if (configurator.LinkModeActive) + args.Cancel(); + } } public sealed partial class ClearAllOverlaysEvent : InstantActionEvent diff --git a/Content.Shared/Disposal/Components/SharedDisposalUnitComponent.cs b/Content.Shared/Disposal/Components/SharedDisposalUnitComponent.cs index 4948cb6640..36dd14f9b2 100644 --- a/Content.Shared/Disposal/Components/SharedDisposalUnitComponent.cs +++ b/Content.Shared/Disposal/Components/SharedDisposalUnitComponent.cs @@ -36,13 +36,6 @@ public abstract partial class SharedDisposalUnitComponent : Component [ViewVariables(VVAccess.ReadWrite), DataField("soundInsert")] public SoundSpecifier? InsertSound = new SoundPathSpecifier("/Audio/Effects/trashbag1.ogg"); - /// - /// Sound played when an item is thrown and misses the disposal unit. - /// - [ViewVariables(VVAccess.ReadWrite), DataField("soundMiss")] - public SoundSpecifier? MissSound = new SoundPathSpecifier("/Audio/Effects/thudswoosh.ogg"); - - /// /// State for this disposals unit. /// diff --git a/Content.Shared/DoAfter/DoAfterArgs.cs b/Content.Shared/DoAfter/DoAfterArgs.cs index d2729ad3c6..73334deddb 100644 --- a/Content.Shared/DoAfter/DoAfterArgs.cs +++ b/Content.Shared/DoAfter/DoAfterArgs.cs @@ -211,7 +211,11 @@ public DoAfterArgs( NetUsed = entManager.GetNetEntity(Used); } - private DoAfterArgs() + /// + /// An empty do-after constructor. This WILL cause runtime errors if used to create a do-after. Only use this if you really know what you're doing! + /// + [Obsolete("Use the other constructors if possible.")] + public DoAfterArgs() { } diff --git a/Content.Shared/DoAfter/DoAfterEvent.cs b/Content.Shared/DoAfter/DoAfterEvent.cs index c01505f9b2..bc9abdab87 100644 --- a/Content.Shared/DoAfter/DoAfterEvent.cs +++ b/Content.Shared/DoAfter/DoAfterEvent.cs @@ -73,7 +73,7 @@ public sealed partial class DoAfterAttemptEvent : CancellableEntityEvent public readonly DoAfter DoAfter; /// - /// The event that the DoAfter will raise after sucesfully finishing. Given that this event has the data + /// The event that the DoAfter will raise after successfully finishing. Given that this event has the data /// required to perform the interaction, it should also contain the data required to validate/attempt the /// interaction. /// diff --git a/Content.Shared/DoAfter/SharedDoAfterSystem.Update.cs b/Content.Shared/DoAfter/SharedDoAfterSystem.Update.cs index abd8888f58..24b3841759 100644 --- a/Content.Shared/DoAfter/SharedDoAfterSystem.Update.cs +++ b/Content.Shared/DoAfter/SharedDoAfterSystem.Update.cs @@ -101,6 +101,7 @@ private bool TryAttemptEvent(DoAfter doAfter) doAfter.AttemptEvent = _factory.CreateInstance(evType, new object[] { doAfter, args.Event }); } + args.Event.DoAfter = doAfter; if (args.EventTarget != null) RaiseLocalEvent(args.EventTarget.Value, doAfter.AttemptEvent, args.Broadcast); else diff --git a/Content.Shared/DoAfter/SharedDoAfterSystem.cs b/Content.Shared/DoAfter/SharedDoAfterSystem.cs index 3b0ba58f55..81c8c4f382 100644 --- a/Content.Shared/DoAfter/SharedDoAfterSystem.cs +++ b/Content.Shared/DoAfter/SharedDoAfterSystem.cs @@ -247,8 +247,9 @@ public bool TryStartDoAfter(DoAfterArgs args, [NotNullWhen(true)] out DoAfterId? if (args.AttemptFrequency == AttemptFrequency.StartAndEnd && !TryAttemptEvent(doAfter)) return false; - if (args.Delay <= TimeSpan.Zero || - _tag.HasTag(args.User, "InstantDoAfters")) + // TODO DO AFTER + // Why does this tag exist? Just make this a bool on the component? + if (args.Delay <= TimeSpan.Zero || _tag.HasTag(args.User, "InstantDoAfters")) { RaiseDoAfterEvents(doAfter, comp); // We don't store instant do-afters. This is just a lazy way of hiding them from client-side visuals. diff --git a/Content.Shared/Dragon/SharedDragonRiftComponent.cs b/Content.Shared/Dragon/SharedDragonRiftComponent.cs index 0d2bf44018..8377dbfee7 100644 --- a/Content.Shared/Dragon/SharedDragonRiftComponent.cs +++ b/Content.Shared/Dragon/SharedDragonRiftComponent.cs @@ -1,9 +1,10 @@ using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization; namespace Content.Shared.Dragon; -[NetworkedComponent] +[NetworkedComponent, EntityCategory("Spawner")] public abstract partial class SharedDragonRiftComponent : Component { [DataField("state")] diff --git a/Content.Shared/Electrocution/ElectrocutionEvents.cs b/Content.Shared/Electrocution/ElectrocutionEvents.cs index fe5753c7fb..1f7121f855 100644 --- a/Content.Shared/Electrocution/ElectrocutionEvents.cs +++ b/Content.Shared/Electrocution/ElectrocutionEvents.cs @@ -24,12 +24,14 @@ public sealed class ElectrocutedEvent : EntityEventArgs public readonly EntityUid TargetUid; public readonly EntityUid? SourceUid; public readonly float SiemensCoefficient; + public readonly float? ShockDamage = null; - public ElectrocutedEvent(EntityUid targetUid, EntityUid? sourceUid, float siemensCoefficient) + public ElectrocutedEvent(EntityUid targetUid, EntityUid? sourceUid, float siemensCoefficient, float shockDamage) { TargetUid = targetUid; SourceUid = sourceUid; SiemensCoefficient = siemensCoefficient; + ShockDamage = shockDamage; } } } diff --git a/Content.Shared/Ensnaring/Components/EnsnaringComponent.cs b/Content.Shared/Ensnaring/Components/EnsnaringComponent.cs index 6e1f3077f3..fe415e0f4a 100644 --- a/Content.Shared/Ensnaring/Components/EnsnaringComponent.cs +++ b/Content.Shared/Ensnaring/Components/EnsnaringComponent.cs @@ -11,59 +11,57 @@ public sealed partial class EnsnaringComponent : Component /// /// How long it should take to free someone else. /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("freeTime")] + [DataField] public float FreeTime = 3.5f; /// /// How long it should take for an entity to free themselves. /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("breakoutTime")] + [DataField] public float BreakoutTime = 30.0f; /// /// How much should this slow down the entities walk? /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("walkSpeed")] + [DataField] public float WalkSpeed = 0.9f; /// /// How much should this slow down the entities sprint? /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("sprintSpeed")] + [DataField] public float SprintSpeed = 0.9f; /// /// How much stamina does the ensnare sap /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("staminaDamage")] + [DataField] public float StaminaDamage = 55f; /// /// Should this ensnare someone when thrown? /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("canThrowTrigger")] + [DataField] public bool CanThrowTrigger; /// /// What is ensnared? /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("ensnared")] + [DataField] public EntityUid? Ensnared; /// /// Should breaking out be possible when moving? /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("canMoveBreakout")] + [DataField] public bool CanMoveBreakout; + /// + /// Should the ensaring entity be deleted upon removal? + /// + [DataField] + public bool DestroyOnRemove = false; + } /// diff --git a/Content.Shared/Explosion/Components/ExplosionVisualsComponent.cs b/Content.Shared/Explosion/Components/ExplosionVisualsComponent.cs index 7477b6c26e..88e81c93c5 100644 --- a/Content.Shared/Explosion/Components/ExplosionVisualsComponent.cs +++ b/Content.Shared/Explosion/Components/ExplosionVisualsComponent.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Robust.Shared.GameStates; using Robust.Shared.Map; using Robust.Shared.Serialization; @@ -15,7 +16,7 @@ public sealed partial class ExplosionVisualsComponent : Component public Dictionary>> Tiles = new(); public List Intensity = new(); public string ExplosionType = string.Empty; - public Matrix3 SpaceMatrix; + public Matrix3x2 SpaceMatrix; public ushort SpaceTileSize; } @@ -27,7 +28,7 @@ public sealed class ExplosionVisualsState : ComponentState public Dictionary>> Tiles; public List Intensity; public string ExplosionType = string.Empty; - public Matrix3 SpaceMatrix; + public Matrix3x2 SpaceMatrix; public ushort SpaceTileSize; public ExplosionVisualsState( @@ -36,7 +37,7 @@ public ExplosionVisualsState( List intensity, Dictionary>? spaceTiles, Dictionary>> tiles, - Matrix3 spaceMatrix, + Matrix3x2 spaceMatrix, ushort spaceTileSize) { Epicenter = epicenter; diff --git a/Content.Shared/Extinguisher/SharedFireExtinguisherComponent.cs b/Content.Shared/Extinguisher/SharedFireExtinguisherComponent.cs index 53d58f1d80..dbb1f6f2c4 100644 --- a/Content.Shared/Extinguisher/SharedFireExtinguisherComponent.cs +++ b/Content.Shared/Extinguisher/SharedFireExtinguisherComponent.cs @@ -1,8 +1,10 @@ using Robust.Shared.Audio; +using Robust.Shared.GameStates; using Robust.Shared.Serialization; namespace Content.Shared.Extinguisher; +[NetworkedComponent] public abstract partial class SharedFireExtinguisherComponent : Component { [DataField("refillSound")] public SoundSpecifier RefillSound = new SoundPathSpecifier("/Audio/Effects/refill.ogg"); diff --git a/Content.Shared/Eye/Blinding/Components/BlindableComponent.cs b/Content.Shared/Eye/Blinding/Components/BlindableComponent.cs index 4379d309bc..c2e73bd6b8 100644 --- a/Content.Shared/Eye/Blinding/Components/BlindableComponent.cs +++ b/Content.Shared/Eye/Blinding/Components/BlindableComponent.cs @@ -24,7 +24,11 @@ public sealed partial class BlindableComponent : Component [ViewVariables(VVAccess.ReadWrite), DataField("EyeDamage"), AutoNetworkedField] public int EyeDamage = 0; - public const int MaxDamage = 9; + [ViewVariables(VVAccess.ReadOnly), DataField] + public int MaxDamage = 9; + + [ViewVariables(VVAccess.ReadOnly), DataField] + public int MinDamage = 0; /// /// Used to ensure that this doesn't break with sandbox or admin tools. diff --git a/Content.Shared/Eye/Blinding/Systems/BlindableSystem.cs b/Content.Shared/Eye/Blinding/Systems/BlindableSystem.cs index 5f24ff2018..24eed3adcf 100644 --- a/Content.Shared/Eye/Blinding/Systems/BlindableSystem.cs +++ b/Content.Shared/Eye/Blinding/Systems/BlindableSystem.cs @@ -37,7 +37,7 @@ public void UpdateIsBlind(Entity blindable) var old = blindable.Comp.IsBlind; // Don't bother raising an event if the eye is too damaged. - if (blindable.Comp.EyeDamage >= BlindableComponent.MaxDamage) + if (blindable.Comp.EyeDamage >= blindable.Comp.MaxDamage) { blindable.Comp.IsBlind = true; } @@ -62,13 +62,31 @@ public void AdjustEyeDamage(Entity blindable, int amount) return; blindable.Comp.EyeDamage += amount; - blindable.Comp.EyeDamage = Math.Clamp(blindable.Comp.EyeDamage, 0, BlindableComponent.MaxDamage); + UpdateEyeDamage(blindable, true); + } + private void UpdateEyeDamage(Entity blindable, bool isDamageChanged) + { + if (!Resolve(blindable, ref blindable.Comp, false)) + return; + + var previousDamage = blindable.Comp.EyeDamage; + blindable.Comp.EyeDamage = Math.Clamp(blindable.Comp.EyeDamage, blindable.Comp.MinDamage, blindable.Comp.MaxDamage); Dirty(blindable); - UpdateIsBlind(blindable); + if (!isDamageChanged && previousDamage == blindable.Comp.EyeDamage) + return; + UpdateIsBlind(blindable); var ev = new EyeDamageChangedEvent(blindable.Comp.EyeDamage); RaiseLocalEvent(blindable.Owner, ref ev); } + public void SetMinDamage(Entity blindable, int amount) + { + if (!Resolve(blindable, ref blindable.Comp, false)) + return; + + blindable.Comp.MinDamage = amount; + UpdateEyeDamage(blindable, false); + } } /// diff --git a/Content.Shared/Eye/Blinding/Systems/EyeClosingSystem.cs b/Content.Shared/Eye/Blinding/Systems/EyeClosingSystem.cs index 5326af9636..9a4afaec3a 100644 --- a/Content.Shared/Eye/Blinding/Systems/EyeClosingSystem.cs +++ b/Content.Shared/Eye/Blinding/Systems/EyeClosingSystem.cs @@ -1,4 +1,3 @@ - using Content.Shared.Actions; using Content.Shared.Eye.Blinding.Components; using Robust.Shared.Audio.Systems; @@ -124,7 +123,7 @@ public void UpdateEyesClosable(Entity blindable) if (_entityManager.TryGetComponent(blindable, out var eyelids) && !eyelids.NaturallyCreated) return; - if (ev.Blur < BlurryVisionComponent.MaxMagnitude || ev.Blur >= BlindableComponent.MaxDamage) + if (ev.Blur < BlurryVisionComponent.MaxMagnitude || ev.Blur >= blindable.Comp.MaxDamage) { RemCompDeferred(blindable); return; diff --git a/Content.Shared/Eye/VisibilityFlags.cs b/Content.Shared/Eye/VisibilityFlags.cs index 7e2dd33d7d..2e20b1de4f 100644 --- a/Content.Shared/Eye/VisibilityFlags.cs +++ b/Content.Shared/Eye/VisibilityFlags.cs @@ -6,10 +6,11 @@ namespace Content.Shared.Eye [FlagsFor(typeof(VisibilityMaskLayer))] public enum VisibilityFlags : int { - None = 0, + None = 0, Normal = 1 << 0, - Ghost = 1 << 1, + Ghost = 1 << 1, PsionicInvisibility = 1 << 2, //Nyano - Summary: adds Psionic Invisibility as a visibility layer. Currently does nothing. - TelegnosticProjection = 5, + TelegnosticProjection = 5, + Ethereal = 1 << 3, } } diff --git a/Content.Server/Fax/FaxMachineComponent.cs b/Content.Shared/Fax/Components/FaxMachineComponent.cs similarity index 80% rename from Content.Server/Fax/FaxMachineComponent.cs rename to Content.Shared/Fax/Components/FaxMachineComponent.cs index a189bdc05a..5599befa31 100644 --- a/Content.Server/Fax/FaxMachineComponent.cs +++ b/Content.Shared/Fax/Components/FaxMachineComponent.cs @@ -1,12 +1,13 @@ using Content.Shared.Containers.ItemSlots; using Content.Shared.Paper; using Robust.Shared.Audio; +using Robust.Shared.GameStates; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -namespace Content.Server.Fax; +namespace Content.Shared.Fax.Components; -[RegisterComponent] +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] public sealed partial class FaxMachineComponent : Component { /// @@ -16,6 +17,13 @@ public sealed partial class FaxMachineComponent : Component [DataField("name")] public string FaxName { get; set; } = "Unknown"; + /// + /// Sprite to use when inserting an object. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField, AutoNetworkedField] + public string InsertingState = "inserting"; + /// /// Device address of fax in network to which data will be send /// @@ -26,7 +34,7 @@ public sealed partial class FaxMachineComponent : Component /// /// Contains the item to be sent, assumes it's paper... /// - [DataField("paperSlot", required: true)] + [DataField(required: true)] public ItemSlot PaperSlot = new(); /// @@ -34,21 +42,21 @@ public sealed partial class FaxMachineComponent : Component /// This will make it visible to others on the network /// [ViewVariables(VVAccess.ReadWrite)] - [DataField("responsePings")] + [DataField] public bool ResponsePings { get; set; } = true; /// /// Should admins be notified on message receive /// [ViewVariables(VVAccess.ReadWrite)] - [DataField("notifyAdmins")] + [DataField] public bool NotifyAdmins { get; set; } = false; /// /// Should that fax receive nuke codes send by admins. Probably should be captain fax only /// [ViewVariables(VVAccess.ReadWrite)] - [DataField("receiveNukeCodes")] + [DataField] public bool ReceiveNukeCodes { get; set; } = false; /// @@ -60,19 +68,19 @@ public sealed partial class FaxMachineComponent : Component /// /// Sound to play when fax has been emagged /// - [DataField("emagSound")] + [DataField] public SoundSpecifier EmagSound = new SoundCollectionSpecifier("sparks"); /// /// Sound to play when fax printing new message /// - [DataField("printSound")] + [DataField] public SoundSpecifier PrintSound = new SoundPathSpecifier("/Audio/Machines/printer.ogg"); /// /// Sound to play when fax successfully send message /// - [DataField("sendSound")] + [DataField] public SoundSpecifier SendSound = new SoundPathSpecifier("/Audio/Machines/high_tech_confirm.ogg"); /// @@ -85,27 +93,27 @@ public sealed partial class FaxMachineComponent : Component /// Print queue of the incoming message /// [ViewVariables] - [DataField("printingQueue")] + [DataField] public Queue PrintingQueue { get; private set; } = new(); /// /// Message sending timeout /// [ViewVariables] - [DataField("sendTimeoutRemaining")] + [DataField] public float SendTimeoutRemaining; /// /// Message sending timeout /// [ViewVariables] - [DataField("sendTimeout")] + [DataField] public float SendTimeout = 5f; /// /// Remaining time of inserting animation /// - [DataField("insertingTimeRemaining")] + [DataField] public float InsertingTimeRemaining; /// @@ -117,7 +125,7 @@ public sealed partial class FaxMachineComponent : Component /// /// Remaining time of printing animation /// - [DataField("printingTimeRemaining")] + [DataField] public float PrintingTimeRemaining; /// @@ -130,13 +138,16 @@ public sealed partial class FaxMachineComponent : Component [DataDefinition] public sealed partial class FaxPrintout { - [DataField("name", required: true)] + [DataField(required: true)] public string Name { get; private set; } = default!; - [DataField("content", required: true)] + [DataField] + public string? Label { get; private set; } + + [DataField(required: true)] public string Content { get; private set; } = default!; - [DataField("prototypeId", customTypeSerializer: typeof(PrototypeIdSerializer), required: true)] + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer), required: true)] public string PrototypeId { get; private set; } = default!; [DataField("stampState")] @@ -149,10 +160,11 @@ private FaxPrintout() { } - public FaxPrintout(string content, string name, string? prototypeId = null, string? stampState = null, List? stampedBy = null) + public FaxPrintout(string content, string name, string? label = null, string? prototypeId = null, string? stampState = null, List? stampedBy = null) { Content = content; Name = name; + Label = label; PrototypeId = prototypeId ?? ""; StampState = stampState; StampedBy = stampedBy ?? new List(); diff --git a/Content.Shared/Fax/Components/FaxableObjectComponent.cs b/Content.Shared/Fax/Components/FaxableObjectComponent.cs new file mode 100644 index 0000000000..57b6e610a3 --- /dev/null +++ b/Content.Shared/Fax/Components/FaxableObjectComponent.cs @@ -0,0 +1,16 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Fax.Components; +/// +/// Entity with this component can be faxed. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class FaxableObjectComponent : Component +{ + /// + /// Sprite to use when inserting an object. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField, AutoNetworkedField] + public string InsertingState = "inserting"; +} diff --git a/Content.Shared/Fax/Components/FaxecuteComponent.cs b/Content.Shared/Fax/Components/FaxecuteComponent.cs new file mode 100644 index 0000000000..9c9bd03020 --- /dev/null +++ b/Content.Shared/Fax/Components/FaxecuteComponent.cs @@ -0,0 +1,19 @@ +using Content.Shared.Damage; +using Robust.Shared.GameStates; + +namespace Content.Shared.Fax.Components; + +/// +/// A fax component which stores a damage specifier for attempting to fax a mob. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class FaxecuteComponent : Component +{ + + /// + /// Type of damage dealt when entity is faxecuted. + /// + [DataField(required: true), AutoNetworkedField] + public DamageSpecifier Damage = new(); +} + diff --git a/Content.Shared/Fax/DamageOnFaxecuteEvent.cs b/Content.Shared/Fax/DamageOnFaxecuteEvent.cs new file mode 100644 index 0000000000..b36f55ab5d --- /dev/null +++ b/Content.Shared/Fax/DamageOnFaxecuteEvent.cs @@ -0,0 +1,9 @@ + +namespace Content.Shared.Fax.Components; + +/// +/// Event for killing any mob within the fax machine. +/// +/// System for handling execution of a mob within fax when copy or send attempt is made. +/// +public sealed class FaxecuteSystem : EntitySystem +{ + [Dependency] private readonly DamageableSystem _damageable = default!; + [Dependency] private readonly SharedPopupSystem _popupSystem = default!; + + public override void Initialize() + { + base.Initialize(); + } + + public void Faxecute(EntityUid uid, FaxMachineComponent component, DamageOnFaxecuteEvent? args = null) + { + var sendEntity = component.PaperSlot.Item; + if (sendEntity == null) + return; + + if (!TryComp(uid, out var faxecute)) + return; + + var damageSpec = faxecute.Damage; + _damageable.TryChangeDamage(sendEntity, damageSpec); + _popupSystem.PopupEntity(Loc.GetString("fax-machine-popup-error", ("target", uid)), uid, PopupType.LargeCaution); + return; + + } +} diff --git a/Content.Shared/Flash/Components/FlashComponent.cs b/Content.Shared/Flash/Components/FlashComponent.cs index a26e32cb70..a9098bc85a 100644 --- a/Content.Shared/Flash/Components/FlashComponent.cs +++ b/Content.Shared/Flash/Components/FlashComponent.cs @@ -12,6 +12,13 @@ public sealed partial class FlashComponent : Component [ViewVariables(VVAccess.ReadWrite)] public int FlashDuration { get; set; } = 5000; + /// + /// How long a target is stunned when a melee flash is used. + /// If null, melee flashes will not stun at all + /// + [DataField] + public TimeSpan? MeleeStunDuration = TimeSpan.FromSeconds(1.5); + [DataField("range")] [ViewVariables(VVAccess.ReadWrite)] public float Range { get; set; } = 7f; @@ -32,5 +39,8 @@ public sealed partial class FlashComponent : Component }; public bool Flashing; + + [DataField] + public float Probability = 1f; } } diff --git a/Content.Shared/Flash/Components/FlashOnTriggerComponent.cs b/Content.Shared/Flash/Components/FlashOnTriggerComponent.cs index d1270e9db7..7658ca0ae5 100644 --- a/Content.Shared/Flash/Components/FlashOnTriggerComponent.cs +++ b/Content.Shared/Flash/Components/FlashOnTriggerComponent.cs @@ -9,4 +9,5 @@ public sealed partial class FlashOnTriggerComponent : Component { [DataField] public float Range = 1.0f; [DataField] public float Duration = 8.0f; + [DataField] public float Probability = 1.0f; } diff --git a/Content.Shared/Flight/Events.cs b/Content.Shared/Flight/Events.cs new file mode 100644 index 0000000000..6666971b53 --- /dev/null +++ b/Content.Shared/Flight/Events.cs @@ -0,0 +1,24 @@ +using Robust.Shared.Serialization; +using Content.Shared.DoAfter; + +namespace Content.Shared.Flight.Events; + +[Serializable, NetSerializable] +public sealed partial class DashDoAfterEvent : SimpleDoAfterEvent { } + +[Serializable, NetSerializable] +public sealed partial class FlightDoAfterEvent : SimpleDoAfterEvent { } + +[Serializable, NetSerializable] +public sealed class FlightEvent : EntityEventArgs +{ + public NetEntity Uid { get; } + public bool IsFlying { get; } + public bool IsAnimated { get; } + public FlightEvent(NetEntity uid, bool isFlying, bool isAnimated) + { + Uid = uid; + IsFlying = isFlying; + IsAnimated = isAnimated; + } +} diff --git a/Content.Shared/Flight/FlightComponent.cs b/Content.Shared/Flight/FlightComponent.cs new file mode 100644 index 0000000000..d250744544 --- /dev/null +++ b/Content.Shared/Flight/FlightComponent.cs @@ -0,0 +1,101 @@ +using Robust.Shared.Audio; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Shared.Flight; + +/// +/// Adds an action that allows the user to become temporarily +/// weightless at the cost of stamina and hand usage. +/// +[RegisterComponent, NetworkedComponent(), AutoGenerateComponentState] +public sealed partial class FlightComponent : Component +{ + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string? ToggleAction = "ActionToggleFlight"; + + [DataField, AutoNetworkedField] + public EntityUid? ToggleActionEntity; + + /// + /// Is the user flying right now? + /// + [DataField, AutoNetworkedField] + public bool On; + + /// + /// Stamina drain per second when flying + /// + [DataField, AutoNetworkedField] + public float StaminaDrainRate = 6.0f; + + /// + /// DoAfter delay until the user becomes weightless. + /// + [DataField, AutoNetworkedField] + public float ActivationDelay = 1.0f; + + /// + /// Speed modifier while in flight + /// + [DataField, AutoNetworkedField] + public float SpeedModifier = 2.0f; + + /// + /// Path to a sound specifier or collection for the noises made during flight + /// + [DataField] + public SoundSpecifier FlapSound = new SoundCollectionSpecifier("WingFlaps"); + + /// + /// Is the flight animated? + /// + [DataField] + public bool IsAnimated = true; + + /// + /// Does the animation animate a layer?. + /// + [DataField] + public bool IsLayerAnimated; + + /// + /// Which RSI layer path does this animate? + /// + [DataField] + public string? Layer; + + /// + /// Whats the speed of the shader? + /// + [DataField] + public float ShaderSpeed = 6.0f; + + /// + /// How much are the values in the shader's calculations multiplied by? + /// + [DataField] + public float ShaderMultiplier = 0.01f; + + /// + /// What is the offset on the shader? + /// + [DataField] + public float ShaderOffset = 0.25f; + + /// + /// What animation does the flight use? + /// + + [DataField] + public string AnimationKey = "default"; + + /// + /// Time between sounds being played + /// + [DataField] + public float FlapInterval = 1.0f; + + public float TimeUntilFlap; +} diff --git a/Content.Shared/Flight/SharedFlightSystem.cs b/Content.Shared/Flight/SharedFlightSystem.cs new file mode 100644 index 0000000000..281c6d70f0 --- /dev/null +++ b/Content.Shared/Flight/SharedFlightSystem.cs @@ -0,0 +1,104 @@ +using Content.Shared.Actions; +using Content.Shared.Movement.Systems; +using Content.Shared.Damage.Systems; +using Content.Shared.Hands.Components; +using Content.Shared.Hands.EntitySystems; +using Content.Shared.Interaction.Components; +using Content.Shared.Inventory.VirtualItem; +using Content.Shared.Flight.Events; + +namespace Content.Shared.Flight; +public abstract class SharedFlightSystem : EntitySystem +{ + [Dependency] private readonly SharedActionsSystem _actionsSystem = default!; + [Dependency] private readonly SharedVirtualItemSystem _virtualItem = default!; + [Dependency] private readonly StaminaSystem _staminaSystem = default!; + [Dependency] private readonly SharedHandsSystem _hands = default!; + [Dependency] private readonly MovementSpeedModifierSystem _movementSpeed = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnStartup); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnRefreshMoveSpeed); + } + + #region Core Functions + private void OnStartup(EntityUid uid, FlightComponent component, ComponentStartup args) + { + _actionsSystem.AddAction(uid, ref component.ToggleActionEntity, component.ToggleAction); + } + + private void OnShutdown(EntityUid uid, FlightComponent component, ComponentShutdown args) + { + _actionsSystem.RemoveAction(uid, component.ToggleActionEntity); + } + + public void ToggleActive(EntityUid uid, bool active, FlightComponent component) + { + component.On = active; + component.TimeUntilFlap = 0f; + _actionsSystem.SetToggled(component.ToggleActionEntity, component.On); + RaiseNetworkEvent(new FlightEvent(GetNetEntity(uid), component.On, component.IsAnimated)); + _staminaSystem.ToggleStaminaDrain(uid, component.StaminaDrainRate, active, false); + _movementSpeed.RefreshMovementSpeedModifiers(uid); + UpdateHands(uid, active); + Dirty(uid, component); + } + + private void UpdateHands(EntityUid uid, bool flying) + { + if (!TryComp(uid, out var handsComponent)) + return; + + if (flying) + BlockHands(uid, handsComponent); + else + FreeHands(uid); + } + + private void BlockHands(EntityUid uid, HandsComponent handsComponent) + { + var freeHands = 0; + foreach (var hand in _hands.EnumerateHands(uid, handsComponent)) + { + if (hand.HeldEntity == null) + { + freeHands++; + continue; + } + + // Is this entity removable? (they might have handcuffs on) + if (HasComp(hand.HeldEntity) && hand.HeldEntity != uid) + continue; + + _hands.DoDrop(uid, hand, true, handsComponent); + freeHands++; + if (freeHands == 2) + break; + } + if (_virtualItem.TrySpawnVirtualItemInHand(uid, uid, out var virtItem1)) + EnsureComp(virtItem1.Value); + + if (_virtualItem.TrySpawnVirtualItemInHand(uid, uid, out var virtItem2)) + EnsureComp(virtItem2.Value); + } + + private void FreeHands(EntityUid uid) + { + _virtualItem.DeleteInHandsMatching(uid, uid); + } + + private void OnRefreshMoveSpeed(EntityUid uid, FlightComponent component, RefreshMovementSpeedModifiersEvent args) + { + if (!component.On) + return; + + args.ModifySpeed(component.SpeedModifier, component.SpeedModifier); + } + + #endregion +} +public sealed partial class ToggleFlightEvent : InstantActionEvent { } diff --git a/Content.Shared/Follower/FollowerSystem.cs b/Content.Shared/Follower/FollowerSystem.cs index fc7cccf9bd..6c02b13076 100644 --- a/Content.Shared/Follower/FollowerSystem.cs +++ b/Content.Shared/Follower/FollowerSystem.cs @@ -247,6 +247,27 @@ public void StopAllFollowers(EntityUid uid, StopFollowingEntity(player, uid, followed); } } + + /// + /// Get the most followed entity. + /// + public EntityUid? GetMostFollowed() + { + EntityUid? picked = null; + int most = 0; + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var comp)) + { + var count = comp.Following.Count; + if (count > most) + { + picked = uid; + most = count; + } + } + + return picked; + } } public abstract class FollowEvent : EntityEventArgs diff --git a/Content.Shared/Footprint/FootPrintComponent.cs b/Content.Shared/Footprint/FootPrintComponent.cs new file mode 100644 index 0000000000..e1f9716057 --- /dev/null +++ b/Content.Shared/Footprint/FootPrintComponent.cs @@ -0,0 +1,23 @@ +using Content.Shared.Chemistry.Components; +using Robust.Shared.GameStates; + +namespace Content.Shared.FootPrint; + +/// +/// This is used for marking footsteps, handling footprint drawing. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class FootPrintComponent : Component +{ + /// + /// Owner (with ) of a print (this component). + /// + [AutoNetworkedField] + public EntityUid PrintOwner; + + [DataField] + public string SolutionName = "step"; + + [DataField] + public Entity? Solution; +} diff --git a/Content.Shared/Footprint/FootPrintVisuals.cs b/Content.Shared/Footprint/FootPrintVisuals.cs new file mode 100644 index 0000000000..f980f60a6d --- /dev/null +++ b/Content.Shared/Footprint/FootPrintVisuals.cs @@ -0,0 +1,25 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.FootPrint; + +[Serializable, NetSerializable] +public enum FootPrintVisuals : byte +{ + BareFootPrint, + ShoesPrint, + SuitPrint, + Dragging +} + +[Serializable, NetSerializable] +public enum FootPrintVisualState : byte +{ + State, + Color +} + +[Serializable, NetSerializable] +public enum FootPrintVisualLayers : byte +{ + Print +} diff --git a/Content.Shared/Footprint/FootPrintsComponent.cs b/Content.Shared/Footprint/FootPrintsComponent.cs new file mode 100644 index 0000000000..2b2c4ed66e --- /dev/null +++ b/Content.Shared/Footprint/FootPrintsComponent.cs @@ -0,0 +1,88 @@ +using System.Numerics; +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; + +namespace Content.Shared.FootPrint; + +[RegisterComponent] +public sealed partial class FootPrintsComponent : Component +{ + [ViewVariables(VVAccess.ReadOnly), DataField] + public ResPath RsiPath = new("/Textures/Effects/footprints.rsi"); + + // all of those are set as a layer + [ViewVariables(VVAccess.ReadOnly), DataField] + public string LeftBarePrint = "footprint-left-bare-human"; + + [ViewVariables(VVAccess.ReadOnly), DataField] + public string RightBarePrint = "footprint-right-bare-human"; + + [ViewVariables(VVAccess.ReadOnly), DataField] + public string ShoesPrint = "footprint-shoes"; + + [ViewVariables(VVAccess.ReadOnly), DataField] + public string SuitPrint = "footprint-suit"; + + [ViewVariables(VVAccess.ReadOnly), DataField] + public string[] DraggingPrint = + [ + "dragging-1", + "dragging-2", + "dragging-3", + "dragging-4", + "dragging-5", + ]; + // yea, those + + [ViewVariables(VVAccess.ReadOnly), DataField] + public EntProtoId StepProtoId = "Footstep"; + + [ViewVariables(VVAccess.ReadOnly), DataField] + public Color PrintsColor = Color.FromHex("#00000000"); + + /// + /// The size scaling factor for footprint steps. Must be positive. + /// + [DataField] + public float StepSize = 0.7f; + + /// + /// The size scaling factor for drag marks. Must be positive. + /// + [DataField] + public float DragSize = 0.5f; + + /// + /// The amount of color to transfer from the source (e.g., puddle) to the footprint. + /// + [DataField] + public float ColorQuantity; + + /// + /// The factor by which the alpha channel is reduced in subsequent footprints. + /// + [DataField] + public float ColorReduceAlpha = 0.1f; + + [DataField] + public string? ReagentToTransfer; + + [DataField] + public Vector2 OffsetPrint = new(0.1f, 0f); + + /// + /// Tracks which foot should make the next print. True for right foot, false for left. + /// + public bool RightStep = true; + + /// + /// The position of the last footprint in world coordinates. + /// + public Vector2 StepPos = Vector2.Zero; + + /// + /// Controls how quickly the footprint color transitions between steps. + /// Value between 0 and 1, where higher values mean faster color changes. + /// + public float ColorInterpolationFactor = 0.2f; +} diff --git a/Content.Shared/Footprint/PuddleFootPrintsComponent.cs b/Content.Shared/Footprint/PuddleFootPrintsComponent.cs new file mode 100644 index 0000000000..0e2ddfe383 --- /dev/null +++ b/Content.Shared/Footprint/PuddleFootPrintsComponent.cs @@ -0,0 +1,11 @@ +namespace Content.Shared.FootPrint; + +[RegisterComponent] +public sealed partial class PuddleFootPrintsComponent : Component +{ + [ViewVariables(VVAccess.ReadWrite)] + public float SizeRatio = 0.2f; + + [ViewVariables(VVAccess.ReadWrite)] + public float OffPercent = 80f; +} diff --git a/Content.Shared/Geras/SharedGerasSystem.cs b/Content.Shared/Geras/SharedGerasSystem.cs new file mode 100644 index 0000000000..8c998371b6 --- /dev/null +++ b/Content.Shared/Geras/SharedGerasSystem.cs @@ -0,0 +1,16 @@ +using Content.Shared.Actions; + +namespace Content.Shared.Geras; + +/// +/// Geras is the god of old age, and A geras is the small morph of a slime. This system allows the slimes to have the morphing action. +/// +public abstract class SharedGerasSystem : EntitySystem +{ + +} + +public sealed partial class MorphIntoGeras : InstantActionEvent +{ + +} diff --git a/Content.Shared/Ghost/GhostComponent.cs b/Content.Shared/Ghost/GhostComponent.cs index f7717e8d23..96e9b717b9 100644 --- a/Content.Shared/Ghost/GhostComponent.cs +++ b/Content.Shared/Ghost/GhostComponent.cs @@ -101,4 +101,6 @@ public sealed partial class ToggleLightingActionEvent : InstantActionEvent { } public sealed partial class ToggleGhostHearingActionEvent : InstantActionEvent { } +public sealed partial class ToggleGhostVisibilityToAllEvent : InstantActionEvent { } + public sealed partial class BooActionEvent : InstantActionEvent { } diff --git a/Content.Shared/Ghost/Roles/GhostRolePrototype.cs b/Content.Shared/Ghost/Roles/GhostRolePrototype.cs new file mode 100644 index 0000000000..43d6432250 --- /dev/null +++ b/Content.Shared/Ghost/Roles/GhostRolePrototype.cs @@ -0,0 +1,38 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.Ghost.Roles; + +/// +/// For selectable ghostrole prototypes in ghostrole spawners. +/// +[Prototype] +public sealed partial class GhostRolePrototype : IPrototype +{ + [ViewVariables] + [IdDataField] + public string ID { get; private set; } = default!; + + /// + /// The name of the ghostrole. + /// + [DataField] + public string Name { get; set; } = default!; + + /// + /// The description of the ghostrole. + /// + [DataField] + public string Description { get; set; } = default!; + + /// + /// The entity prototype of the ghostrole + /// + [DataField] + public string EntityPrototype = default!; + + /// + /// Rules of the ghostrole + /// + [DataField] + public string Rules = default!; +} \ No newline at end of file diff --git a/Content.Shared/Ghost/Roles/GhostRolesEuiMessages.cs b/Content.Shared/Ghost/Roles/GhostRolesEuiMessages.cs index 74af1e89ec..4dd6bfa433 100644 --- a/Content.Shared/Ghost/Roles/GhostRolesEuiMessages.cs +++ b/Content.Shared/Ghost/Roles/GhostRolesEuiMessages.cs @@ -13,6 +13,20 @@ public struct GhostRoleInfo public string Description { get; set; } public string Rules { get; set; } public List? Requirements { get; set; } + + /// + public GhostRoleKind Kind { get; set; } + + /// + /// if is , specifies how many players are currently + /// in the raffle for this role. + /// + public uint RafflePlayerCount { get; set; } + + /// + /// if is , specifies when raffle finishes. + /// + public TimeSpan RaffleEndTime { get; set; } } [NetSerializable, Serializable] @@ -27,24 +41,62 @@ public GhostRolesEuiState(GhostRoleInfo[] ghostRoles) } [NetSerializable, Serializable] - public sealed class GhostRoleTakeoverRequestMessage : EuiMessageBase + public sealed class RequestGhostRoleMessage : EuiMessageBase { public uint Identifier { get; } - public GhostRoleTakeoverRequestMessage(uint identifier) + public RequestGhostRoleMessage(uint identifier) { Identifier = identifier; } } [NetSerializable, Serializable] - public sealed class GhostRoleFollowRequestMessage : EuiMessageBase + public sealed class FollowGhostRoleMessage : EuiMessageBase { public uint Identifier { get; } - public GhostRoleFollowRequestMessage(uint identifier) + public FollowGhostRoleMessage(uint identifier) { Identifier = identifier; } } + + [NetSerializable, Serializable] + public sealed class LeaveGhostRoleRaffleMessage : EuiMessageBase + { + public uint Identifier { get; } + + public LeaveGhostRoleRaffleMessage(uint identifier) + { + Identifier = identifier; + } + } + + /// + /// Determines whether a ghost role is a raffle role, and if it is, whether it's running. + /// + [NetSerializable, Serializable] + public enum GhostRoleKind + { + /// + /// Role is not a raffle role and can be taken immediately. + /// + FirstComeFirstServe, + + /// + /// Role is a raffle role, but raffle hasn't started yet. + /// + RaffleReady, + + /// + /// Role is raffle role and currently being raffled, but player hasn't joined raffle. + /// + RaffleInProgress, + + /// + /// Role is raffle role and currently being raffled, and player joined raffle. + /// + RaffleJoined + } } diff --git a/Content.Shared/Ghost/Roles/Raffles/GhostRoleRaffleSettings.cs b/Content.Shared/Ghost/Roles/Raffles/GhostRoleRaffleSettings.cs new file mode 100644 index 0000000000..a7aa757b72 --- /dev/null +++ b/Content.Shared/Ghost/Roles/Raffles/GhostRoleRaffleSettings.cs @@ -0,0 +1,30 @@ +namespace Content.Server.Ghost.Roles.Raffles; + +/// +/// Defines settings for a ghost role raffle. +/// +[DataDefinition] +public sealed partial class GhostRoleRaffleSettings +{ + /// + /// The initial duration of a raffle in seconds. This is the countdown timer's value when the raffle starts. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField(required: true)] + public uint InitialDuration { get; set; } + + /// + /// When the raffle is joined by a player, the countdown timer is extended by this value in seconds. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField(required: true)] + public uint JoinExtendsDurationBy { get; set; } + + /// + /// The maximum duration in seconds for the ghost role raffle. A raffle cannot run for longer than this + /// duration, even if extended by joiners. Must be greater than or equal to . + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField(required: true)] + public uint MaxDuration { get; set; } +} diff --git a/Content.Shared/Ghost/Roles/Raffles/GhostRoleRaffleSettingsPrototype.cs b/Content.Shared/Ghost/Roles/Raffles/GhostRoleRaffleSettingsPrototype.cs new file mode 100644 index 0000000000..f1447a0e09 --- /dev/null +++ b/Content.Shared/Ghost/Roles/Raffles/GhostRoleRaffleSettingsPrototype.cs @@ -0,0 +1,22 @@ +using Content.Server.Ghost.Roles.Raffles; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Ghost.Roles.Raffles; + +/// +/// Allows specifying the settings for a ghost role raffle as a prototype. +/// +[Prototype("ghostRoleRaffleSettings")] +public sealed class GhostRoleRaffleSettingsPrototype : IPrototype +{ + /// + [IdDataField] + public string ID { get; private set; } = default!; + + /// + /// The settings for a ghost role raffle. + /// + /// + [DataField(required: true)] + public GhostRoleRaffleSettings Settings { get; private set; } = new(); +} diff --git a/Content.Shared/Ghost/SharedGhostSystem.cs b/Content.Shared/Ghost/SharedGhostSystem.cs index c1c2c3c71e..6f7370fe58 100644 --- a/Content.Shared/Ghost/SharedGhostSystem.cs +++ b/Content.Shared/Ghost/SharedGhostSystem.cs @@ -1,6 +1,7 @@ using Content.Shared.Emoting; using Content.Shared.Hands; using Content.Shared.Interaction.Events; +using Content.Shared.InteractionVerbs.Events; using Content.Shared.Item; using Content.Shared.Popups; using Robust.Shared.Serialization; @@ -23,6 +24,7 @@ public override void Initialize() SubscribeLocalEvent(OnAttempt); SubscribeLocalEvent(OnAttempt); SubscribeLocalEvent(OnAttempt); + SubscribeLocalEvent(OnAttempt); } private void OnAttempt(EntityUid uid, GhostComponent component, CancellableEntityEventArgs args) @@ -125,6 +127,12 @@ public GhostWarpToTargetRequestEvent(NetEntity target) } } + /// + /// A client to server request for their ghost to be warped to the most followed entity. + /// + [Serializable, NetSerializable] + public sealed class GhostnadoRequestEvent : EntityEventArgs; + /// /// A client to server request for their ghost to return to body /// @@ -146,4 +154,7 @@ public GhostUpdateGhostRoleCountEvent(int availableGhostRoleCount) AvailableGhostRoles = availableGhostRoleCount; } } + + [Serializable, NetSerializable] + public sealed class GhostReturnToRoundRequest : EntityEventArgs; } diff --git a/Content.Shared/Gravity/SharedFloatingVisualizerSystem.cs b/Content.Shared/Gravity/SharedFloatingVisualizerSystem.cs index 57136116ca..8fe9e00e7e 100644 --- a/Content.Shared/Gravity/SharedFloatingVisualizerSystem.cs +++ b/Content.Shared/Gravity/SharedFloatingVisualizerSystem.cs @@ -1,5 +1,6 @@ using System.Numerics; using Robust.Shared.Map; +using Content.Shared.Flight.Events; namespace Content.Shared.Gravity; @@ -17,6 +18,7 @@ public override void Initialize() SubscribeLocalEvent(OnComponentStartup); SubscribeLocalEvent(OnGravityChanged); SubscribeLocalEvent(OnEntParentChanged); + SubscribeNetworkEvent(OnFlight); } /// @@ -62,10 +64,24 @@ private void OnGravityChanged(ref GravityChangedEvent args) } } + private void OnFlight(FlightEvent args) + { + var uid = GetEntity(args.Uid); + if (!TryComp(uid, out var floating)) + return; + floating.CanFloat = args.IsFlying; + + if (!args.IsFlying + || !args.IsAnimated) + return; + + FloatAnimation(uid, floating.Offset, floating.AnimationKey, floating.AnimationTime); + } + private void OnEntParentChanged(EntityUid uid, FloatingVisualsComponent component, ref EntParentChangedMessage args) { var transform = args.Transform; if (CanFloat(uid, component, transform)) FloatAnimation(uid, component.Offset, component.AnimationKey, component.AnimationTime); } -} +} \ No newline at end of file diff --git a/Content.Shared/Gravity/SharedGravitySystem.cs b/Content.Shared/Gravity/SharedGravitySystem.cs index 100d2ee74f..55187bf14a 100644 --- a/Content.Shared/Gravity/SharedGravitySystem.cs +++ b/Content.Shared/Gravity/SharedGravitySystem.cs @@ -8,6 +8,7 @@ using Robust.Shared.Physics.Components; using Robust.Shared.Serialization; using Robust.Shared.Timing; +using Content.Shared.Flight; namespace Content.Shared.Gravity { @@ -24,6 +25,9 @@ public bool IsWeightless(EntityUid uid, PhysicsComponent? body = null, Transform if ((body?.BodyType & (BodyType.Static | BodyType.Kinematic)) != 0) return false; + if (TryComp(uid, out var flying) && flying.On) + return true; + if (TryComp(uid, out var ignoreGravityComponent)) return ignoreGravityComponent.Weightless; @@ -142,4 +146,4 @@ public GravityComponentState(bool enabled) } } } -} +} \ No newline at end of file diff --git a/Content.Shared/GridPreloader/Prototypes/PreloadedGridPrototype.cs b/Content.Shared/GridPreloader/Prototypes/PreloadedGridPrototype.cs new file mode 100644 index 0000000000..89da9dfb8f --- /dev/null +++ b/Content.Shared/GridPreloader/Prototypes/PreloadedGridPrototype.cs @@ -0,0 +1,21 @@ +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; + +namespace Content.Shared.GridPreloader.Prototypes; + +/// +/// Creating this prototype will automatically load the grid at the specified path at the beginning of the round, +/// and allow the GridPreloader system to load them in the middle of the round. This is needed for optimization, +/// because loading grids in the middle of a round causes the server to lag. +/// +[Prototype("preloadedGrid")] +public sealed partial class PreloadedGridPrototype : IPrototype +{ + [IdDataField] public string ID { get; } = string.Empty; + + [DataField(required: true)] + public ResPath Path; + + [DataField] + public int Copies = 1; +} diff --git a/Content.Shared/GridPreloader/Systems/SharedGridPreloaderSystem.cs b/Content.Shared/GridPreloader/Systems/SharedGridPreloaderSystem.cs new file mode 100644 index 0000000000..04c994adf2 --- /dev/null +++ b/Content.Shared/GridPreloader/Systems/SharedGridPreloaderSystem.cs @@ -0,0 +1,4 @@ +namespace Content.Shared.GridPreloader.Systems; +public abstract class SharedGridPreloaderSystem : EntitySystem +{ +} diff --git a/Content.Shared/Hands/Components/HandHelpers.cs b/Content.Shared/Hands/Components/HandHelpers.cs index 11fff6d9c8..aecf3a6936 100644 --- a/Content.Shared/Hands/Components/HandHelpers.cs +++ b/Content.Shared/Hands/Components/HandHelpers.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.Shared.Hands.EntitySystems; namespace Content.Shared.Hands.Components; @@ -20,6 +21,15 @@ public static class HandHelpers /// public static int CountFreeHands(this HandsComponent component) => component.Hands.Values.Count(hand => hand.IsEmpty); + /// + /// Get the number of hands that are not currently holding anything. This is a LinQ method, not a property, so + /// cache it instead of accessing this multiple times. + /// + public static int CountFreeableHands(this Entity component, SharedHandsSystem system) + { + return system.CountFreeableHands(component); + } + /// /// Get a list of hands that are currently holding nothing. This is a LinQ method, not a property, so cache /// it instead of accessing this multiple times. diff --git a/Content.Shared/Hands/Components/HandsComponent.cs b/Content.Shared/Hands/Components/HandsComponent.cs index f1f25a69f7..a7464e5bac 100644 --- a/Content.Shared/Hands/Components/HandsComponent.cs +++ b/Content.Shared/Hands/Components/HandsComponent.cs @@ -29,6 +29,7 @@ public sealed partial class HandsComponent : Component /// /// List of hand-names. These are keys for . The order of this list determines the order in which hands are iterated over. /// + [ViewVariables] public List SortedHands = new(); /// @@ -126,9 +127,43 @@ public HandsComponentState(HandsComponent handComp) /// /// What side of the body this hand is on. /// +/// +/// public enum HandLocation : byte { Left, Middle, Right } + +/// +/// What side of the UI a hand is on. +/// +/// +/// +public enum HandUILocation : byte +{ + Left, + Right +} + +/// +/// Helper functions for working with . +/// +public static class HandLocationExt +{ + /// + /// Convert a into the appropriate . + /// This maps "middle" hands to . + /// + public static HandUILocation GetUILocation(this HandLocation location) + { + return location switch + { + HandLocation.Left => HandUILocation.Left, + HandLocation.Middle => HandUILocation.Right, + HandLocation.Right => HandUILocation.Right, + _ => throw new ArgumentOutOfRangeException(nameof(location), location, null) + }; + } +} diff --git a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Interactions.cs b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Interactions.cs index 32339eb03a..6d4d332479 100644 --- a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Interactions.cs +++ b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Interactions.cs @@ -8,6 +8,7 @@ using Robust.Shared.Input.Binding; using Robust.Shared.Map; using Robust.Shared.Player; +using Robust.Shared.Utility; namespace Content.Shared.Hands.EntitySystems; @@ -181,27 +182,21 @@ public bool TryMoveHeldEntityToActiveHand(EntityUid uid, string handName, bool c } //TODO: Actually shows all items/clothing/etc. - private void HandleExamined(EntityUid uid, HandsComponent handsComp, ExaminedEvent args) + private void HandleExamined(EntityUid examinedUid, HandsComponent handsComp, ExaminedEvent args) { - var held = EnumerateHeld(uid, handsComp) - .Where(x => !HasComp(x)).ToList(); + var heldItemNames = EnumerateHeld(examinedUid, handsComp) + .Where(entity => !HasComp(entity)) + .Select(item => FormattedMessage.EscapeText(Identity.Name(item, EntityManager))) + .Select(itemName => Loc.GetString("comp-hands-examine-wrapper", ("item", itemName))) + .ToList(); + + var locKey = heldItemNames.Count != 0 ? "comp-hands-examine" : "comp-hands-examine-empty"; + var locUser = ("user", Identity.Entity(examinedUid, EntityManager)); + var locItems = ("items", ContentLocalizationManager.FormatList(heldItemNames)); using (args.PushGroup(nameof(HandsComponent))) { - if (!held.Any()) - { - args.PushText(Loc.GetString("comp-hands-examine-empty", - ("user", Identity.Entity(uid, EntityManager)))); - return; - } - - var heldList = ContentLocalizationManager.FormatList(held - .Select(x => Loc.GetString("comp-hands-examine-wrapper", - ("item", Identity.Entity(x, EntityManager)))).ToList()); - - args.PushMarkup(Loc.GetString("comp-hands-examine", - ("user", Identity.Entity(uid, EntityManager)), - ("items", heldList))); + args.PushMarkup(Loc.GetString(locKey, locUser, locItems)); } } } diff --git a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs index b72a7c4eb3..ead824e712 100644 --- a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs +++ b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs @@ -4,8 +4,8 @@ using Content.Shared.Administration.Logs; using Content.Shared.Hands.Components; using Content.Shared.Interaction; +using Content.Shared.Inventory; using Content.Shared.Inventory.VirtualItem; -using Content.Shared.Item; using Content.Shared.Storage.EntitySystems; using Robust.Shared.Containers; using Robust.Shared.Input.Binding; @@ -18,6 +18,7 @@ public abstract partial class SharedHandsSystem [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; [Dependency] protected readonly SharedContainerSystem ContainerSystem = default!; [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; + [Dependency] private readonly InventorySystem _inventorySystem = default!; [Dependency] private readonly SharedStorageSystem _storage = default!; [Dependency] protected readonly SharedTransformSystem TransformSystem = default!; [Dependency] private readonly SharedVirtualItemSystem _virtualSystem = default!; @@ -87,7 +88,6 @@ public virtual void RemoveHand(EntityUid uid, string handName, HandsComponent? h /// /// /// - public void RemoveHands(EntityUid uid, HandsComponent? handsComp = null) { if (!Resolve(uid, ref handsComp)) @@ -137,6 +137,43 @@ public bool TryGetEmptyHand(EntityUid uid, [NotNullWhen(true)] out Hand? emptyHa return false; } + public bool TryGetActiveHand(Entity entity, [NotNullWhen(true)] out Hand? hand) + { + if (!Resolve(entity, ref entity.Comp, false)) + { + hand = null; + return false; + } + + hand = entity.Comp.ActiveHand; + return hand != null; + } + + public bool TryGetActiveItem(Entity entity, [NotNullWhen(true)] out EntityUid? item) + { + if (!TryGetActiveHand(entity, out var hand)) + { + item = null; + return false; + } + + item = hand.HeldEntity; + return item != null; + } + + public Hand? GetActiveHand(Entity entity) + { + if (!Resolve(entity, ref entity.Comp)) + return null; + + return entity.Comp.ActiveHand; + } + + public EntityUid? GetActiveItem(Entity entity) + { + return GetActiveHand(entity)?.HeldEntity; + } + /// /// Enumerate over hands, starting with the currently active hand. /// @@ -227,9 +264,17 @@ public bool SetActiveHand(EntityUid uid, Hand? hand, HandsComponent? handComp = return true; } - public bool IsHolding(EntityUid uid, EntityUid? entity, [NotNullWhen(true)] out Hand? inHand, HandsComponent? handsComp = null) + public bool IsHolding(Entity entity, [NotNullWhen(true)] EntityUid? item) + { + return IsHolding(entity, item, out _, entity); + } + + public bool IsHolding(EntityUid uid, [NotNullWhen(true)] EntityUid? entity, [NotNullWhen(true)] out Hand? inHand, HandsComponent? handsComp = null) { inHand = null; + if (entity == null) + return false; + if (!Resolve(uid, ref handsComp, false)) return false; @@ -255,4 +300,14 @@ public bool TryGetHand(EntityUid handsUid, string handId, [NotNullWhen(true)] ou return hands.Hands.TryGetValue(handId, out hand); } + + public int CountFreeableHands(Entity hands) + { + var freeable = 0; + foreach (var hand in hands.Comp.Hands.Values) + if (hand.IsEmpty || CanDropHeld(hands, hand)) + freeable++; + + return freeable; + } } diff --git a/Content.Shared/HealthExaminable/HealthExaminableComponent.cs b/Content.Shared/HealthExaminable/HealthExaminableComponent.cs new file mode 100644 index 0000000000..88a79782a5 --- /dev/null +++ b/Content.Shared/HealthExaminable/HealthExaminableComponent.cs @@ -0,0 +1,27 @@ +using Content.Shared.Damage.Prototypes; +using Content.Shared.FixedPoint; +using Robust.Shared.Prototypes; + +namespace Content.Shared.HealthExaminable; + +[RegisterComponent, Access(typeof(HealthExaminableSystem))] +public sealed partial class HealthExaminableComponent : Component +{ + // + // The thresholds for determining the examine text for certain amounts of damage. + // These are calculated as a percentage of the entity's critical threshold. + // + public List Thresholds = new() + { FixedPoint2.New(0.10), FixedPoint2.New(0.25), FixedPoint2.New(0.50), FixedPoint2.New(0.75) }; + + [DataField(required: true)] + public HashSet> ExaminableTypes = default!; + + /// + /// Health examine text is automatically generated through creating loc string IDs, in the form: + /// `health-examine-[prefix]-[type]-[threshold]` + /// This part determines the prefix. + /// + [DataField] + public string LocPrefix = "carbon"; +} diff --git a/Content.Server/HealthExaminable/HealthExaminableSystem.cs b/Content.Shared/HealthExaminable/HealthExaminableSystem.cs similarity index 90% rename from Content.Server/HealthExaminable/HealthExaminableSystem.cs rename to Content.Shared/HealthExaminable/HealthExaminableSystem.cs index 89291726fb..091176a3cb 100644 --- a/Content.Server/HealthExaminable/HealthExaminableSystem.cs +++ b/Content.Shared/HealthExaminable/HealthExaminableSystem.cs @@ -1,4 +1,3 @@ -using Content.Server.Traits.Assorted; using Content.Shared.Damage; using Content.Shared.Examine; using Content.Shared.FixedPoint; @@ -8,8 +7,9 @@ using Content.Shared.Verbs; using Robust.Shared.Utility; using System.Linq; +using Content.Shared.Traits.Assorted.Components; -namespace Content.Server.HealthExaminable; +namespace Content.Shared.HealthExaminable; public sealed class HealthExaminableSystem : EntitySystem { @@ -30,29 +30,32 @@ private void OnGetExamineVerbs(EntityUid uid, HealthExaminableComponent componen var detailsRange = _examineSystem.IsInDetailsRange(args.User, uid); - var verb = new ExamineVerb() + var verb = new ExamineVerb { Act = () => { - FormattedMessage markup; - if (uid == args.User - && TryComp(uid, out var selfAware)) - markup = CreateMarkupSelfAware(uid, selfAware, component, damage); - else - markup = CreateMarkup(uid, component, damage); - + var markup = GetMarkup(args.User, (uid, component), damage); _examineSystem.SendExamineTooltip(args.User, uid, markup, false, false); }, Text = Loc.GetString("health-examinable-verb-text"), Category = VerbCategory.Examine, Disabled = !detailsRange, Message = detailsRange ? null : Loc.GetString("health-examinable-verb-disabled"), - Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/rejuvenate.svg.192dpi.png")) + Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/rejuvenate.svg.192dpi.png")) }; args.Verbs.Add(verb); } + public FormattedMessage GetMarkup(EntityUid examiner, + Entity examinable, + DamageableComponent damageable) + { + return examiner == examinable.Owner && TryComp(examinable, out var selfAware) + ? CreateMarkupSelfAware(examinable, selfAware, examinable.Comp, damageable) + : CreateMarkup(examinable, examinable.Comp, damageable); + } + private FormattedMessage CreateMarkup(EntityUid uid, HealthExaminableComponent component, DamageableComponent damage) { var msg = new FormattedMessage(); @@ -92,20 +95,14 @@ private FormattedMessage CreateMarkup(EntityUid uid, HealthExaminableComponent c continue; if (!first) - { msg.PushNewline(); - } else - { first = false; - } msg.AddMarkup(chosenLocStr); } if (msg.IsEmpty) - { msg.AddMarkup(Loc.GetString($"health-examinable-{component.LocPrefix}-none")); - } // Anything else want to add on to this? RaiseLocalEvent(uid, new HealthBeingExaminedEvent(msg, false), true); diff --git a/Content.Shared/HeightAdjust/HeightAdjustSystem.cs b/Content.Shared/HeightAdjust/HeightAdjustSystem.cs index 46b2d9b656..8bfdaccfd1 100644 --- a/Content.Shared/HeightAdjust/HeightAdjustSystem.cs +++ b/Content.Shared/HeightAdjust/HeightAdjustSystem.cs @@ -25,27 +25,7 @@ public sealed class HeightAdjustSystem : EntitySystem /// True if all operations succeeded public bool SetScale(EntityUid uid, float scale) { - var succeeded = true; - if (_config.GetCVar(CCVars.HeightAdjustModifiesZoom) && EntityManager.TryGetComponent(uid, out var eye)) - _eye.SetMaxZoom(uid, eye.MaxZoom * scale); - else - succeeded = false; - - if (_config.GetCVar(CCVars.HeightAdjustModifiesHitbox) && EntityManager.TryGetComponent(uid, out var fixtures)) - foreach (var fixture in fixtures.Fixtures) - _physics.SetRadius(uid, fixture.Key, fixture.Value, fixture.Value.Shape, MathF.MinMagnitude(fixture.Value.Shape.Radius * scale, 0.49f)); - else - succeeded = false; - - if (EntityManager.HasComponent(uid)) - { - _appearance.SetHeight(uid, scale); - _appearance.SetWidth(uid, scale); - } - else - succeeded = false; - - return succeeded; + return SetScale(uid, new Vector2(scale, scale)); } /// @@ -75,6 +55,8 @@ public bool SetScale(EntityUid uid, Vector2 scale) else succeeded = false; + RaiseLocalEvent(uid, new HeightAdjustedEvent { NewScale = scale }); + return succeeded; } } diff --git a/Content.Shared/HeightAdjust/HeightAdjustedEvent.cs b/Content.Shared/HeightAdjust/HeightAdjustedEvent.cs new file mode 100644 index 0000000000..3db856e0d8 --- /dev/null +++ b/Content.Shared/HeightAdjust/HeightAdjustedEvent.cs @@ -0,0 +1,11 @@ +using System.Numerics; + +namespace Content.Shared.HeightAdjust; + +/// +/// Raised on a humanoid after their scale has been adjusted in accordance with their profile and their physics have been updated. +/// +public sealed class HeightAdjustedEvent : EntityEventArgs +{ + public Vector2 NewScale; +} diff --git a/Content.Shared/Humanoid/Events/ProfileLoadFinishedEvent.cs b/Content.Shared/Humanoid/Events/ProfileLoadFinishedEvent.cs new file mode 100644 index 0000000000..afe78a1517 --- /dev/null +++ b/Content.Shared/Humanoid/Events/ProfileLoadFinishedEvent.cs @@ -0,0 +1,7 @@ +namespace Content.Shared.Humanoid.Events; + +/// +/// Raised on an entity when their profile has finished being loaded +/// +public sealed class ProfileLoadFinishedEvent : EntityEventArgs { } + diff --git a/Content.Shared/Humanoid/EyeColor.cs b/Content.Shared/Humanoid/EyeColor.cs new file mode 100644 index 0000000000..a39e090a86 --- /dev/null +++ b/Content.Shared/Humanoid/EyeColor.cs @@ -0,0 +1,4 @@ +namespace Content.Shared.Humanoid; + +[ByRefEvent] +public record struct EyeColorInitEvent(); \ No newline at end of file diff --git a/Content.Shared/Humanoid/HumanoidAppearanceComponent.cs b/Content.Shared/Humanoid/HumanoidAppearanceComponent.cs index c9292337d7..c0f9b8ac00 100644 --- a/Content.Shared/Humanoid/HumanoidAppearanceComponent.cs +++ b/Content.Shared/Humanoid/HumanoidAppearanceComponent.cs @@ -32,6 +32,9 @@ public sealed partial class HumanoidAppearanceComponent : Component [DataField, AutoNetworkedField] public int Age = 18; + [DataField, AutoNetworkedField] + public string CustomSpecieName = ""; + /// /// Any custom base layers this humanoid might have. See: /// limb transplants (potentially), robotic arms, etc. @@ -85,6 +88,12 @@ public sealed partial class HumanoidAppearanceComponent : Component [ViewVariables(VVAccess.ReadOnly)] public Color? CachedFacialHairColor; + /// + /// Which layers of this humanoid that should be hidden on equipping a corresponding item.. + /// + [DataField] + public HashSet HideLayersOnEquip = [HumanoidVisualLayers.Hair]; + /// /// DeltaV - let paradox anomaly be cloned /// diff --git a/Content.Shared/Humanoid/HumanoidCharacterAppearance.cs b/Content.Shared/Humanoid/HumanoidCharacterAppearance.cs index f1f7de5c11..fc44ed5e3b 100644 --- a/Content.Shared/Humanoid/HumanoidCharacterAppearance.cs +++ b/Content.Shared/Humanoid/HumanoidCharacterAppearance.cs @@ -5,262 +5,286 @@ using Robust.Shared.Random; using Robust.Shared.Serialization; -namespace Content.Shared.Humanoid +namespace Content.Shared.Humanoid; + +[DataDefinition] +[Serializable, NetSerializable] +public sealed partial class HumanoidCharacterAppearance : ICharacterAppearance, IEquatable { - [DataDefinition] - [Serializable, NetSerializable] - public sealed partial class HumanoidCharacterAppearance : ICharacterAppearance - { - public HumanoidCharacterAppearance(string hairStyleId, - Color hairColor, - string facialHairStyleId, - Color facialHairColor, - Color eyeColor, - Color skinColor, - List markings) - { - HairStyleId = hairStyleId; - HairColor = ClampColor(hairColor); - FacialHairStyleId = facialHairStyleId; - FacialHairColor = ClampColor(facialHairColor); - EyeColor = ClampColor(eyeColor); - SkinColor = ClampColor(skinColor); - Markings = markings; - } + [DataField("hair")] + public string HairStyleId { get; set; } = HairStyles.DefaultHairStyle; - [DataField("hair")] - public string HairStyleId { get; private set; } + [DataField] + public Color HairColor { get; set; } = Color.Black; - [DataField("hairColor")] - public Color HairColor { get; private set; } + [DataField("facialHair")] + public string FacialHairStyleId { get; set; } = HairStyles.DefaultFacialHairStyle; - [DataField("facialHair")] - public string FacialHairStyleId { get; private set; } + [DataField] + public Color FacialHairColor { get; set; } = Color.Black; - [DataField("facialHairColor")] - public Color FacialHairColor { get; private set; } + [DataField] + public Color EyeColor { get; private set; } - [DataField("eyeColor")] - public Color EyeColor { get; private set; } + [DataField] + public Color SkinColor { get; private set; } - [DataField("skinColor")] - public Color SkinColor { get; private set; } + [DataField] + public List Markings { get; private set; } = new(); - [DataField("markings")] - public List Markings { get; private set; } + public HumanoidCharacterAppearance(string hairStyleId, + Color hairColor, + string facialHairStyleId, + Color facialHairColor, + Color eyeColor, + Color skinColor, + List markings) + { + HairStyleId = hairStyleId; + HairColor = ClampColor(hairColor); + FacialHairStyleId = facialHairStyleId; + FacialHairColor = ClampColor(facialHairColor); + EyeColor = ClampColor(eyeColor); + SkinColor = ClampColor(skinColor); + Markings = markings; + } - public HumanoidCharacterAppearance WithHairStyleName(string newName) - { - return new(newName, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings); - } + public HumanoidCharacterAppearance(HumanoidCharacterAppearance other) + : this( + other.HairStyleId, + other.HairColor, + other.FacialHairStyleId, + other.FacialHairColor, + other.EyeColor, + other.SkinColor, + new(other.Markings)) + { - public HumanoidCharacterAppearance WithHairColor(Color newColor) - { - return new(HairStyleId, newColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings); - } + } - public HumanoidCharacterAppearance WithFacialHairStyleName(string newName) - { - return new(HairStyleId, HairColor, newName, FacialHairColor, EyeColor, SkinColor, Markings); - } + public HumanoidCharacterAppearance WithHairStyleName(string newName) + { + return new(newName, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings); + } - public HumanoidCharacterAppearance WithFacialHairColor(Color newColor) - { - return new(HairStyleId, HairColor, FacialHairStyleId, newColor, EyeColor, SkinColor, Markings); - } + public HumanoidCharacterAppearance WithHairColor(Color newColor) + { + return new(HairStyleId, newColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, Markings); + } - public HumanoidCharacterAppearance WithEyeColor(Color newColor) - { - return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, newColor, SkinColor, Markings); - } + public HumanoidCharacterAppearance WithFacialHairStyleName(string newName) + { + return new(HairStyleId, HairColor, newName, FacialHairColor, EyeColor, SkinColor, Markings); + } - public HumanoidCharacterAppearance WithSkinColor(Color newColor) - { - return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, newColor, Markings); - } + public HumanoidCharacterAppearance WithFacialHairColor(Color newColor) + { + return new(HairStyleId, HairColor, FacialHairStyleId, newColor, EyeColor, SkinColor, Markings); + } - public HumanoidCharacterAppearance WithMarkings(List newMarkings) + public HumanoidCharacterAppearance WithEyeColor(Color newColor) + { + return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, newColor, SkinColor, Markings); + } + + public HumanoidCharacterAppearance WithSkinColor(Color newColor) + { + return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, newColor, Markings); + } + + public HumanoidCharacterAppearance WithMarkings(List newMarkings) + { + return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, newMarkings); + } + + public static HumanoidCharacterAppearance DefaultWithSpecies(string species) + { + var speciesPrototype = IoCManager.Resolve().Index(species); + var skinColor = speciesPrototype.SkinColoration switch { - return new(HairStyleId, HairColor, FacialHairStyleId, FacialHairColor, EyeColor, SkinColor, newMarkings); - } + HumanoidSkinColor.HumanToned => Humanoid.SkinColor.HumanSkinTone(speciesPrototype.DefaultHumanSkinTone), + HumanoidSkinColor.Hues => speciesPrototype.DefaultSkinTone, + HumanoidSkinColor.TintedHues => Humanoid.SkinColor.TintedHues(speciesPrototype.DefaultSkinTone), + HumanoidSkinColor.VoxFeathers => Humanoid.SkinColor.ClosestVoxColor(speciesPrototype.DefaultSkinTone), + HumanoidSkinColor.TintedHuesSkin => Humanoid.SkinColor.TintedHuesSkin(speciesPrototype.DefaultSkinTone, speciesPrototype.DefaultSkinTone), + _ => Humanoid.SkinColor.ValidHumanSkinTone, + }; - public HumanoidCharacterAppearance() : this( + return new( HairStyles.DefaultHairStyle, Color.Black, HairStyles.DefaultFacialHairStyle, Color.Black, Color.Black, - Humanoid.SkinColor.ValidHumanSkinTone, + skinColor, new () - ) - { - } - - public static HumanoidCharacterAppearance DefaultWithSpecies(string species) - { - var speciesPrototype = IoCManager.Resolve().Index(species); - var skinColor = speciesPrototype.SkinColoration switch - { - HumanoidSkinColor.HumanToned => Humanoid.SkinColor.HumanSkinTone(speciesPrototype.DefaultHumanSkinTone), - HumanoidSkinColor.Hues => speciesPrototype.DefaultSkinTone, - HumanoidSkinColor.TintedHues => Humanoid.SkinColor.TintedHues(speciesPrototype.DefaultSkinTone), - // DeltaV - Blended tint for moths - HumanoidSkinColor.TintedHuesSkin => Humanoid.SkinColor.TintedHuesSkin(speciesPrototype.DefaultSkinTone, speciesPrototype.DefaultSkinTone), - _ => Humanoid.SkinColor.ValidHumanSkinTone - }; - - return new( - HairStyles.DefaultHairStyle, - Color.Black, - HairStyles.DefaultFacialHairStyle, - Color.Black, - Color.Black, - skinColor, - new () - ); - } - - private static IReadOnlyList RealisticEyeColors = new List - { - Color.Brown, - Color.Gray, - Color.Azure, - Color.SteelBlue, - Color.Black - }; + ); + } - public static HumanoidCharacterAppearance Random(string species, Sex sex) - { - var random = IoCManager.Resolve(); - var markingManager = IoCManager.Resolve(); - var hairStyles = markingManager.MarkingsByCategoryAndSpecies(MarkingCategories.Hair, species).Keys.ToList(); - var facialHairStyles = markingManager.MarkingsByCategoryAndSpecies(MarkingCategories.FacialHair, species).Keys.ToList(); + private static IReadOnlyList RealisticEyeColors = new List + { + Color.Brown, + Color.Gray, + Color.Azure, + Color.SteelBlue, + Color.Black + }; + + public static HumanoidCharacterAppearance Random(string species, Sex sex) + { + var random = IoCManager.Resolve(); + var markingManager = IoCManager.Resolve(); + var hairStyles = markingManager.MarkingsByCategoryAndSpecies(MarkingCategories.Hair, species).Keys.ToList(); + var facialHairStyles = markingManager.MarkingsByCategoryAndSpecies(MarkingCategories.FacialHair, species).Keys.ToList(); - var newHairStyle = hairStyles.Count > 0 - ? random.Pick(hairStyles) - : HairStyles.DefaultHairStyle; + var newHairStyle = hairStyles.Count > 0 + ? random.Pick(hairStyles) + : HairStyles.DefaultHairStyle; - var newFacialHairStyle = facialHairStyles.Count == 0 || sex == Sex.Female - ? HairStyles.DefaultFacialHairStyle - : random.Pick(facialHairStyles); + var newFacialHairStyle = facialHairStyles.Count == 0 || sex == Sex.Female + ? HairStyles.DefaultFacialHairStyle + : random.Pick(facialHairStyles); - var newHairColor = random.Pick(HairStyles.RealisticHairColors); - newHairColor = newHairColor - .WithRed(RandomizeColor(newHairColor.R)) - .WithGreen(RandomizeColor(newHairColor.G)) - .WithBlue(RandomizeColor(newHairColor.B)); + var newHairColor = random.Pick(HairStyles.RealisticHairColors); + newHairColor = newHairColor + .WithRed(RandomizeColor(newHairColor.R)) + .WithGreen(RandomizeColor(newHairColor.G)) + .WithBlue(RandomizeColor(newHairColor.B)); - // TODO: Add random markings + // TODO: Add random markings - var newEyeColor = random.Pick(RealisticEyeColors); + var newEyeColor = random.Pick(RealisticEyeColors); - var skinType = IoCManager.Resolve().Index(species).SkinColoration; + var skinType = IoCManager.Resolve().Index(species).SkinColoration; var skinTone = IoCManager.Resolve().Index(species).DefaultSkinTone; // DeltaV, required for tone blending - var newSkinColor = Humanoid.SkinColor.ValidHumanSkinTone; - switch (skinType) - { - case HumanoidSkinColor.HumanToned: - var tone = random.Next(0, 100); - newSkinColor = Humanoid.SkinColor.HumanSkinTone(tone); - break; - case HumanoidSkinColor.Hues: - case HumanoidSkinColor.TintedHues: - var rbyte = random.NextByte(); - var gbyte = random.NextByte(); - var bbyte = random.NextByte(); - newSkinColor = new Color(rbyte, gbyte, bbyte); - break; - case HumanoidSkinColor.TintedHuesSkin: // DeltaV, tone blending - rbyte = random.NextByte(); - gbyte = random.NextByte(); - bbyte = random.NextByte(); - newSkinColor = new Color(rbyte, gbyte, bbyte); - break; - } - - if (skinType == HumanoidSkinColor.TintedHues) - { + var newSkinColor = new Color(random.NextFloat(1), random.NextFloat(1), random.NextFloat(1), 1); + switch (skinType) + { + case HumanoidSkinColor.HumanToned: + var tone = Math.Round(Humanoid.SkinColor.HumanSkinToneFromColor(newSkinColor)); + newSkinColor = Humanoid.SkinColor.HumanSkinTone((int)tone); + break; + case HumanoidSkinColor.Hues: + break; + case HumanoidSkinColor.TintedHues: newSkinColor = Humanoid.SkinColor.ValidTintedHuesSkinTone(newSkinColor); - } - - if (skinType == HumanoidSkinColor.TintedHuesSkin) // DeltaV, tone blending - { + break; + case HumanoidSkinColor.TintedHuesSkin: newSkinColor = Humanoid.SkinColor.ValidTintedHuesSkinTone(skinTone, newSkinColor); - } + break; + case HumanoidSkinColor.VoxFeathers: + newSkinColor = Humanoid.SkinColor.ProportionalVoxColor(newSkinColor); + break; + } - return new HumanoidCharacterAppearance(newHairStyle, newHairColor, newFacialHairStyle, newHairColor, newEyeColor, newSkinColor, new ()); + return new HumanoidCharacterAppearance(newHairStyle, newHairColor, newFacialHairStyle, newHairColor, newEyeColor, newSkinColor, new ()); - float RandomizeColor(float channel) - { - return MathHelper.Clamp01(channel + random.Next(-25, 25) / 100f); - } + float RandomizeColor(float channel) + { + return MathHelper.Clamp01(channel + random.Next(-25, 25) / 100f); } + } + + public static Color ClampColor(Color color) + { + return new(color.RByte, color.GByte, color.BByte); + } + + public static HumanoidCharacterAppearance EnsureValid(HumanoidCharacterAppearance appearance, string species, Sex sex) + { + var hairStyleId = appearance.HairStyleId; + var facialHairStyleId = appearance.FacialHairStyleId; + + var hairColor = ClampColor(appearance.HairColor); + var facialHairColor = ClampColor(appearance.FacialHairColor); + var eyeColor = ClampColor(appearance.EyeColor); - public static Color ClampColor(Color color) + var proto = IoCManager.Resolve(); + var markingManager = IoCManager.Resolve(); + + if (!markingManager.MarkingsByCategory(MarkingCategories.Hair).ContainsKey(hairStyleId)) { - return new(color.RByte, color.GByte, color.BByte); + hairStyleId = HairStyles.DefaultHairStyle; } - public static HumanoidCharacterAppearance EnsureValid(HumanoidCharacterAppearance appearance, string species, Sex sex) + if (!markingManager.MarkingsByCategory(MarkingCategories.FacialHair).ContainsKey(facialHairStyleId)) { - var hairStyleId = appearance.HairStyleId; - var facialHairStyleId = appearance.FacialHairStyleId; - - var hairColor = ClampColor(appearance.HairColor); - var facialHairColor = ClampColor(appearance.FacialHairColor); - var eyeColor = ClampColor(appearance.EyeColor); + facialHairStyleId = HairStyles.DefaultFacialHairStyle; + } - var proto = IoCManager.Resolve(); - var markingManager = IoCManager.Resolve(); + var markingSet = new MarkingSet(); + var skinColor = appearance.SkinColor; + if (proto.TryIndex(species, out SpeciesPrototype? speciesProto)) + { + markingSet = new MarkingSet(appearance.Markings, speciesProto.MarkingPoints, markingManager, proto); + markingSet.EnsureValid(markingManager); - if (!markingManager.MarkingsByCategory(MarkingCategories.Hair).ContainsKey(hairStyleId)) + if (!Humanoid.SkinColor.VerifySkinColor(speciesProto.SkinColoration, skinColor)) { - hairStyleId = HairStyles.DefaultHairStyle; + skinColor = Humanoid.SkinColor.ValidSkinTone(speciesProto.SkinColoration, skinColor); } - if (!markingManager.MarkingsByCategory(MarkingCategories.FacialHair).ContainsKey(facialHairStyleId)) - { - facialHairStyleId = HairStyles.DefaultFacialHairStyle; - } + markingSet.EnsureSpecies(species, skinColor, markingManager); + markingSet.EnsureSexes(sex, markingManager); + } - var markingSet = new MarkingSet(); - var skinColor = appearance.SkinColor; - if (proto.TryIndex(species, out SpeciesPrototype? speciesProto)) - { - markingSet = new MarkingSet(appearance.Markings, speciesProto.MarkingPoints, markingManager, proto); - markingSet.EnsureValid(markingManager); + return new HumanoidCharacterAppearance( + hairStyleId, + hairColor, + facialHairStyleId, + facialHairColor, + eyeColor, + skinColor, + markingSet.GetForwardEnumerator().ToList()); + } - if (!Humanoid.SkinColor.VerifySkinColor(speciesProto.SkinColoration, skinColor)) - { - skinColor = Humanoid.SkinColor.ValidSkinTone(speciesProto.SkinColoration, skinColor); - } + public bool MemberwiseEquals(ICharacterAppearance maybeOther) + { + return + maybeOther is HumanoidCharacterAppearance other + && HairStyleId == other.HairStyleId + && HairColor.Equals(other.HairColor) + && FacialHairStyleId == other.FacialHairStyleId + && FacialHairColor.Equals(other.FacialHairColor) + && EyeColor.Equals(other.EyeColor) + && SkinColor.Equals(other.SkinColor) + && Markings.SequenceEqual(other.Markings); + } - markingSet.EnsureSpecies(species, skinColor, markingManager); - markingSet.EnsureSexes(sex, markingManager); - } + public bool Equals(HumanoidCharacterAppearance? other) + { + if (ReferenceEquals(null, other)) + return false; + if (ReferenceEquals(this, other)) + return true; + return HairStyleId == other.HairStyleId + && HairColor.Equals(other.HairColor) + && FacialHairStyleId == other.FacialHairStyleId + && FacialHairColor.Equals(other.FacialHairColor) + && EyeColor.Equals(other.EyeColor) + && SkinColor.Equals(other.SkinColor) + && Markings.SequenceEqual(other.Markings); + } - return new HumanoidCharacterAppearance( - hairStyleId, - hairColor, - facialHairStyleId, - facialHairColor, - eyeColor, - skinColor, - markingSet.GetForwardEnumerator().ToList()); - } + public override bool Equals(object? obj) + { + return ReferenceEquals(this, obj) + || obj is HumanoidCharacterAppearance other + && Equals(other); + } - public bool MemberwiseEquals(ICharacterAppearance maybeOther) - { - if (maybeOther is not HumanoidCharacterAppearance other) return false; - if (HairStyleId != other.HairStyleId) return false; - if (!HairColor.Equals(other.HairColor)) return false; - if (FacialHairStyleId != other.FacialHairStyleId) return false; - if (!FacialHairColor.Equals(other.FacialHairColor)) return false; - if (!EyeColor.Equals(other.EyeColor)) return false; - if (!SkinColor.Equals(other.SkinColor)) return false; - if (!Markings.SequenceEqual(other.Markings)) return false; - return true; - } + public override int GetHashCode() + { + return HashCode.Combine( + HairStyleId, + HairColor, + FacialHairStyleId, + FacialHairColor, + EyeColor, + SkinColor, + Markings); } + + public HumanoidCharacterAppearance Clone() => new(this); } diff --git a/Content.Shared/Humanoid/HumanoidProfileExport.cs b/Content.Shared/Humanoid/HumanoidProfileExport.cs new file mode 100644 index 0000000000..4f020cce19 --- /dev/null +++ b/Content.Shared/Humanoid/HumanoidProfileExport.cs @@ -0,0 +1,17 @@ +using Content.Shared.Preferences; + +namespace Content.Shared.Humanoid; + +/// Holds all of the data for importing / exporting character profiles. +[DataDefinition] +public sealed partial class HumanoidProfileExport +{ + [DataField] + public string ForkId; + + [DataField] + public int Version = 1; + + [DataField(required: true)] + public HumanoidCharacterProfile Profile = default!; +} diff --git a/Content.Shared/Humanoid/HumanoidVisualLayersExtension.cs b/Content.Shared/Humanoid/HumanoidVisualLayersExtension.cs index 7286024e25..0e1c8caa26 100644 --- a/Content.Shared/Humanoid/HumanoidVisualLayersExtension.cs +++ b/Content.Shared/Humanoid/HumanoidVisualLayersExtension.cs @@ -47,18 +47,30 @@ public static IEnumerable Sublayers(HumanoidVisualLayers l yield return HumanoidVisualLayers.LArm; yield return HumanoidVisualLayers.LHand; break; + case HumanoidVisualLayers.LHand: + yield return HumanoidVisualLayers.LHand; + break; case HumanoidVisualLayers.RArm: yield return HumanoidVisualLayers.RArm; yield return HumanoidVisualLayers.RHand; break; + case HumanoidVisualLayers.RHand: + yield return HumanoidVisualLayers.RHand; + break; case HumanoidVisualLayers.LLeg: yield return HumanoidVisualLayers.LLeg; yield return HumanoidVisualLayers.LFoot; break; + case HumanoidVisualLayers.LFoot: + yield return HumanoidVisualLayers.LFoot; + break; case HumanoidVisualLayers.RLeg: yield return HumanoidVisualLayers.RLeg; yield return HumanoidVisualLayers.RFoot; break; + case HumanoidVisualLayers.RFoot: + yield return HumanoidVisualLayers.RFoot; + break; case HumanoidVisualLayers.Chest: yield return HumanoidVisualLayers.Chest; yield return HumanoidVisualLayers.Wings; // Parkstation-Wings diff --git a/Content.Shared/Humanoid/Markings/MarkingCategories.cs b/Content.Shared/Humanoid/Markings/MarkingCategories.cs index ea2fb03493..f61de0add0 100644 --- a/Content.Shared/Humanoid/Markings/MarkingCategories.cs +++ b/Content.Shared/Humanoid/Markings/MarkingCategories.cs @@ -12,8 +12,14 @@ public enum MarkingCategories : byte HeadSide, Snout, Chest, - Arms, - Legs, + RightArm, + RightHand, + LeftArm, + LeftHand, + RightLeg, + RightFoot, + LeftLeg, + LeftFoot, Wings, // Parkstation-Wings Tail, Overlay @@ -32,14 +38,14 @@ public static MarkingCategories FromHumanoidVisualLayers(HumanoidVisualLayers la HumanoidVisualLayers.HeadSide => MarkingCategories.HeadSide, HumanoidVisualLayers.Snout => MarkingCategories.Snout, HumanoidVisualLayers.Chest => MarkingCategories.Chest, - HumanoidVisualLayers.RArm => MarkingCategories.Arms, - HumanoidVisualLayers.LArm => MarkingCategories.Arms, - HumanoidVisualLayers.RHand => MarkingCategories.Arms, - HumanoidVisualLayers.LHand => MarkingCategories.Arms, - HumanoidVisualLayers.LLeg => MarkingCategories.Legs, - HumanoidVisualLayers.RLeg => MarkingCategories.Legs, - HumanoidVisualLayers.LFoot => MarkingCategories.Legs, - HumanoidVisualLayers.RFoot => MarkingCategories.Legs, + HumanoidVisualLayers.RArm => MarkingCategories.RightArm, + HumanoidVisualLayers.LArm => MarkingCategories.LeftArm, + HumanoidVisualLayers.RHand => MarkingCategories.RightHand, + HumanoidVisualLayers.LHand => MarkingCategories.LeftHand, + HumanoidVisualLayers.LLeg => MarkingCategories.LeftLeg, + HumanoidVisualLayers.RLeg => MarkingCategories.RightLeg, + HumanoidVisualLayers.LFoot => MarkingCategories.LeftFoot, + HumanoidVisualLayers.RFoot => MarkingCategories.RightFoot, HumanoidVisualLayers.Wings => MarkingCategories.Wings, // Parkstation-Wings HumanoidVisualLayers.Tail => MarkingCategories.Tail, _ => MarkingCategories.Overlay diff --git a/Content.Shared/Humanoid/NamingSystem.cs b/Content.Shared/Humanoid/NamingSystem.cs index d4cca026ef..c1e861c0d6 100644 --- a/Content.Shared/Humanoid/NamingSystem.cs +++ b/Content.Shared/Humanoid/NamingSystem.cs @@ -40,6 +40,9 @@ public string GetName(string species, Gender? gender = null) case SpeciesNaming.FirstDashFirst: return Loc.GetString("namepreset-firstdashfirst", ("first1", GetFirstName(speciesProto, gender)), ("first2", GetFirstName(speciesProto, gender))); + case SpeciesNaming.FirstDashLast: + return Loc.GetString("namepreset-firstdashlast", + ("first", GetFirstName(speciesProto, gender)), ("last", GetLastName(speciesProto))); case SpeciesNaming.FirstLast: default: return Loc.GetString("namepreset-firstlast", diff --git a/Content.Shared/Humanoid/Prototypes/SpeciesPrototype.cs b/Content.Shared/Humanoid/Prototypes/SpeciesPrototype.cs index 340ce5acd0..36b1e2387b 100644 --- a/Content.Shared/Humanoid/Prototypes/SpeciesPrototype.cs +++ b/Content.Shared/Humanoid/Prototypes/SpeciesPrototype.cs @@ -75,6 +75,12 @@ public sealed partial class SpeciesPrototype : IPrototype [DataField(required: true)] public EntProtoId DollPrototype { get; private set; } + /// + /// Allow Custom Specie Name for this Specie. + /// + [DataField] + public Boolean CustomName { get; private set; } = false; + /// /// Method of skin coloration used by the species. /// @@ -121,6 +127,12 @@ public sealed partial class SpeciesPrototype : IPrototype [DataField] public int MaxAge = 120; + /// + /// The minimum height and width ratio for this species + /// + [DataField] + public float SizeRatio = 1.2f; + /// /// The minimum height for this species /// @@ -179,4 +191,5 @@ public enum SpeciesNaming : byte LastNoFirst, //End of Nyano - Summary: for Oni naming TheFirstofLast, + FirstDashLast, } diff --git a/Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs b/Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs index ece4b59e91..f33c65b591 100644 --- a/Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs +++ b/Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs @@ -1,15 +1,26 @@ +using System.IO; using System.Linq; using System.Numerics; using Content.Shared.Decals; using Content.Shared.Examine; using Content.Shared.Humanoid.Markings; using Content.Shared.Humanoid.Prototypes; +using Content.Shared.Humanoid.Events; using Content.Shared.IdentityManagement; using Content.Shared.Preferences; using Content.Shared.HeightAdjust; +using Microsoft.Extensions.Configuration; +using Robust.Shared; +using Robust.Shared.Configuration; using Robust.Shared.GameObjects.Components.Localization; using Robust.Shared.Network; +using Robust.Shared.Player; using Robust.Shared.Prototypes; +using Content.Shared.Shadowkin; +using Robust.Shared.Serialization.Manager; +using Robust.Shared.Serialization.Markdown; +using Robust.Shared.Utility; +using YamlDotNet.RepresentationModel; namespace Content.Shared.Humanoid; @@ -24,9 +35,11 @@ namespace Content.Shared.Humanoid; /// public abstract class SharedHumanoidAppearanceSystem : EntitySystem { + [Dependency] private readonly IConfigurationManager _cfgManager = default!; [Dependency] private readonly INetManager _netManager = default!; [Dependency] private readonly IPrototypeManager _proto = default!; [Dependency] private readonly MarkingManager _markingManager = default!; + [Dependency] private readonly ISerializationManager _serManager = default!; [Dependency] private readonly HeightAdjustSystem _heightAdjust = default!; [ValidatePrototypeId] @@ -40,12 +53,39 @@ public override void Initialize() SubscribeLocalEvent(OnExamined); } + public DataNode ToDataNode(HumanoidCharacterProfile profile) + { + var export = new HumanoidProfileExport + { + ForkId = _cfgManager.GetCVar(CVars.BuildForkId), + Profile = profile, + }; + + var dataNode = _serManager.WriteValue(export, alwaysWrite: true, notNullableOverride: true); + return dataNode; + } + + public HumanoidCharacterProfile FromStream(Stream stream, ICommonSession session) + { + using var reader = new StreamReader(stream, EncodingHelpers.UTF8); + var yamlStream = new YamlStream(); + yamlStream.Load(reader); + + var root = yamlStream.Documents[0].RootNode; + var export = _serManager.Read(root.ToDataNode(), notNullableOverride: true); + + // Add custom handling here for forks / version numbers if you care + + var profile = export.Profile; + var collection = IoCManager.Instance; + profile.EnsureValid(session, collection!); + return profile; + } + private void OnInit(EntityUid uid, HumanoidAppearanceComponent humanoid, ComponentInit args) { if (string.IsNullOrEmpty(humanoid.Species) || _netManager.IsClient && !IsClientSide(uid)) - { return; - } if (string.IsNullOrEmpty(humanoid.Initial) || !_proto.TryIndex(humanoid.Initial, out HumanoidProfilePrototype? startingSet)) @@ -56,9 +96,7 @@ private void OnInit(EntityUid uid, HumanoidAppearanceComponent humanoid, Compone // Do this first, because profiles currently do not support custom base layers foreach (var (layer, info) in startingSet.CustomBaseLayers) - { humanoid.CustomBaseLayers.Add(layer, info); - } LoadProfile(uid, startingSet.Profile, humanoid); } @@ -66,8 +104,14 @@ private void OnInit(EntityUid uid, HumanoidAppearanceComponent humanoid, Compone private void OnExamined(EntityUid uid, HumanoidAppearanceComponent component, ExaminedEvent args) { var identity = Identity.Entity(uid, EntityManager); - var species = GetSpeciesRepresentation(component.Species).ToLower(); + var species = GetSpeciesRepresentation(component.Species, component.CustomSpecieName).ToLower(); var age = GetAgeRepresentation(component.Species, component.Age); + if (HasComp(uid)) + { + var color = component.EyeColor.Name(); + if (color != null) + age = Loc.GetString("identity-eye-shadowkin", ("color", color)); + } args.PushText(Loc.GetString("humanoid-appearance-component-examine", ("user", identity), ("age", age), ("species", species))); } @@ -110,9 +154,7 @@ public void SetLayersVisibility(EntityUid uid, IEnumerable var dirty = false; foreach (var layer in layers) - { SetLayerVisibility(uid, humanoid, layer, visible, permanent, ref dirty); - } if (dirty) Dirty(humanoid); @@ -153,9 +195,7 @@ protected virtual void SetLayerVisibility( public void SetSpecies(EntityUid uid, string species, bool sync = true, HumanoidAppearanceComponent? humanoid = null) { if (!Resolve(uid, ref humanoid) || !_proto.TryIndex(species, out var prototype)) - { return; - } humanoid.Species = species; humanoid.MarkingSet.EnsureSpecies(species, humanoid.SkinColor, _markingManager); @@ -181,14 +221,10 @@ public virtual void SetSkinColor(EntityUid uid, Color skinColor, bool sync = tru return; if (!_proto.TryIndex(humanoid.Species, out var species)) - { return; - } if (verify && !SkinColor.VerifySkinColor(species.SkinColoration, skinColor)) - { skinColor = SkinColor.ValidSkinTone(species.SkinColoration, skinColor); - } humanoid.SkinColor = skinColor; @@ -259,9 +295,7 @@ public void SetSex(EntityUid uid, Sex sex, bool sync = true, HumanoidAppearanceC RaiseLocalEvent(uid, new SexChangedEvent(oldSex, sex)); if (sync) - { Dirty(humanoid); - } } /// @@ -331,13 +365,13 @@ public void SetScale(EntityUid uid, Vector2 scale, bool sync = true, HumanoidApp public virtual void LoadProfile(EntityUid uid, HumanoidCharacterProfile profile, HumanoidAppearanceComponent? humanoid = null) { if (!Resolve(uid, ref humanoid)) - { return; - } SetSpecies(uid, profile.Species, false, humanoid); SetSex(uid, profile.Sex, false, humanoid); humanoid.EyeColor = profile.Appearance.EyeColor; + var ev = new EyeColorInitEvent(); + RaiseLocalEvent(uid, ref ev); SetSkinColor(uid, profile.Appearance.SkinColor, false); @@ -350,13 +384,9 @@ public virtual void LoadProfile(EntityUid uid, HumanoidCharacterProfile profile, if (_markingManager.TryGetMarking(marking, out var prototype)) { if (!prototype.ForcedColoring) - { AddMarking(uid, marking.MarkingId, marking.MarkingColors, false); - } else - { markingFColored.Add(marking, prototype); - } } } @@ -369,15 +399,11 @@ public virtual void LoadProfile(EntityUid uid, HumanoidCharacterProfile profile, if (_markingManager.Markings.TryGetValue(profile.Appearance.HairStyleId, out var hairPrototype) && _markingManager.CanBeApplied(profile.Species, profile.Sex, hairPrototype, _proto)) - { AddMarking(uid, profile.Appearance.HairStyleId, hairColor, false); - } if (_markingManager.Markings.TryGetValue(profile.Appearance.FacialHairStyleId, out var facialHairPrototype) && _markingManager.CanBeApplied(profile.Species, profile.Sex, facialHairPrototype, _proto)) - { AddMarking(uid, profile.Appearance.FacialHairStyleId, facialHairColor, false); - } humanoid.MarkingSet.EnsureSpecies(profile.Species, profile.Appearance.SkinColor, _markingManager, _proto); @@ -397,17 +423,25 @@ public virtual void LoadProfile(EntityUid uid, HumanoidCharacterProfile profile, humanoid.Gender = profile.Gender; if (TryComp(uid, out var grammar)) - { grammar.Gender = profile.Gender; - } humanoid.Age = profile.Age; - _heightAdjust.SetScale(uid, new Vector2(profile.Width, profile.Height)); + humanoid.CustomSpecieName = profile.Customspeciename; + + var species = _proto.Index(humanoid.Species); + + if (profile.Height <= 0 || profile.Width <= 0) + SetScale(uid, new Vector2(species.DefaultWidth, species.DefaultHeight), true, humanoid); + else + SetScale(uid, new Vector2(profile.Width, profile.Height), true, humanoid); + + _heightAdjust.SetScale(uid, new Vector2(humanoid.Width, humanoid.Height)); humanoid.LastProfileLoaded = profile; // DeltaV - let paradox anomaly be cloned Dirty(humanoid); + RaiseLocalEvent(uid, new ProfileLoadFinishedEvent()); } /// @@ -423,9 +457,7 @@ public void AddMarking(EntityUid uid, string marking, Color? color = null, bool { if (!Resolve(uid, ref humanoid) || !_markingManager.Markings.TryGetValue(marking, out var prototype)) - { return; - } var markingObject = prototype.AsMarking(); markingObject.Forced = forced; @@ -446,9 +478,7 @@ public void AddMarking(EntityUid uid, string marking, Color? color = null, bool private void EnsureDefaultMarkings(EntityUid uid, HumanoidAppearanceComponent? humanoid) { if (!Resolve(uid, ref humanoid)) - { return; - } humanoid.MarkingSet.EnsureDefault(humanoid.SkinColor, humanoid.EyeColor, _markingManager); } @@ -465,9 +495,7 @@ public void AddMarking(EntityUid uid, string marking, IReadOnlyList color { if (!Resolve(uid, ref humanoid) || !_markingManager.Markings.TryGetValue(marking, out var prototype)) - { return; - } var markingObject = new Marking(marking, colors); markingObject.Forced = forced; @@ -480,12 +508,13 @@ public void AddMarking(EntityUid uid, string marking, IReadOnlyList color /// /// Takes ID of the species prototype, returns UI-friendly name of the species. /// - public string GetSpeciesRepresentation(string speciesId) + public string GetSpeciesRepresentation(string speciesId, string? customespeciename) { + if (!string.IsNullOrEmpty(customespeciename)) + return Loc.GetString(customespeciename); + if (_proto.TryIndex(speciesId, out var species)) - { return Loc.GetString(species.Name); - } Log.Error("Tried to get representation of unknown species: {speciesId}"); return Loc.GetString("humanoid-appearance-component-unknown-species"); @@ -500,14 +529,10 @@ public string GetAgeRepresentation(string species, int age) } if (age < speciesPrototype.YoungAge) - { return Loc.GetString("identity-age-young"); - } if (age < speciesPrototype.OldAge) - { return Loc.GetString("identity-age-middle-aged"); - } return Loc.GetString("identity-age-old"); } diff --git a/Content.Shared/Humanoid/SkinColor.cs b/Content.Shared/Humanoid/SkinColor.cs index dcc5c2d764..64ad82ac98 100644 --- a/Content.Shared/Humanoid/SkinColor.cs +++ b/Content.Shared/Humanoid/SkinColor.cs @@ -1,3 +1,6 @@ +using System.Security.Cryptography; +using Microsoft.VisualBasic.CompilerServices; + namespace Content.Shared.Humanoid; public static class SkinColor @@ -7,6 +10,13 @@ public static class SkinColor public const float MinHuesLightness = 0.175f; + public const float MinFeathersHue = 29f / 360; + public const float MaxFeathersHue = 174f / 360; + public const float MinFeathersSaturation = 20f / 100; + public const float MaxFeathersSaturation = 88f / 100; + public const float MinFeathersValue = 36f / 100; + public const float MaxFeathersValue = 55f / 100; + public static Color ValidHumanSkinTone => Color.FromHsv(new Vector4(0.07f, 0.2f, 1f, 1f)); /// @@ -159,6 +169,60 @@ public static bool VerifyTintedHues(Color color) return Color.ToHsl(color).Y <= MaxTintedHuesSaturation && Color.ToHsl(color).Z >= MinTintedHuesLightness; } + /// + /// Converts a Color proportionally to the allowed vox color range. + /// Will NOT preserve the specific input color even if it is within the allowed vox color range. + /// + /// Color to convert + /// Vox feather coloration + public static Color ProportionalVoxColor(Color color) + { + var newColor = Color.ToHsv(color); + + newColor.X = newColor.X * (MaxFeathersHue - MinFeathersHue) + MinFeathersHue; + newColor.Y = newColor.Y * (MaxFeathersSaturation - MinFeathersSaturation) + MinFeathersSaturation; + newColor.Z = newColor.Z * (MaxFeathersValue - MinFeathersValue) + MinFeathersValue; + + return Color.FromHsv(newColor); + } + + // /// + // /// Ensures the input Color is within the allowed vox color range. + // /// + // /// Color to convert + // /// The same Color if it was within the allowed range, or the closest matching Color otherwise + public static Color ClosestVoxColor(Color color) + { + var hsv = Color.ToHsv(color); + + hsv.X = Math.Clamp(hsv.X, MinFeathersHue, MaxFeathersHue); + hsv.Y = Math.Clamp(hsv.Y, MinFeathersSaturation, MaxFeathersSaturation); + hsv.Z = Math.Clamp(hsv.Z, MinFeathersValue, MaxFeathersValue); + + return Color.FromHsv(hsv); + } + + /// + /// Verify if this color is a valid vox feather coloration, or not. + /// + /// The color to verify + /// True if valid, false otherwise + public static bool VerifyVoxFeathers(Color color) + { + var colorHsv = Color.ToHsv(color); + + if (colorHsv.X < MinFeathersHue || colorHsv.X > MaxFeathersHue) + return false; + + if (colorHsv.Y < MinFeathersSaturation || colorHsv.Y > MaxFeathersSaturation) + return false; + + if (colorHsv.Z < MinFeathersValue || colorHsv.Z > MaxFeathersValue) + return false; + + return true; + } + /// /// This takes in a color, and returns a color guaranteed to be above MinHuesLightness /// @@ -189,6 +253,7 @@ public static bool VerifySkinColor(HumanoidSkinColor type, Color color) HumanoidSkinColor.TintedHues => VerifyTintedHues(color), HumanoidSkinColor.TintedHuesSkin => true, // DeltaV - Tone blending HumanoidSkinColor.Hues => VerifyHues(color), + HumanoidSkinColor.VoxFeathers => VerifyVoxFeathers(color), _ => false, }; } @@ -201,6 +266,7 @@ public static Color ValidSkinTone(HumanoidSkinColor type, Color color) HumanoidSkinColor.TintedHues => ValidTintedHuesSkinTone(color), HumanoidSkinColor.TintedHuesSkin => ValidTintedHuesSkinTone(color), // DeltaV - Tone blending HumanoidSkinColor.Hues => MakeHueValid(color), + HumanoidSkinColor.VoxFeathers => ClosestVoxColor(color), _ => color }; } @@ -210,6 +276,7 @@ public enum HumanoidSkinColor : byte { HumanToned, Hues, + VoxFeathers, // Vox feathers are limited to a specific color range TintedHues, //This gives a color tint to a humanoid's skin (10% saturation with full hue range). TintedHuesSkin, // DeltaV - Default TintedHues assumes the texture will have the proper skin color, but moths dont } diff --git a/Content.Shared/Implants/SharedSubdermalImplantSystem.cs b/Content.Shared/Implants/SharedSubdermalImplantSystem.cs index 830d2270aa..a43d4fca72 100644 --- a/Content.Shared/Implants/SharedSubdermalImplantSystem.cs +++ b/Content.Shared/Implants/SharedSubdermalImplantSystem.cs @@ -1,5 +1,6 @@ using System.Linq; using Content.Shared.Actions; +using Content.Shared.Chat; using Content.Shared.Implants.Components; using Content.Shared.Interaction; using Content.Shared.Interaction.Events; @@ -29,6 +30,7 @@ public override void Initialize() SubscribeLocalEvent(RelayToImplantEvent); SubscribeLocalEvent(RelayToImplantEvent); SubscribeLocalEvent(RelayToImplantEvent); + SubscribeLocalEvent(RelayToImplantEvent); } private void OnInsert(EntityUid uid, SubdermalImplantComponent component, EntGotInsertedIntoContainerMessage args) diff --git a/Content.Shared/Input/ContentKeyFunctions.cs b/Content.Shared/Input/ContentKeyFunctions.cs index dac780783c..699acc87ad 100644 --- a/Content.Shared/Input/ContentKeyFunctions.cs +++ b/Content.Shared/Input/ContentKeyFunctions.cs @@ -25,6 +25,7 @@ public static class ContentKeyFunctions public static readonly BoundKeyFunction CycleChatChannelBackward = "CycleChatChannelBackward"; public static readonly BoundKeyFunction EscapeContext = "EscapeContext"; public static readonly BoundKeyFunction OpenCharacterMenu = "OpenCharacterMenu"; + public static readonly BoundKeyFunction OpenEmotesMenu = "OpenEmotesMenu"; public static readonly BoundKeyFunction OpenLanguageMenu = "OpenLanguageMenu"; public static readonly BoundKeyFunction OpenCraftingMenu = "OpenCraftingMenu"; public static readonly BoundKeyFunction OpenGuidebook = "OpenGuidebook"; @@ -43,6 +44,7 @@ public static class ContentKeyFunctions public static readonly BoundKeyFunction MovePulledObject = "MovePulledObject"; public static readonly BoundKeyFunction ReleasePulledObject = "ReleasePulledObject"; public static readonly BoundKeyFunction MouseMiddle = "MouseMiddle"; + public static readonly BoundKeyFunction ToggleRoundEndSummaryWindow = "ToggleRoundEndSummaryWindow"; public static readonly BoundKeyFunction OpenEntitySpawnWindow = "OpenEntitySpawnWindow"; public static readonly BoundKeyFunction OpenSandboxWindow = "OpenSandboxWindow"; public static readonly BoundKeyFunction OpenTileSpawnWindow = "OpenTileSpawnWindow"; @@ -57,6 +59,14 @@ public static class ContentKeyFunctions public static readonly BoundKeyFunction ResetZoom = "ResetZoom"; public static readonly BoundKeyFunction OfferItem = "OfferItem"; public static readonly BoundKeyFunction ToggleStanding = "ToggleStanding"; + public static readonly BoundKeyFunction ToggleCrawlingUnder = "ToggleCrawlingUnder"; + public static readonly BoundKeyFunction LookUp = "LookUp"; + public static readonly BoundKeyFunction TargetHead = "TargetHead"; + public static readonly BoundKeyFunction TargetTorso = "TargetTorso"; + public static readonly BoundKeyFunction TargetLeftArm = "TargetLeftArm"; + public static readonly BoundKeyFunction TargetRightArm = "TargetRightArm"; + public static readonly BoundKeyFunction TargetLeftLeg = "TargetLeftLeg"; + public static readonly BoundKeyFunction TargetRightLeg = "TargetRightLeg"; public static readonly BoundKeyFunction ArcadeUp = "ArcadeUp"; public static readonly BoundKeyFunction ArcadeDown = "ArcadeDown"; diff --git a/Content.Shared/Interaction/Components/ClumsyComponent.cs b/Content.Shared/Interaction/Components/ClumsyComponent.cs index 5b72fc224c..824696c838 100644 --- a/Content.Shared/Interaction/Components/ClumsyComponent.cs +++ b/Content.Shared/Interaction/Components/ClumsyComponent.cs @@ -1,22 +1,24 @@ using Content.Shared.Damage; using Robust.Shared.Audio; +using Robust.Shared.GameStates; -namespace Content.Shared.Interaction.Components +namespace Content.Shared.Interaction.Components; + +/// +/// A simple clumsy tag-component. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class ClumsyComponent : Component { /// - /// A simple clumsy tag-component. + /// Damage dealt to a clumsy character when they try to fire a gun. /// - [RegisterComponent] - public sealed partial class ClumsyComponent : Component - { - [DataField("clumsyDamage", required: true)] - [ViewVariables(VVAccess.ReadWrite)] - public DamageSpecifier ClumsyDamage = default!; + [DataField(required: true), AutoNetworkedField] + public DamageSpecifier ClumsyDamage = default!; - /// - /// Sound to play when clumsy interactions fail - /// - [DataField("clumsySound")] - public SoundSpecifier ClumsySound = new SoundPathSpecifier("/Audio/Items/bikehorn.ogg"); - } + /// + /// Sound to play when clumsy interactions fail. + /// + [DataField] + public SoundSpecifier ClumsySound = new SoundPathSpecifier("/Audio/Items/bikehorn.ogg"); } diff --git a/Content.Shared/Interaction/SharedInteractionSystem.cs b/Content.Shared/Interaction/SharedInteractionSystem.cs index 8d49ce31f0..4c22bcb14e 100644 --- a/Content.Shared/Interaction/SharedInteractionSystem.cs +++ b/Content.Shared/Interaction/SharedInteractionSystem.cs @@ -1,11 +1,10 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using Content.Shared.ActionBlocker; -using Content.Shared.Administration; using Content.Shared.Administration.Logs; -using Content.Shared.Administration.Managers; using Content.Shared.CombatMode; using Content.Shared.Database; +using Content.Shared.Ghost; using Content.Shared.Hands; using Content.Shared.Hands.Components; using Content.Shared.Input; @@ -15,12 +14,13 @@ using Content.Shared.Inventory.Events; using Content.Shared.Item; using Content.Shared.Movement.Components; -using Content.Shared.Movement.Pulling.Components; using Content.Shared.Movement.Pulling.Systems; using Content.Shared.Physics; using Content.Shared.Popups; +using Content.Shared.Storage; using Content.Shared.Tag; using Content.Shared.Timing; +using Content.Shared.UserInterface; using Content.Shared.Verbs; using Content.Shared.Wall; using JetBrains.Annotations; @@ -36,6 +36,7 @@ using Robust.Shared.Random; using Robust.Shared.Serialization; using Robust.Shared.Timing; +using Robust.Shared.Utility; #pragma warning disable 618 @@ -50,12 +51,11 @@ public abstract partial class SharedInteractionSystem : EntitySystem [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly INetManager _net = default!; [Dependency] private readonly IMapManager _mapManager = default!; - [Dependency] private readonly ISharedAdminManager _adminManager = default!; [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; [Dependency] private readonly RotateToFaceSystem _rotateToFaceSystem = default!; [Dependency] private readonly SharedContainerSystem _containerSystem = default!; - [Dependency] private readonly SharedPhysicsSystem _sharedBroadphaseSystem = default!; + [Dependency] private readonly SharedPhysicsSystem _broadphase = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly SharedVerbSystem _verbSystem = default!; [Dependency] private readonly SharedPopupSystem _popupSystem = default!; @@ -64,6 +64,18 @@ public abstract partial class SharedInteractionSystem : EntitySystem [Dependency] private readonly InventorySystem _inventory = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly TagSystem _tagSystem = default!; + [Dependency] private readonly SharedUserInterfaceSystem _ui = default!; + + private EntityQuery _ignoreUiRangeQuery; + private EntityQuery _fixtureQuery; + private EntityQuery _itemQuery; + private EntityQuery _physicsQuery; + private EntityQuery _handsQuery; + private EntityQuery _relayQuery; + private EntityQuery _combatQuery; + private EntityQuery _wallMountQuery; + private EntityQuery _delayQuery; + private EntityQuery _uiQuery; private const CollisionGroup InRangeUnobstructedMask = CollisionGroup.Impassable | CollisionGroup.InteractImpassable; @@ -76,8 +88,22 @@ public abstract partial class SharedInteractionSystem : EntitySystem public override void Initialize() { + _ignoreUiRangeQuery = GetEntityQuery(); + _fixtureQuery = GetEntityQuery(); + _itemQuery = GetEntityQuery(); + _physicsQuery = GetEntityQuery(); + _handsQuery = GetEntityQuery(); + _relayQuery = GetEntityQuery(); + _combatQuery = GetEntityQuery(); + _wallMountQuery = GetEntityQuery(); + _delayQuery = GetEntityQuery(); + _uiQuery = GetEntityQuery(); + + SubscribeLocalEvent(HandleUserInterfaceRangeCheck); SubscribeLocalEvent(OnBoundInterfaceInteractAttempt); + SubscribeAllEvent(HandleInteractInventorySlotEvent); + SubscribeLocalEvent(OnRemoveAttempt); SubscribeLocalEvent(OnUnequip); SubscribeLocalEvent(OnUnequipHand); @@ -108,27 +134,57 @@ public override void Shutdown() /// private void OnBoundInterfaceInteractAttempt(BoundUserInterfaceMessageAttempt ev) { - if (ev.Sender.AttachedEntity is not { } user || !_actionBlockerSystem.CanInteract(user, ev.Target)) + _uiQuery.TryComp(ev.Target, out var uiComp); + if (!_actionBlockerSystem.CanInteract(ev.Actor, ev.Target)) { - ev.Cancel(); - return; + // We permit ghosts to open uis unless explicitly blocked + if (ev.Message is not OpenBoundInterfaceMessage || !HasComp(ev.Actor) || uiComp?.BlockSpectators == true) + { + ev.Cancel(); + return; + } } - // Check if the bound entity is accessible. Note that we allow admins to ignore this restriction, so that - // they can fiddle with UI's that people can't normally interact with (e.g., placing things directly into - // other people's backpacks). - if (!_containerSystem.IsInSameOrParentContainer(user, ev.Target) - && !CanAccessViaStorage(user, ev.Target) - && !_adminManager.HasAdminFlag(user, AdminFlags.Admin)) + var range = _ui.GetUiRange(ev.Target, ev.UiKey); + + // As long as range>0, the UI frame updates should have auto-closed the UI if it is out of range. + DebugTools.Assert(range <= 0 || UiRangeCheck(ev.Actor, ev.Target, range)); + + if (range <= 0 && !IsAccessible(ev.Actor, ev.Target)) { ev.Cancel(); return; } - if (!InRangeUnobstructed(user, ev.Target)) + if (uiComp == null) + return; + + if (uiComp.SingleUser && uiComp.CurrentSingleUser != null && uiComp.CurrentSingleUser != ev.Actor) { ev.Cancel(); + return; } + + if (!uiComp.RequireHands) + return; + + if (!_handsQuery.TryComp(ev.Actor, out var hands) || hands.Hands.Count == 0) + ev.Cancel(); + } + + private bool UiRangeCheck(Entity user, Entity target, float range) + { + if (!Resolve(target, ref target.Comp)) + return false; + + if (user.Owner == target.Owner) + return true; + + // Fast check: if the user is the parent of the entity (e.g., holding it), we always assume that it is in range + if (target.Comp.ParentUid == user.Owner) + return true; + + return InRangeAndAccessible(user, target, range) || _ignoreUiRangeQuery.HasComp(user); } /// @@ -185,10 +241,7 @@ private bool HandleTryPullObject(ICommonSession? session, EntityCoordinates coor if (!InRangeUnobstructed(userEntity.Value, uid, popup: true)) return false; - if (!TryComp(uid, out PullableComponent? pull)) - return false; - - _pullSystem.TogglePull(uid, userEntity.Value, pull); + _pullSystem.TogglePull(uid, userEntity.Value); return false; } @@ -264,7 +317,7 @@ private bool ShouldCheckAccess(EntityUid user) public bool CombatModeCanHandInteract(EntityUid user, EntityUid? target) { // Always allow attack in these cases - if (target == null || !TryComp(user, out var hands) || hands.ActiveHand?.HeldEntity is not null) + if (target == null || !_handsQuery.TryComp(user, out var hands) || hands.ActiveHand?.HeldEntity is not null) return false; // Only eat input if: @@ -272,7 +325,7 @@ public bool CombatModeCanHandInteract(EntityUid user, EntityUid? target) // - Target doesn't cancel should-interact event // This is intended to allow items to be picked up in combat mode, // but to also allow items to force attacks anyway (like mobs which are items, e.g. mice) - if (!HasComp(target)) + if (!_itemQuery.HasComp(target)) return false; var combatEv = new CombatModeShouldHandInteractEvent(); @@ -302,7 +355,7 @@ public void UserInteraction( bool checkAccess = true, bool checkCanUse = true) { - if (TryComp(user, out var relay) && relay.RelayEntity is not null) + if (_relayQuery.TryComp(user, out var relay) && relay.RelayEntity is not null) { // TODO this needs to be handled better. This probably bypasses many complex can-interact checks in weird roundabout ways. if (_actionBlockerSystem.CanInteract(user, target)) @@ -316,7 +369,7 @@ public void UserInteraction( if (target != null && Deleted(target.Value)) return; - if (!altInteract && TryComp(user, out var combatMode) && combatMode.IsInCombatMode) + if (!altInteract && _combatQuery.TryComp(user, out var combatMode) && combatMode.IsInCombatMode) { if (!CombatModeCanHandInteract(user, target)) return; @@ -338,10 +391,7 @@ public void UserInteraction( // Check if interacted entity is in the same container, the direct child, or direct parent of the user. // Also checks if the item is accessible via some storage UI (e.g., open backpack) - if (checkAccess - && target != null - && !_containerSystem.IsInSameOrParentContainer(user, target.Value) - && !CanAccessViaStorage(user, target.Value)) + if (checkAccess && target != null && !IsAccessible(user, target.Value)) return; var inRangeUnobstructed = target == null @@ -349,7 +399,7 @@ public void UserInteraction( : !checkAccess || InRangeUnobstructed(user, target.Value); // permits interactions with wall mounted entities // Does the user have hands? - if (!TryComp(user, out var hands) || hands.ActiveHand == null) + if (!_handsQuery.TryComp(user, out var hands) || hands.ActiveHand == null) { var ev = new InteractNoHandEvent(user, target, coordinates); RaiseLocalEvent(user, ev); @@ -489,7 +539,7 @@ public float UnobstructedDistance( predicate ??= _ => false; var ray = new CollisionRay(origin.Position, dir.Normalized(), collisionMask); - var rayResults = _sharedBroadphaseSystem.IntersectRayWithPredicate(origin.MapId, ray, dir.Length(), predicate.Invoke, false).ToList(); + var rayResults = _broadphase.IntersectRayWithPredicate(origin.MapId, ray, dir.Length(), predicate.Invoke, false).ToList(); if (rayResults.Count == 0) return dir.Length(); @@ -552,23 +602,29 @@ public bool InRangeUnobstructed( } var ray = new CollisionRay(origin.Position, dir.Normalized(), (int) collisionMask); - var rayResults = _sharedBroadphaseSystem.IntersectRayWithPredicate(origin.MapId, ray, length, predicate.Invoke, false).ToList(); + var rayResults = _broadphase.IntersectRayWithPredicate(origin.MapId, ray, length, predicate.Invoke, false).ToList(); return rayResults.Count == 0; } public bool InRangeUnobstructed( - EntityUid origin, - EntityUid other, + Entity origin, + Entity other, float range = InteractionRange, CollisionGroup collisionMask = InRangeUnobstructedMask, Ignored? predicate = null, bool popup = false) { - if (!TryComp(other, out var otherXform)) + if (!Resolve(other, ref other.Comp)) return false; - return InRangeUnobstructed(origin, other, otherXform.Coordinates, otherXform.LocalRotation, range, collisionMask, predicate, + return InRangeUnobstructed(origin, + other, + other.Comp.Coordinates, + other.Comp.LocalRotation, + range, + collisionMask, + predicate, popup); } @@ -600,8 +656,8 @@ public bool InRangeUnobstructed( /// True if the two points are within a given range without being obstructed. /// public bool InRangeUnobstructed( - EntityUid origin, - EntityUid other, + Entity origin, + Entity other, EntityCoordinates otherCoordinates, Angle otherAngle, float range = InteractionRange, @@ -609,10 +665,10 @@ public bool InRangeUnobstructed( Ignored? predicate = null, bool popup = false) { - Ignored combinedPredicate = e => e == origin || (predicate?.Invoke(e) ?? false); + Ignored combinedPredicate = e => e == origin.Owner || (predicate?.Invoke(e) ?? false); var inRange = true; MapCoordinates originPos = default; - var targetPos = otherCoordinates.ToMap(EntityManager, _transform); + var targetPos = _transform.ToMapCoordinates(otherCoordinates); Angle targetRot = default; // So essentially: @@ -622,23 +678,30 @@ public bool InRangeUnobstructed( // Alternatively we could check centre distances first though // that means we wouldn't be able to easily check overlap interactions. if (range > 0f && - TryComp(origin, out var fixtureA) && + _fixtureQuery.TryComp(origin, out var fixtureA) && // These fixture counts are stuff that has the component but no fixtures for (e.g. buttons). // At least until they get removed. fixtureA.FixtureCount > 0 && - TryComp(other, out var fixtureB) && + _fixtureQuery.TryComp(other, out var fixtureB) && fixtureB.FixtureCount > 0 && - TryComp(origin, out var xformA)) + Resolve(origin, ref origin.Comp)) { - var (worldPosA, worldRotA) = xformA.GetWorldPositionRotation(); + var (worldPosA, worldRotA) = origin.Comp.GetWorldPositionRotation(); var xfA = new Transform(worldPosA, worldRotA); var parentRotB = _transform.GetWorldRotation(otherCoordinates.EntityId); var xfB = new Transform(targetPos.Position, parentRotB + otherAngle); // Different map or the likes. - if (!_sharedBroadphaseSystem.TryGetNearest(origin, other, - out _, out _, out var distance, - xfA, xfB, fixtureA, fixtureB)) + if (!_broadphase.TryGetNearest( + origin, + other, + out _, + out _, + out var distance, + xfA, + xfB, + fixtureA, + fixtureB)) { inRange = false; } @@ -660,15 +723,15 @@ public bool InRangeUnobstructed( else { // We'll still do the raycast from the centres but we'll bump the range as we know they're in range. - originPos = xformA.MapPosition; + originPos = _transform.GetMapCoordinates(origin, xform: origin.Comp); range = (originPos.Position - targetPos.Position).Length(); } } // No fixtures, e.g. wallmounts. else { - originPos = Transform(origin).MapPosition; - var otherParent = Transform(other).ParentUid; + originPos = _transform.GetMapCoordinates(origin, origin); + var otherParent = (other.Comp ?? Transform(other)).ParentUid; targetRot = otherParent.IsValid() ? Transform(otherParent).LocalRotation + otherAngle : otherAngle; } @@ -719,13 +782,13 @@ private Ignored GetPredicate( { HashSet ignored = new(); - if (HasComp(target) && TryComp(target, out PhysicsComponent? physics) && physics.CanCollide) + if (_itemQuery.HasComp(target) && _physicsQuery.TryComp(target, out var physics) && physics.CanCollide) { // If the target is an item, we ignore any colliding entities. Currently done so that if items get stuck // inside of walls, users can still pick them up. - ignored.UnionWith(_sharedBroadphaseSystem.GetEntitiesIntersectingBody(target, (int) collisionMask, false, physics)); + ignored.UnionWith(_broadphase.GetEntitiesIntersectingBody(target, (int) collisionMask, false, physics)); } - else if (TryComp(target, out WallMountComponent? wallMount)) + else if (_wallMountQuery.TryComp(target, out var wallMount)) { // wall-mount exemptions may be restricted to a specific angle range.da @@ -743,13 +806,7 @@ private Ignored GetPredicate( ignored.UnionWith(grid.GetAnchoredEntities(targetCoords)); } - Ignored combinedPredicate = e => - { - return e == target - || (predicate?.Invoke(e) ?? false) - || ignored.Contains(e); - }; - + Ignored combinedPredicate = e => e == target || (predicate?.Invoke(e) ?? false) || ignored.Contains(e); return combinedPredicate; } @@ -946,10 +1003,8 @@ public bool InteractionActivate( bool checkUseDelay = true, bool checkAccess = true) { - UseDelayComponent? delayComponent = null; - if (checkUseDelay - && TryComp(used, out delayComponent) - && _useDelay.IsDelayed((used, delayComponent))) + _delayQuery.TryComp(used, out var delayComponent); + if (checkUseDelay && delayComponent != null && _useDelay.IsDelayed((used, delayComponent))) return false; if (checkCanInteract && !_actionBlockerSystem.CanInteract(user, used)) @@ -960,11 +1015,11 @@ public bool InteractionActivate( // Check if interacted entity is in the same container, the direct child, or direct parent of the user. // This is bypassed IF the interaction happened through an item slot (e.g., backpack UI) - if (checkAccess && !_containerSystem.IsInSameOrParentContainer(user, used) && !CanAccessViaStorage(user, used)) + if (checkAccess && !IsAccessible(user, used)) return false; // Does the user have hands? - if (!HasComp(user)) + if (!_handsQuery.HasComp(user)) return false; var activateMsg = new ActivateInWorldEvent(user, used); @@ -973,8 +1028,10 @@ public bool InteractionActivate( return false; DoContactInteraction(user, used, activateMsg); + // Still need to call this even without checkUseDelay in case this gets relayed from Activate. if (delayComponent != null) - _useDelay.TryResetDelay((used, delayComponent)); + _useDelay.TryResetDelay(used, component: delayComponent); + if (!activateMsg.WasLogged) _adminLogger.Add(LogType.InteractActivate, LogImpact.Low, $"{ToPrettyString(user):user} activated {ToPrettyString(used):used}"); return true; @@ -995,11 +1052,8 @@ public bool UseInHandInteraction( bool checkCanInteract = true, bool checkUseDelay = true) { - UseDelayComponent? delayComponent = null; - - if (checkUseDelay - && TryComp(used, out delayComponent) - && _useDelay.IsDelayed((used, delayComponent))) + _delayQuery.TryComp(used, out var delayComponent); + if (checkUseDelay && delayComponent != null && _useDelay.IsDelayed((used, delayComponent))) return true; // if the item is on cooldown, we consider this handled. if (checkCanInteract && !_actionBlockerSystem.CanInteract(user, used)) @@ -1061,11 +1115,60 @@ public void DroppedInteraction(EntityUid user, EntityUid item) } #endregion + /// + /// Check if a user can access a target (stored in the same containers) and is in range without obstructions. + /// + public bool InRangeAndAccessible( + Entity user, + Entity target, + float range = InteractionRange, + CollisionGroup collisionMask = InRangeUnobstructedMask, + Ignored? predicate = null) + { + if (user == target) + return true; + + if (!Resolve(user, ref user.Comp)) + return false; + + if (!Resolve(target, ref target.Comp)) + return false; + + return IsAccessible(user, target) && InRangeUnobstructed(user, target, range, collisionMask, predicate); + } + + /// + /// Check if a user can access a target or if they are stored in different containers. + /// + public bool IsAccessible(Entity user, Entity target) + { + if (_containerSystem.IsInSameOrParentContainer(user, target, out _, out var container)) + return true; + + return container != null && CanAccessViaStorage(user, target, container); + } + /// /// If a target is in range, but not in the same container as the user, it may be inside of a backpack. This /// checks if the user can access the item in these situations. /// - public abstract bool CanAccessViaStorage(EntityUid user, EntityUid target); + public bool CanAccessViaStorage(EntityUid user, EntityUid target) + { + if (!_containerSystem.TryGetContainingContainer(target, out var container)) + return false; + + return CanAccessViaStorage(user, target, container); + } + + /// + public bool CanAccessViaStorage(EntityUid user, EntityUid target, BaseContainer container) + { + if (StorageComponent.ContainerId != container.ID) + return false; + + // we don't check if the user can access the storage entity itself. This should be handed by the UI system. + return _ui.IsUiOpen(target, StorageComponent.StorageUiKey.Key, user); + } /// /// Checks whether an entity currently equipped by another player is accessible to some user. This shouldn't @@ -1145,6 +1248,17 @@ public void DoContactInteraction(EntityUid uidA, EntityUid? uidB, HandledEntityE RaiseLocalEvent(uidA, new ContactInteractionEvent(uidB.Value)); RaiseLocalEvent(uidB.Value, new ContactInteractionEvent(uidA)); } + + + private void HandleUserInterfaceRangeCheck(ref BoundUserInterfaceCheckRangeEvent ev) + { + if (ev.Result == BoundUserInterfaceRangeResult.Fail) + return; + + ev.Result = UiRangeCheck(ev.Actor!, ev.Target, ev.Data.InteractionRange) + ? BoundUserInterfaceRangeResult.Pass + : BoundUserInterfaceRangeResult.Fail; + } } /// diff --git a/Content.Shared/InteractionVerbs/Events/GetInteractionVerbsEvent.cs b/Content.Shared/InteractionVerbs/Events/GetInteractionVerbsEvent.cs new file mode 100644 index 0000000000..ed9571df2e --- /dev/null +++ b/Content.Shared/InteractionVerbs/Events/GetInteractionVerbsEvent.cs @@ -0,0 +1,25 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.InteractionVerbs.Events; + +/// +/// Raised directly on the user entity to get more interaction verbs it may allow. +/// While InteractionVerbsComponent defines which verbs may be performed on the entity, +/// This event allows to also define which verbs the entity itself may perform.

+/// +/// Note that this is raised before IsAllowed checks are performed on any of the verbs. +///
+[ByRefEvent] +public sealed class GetInteractionVerbsEvent(List> verbs) +{ + public List> Verbs = verbs; + + public bool Add(ProtoId verb) + { + if (Verbs.Contains(verb)) + return false; + + Verbs.Add(verb); + return true; + } +} diff --git a/Content.Shared/InteractionVerbs/Events/InteractionVerbAttemptEvent.cs b/Content.Shared/InteractionVerbs/Events/InteractionVerbAttemptEvent.cs new file mode 100644 index 0000000000..ade867fddd --- /dev/null +++ b/Content.Shared/InteractionVerbs/Events/InteractionVerbAttemptEvent.cs @@ -0,0 +1,14 @@ +namespace Content.Shared.InteractionVerbs.Events; + +/// +/// Raised directly on the performer of the interaction verb and on its target to determine if it should be allowed. +/// Note that this is raised if and only if verb's own CanPerform check returns true. +/// +[ByRefEvent] +public sealed class InteractionVerbAttemptEvent(InteractionVerbPrototype proto, InteractionArgs args) : CancellableEntityEventArgs +{ + public bool Handled { get; set; } = false; + + public InteractionVerbPrototype Proto => proto; + public InteractionArgs Args => args; +} diff --git a/Content.Shared/InteractionVerbs/Events/InteractionVerbDoAfterEvent.cs b/Content.Shared/InteractionVerbs/Events/InteractionVerbDoAfterEvent.cs new file mode 100644 index 0000000000..dda0c0ba20 --- /dev/null +++ b/Content.Shared/InteractionVerbs/Events/InteractionVerbDoAfterEvent.cs @@ -0,0 +1,21 @@ +using Content.Shared.DoAfter; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; + +namespace Content.Shared.InteractionVerbs.Events; + +[Serializable, NetSerializable] +public sealed partial class InteractionVerbDoAfterEvent : SimpleDoAfterEvent +{ + [DataField] + public ProtoId? VerbPrototype; + + [NonSerialized] + public InteractionArgs? VerbArgs; // Only ever used on the server, it should be fine™. If it ever isn't, move the entire code to server and forget it. + + public InteractionVerbDoAfterEvent(ProtoId? verbPrototype, InteractionArgs? verbArgs) + { + VerbPrototype = verbPrototype; + VerbArgs = verbArgs; + } +} diff --git a/Content.Shared/InteractionVerbs/InteractionAction.cs b/Content.Shared/InteractionVerbs/InteractionAction.cs new file mode 100644 index 0000000000..6c74b304c9 --- /dev/null +++ b/Content.Shared/InteractionVerbs/InteractionAction.cs @@ -0,0 +1,59 @@ +using Robust.Shared.Prototypes; +using Robust.Shared.Random; +using Robust.Shared.Serialization; +using Robust.Shared.Serialization.Manager; +using Robust.Shared.Timing; + +namespace Content.Shared.InteractionVerbs; + +/// +/// Represents an action performed when a verb is used successfully. +/// +[ImplicitDataDefinitionForInheritors, Serializable, NetSerializable] +public abstract partial class InteractionAction +{ + /// + /// Invoked when the user wants to get the list of verbs that can be performed on the target, after all verb-specific checks have passed.. + /// If this method returns false, it will not be shown to the user. + /// + public virtual bool IsAllowed( + InteractionArgs args, + InteractionVerbPrototype proto, + VerbDependencies deps + ) => true; + + /// + /// Checks whether this verb can be performed at the current moment. + /// If the verb has a do-after, this will be called both before and after the do-after. + /// + public abstract bool CanPerform( + InteractionArgs args, + InteractionVerbPrototype proto, + bool beforeDelay, + VerbDependencies deps + ); + + /// + /// Performs the action and returns whether it was successful. + /// + public abstract bool Perform( + InteractionArgs args, + InteractionVerbPrototype proto, + VerbDependencies deps + ); + + /// + /// Provided to interaction verbs to avoid unnecessary dependency injection. + /// + /// + /// To acquire a working instance of this class, allocate a new instance and use IoCManager.InjectDependencies(). + /// + public sealed class VerbDependencies + { + [Dependency] public readonly IEntityManager EntMan = default!; + [Dependency] public readonly IPrototypeManager ProtoMan = default!; + [Dependency] public readonly IRobustRandom Random = default!; + [Dependency] public readonly IGameTiming Timing = default!; + [Dependency] public readonly ISerializationManager Serialization = default!; + } +} diff --git a/Content.Shared/InteractionVerbs/InteractionArgs.cs b/Content.Shared/InteractionVerbs/InteractionArgs.cs new file mode 100644 index 0000000000..71e33dde63 --- /dev/null +++ b/Content.Shared/InteractionVerbs/InteractionArgs.cs @@ -0,0 +1,59 @@ +using System.Diagnostics.CodeAnalysis; +using Content.Shared.Hands.Components; +using Content.Shared.Verbs; +using Robust.Shared.Serialization; + +namespace Content.Shared.InteractionVerbs; + +public sealed partial class InteractionArgs +{ + public EntityUid User, Target; + public EntityUid? Used; + public bool CanAccess, CanInteract, HasHands; + + /// + /// A float value between 0 and positive infinity that indicates how much stronger the user + /// is compared to the target in terms of contests allowed for this verb. 1.0 means no advantage or disadvantage. + /// + /// Can be null, which means it's not calculated yet. That can happen before the user attempts to perform the verb. + public float? ContestAdvantage; + + /// + /// A dictionary for actions and requirements to store data between different execution stages. + /// For instance, an action can cache some data in its CanPerform check and later use it in Perform. + /// + /// + /// Non-action classes are highly not recommended to write anything to this dictionary - it can easily lead to errors. + /// + public Dictionary Blackboard => _blackboardField ??= new(3); + private Dictionary? _blackboardField; // null by default, allocated lazily (only if actually needed) + + public InteractionArgs(EntityUid user, EntityUid target, EntityUid? used, bool canAccess, bool canInteract, bool hasHands, float? contestAdvantage) + { + User = user; + Target = target; + Used = used; + CanAccess = canAccess; + CanInteract = canInteract; + HasHands = hasHands; + ContestAdvantage = contestAdvantage; + } + + public InteractionArgs(InteractionArgs other) : this(other.User, other.Target, other.Used, other.CanAccess, other.CanInteract, other.HasHands, other.ContestAdvantage) {} + + public static InteractionArgs From(GetVerbsEvent ev) where T : Verb => new(ev.User, ev.Target, ev.Using, ev.CanAccess, ev.CanInteract, ev.Hands is not null, null); + + /// + /// Tries to get a value from the blackboard as an instance of a specific type. + /// + public bool TryGetBlackboard(string key, [NotNullWhen(true)] out T? value) + { + value = default; + if (_blackboardField == null || !_blackboardField.TryGetValue(key, out var maybeValue)) + return false; + + // Cannot use a type check here. If someone fucks up, it's gonna be on them. + value = (T?) maybeValue; + return value != null; + } +} diff --git a/Content.Shared/InteractionVerbs/InteractionPopupPrototype.cs b/Content.Shared/InteractionVerbs/InteractionPopupPrototype.cs new file mode 100644 index 0000000000..61f70db43b --- /dev/null +++ b/Content.Shared/InteractionVerbs/InteractionPopupPrototype.cs @@ -0,0 +1,85 @@ +using Content.Shared.Chat; +using Content.Shared.Popups; +using Robust.Shared.Prototypes; + +namespace Content.Shared.InteractionVerbs; + +/// +/// Specifies how popups should be shown.
+/// Popup locales follow the format "interaction-[verb id]-[prefix]-[kind suffix]-popup", where:
+/// - [prefix] is , which is one of: "success", "fail", "delayed".
+/// - [kind suffix] is one of the respective suffix properties, typically "self", "target", or "others"
+///
+/// +/// The following parameters may be used in the locale:
+/// - {$user} - The performer of the action.
+/// - {$target} - The target of the action.
+/// - {$used} - The active-hand item used in the action. May be null, then "0" is used instead. +/// - {$selfTarget} - A boolean value that indicates whether the action is used on the user itself. +/// - {$hasUsed} - A boolean value that indicates whether the user is holding an item ($used is not null). +///
+[Prototype("InteractionPopup"), Serializable] +public sealed partial class InteractionPopupPrototype : IPrototype +{ + [IdDataField] + public string ID { get; } = default!; + + [DataField] + public PopupType PopupType = PopupType.Medium; + + /// + /// If true, the respective success/fail popups will be logged into chat, as players perceive them. + /// + [DataField] + public bool LogPopup = true; + + /// + /// Chat channel to which popups will be logged if is true. + /// + [DataField] + public ChatChannel LogChannel = ChatChannel.Emotes; + + /// + /// Color of the chat message sent if is true. If null, defaults based on . + /// + [DataField] + public Color? LogColor = null; + + /// + /// If true, entities who cannot directly see the popup target will not chat log. Only has effect if is true. + /// + [DataField] + public bool DoClipping = true; + + /// + /// Range in which other entities, given that they can directly see the performer, see the chat log. + /// This does not affect the user and target. Only has effect if is true. + /// + [DataField] + public float VisibilityRange = 20f; + + /// + /// Loc prefix for popups shown for the performer of the verb. If set to null, defaults to . + /// + [DataField("self")] + public string? SelfSuffix = "self"; + + /// + /// Loc prefix for popups shown for the target of the verb. If set to null, defaults to . + /// + [DataField("target")] + public string? TargetSuffix = "target"; + + /// + /// Loc prefix for popups shown for other people observing the verb. If null, no popup will be shown for others. + /// + [DataField("others")] + public string? OthersSuffix = "others"; + + public enum Prefix : byte + { + Success, + Fail, + Delayed + } +} diff --git a/Content.Shared/InteractionVerbs/InteractionRequirement.cs b/Content.Shared/InteractionVerbs/InteractionRequirement.cs new file mode 100644 index 0000000000..1139ad2114 --- /dev/null +++ b/Content.Shared/InteractionVerbs/InteractionRequirement.cs @@ -0,0 +1,23 @@ +using Content.Shared.Verbs; +using JetBrains.Annotations; +using Robust.Shared.Serialization; + +namespace Content.Shared.InteractionVerbs; + +/// +/// Defines a requirement for an . +/// If a verb does not meet the requirement, it will be hidden or disabled in the verb menu. +/// +[ImplicitDataDefinitionForInheritors, Serializable, NetSerializable] +[UsedImplicitly(ImplicitUseTargetFlags.WithInheritors )] +public abstract partial class InteractionRequirement +{ + public abstract bool IsMet(InteractionArgs args, InteractionVerbPrototype proto, InteractionAction.VerbDependencies deps); +} + +/// +[Serializable, NetSerializable] +public abstract partial class InvertableInteractionRequirement : InteractionRequirement +{ + [DataField] public bool Inverted = false; +} diff --git a/Content.Shared/InteractionVerbs/InteractionVerbPrototype.cs b/Content.Shared/InteractionVerbs/InteractionVerbPrototype.cs new file mode 100644 index 0000000000..671e8e4718 --- /dev/null +++ b/Content.Shared/InteractionVerbs/InteractionVerbPrototype.cs @@ -0,0 +1,259 @@ +using Content.Shared.DoAfter; +using Content.Shared.InteractionVerbs.Events; +using Robust.Shared.Audio; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array; +using Robust.Shared.Utility; + +#pragma warning disable CS0618 // Type or member is obsolete + +namespace Content.Shared.InteractionVerbs; + +/// +/// Represents an action that can be performed on an entity. +/// +[Prototype("Interaction"), Serializable] +public sealed partial class InteractionVerbPrototype : IPrototype, IInheritingPrototype +{ + /// + [ParentDataField(typeof(AbstractPrototypeIdArraySerializer))] + public string[]? Parents { get; } + + /// + [NeverPushInheritance] + [AbstractDataField] + public bool Abstract { get; } + + [IdDataField] + public string ID { get; } = default!; + + // Locale getters + public string Name => Loc.TryGetString($"interaction-{ID}-name", out var loc) ? loc : ID; + + public string? Description => Loc.TryGetString($"interaction-{ID}-description" , out var loc) ? loc : null; + + /// + /// Sprite of the icon that the user sees on the verb button. + /// + [DataField] + public SpriteSpecifier? Icon; + + /// + /// Specifies what effects are shown when this verb is performed successfully, or unsuccessfully. + /// Effects specified here are shown after the associated do-after has ended, if any. + /// + [DataField] + public EffectSpecifier? EffectSuccess, EffectFailure; + + /// + /// Specifies what popups are shown when a do-after for this verb is started. + /// This is only ever used if is set to a non-zero value. + /// + [DataField] + public EffectSpecifier? EffectDelayed; + + /// + /// The requirement of this verb. + /// + [DataField] + public InteractionRequirement? Requirement = null; + + /// + /// The action of this verb. It defines the conditions under which this verb is shown, as well as what the verb does. + /// + /// Made server-only because many actions require authoritative access to the server. + [DataField(serverOnly: true)] + public InteractionAction? Action = null; + + /// + /// If true, this action will be hidden if the does not pass its IsMet check. Otherwise it will be shown, but disabled. + /// + /// I apologize, I could not come up with a better name. + [DataField] + public bool HideByRequirement = false; + + /// + /// If true, this action will be hidden if the does not pass its IsAllowed check. Otherwise it will be shown, but disabled. + /// + [DataField] + public bool HideWhenInvalid = true; + + /// + /// The delay of the verb. Anything greater than zero constitutes a do-after. + /// + [DataField] + public TimeSpan Delay = TimeSpan.Zero; + + /// + /// Cooldown between uses of this verb. Applied per user or per user-target pair (see ) and before the do-after. + /// + [DataField] + public TimeSpan Cooldown = TimeSpan.FromSeconds(0.5f); + + /// + /// If true, the contests defined in will affect the delay or the cooldown of the verb. + /// + [DataField] + public bool ContestDelay = true, ContestCooldown = false; + + /// + /// If true, the cooldown of this verb will be applied regardless of the verb target, + /// i.e. a user won't be able to apply the same verb to any different entity until the cooldown ends. + /// + [DataField] + public bool GlobalCooldown = false; + + /// + /// Arguments of the do-after shown if is greater than zero. + /// The user, target, needHand, event, and other required parameters are set up automatically when the do-after is created. + /// + [DataField] + public DoAfterArgs DoAfter = new DoAfterArgs() + { + User = EntityUid.Invalid, + NetUser = NetEntity.Invalid, + BreakOnDamage = true, + BreakOnTargetMove = true, + BreakOnUserMove = true, + BreakOnWeightlessMove = true, + RequireCanInteract = false, + // Never used, but must be present because the field is non-nullable and will error during serialization if not set. + Event = new InteractionVerbDoAfterEvent(default, default!) + }; + + [DataField] + public RangeSpecifier Range = new(); + + /// + /// Range of contest advantages valid for this verb. + /// If the user's contest advantage is outside of this range, the verb will be disabled or hidden. + /// + /// If not specified, contest advantage won't be calculated until the verb is performed. + [DataField] + public RangeSpecifier? ContestAdvantageRange; + + /// + /// Range of contest advantages that the user can gain while using this verb. + /// The user's advantage will never exceed this range. This is applied after is checked. + /// + /// + [DataField] + public RangeSpecifier ContestAdvantageLimit = new() { Min = 0.2f, Max = 5f }; + + [DataField] + public ContestType AllowedContests = ContestType.None; + + /// + /// Whether this interaction implies direct body contact (transfer of fibers, fingerprints, etc). + /// + [DataField("contactInteraction")] + public bool DoContactInteraction = true; + + [DataField] + public bool RequiresHands = false; + + /// + /// Whether this verb requires the user to be able to access the target normally (with their hands or otherwise). + /// + /// The misleading yml name is kept for backwards compatibility with downstreams. + [DataField("requiresCanInteract")] + public bool RequiresCanAccess = true; + + /// + /// If true, this verb can be invoked by the user on itself. + /// + [DataField] + public bool AllowSelfInteract = false; + + /// + /// Priority of the verb. Verbs with higher priority will be shown first. + /// + [DataField] + public int Priority = 0; + + /// + /// If true, this verb can be invoked on any entity that the action is allowed on, even if its components don't specify it. + /// + [DataField] + public bool Global = false; + + [DataDefinition, Serializable] + public partial struct RangeSpecifier() + { + [DataField] public float Min = 0f; + [DataField] public float Max = float.PositiveInfinity; + [DataField] public bool Inverse = false; + + public bool IsInRange(float value) => (Inverse ? value < Min || value > Max : value >= Min && value <= Max); + + public float Clamp(float value) + { + DebugTools.Assert(!Inverse, "Inverse ranges do not support clamping."); + return Math.Clamp(value, Min, Max); + } + } + + [DataDefinition, Serializable] + public partial class EffectSpecifier + { + [DataField] + public EffectTargetSpecifier EffectTarget = EffectTargetSpecifier.TargetThenUser; + + /// + /// The interaction popup to show, at . If null, no popup will be shown. + /// + [DataField] + public ProtoId? Popup = null; + + /// + /// Sound played when the effect is shown, at . If null, no sound will be played. + /// + [DataField] + public SoundSpecifier? Sound; + + /// + /// If true, the sound will be perceived by everyone in the PVS of the popup. + /// Otherwise, it will be perceived only by the target and the user. + /// + [DataField] + public bool SoundPerceivedByOthers = true; + + [DataField] + public AudioParams SoundParams = new AudioParams() + { + Variation = 0.1f + }; + } + + [Serializable, Flags] + public enum EffectTargetSpecifier + { + /// + /// Popup will be shown above the person executing the verb. + /// + User, + /// + /// Popup will be shown above the target of the verb. + /// + Target, + /// + /// The user will see the popup shown above itself, others will see the popup above the target. + /// + UserThenTarget, + /// + /// The target will see the popup shown above itself, others will see the popup above the user. + /// + TargetThenUser + } + + [Serializable, Flags] + public enum ContestType : byte + { + Mass = 1, + Stamina = 1 << 1, + Health = 1 << 2, + All = Mass | Stamina | Health, + None = 0 + } +} + diff --git a/Content.Shared/InteractionVerbs/InteractionVerbsComponent.cs b/Content.Shared/InteractionVerbs/InteractionVerbsComponent.cs new file mode 100644 index 0000000000..bcbbe5e8a0 --- /dev/null +++ b/Content.Shared/InteractionVerbs/InteractionVerbsComponent.cs @@ -0,0 +1,11 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.InteractionVerbs; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class InteractionVerbsComponent : Component +{ + [DataField, AutoNetworkedField] + public List> AllowedVerbs = new(); +} diff --git a/Content.Shared/InteractionVerbs/OwnInteractionVerbsComponent.cs b/Content.Shared/InteractionVerbs/OwnInteractionVerbsComponent.cs new file mode 100644 index 0000000000..450f1d42fc --- /dev/null +++ b/Content.Shared/InteractionVerbs/OwnInteractionVerbsComponent.cs @@ -0,0 +1,20 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.InteractionVerbs; + +/// +/// Specifies which verbs this entity may perform on its own, on any entity that the verb allows. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class OwnInteractionVerbsComponent : Component +{ + public override bool SendOnlyToOwner => true; + + [DataField, AutoNetworkedField] + public List> AllowedVerbs = new(); + + // Too volatile to be worth networking; client and server just keep track of this field independently. + [NonSerialized, ViewVariables] + public Dictionary<(ProtoId, EntityUid), TimeSpan> Cooldowns = new(); +} diff --git a/Content.Shared/InteractionVerbs/Requirements/AssortedRequirements.cs b/Content.Shared/InteractionVerbs/Requirements/AssortedRequirements.cs new file mode 100644 index 0000000000..9a4dd32336 --- /dev/null +++ b/Content.Shared/InteractionVerbs/Requirements/AssortedRequirements.cs @@ -0,0 +1,73 @@ +using Content.Shared.Mobs; +using Content.Shared.Mobs.Components; +using Content.Shared.Standing; +using Content.Shared.Stunnable; +using Content.Shared.Whitelist; +using Robust.Shared.Serialization; + +namespace Content.Shared.InteractionVerbs.Requirements; + +/// +/// Requires the target to meet a certain whitelist and not meet a blacklist. +/// +[Serializable, NetSerializable] +public sealed partial class EntityWhitelistRequirement : InteractionRequirement +{ + [DataField] public EntityWhitelist? Whitelist, Blacklist; + + public override bool IsMet(InteractionArgs args, InteractionVerbPrototype proto, InteractionAction.VerbDependencies deps) + { + return Whitelist?.IsValid(args.Target, deps.EntMan) != false + && Blacklist?.IsValid(args.Target, deps.EntMan) != true; + } +} + +/// +/// Requires the mob to be a mob in a certain state. If inverted, requires the mob to not be in that state. +/// +[Serializable, NetSerializable] +public sealed partial class MobStateRequirement : InvertableInteractionRequirement +{ + [DataField] public List AllowedStates = new(); + + public override bool IsMet(InteractionArgs args, InteractionVerbPrototype proto, InteractionAction.VerbDependencies deps) + { + if (!deps.EntMan.TryGetComponent(args.Target, out var state)) + return false; + + return AllowedStates.Contains(state.CurrentState) ^ Inverted; + } +} + +/// +/// Requires the target to be in a specific standing state. +/// +[Serializable, NetSerializable] +public sealed partial class StandingStateRequirement : InteractionRequirement +{ + [DataField] public bool AllowStanding, AllowLaying, AllowKnockedDown; + + public override bool IsMet(InteractionArgs args, InteractionVerbPrototype proto, InteractionAction.VerbDependencies deps) + { + if (deps.EntMan.HasComponent(args.Target)) + return AllowKnockedDown; + + if (!deps.EntMan.TryGetComponent(args.Target, out var state)) + return false; + + return state.CurrentState == StandingState.Standing && AllowStanding + || state.CurrentState == StandingState.Lying && AllowLaying; + } +} + +/// +/// Requires the target to be the user itself. +/// +[Serializable, NetSerializable] +public sealed partial class SelfTargetRequirement : InvertableInteractionRequirement +{ + public override bool IsMet(InteractionArgs args, InteractionVerbPrototype proto, InteractionAction.VerbDependencies deps) + { + return (args.Target == args.User) ^ Inverted; + } +} diff --git a/Content.Shared/InteractionVerbs/Requirements/ComplexRequirement.cs b/Content.Shared/InteractionVerbs/Requirements/ComplexRequirement.cs new file mode 100644 index 0000000000..2b8fba7919 --- /dev/null +++ b/Content.Shared/InteractionVerbs/Requirements/ComplexRequirement.cs @@ -0,0 +1,27 @@ +using System.Linq; +using Robust.Shared.Serialization; + +namespace Content.Shared.InteractionVerbs.Requirements; + +/// +/// A requirement that combines multiple other requirements. +/// +[Serializable, NetSerializable] +public sealed partial class ComplexRequirement : InteractionRequirement +{ + [DataField] + public List Requirements = new(); + + /// + /// If true, all requirements must pass (boolean and). Otherwise, at least one must pass (boolean or). + /// + [DataField] + public bool RequireAll = true; + + public override bool IsMet(InteractionArgs args, InteractionVerbPrototype proto, InteractionAction.VerbDependencies deps) + { + return RequireAll + ? Requirements.All(r => r.IsMet(args, proto, deps)) + : Requirements.Any(r => r.IsMet(args, proto, deps)); + } +} diff --git a/Content.Shared/InteractionVerbs/Requirements/UtilityRequirements.cs b/Content.Shared/InteractionVerbs/Requirements/UtilityRequirements.cs new file mode 100644 index 0000000000..c63d495f00 --- /dev/null +++ b/Content.Shared/InteractionVerbs/Requirements/UtilityRequirements.cs @@ -0,0 +1,16 @@ +using Robust.Shared.Random; +using Robust.Shared.Serialization; + +namespace Content.Shared.InteractionVerbs.Requirements; + +[Serializable, NetSerializable] +public sealed partial class ChanceRequirement : InteractionRequirement +{ + [DataField(required: true)] + public float Chance; + + public override bool IsMet(InteractionArgs args, InteractionVerbPrototype proto, InteractionAction.VerbDependencies deps) + { + return Chance > 0f && (Chance > 1f || deps.Random.Prob(Chance)); + } +} diff --git a/Content.Shared/InteractionVerbs/SharedInteractionVerbsSystem.cs b/Content.Shared/InteractionVerbs/SharedInteractionVerbsSystem.cs new file mode 100644 index 0000000000..f8100e71c3 --- /dev/null +++ b/Content.Shared/InteractionVerbs/SharedInteractionVerbsSystem.cs @@ -0,0 +1,435 @@ +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Content.Shared.ActionBlocker; +using Content.Shared.Contests; +using Content.Shared.DoAfter; +using Content.Shared.Ghost; +using Content.Shared.Interaction; +using Content.Shared.InteractionVerbs.Events; +using Content.Shared.Popups; +using Content.Shared.Verbs; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Containers; +using Robust.Shared.Network; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; +using Robust.Shared.Utility; +using static Content.Shared.InteractionVerbs.InteractionPopupPrototype.Prefix; +using static Content.Shared.InteractionVerbs.InteractionVerbPrototype.ContestType; +using static Content.Shared.InteractionVerbs.InteractionVerbPrototype.EffectTargetSpecifier; + +namespace Content.Shared.InteractionVerbs; + +public abstract class SharedInteractionVerbsSystem : EntitySystem +{ + private readonly InteractionAction.VerbDependencies _verbDependencies = new(); + private List _globalPrototypes = default!; + + [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfters = default!; + [Dependency] private readonly SharedContainerSystem _containers = default!; + [Dependency] private readonly ContestsSystem _contests = default!; + [Dependency] private readonly SharedInteractionSystem _interactions = default!; + [Dependency] private readonly INetManager _net = default!; + [Dependency] private readonly SharedPopupSystem _popups = default!; + [Dependency] private readonly IPrototypeManager _protoMan = default!; + [Dependency] private readonly IGameTiming _timing = default!; + + public override void Initialize() + { + IoCManager.InjectDependencies(_verbDependencies); + + LoadGlobalVerbs(); + SubscribeLocalEvent(OnPrototypesReloaded); + + SubscribeLocalEvent>(OnGetOthersVerbs); + SubscribeLocalEvent>(OnGetOwnVerbs); + SubscribeLocalEvent(OnDoAfterFinished); + } + + private void LoadGlobalVerbs() + { + _globalPrototypes = _protoMan.EnumeratePrototypes() + .Where(v => v is { Global: true, Abstract: false }) + .ToList(); + } + + #region event handling + + private void OnPrototypesReloaded(PrototypesReloadedEventArgs args) + { + if (!args.WasModified()) + return; + + LoadGlobalVerbs(); + } + + private void OnGetOthersVerbs(Entity entity, ref GetVerbsEvent args) + { + // Global verbs are not added here since OnGetOwnVerbs already adds them + AddAll(entity.Comp.AllowedVerbs.Select(_protoMan.Index), args, () => new InteractionVerb()); + } + + private void OnGetOwnVerbs(Entity entity, ref GetVerbsEvent args) + { + var allVerbs = entity.Comp.AllowedVerbs; + + var getVerbsEv = new GetInteractionVerbsEvent(allVerbs); + RaiseLocalEvent(entity, ref getVerbsEv); + + // Global verbs are added here because they should be allowed even on entities that do not define any interactions + AddAll(allVerbs.Select(_protoMan.Index).Union(_globalPrototypes), args, () => new InnateVerb()); + } + + private void OnDoAfterFinished(InteractionVerbDoAfterEvent ev) + { + if (ev.Cancelled || ev.Handled || !_protoMan.TryIndex(ev.VerbPrototype, out var proto)) + return; + + PerformVerb(proto, ev.VerbArgs!); + ev.Handled = true; + } + + #endregion + + #region public api + + /// + /// Starts the verb, checking if it can be performed first, unless forced. + /// Upon success, this method will either start a do-after, or pass control to . + /// + // TODO this function is an active battlefield + public bool StartVerb(InteractionVerbPrototype proto, InteractionArgs args, bool force = false) + { + if (!TryComp(args.User, out var ownInteractions) + || !force && !CheckVerbCooldown(proto, args, out _, ownInteractions)) + return false; + + // If contest advantage wasn't calculated yet, calculate it now and ensure it's in the allowed range + var contestAdvantageValid = true; + if (args.ContestAdvantage is null) + CalculateAdvantage(proto, ref args, out contestAdvantageValid); + + if (!_net.IsClient + && !force + && (!contestAdvantageValid || proto.Action?.CanPerform(args, proto, true, _verbDependencies) != true)) + { + CreateVerbEffects(proto.EffectFailure, Fail, proto, args); + return false; + } + + var attemptEv = new InteractionVerbAttemptEvent(proto, args); + RaiseLocalEvent(args.User, ref attemptEv); + RaiseLocalEvent(args.Target, ref attemptEv); + + if (attemptEv.Cancelled) + { + CreateVerbEffects(proto.EffectFailure, Fail, proto, args); + return false; + } + if (attemptEv.Handled) + return true; + + var cooldown = proto.Cooldown; + var delay = proto.Delay; + if (proto.ContestDelay) + delay /= args.ContestAdvantage!.Value; + if (proto.ContestCooldown) + cooldown /= args.ContestAdvantage!.Value; + + StartVerbCooldown(proto, args, cooldown, ownInteractions); + + // Delay can become zero if the contest advantage is infinity or just really large... + if (delay <= TimeSpan.Zero) + { + PerformVerb(proto, args); + return true; + } + + var doAfter = new DoAfterArgs(proto.DoAfter) + { + User = args.User, + Target = args.Target, + EventTarget = EntityUid.Invalid, // Raised broadcast + Broadcast = true, + BreakOnHandChange = proto.RequiresHands, + NeedHand = proto.RequiresHands, + RequireCanInteract = proto.RequiresCanAccess, + Delay = delay, + Event = new InteractionVerbDoAfterEvent(proto.ID, args) + }; + + var isSuccess = _doAfters.TryStartDoAfter(doAfter); + if (isSuccess) + CreateVerbEffects(proto.EffectDelayed, Delayed, proto, args); + + return isSuccess; + } + + /// + /// Performs an additional CanPerform check (unless forced) and then actually performs the action of the verb + /// and shows a success/failure popup. + /// + /// This does nothing on client, as the client has no clue about verb actions. Only the server should ever perform verbs. + public void PerformVerb(InteractionVerbPrototype proto, InteractionArgs args, bool force = false) + { + if (_net.IsClient) + return; // this leads to issues + + if (!PerformChecks(proto, ref args, out _, out _) && !force + || !proto.Action!.CanPerform(args, proto, false, _verbDependencies) && !force + || !proto.Action.Perform(args, proto, _verbDependencies)) + { + CreateVerbEffects(proto.EffectFailure, Fail, proto, args); + return; + } + + CreateVerbEffects(proto.EffectSuccess, Success, proto, args); + } + + #endregion + + #region private api + + /// + /// Creates verbs for all listed prototypes that match their own requirements. Uses the provided factory to create new verb instances. + /// + // Note: using `where T : Verb, new()` here results in a sandbox violation... Yea we peasants don't get OOP in ss14. + private void AddAll(IEnumerable verbs, GetVerbsEvent args, Func factory) where T : Verb + { + // Don't add verbs to ghosts. Ghost system will also cancel all verbs by/on non-admin ghosts. + if (TryComp(args.User, out var ghost) && !ghost.CanGhostInteract) + return; + + var ownInteractions = EnsureComp(args.User); + foreach (var proto in verbs) + { + DebugTools.AssertNotEqual(proto.Abstract, true, "Attempted to add a verb with an abstract prototype."); + + var name = proto.Name; + if (args.Verbs.Any(v => v.Text == name)) + continue; + + var verbArgs = InteractionArgs.From(args); + var isEnabled = PerformChecks(proto, ref verbArgs, out var skipAdding, out var errorLocale); + + if (skipAdding) + continue; + + var verb = factory.Invoke(); + CopyVerbData(proto, verb); + verb.Act = () => StartVerb(proto, verbArgs); + verb.Disabled = !isEnabled; + + if (!isEnabled) + verb.Message = Loc.GetString(errorLocale!); + + if (isEnabled && !CheckVerbCooldown(proto, verbArgs, out var remainingTime, ownInteractions)) + { + verb.Disabled = true; + verb.Message = Loc.GetString("interaction-verb-cooldown", ("seconds", remainingTime.TotalSeconds)); + } + + args.Verbs.Add(verb); + } + } + + /// + /// Performs all requirement/action checks on the verb. Returns true if the verb can be executed right now. + /// The skipAdding output param indicates whether the caller should skip adding this verb to the verb list, if applicable. + /// + private bool PerformChecks(InteractionVerbPrototype proto, ref InteractionArgs args, out bool skipAdding, [NotNullWhen(false)] out string? errorLocale) + { + if (!proto.AllowSelfInteract && args.User == args.Target + || !Transform(args.User).Coordinates.TryDistance(EntityManager, Transform(args.Target).Coordinates, out var distance)) + { + skipAdding = true; + errorLocale = "interaction-verb-invalid-target"; + return false; + } + + if (proto.Requirement?.IsMet(args, proto, _verbDependencies) == false) + { + skipAdding = proto.HideByRequirement; + errorLocale = "interaction-verb-invalid"; + return false; + } + + // TODO: we skip this check since the client is not aware of actions. This should be changed, maybe make actions mixed server/client? + if (proto.Action?.IsAllowed(args, proto, _verbDependencies) != true && !_net.IsClient) + { + skipAdding = proto.HideWhenInvalid; + errorLocale = "interaction-verb-invalid"; + return false; + } + + skipAdding = false; + if (proto.RequiresHands && !args.HasHands) + { + errorLocale = "interaction-verb-no-hands"; + return false; + } + + if (!args.CanInteract || proto.RequiresCanAccess && !args.CanAccess || !proto.Range.IsInRange(distance)) + { + errorLocale = "interaction-verb-cannot-reach"; + return false; + } + + // Calculate contest advantage early if required + if (proto.ContestAdvantageRange is not null) + { + CalculateAdvantage(proto, ref args, out var canPerform); + + if (!canPerform) + { + errorLocale = "interaction-verb-too-" + (args.ContestAdvantage > 1f ? "strong" : "weak"); + return false; + } + } + + errorLocale = null; + return true; + } + + /// + /// Calculates the effective contest advantage for the verb and writes their clamped value to . + /// + private void CalculateAdvantage(InteractionVerbPrototype proto, ref InteractionArgs args, out bool canPerform) + { + args.ContestAdvantage = 1f; + canPerform = true; + + var contests = proto.AllowedContests; + if (contests == None) + return; + + // We don't use EveryContest here because it's straight up bad + if (contests.HasFlag(Mass)) + args.ContestAdvantage *= _contests.MassContest(args.User, args.Target, true, 10f); + if (contests.HasFlag(Stamina)) + args.ContestAdvantage *= _contests.MassContest(args.User, args.Target, true, 10f); + if (contests.HasFlag(Health)) + args.ContestAdvantage *= _contests.MassContest(args.User, args.Target, true, 10f); + + canPerform = proto.ContestAdvantageRange?.IsInRange(args.ContestAdvantage.Value) ?? true; + args.ContestAdvantage = proto.ContestAdvantageLimit.Clamp(args.ContestAdvantage.Value); + } + + private void CopyVerbData(InteractionVerbPrototype proto, Verb verb) + { + verb.Text = proto.Name; + verb.Message = proto.Description; + verb.DoContactInteraction = proto.DoContactInteraction; + verb.Priority = proto.Priority; + verb.Icon = proto.Icon; + verb.Category = VerbCategory.Interaction; + } + + /// + /// Checks if the verb is on cooldown. Returns true if the verb can be used right now. + /// + private bool CheckVerbCooldown(InteractionVerbPrototype proto, InteractionArgs args, out TimeSpan remainingTime, OwnInteractionVerbsComponent? comp = null) + { + remainingTime = TimeSpan.Zero; + if (!Resolve(args.User, ref comp)) + return false; + + var cooldownTarget = proto.GlobalCooldown ? EntityUid.Invalid : args.Target; + if (!comp.Cooldowns.TryGetValue((proto.ID, cooldownTarget), out var cooldown)) + return true; + + remainingTime = cooldown - _timing.CurTime; + return remainingTime <= TimeSpan.Zero; + } + + private void StartVerbCooldown(InteractionVerbPrototype proto, InteractionArgs args, TimeSpan cooldown, OwnInteractionVerbsComponent? comp = null) + { + if (!Resolve(args.User, ref comp)) + return; + + var cooldownTarget = proto.GlobalCooldown ? EntityUid.Invalid : args.Target; + comp.Cooldowns[(proto.ID, cooldownTarget)] = _timing.CurTime + cooldown; + + // We also clean up old cooldowns here to avoid a memory leak... This is probably a bad place to do it. + // TODO might wanna switch to a list because dict is probably overkill for this task given we clean it up often. + foreach (var (key, time) in comp.Cooldowns.ToArray()) + { + if (time < _timing.CurTime) + comp.Cooldowns.Remove(key); + } + } + + private void CreateVerbEffects(InteractionVerbPrototype.EffectSpecifier? specifier, InteractionPopupPrototype.Prefix prefix, InteractionVerbPrototype proto, InteractionArgs args) + { + // Not doing effects on client because it causes issues + if (specifier is null || _net.IsClient) + return; + + var (user, target, used) = (args.User, args.Target, args.Used); + + // Effect targets for different players + var userTarget = specifier.EffectTarget is User or UserThenTarget or TargetThenUser ? user : target; + var targetTarget = specifier.EffectTarget is Target or UserThenTarget or TargetThenUser ? target : user; + var othersTarget = specifier.EffectTarget is Target or UserThenTarget ? target : user; + var othersFilter = Filter.Pvs(othersTarget).RemoveWhereAttachedEntity(ent => ent == user || ent == target); + + // Popups + if (_protoMan.TryIndex(specifier.Popup, out var popup)) + { + var locPrefix = $"interaction-{proto.ID}-{prefix.ToString().ToLower()}"; + + (string, object)[] localeArgs = + [ + ("user", user), + ("target", target), + ("used", used ?? EntityUid.Invalid), + ("selfTarget", user == target), + ("hasUsed", used != null) + ]; + + // User popup + var userSuffix = popup.SelfSuffix ?? popup.OthersSuffix; + if (userSuffix is not null) + PopupEffects(Loc.GetString($"{locPrefix}-{userSuffix}-popup", localeArgs), userTarget, Filter.Entities(user), false, popup); + + // Target popup + var targetSuffix = popup.TargetSuffix ?? popup.OthersSuffix; + if (targetSuffix is not null && user != target) + PopupEffects(Loc.GetString($"{locPrefix}-{targetSuffix}-popup", localeArgs), targetTarget, Filter.Entities(target), false, popup); + + // Others popup + var othersSuffix = popup.OthersSuffix; + if (othersSuffix is not null) + PopupEffects(Loc.GetString($"{locPrefix}-{othersSuffix}-popup", localeArgs), othersTarget, othersFilter, true, popup, clip: true); + } + + // Sounds + if (specifier.Sound is { } sound) + { + // TODO we have a choice between having an accurate sound source or saving on an entity spawn... + _audio.PlayEntity(sound, Filter.Entities(user, target), target, false, specifier.SoundParams); + + if (specifier.SoundPerceivedByOthers) + _audio.PlayEntity(sound, othersFilter, othersTarget, false, specifier.SoundParams); + } + } + + private void PopupEffects(string message, EntityUid target, Filter filter, bool recordReplay, InteractionPopupPrototype popup, bool clip = false) + { + // Sending a chat message will result in a popup anyway + // TODO this needs to be fixed probably. Popups and chat messages should be independent. + if (popup.LogPopup) + SendChatLog(message, target, filter, popup, clip); + else + _popups.PopupEntity(message, target, filter, recordReplay, popup.PopupType); + } + + protected virtual void SendChatLog(string message, EntityUid source, Filter filter, InteractionPopupPrototype popup, bool clip) + { + } + + #endregion +} diff --git a/Content.Shared/Inventory/Events/UnequippedEvents.cs b/Content.Shared/Inventory/Events/UnequippedEvents.cs index ef607f071a..4e1764a7d2 100644 --- a/Content.Shared/Inventory/Events/UnequippedEvents.cs +++ b/Content.Shared/Inventory/Events/UnequippedEvents.cs @@ -22,12 +22,18 @@ public abstract class UnequippedEventBase : EntityEventArgs /// public readonly string SlotGroup; + /// + /// Slotflags of the slot the entity just got unequipped from. + /// + public readonly SlotFlags SlotFlags; + public UnequippedEventBase(EntityUid equipee, EntityUid equipment, SlotDefinition slotDefinition) { Equipee = equipee; Equipment = equipment; Slot = slotDefinition.Name; SlotGroup = slotDefinition.SlotGroup; + SlotFlags = slotDefinition.SlotFlags; } } diff --git a/Content.Shared/Inventory/InventoryComponent.cs b/Content.Shared/Inventory/InventoryComponent.cs index 2a8710f0f2..edc8e6641c 100644 --- a/Content.Shared/Inventory/InventoryComponent.cs +++ b/Content.Shared/Inventory/InventoryComponent.cs @@ -13,6 +13,19 @@ public sealed partial class InventoryComponent : Component [DataField("speciesId")] public string? SpeciesId { get; set; } + [DataField] public Dictionary Displacements = []; + public SlotDefinition[] Slots = Array.Empty(); + public ContainerSlot[] Containers = Array.Empty(); + + [DataDefinition] + public sealed partial class SlotDisplacementData + { + [DataField(required: true)] + public PrototypeLayerData Layer = default!; + + [DataField] + public string? ShaderOverride = "DisplacedStencilDraw"; + } } diff --git a/Content.Shared/Inventory/InventorySystem.Equip.cs b/Content.Shared/Inventory/InventorySystem.Equip.cs index 7bdd17ee6f..12435eba89 100644 --- a/Content.Shared/Inventory/InventorySystem.Equip.cs +++ b/Content.Shared/Inventory/InventorySystem.Equip.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; +using Content.Shared.Armor; using Content.Shared.Clothing.Components; using Content.Shared.DoAfter; using Content.Shared.Hands; @@ -114,7 +115,7 @@ private void OnUseSlot(UseSlotNetworkMessage ev, EntitySessionEventArgs eventArg if (!_handsSystem.CanDropHeld(actor, hands.ActiveHand!, checkActionBlocker: false)) return; - RaiseLocalEvent(held.Value, new HandDeselectedEvent(actor), false); + RaiseLocalEvent(held.Value, new HandDeselectedEvent(actor)); TryEquip(actor, actor, held.Value, ev.Slot, predicted: true, inventory: inventory, force: true, checkDoafter:true); } @@ -210,11 +211,7 @@ public bool CanAccess(EntityUid actor, EntityUid target, EntityUid itemUid) return false; // Can the actor reach the item? - if (_interactionSystem.InRangeUnobstructed(actor, itemUid) && _containerSystem.IsInSameOrParentContainer(actor, itemUid)) - return true; - - // Is the item in an open storage UI, i.e., is the user quick-equipping from an open backpack? - if (_interactionSystem.CanAccessViaStorage(actor, itemUid)) + if (_interactionSystem.InRangeAndAccessible(actor, itemUid)) return true; // Is the actor currently stripping the target? Here we could check if the actor has the stripping UI open, but @@ -244,8 +241,16 @@ public bool CanEquip(EntityUid actor, EntityUid target, EntityUid itemUid, strin return false; DebugTools.Assert(slotDefinition.Name == slot); - if (slotDefinition.DependsOn != null && !TryGetSlotEntity(target, slotDefinition.DependsOn, out _, inventory)) - return false; + if (slotDefinition.DependsOn != null) + { + if (!TryGetSlotEntity(target, slotDefinition.DependsOn, out EntityUid? slotEntity, inventory)) + return false; + + if (slotDefinition.DependsOnComponents is { } componentRegistry) + foreach (var (_, entry) in componentRegistry) + if (!HasComp(slotEntity, entry.Component.GetType())) + return false; + } var fittingInPocket = slotDefinition.SlotFlags.HasFlag(SlotFlags.POCKET) && item != null && @@ -302,7 +307,6 @@ public bool CanEquip(EntityUid actor, EntityUid target, EntityUid itemUid, strin reason = itemAttemptEvent.Reason ?? reason; return false; } - return true; } diff --git a/Content.Shared/Inventory/InventorySystem.Helpers.cs b/Content.Shared/Inventory/InventorySystem.Helpers.cs index 811387d375..7e325abe21 100644 --- a/Content.Shared/Inventory/InventorySystem.Helpers.cs +++ b/Content.Shared/Inventory/InventorySystem.Helpers.cs @@ -1,8 +1,6 @@ using System.Diagnostics.CodeAnalysis; -using System.Linq; using Content.Shared.Hands.Components; using Content.Shared.Storage.EntitySystems; -using Robust.Shared.Containers; using Robust.Shared.Prototypes; namespace Content.Shared.Inventory; @@ -96,7 +94,7 @@ bool DeleteItem() ///
/// The entity that you want to spawn an item on /// A list of prototype IDs that you want to spawn in the bag. - public void SpawnItemsOnEntity(EntityUid entity, List items) + public void SpawnItemsOnEntity(EntityUid entity, List items) { foreach (var item in items) { diff --git a/Content.Shared/Inventory/InventorySystem.Relay.cs b/Content.Shared/Inventory/InventorySystem.Relay.cs index 3308e881c5..39e10415f8 100644 --- a/Content.Shared/Inventory/InventorySystem.Relay.cs +++ b/Content.Shared/Inventory/InventorySystem.Relay.cs @@ -13,6 +13,7 @@ using Content.Shared.Strip.Components; using Content.Shared.Temperature; using Content.Shared.Verbs; +using Content.Shared.Chat; namespace Content.Shared.Inventory; @@ -29,6 +30,7 @@ public void InitializeRelay() SubscribeLocalEvent(RelayInventoryEvent); SubscribeLocalEvent(RelayInventoryEvent); SubscribeLocalEvent(RelayInventoryEvent); + SubscribeLocalEvent(RelayInventoryEvent); // by-ref events SubscribeLocalEvent(RefRelayInventoryEvent); @@ -40,12 +42,14 @@ public void InitializeRelay() SubscribeLocalEvent(RelayInventoryEvent); // ComponentActivatedClientSystems - SubscribeLocalEvent>(RelayInventoryEvent); + SubscribeLocalEvent>(RelayInventoryEvent); SubscribeLocalEvent>(RelayInventoryEvent); SubscribeLocalEvent>(RelayInventoryEvent); SubscribeLocalEvent>(RelayInventoryEvent); SubscribeLocalEvent>(RelayInventoryEvent); + SubscribeLocalEvent>(RelayInventoryEvent); SubscribeLocalEvent>(RelayInventoryEvent); + SubscribeLocalEvent>(RelayInventoryEvent); SubscribeLocalEvent>(OnGetEquipmentVerbs); } @@ -123,6 +127,11 @@ public InventoryRelayedEvent(TEvent args) } } +public interface IClothingSlots +{ + SlotFlags Slots { get; } +} + /// /// Events that should be relayed to inventory slots should implement this interface. /// diff --git a/Content.Shared/Inventory/InventorySystem.Slots.cs b/Content.Shared/Inventory/InventorySystem.Slots.cs index cbbee3a85b..97f63262db 100644 --- a/Content.Shared/Inventory/InventorySystem.Slots.cs +++ b/Content.Shared/Inventory/InventorySystem.Slots.cs @@ -1,18 +1,25 @@ +using Content.Shared.Random; using System.Diagnostics.CodeAnalysis; +using Content.Shared.Inventory.Events; +using Content.Shared.Storage; using Robust.Shared.Containers; using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.Manager; using Robust.Shared.Utility; +using System.Diagnostics.CodeAnalysis; +using System.Linq; namespace Content.Shared.Inventory; - public partial class InventorySystem : EntitySystem { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IViewVariablesManager _vvm = default!; - + [Dependency] private readonly RandomHelperSystem _randomHelper = default!; + [Dependency] private readonly ISerializationManager _serializationManager = default!; private void InitializeSlots() { SubscribeLocalEvent(OnInit); + SubscribeNetworkEvent(OnOpenSlotStorage); _vvm.GetTypeHandler() .AddHandler(HandleViewVariablesSlots, ListViewVariablesSlots); @@ -24,12 +31,38 @@ private void ShutdownSlots() .RemoveHandler(HandleViewVariablesSlots, ListViewVariablesSlots); } + /// + /// Tries to find an entity in the specified slot with the specified component. + /// + public bool TryGetInventoryEntity(Entity entity, out EntityUid targetUid) + where T : IComponent, IClothingSlots + { + if (TryGetContainerSlotEnumerator(entity.Owner, out var containerSlotEnumerator)) + { + while (containerSlotEnumerator.NextItem(out var item, out var slot)) + { + if (!TryComp(item, out var required)) + continue; + + if ((((IClothingSlots) required).Slots & slot.SlotFlags) == 0x0) + continue; + + targetUid = item; + return true; + } + } + + targetUid = EntityUid.Invalid; + return false; + } + protected virtual void OnInit(EntityUid uid, InventoryComponent component, ComponentInit args) { if (!_prototypeManager.TryIndex(component.TemplateId, out InventoryTemplatePrototype? invTemplate)) return; - component.Slots = invTemplate.Slots; + _serializationManager.CopyTo(invTemplate.Slots, ref component.Slots, notNullableOverride: true); + component.Containers = new ContainerSlot[component.Slots.Length]; for (var i = 0; i < component.Containers.Length; i++) { @@ -40,6 +73,17 @@ protected virtual void OnInit(EntityUid uid, InventoryComponent component, Compo } } + private void OnOpenSlotStorage(OpenSlotStorageNetworkMessage ev, EntitySessionEventArgs args) + { + if (args.SenderSession.AttachedEntity is not { Valid: true } uid) + return; + + if (TryGetSlotEntity(uid, ev.Slot, out var entityUid) && TryComp(entityUid, out var storageComponent)) + { + _storageSystem.OpenStorageUI(entityUid.Value, uid, storageComponent); + } + } + public bool TryGetSlotContainer(EntityUid uid, string slot, [NotNullWhen(true)] out ContainerSlot? containerSlot, [NotNullWhen(true)] out SlotDefinition? slotDefinition, InventoryComponent? inventory = null, ContainerManagerComponent? containerComp = null) { @@ -76,7 +120,7 @@ public bool TryGetSlot(EntityUid uid, string slot, [NotNullWhen(true)] out SlotD foreach (var slotDef in inventory.Slots) { - if (!slotDef.Name.Equals(slot)) + if (!slotDef.Name.Equals(slot) || slotDef.Disabled) continue; slotDefinition = slotDef; return true; @@ -112,7 +156,6 @@ public bool TryGetSlots(EntityUid uid, [NotNullWhen(true)] out SlotDefinition[]? slotDefinitions = null; return false; } - slotDefinitions = inv.Slots; return true; } @@ -132,6 +175,37 @@ private IEnumerable ListViewVariablesSlots(EntityUid uid, InventoryCompo } } + public void SetSlotStatus(EntityUid uid, string slotName, bool isDisabled, InventoryComponent? inventory = null) + { + if (!Resolve(uid, ref inventory)) + return; + + foreach (var slot in inventory.Slots) + { + if (slot.Name != slotName) + continue; + + if (isDisabled) + { + if (!TryGetSlotContainer(uid, slotName, out var container, out _, inventory)) + break; + + if (container.ContainedEntity is { } entityUid && TryComp(entityUid, out TransformComponent? transform) && _gameTiming.IsFirstTimePredicted) + { + _transform.AttachToGridOrMap(entityUid, transform); + _randomHelper.RandomOffset(entityUid, 0.5f); + } + //_containerSystem.ShutdownContainer(container); + } + //else + //_containerSystem.EnsureContainer(uid, slotName); + slot.Disabled = isDisabled; + break; + } + + Dirty(uid, inventory); + } + /// /// Enumerator for iterating over an inventory's slot containers. Also has methods that skip empty containers. /// It should be safe to add or remove items while enumerating. @@ -144,12 +218,12 @@ public struct InventorySlotEnumerator private int _nextIdx = 0; public static InventorySlotEnumerator Empty = new(Array.Empty(), Array.Empty()); - public InventorySlotEnumerator(InventoryComponent inventory, SlotFlags flags = SlotFlags.All) + public InventorySlotEnumerator(InventoryComponent inventory, SlotFlags flags = SlotFlags.All) : this(inventory.Slots, inventory.Containers, flags) { } - public InventorySlotEnumerator(SlotDefinition[] slots, ContainerSlot[] containers, SlotFlags flags = SlotFlags.All) + public InventorySlotEnumerator(SlotDefinition[] slots, ContainerSlot[] containers, SlotFlags flags = SlotFlags.All) { DebugTools.Assert(flags != SlotFlags.NONE); DebugTools.AssertEqual(slots.Length, containers.Length); @@ -165,7 +239,7 @@ public bool MoveNext([NotNullWhen(true)] out ContainerSlot? container) var i = _nextIdx++; var slot = _slots[i]; - if ((slot.SlotFlags & _flags) == 0) + if ((slot.SlotFlags & _flags) == 0 || slot.Disabled) continue; container = _containers[i]; @@ -183,7 +257,7 @@ public bool NextItem(out EntityUid item) var i = _nextIdx++; var slot = _slots[i]; - if ((slot.SlotFlags & _flags) == 0) + if ((slot.SlotFlags & _flags) == 0 || slot.Disabled) continue; var container = _containers[i]; diff --git a/Content.Shared/Inventory/InventoryTemplatePrototype.cs b/Content.Shared/Inventory/InventoryTemplatePrototype.cs index 585f80d4ce..0d900688fc 100644 --- a/Content.Shared/Inventory/InventoryTemplatePrototype.cs +++ b/Content.Shared/Inventory/InventoryTemplatePrototype.cs @@ -17,6 +17,10 @@ public sealed partial class SlotDefinition { [DataField("name", required: true)] public string Name { get; private set; } = string.Empty; [DataField("slotTexture")] public string TextureName { get; private set; } = "pocket"; + /// + /// The texture displayed in a slot when it has an item inside of it. + /// + [DataField] public string FullTextureName { get; private set; } = "SlotBackground"; [DataField("slotFlags")] public SlotFlags SlotFlags { get; private set; } = SlotFlags.PREVENTEQUIP; [DataField("showInWindow")] public bool ShowInWindow { get; private set; } = true; [DataField("slotGroup")] public string SlotGroup { get; private set; } = "Default"; @@ -30,6 +34,8 @@ public sealed partial class SlotDefinition [DataField("dependsOn")] public string? DependsOn { get; private set; } + [DataField("dependsOnComponents")] public ComponentRegistry? DependsOnComponents { get; private set; } + [DataField("displayName", required: true)] public string DisplayName { get; private set; } = string.Empty; @@ -49,4 +55,9 @@ public sealed partial class SlotDefinition /// Entity blacklist for CanEquip checks. /// [DataField("blacklist")] public EntityWhitelist? Blacklist = null; + + /// + /// Is this slot disabled? Could be due to severing or other reasons. + /// + [DataField] public bool Disabled; } diff --git a/Content.Shared/Inventory/SlotFlags.cs b/Content.Shared/Inventory/SlotFlags.cs index 8d5e33e348..90971d1670 100644 --- a/Content.Shared/Inventory/SlotFlags.cs +++ b/Content.Shared/Inventory/SlotFlags.cs @@ -27,4 +27,6 @@ public enum SlotFlags FEET = 1 << 14, SUITSTORAGE = 1 << 15, All = ~NONE, + + WITHOUT_POCKET = All & ~POCKET } diff --git a/Content.Shared/Inventory/VirtualItem/SharedVirtualItemSystem.cs b/Content.Shared/Inventory/VirtualItem/SharedVirtualItemSystem.cs index e45530e458..60beb54652 100644 --- a/Content.Shared/Inventory/VirtualItem/SharedVirtualItemSystem.cs +++ b/Content.Shared/Inventory/VirtualItem/SharedVirtualItemSystem.cs @@ -4,6 +4,7 @@ using Content.Shared.Interaction; using Content.Shared.Inventory.Events; using Content.Shared.Item; +using Content.Shared.Popups; using Robust.Shared.Containers; using Robust.Shared.Network; using Robust.Shared.Prototypes; @@ -29,6 +30,7 @@ public abstract class SharedVirtualItemSystem : EntitySystem [Dependency] private readonly SharedItemSystem _itemSystem = default!; [Dependency] private readonly InventorySystem _inventorySystem = default!; [Dependency] private readonly SharedHandsSystem _handsSystem = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; [ValidatePrototypeId] private const string VirtualItem = "VirtualItem"; @@ -71,23 +73,48 @@ private void OnBeforeRangedInteract(Entity ent, ref Before } #region Hands + /// /// Spawns a virtual item in a empty hand /// /// The entity we will make a virtual entity copy of /// The entity that we want to insert the virtual entity - public bool TrySpawnVirtualItemInHand(EntityUid blockingEnt, EntityUid user) + /// Whether or not to try and drop other items to make space + public bool TrySpawnVirtualItemInHand(EntityUid blockingEnt, EntityUid user, bool dropOthers = false) { - return TrySpawnVirtualItemInHand(blockingEnt, user, out _); + return TrySpawnVirtualItemInHand(blockingEnt, user, out _, dropOthers); } - /// - public bool TrySpawnVirtualItemInHand(EntityUid blockingEnt, EntityUid user, [NotNullWhen(true)] out EntityUid? virtualItem) + /// + public bool TrySpawnVirtualItemInHand(EntityUid blockingEnt, EntityUid user, [NotNullWhen(true)] out EntityUid? virtualItem, bool dropOthers = false) { - if (!TrySpawnVirtualItem(blockingEnt, user, out virtualItem) || !_handsSystem.TryGetEmptyHand(user, out var hand)) + virtualItem = null; + if (!_handsSystem.TryGetEmptyHand(user, out var empty)) + { + if (!dropOthers) + return false; + + foreach (var hand in _handsSystem.EnumerateHands(user)) + { + if (hand.HeldEntity is not { } held + || held == blockingEnt + || HasComp(held) + || !_handsSystem.TryDrop(user, hand)) + continue; + + if (!TerminatingOrDeleted(held)) + _popup.PopupClient(Loc.GetString("virtual-item-dropped-other", ("dropped", held)), user, user); + + empty = hand; + break; + } + } + + if (empty == null + || !TrySpawnVirtualItem(blockingEnt, user, out virtualItem)) return false; - _handsSystem.DoPickup(user, hand, virtualItem.Value); + _handsSystem.DoPickup(user, empty, virtualItem.Value); return true; } @@ -120,6 +147,7 @@ public void DeleteInHandsMatching(EntityUid user, EntityUid matching) /// The entity we will make a virtual entity copy of /// The entity that we want to insert the virtual entity /// The slot to which we will insert the virtual entity (could be the "shoes" slot, for example) + /// Whether or not to force an equip public bool TrySpawnVirtualItemInInventory(EntityUid blockingEnt, EntityUid user, string slot, bool force = false) { return TrySpawnVirtualItemInInventory(blockingEnt, user, slot, force, out _); @@ -140,6 +168,8 @@ public bool TrySpawnVirtualItemInInventory(EntityUid blockingEnt, EntityUid user /// that's done check if the found virtual entity is a copy of our matching entity, /// if it is, delete it ///
+ /// The entity that we want to delete the virtual entity from + /// The entity that made the virtual entity /// Set this param if you have the name of the slot, it avoids unnecessary queries public void DeleteInSlotMatching(EntityUid user, EntityUid matching, string? slotName = null) { @@ -178,6 +208,7 @@ public void DeleteInSlotMatching(EntityUid user, EntityUid matching, string? slo ///
/// The entity we will make a virtual entity copy of /// The entity that we want to insert the virtual entity + /// The virtual item, if spawned public bool TrySpawnVirtualItem(EntityUid blockingEnt, EntityUid user, [NotNullWhen(true)] out EntityUid? virtualItem) { if (_netManager.IsClient) diff --git a/Content.Shared/Item/SharedItemSystem.cs b/Content.Shared/Item/SharedItemSystem.cs index 29e82f8ade..5eaa25f484 100644 --- a/Content.Shared/Item/SharedItemSystem.cs +++ b/Content.Shared/Item/SharedItemSystem.cs @@ -192,7 +192,7 @@ public IReadOnlyList GetAdjustedItemShape(Entity entity, var shapes = GetItemShape(entity); var boundingShape = shapes.GetBoundingBox(); var boundingCenter = ((Box2) boundingShape).Center; - var matty = Matrix3.CreateTransform(boundingCenter, rotation); + var matty = Matrix3Helpers.CreateTransform(boundingCenter, rotation); var drift = boundingShape.BottomLeft - matty.TransformBox(boundingShape).BottomLeft; var adjustedShapes = new List(); diff --git a/Content.Shared/Labels/Components/HandLabelerComponent.cs b/Content.Shared/Labels/Components/HandLabelerComponent.cs new file mode 100644 index 0000000000..8e2cb7b067 --- /dev/null +++ b/Content.Shared/Labels/Components/HandLabelerComponent.cs @@ -0,0 +1,30 @@ +using Content.Shared.Labels.EntitySystems; +using Content.Shared.Whitelist; +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; + +namespace Content.Shared.Labels.Components; + +[RegisterComponent, NetworkedComponent] +[Access(typeof(SharedHandLabelerSystem))] +public sealed partial class HandLabelerComponent : Component +{ + [ViewVariables(VVAccess.ReadWrite), Access(Other = AccessPermissions.ReadWriteExecute)] + [DataField] + public string AssignedLabel = string.Empty; + + [ViewVariables(VVAccess.ReadWrite)] + [DataField] + public int MaxLabelChars = 50; + + [DataField] + public EntityWhitelist Whitelist = new(); +} + +[Serializable, NetSerializable] +public sealed class HandLabelerComponentState(string assignedLabel) : IComponentState +{ + public string AssignedLabel = assignedLabel; + + public int MaxLabelChars; +} diff --git a/Content.Shared/Labels/EntitySystems/SharedHandLabelerSystem.cs b/Content.Shared/Labels/EntitySystems/SharedHandLabelerSystem.cs new file mode 100644 index 0000000000..7dbeee3e77 --- /dev/null +++ b/Content.Shared/Labels/EntitySystems/SharedHandLabelerSystem.cs @@ -0,0 +1,129 @@ +using Content.Shared.Administration.Logs; +using Content.Shared.Database; +using Content.Shared.Interaction; +using Content.Shared.Labels.Components; +using Content.Shared.Popups; +using Content.Shared.Verbs; +using Robust.Shared.GameStates; +using Robust.Shared.Network; + +namespace Content.Shared.Labels.EntitySystems; + +public abstract class SharedHandLabelerSystem : EntitySystem +{ + [Dependency] protected readonly SharedUserInterfaceSystem UserInterfaceSystem = default!; + [Dependency] private readonly SharedPopupSystem _popupSystem = default!; + [Dependency] private readonly SharedLabelSystem _labelSystem = default!; + [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; + [Dependency] private readonly INetManager _netManager = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(AfterInteractOn); + SubscribeLocalEvent>(OnUtilityVerb); + // Bound UI subscriptions + SubscribeLocalEvent(OnHandLabelerLabelChanged); + SubscribeLocalEvent(OnGetState); + SubscribeLocalEvent(OnHandleState); + } + + private void OnGetState(Entity ent, ref ComponentGetState args) + { + args.State = new HandLabelerComponentState(ent.Comp.AssignedLabel) + { + MaxLabelChars = ent.Comp.MaxLabelChars, + }; + } + + private void OnHandleState(Entity ent, ref ComponentHandleState args) + { + if (args.Current is not HandLabelerComponentState state) + return; + + ent.Comp.MaxLabelChars = state.MaxLabelChars; + + if (ent.Comp.AssignedLabel == state.AssignedLabel) + return; + + ent.Comp.AssignedLabel = state.AssignedLabel; + UpdateUI(ent); + } + + protected virtual void UpdateUI(Entity ent) + { + } + + private void AddLabelTo(EntityUid uid, HandLabelerComponent? handLabeler, EntityUid target, out string? result) + { + if (!Resolve(uid, ref handLabeler)) + { + result = null; + return; + } + + if (handLabeler.AssignedLabel == string.Empty) + { + if (_netManager.IsServer) + _labelSystem.Label(target, null); + result = Loc.GetString("hand-labeler-successfully-removed"); + return; + } + if (_netManager.IsServer) + _labelSystem.Label(target, handLabeler.AssignedLabel); + result = Loc.GetString("hand-labeler-successfully-applied"); + } + + private void OnUtilityVerb(EntityUid uid, HandLabelerComponent handLabeler, GetVerbsEvent args) + { + if (args.Target is not { Valid: true } target || !handLabeler.Whitelist.IsValid(target) || !args.CanAccess) + return; + + var labelerText = handLabeler.AssignedLabel == string.Empty ? Loc.GetString("hand-labeler-remove-label-text") : Loc.GetString("hand-labeler-add-label-text"); + + var verb = new UtilityVerb() + { + Act = () => + { + Labeling(uid, target, args.User, handLabeler); + }, + Text = labelerText + }; + + args.Verbs.Add(verb); + } + + private void AfterInteractOn(EntityUid uid, HandLabelerComponent handLabeler, AfterInteractEvent args) + { + if (args.Target is not { Valid: true } target || !handLabeler.Whitelist.IsValid(target) || !args.CanReach) + return; + + Labeling(uid, target, args.User, handLabeler); + } + + private void Labeling(EntityUid uid, EntityUid target, EntityUid User, HandLabelerComponent handLabeler) + { + AddLabelTo(uid, handLabeler, target, out var result); + if (result == null) + return; + + _popupSystem.PopupClient(result, User, User); + + // Log labeling + _adminLogger.Add(LogType.Action, LogImpact.Low, + $"{ToPrettyString(User):user} labeled {ToPrettyString(target):target} with {ToPrettyString(uid):labeler}"); + } + + private void OnHandLabelerLabelChanged(EntityUid uid, HandLabelerComponent handLabeler, HandLabelerLabelChangedMessage args) + { + var label = args.Label.Trim(); + handLabeler.AssignedLabel = label[..Math.Min(handLabeler.MaxLabelChars, label.Length)]; + UpdateUI((uid, handLabeler)); + Dirty(uid, handLabeler); + + // Log label change + _adminLogger.Add(LogType.Action, LogImpact.Low, + $"{ToPrettyString(args.Actor):user} set {ToPrettyString(uid):labeler} to apply label \"{handLabeler.AssignedLabel}\""); + } +} diff --git a/Content.Shared/Labels/EntitySystems/SharedLabelSystem.cs b/Content.Shared/Labels/EntitySystems/SharedLabelSystem.cs index a8239e7867..1189bb46d0 100644 --- a/Content.Shared/Labels/EntitySystems/SharedLabelSystem.cs +++ b/Content.Shared/Labels/EntitySystems/SharedLabelSystem.cs @@ -13,6 +13,8 @@ public override void Initialize() SubscribeLocalEvent(OnExamine); } + public virtual void Label(EntityUid uid, string? text, MetaDataComponent? metadata = null, LabelComponent? label = null){} + private void OnExamine(EntityUid uid, LabelComponent? label, ExaminedEvent args) { if (!Resolve(uid, ref label)) diff --git a/Content.Shared/Labels/LabelEvents.cs b/Content.Shared/Labels/LabelEvents.cs index 9f00354af2..62e9c15c85 100644 --- a/Content.Shared/Labels/LabelEvents.cs +++ b/Content.Shared/Labels/LabelEvents.cs @@ -1,47 +1,27 @@ using Robust.Shared.Serialization; -namespace Content.Shared.Labels -{ - /// - /// Key representing which is currently open. - /// Useful when there are multiple UI for an object. Here it's future-proofing only. - /// - [Serializable, NetSerializable] - public enum HandLabelerUiKey - { - Key, - } - - [Serializable, NetSerializable] - public enum PaperLabelVisuals : byte - { - Layer, - HasLabel, - LabelType - } +namespace Content.Shared.Labels; - /// - /// Represents a state that can be sent to the client - /// - [Serializable, NetSerializable] - public sealed class HandLabelerBoundUserInterfaceState : BoundUserInterfaceState - { - public string CurrentLabel { get; } - - public HandLabelerBoundUserInterfaceState(string currentLabel) - { - CurrentLabel = currentLabel; - } - } +/// +/// Key representing which is currently open. +/// Useful when there are multiple UI for an object. Here it's future-proofing only. +/// +[Serializable, NetSerializable] +public enum HandLabelerUiKey +{ + Key, +} - [Serializable, NetSerializable] - public sealed class HandLabelerLabelChangedMessage : BoundUserInterfaceMessage - { - public string Label { get; } +[Serializable, NetSerializable] +public enum PaperLabelVisuals : byte +{ + Layer, + HasLabel, + LabelType +} - public HandLabelerLabelChangedMessage(string label) - { - Label = label; - } - } +[Serializable, NetSerializable] +public sealed class HandLabelerLabelChangedMessage(string label) : BoundUserInterfaceMessage +{ + public string Label { get; } = label; } diff --git a/Content.Shared/Language/Components/LanguageKnowledgeComponent.cs b/Content.Shared/Language/Components/LanguageKnowledgeComponent.cs deleted file mode 100644 index ddbdc742be..0000000000 --- a/Content.Shared/Language/Components/LanguageKnowledgeComponent.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; - -namespace Content.Shared.Language.Components; - -// TODO: move to server side, it's never synchronized! - -/// -/// Stores data about entities' intrinsic language knowledge. -/// -[RegisterComponent] -public sealed partial class LanguageKnowledgeComponent : Component -{ - /// - /// List of languages this entity can speak without any external tools. - /// - [DataField("speaks", customTypeSerializer: typeof(PrototypeIdListSerializer), required: true)] - public List SpokenLanguages = new(); - - /// - /// List of languages this entity can understand without any external tools. - /// - [DataField("understands", customTypeSerializer: typeof(PrototypeIdListSerializer), required: true)] - public List UnderstoodLanguages = new(); -} diff --git a/Content.Shared/Language/Components/LanguageSpeakerComponent.cs b/Content.Shared/Language/Components/LanguageSpeakerComponent.cs index e8ebccb3dd..f026361cad 100644 --- a/Content.Shared/Language/Components/LanguageSpeakerComponent.cs +++ b/Content.Shared/Language/Components/LanguageSpeakerComponent.cs @@ -1,7 +1,9 @@ -namespace Content.Shared.Language; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; +using Robust.Shared.Serialization.TypeSerializers.Implementations; -// TODO: either move all language speaker-related components to server side, or make everything else shared. -// The current approach leads to confusion, as the server never informs the client of updates in these components. +namespace Content.Shared.Language.Components; /// /// Stores the current state of the languages the entity can speak and understand. @@ -10,23 +12,35 @@ namespace Content.Shared.Language; /// All fields of this component are populated during a DetermineEntityLanguagesEvent. /// They are not to be modified externally. /// -[RegisterComponent] +[RegisterComponent, NetworkedComponent] public sealed partial class LanguageSpeakerComponent : Component { + public override bool SendOnlyToOwner => true; + /// /// The current language the entity uses when speaking. /// Other listeners will hear the entity speak in this language. /// [DataField] - public string CurrentLanguage = ""; // The language system will override it on init + public string CurrentLanguage = ""; // The language system will override it on mapinit /// /// List of languages this entity can speak at the current moment. /// - public List SpokenLanguages = []; + [DataField] + public List> SpokenLanguages = []; /// /// List of languages this entity can understand at the current moment. /// - public List UnderstoodLanguages = []; + [DataField] + public List> UnderstoodLanguages = []; + + [Serializable, NetSerializable] + public sealed class State : ComponentState + { + public string CurrentLanguage = default!; + public List> SpokenLanguages = default!; + public List> UnderstoodLanguages = default!; + } } diff --git a/Content.Shared/Language/Components/Translators/BaseTranslatorComponent.cs b/Content.Shared/Language/Components/Translators/BaseTranslatorComponent.cs index 072480031d..8bd1f6be48 100644 --- a/Content.Shared/Language/Components/Translators/BaseTranslatorComponent.cs +++ b/Content.Shared/Language/Components/Translators/BaseTranslatorComponent.cs @@ -1,3 +1,4 @@ +using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; namespace Content.Shared.Language.Components.Translators; @@ -7,21 +8,21 @@ public abstract partial class BaseTranslatorComponent : Component /// /// The list of additional languages this translator allows the wielder to speak. /// - [DataField("spoken", customTypeSerializer: typeof(PrototypeIdListSerializer))] - public List SpokenLanguages = new(); + [DataField("spoken")] + public List> SpokenLanguages = new(); /// /// The list of additional languages this translator allows the wielder to understand. /// - [DataField("understood", customTypeSerializer: typeof(PrototypeIdListSerializer))] - public List UnderstoodLanguages = new(); + [DataField("understood")] + public List> UnderstoodLanguages = new(); /// /// The languages the wielding MUST know in order for this translator to have effect. /// The field [RequiresAllLanguages] indicates whether all of them are required, or just one. /// - [DataField("requires", customTypeSerializer: typeof(PrototypeIdListSerializer))] - public List RequiredLanguages = new(); + [DataField("requires")] + public List> RequiredLanguages = new(); /// /// If true, the wielder must understand all languages in [RequiredLanguages] to speak [SpokenLanguages], @@ -30,9 +31,8 @@ public abstract partial class BaseTranslatorComponent : Component /// Otherwise, at least one language must be known (or the list must be empty). /// [DataField("requiresAll")] - [ViewVariables(VVAccess.ReadWrite)] public bool RequiresAllLanguages = false; - [DataField("enabled"), ViewVariables(VVAccess.ReadWrite)] + [DataField("enabled")] public bool Enabled = true; } diff --git a/Content.Shared/Language/Components/Translators/HandheldTranslatorComponent.cs b/Content.Shared/Language/Components/Translators/HandheldTranslatorComponent.cs index 7e3de0eca6..6b2f434fa7 100644 --- a/Content.Shared/Language/Components/Translators/HandheldTranslatorComponent.cs +++ b/Content.Shared/Language/Components/Translators/HandheldTranslatorComponent.cs @@ -4,7 +4,7 @@ namespace Content.Shared.Language.Components.Translators; /// A translator that must be held in a hand or a pocket of an entity in order ot have effect. /// [RegisterComponent] -public sealed partial class HandheldTranslatorComponent : Translators.BaseTranslatorComponent +public sealed partial class HandheldTranslatorComponent : BaseTranslatorComponent { /// /// Whether interacting with this translator toggles it on and off. diff --git a/Content.Shared/Language/Components/Translators/HoldsTranslatorComponent.cs b/Content.Shared/Language/Components/Translators/HoldsTranslatorComponent.cs index caea9b9a94..b35cc7fd03 100644 --- a/Content.Shared/Language/Components/Translators/HoldsTranslatorComponent.cs +++ b/Content.Shared/Language/Components/Translators/HoldsTranslatorComponent.cs @@ -1,11 +1,11 @@ namespace Content.Shared.Language.Components.Translators; /// -/// Applied internally to the holder of [HandheldTranslatorComponent]. -/// Do not use directly. Use [HandheldTranslatorComponent] instead. +/// Applied internally to the holder of an entity with [HandheldTranslatorComponent]. /// [RegisterComponent] -public sealed partial class HoldsTranslatorComponent : IntrinsicTranslatorComponent +public sealed partial class HoldsTranslatorComponent : Component { - public Component? Issuer = null; + [NonSerialized] + public HashSet> Translators = new(); } diff --git a/Content.Shared/Language/Components/Translators/ImplantedTranslatorComponent.cs b/Content.Shared/Language/Components/Translators/ImplantedTranslatorComponent.cs deleted file mode 100644 index d1d72e83ed..0000000000 --- a/Content.Shared/Language/Components/Translators/ImplantedTranslatorComponent.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Content.Shared.Language.Components.Translators; - -/// -/// Applied to entities who were injected with a translator implant. -/// -[RegisterComponent] -public sealed partial class ImplantedTranslatorComponent : IntrinsicTranslatorComponent -{ -} diff --git a/Content.Shared/Language/Components/UniversalLanguageSpeakerComponent.cs b/Content.Shared/Language/Components/UniversalLanguageSpeakerComponent.cs deleted file mode 100644 index 6f5ad1178b..0000000000 --- a/Content.Shared/Language/Components/UniversalLanguageSpeakerComponent.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Content.Shared.Language.Components; - -// -// Signifies that this entity can speak and understand any language. -// Applies to such entities as ghosts. -// -[RegisterComponent] -public sealed partial class UniversalLanguageSpeakerComponent : Component -{ - -} diff --git a/Content.Server/Language/DetermineEntityLanguagesEvent.cs b/Content.Shared/Language/Events/DetermineEntityLanguagesEvent.cs similarity index 77% rename from Content.Server/Language/DetermineEntityLanguagesEvent.cs rename to Content.Shared/Language/Events/DetermineEntityLanguagesEvent.cs index 8d6b868d07..a01e5613f8 100644 --- a/Content.Server/Language/DetermineEntityLanguagesEvent.cs +++ b/Content.Shared/Language/Events/DetermineEntityLanguagesEvent.cs @@ -1,6 +1,6 @@ -using Content.Shared.Language; +using Robust.Shared.Prototypes; -namespace Content.Server.Language; +namespace Content.Shared.Language.Events; /// /// Raised in order to determine the list of languages the entity can speak and understand at the given moment. @@ -13,13 +13,13 @@ public record struct DetermineEntityLanguagesEvent /// The list of all languages the entity may speak. /// By default, contains the languages this entity speaks intrinsically. /// - public HashSet SpokenLanguages = new(); + public HashSet> SpokenLanguages = new(); /// /// The list of all languages the entity may understand. /// By default, contains the languages this entity understands intrinsically. /// - public HashSet UnderstoodLanguages = new(); + public HashSet> UnderstoodLanguages = new(); public DetermineEntityLanguagesEvent() {} } diff --git a/Content.Shared/Language/Events/LanguagesUpdateEvent.cs b/Content.Shared/Language/Events/LanguagesUpdateEvent.cs new file mode 100644 index 0000000000..fa68bf5af6 --- /dev/null +++ b/Content.Shared/Language/Events/LanguagesUpdateEvent.cs @@ -0,0 +1,12 @@ +namespace Content.Shared.Language.Events; + +/// +/// Raised on an entity when its list of languages changes. +/// +/// +/// This is raised both on the server and on the client. +/// The client raises it broadcast after receiving a new language comp state from the server. +/// +public sealed class LanguagesUpdateEvent : EntityEventArgs +{ +} diff --git a/Content.Shared/Language/Events/LanguagesUpdatedMessage.cs b/Content.Shared/Language/Events/LanguagesUpdatedMessage.cs deleted file mode 100644 index 563f036df6..0000000000 --- a/Content.Shared/Language/Events/LanguagesUpdatedMessage.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Robust.Shared.Serialization; - -namespace Content.Shared.Language.Events; - -/// -/// Sent to the client when its list of languages changes. -/// The client should in turn update its HUD and relevant systems. -/// -[Serializable, NetSerializable] -public sealed class LanguagesUpdatedMessage(string currentLanguage, List spoken, List understood) : EntityEventArgs -{ - public string CurrentLanguage = currentLanguage; - public List Spoken = spoken; - public List Understood = understood; -} diff --git a/Content.Shared/Language/Events/RequestLanguagesMessage.cs b/Content.Shared/Language/Events/RequestLanguagesMessage.cs deleted file mode 100644 index aead1f4cd1..0000000000 --- a/Content.Shared/Language/Events/RequestLanguagesMessage.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Robust.Shared.Serialization; - -namespace Content.Shared.Language.Events; - -/// -/// Sent from the client to the server when it needs to learn the list of languages its entity knows. -/// This event should always be followed by a , unless the client doesn't have an entity. -/// -[Serializable, NetSerializable] -public sealed class RequestLanguagesMessage : EntityEventArgs; diff --git a/Content.Shared/Language/LanguagePrototype.cs b/Content.Shared/Language/LanguagePrototype.cs index d40a7b4068..dbc53c65d4 100644 --- a/Content.Shared/Language/LanguagePrototype.cs +++ b/Content.Shared/Language/LanguagePrototype.cs @@ -4,10 +4,16 @@ namespace Content.Shared.Language; [Prototype("language")] -public sealed class LanguagePrototype : IPrototype +public sealed partial class LanguagePrototype : IPrototype { [IdDataField] - public string ID { get; private set; } = default!; + public string ID { get; private set; } = default!; + + /// + /// Whether this language will display its name in chat behind a player's name. + /// + [DataField] + public bool IsVisibleLanguage { get; set; } /// /// Obfuscation method used by this language. By default, uses @@ -27,6 +33,11 @@ public sealed class LanguagePrototype : IPrototype /// public string Name => Loc.GetString($"language-{ID}-name"); + /// + /// The in-world chat abbreviation of this language, localized. + /// + public string ChatName => Loc.GetString($"chat-language-{ID}-name"); + /// /// The in-world description of this language, localized. /// @@ -53,6 +64,14 @@ public sealed partial class SpeechOverrideInfo [DataField] public bool AllowRadio = true; + /// + /// If true, the message will be relayed to the Empathy Chat and + /// anyone with that language will also hear Empathy Chat. (Unless user has ShadowkinBlackeyeComponent) + /// This is mostly only use for "Marish" but... fuckit modularity :p + /// + [DataField] + public bool EmpathySpeech = false; + /// /// If false, the entity can use this language even when it's unable to speak (i.e. muffled or muted), /// and accents are not applied to messages in this language. diff --git a/Content.Shared/Language/Systems/SharedLanguageSystem.cs b/Content.Shared/Language/Systems/SharedLanguageSystem.cs index 0a03086ebe..86a58dc2a7 100644 --- a/Content.Shared/Language/Systems/SharedLanguageSystem.cs +++ b/Content.Shared/Language/Systems/SharedLanguageSystem.cs @@ -10,7 +10,7 @@ public abstract class SharedLanguageSystem : EntitySystem /// The language used as a fallback in cases where an entity suddenly becomes a language speaker (e.g. the usage of make-sentient) /// [ValidatePrototypeId] - public static readonly string FallbackLanguagePrototype = "GalacticCommon"; + public static readonly string FallbackLanguagePrototype = "TauCetiBasic"; /// /// The language whose speakers are assumed to understand and speak every language. Should never be added directly. @@ -31,9 +31,9 @@ public override void Initialize() Universal = _prototype.Index("Universal"); } - public LanguagePrototype? GetLanguagePrototype(string id) + public LanguagePrototype? GetLanguagePrototype(ProtoId id) { - _prototype.TryIndex(id, out var proto); + _prototype.TryIndex(id, out var proto); return proto; } @@ -43,8 +43,7 @@ public override void Initialize() public string ObfuscateSpeech(string message, LanguagePrototype language) { var builder = new StringBuilder(); - var method = language.Obfuscation; - method.Obfuscate(builder, message, this); + language.Obfuscation.Obfuscate(builder, message, this); return builder.ToString(); } diff --git a/Content.Shared/Light/Components/SharedExpendableLightComponent.cs b/Content.Shared/Light/Components/SharedExpendableLightComponent.cs index e40174ab78..001794880a 100644 --- a/Content.Shared/Light/Components/SharedExpendableLightComponent.cs +++ b/Content.Shared/Light/Components/SharedExpendableLightComponent.cs @@ -7,7 +7,6 @@ namespace Content.Shared.Light.Components; [NetworkedComponent] public abstract partial class SharedExpendableLightComponent : Component { - public static readonly AudioParams LoopedSoundParams = new(0, 1, 62.5f, 1, 1, true, 0.3f); [ViewVariables(VVAccess.ReadOnly)] public ExpendableLightState CurrentState { get; set; } diff --git a/Content.Shared/Light/Components/UnpoweredFlashlightComponent.cs b/Content.Shared/Light/Components/UnpoweredFlashlightComponent.cs index 1b0701edd2..2953a01ced 100644 --- a/Content.Shared/Light/Components/UnpoweredFlashlightComponent.cs +++ b/Content.Shared/Light/Components/UnpoweredFlashlightComponent.cs @@ -2,7 +2,6 @@ using Robust.Shared.Audio; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; namespace Content.Shared.Light.Components; @@ -17,7 +16,7 @@ public sealed partial class UnpoweredFlashlightComponent : Component public SoundSpecifier ToggleSound = new SoundPathSpecifier("/Audio/Items/flashlight_pda.ogg"); [DataField, AutoNetworkedField] - public bool LightOn = false; + public bool LightOn; [DataField] public EntProtoId ToggleAction = "ActionToggleLight"; diff --git a/Content.Shared/Light/EntitySystems/UnpoweredFlashlightSystem.cs b/Content.Shared/Light/EntitySystems/UnpoweredFlashlightSystem.cs new file mode 100644 index 0000000000..42e55bea55 --- /dev/null +++ b/Content.Shared/Light/EntitySystems/UnpoweredFlashlightSystem.cs @@ -0,0 +1,122 @@ +using Content.Shared.Actions; +using Content.Shared.Emag.Systems; +using Content.Shared.Light.Components; +using Content.Shared.Mind.Components; +using Content.Shared.Toggleable; +using Content.Shared.Verbs; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; +using Robust.Shared.Utility; + +namespace Content.Shared.Light.EntitySystems; + +public sealed class UnpoweredFlashlightSystem : EntitySystem +{ + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly SharedActionsSystem _actionsSystem = default!; + [Dependency] private readonly ActionContainerSystem _actionContainer = default!; + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly SharedAudioSystem _audioSystem = default!; + [Dependency] private readonly SharedPointLightSystem _light = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent>(AddToggleLightVerbs); + SubscribeLocalEvent(OnGetActions); + SubscribeLocalEvent(OnToggleAction); + SubscribeLocalEvent(OnMindAdded); + SubscribeLocalEvent(OnGotEmagged); + SubscribeLocalEvent(OnMapInit); + } + + private void OnMapInit(EntityUid uid, UnpoweredFlashlightComponent component, MapInitEvent args) + { + _actionContainer.EnsureAction(uid, ref component.ToggleActionEntity, component.ToggleAction); + Dirty(uid, component); + } + + private void OnToggleAction(EntityUid uid, UnpoweredFlashlightComponent component, ToggleActionEvent args) + { + if (args.Handled) + return; + + TryToggleLight((uid, component), args.Performer); + args.Handled = true; + } + + private void OnGetActions(EntityUid uid, UnpoweredFlashlightComponent component, GetItemActionsEvent args) + { + args.AddAction(component.ToggleActionEntity); + } + + private void AddToggleLightVerbs(EntityUid uid, UnpoweredFlashlightComponent component, GetVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract) + return; + + ActivationVerb verb = new() + { + Text = Loc.GetString("toggle-flashlight-verb-get-data-text"), + Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/light.svg.192dpi.png")), + Act = () => TryToggleLight((uid, component), args.User), + Priority = -1 // For things like PDA's, Open-UI and other verbs that should be higher priority. + }; + + args.Verbs.Add(verb); + } + + private void OnMindAdded(EntityUid uid, UnpoweredFlashlightComponent component, MindAddedMessage args) + { + _actionsSystem.AddAction(uid, ref component.ToggleActionEntity, component.ToggleAction); + } + + private void OnGotEmagged(EntityUid uid, UnpoweredFlashlightComponent component, ref GotEmaggedEvent args) + { + if (!_light.TryGetLight(uid, out var light)) + return; + + if (_prototypeManager.TryIndex(component.EmaggedColorsPrototype, out var possibleColors)) + { + var pick = _random.Pick(possibleColors.Colors.Values); + _light.SetColor(uid, pick, light); + } + + args.Repeatable = true; + args.Handled = true; + } + + public void TryToggleLight(Entity ent, EntityUid? user = null, bool quiet = false) + { + if (!Resolve(ent, ref ent.Comp, false)) + return; + + SetLight(ent, !ent.Comp.LightOn, user, quiet); + } + + public void SetLight(Entity ent, bool value, EntityUid? user = null, bool quiet = false) + { + if (!Resolve(ent, ref ent.Comp)) + return; + + if (ent.Comp.LightOn == value) + return; + + if (!_light.TryGetLight(ent, out var light)) + return; + + Dirty(ent); + ent.Comp.LightOn = value; + _light.SetEnabled(ent, value, light); + _appearance.SetData(ent, UnpoweredFlashlightVisuals.LightOn, value); + + if (!quiet) + _audioSystem.PlayPredicted(ent.Comp.ToggleSound, ent, user); + + _actionsSystem.SetToggled(ent.Comp.ToggleActionEntity, value); + RaiseLocalEvent(ent, new LightToggleEvent(value)); + } +} diff --git a/Content.Shared/Light/LightToggleEvent.cs b/Content.Shared/Light/LightToggleEvent.cs new file mode 100644 index 0000000000..ac48c09419 --- /dev/null +++ b/Content.Shared/Light/LightToggleEvent.cs @@ -0,0 +1,6 @@ +namespace Content.Shared.Light; + +public sealed class LightToggleEvent(bool isOn) : EntityEventArgs +{ + public bool IsOn = isOn; +} diff --git a/Content.Shared/Localizations/ContentLocalizationManager.cs b/Content.Shared/Localizations/ContentLocalizationManager.cs index 3c311f4382..7d40182f6c 100644 --- a/Content.Shared/Localizations/ContentLocalizationManager.cs +++ b/Content.Shared/Localizations/ContentLocalizationManager.cs @@ -69,7 +69,7 @@ private ILocValue FormatNaturalPercent(LocArgs args) var maxDecimals = (int)Math.Floor(((LocValueNumber) args.Args[1]).Value); var formatter = (NumberFormatInfo)NumberFormatInfo.GetInstance(CultureInfo.GetCultureInfo(Culture)).Clone(); formatter.NumberDecimalDigits = maxDecimals; - return new LocValueString(string.Format(formatter, "{0:N}", number).TrimEnd('0').TrimEnd('.') + "%"); + return new LocValueString(string.Format(formatter, "{0:N}", number).TrimEnd('0').TrimEnd(char.Parse(formatter.NumberDecimalSeparator)) + "%"); } private ILocValue FormatNaturalFixed(LocArgs args) @@ -78,7 +78,7 @@ private ILocValue FormatNaturalFixed(LocArgs args) var maxDecimals = (int)Math.Floor(((LocValueNumber) args.Args[1]).Value); var formatter = (NumberFormatInfo)NumberFormatInfo.GetInstance(CultureInfo.GetCultureInfo(Culture)).Clone(); formatter.NumberDecimalDigits = maxDecimals; - return new LocValueString(string.Format(formatter, "{0:N}", number).TrimEnd('0').TrimEnd('.')); + return new LocValueString(string.Format(formatter, "{0:N}", number).TrimEnd('0').TrimEnd(char.Parse(formatter.NumberDecimalSeparator))); } private static readonly Regex PluralEsRule = new("^.*(s|sh|ch|x|z)$"); diff --git a/Content.Shared/Lock/LockComponent.cs b/Content.Shared/Lock/LockComponent.cs index 5587fc2698..e1d47365b7 100644 --- a/Content.Shared/Lock/LockComponent.cs +++ b/Content.Shared/Lock/LockComponent.cs @@ -14,80 +14,81 @@ namespace Content.Shared.Lock; public sealed partial class LockComponent : Component { /// - /// Whether or not the lock is locked. + /// Whether or not the lock is locked. /// - [DataField("locked"), ViewVariables(VVAccess.ReadWrite)] - [AutoNetworkedField] - public bool Locked = true; + [DataField, AutoNetworkedField] + public bool Locked = true; /// - /// Whether or not the lock is toggled by simply clicking. + /// Whether or not the lock is toggled by simply clicking. /// - [DataField("lockOnClick"), ViewVariables(VVAccess.ReadWrite)] - [AutoNetworkedField] + [DataField, AutoNetworkedField] public bool LockOnClick; /// - /// The sound played when unlocked. + /// Whether or not the lock is unlocked by simply clicking. /// - [DataField("unlockingSound"), ViewVariables(VVAccess.ReadWrite)] + [DataField, AutoNetworkedField] + public bool UnlockOnClick = true; + + /// + /// The sound played when unlocked. + /// + [DataField] public SoundSpecifier UnlockSound = new SoundPathSpecifier("/Audio/Machines/door_lock_off.ogg") { Params = AudioParams.Default.WithVolume(-5f), }; /// - /// The sound played when locked. + /// The sound played when locked. /// - [DataField("lockingSound"), ViewVariables(VVAccess.ReadWrite)] + [DataField] public SoundSpecifier LockSound = new SoundPathSpecifier("/Audio/Machines/door_lock_on.ogg") { Params = AudioParams.Default.WithVolume(-5f) }; /// - /// Whether or not an emag disables it. + /// Whether or not an emag disables it. /// - [DataField("breakOnEmag")] - [AutoNetworkedField] + [DataField, AutoNetworkedField] public bool BreakOnEmag = true; /// - /// Amount of do-after time needed to lock the entity. + /// Amount of do-after time needed to lock the entity. /// /// - /// If set to zero, no do-after will be used. + /// If set to zero, no do-after will be used. /// - [DataField] - [AutoNetworkedField] + [DataField, AutoNetworkedField] public TimeSpan LockTime; /// - /// Amount of do-after time needed to unlock the entity. + /// Amount of do-after time needed to unlock the entity. /// /// - /// If set to zero, no do-after will be used. + /// If set to zero, no do-after will be used. /// - [DataField] - [AutoNetworkedField] + [DataField, AutoNetworkedField] public TimeSpan UnlockTime; } /// -/// Event raised on the lock when a toggle is attempted. -/// Can be cancelled to prevent it. +/// Event raised on the lock when a toggle is attempted. +/// Can be cancelled to prevent it. /// [ByRefEvent] public record struct LockToggleAttemptEvent(EntityUid User, bool Silent = false, bool Cancelled = false); /// -/// Event raised on a lock after it has been toggled. +/// Event raised on a lock after it has been toggled. /// [ByRefEvent] public readonly record struct LockToggledEvent(bool Locked); /// -/// Used to lock a lockable entity that has a lock time configured. +/// Used to lock a lockable entity that has a lock time configured. /// /// /// @@ -101,7 +102,7 @@ public override DoAfterEvent Clone() } /// -/// Used to unlock a lockable entity that has an unlock time configured. +/// Used to unlock a lockable entity that has an unlock time configured. /// /// /// diff --git a/Content.Shared/Lock/LockSystem.cs b/Content.Shared/Lock/LockSystem.cs index 165e01f70d..9296a354d2 100644 --- a/Content.Shared/Lock/LockSystem.cs +++ b/Content.Shared/Lock/LockSystem.cs @@ -58,12 +58,14 @@ private void OnActivated(EntityUid uid, LockComponent lockComp, ActivateInWorldE return; // Only attempt an unlock by default on Activate - if (lockComp.Locked) + if (lockComp.Locked && lockComp.UnlockOnClick) { + if (!lockComp.UnlockOnClick) + return; TryUnlock(uid, args.User, lockComp); args.Handled = true; } - else if (lockComp.LockOnClick) + else if (!lockComp.Locked && lockComp.LockOnClick) { TryLock(uid, args.User, lockComp); args.Handled = true; @@ -201,6 +203,18 @@ public bool TryUnlock(EntityUid uid, EntityUid user, LockComponent? lockComp = n return true; } + /// + /// Returns true if the entity is locked. + /// Entities with no lock component are considered unlocked. + /// + public bool IsLocked(Entity ent) + { + if (!Resolve(ent, ref ent.Comp, false)) + return false; + + return ent.Comp.Locked; + } + /// /// Raises an event for other components to check whether or not /// the entity can be locked in its current state. diff --git a/Content.Shared/Magic/Components/MagicComponent.cs b/Content.Shared/Magic/Components/MagicComponent.cs new file mode 100644 index 0000000000..bcc11063b7 --- /dev/null +++ b/Content.Shared/Magic/Components/MagicComponent.cs @@ -0,0 +1,39 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Magic.Components; + +// TODO: Rename to MagicActionComponent or MagicRequirementsComponent +[RegisterComponent, NetworkedComponent, Access(typeof(SharedMagicSystem))] +public sealed partial class MagicComponent : Component +{ + // TODO: Split into different components? + // This could be the MagicRequirementsComp - which just is requirements for the spell + // Magic comp could be on the actual entities itself + // Could handle lifetime, ignore caster, etc? + // Magic caster comp would be on the caster, used for what I'm not sure + + // TODO: Do After here or in actions + + // TODO: Spell requirements + // A list of requirements to cast the spell + // Hands + // Any item in hand + // Spell takes up an inhand slot + // May be an action toggle or something + + // TODO: List requirements in action desc + /// + /// Does this spell require Wizard Robes & Hat? + /// + [DataField, ViewVariables(VVAccess.ReadWrite)] + public bool RequiresClothes; + + /// + /// Does this spell require the user to speak? + /// + [DataField, ViewVariables(VVAccess.ReadWrite)] + public bool RequiresSpeech; + + // TODO: FreeHand - should check if toggleable action + // Check which hand is free to toggle action in +} diff --git a/Content.Shared/Magic/Components/SpellbookComponent.cs b/Content.Shared/Magic/Components/SpellbookComponent.cs new file mode 100644 index 0000000000..f1b307c245 --- /dev/null +++ b/Content.Shared/Magic/Components/SpellbookComponent.cs @@ -0,0 +1,36 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.Magic.Components; + +/// +/// Spellbooks can grant one or more spells to the user. If marked as it will teach +/// the performer the spells and wipe the book. +/// Default behavior requires the book to be held in hand +/// +[RegisterComponent, Access(typeof(SpellbookSystem))] +public sealed partial class SpellbookComponent : Component +{ + /// + /// List of spells that this book has. This is a combination of the WorldSpells, EntitySpells, and InstantSpells. + /// + [ViewVariables] + public readonly List Spells = new(); + + /// + /// The three fields below is just used for initialization. + /// + [DataField] + [ViewVariables(VVAccess.ReadWrite)] + public Dictionary SpellActions = new(); + + [DataField] + [ViewVariables(VVAccess.ReadWrite)] + public float LearnTime = .75f; + + /// + /// If true, the spell action stays even after the book is removed + /// + [DataField] + [ViewVariables(VVAccess.ReadWrite)] + public bool LearnPermanently; +} diff --git a/Content.Shared/Magic/Components/WizardClothesComponent.cs b/Content.Shared/Magic/Components/WizardClothesComponent.cs new file mode 100644 index 0000000000..063cf56c33 --- /dev/null +++ b/Content.Shared/Magic/Components/WizardClothesComponent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Magic.Components; + +/// +/// The checks this if a spell requires wizard clothes +/// +[RegisterComponent, NetworkedComponent] +[Access(typeof(SharedMagicSystem))] +public sealed partial class WizardClothesComponent : Component; diff --git a/Content.Shared/Magic/Events/BeforeCastSpellEvent.cs b/Content.Shared/Magic/Events/BeforeCastSpellEvent.cs new file mode 100644 index 0000000000..afb5c1f090 --- /dev/null +++ b/Content.Shared/Magic/Events/BeforeCastSpellEvent.cs @@ -0,0 +1,12 @@ +namespace Content.Shared.Magic.Events; + +[ByRefEvent] +public struct BeforeCastSpellEvent(EntityUid performer) +{ + /// + /// The Performer of the event, to check if they meet the requirements. + /// + public EntityUid Performer = performer; + + public bool Cancelled; +} diff --git a/Content.Shared/Magic/Events/ChargeSpellEvent.cs b/Content.Shared/Magic/Events/ChargeSpellEvent.cs new file mode 100644 index 0000000000..8898761ec2 --- /dev/null +++ b/Content.Shared/Magic/Events/ChargeSpellEvent.cs @@ -0,0 +1,18 @@ +using Content.Shared.Actions; + +namespace Content.Shared.Magic.Events; + +/// +/// Adds provided Charge to the held wand +/// +public sealed partial class ChargeSpellEvent : InstantActionEvent, ISpeakSpell +{ + [DataField(required: true)] + public int Charge; + + [DataField] + public string WandTag = "WizardWand"; + + [DataField] + public string? Speech { get; private set; } +} diff --git a/Content.Shared/Magic/Events/InstantSpawnSpellEvent.cs b/Content.Shared/Magic/Events/InstantSpawnSpellEvent.cs index ef8d689862..1405b15827 100644 --- a/Content.Shared/Magic/Events/InstantSpawnSpellEvent.cs +++ b/Content.Shared/Magic/Events/InstantSpawnSpellEvent.cs @@ -1,6 +1,5 @@ using Content.Shared.Actions; using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; namespace Content.Shared.Magic.Events; @@ -9,17 +8,18 @@ public sealed partial class InstantSpawnSpellEvent : InstantActionEvent, ISpeakS /// /// What entity should be spawned. /// - [DataField("prototype", required: true, customTypeSerializer: typeof(PrototypeIdSerializer))] - public string Prototype = default!; + [DataField(required: true)] + public EntProtoId Prototype; - [DataField("preventCollide")] + [DataField] public bool PreventCollideWithCaster = true; - [DataField("speech")] + [DataField] public string? Speech { get; private set; } /// /// Gets the targeted spawn positons; may lead to multiple entities being spawned. /// - [DataField("posData")] public MagicSpawnData Pos = new TargetCasterPos(); + [DataField] + public MagicInstantSpawnData PosData = new TargetCasterPos(); } diff --git a/Content.Shared/Magic/Events/KnockSpellEvent.cs b/Content.Shared/Magic/Events/KnockSpellEvent.cs index a3b0be5575..24a1700d21 100644 --- a/Content.Shared/Magic/Events/KnockSpellEvent.cs +++ b/Content.Shared/Magic/Events/KnockSpellEvent.cs @@ -1,5 +1,4 @@ using Content.Shared.Actions; -using Robust.Shared.Audio; namespace Content.Shared.Magic.Events; @@ -7,20 +6,12 @@ public sealed partial class KnockSpellEvent : InstantActionEvent, ISpeakSpell { /// /// The range this spell opens doors in - /// 4f is the default + /// 10f is the default + /// Should be able to open all doors/lockers in visible sight /// - [DataField("range")] - public float Range = 4f; + [DataField] + public float Range = 10f; - [DataField("knockSound")] - public SoundSpecifier KnockSound = new SoundPathSpecifier("/Audio/Magic/knock.ogg"); - - /// - /// Volume control for the spell. - /// - [DataField("knockVolume")] - public float KnockVolume = 5f; - - [DataField("speech")] + [DataField] public string? Speech { get; private set; } } diff --git a/Content.Shared/Magic/Events/ProjectileSpellEvent.cs b/Content.Shared/Magic/Events/ProjectileSpellEvent.cs index 4496625769..336ea03346 100644 --- a/Content.Shared/Magic/Events/ProjectileSpellEvent.cs +++ b/Content.Shared/Magic/Events/ProjectileSpellEvent.cs @@ -1,6 +1,5 @@ using Content.Shared.Actions; using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; namespace Content.Shared.Magic.Events; @@ -9,14 +8,9 @@ public sealed partial class ProjectileSpellEvent : WorldTargetActionEvent, ISpea /// /// What entity should be spawned. /// - [DataField("prototype", required: true, customTypeSerializer: typeof(PrototypeIdSerializer))] - public string Prototype = default!; + [DataField(required: true)] + public EntProtoId Prototype; - /// - /// Gets the targeted spawn positions; may lead to multiple entities being spawned. - /// - [DataField("posData")] public MagicSpawnData Pos = new TargetCasterPos(); - - [DataField("speech")] + [DataField] public string? Speech { get; private set; } } diff --git a/Content.Shared/Magic/Events/SmiteSpellEvent.cs b/Content.Shared/Magic/Events/SmiteSpellEvent.cs index 08ec63c05e..74ca116ad5 100644 --- a/Content.Shared/Magic/Events/SmiteSpellEvent.cs +++ b/Content.Shared/Magic/Events/SmiteSpellEvent.cs @@ -4,12 +4,13 @@ namespace Content.Shared.Magic.Events; public sealed partial class SmiteSpellEvent : EntityTargetActionEvent, ISpeakSpell { + // TODO: Make part of gib method /// - /// Should this smite delete all parts/mechanisms gibbed except for the brain? + /// Should this smite delete all parts/mechanisms gibbed except for the brain? /// - [DataField("deleteNonBrainParts")] + [DataField] public bool DeleteNonBrainParts = true; - [DataField("speech")] + [DataField] public string? Speech { get; private set; } } diff --git a/Content.Shared/Magic/Events/SpeakSpellEvent.cs b/Content.Shared/Magic/Events/SpeakSpellEvent.cs new file mode 100644 index 0000000000..1b3f7af63c --- /dev/null +++ b/Content.Shared/Magic/Events/SpeakSpellEvent.cs @@ -0,0 +1,8 @@ +namespace Content.Shared.Magic.Events; + +[ByRefEvent] +public readonly struct SpeakSpellEvent(EntityUid performer, string speech) +{ + public readonly EntityUid Performer = performer; + public readonly string Speech = speech; +} diff --git a/Content.Shared/Magic/Events/TeleportSpellEvent.cs b/Content.Shared/Magic/Events/TeleportSpellEvent.cs index b24f6ec72f..525c1e5105 100644 --- a/Content.Shared/Magic/Events/TeleportSpellEvent.cs +++ b/Content.Shared/Magic/Events/TeleportSpellEvent.cs @@ -1,19 +1,19 @@ using Content.Shared.Actions; -using Robust.Shared.Audio; namespace Content.Shared.Magic.Events; +// TODO: Can probably just be an entity or something public sealed partial class TeleportSpellEvent : WorldTargetActionEvent, ISpeakSpell { - [DataField("blinkSound")] - public SoundSpecifier BlinkSound = new SoundPathSpecifier("/Audio/Magic/blink.ogg"); - - [DataField("speech")] + [DataField] public string? Speech { get; private set; } + // TODO: Move to magic component + // TODO: Maybe not since sound specifier is a thing + // Keep here to remind what the volume was set as /// /// Volume control for the spell. /// - [DataField("blinkVolume")] + [DataField] public float BlinkVolume = 5f; } diff --git a/Content.Shared/Magic/Events/WorldSpawnSpellEvent.cs b/Content.Shared/Magic/Events/WorldSpawnSpellEvent.cs index 4355cab842..2f50c67b3e 100644 --- a/Content.Shared/Magic/Events/WorldSpawnSpellEvent.cs +++ b/Content.Shared/Magic/Events/WorldSpawnSpellEvent.cs @@ -4,29 +4,31 @@ namespace Content.Shared.Magic.Events; +// TODO: This class needs combining with InstantSpawnSpellEvent + public sealed partial class WorldSpawnSpellEvent : WorldTargetActionEvent, ISpeakSpell { - // TODO:This class needs combining with InstantSpawnSpellEvent - /// /// The list of prototypes this spell will spawn /// - [DataField("prototypes")] - public List Contents = new(); + [DataField] + public List Prototypes = new(); // TODO: This offset is liable for deprecation. + // TODO: Target tile via code instead? /// /// The offset the prototypes will spawn in on relative to the one prior. /// Set to 0,0 to have them spawn on the same tile. /// - [DataField("offset")] + [DataField] public Vector2 Offset; /// /// Lifetime to set for the entities to self delete /// - [DataField("lifetime")] public float? Lifetime; + [DataField] + public float? Lifetime; - [DataField("speech")] + [DataField] public string? Speech { get; private set; } } diff --git a/Content.Shared/Magic/MagicInstantSpawnData.cs b/Content.Shared/Magic/MagicInstantSpawnData.cs new file mode 100644 index 0000000000..5dcc1453ed --- /dev/null +++ b/Content.Shared/Magic/MagicInstantSpawnData.cs @@ -0,0 +1,25 @@ +namespace Content.Shared.Magic; + +// TODO: If still needed, move to magic component +[ImplicitDataDefinitionForInheritors] +public abstract partial class MagicInstantSpawnData; + +/// +/// Spawns underneath caster. +/// +public sealed partial class TargetCasterPos : MagicInstantSpawnData; + +/// +/// Spawns 3 tiles wide in front of the caster. +/// +public sealed partial class TargetInFront : MagicInstantSpawnData +{ + [DataField] + public int Width = 3; +} + + +/// +/// Spawns 1 tile in front of caster +/// +public sealed partial class TargetInFrontSingle : MagicInstantSpawnData; diff --git a/Content.Shared/Magic/MagicSpawnData.cs b/Content.Shared/Magic/MagicSpawnData.cs deleted file mode 100644 index cd96d4ad76..0000000000 --- a/Content.Shared/Magic/MagicSpawnData.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Content.Shared.Magic; - -[ImplicitDataDefinitionForInheritors] -public abstract partial class MagicSpawnData -{ - -} - -/// -/// Spawns 1 at the caster's feet. -/// -public sealed partial class TargetCasterPos : MagicSpawnData {} - -/// -/// Targets the 3 tiles in front of the caster. -/// -public sealed partial class TargetInFront : MagicSpawnData -{ - [DataField("width")] public int Width = 3; -} diff --git a/Content.Shared/Magic/SharedMagicSystem.cs b/Content.Shared/Magic/SharedMagicSystem.cs new file mode 100644 index 0000000000..cc7a297aa4 --- /dev/null +++ b/Content.Shared/Magic/SharedMagicSystem.cs @@ -0,0 +1,519 @@ +using System.Numerics; +using Content.Shared.Actions; +using Content.Shared.Body.Components; +using Content.Shared.Body.Systems; +using Content.Shared.Coordinates.Helpers; +using Content.Shared.Doors.Components; +using Content.Shared.Doors.Systems; +using Content.Shared.Hands.Components; +using Content.Shared.Hands.EntitySystems; +using Content.Shared.Interaction; +using Content.Shared.Inventory; +using Content.Shared.Lock; +using Content.Shared.Magic.Components; +using Content.Shared.Magic.Events; +using Content.Shared.Maps; +using Content.Shared.Physics; +using Content.Shared.Popups; +using Content.Shared.Speech.Muting; +using Content.Shared.Storage; +using Content.Shared.Tag; +using Content.Shared.Weapons.Ranged.Components; +using Content.Shared.Weapons.Ranged.Systems; +using Robust.Shared.Map; +using Robust.Shared.Map.Components; +using Robust.Shared.Network; +using Robust.Shared.Physics.Systems; +using Robust.Shared.Random; +using Robust.Shared.Serialization.Manager; +using Robust.Shared.Spawners; + +namespace Content.Shared.Magic; + +/// +/// Handles learning and using spells (actions) +/// +public abstract class SharedMagicSystem : EntitySystem +{ + [Dependency] private readonly ISerializationManager _seriMan = default!; + [Dependency] private readonly IComponentFactory _compFact = default!; + [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly SharedMapSystem _mapSystem = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly SharedGunSystem _gunSystem = default!; + [Dependency] private readonly SharedPhysicsSystem _physics = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly INetManager _net = default!; + [Dependency] private readonly SharedBodySystem _body = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly SharedDoorSystem _door = default!; + [Dependency] private readonly InventorySystem _inventory = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly SharedInteractionSystem _interaction = default!; + [Dependency] private readonly LockSystem _lock = default!; + [Dependency] private readonly SharedHandsSystem _hands = default!; + [Dependency] private readonly TagSystem _tag = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnBeforeCastSpell); + + SubscribeLocalEvent(OnInstantSpawn); + SubscribeLocalEvent(OnTeleportSpell); + SubscribeLocalEvent(OnWorldSpawn); + SubscribeLocalEvent(OnProjectileSpell); + SubscribeLocalEvent(OnChangeComponentsSpell); + SubscribeLocalEvent(OnSmiteSpell); + SubscribeLocalEvent(OnKnockSpell); + SubscribeLocalEvent(OnChargeSpell); + + // Spell wishlist + // A wishlish of spells that I'd like to implement or planning on implementing in a future PR + + // TODO: InstantDoAfterSpell and WorldDoafterSpell + // Both would be an action that take in an event, that passes an event to trigger once the doafter is done + // This would be three events: + // 1 - Event that triggers from the action that starts the doafter + // 2 - The doafter event itself, which passes the event with it + // 3 - The event to trigger once the do-after finishes + + // TODO: Inanimate objects to life ECS + // AI sentience + + // TODO: Flesh2Stone + // Entity Target spell + // Synergy with Inanimate object to life (detects player and allows player to move around) + + // TODO: Lightning Spell + // Should just fire lightning, try to prevent arc back to caster + + // TODO: Magic Missile (homing projectile ecs) + // Instant action, target any player (except self) on screen + + // TODO: Random projectile ECS for magic-carp, wand of magic + + // TODO: Recall Spell + // mark any item in hand to recall + // ItemRecallComponent + // Event adds the component if it doesn't exist and the performer isn't stored in the comp + // 2nd firing of the event checks to see if the recall comp has this uid, and if it does it calls it + // if no free hands, summon at feet + // if item deleted, clear stored item + + // TODO: Jaunt (should be its own ECS) + // Instant action + // When clicked, disappear/reappear (goes to paused map) + // option to restrict to tiles + // option for requiring entry/exit (blood jaunt) + // speed option + + // TODO: Summon Events + // List of wizard events to add into the event pool that frequently activate + // floor is lava + // change places + // ECS that when triggered, will periodically trigger a random GameRule + // Would need a controller/controller entity? + + // TODO: Summon Guns + // Summon a random gun at peoples feet + // Get every alive player (not in cryo, not a simplemob) + // TODO: After Antag Rework - Rare chance of giving gun collector status to people + + // TODO: Summon Magic + // Summon a random magic wand at peoples feet + // Get every alive player (not in cryo, not a simplemob) + // TODO: After Antag Rework - Rare chance of giving magic collector status to people + + // TODO: Bottle of Blood + // Summons Slaughter Demon + // TODO: Slaughter Demon + // Also see Jaunt + + // TODO: Field Spells + // Should be able to specify a grid of tiles (3x3 for example) that it effects + // Timed despawn - so it doesn't last forever + // Ignore caster - for spells that shouldn't effect the caster (ie if timestop should effect the caster) + + // TODO: Touch toggle spell + // 1 - When toggled on, show in hand + // 2 - Block hand when toggled on + // - Require free hand + // 3 - use spell event when toggled & click + } + + private void OnBeforeCastSpell(Entity ent, ref BeforeCastSpellEvent args) + { + var comp = ent.Comp; + var hasReqs = true; + + if (comp.RequiresClothes) + { + var enumerator = _inventory.GetSlotEnumerator(args.Performer, SlotFlags.OUTERCLOTHING | SlotFlags.HEAD); + while (enumerator.MoveNext(out var containerSlot)) + { + if (containerSlot.ContainedEntity is { } item) + hasReqs = HasComp(item); + else + hasReqs = false; + + if (!hasReqs) + break; + } + } + + if (comp.RequiresSpeech && HasComp(args.Performer)) + hasReqs = false; + + if (hasReqs) + return; + + args.Cancelled = true; + _popup.PopupClient(Loc.GetString("spell-requirements-failed"), args.Performer, args.Performer); + + // TODO: Pre-cast do after, either here or in SharedActionsSystem + } + + private bool PassesSpellPrerequisites(EntityUid spell, EntityUid performer) + { + var ev = new BeforeCastSpellEvent(performer); + RaiseLocalEvent(spell, ref ev); + return !ev.Cancelled; + } + + #region Spells + #region Instant Spawn Spells + /// + /// Handles the instant action (i.e. on the caster) attempting to spawn an entity. + /// + private void OnInstantSpawn(InstantSpawnSpellEvent args) + { + if (args.Handled || !PassesSpellPrerequisites(args.Action, args.Performer)) + return; + + var transform = Transform(args.Performer); + + foreach (var position in GetInstantSpawnPositions(transform, args.PosData)) + { + SpawnSpellHelper(args.Prototype, position, args.Performer, preventCollide: args.PreventCollideWithCaster); + } + + Speak(args); + args.Handled = true; + } + + /// + /// Gets spawn positions listed on + /// + /// + private List GetInstantSpawnPositions(TransformComponent casterXform, MagicInstantSpawnData data) + { + switch (data) + { + case TargetCasterPos: + return new List(1) {casterXform.Coordinates}; + case TargetInFrontSingle: + { + var directionPos = casterXform.Coordinates.Offset(casterXform.LocalRotation.ToWorldVec().Normalized()); + + if (!TryComp(casterXform.GridUid, out var mapGrid)) + return new List(); + if (!directionPos.TryGetTileRef(out var tileReference, EntityManager, _mapManager)) + return new List(); + + var tileIndex = tileReference.Value.GridIndices; + return new List(1) { _mapSystem.GridTileToLocal(casterXform.GridUid.Value, mapGrid, tileIndex) }; + } + case TargetInFront: + { + var directionPos = casterXform.Coordinates.Offset(casterXform.LocalRotation.ToWorldVec().Normalized()); + + if (!TryComp(casterXform.GridUid, out var mapGrid)) + return new List(); + + if (!directionPos.TryGetTileRef(out var tileReference, EntityManager, _mapManager)) + return new List(); + + var tileIndex = tileReference.Value.GridIndices; + var coords = _mapSystem.GridTileToLocal(casterXform.GridUid.Value, mapGrid, tileIndex); + EntityCoordinates coordsPlus; + EntityCoordinates coordsMinus; + + var dir = casterXform.LocalRotation.GetCardinalDir(); + switch (dir) + { + case Direction.North: + case Direction.South: + { + coordsPlus = _mapSystem.GridTileToLocal(casterXform.GridUid.Value, mapGrid, tileIndex + (1, 0)); + coordsMinus = _mapSystem.GridTileToLocal(casterXform.GridUid.Value, mapGrid, tileIndex + (-1, 0)); + return new List(3) + { + coords, + coordsPlus, + coordsMinus, + }; + } + case Direction.East: + case Direction.West: + { + coordsPlus = _mapSystem.GridTileToLocal(casterXform.GridUid.Value, mapGrid, tileIndex + (0, 1)); + coordsMinus = _mapSystem.GridTileToLocal(casterXform.GridUid.Value, mapGrid, tileIndex + (0, -1)); + return new List(3) + { + coords, + coordsPlus, + coordsMinus, + }; + } + } + + return new List(); + } + default: + throw new ArgumentOutOfRangeException(); + } + } + // End Instant Spawn Spells + #endregion + #region World Spawn Spells + /// + /// Spawns entities from a list within range of click. + /// + /// + /// It will offset entities after the first entity based on the OffsetVector2. + /// + /// The Spawn Spell Event args. + private void OnWorldSpawn(WorldSpawnSpellEvent args) + { + if (args.Handled || !PassesSpellPrerequisites(args.Action, args.Performer)) + return; + + var targetMapCoords = args.Target; + + WorldSpawnSpellHelper(args.Prototypes, targetMapCoords, args.Performer, args.Lifetime, args.Offset); + Speak(args); + args.Handled = true; + } + + /// + /// Loops through a supplied list of entity prototypes and spawns them + /// + /// + /// If an offset of 0, 0 is supplied then the entities will all spawn on the same tile. + /// Any other offset will spawn entities starting from the source Map Coordinates and will increment the supplied + /// offset + /// + /// The list of Entities to spawn in + /// Map Coordinates where the entities will spawn + /// Check to see if the entities should self delete + /// A Vector2 offset that the entities will spawn in + private void WorldSpawnSpellHelper(List entityEntries, EntityCoordinates entityCoords, EntityUid performer, float? lifetime, Vector2 offsetVector2) + { + var getProtos = EntitySpawnCollection.GetSpawns(entityEntries, _random); + + var offsetCoords = entityCoords; + foreach (var proto in getProtos) + { + SpawnSpellHelper(proto, offsetCoords, performer, lifetime); + offsetCoords = offsetCoords.Offset(offsetVector2); + } + } + // End World Spawn Spells + #endregion + #region Projectile Spells + private void OnProjectileSpell(ProjectileSpellEvent ev) + { + if (ev.Handled || !PassesSpellPrerequisites(ev.Action, ev.Performer) || !_net.IsServer) + return; + + ev.Handled = true; + Speak(ev); + + var xform = Transform(ev.Performer); + var fromCoords = xform.Coordinates; + var toCoords = ev.Target; + var userVelocity = _physics.GetMapLinearVelocity(ev.Performer); + + // If applicable, this ensures the projectile is parented to grid on spawn, instead of the map. + var fromMap = fromCoords.ToMap(EntityManager, _transform); + var spawnCoords = _mapManager.TryFindGridAt(fromMap, out var gridUid, out _) + ? fromCoords.WithEntityId(gridUid, EntityManager) + : new(_mapManager.GetMapEntityId(fromMap.MapId), fromMap.Position); + + var ent = Spawn(ev.Prototype, spawnCoords); + var direction = toCoords.ToMapPos(EntityManager, _transform) - + spawnCoords.ToMapPos(EntityManager, _transform); + _gunSystem.ShootProjectile(ent, direction, userVelocity, ev.Performer, ev.Performer); + } + // End Projectile Spells + #endregion + #region Change Component Spells + // staves.yml ActionRGB light + private void OnChangeComponentsSpell(ChangeComponentsSpellEvent ev) + { + if (ev.Handled || !PassesSpellPrerequisites(ev.Action, ev.Performer)) + return; + + ev.Handled = true; + Speak(ev); + + foreach (var toRemove in ev.ToRemove) + { + if (_compFact.TryGetRegistration(toRemove, out var registration)) + RemComp(ev.Target, registration.Type); + } + + foreach (var (name, data) in ev.ToAdd) + { + if (HasComp(ev.Target, data.Component.GetType())) + continue; + + var component = (Component) _compFact.GetComponent(name); + component.Owner = ev.Target; + var temp = (object) component; + _seriMan.CopyTo(data.Component, ref temp); + EntityManager.AddComponent(ev.Target, (Component) temp!); + } + } + // End Change Component Spells + #endregion + #region Teleport Spells + // TODO: Rename to teleport clicked spell? + /// + /// Teleports the user to the clicked location + /// + /// + private void OnTeleportSpell(TeleportSpellEvent args) + { + if (args.Handled || !PassesSpellPrerequisites(args.Action, args.Performer)) + return; + + var transform = Transform(args.Performer); + + if (transform.MapID != args.Target.GetMapId(EntityManager) || !_interaction.InRangeUnobstructed(args.Performer, args.Target, range: 1000F, collisionMask: CollisionGroup.Opaque, popup: true)) + return; + + _transform.SetCoordinates(args.Performer, args.Target); + _transform.AttachToGridOrMap(args.Performer, transform); + Speak(args); + args.Handled = true; + } + // End Teleport Spells + #endregion + #region Spell Helpers + private void SpawnSpellHelper(string? proto, EntityCoordinates position, EntityUid performer, float? lifetime = null, bool preventCollide = false) + { + if (!_net.IsServer) + return; + + var ent = Spawn(proto, position.SnapToGrid(EntityManager, _mapManager)); + + if (lifetime != null) + { + var comp = EnsureComp(ent); + comp.Lifetime = lifetime.Value; + } + + if (preventCollide) + { + var comp = EnsureComp(ent); + comp.Uid = performer; + } + } + // End Spell Helpers + #endregion + #region Smite Spells + private void OnSmiteSpell(SmiteSpellEvent ev) + { + if (ev.Handled || !PassesSpellPrerequisites(ev.Action, ev.Performer)) + return; + + ev.Handled = true; + Speak(ev); + + var direction = _transform.GetMapCoordinates(ev.Target, Transform(ev.Target)).Position - _transform.GetMapCoordinates(ev.Performer, Transform(ev.Performer)).Position; + var impulseVector = direction * 10000; + + _physics.ApplyLinearImpulse(ev.Target, impulseVector); + + if (!TryComp(ev.Target, out var body)) + return; + + _body.GibBody(ev.Target, true, body); + } + // End Smite Spells + #endregion + #region Knock Spells + /// + /// Opens all doors and locks within range + /// + /// + private void OnKnockSpell(KnockSpellEvent args) + { + if (args.Handled || !PassesSpellPrerequisites(args.Action, args.Performer)) + return; + + args.Handled = true; + Speak(args); + + var transform = Transform(args.Performer); + + // Look for doors and lockers, and don't open/unlock them if they're already opened/unlocked. + foreach (var target in _lookup.GetEntitiesInRange(_transform.GetMapCoordinates(args.Performer, transform), args.Range, flags: LookupFlags.Dynamic | LookupFlags.Static)) + { + if (!_interaction.InRangeUnobstructed(args.Performer, target, range: 0, collisionMask: CollisionGroup.Opaque)) + continue; + + if (TryComp(target, out var doorBoltComp) && doorBoltComp.BoltsDown) + _door.SetBoltsDown((target, doorBoltComp), false, predicted: true); + + if (TryComp(target, out var doorComp) && doorComp.State is not DoorState.Open) + _door.StartOpening(target); + + if (TryComp(target, out var lockComp) && lockComp.Locked) + _lock.Unlock(target, args.Performer, lockComp); + } + } + // End Knock Spells + #endregion + #region Charge Spells + // TODO: Future support to charge other items + private void OnChargeSpell(ChargeSpellEvent ev) + { + if (ev.Handled || !PassesSpellPrerequisites(ev.Action, ev.Performer) || !TryComp(ev.Performer, out var handsComp)) + return; + + EntityUid? wand = null; + foreach (var item in _hands.EnumerateHeld(ev.Performer, handsComp)) + { + if (!_tag.HasTag(item, ev.WandTag)) + continue; + + wand = item; + } + + ev.Handled = true; + Speak(ev); + + if (wand == null || !TryComp(wand, out var basicAmmoComp) || basicAmmoComp.Count == null) + return; + + _gunSystem.UpdateBasicEntityAmmoCount(wand.Value, basicAmmoComp.Count.Value + ev.Charge, basicAmmoComp); + } + // End Charge Spells + #endregion + // End Spells + #endregion + + // When any spell is cast it will raise this as an event, so then it can be played in server or something. At least until chat gets moved to shared + // TODO: Temp until chat is in shared + private void Speak(BaseActionEvent args) + { + if (args is not ISpeakSpell speak || string.IsNullOrWhiteSpace(speak.Speech)) + return; + + var ev = new SpeakSpellEvent(args.Performer, speak.Speech); + RaiseLocalEvent(ref ev); + } +} diff --git a/Content.Shared/Magic/SpellbookSystem.cs b/Content.Shared/Magic/SpellbookSystem.cs new file mode 100644 index 0000000000..a7c8274624 --- /dev/null +++ b/Content.Shared/Magic/SpellbookSystem.cs @@ -0,0 +1,98 @@ +using Content.Shared.Actions; +using Content.Shared.DoAfter; +using Content.Shared.Interaction.Events; +using Content.Shared.Magic.Components; +using Content.Shared.Mind; +using Robust.Shared.Network; + +namespace Content.Shared.Magic; + +public sealed class SpellbookSystem : EntitySystem +{ + [Dependency] private readonly SharedMindSystem _mind = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + [Dependency] private readonly SharedActionsSystem _actions = default!; + [Dependency] private readonly ActionContainerSystem _actionContainer = default!; + [Dependency] private readonly INetManager _netManager = default!; + + public override void Initialize() + { + SubscribeLocalEvent(OnInit, before: [typeof(SharedMagicSystem)]); + SubscribeLocalEvent(OnUse); + SubscribeLocalEvent(OnDoAfter); + } + + private void OnInit(Entity ent, ref MapInitEvent args) + { + foreach (var (id, charges) in ent.Comp.SpellActions) + { + var spell = _actionContainer.AddAction(ent, id); + if (spell == null) + continue; + + int? charge = charges; + if (_actions.GetCharges(spell) != null) + charge = _actions.GetCharges(spell); + + _actions.SetCharges(spell, charge < 0 ? null : charge); + ent.Comp.Spells.Add(spell.Value); + } + } + + private void OnUse(Entity ent, ref UseInHandEvent args) + { + if (args.Handled) + return; + + AttemptLearn(ent, args); + + args.Handled = true; + } + + private void OnDoAfter(Entity ent, ref T args) where T : DoAfterEvent // Sometimes i despise this language + { + if (args.Handled || args.Cancelled) + return; + + args.Handled = true; + + if (!ent.Comp.LearnPermanently) + { + _actions.GrantActions(args.Args.User, ent.Comp.Spells, ent); + return; + } + + if (_mind.TryGetMind(args.Args.User, out var mindId, out _)) + { + var mindActionContainerComp = EnsureComp(mindId); + + if (_netManager.IsServer) + _actionContainer.TransferAllActionsWithNewAttached(ent, mindId, args.Args.User, newContainer: mindActionContainerComp); + } + else + { + foreach (var (id, charges) in ent.Comp.SpellActions) + { + EntityUid? actionId = null; + if (_actions.AddAction(args.Args.User, ref actionId, id)) + _actions.SetCharges(actionId, charges < 0 ? null : charges); + } + } + + ent.Comp.SpellActions.Clear(); + } + + private void AttemptLearn(Entity ent, UseInHandEvent args) + { + var doAfterEventArgs = new DoAfterArgs(EntityManager, args.User, ent.Comp.LearnTime, new SpellbookDoAfterEvent(), ent, target: ent) + { + BreakOnTargetMove = true, + BreakOnUserMove = true, + BreakOnWeightlessMove = true, + BreakOnDamage = true, + NeedHand = true, // What, are you going to read with your eyes only?? + }; + + _doAfter.TryStartDoAfter(doAfterEventArgs); + } +} diff --git a/Content.Server/MagicMirror/MagicMirrorComponent.cs b/Content.Shared/MagicMirror/MagicMirrorComponent.cs similarity index 89% rename from Content.Server/MagicMirror/MagicMirrorComponent.cs rename to Content.Shared/MagicMirror/MagicMirrorComponent.cs index 624a381ca5..6357543905 100644 --- a/Content.Server/MagicMirror/MagicMirrorComponent.cs +++ b/Content.Shared/MagicMirror/MagicMirrorComponent.cs @@ -1,13 +1,13 @@ using Content.Shared.DoAfter; -using Content.Shared.Humanoid; using Robust.Shared.Audio; +using Robust.Shared.GameStates; -namespace Content.Server.MagicMirror; +namespace Content.Shared.MagicMirror; /// /// Allows humanoids to change their appearance mid-round. /// -[RegisterComponent] +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] public sealed partial class MagicMirrorComponent : Component { [DataField] @@ -16,7 +16,7 @@ public sealed partial class MagicMirrorComponent : Component /// /// Magic mirror target, used for validating UI messages. /// - [DataField] + [DataField, AutoNetworkedField] public EntityUid? Target; /// diff --git a/Content.Shared/MagicMirror/SharedMagicMirrorSystem.cs b/Content.Shared/MagicMirror/SharedMagicMirrorSystem.cs index 0b22e02498..ea5838a723 100644 --- a/Content.Shared/MagicMirror/SharedMagicMirrorSystem.cs +++ b/Content.Shared/MagicMirror/SharedMagicMirrorSystem.cs @@ -1,10 +1,33 @@ using Content.Shared.DoAfter; using Content.Shared.Humanoid.Markings; -using Robust.Shared.Player; +using Content.Shared.Interaction; using Robust.Shared.Serialization; +using Robust.Shared.Utility; namespace Content.Shared.MagicMirror; +public abstract class SharedMagicMirrorSystem : EntitySystem +{ + [Dependency] private readonly SharedInteractionSystem _interaction = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnMirrorRangeCheck); + } + + private void OnMirrorRangeCheck(EntityUid uid, MagicMirrorComponent component, ref BoundUserInterfaceCheckRangeEvent args) + { + if (args.Result == BoundUserInterfaceRangeResult.Fail) + return; + + DebugTools.Assert(component.Target != null && Exists(component.Target)); + + if (!_interaction.InRangeUnobstructed(uid, component.Target.Value)) + args.Result = BoundUserInterfaceRangeResult.Fail; + } +} + [Serializable, NetSerializable] public enum MagicMirrorUiKey : byte { diff --git a/Content.Shared/Maps/TurfSystem.cs b/Content.Shared/Maps/TurfSystem.cs index ad8b3ddea8..8a4bbf68be 100644 --- a/Content.Shared/Maps/TurfSystem.cs +++ b/Content.Shared/Maps/TurfSystem.cs @@ -44,7 +44,7 @@ public bool IsTileBlocked(EntityUid gridUid, var size = grid.TileSize; var localPos = new Vector2(indices.X * size + (size / 2f), indices.Y * size + (size / 2f)); - var worldPos = matrix.Transform(localPos); + var worldPos = Vector2.Transform(localPos, matrix); // This is scaled to 95 % so it doesn't encompass walls on other tiles. var tileAabb = Box2.UnitCentered.Scale(0.95f * size); diff --git a/Content.Shared/Materials/MaterialReclaimerComponent.cs b/Content.Shared/Materials/MaterialReclaimerComponent.cs index 3e72baf604..2fd6cd6fc8 100644 --- a/Content.Shared/Materials/MaterialReclaimerComponent.cs +++ b/Content.Shared/Materials/MaterialReclaimerComponent.cs @@ -1,100 +1,121 @@ -using Content.Shared.Whitelist; +using Content.Shared.Construction.Prototypes; +using Content.Shared.Whitelist; using Robust.Shared.Audio; using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; namespace Content.Shared.Materials; /// -/// This is a machine that handles converting entities -/// into the raw materials and chemicals that make them up. +/// This is a machine that handles converting entities +/// into the raw materials and chemicals that make them up. /// [RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause] [Access(typeof(SharedMaterialReclaimerSystem))] public sealed partial class MaterialReclaimerComponent : Component { /// - /// Whether or not the machine has power. We put it here - /// so we can network and predict it. + /// Whether or not the machine has power. We put it here + /// so we can network and predict it. /// - [DataField, AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)] + [DataField, AutoNetworkedField] public bool Powered; /// - /// An "enable" toggle for things like interfacing with machine linking + /// An "enable" toggle for things like interfacing with machine linking /// - [DataField, AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)] + [DataField, AutoNetworkedField] public bool Enabled = true; /// - /// How efficiently the materials are reclaimed. - /// In practice, a multiplier per material when calculating the output of the reclaimer. + /// How efficiently the materials are reclaimed. + /// In practice, a multiplier per material when calculating the output of the reclaimer. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public float Efficiency = 1f; /// - /// Whether or not the process - /// speed scales with the amount of materials being processed - /// or if it's just + /// Whether or not the process + /// speed scales with the amount of materials being processed + /// or if it's just /// [DataField] public bool ScaleProcessSpeed = true; /// - /// How quickly it takes to consume X amount of materials per second. - /// For example, with a rate of 50, an entity with 100 total material takes 2 seconds to process. + /// How quickly it takes to consume X amount of materials per second. + /// For example, with a rate of 50, an entity with 100 total material takes 2 seconds to process. /// - [DataField, AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)] + [DataField] + public float BaseMaterialProcessRate = 100f; + + /// + /// How quickly it takes to consume X amount of materials per second. + /// For example, with a rate of 50, an entity with 100 total material takes 2 seconds to process. + /// + [DataField, AutoNetworkedField] public float MaterialProcessRate = 100f; /// - /// The minimum amount fo time it can take to process an entity. - /// this value supercedes the calculated one using + /// Machine part whose rating modifies + /// + [DataField] + public ProtoId MachinePartProcessRate = "Manipulator"; + + /// + /// How much the machine part quality affects the /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] + public float PartRatingProcessRateMultiplier = 1.5f; + + /// + /// The minimum amount fo time it can take to process an entity. + /// this value supercedes the calculated one using + /// + [DataField] public TimeSpan MinimumProcessDuration = TimeSpan.FromSeconds(0.5f); /// - /// The id of our output solution + /// The id of our output solution /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public string SolutionContainerId = "output"; /// - /// a whitelist for what entities can be inserted into this reclaimer + /// a whitelist for what entities can be inserted into this reclaimer /// [DataField] public EntityWhitelist? Whitelist; /// - /// a blacklist for what entities cannot be inserted into this reclaimer + /// a blacklist for what entities cannot be inserted into this reclaimer /// [DataField] public EntityWhitelist? Blacklist; /// - /// The sound played when something is being processed. + /// The sound played when something is being processed. /// [DataField] public SoundSpecifier? Sound; /// - /// whether or not we cut off the sound early when the reclaiming ends. + /// whether or not we cut off the sound early when the reclaiming ends. /// [DataField] public bool CutOffSound = true; /// - /// When the next sound will be allowed to be played. Used to prevent spam. + /// When the next sound will be allowed to be played. Used to prevent spam. /// [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] [AutoPausedField] public TimeSpan NextSound; /// - /// Minimum time inbetween each + /// Minimum time inbetween each /// [DataField] public TimeSpan SoundCooldown = TimeSpan.FromSeconds(0.8f); @@ -102,10 +123,10 @@ public sealed partial class MaterialReclaimerComponent : Component public EntityUid? Stream; /// - /// A counter of how many items have been processed + /// A counter of how many items have been processed /// /// - /// I saw this on the recycler and i'm porting it because it's cute af + /// I saw this on the recycler and i'm porting it because it's cute af /// [DataField, AutoNetworkedField] public int ItemsProcessed; diff --git a/Content.Shared/Materials/SharedMaterialReclaimerSystem.cs b/Content.Shared/Materials/SharedMaterialReclaimerSystem.cs index 50dce3c766..cd4ae2e767 100644 --- a/Content.Shared/Materials/SharedMaterialReclaimerSystem.cs +++ b/Content.Shared/Materials/SharedMaterialReclaimerSystem.cs @@ -2,6 +2,7 @@ using Content.Shared.Administration.Logs; using Content.Shared.Audio; using Content.Shared.Body.Components; +using Content.Shared.CCVar; using Content.Shared.Coordinates; using Content.Shared.Database; using Content.Shared.Emag.Components; @@ -11,6 +12,7 @@ using Content.Shared.Stacks; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; +using Robust.Shared.Configuration; using Robust.Shared.Containers; using Robust.Shared.Map; using Robust.Shared.Physics.Events; @@ -29,6 +31,7 @@ public abstract class SharedMaterialReclaimerSystem : EntitySystem [Dependency] protected readonly SharedAmbientSoundSystem AmbientSound = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] protected readonly SharedContainerSystem Container = default!; + [Dependency] private readonly IConfigurationManager _config = default!; public const string ActiveReclaimerContainerId = "active-material-reclaimer-container"; @@ -193,16 +196,16 @@ public bool CanStart(EntityUid uid, MaterialReclaimerComponent component) } /// - /// Whether or not the reclaimer satisfies the conditions - /// allowing it to gib/reclaim a living creature. + /// Whether or not the reclaimer satisfies the conditions + /// allowing it to gib/reclaim a living creature. /// public bool CanGib(EntityUid uid, EntityUid victim, MaterialReclaimerComponent component) { - return false; // DeltaV - Kinda LRP - // return component.Powered && - // component.Enabled && - // HasComp(victim) && - // HasComp(uid); + return _config.GetCVar(CCVars.ReclaimerAllowGibbing) + && component.Powered + && component.Enabled + && HasComp(victim) + && HasComp(uid); } /// diff --git a/Content.Shared/Medical/PenLightComponent.cs b/Content.Shared/Medical/PenLightComponent.cs index 50dacae3dc..770e8af7e9 100644 --- a/Content.Shared/Medical/PenLightComponent.cs +++ b/Content.Shared/Medical/PenLightComponent.cs @@ -9,6 +9,7 @@ namespace Content.Shared.Medical; [RegisterComponent, NetworkedComponent, AutoGenerateComponentPause] public sealed partial class PenLightComponent : Component { + /// /// Cooldown Time, exams take a bit /// diff --git a/Content.Shared/Medical/Surgery/Conditions/SurgeryCloseIncisionConditionComponent.cs b/Content.Shared/Medical/Surgery/Conditions/SurgeryCloseIncisionConditionComponent.cs new file mode 100644 index 0000000000..bab7e405ad --- /dev/null +++ b/Content.Shared/Medical/Surgery/Conditions/SurgeryCloseIncisionConditionComponent.cs @@ -0,0 +1,6 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Medical.Surgery.Conditions; + +[RegisterComponent, NetworkedComponent] +public sealed partial class SurgeryCloseIncisionConditionComponent : Component; \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/Conditions/SurgeryLarvaConditionComponent.cs b/Content.Shared/Medical/Surgery/Conditions/SurgeryLarvaConditionComponent.cs new file mode 100644 index 0000000000..3aac5951c6 --- /dev/null +++ b/Content.Shared/Medical/Surgery/Conditions/SurgeryLarvaConditionComponent.cs @@ -0,0 +1,6 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Medical.Surgery.Conditions; + +[RegisterComponent, NetworkedComponent] +public sealed partial class SurgeryLarvaConditionComponent : Component; \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/Conditions/SurgeryMarkingConditionComponent.cs b/Content.Shared/Medical/Surgery/Conditions/SurgeryMarkingConditionComponent.cs new file mode 100644 index 0000000000..f22b1f682e --- /dev/null +++ b/Content.Shared/Medical/Surgery/Conditions/SurgeryMarkingConditionComponent.cs @@ -0,0 +1,27 @@ +using Content.Shared.Body.Organ; +using Content.Shared.Humanoid; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Medical.Surgery.Conditions; + +[RegisterComponent, NetworkedComponent] +public sealed partial class SurgeryMarkingConditionComponent : Component +{ + + [DataField] + public bool Inverse; + + /// + /// The marking category to check for. + /// + [DataField] + public HumanoidVisualLayers MarkingCategory = default!; + + /// + /// Can be either a segment of a marking ID, or an entire ID that will be checked + /// against the entity to validate that the marking is not already present. + /// + [DataField] + public String MatchString = ""; +} diff --git a/Content.Shared/Medical/Surgery/Conditions/SurgeryOperatingTableConditionComponent.cs b/Content.Shared/Medical/Surgery/Conditions/SurgeryOperatingTableConditionComponent.cs new file mode 100644 index 0000000000..0c43549e66 --- /dev/null +++ b/Content.Shared/Medical/Surgery/Conditions/SurgeryOperatingTableConditionComponent.cs @@ -0,0 +1,6 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Medical.Surgery.Conditions; + +[RegisterComponent, NetworkedComponent] +public sealed partial class SurgeryOperatingTableConditionComponent : Component; \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/Conditions/SurgeryOrganConditionComponent.cs b/Content.Shared/Medical/Surgery/Conditions/SurgeryOrganConditionComponent.cs new file mode 100644 index 0000000000..c8c475f115 --- /dev/null +++ b/Content.Shared/Medical/Surgery/Conditions/SurgeryOrganConditionComponent.cs @@ -0,0 +1,18 @@ +using Content.Shared.Body.Organ; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Medical.Surgery.Conditions; + +[RegisterComponent, NetworkedComponent] +public sealed partial class SurgeryOrganConditionComponent : Component +{ + [DataField] + public ComponentRegistry? Organ; + + [DataField] + public bool Inverse; + + [DataField] + public bool Reattaching; +} \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/Conditions/SurgeryPartConditionComponent.cs b/Content.Shared/Medical/Surgery/Conditions/SurgeryPartConditionComponent.cs new file mode 100644 index 0000000000..08a89eb9e1 --- /dev/null +++ b/Content.Shared/Medical/Surgery/Conditions/SurgeryPartConditionComponent.cs @@ -0,0 +1,17 @@ +using Content.Shared.Body.Part; +using Robust.Shared.GameStates; + +namespace Content.Shared.Medical.Surgery.Conditions; + +[RegisterComponent, NetworkedComponent] +public sealed partial class SurgeryPartConditionComponent : Component +{ + [DataField] + public BodyPartType Part; + + [DataField] + public BodyPartSymmetry? Symmetry; + + [DataField] + public bool Inverse; +} \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/Conditions/SurgeryPartPresentCondition.cs b/Content.Shared/Medical/Surgery/Conditions/SurgeryPartPresentCondition.cs new file mode 100644 index 0000000000..608f90ba4c --- /dev/null +++ b/Content.Shared/Medical/Surgery/Conditions/SurgeryPartPresentCondition.cs @@ -0,0 +1,6 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Medical.Surgery.Conditions; + +[RegisterComponent, NetworkedComponent] +public sealed partial class SurgeryPartPresentConditionComponent : Component; \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/Conditions/SurgeryPartRemovedConditionComponent.cs b/Content.Shared/Medical/Surgery/Conditions/SurgeryPartRemovedConditionComponent.cs new file mode 100644 index 0000000000..fb51ab5b06 --- /dev/null +++ b/Content.Shared/Medical/Surgery/Conditions/SurgeryPartRemovedConditionComponent.cs @@ -0,0 +1,14 @@ +using Content.Shared.Body.Part; +using Robust.Shared.GameStates; + +namespace Content.Shared.Medical.Surgery.Conditions; + +[RegisterComponent, NetworkedComponent] +public sealed partial class SurgeryPartRemovedConditionComponent : Component +{ + [DataField] + public BodyPartType Part; + + [DataField] + public BodyPartSymmetry? Symmetry; +} \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/Conditions/SurgeryValidEvent.cs b/Content.Shared/Medical/Surgery/Conditions/SurgeryValidEvent.cs new file mode 100644 index 0000000000..da769a457a --- /dev/null +++ b/Content.Shared/Medical/Surgery/Conditions/SurgeryValidEvent.cs @@ -0,0 +1,9 @@ +using Content.Shared.Body.Part; + +namespace Content.Shared.Medical.Surgery.Conditions; + +/// +/// Raised on the entity that is receiving surgery. +/// +[ByRefEvent] +public record struct SurgeryValidEvent(EntityUid Body, EntityUid Part, bool Cancelled = false, BodyPartType PartType = default, BodyPartSymmetry? Symmetry = default); \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/Conditions/SurgeryWoundedConditionComponent.cs b/Content.Shared/Medical/Surgery/Conditions/SurgeryWoundedConditionComponent.cs new file mode 100644 index 0000000000..2279fcd044 --- /dev/null +++ b/Content.Shared/Medical/Surgery/Conditions/SurgeryWoundedConditionComponent.cs @@ -0,0 +1,7 @@ +using Content.Shared.Body.Part; +using Robust.Shared.GameStates; + +namespace Content.Shared.Medical.Surgery.Conditions; + +[RegisterComponent, NetworkedComponent] +public sealed partial class SurgeryWoundedConditionComponent : Component; \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/Effects/Complete/SurgeryCompletedEvent.cs b/Content.Shared/Medical/Surgery/Effects/Complete/SurgeryCompletedEvent.cs new file mode 100644 index 0000000000..a0e040fbe7 --- /dev/null +++ b/Content.Shared/Medical/Surgery/Effects/Complete/SurgeryCompletedEvent.cs @@ -0,0 +1,7 @@ +namespace Content.Shared.Medical.Surgery.Effects.Complete; + +/// +/// Raised on the entity that received the surgery. +/// +[ByRefEvent] +public record struct SurgeryCompletedEvent; \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/Effects/Complete/SurgeryRemoveLarvaComponent.cs b/Content.Shared/Medical/Surgery/Effects/Complete/SurgeryRemoveLarvaComponent.cs new file mode 100644 index 0000000000..2077dfa53b --- /dev/null +++ b/Content.Shared/Medical/Surgery/Effects/Complete/SurgeryRemoveLarvaComponent.cs @@ -0,0 +1,6 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Medical.Surgery.Effects.Complete; + +[RegisterComponent, NetworkedComponent] +public sealed partial class SurgeryRemoveLarvaComponent : Component; \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/Effects/Step/SurgeryDamageChangeEffectComponent.cs b/Content.Shared/Medical/Surgery/Effects/Step/SurgeryDamageChangeEffectComponent.cs new file mode 100644 index 0000000000..0db43011a0 --- /dev/null +++ b/Content.Shared/Medical/Surgery/Effects/Step/SurgeryDamageChangeEffectComponent.cs @@ -0,0 +1,17 @@ +using Content.Shared.Damage; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; +namespace Content.Shared.Medical.Surgery.Effects.Step; + +[RegisterComponent, NetworkedComponent] +public sealed partial class SurgeryDamageChangeEffectComponent : Component +{ + [DataField] + public DamageSpecifier Damage = default!; + + [DataField] + public float SleepModifier = 0.5f; + + [DataField] + public bool IsConsumable; +} \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/Effects/Step/SurgerySpecialDamageChangeEffectComponent.cs b/Content.Shared/Medical/Surgery/Effects/Step/SurgerySpecialDamageChangeEffectComponent.cs new file mode 100644 index 0000000000..e375865277 --- /dev/null +++ b/Content.Shared/Medical/Surgery/Effects/Step/SurgerySpecialDamageChangeEffectComponent.cs @@ -0,0 +1,14 @@ +using Content.Shared.Damage; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; +namespace Content.Shared.Medical.Surgery.Effects.Step; + +[RegisterComponent, NetworkedComponent] +public sealed partial class SurgerySpecialDamageChangeEffectComponent : Component +{ + [DataField] + public string DamageType = ""; + + [DataField] + public bool IsConsumable; +} \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/Effects/Step/SurgeryStepCavityEffectComponent.cs b/Content.Shared/Medical/Surgery/Effects/Step/SurgeryStepCavityEffectComponent.cs new file mode 100644 index 0000000000..61300425a7 --- /dev/null +++ b/Content.Shared/Medical/Surgery/Effects/Step/SurgeryStepCavityEffectComponent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Medical.Surgery.Effects.Step; + +[RegisterComponent, NetworkedComponent] +public sealed partial class SurgeryStepCavityEffectComponent : Component +{ + [DataField] + public string Action = "Insert"; +} \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/Effects/Step/SurgeryStepEmoteEffectComponent.cs b/Content.Shared/Medical/Surgery/Effects/Step/SurgeryStepEmoteEffectComponent.cs new file mode 100644 index 0000000000..02e8b749ee --- /dev/null +++ b/Content.Shared/Medical/Surgery/Effects/Step/SurgeryStepEmoteEffectComponent.cs @@ -0,0 +1,12 @@ +using Content.Shared.Chat.Prototypes; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Medical.Surgery.Effects.Step; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class SurgeryStepEmoteEffectComponent : Component +{ + [DataField, AutoNetworkedField] + public ProtoId Emote = "Scream"; +} \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/Effects/Step/SurgeryStepSpawnEffect.cs b/Content.Shared/Medical/Surgery/Effects/Step/SurgeryStepSpawnEffect.cs new file mode 100644 index 0000000000..766713c6f6 --- /dev/null +++ b/Content.Shared/Medical/Surgery/Effects/Step/SurgeryStepSpawnEffect.cs @@ -0,0 +1,13 @@ +using Content.Shared.Chat.Prototypes; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; +using System.ComponentModel.DataAnnotations; + +namespace Content.Shared.Medical.Surgery.Effects.Step; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class SurgeryStepSpawnEffectComponent : Component +{ + [DataField(required: true), AutoNetworkedField] + public EntProtoId Entity; +} \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/Effects/Step/SurgeryTendWoundsEffectComponent.cs b/Content.Shared/Medical/Surgery/Effects/Step/SurgeryTendWoundsEffectComponent.cs new file mode 100644 index 0000000000..58db1422d8 --- /dev/null +++ b/Content.Shared/Medical/Surgery/Effects/Step/SurgeryTendWoundsEffectComponent.cs @@ -0,0 +1,20 @@ +using Content.Shared.Damage; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; +namespace Content.Shared.Medical.Surgery.Effects.Step; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class SurgeryTendWoundsEffectComponent : Component +{ + [DataField, AutoNetworkedField] + public string MainGroup = "Brute"; + + [DataField, AutoNetworkedField] + public bool IsAutoRepeatable = true; + + [DataField, AutoNetworkedField] + public DamageSpecifier Damage = default!; + + [DataField, AutoNetworkedField] + public float HealMultiplier = 0.07f; +} \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/OperatingTableComponent.cs b/Content.Shared/Medical/Surgery/OperatingTableComponent.cs new file mode 100644 index 0000000000..fa0ccf7258 --- /dev/null +++ b/Content.Shared/Medical/Surgery/OperatingTableComponent.cs @@ -0,0 +1,6 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Medical.Surgery; + +[RegisterComponent, NetworkedComponent] +public sealed partial class OperatingTableComponent : Component; \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/SharedSurgerySystem.Steps.cs b/Content.Shared/Medical/Surgery/SharedSurgerySystem.Steps.cs new file mode 100644 index 0000000000..0686bf5309 --- /dev/null +++ b/Content.Shared/Medical/Surgery/SharedSurgerySystem.Steps.cs @@ -0,0 +1,755 @@ +using Content.Shared.Humanoid; +using Content.Shared.Humanoid.Markings; +using Content.Shared.Bed.Sleep; +using Content.Shared.Body.Part; +using Content.Shared.Body.Organ; +using Content.Shared.Body.Events; +using Content.Shared.Buckle.Components; +using Content.Shared.Containers.ItemSlots; +using Content.Shared.Damage; +using Content.Shared.Damage.Prototypes; +using Content.Shared.DoAfter; +using Content.Shared.Medical.Surgery.Conditions; +using Content.Shared.Medical.Surgery.Effects.Step; +using Content.Shared.Medical.Surgery.Steps; +using Content.Shared.Medical.Surgery.Steps.Parts; +using Content.Shared.Medical.Surgery.Tools; +using Content.Shared.Mood; +using Content.Shared.Inventory; +using Content.Shared.Item; +using Content.Shared.Popups; +using Robust.Shared.Prototypes; +using Robust.Shared.Toolshed.TypeParsers; +using System.Linq; + +namespace Content.Shared.Medical.Surgery; + +public abstract partial class SharedSurgerySystem +{ + private static readonly string[] BruteDamageTypes = { "Slash", "Blunt", "Piercing" }; + private static readonly string[] BurnDamageTypes = { "Heat", "Shock", "Cold", "Caustic" }; + private void InitializeSteps() + { + SubscribeLocalEvent(OnToolStep); + SubscribeLocalEvent(OnToolCheck); + SubscribeLocalEvent(OnToolCanPerform); + + //SubSurgery(OnCutLarvaRootsStep, OnCutLarvaRootsCheck); + + /* Abandon all hope ye who enter here. Now I am become shitcoder, the bloater of files. + On a serious note, I really hate how much bloat this pattern of subscribing to a StepEvent and a CheckEvent + creates in terms of readability. And while Check DOES only run on the server side, it's still annoying to parse through.*/ + + SubSurgery(OnTendWoundsStep, OnTendWoundsCheck); + SubSurgery(OnCavityStep, OnCavityCheck); + SubSurgery(OnAddPartStep, OnAddPartCheck); + SubSurgery(OnAffixPartStep, OnAffixPartCheck); + SubSurgery(OnRemovePartStep, OnRemovePartCheck); + SubSurgery(OnAddOrganStep, OnAddOrganCheck); + SubSurgery(OnRemoveOrganStep, OnRemoveOrganCheck); + SubSurgery(OnAffixOrganStep, OnAffixOrganCheck); + SubSurgery(OnAddMarkingStep, OnAddMarkingCheck); + SubSurgery(OnRemoveMarkingStep, OnRemoveMarkingCheck); + Subs.BuiEvents(SurgeryUIKey.Key, subs => + { + subs.Event(OnSurgeryTargetStepChosen); + }); + } + + private void SubSurgery(EntityEventRefHandler onStep, + EntityEventRefHandler onComplete) where TComp : IComponent + { + SubscribeLocalEvent(onStep); + SubscribeLocalEvent(onComplete); + } + + private void OnToolStep(Entity ent, ref SurgeryStepEvent args) + { + if (ent.Comp.Tool != null) + { + foreach (var reg in ent.Comp.Tool.Values) + { + if (!AnyHaveComp(args.Tools, reg.Component, out var tool)) + return; + + if (_net.IsServer && + TryComp(tool, out SurgeryToolComponent? toolComp) && + toolComp.EndSound != null) + { + _audio.PlayEntity(toolComp.EndSound, args.User, tool); + } + } + } + + if (ent.Comp.Add != null) + { + foreach (var reg in ent.Comp.Add.Values) + { + var compType = reg.Component.GetType(); + if (HasComp(args.Part, compType)) + continue; + AddComp(args.Part, _compFactory.GetComponent(compType)); + } + } + + if (ent.Comp.Remove != null) + { + foreach (var reg in ent.Comp.Remove.Values) + { + RemComp(args.Part, reg.Component.GetType()); + } + } + + if (ent.Comp.BodyRemove != null) + { + foreach (var reg in ent.Comp.BodyRemove.Values) + { + RemComp(args.Body, reg.Component.GetType()); + } + } + + if (!HasComp(args.Body)) + RaiseLocalEvent(args.Body, new MoodEffectEvent("SurgeryPain")); + + if (!_inventory.TryGetSlotEntity(args.User, "gloves", out var _) + || !_inventory.TryGetSlotEntity(args.User, "mask", out var _)) + { + var sepsis = new DamageSpecifier(_prototypes.Index("Poison"), 5); + var ev = new SurgeryStepDamageEvent(args.User, args.Body, args.Part, args.Surgery, sepsis, 0.5f); + RaiseLocalEvent(args.Body, ref ev); + } + } + + private void OnToolCheck(Entity ent, ref SurgeryStepCompleteCheckEvent args) + { + // Lord this function is fucking bloated now. Need to clean it up so its less spammy. + if (ent.Comp.Add != null) + { + foreach (var reg in ent.Comp.Add.Values) + { + if (!HasComp(args.Part, reg.Component.GetType())) + { + args.Cancelled = true; + return; + } + } + } + + if (ent.Comp.Remove != null) + { + foreach (var reg in ent.Comp.Remove.Values) + { + if (HasComp(args.Part, reg.Component.GetType())) + { + args.Cancelled = true; + return; + } + } + } + + if (ent.Comp.BodyRemove != null) + { + foreach (var reg in ent.Comp.BodyRemove.Values) + { + if (HasComp(args.Body, reg.Component.GetType())) + { + args.Cancelled = true; + return; + } + } + } + } + + private void OnToolCanPerform(Entity ent, ref SurgeryCanPerformStepEvent args) + { + if (HasComp(ent)) + { + if (!TryComp(args.Body, out BuckleComponent? buckle) || + !HasComp(buckle.BuckledTo)) + { + args.Invalid = StepInvalidReason.NeedsOperatingTable; + return; + } + } + + if (_inventory.TryGetContainerSlotEnumerator(args.Body, out var containerSlotEnumerator, args.TargetSlots)) + { + while (containerSlotEnumerator.MoveNext(out var containerSlot)) + { + if (!containerSlot.ContainedEntity.HasValue) + continue; + + args.Invalid = StepInvalidReason.Armor; + args.Popup = Loc.GetString("surgery-ui-window-steps-error-armor"); + return; + } + } + + RaiseLocalEvent(args.Body, ref args); + + if (args.Invalid != StepInvalidReason.None) + return; + + if (ent.Comp.Tool != null) + { + args.ValidTools ??= new HashSet(); + + foreach (var reg in ent.Comp.Tool.Values) + { + if (!AnyHaveComp(args.Tools, reg.Component, out var withComp)) + { + args.Invalid = StepInvalidReason.MissingTool; + + if (reg.Component is ISurgeryToolComponent tool) + args.Popup = $"You need {tool.ToolName} to perform this step!"; + + return; + } + + args.ValidTools.Add(withComp); + } + } + } + + private EntProtoId? GetProtoId(EntityUid entityUid) + { + if (!TryComp(entityUid, out var metaData)) + return null; + + return metaData.EntityPrototype?.ID; + } + + // I wonder if theres not a function that can do this already. + private bool HasDamageGroup(EntityUid entity, string[] group, out DamageableComponent? damageable) + { + if (!TryComp(entity, out var damageableComp)) + { + damageable = null; + return false; + } + + damageable = damageableComp; + return group.Any(damageType => damageableComp.Damage.DamageDict.TryGetValue(damageType, out var value) && value > 0); + + } + + private void OnTendWoundsStep(Entity ent, ref SurgeryStepEvent args) + { + var group = ent.Comp.MainGroup == "Brute" ? BruteDamageTypes : BurnDamageTypes; + + if (!HasDamageGroup(args.Body, group, out var damageable) + && !HasDamageGroup(args.Part, group, out var _) + || damageable == null) // This shouldnt be possible but the compiler doesn't shut up. + return; + + + // Right now the bonus is based off the body's total damage, maybe we could make it based off each part in the future. + var bonus = ent.Comp.HealMultiplier * damageable.DamagePerGroup[ent.Comp.MainGroup]; + if (_mobState.IsDead(args.Body)) + bonus *= 0.2; + + var adjustedDamage = new DamageSpecifier(ent.Comp.Damage); + var bonusPerType = bonus / group.Length; + + foreach (var type in group) + { + adjustedDamage.DamageDict[type] -= bonusPerType; + } + + var ev = new SurgeryStepDamageEvent(args.User, args.Body, args.Part, args.Surgery, adjustedDamage, 0.5f); + RaiseLocalEvent(args.Body, ref ev); + } + + private void OnTendWoundsCheck(Entity ent, ref SurgeryStepCompleteCheckEvent args) + { + var group = ent.Comp.MainGroup == "Brute" ? BruteDamageTypes : BurnDamageTypes; + + if (HasDamageGroup(args.Body, group, out var _) + || HasDamageGroup(args.Part, group, out var _)) + args.Cancelled = true; + } + + /*private void OnCutLarvaRootsStep(Entity ent, ref SurgeryStepEvent args) + { + if (TryComp(args.Body, out VictimInfectedComponent? infected) && + infected.BurstAt > _timing.CurTime && + infected.SpawnedLarva == null) + { + infected.RootsCut = true; + } + } + + private void OnCutLarvaRootsCheck(Entity ent, ref SurgeryStepCompleteCheckEvent args) + { + if (!TryComp(args.Body, out VictimInfectedComponent? infected) || !infected.RootsCut) + args.Cancelled = true; + + // The larva has fully developed and surgery is now impossible + // TODO: Surgery should still be possible, but the fully developed larva should escape while also saving the hosts life + if (infected != null && infected.SpawnedLarva != null) + args.Cancelled = true; + }*/ + + private void OnCavityStep(Entity ent, ref SurgeryStepEvent args) + { + if (!TryComp(args.Part, out BodyPartComponent? partComp) || partComp.PartType != BodyPartType.Torso) + return; + + var activeHandEntity = _hands.EnumerateHeld(args.User).FirstOrDefault(); + if (activeHandEntity != default + && ent.Comp.Action == "Insert" + && TryComp(activeHandEntity, out ItemComponent? itemComp) + && (itemComp.Size.Id == "Tiny" + || itemComp.Size.Id == "Small")) + _itemSlotsSystem.TryInsert(ent, partComp.ItemInsertionSlot, activeHandEntity, args.User); + else if (ent.Comp.Action == "Remove") + _itemSlotsSystem.TryEjectToHands(ent, partComp.ItemInsertionSlot, args.User); + } + + private void OnCavityCheck(Entity ent, ref SurgeryStepCompleteCheckEvent args) + { + // Normally this check would simply be partComp.ItemInsertionSlot.HasItem, but as mentioned before, + // For whatever reason it's not instantiating the field on the clientside after the wizmerge. + if (!TryComp(args.Part, out BodyPartComponent? partComp) + || !TryComp(args.Part, out ItemSlotsComponent? itemComp) + || ent.Comp.Action == "Insert" + && !itemComp.Slots[partComp.ContainerName].HasItem + || ent.Comp.Action == "Remove" + && itemComp.Slots[partComp.ContainerName].HasItem) + args.Cancelled = true; + } + + private void OnAddPartStep(Entity ent, ref SurgeryStepEvent args) + { + if (!TryComp(args.Surgery, out SurgeryPartRemovedConditionComponent? removedComp)) + return; + + foreach (var tool in args.Tools) + { + if (TryComp(tool, out BodyPartComponent? partComp) + && partComp.PartType == removedComp.Part + && (removedComp.Symmetry == null || partComp.Symmetry == removedComp.Symmetry)) + { + var slotName = removedComp.Symmetry != null + ? $"{removedComp.Symmetry?.ToString().ToLower()} {removedComp.Part.ToString().ToLower()}" + : removedComp.Part.ToString().ToLower(); + _body.TryCreatePartSlot(args.Part, slotName, partComp.PartType, out var _); + _body.AttachPart(args.Part, slotName, tool); + _body.ChangeSlotState((tool, partComp), false); + EnsureComp(tool); + var ev = new BodyPartAttachedEvent((tool, partComp)); + RaiseLocalEvent(args.Body, ref ev); + } + } + } + + private void OnAffixPartStep(Entity ent, ref SurgeryStepEvent args) + { + if (!TryComp(args.Surgery, out SurgeryPartRemovedConditionComponent? removedComp)) + return; + + var targetPart = _body.GetBodyChildrenOfType(args.Body, removedComp.Part, symmetry: removedComp.Symmetry).FirstOrDefault(); + + if (targetPart != default) + { + // We reward players for properly affixing the parts by healing a little bit of damage, and enabling the part temporarily. + var ev = new BodyPartEnableChangedEvent(true); + RaiseLocalEvent(targetPart.Id, ref ev); + _damageable.TryChangeDamage(args.Body, + _body.GetHealingSpecifier(targetPart.Component) * 2, + canSever: false, // Just in case we heal a brute damage specifier and the logic gets fucky lol + targetPart: _body.GetTargetBodyPart(targetPart.Component.PartType, targetPart.Component.Symmetry)); + RemComp(targetPart.Id); + } + } + + private void OnAffixPartCheck(Entity ent, ref SurgeryStepCompleteCheckEvent args) + { + if (!TryComp(args.Surgery, out SurgeryPartRemovedConditionComponent? removedComp)) + return; + + var targetPart = _body.GetBodyChildrenOfType(args.Body, removedComp.Part, symmetry: removedComp.Symmetry).FirstOrDefault(); + + if (targetPart != default + && HasComp(targetPart.Id)) + args.Cancelled = true; + } + + private void OnAddPartCheck(Entity ent, ref SurgeryStepCompleteCheckEvent args) + { + if (!TryComp(args.Surgery, out SurgeryPartRemovedConditionComponent? removedComp) + || !_body.GetBodyChildrenOfType(args.Body, removedComp.Part, symmetry: removedComp.Symmetry).Any()) + args.Cancelled = true; + } + + private void OnRemovePartStep(Entity ent, ref SurgeryStepEvent args) + { + if (!TryComp(args.Part, out BodyPartComponent? partComp) + || partComp.Body != args.Body) + return; + + var ev = new AmputateAttemptEvent(args.Part); + RaiseLocalEvent(args.Part, ref ev); + _hands.TryPickupAnyHand(args.User, args.Part); + } + + private void OnRemovePartCheck(Entity ent, ref SurgeryStepCompleteCheckEvent args) + { + if (!TryComp(args.Part, out BodyPartComponent? partComp) + || partComp.Body == args.Body) + args.Cancelled = true; + } + + private void OnAddOrganStep(Entity ent, ref SurgeryStepEvent args) + { + if (!TryComp(args.Part, out BodyPartComponent? partComp) + || partComp.Body != args.Body + || !TryComp(args.Surgery, out SurgeryOrganConditionComponent? organComp) + || organComp.Organ == null) + return; + + // Adding organs is generally done for a single one at a time, so we only need to check for the first. + var firstOrgan = organComp.Organ.Values.FirstOrDefault(); + if (firstOrgan == default) + return; + + foreach (var tool in args.Tools) + { + if (HasComp(tool, firstOrgan.Component.GetType()) + && TryComp(tool, out var insertedOrgan) + && _body.InsertOrgan(args.Part, tool, insertedOrgan.SlotId, partComp, insertedOrgan)) + { + EnsureComp(tool); + break; + } + } + } + + private void OnAddOrganCheck(Entity ent, ref SurgeryStepCompleteCheckEvent args) + { + if (!TryComp(args.Surgery, out var organComp) + || organComp.Organ is null + || !TryComp(args.Part, out BodyPartComponent? partComp) + || partComp.Body != args.Body) + return; + + // For now we naively assume that every entity will only have one of each organ type. + // that we do surgery on, but in the future we'll need to reference their prototype somehow + // to know if they need 2 hearts, 2 lungs, etc. + foreach (var reg in organComp.Organ.Values) + { + if (!_body.TryGetBodyPartOrgans(args.Part, reg.Component.GetType(), out var _)) + { + args.Cancelled = true; + } + } + } + + private void OnAffixOrganStep(Entity ent, ref SurgeryStepEvent args) + { + if (!TryComp(args.Surgery, out SurgeryOrganConditionComponent? removedOrganComp) + || removedOrganComp.Organ == null + || !removedOrganComp.Reattaching) + return; + + foreach (var reg in removedOrganComp.Organ.Values) + { + _body.TryGetBodyPartOrgans(args.Part, reg.Component.GetType(), out var organs); + if (organs != null && organs.Count > 0) + RemComp(organs[0].Id); + } + + } + + private void OnAffixOrganCheck(Entity ent, ref SurgeryStepCompleteCheckEvent args) + { + if (!TryComp(args.Surgery, out SurgeryOrganConditionComponent? removedOrganComp) + || removedOrganComp.Organ == null + || !removedOrganComp.Reattaching) + return; + + foreach (var reg in removedOrganComp.Organ.Values) + { + _body.TryGetBodyPartOrgans(args.Part, reg.Component.GetType(), out var organs); + if (organs != null + && organs.Count > 0 + && organs.Any(organ => HasComp(organ.Id))) + args.Cancelled = true; + } + } + + private void OnRemoveOrganStep(Entity ent, ref SurgeryStepEvent args) + { + if (!TryComp(args.Surgery, out var organComp) + || organComp.Organ == null) + return; + + foreach (var reg in organComp.Organ.Values) + { + _body.TryGetBodyPartOrgans(args.Part, reg.Component.GetType(), out var organs); + if (organs != null && organs.Count > 0) + { + _body.RemoveOrgan(organs[0].Id, organs[0].Organ); + _hands.TryPickupAnyHand(args.User, organs[0].Id); + } + } + } + + private void OnRemoveOrganCheck(Entity ent, ref SurgeryStepCompleteCheckEvent args) + { + if (!TryComp(args.Surgery, out var organComp) + || organComp.Organ == null + || !TryComp(args.Part, out BodyPartComponent? partComp) + || partComp.Body != args.Body) + return; + + foreach (var reg in organComp.Organ.Values) + { + if (_body.TryGetBodyPartOrgans(args.Part, reg.Component.GetType(), out var organs) + && organs != null + && organs.Count > 0) + { + args.Cancelled = true; + return; + } + } + } + + // TODO: Refactor bodies to include ears as a prototype instead of doing whatever the hell this is. + private void OnAddMarkingStep(Entity ent, ref SurgeryStepEvent args) + { + if (!TryComp(args.Body, out HumanoidAppearanceComponent? bodyAppearance) + || ent.Comp.Organ == null) + return; + + var organType = ent.Comp.Organ.Values.FirstOrDefault(); + if (organType == default) + return; + + var markingCategory = MarkingCategoriesConversion.FromHumanoidVisualLayers(ent.Comp.MarkingCategory); + foreach (var tool in args.Tools) + { + if (TryComp(tool, out MarkingContainerComponent? markingComp) + && HasComp(tool, organType.Component.GetType())) + { + if (!bodyAppearance.MarkingSet.Markings.TryGetValue(markingCategory, out var markingList) + || !markingList.Any(marking => marking.MarkingId.Contains(ent.Comp.MatchString))) + { + EnsureComp(args.Part); + _body.ModifyMarkings(args.Body, args.Part, bodyAppearance, ent.Comp.MarkingCategory, markingComp.Marking); + + if (ent.Comp.Accent != null + && ent.Comp.Accent.Values.FirstOrDefault() is { } accent) + { + var compType = accent.Component.GetType(); + if (!HasComp(args.Body, compType)) + AddComp(args.Body, _compFactory.GetComponent(compType)); + } + + QueueDel(tool); // Again since this isnt actually being inserted we just delete it lol. + } + } + } + + } + + private void OnAddMarkingCheck(Entity ent, ref SurgeryStepCompleteCheckEvent args) + { + var markingCategory = MarkingCategoriesConversion.FromHumanoidVisualLayers(ent.Comp.MarkingCategory); + + if (!TryComp(args.Body, out HumanoidAppearanceComponent? bodyAppearance) + || !bodyAppearance.MarkingSet.Markings.TryGetValue(markingCategory, out var markingList) + || !markingList.Any(marking => marking.MarkingId.Contains(ent.Comp.MatchString))) + args.Cancelled = true; + } + + private void OnRemoveMarkingStep(Entity ent, ref SurgeryStepEvent args) + { + + } + + private void OnRemoveMarkingCheck(Entity ent, ref SurgeryStepCompleteCheckEvent args) + { + + } + + private void OnSurgeryTargetStepChosen(Entity ent, ref SurgeryStepChosenBuiMsg args) + { + var user = args.Actor; + if (GetEntity(args.Entity) is not { Valid: true } body || + GetEntity(args.Part) is not { Valid: true } targetPart || + !IsSurgeryValid(body, targetPart, args.Surgery, args.Step, user, out var surgery, out var part, out var step)) + { + return; + } + + if (!PreviousStepsComplete(body, part, surgery, args.Step) || + IsStepComplete(body, part, args.Step, surgery)) + return; + + if (!CanPerformStep(user, body, part, step, true, out _, out _, out var validTools)) + return; + + if (_net.IsServer && validTools?.Count > 0) + { + foreach (var tool in validTools) + { + if (TryComp(tool, out SurgeryToolComponent? toolComp) && + toolComp.EndSound != null) + { + _audio.PlayEntity(toolComp.StartSound, user, tool); + } + } + } + + if (TryComp(body, out TransformComponent? xform)) + _rotateToFace.TryFaceCoordinates(user, _transform.GetMapCoordinates(body, xform).Position); + + var ev = new SurgeryDoAfterEvent(args.Surgery, args.Step); + // TODO: Make this serialized on a per surgery step basis, and also add penalties based on ghetto tools. + var duration = 2f; + if (TryComp(user, out SurgerySpeedModifierComponent? surgerySpeedMod) + && surgerySpeedMod is not null) + duration = duration / surgerySpeedMod.SpeedModifier; + + var doAfter = new DoAfterArgs(EntityManager, user, TimeSpan.FromSeconds(duration), ev, body, part) + { + BreakOnUserMove = true, + BreakOnTargetMove = true, + CancelDuplicate = true, + DuplicateCondition = DuplicateConditions.SameEvent, + NeedHand = true, + BreakOnHandChange = true, + }; + + _doAfter.TryStartDoAfter(doAfter); + } + + private (Entity Surgery, int Step)? GetNextStep(EntityUid body, EntityUid part, Entity surgery, List requirements) + { + if (!Resolve(surgery, ref surgery.Comp)) + return null; + + if (requirements.Contains(surgery)) + throw new ArgumentException($"Surgery {surgery} has a requirement loop: {string.Join(", ", requirements)}"); + + requirements.Add(surgery); + + if (surgery.Comp.Requirement is { } requirementId && + GetSingleton(requirementId) is { } requirement && + GetNextStep(body, part, requirement, requirements) is { } requiredNext) + { + return requiredNext; + } + + for (var i = 0; i < surgery.Comp.Steps.Count; i++) + { + var surgeryStep = surgery.Comp.Steps[i]; + if (!IsStepComplete(body, part, surgeryStep, surgery)) + return ((surgery, surgery.Comp), i); + } + + return null; + } + + public (Entity Surgery, int Step)? GetNextStep(EntityUid body, EntityUid part, EntityUid surgery) + { + return GetNextStep(body, part, surgery, new List()); + } + + public bool PreviousStepsComplete(EntityUid body, EntityUid part, Entity surgery, EntProtoId step) + { + // TODO RMC14 use index instead of the prototype id + if (surgery.Comp.Requirement is { } requirement) + { + if (GetSingleton(requirement) is not { } requiredEnt || + !TryComp(requiredEnt, out SurgeryComponent? requiredComp) || + !PreviousStepsComplete(body, part, (requiredEnt, requiredComp), step)) + { + return false; + } + } + + foreach (var surgeryStep in surgery.Comp.Steps) + { + if (surgeryStep == step) + break; + + if (!IsStepComplete(body, part, surgeryStep, surgery)) + return false; + } + + return true; + } + + public bool CanPerformStep(EntityUid user, EntityUid body, EntityUid part, + EntityUid step, bool doPopup, out string? popup, out StepInvalidReason reason, + out HashSet? validTools) + { + var type = BodyPartType.Other; + if (TryComp(part, out BodyPartComponent? partComp)) + { + type = partComp.PartType; + } + + var slot = type switch + { + BodyPartType.Head => SlotFlags.HEAD, + BodyPartType.Torso => SlotFlags.OUTERCLOTHING | SlotFlags.INNERCLOTHING, + BodyPartType.Arm => SlotFlags.OUTERCLOTHING | SlotFlags.INNERCLOTHING, + BodyPartType.Hand => SlotFlags.GLOVES, + BodyPartType.Leg => SlotFlags.OUTERCLOTHING | SlotFlags.LEGS, + BodyPartType.Foot => SlotFlags.FEET, + BodyPartType.Tail => SlotFlags.NONE, + BodyPartType.Other => SlotFlags.NONE, + _ => SlotFlags.NONE + }; + + var check = new SurgeryCanPerformStepEvent(user, body, GetTools(user), slot); + RaiseLocalEvent(step, ref check); + popup = check.Popup; + validTools = check.ValidTools; + + if (check.Invalid != StepInvalidReason.None) + { + if (doPopup && check.Popup != null) + _popup.PopupEntity(check.Popup, user, user, PopupType.SmallCaution); + + reason = check.Invalid; + return false; + } + + reason = default; + return true; + } + + public bool CanPerformStep(EntityUid user, EntityUid body, EntityUid part, EntityUid step, bool doPopup) + { + return CanPerformStep(user, body, part, step, doPopup, out _, out _, out _); + } + + public bool IsStepComplete(EntityUid body, EntityUid part, EntProtoId step, EntityUid surgery) + { + if (GetSingleton(step) is not { } stepEnt) + return false; + + var ev = new SurgeryStepCompleteCheckEvent(body, part, surgery); + RaiseLocalEvent(stepEnt, ref ev); + return !ev.Cancelled; + } + + private bool AnyHaveComp(List tools, IComponent component, out EntityUid withComp) + { + foreach (var tool in tools) + { + if (HasComp(tool, component.GetType())) + { + withComp = tool; + return true; + } + } + + withComp = default; + return false; + } +} diff --git a/Content.Shared/Medical/Surgery/SharedSurgerySystem.cs b/Content.Shared/Medical/Surgery/SharedSurgerySystem.cs new file mode 100644 index 0000000000..d7b049b024 --- /dev/null +++ b/Content.Shared/Medical/Surgery/SharedSurgerySystem.cs @@ -0,0 +1,283 @@ +using System.Linq; +using Content.Shared.Medical.Surgery.Conditions; +using Content.Shared.Medical.Surgery.Effects.Complete; +using Content.Shared.Body.Systems; +using Content.Shared.Medical.Surgery.Steps; +using Content.Shared.Medical.Surgery.Steps.Parts; +//using Content.Shared._RMC14.Xenonids.Parasite; +using Content.Shared.Body.Part; +using Content.Shared.Damage; +using Content.Shared.Containers.ItemSlots; +using Content.Shared.Body.Components; +using Content.Shared.Buckle.Components; +using Content.Shared.DoAfter; +using Content.Shared.Mobs.Systems; +using Content.Shared.GameTicking; +using Content.Shared.Hands.EntitySystems; +using Content.Shared.Humanoid; +using Content.Shared.Humanoid.Markings; +using Content.Shared.Interaction; +using Content.Shared.Inventory; +using Content.Shared.Popups; +using Content.Shared.Standing; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Map; +using Robust.Shared.Network; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; + +namespace Content.Shared.Medical.Surgery; + +public abstract partial class SharedSurgerySystem : EntitySystem +{ + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly IComponentFactory _compFactory = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + [Dependency] private readonly SharedHandsSystem _hands = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly SharedBodySystem _body = default!; + [Dependency] private readonly DamageableSystem _damageable = default!; + [Dependency] private readonly INetManager _net = default!; + [Dependency] private readonly InventorySystem _inventory = default!; + [Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!; + [Dependency] private readonly MobStateSystem _mobState = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly IPrototypeManager _prototypes = default!; + [Dependency] private readonly RotateToFaceSystem _rotateToFace = default!; + [Dependency] private readonly StandingStateSystem _standing = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + + private readonly Dictionary _surgeries = new(); + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnRoundRestartCleanup); + + SubscribeLocalEvent(OnTargetDoAfter); + SubscribeLocalEvent(OnCloseIncisionValid); + //SubscribeLocalEvent(OnLarvaValid); + SubscribeLocalEvent(OnPartConditionValid); + SubscribeLocalEvent(OnOrganConditionValid); + SubscribeLocalEvent(OnWoundedValid); + SubscribeLocalEvent(OnPartRemovedConditionValid); + SubscribeLocalEvent(OnPartPresentConditionValid); + SubscribeLocalEvent(OnMarkingPresentValid); + //SubscribeLocalEvent(OnRemoveLarva); + + InitializeSteps(); + } + + private void OnRoundRestartCleanup(RoundRestartCleanupEvent ev) + { + _surgeries.Clear(); + } + + private void OnTargetDoAfter(Entity ent, ref SurgeryDoAfterEvent args) + { + if (!_timing.IsFirstTimePredicted) + return; + + if (args.Cancelled + || args.Handled + || args.Target is not { } target + || !IsSurgeryValid(ent, target, args.Surgery, args.Step, args.User, out var surgery, out var part, out var step) + || !PreviousStepsComplete(ent, part, surgery, args.Step) + || !CanPerformStep(args.User, ent, part, step, false)) + { + Log.Warning($"{ToPrettyString(args.User)} tried to start invalid surgery."); + return; + } + + args.Repeat = (HasComp(step) && !IsStepComplete(ent, part, args.Step, surgery)); + var ev = new SurgeryStepEvent(args.User, ent, part, GetTools(args.User), surgery); + RaiseLocalEvent(step, ref ev); + RefreshUI(ent); + } + + private void OnCloseIncisionValid(Entity ent, ref SurgeryValidEvent args) + { + if (!HasComp(args.Part) || + !HasComp(args.Part) || + !HasComp(args.Part) || + !HasComp(args.Part) || + !HasComp(args.Part)) + { + args.Cancelled = true; + } + } + + private void OnWoundedValid(Entity ent, ref SurgeryValidEvent args) + { + if (!TryComp(args.Body, out DamageableComponent? damageable) + || !TryComp(args.Part, out DamageableComponent? partDamageable) + || damageable.TotalDamage <= 0 + && partDamageable.TotalDamage <= 0 + && !HasComp(args.Part)) + args.Cancelled = true; + } + + /*private void OnLarvaValid(Entity ent, ref SurgeryValidEvent args) + { + if (!TryComp(args.Body, out VictimInfectedComponent? infected)) + args.Cancelled = true; + + // The larva has fully developed and surgery is now impossible + if (infected != null && infected.SpawnedLarva != null) + args.Cancelled = true; + }*/ + private void OnPartConditionValid(Entity ent, ref SurgeryValidEvent args) + { + if (!TryComp(args.Part, out var part)) + { + args.Cancelled = true; + return; + } + + var typeMatch = part.PartType == ent.Comp.Part; + var symmetryMatch = ent.Comp.Symmetry == null || part.Symmetry == ent.Comp.Symmetry; + var valid = typeMatch && symmetryMatch; + + if (ent.Comp.Inverse ? valid : !valid) + args.Cancelled = true; + } + + private void OnOrganConditionValid(Entity ent, ref SurgeryValidEvent args) + { + if (!TryComp(args.Part, out var partComp) + || partComp.Body != args.Body + || ent.Comp.Organ == null) + { + args.Cancelled = true; + return; + } + + foreach (var reg in ent.Comp.Organ.Values) + { + if (_body.TryGetBodyPartOrgans(args.Part, reg.Component.GetType(), out var organs) + && organs.Count > 0) + { + if (ent.Comp.Inverse + && (!ent.Comp.Reattaching + || ent.Comp.Reattaching + && !organs.Any(organ => HasComp(organ.Id)))) + args.Cancelled = true; + } + else if (!ent.Comp.Inverse) + args.Cancelled = true; + } + } + + private void OnPartRemovedConditionValid(Entity ent, ref SurgeryValidEvent args) + { + var results = _body.GetBodyChildrenOfType(args.Body, ent.Comp.Part, symmetry: ent.Comp.Symmetry); + if (results is not { } || !results.Any()) + return; + + if (!results.Any(part => HasComp(part.Id))) + args.Cancelled = true; + } + + private void OnPartPresentConditionValid(Entity ent, ref SurgeryValidEvent args) + { + if (args.Part == EntityUid.Invalid + || !HasComp(args.Part)) + args.Cancelled = true; + } + + private void OnMarkingPresentValid(Entity ent, ref SurgeryValidEvent args) + { + var markingCategory = MarkingCategoriesConversion.FromHumanoidVisualLayers(ent.Comp.MarkingCategory); + + var hasMarking = TryComp(args.Body, out HumanoidAppearanceComponent? bodyAppearance) + && bodyAppearance.MarkingSet.Markings.TryGetValue(markingCategory, out var markingList) + && markingList.Any(marking => marking.MarkingId.Contains(ent.Comp.MatchString)); + + if ((!ent.Comp.Inverse && hasMarking) || (ent.Comp.Inverse && !hasMarking)) + args.Cancelled = true; + } + + /*private void OnRemoveLarva(Entity ent, ref SurgeryCompletedEvent args) + { + RemCompDeferred(ent); + }*/ + + protected bool IsSurgeryValid(EntityUid body, EntityUid targetPart, EntProtoId surgery, EntProtoId stepId, + EntityUid user, out Entity surgeryEnt, out EntityUid part, out EntityUid step) + { + surgeryEnt = default; + part = default; + step = default; + + if (!HasComp(body) || + !IsLyingDown(body, user) || + GetSingleton(surgery) is not { } surgeryEntId || + !TryComp(surgeryEntId, out SurgeryComponent? surgeryComp) || + !surgeryComp.Steps.Contains(stepId) || + GetSingleton(stepId) is not { } stepEnt + || !HasComp(targetPart) + && !HasComp(targetPart)) + return false; + + + var ev = new SurgeryValidEvent(body, targetPart); + if (_timing.IsFirstTimePredicted) + { + RaiseLocalEvent(stepEnt, ref ev); + RaiseLocalEvent(surgeryEntId, ref ev); + } + + if (ev.Cancelled) + return false; + + surgeryEnt = (surgeryEntId, surgeryComp); + part = targetPart; + step = stepEnt; + return true; + } + + public EntityUid? GetSingleton(EntProtoId surgeryOrStep) + { + if (!_prototypes.HasIndex(surgeryOrStep)) + return null; + + // This (for now) assumes that surgery entity data remains unchanged between client + // and server + // if it does not you get the bullet + if (!_surgeries.TryGetValue(surgeryOrStep, out var ent) || TerminatingOrDeleted(ent)) + { + ent = Spawn(surgeryOrStep, MapCoordinates.Nullspace); + _surgeries[surgeryOrStep] = ent; + } + + return ent; + } + + private List GetTools(EntityUid surgeon) + { + return _hands.EnumerateHeld(surgeon).ToList(); + } + + public bool IsLyingDown(EntityUid entity, EntityUid user) + { + if (_standing.IsDown(entity)) + return true; + + if (TryComp(entity, out BuckleComponent? buckle) && + TryComp(buckle.BuckledTo, out StrapComponent? strap)) + { + var rotation = strap.Rotation; + if (rotation.GetCardinalDir() is Direction.West or Direction.East) + return true; + } + + _popup.PopupEntity(Loc.GetString("surgery-error-laying"), user, user); + + return false; + } + + protected virtual void RefreshUI(EntityUid body) + { + } +} diff --git a/Content.Shared/Medical/Surgery/StepInvalidReason.cs b/Content.Shared/Medical/Surgery/StepInvalidReason.cs new file mode 100644 index 0000000000..dbea495d08 --- /dev/null +++ b/Content.Shared/Medical/Surgery/StepInvalidReason.cs @@ -0,0 +1,10 @@ +namespace Content.Shared.Medical.Surgery; + +public enum StepInvalidReason +{ + None, + MissingSkills, + NeedsOperatingTable, + Armor, + MissingTool, +} \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/Steps/Parts/BleedersClampedComponent.cs b/Content.Shared/Medical/Surgery/Steps/Parts/BleedersClampedComponent.cs new file mode 100644 index 0000000000..24d4fd9935 --- /dev/null +++ b/Content.Shared/Medical/Surgery/Steps/Parts/BleedersClampedComponent.cs @@ -0,0 +1,6 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Medical.Surgery.Steps.Parts; + +[RegisterComponent, NetworkedComponent] +public sealed partial class BleedersClampedComponent : Component; \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/Steps/Parts/BodyPartReattachedComponent.cs b/Content.Shared/Medical/Surgery/Steps/Parts/BodyPartReattachedComponent.cs new file mode 100644 index 0000000000..30739c821b --- /dev/null +++ b/Content.Shared/Medical/Surgery/Steps/Parts/BodyPartReattachedComponent.cs @@ -0,0 +1,6 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Medical.Surgery.Steps.Parts; + +[RegisterComponent, NetworkedComponent] +public sealed partial class BodyPartReattachedComponent : Component; \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/Steps/Parts/BodyPartSawedComponent.cs b/Content.Shared/Medical/Surgery/Steps/Parts/BodyPartSawedComponent.cs new file mode 100644 index 0000000000..0838175d9a --- /dev/null +++ b/Content.Shared/Medical/Surgery/Steps/Parts/BodyPartSawedComponent.cs @@ -0,0 +1,6 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Medical.Surgery.Steps.Parts; + +[RegisterComponent, NetworkedComponent] +public sealed partial class BodyPartSawedComponent : Component; \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/Steps/Parts/IncisionOpenComponent.cs b/Content.Shared/Medical/Surgery/Steps/Parts/IncisionOpenComponent.cs new file mode 100644 index 0000000000..f41319549c --- /dev/null +++ b/Content.Shared/Medical/Surgery/Steps/Parts/IncisionOpenComponent.cs @@ -0,0 +1,6 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Medical.Surgery.Steps.Parts; + +[RegisterComponent, NetworkedComponent] +public sealed partial class IncisionOpenComponent : Component; \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/Steps/Parts/InternalBleedersClampedComponent.cs b/Content.Shared/Medical/Surgery/Steps/Parts/InternalBleedersClampedComponent.cs new file mode 100644 index 0000000000..7e597e88ef --- /dev/null +++ b/Content.Shared/Medical/Surgery/Steps/Parts/InternalBleedersClampedComponent.cs @@ -0,0 +1,6 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Medical.Surgery.Steps.Parts; + +[RegisterComponent, NetworkedComponent] +public sealed partial class InternalBleedersClampedComponent : Component; \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/Steps/Parts/OrganReattachedComponent.cs b/Content.Shared/Medical/Surgery/Steps/Parts/OrganReattachedComponent.cs new file mode 100644 index 0000000000..9e034598e6 --- /dev/null +++ b/Content.Shared/Medical/Surgery/Steps/Parts/OrganReattachedComponent.cs @@ -0,0 +1,6 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Medical.Surgery.Steps.Parts; + +[RegisterComponent, NetworkedComponent] +public sealed partial class OrganReattachedComponent : Component; \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/Steps/Parts/PartRemovedComponent.cs b/Content.Shared/Medical/Surgery/Steps/Parts/PartRemovedComponent.cs new file mode 100644 index 0000000000..ced1d1b984 --- /dev/null +++ b/Content.Shared/Medical/Surgery/Steps/Parts/PartRemovedComponent.cs @@ -0,0 +1,6 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Medical.Surgery.Steps.Parts; + +[RegisterComponent, NetworkedComponent] +public sealed partial class PartsRemovedComponent : Component; \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/Steps/Parts/RibcageOpenComponent.cs b/Content.Shared/Medical/Surgery/Steps/Parts/RibcageOpenComponent.cs new file mode 100644 index 0000000000..d8942bd966 --- /dev/null +++ b/Content.Shared/Medical/Surgery/Steps/Parts/RibcageOpenComponent.cs @@ -0,0 +1,6 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Medical.Surgery.Steps.Parts; + +[RegisterComponent, NetworkedComponent] +public sealed partial class RibcageOpenComponent : Component; \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/Steps/Parts/RibcageSawedComponent.cs b/Content.Shared/Medical/Surgery/Steps/Parts/RibcageSawedComponent.cs new file mode 100644 index 0000000000..527b3dc99a --- /dev/null +++ b/Content.Shared/Medical/Surgery/Steps/Parts/RibcageSawedComponent.cs @@ -0,0 +1,6 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Medical.Surgery.Steps.Parts; + +[RegisterComponent, NetworkedComponent] +public sealed partial class RibcageSawedComponent : Component; \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/Steps/Parts/SkinRetractedComponent.cs b/Content.Shared/Medical/Surgery/Steps/Parts/SkinRetractedComponent.cs new file mode 100644 index 0000000000..6f75a83f17 --- /dev/null +++ b/Content.Shared/Medical/Surgery/Steps/Parts/SkinRetractedComponent.cs @@ -0,0 +1,6 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Medical.Surgery.Steps.Parts; + +[RegisterComponent, NetworkedComponent] +public sealed partial class SkinRetractedComponent : Component; \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/Steps/SurgeryAddMarkingStepComponent.cs b/Content.Shared/Medical/Surgery/Steps/SurgeryAddMarkingStepComponent.cs new file mode 100644 index 0000000000..b945c8d909 --- /dev/null +++ b/Content.Shared/Medical/Surgery/Steps/SurgeryAddMarkingStepComponent.cs @@ -0,0 +1,34 @@ +using Content.Shared.Humanoid; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Medical.Surgery.Steps; + +[RegisterComponent, NetworkedComponent] +public sealed partial class SurgeryAddMarkingStepComponent : Component +{ + /// + /// The marking category to add the marking to. + /// + [DataField] + public HumanoidVisualLayers MarkingCategory = default!; + + /// + /// Can be either a segment of a marking ID, or an entire ID that will be checked + /// against the entity to validate that the marking is not already present. + /// + [DataField] + public String MatchString = ""; + + /// + /// What type of organ is required for this surgery? + /// + [DataField] + public ComponentRegistry? Organ; + + /// + /// Component name for accent that will be applied. + /// + [DataField] + public ComponentRegistry? Accent; +} diff --git a/Content.Shared/Medical/Surgery/Steps/SurgeryAddOrganStepComponent.cs b/Content.Shared/Medical/Surgery/Steps/SurgeryAddOrganStepComponent.cs new file mode 100644 index 0000000000..2d169879f9 --- /dev/null +++ b/Content.Shared/Medical/Surgery/Steps/SurgeryAddOrganStepComponent.cs @@ -0,0 +1,6 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Medical.Surgery.Steps; + +[RegisterComponent, NetworkedComponent] +public sealed partial class SurgeryAddOrganStepComponent : Component; \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/Steps/SurgeryAddPartStepComponent.cs b/Content.Shared/Medical/Surgery/Steps/SurgeryAddPartStepComponent.cs new file mode 100644 index 0000000000..0229552ae8 --- /dev/null +++ b/Content.Shared/Medical/Surgery/Steps/SurgeryAddPartStepComponent.cs @@ -0,0 +1,6 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Medical.Surgery.Steps; + +[RegisterComponent, NetworkedComponent] +public sealed partial class SurgeryAddPartStepComponent : Component; \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/Steps/SurgeryAffixOrganStepComponent.cs b/Content.Shared/Medical/Surgery/Steps/SurgeryAffixOrganStepComponent.cs new file mode 100644 index 0000000000..5f82cbe425 --- /dev/null +++ b/Content.Shared/Medical/Surgery/Steps/SurgeryAffixOrganStepComponent.cs @@ -0,0 +1,6 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Medical.Surgery.Steps; + +[RegisterComponent, NetworkedComponent] +public sealed partial class SurgeryAffixOrganStepComponent : Component; diff --git a/Content.Shared/Medical/Surgery/Steps/SurgeryAffixPartStepComponent.cs b/Content.Shared/Medical/Surgery/Steps/SurgeryAffixPartStepComponent.cs new file mode 100644 index 0000000000..cc080e8be0 --- /dev/null +++ b/Content.Shared/Medical/Surgery/Steps/SurgeryAffixPartStepComponent.cs @@ -0,0 +1,6 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Medical.Surgery.Steps; + +[RegisterComponent, NetworkedComponent] +public sealed partial class SurgeryAffixPartStepComponent : Component; diff --git a/Content.Shared/Medical/Surgery/Steps/SurgeryCanPerformStepEvent.cs b/Content.Shared/Medical/Surgery/Steps/SurgeryCanPerformStepEvent.cs new file mode 100644 index 0000000000..cd6d0fd455 --- /dev/null +++ b/Content.Shared/Medical/Surgery/Steps/SurgeryCanPerformStepEvent.cs @@ -0,0 +1,14 @@ +using Content.Shared.Inventory; + +namespace Content.Shared.Medical.Surgery.Steps; + +[ByRefEvent] +public record struct SurgeryCanPerformStepEvent( + EntityUid User, + EntityUid Body, + List Tools, + SlotFlags TargetSlots, + string? Popup = null, + StepInvalidReason Invalid = StepInvalidReason.None, + HashSet? ValidTools = null +) : IInventoryRelayEvent; \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/Steps/SurgeryCutLarvaRootsStepComponent.cs b/Content.Shared/Medical/Surgery/Steps/SurgeryCutLarvaRootsStepComponent.cs new file mode 100644 index 0000000000..349815379b --- /dev/null +++ b/Content.Shared/Medical/Surgery/Steps/SurgeryCutLarvaRootsStepComponent.cs @@ -0,0 +1,6 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Medical.Surgery.Steps; + +[RegisterComponent, NetworkedComponent] +public sealed partial class SurgeryCutLarvaRootsStepComponent : Component; \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/Steps/SurgeryRemoveMarkingStepComponent.cs b/Content.Shared/Medical/Surgery/Steps/SurgeryRemoveMarkingStepComponent.cs new file mode 100644 index 0000000000..47368a154c --- /dev/null +++ b/Content.Shared/Medical/Surgery/Steps/SurgeryRemoveMarkingStepComponent.cs @@ -0,0 +1,29 @@ +using Robust.Shared.Prototypes; +using Content.Shared.Humanoid; +using Robust.Shared.GameStates; + +namespace Content.Shared.Medical.Surgery.Steps; + +[RegisterComponent, NetworkedComponent] +public sealed partial class SurgeryRemoveMarkingStepComponent : Component +{ + /// + /// The category the marking belongs to. + /// + [DataField] + public HumanoidVisualLayers MarkingCategory = default!; + + /// + /// Can be either a segment of a marking ID, or an entire ID that will be checked + /// against the entity to validate that the marking is present. + /// + [DataField] + public String MatchString = ""; + + /// + /// Will this step spawn an item as a result of removing the markings? If so, which? + /// + [DataField] + public EntProtoId? ItemSpawn = default!; + +} diff --git a/Content.Shared/Medical/Surgery/Steps/SurgeryRemoveOrganStepComponent.cs b/Content.Shared/Medical/Surgery/Steps/SurgeryRemoveOrganStepComponent.cs new file mode 100644 index 0000000000..66f2ea62fd --- /dev/null +++ b/Content.Shared/Medical/Surgery/Steps/SurgeryRemoveOrganStepComponent.cs @@ -0,0 +1,6 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Medical.Surgery.Steps; + +[RegisterComponent, NetworkedComponent] +public sealed partial class SurgeryRemoveOrganStepComponent : Component; \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/Steps/SurgeryRemovePartStepComponent.cs b/Content.Shared/Medical/Surgery/Steps/SurgeryRemovePartStepComponent.cs new file mode 100644 index 0000000000..f55f3d1b7b --- /dev/null +++ b/Content.Shared/Medical/Surgery/Steps/SurgeryRemovePartStepComponent.cs @@ -0,0 +1,6 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Medical.Surgery.Steps; + +[RegisterComponent, NetworkedComponent] +public sealed partial class SurgeryRemovePartStepComponent : Component; \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/Steps/SurgeryRepeatableStepComponent.cs b/Content.Shared/Medical/Surgery/Steps/SurgeryRepeatableStepComponent.cs new file mode 100644 index 0000000000..14010b7e96 --- /dev/null +++ b/Content.Shared/Medical/Surgery/Steps/SurgeryRepeatableStepComponent.cs @@ -0,0 +1,6 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Medical.Surgery.Steps; + +[RegisterComponent, NetworkedComponent] +public sealed partial class SurgeryRepeatableStepComponent : Component; diff --git a/Content.Shared/Medical/Surgery/Steps/SurgeryStepCompleteCheckEvent.cs b/Content.Shared/Medical/Surgery/Steps/SurgeryStepCompleteCheckEvent.cs new file mode 100644 index 0000000000..ed28aab1db --- /dev/null +++ b/Content.Shared/Medical/Surgery/Steps/SurgeryStepCompleteCheckEvent.cs @@ -0,0 +1,4 @@ +namespace Content.Shared.Medical.Surgery.Steps; + +[ByRefEvent] +public record struct SurgeryStepCompleteCheckEvent(EntityUid Body, EntityUid Part, EntityUid Surgery, bool Cancelled = false); \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/Steps/SurgeryStepComponent.cs b/Content.Shared/Medical/Surgery/Steps/SurgeryStepComponent.cs new file mode 100644 index 0000000000..1c6e801a42 --- /dev/null +++ b/Content.Shared/Medical/Surgery/Steps/SurgeryStepComponent.cs @@ -0,0 +1,22 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Medical.Surgery.Steps; + +[RegisterComponent, NetworkedComponent] +[Prototype("SurgerySteps")] +public sealed partial class SurgeryStepComponent : Component +{ + + [DataField] + public ComponentRegistry? Tool; + + [DataField] + public ComponentRegistry? Add; + + [DataField] + public ComponentRegistry? Remove; + + [DataField] + public ComponentRegistry? BodyRemove; +} diff --git a/Content.Shared/Medical/Surgery/SurgeryComponent.cs b/Content.Shared/Medical/Surgery/SurgeryComponent.cs new file mode 100644 index 0000000000..3d3c895234 --- /dev/null +++ b/Content.Shared/Medical/Surgery/SurgeryComponent.cs @@ -0,0 +1,18 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Medical.Surgery; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +[Prototype("Surgeries")] +public sealed partial class SurgeryComponent : Component +{ + [DataField, AutoNetworkedField] + public int Priority; + + [DataField, AutoNetworkedField] + public EntProtoId? Requirement; + + [DataField(required: true), AutoNetworkedField] + public List Steps = new(); +} \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/SurgeryDoAfterEvent.cs b/Content.Shared/Medical/Surgery/SurgeryDoAfterEvent.cs new file mode 100644 index 0000000000..e61cfbd8e4 --- /dev/null +++ b/Content.Shared/Medical/Surgery/SurgeryDoAfterEvent.cs @@ -0,0 +1,18 @@ +using Content.Shared.DoAfter; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; + +namespace Content.Shared.Medical.Surgery; + +[Serializable, NetSerializable] +public sealed partial class SurgeryDoAfterEvent : SimpleDoAfterEvent +{ + public readonly EntProtoId Surgery; + public readonly EntProtoId Step; + + public SurgeryDoAfterEvent(EntProtoId surgery, EntProtoId step) + { + Surgery = surgery; + Step = step; + } +} \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/SurgerySpeedModifierComponent.cs b/Content.Shared/Medical/Surgery/SurgerySpeedModifierComponent.cs new file mode 100644 index 0000000000..b9b586b8f5 --- /dev/null +++ b/Content.Shared/Medical/Surgery/SurgerySpeedModifierComponent.cs @@ -0,0 +1,11 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Medical.Surgery; + +[RegisterComponent, NetworkedComponent] +public sealed partial class SurgerySpeedModifierComponent : Component +{ + [DataField] + public float SpeedModifier = 1.5f; +} \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/SurgeryStepDamageEvent.cs b/Content.Shared/Medical/Surgery/SurgeryStepDamageEvent.cs new file mode 100644 index 0000000000..781cf81acf --- /dev/null +++ b/Content.Shared/Medical/Surgery/SurgeryStepDamageEvent.cs @@ -0,0 +1,9 @@ +using Content.Shared.Damage; + +namespace Content.Shared.Medical.Surgery; + +/// +/// Raised on the target entity. +/// +[ByRefEvent] +public record struct SurgeryStepDamageEvent(EntityUid User, EntityUid Body, EntityUid Part, EntityUid Surgery, DamageSpecifier Damage, float PartMultiplier); \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/SurgeryStepEvent.cs b/Content.Shared/Medical/Surgery/SurgeryStepEvent.cs new file mode 100644 index 0000000000..9123c6d0d5 --- /dev/null +++ b/Content.Shared/Medical/Surgery/SurgeryStepEvent.cs @@ -0,0 +1,7 @@ +namespace Content.Shared.Medical.Surgery; + +/// +/// Raised on the step entity. +/// +[ByRefEvent] +public record struct SurgeryStepEvent(EntityUid User, EntityUid Body, EntityUid Part, List Tools, EntityUid Surgery); \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/SurgeryTargetComponent.cs b/Content.Shared/Medical/Surgery/SurgeryTargetComponent.cs new file mode 100644 index 0000000000..d2d7f8d462 --- /dev/null +++ b/Content.Shared/Medical/Surgery/SurgeryTargetComponent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Medical.Surgery; + +[RegisterComponent, NetworkedComponent] +public sealed partial class SurgeryTargetComponent : Component +{ + [DataField] + public bool CanOperate = true; +} diff --git a/Content.Shared/Medical/Surgery/SurgeryUI.cs b/Content.Shared/Medical/Surgery/SurgeryUI.cs new file mode 100644 index 0000000000..2572aaca65 --- /dev/null +++ b/Content.Shared/Medical/Surgery/SurgeryUI.cs @@ -0,0 +1,32 @@ +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; + +namespace Content.Shared.Medical.Surgery; + +[Serializable, NetSerializable] +public enum SurgeryUIKey +{ + Key +} + +[Serializable, NetSerializable] +public sealed class SurgeryBuiState(Dictionary> choices) : BoundUserInterfaceState +{ + public readonly Dictionary> Choices = choices; +} + +[Serializable, NetSerializable] +public sealed class SurgeryBuiRefreshMessage : BoundUserInterfaceMessage +{ +} + +[Serializable, NetSerializable] +public sealed class SurgeryStepChosenBuiMsg(NetEntity part, EntProtoId surgery, EntProtoId step, bool isBody) : BoundUserInterfaceMessage +{ + public readonly NetEntity Part = part; + public readonly EntProtoId Surgery = surgery; + public readonly EntProtoId Step = step; + + // Used as a marker for whether or not we're hijacking surgery by applying it on the body itself. + public readonly bool IsBody = isBody; +} diff --git a/Content.Shared/Medical/Surgery/SurgeryUiRefreshEvent.cs b/Content.Shared/Medical/Surgery/SurgeryUiRefreshEvent.cs new file mode 100644 index 0000000000..9d41401d7f --- /dev/null +++ b/Content.Shared/Medical/Surgery/SurgeryUiRefreshEvent.cs @@ -0,0 +1,14 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.Medical.Surgery; + +[Serializable, NetSerializable] +public sealed class SurgeryUiRefreshEvent : EntityEventArgs +{ + public NetEntity Uid { get; } + + public SurgeryUiRefreshEvent(NetEntity uid) + { + Uid = uid; + } +} diff --git a/Content.Shared/Medical/Surgery/Tools/BoneGelComponent.cs b/Content.Shared/Medical/Surgery/Tools/BoneGelComponent.cs new file mode 100644 index 0000000000..50f614afe1 --- /dev/null +++ b/Content.Shared/Medical/Surgery/Tools/BoneGelComponent.cs @@ -0,0 +1,11 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Medical.Surgery.Tools; + +[RegisterComponent, NetworkedComponent] +public sealed partial class BoneGelComponent : Component, ISurgeryToolComponent +{ + public string ToolName => "bone gel"; + + public bool? Used { get; set; } = null; +} \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/Tools/BoneSawComponent.cs b/Content.Shared/Medical/Surgery/Tools/BoneSawComponent.cs new file mode 100644 index 0000000000..2b7c76eeac --- /dev/null +++ b/Content.Shared/Medical/Surgery/Tools/BoneSawComponent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Medical.Surgery.Tools; + +[RegisterComponent, NetworkedComponent] +public sealed partial class BoneSawComponent : Component, ISurgeryToolComponent +{ + public string ToolName => "a bone saw"; + public bool? Used { get; set; } = null; +} \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/Tools/BoneSetterComponent.cs b/Content.Shared/Medical/Surgery/Tools/BoneSetterComponent.cs new file mode 100644 index 0000000000..e68d64b407 --- /dev/null +++ b/Content.Shared/Medical/Surgery/Tools/BoneSetterComponent.cs @@ -0,0 +1,6 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Medical.Surgery.Tools; + +[RegisterComponent, NetworkedComponent] +public sealed partial class BoneSetterComponent : Component; \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/Tools/CauteryComponent.cs b/Content.Shared/Medical/Surgery/Tools/CauteryComponent.cs new file mode 100644 index 0000000000..9a56f80973 --- /dev/null +++ b/Content.Shared/Medical/Surgery/Tools/CauteryComponent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Medical.Surgery.Tools; + +[RegisterComponent, NetworkedComponent] +public sealed partial class CauteryComponent : Component, ISurgeryToolComponent +{ + public string ToolName => "a cautery"; + public bool? Used { get; set; } = null; +} \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/Tools/HemostatComponent.cs b/Content.Shared/Medical/Surgery/Tools/HemostatComponent.cs new file mode 100644 index 0000000000..3e869e1c09 --- /dev/null +++ b/Content.Shared/Medical/Surgery/Tools/HemostatComponent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Medical.Surgery.Tools; + +[RegisterComponent, NetworkedComponent] +public sealed partial class HemostatComponent : Component, ISurgeryToolComponent +{ + public string ToolName => "a hemostat"; + public bool? Used { get; set; } = null; +} \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/Tools/ISurgeryToolComponent.cs b/Content.Shared/Medical/Surgery/Tools/ISurgeryToolComponent.cs new file mode 100644 index 0000000000..7fb4491f0c --- /dev/null +++ b/Content.Shared/Medical/Surgery/Tools/ISurgeryToolComponent.cs @@ -0,0 +1,11 @@ +namespace Content.Shared.Medical.Surgery.Tools; + +public interface ISurgeryToolComponent +{ + [DataField] + public string ToolName { get; } + + // Mostly intended for discardable or non-reusable tools. + [DataField] + public bool? Used { get; set; } +} \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/Tools/RetractorComponent.cs b/Content.Shared/Medical/Surgery/Tools/RetractorComponent.cs new file mode 100644 index 0000000000..bf57c70729 --- /dev/null +++ b/Content.Shared/Medical/Surgery/Tools/RetractorComponent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Medical.Surgery.Tools; + +[RegisterComponent, NetworkedComponent] +public sealed partial class RetractorComponent : Component, ISurgeryToolComponent +{ + public string ToolName => "a retractor"; + public bool? Used { get; set; } = null; +} \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/Tools/ScalpelComponent.cs b/Content.Shared/Medical/Surgery/Tools/ScalpelComponent.cs new file mode 100644 index 0000000000..40fb4af556 --- /dev/null +++ b/Content.Shared/Medical/Surgery/Tools/ScalpelComponent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Medical.Surgery.Tools; + +[RegisterComponent, NetworkedComponent] +public sealed partial class ScalpelComponent : Component, ISurgeryToolComponent +{ + public string ToolName => "a scalpel"; + public bool? Used { get; set; } = null; +} \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/Tools/SurgeryToolComponent.cs b/Content.Shared/Medical/Surgery/Tools/SurgeryToolComponent.cs new file mode 100644 index 0000000000..f0dd96ecb0 --- /dev/null +++ b/Content.Shared/Medical/Surgery/Tools/SurgeryToolComponent.cs @@ -0,0 +1,16 @@ +using Robust.Shared.Audio; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Medical.Surgery.Tools; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class SurgeryToolComponent : Component +{ + + [DataField, AutoNetworkedField] + public SoundSpecifier? StartSound; + + [DataField, AutoNetworkedField] + public SoundSpecifier? EndSound; +} \ No newline at end of file diff --git a/Content.Shared/Medical/Surgery/Tools/SurgicalDrillComponent.cs b/Content.Shared/Medical/Surgery/Tools/SurgicalDrillComponent.cs new file mode 100644 index 0000000000..c8995edcec --- /dev/null +++ b/Content.Shared/Medical/Surgery/Tools/SurgicalDrillComponent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Medical.Surgery.Tools; + +[RegisterComponent, NetworkedComponent] +public sealed partial class SurgicalDrillComponent : Component, ISurgeryToolComponent +{ + public string ToolName => "a surgical drill"; + public bool? Used { get; set; } = null; +} \ No newline at end of file diff --git a/Content.Shared/MedicalScanner/HealthAnalyzerScannedUserMessage.cs b/Content.Shared/MedicalScanner/HealthAnalyzerScannedUserMessage.cs index 78f26ed5c0..14e7c2a3fc 100644 --- a/Content.Shared/MedicalScanner/HealthAnalyzerScannedUserMessage.cs +++ b/Content.Shared/MedicalScanner/HealthAnalyzerScannedUserMessage.cs @@ -1,3 +1,5 @@ +using Content.Shared.Targeting; +using Content.Shared.Body.Components; using Robust.Shared.Serialization; namespace Content.Shared.MedicalScanner; @@ -13,14 +15,28 @@ public sealed class HealthAnalyzerScannedUserMessage : BoundUserInterfaceMessage public float BloodLevel; public bool? ScanMode; public bool? Bleeding; + public bool? Unrevivable; + public Dictionary? Body; // Shitmed + public NetEntity? Part; // Shitmed - public HealthAnalyzerScannedUserMessage(NetEntity? targetEntity, float temperature, float bloodLevel, bool? scanMode, bool? bleeding) + public HealthAnalyzerScannedUserMessage(NetEntity? targetEntity, float temperature, float bloodLevel, bool? scanMode, bool? bleeding, bool? unrevivable, Dictionary? body, NetEntity? part = null) { TargetEntity = targetEntity; Temperature = temperature; BloodLevel = bloodLevel; ScanMode = scanMode; Bleeding = bleeding; + Unrevivable = unrevivable; + Body = body; // Shitmed + Part = part; // Shitmed } } +[Serializable, NetSerializable] +public sealed class HealthAnalyzerPartMessage(NetEntity? owner, TargetBodyPart? bodyPart) : BoundUserInterfaceMessage +{ + public readonly NetEntity? Owner = owner; + public readonly TargetBodyPart? BodyPart = bodyPart; + +} + diff --git a/Content.Shared/Mind/SharedMindSystem.cs b/Content.Shared/Mind/SharedMindSystem.cs index 1898126d80..bf1065c1b1 100644 --- a/Content.Shared/Mind/SharedMindSystem.cs +++ b/Content.Shared/Mind/SharedMindSystem.cs @@ -26,7 +26,6 @@ public abstract class SharedMindSystem : EntitySystem [Dependency] private readonly SharedObjectivesSystem _objectives = default!; [Dependency] private readonly SharedPlayerSystem _player = default!; [Dependency] private readonly MetaDataSystem _metadata = default!; - [Dependency] private readonly ISharedPlayerManager _playerMan = default!; [ViewVariables] protected readonly Dictionary UserMinds = new(); @@ -383,6 +382,30 @@ public bool TryGetObjectiveComp(EntityUid mindId, [NotNullWhen(true)] out T? return false; } + /// + /// Tries to find an objective that has the same prototype as the argument. + /// + /// + /// Will not work for objectives that have no prototype, or duplicate objectives with the same prototype. + /// + public bool TryFindObjective(Entity mind, string prototype, [NotNullWhen(true)] out EntityUid? objective) + { + objective = null; + if (!Resolve(mind, ref mind.Comp)) + return false; + + foreach (var uid in mind.Comp.Objectives) + { + if (MetaData(uid).EntityPrototype?.ID == prototype) + { + objective = uid; + return true; + } + } + + return false; + } + public bool TryGetSession(EntityUid? mindId, [NotNullWhen(true)] out ICommonSession? session) { session = null; diff --git a/Content.Shared/Mobs/Components/MobStateComponent.cs b/Content.Shared/Mobs/Components/MobStateComponent.cs index a2ff349e13..7cff0779cb 100644 --- a/Content.Shared/Mobs/Components/MobStateComponent.cs +++ b/Content.Shared/Mobs/Components/MobStateComponent.cs @@ -13,30 +13,21 @@ namespace Content.Shared.Mobs.Components /// [RegisterComponent] [NetworkedComponent] + [AutoGenerateComponentState] [Access(typeof(MobStateSystem), typeof(MobThresholdSystem))] public sealed partial class MobStateComponent : Component { //default mobstate is always the lowest state level - [ViewVariables] public MobState CurrentState { get; set; } = MobState.Alive; + [AutoNetworkedField, ViewVariables] + public MobState CurrentState { get; set; } = MobState.Alive; - [DataField("allowedStates")] public HashSet AllowedStates = new() + [DataField] + [AutoNetworkedField] + public HashSet AllowedStates = new() { MobState.Alive, MobState.Critical, MobState.Dead }; } - - [Serializable, NetSerializable] - public sealed class MobStateComponentState : ComponentState - { - public readonly MobState CurrentState; - public readonly HashSet AllowedStates; - - public MobStateComponentState(MobState currentState, HashSet allowedStates) - { - CurrentState = currentState; - AllowedStates = allowedStates; - } - } } diff --git a/Content.Shared/Mobs/Systems/MobStateSystem.StateMachine.cs b/Content.Shared/Mobs/Systems/MobStateSystem.StateMachine.cs index 2fa522dea5..b65d970eb9 100644 --- a/Content.Shared/Mobs/Systems/MobStateSystem.StateMachine.cs +++ b/Content.Shared/Mobs/Systems/MobStateSystem.StateMachine.cs @@ -1,5 +1,6 @@ using Content.Shared.Database; using Content.Shared.Mobs.Components; +using Content.Shared.Body.Organ; namespace Content.Shared.Mobs.Systems; @@ -102,6 +103,9 @@ private void ChangeState(EntityUid target, MobStateComponent component, MobState if (oldState == newState || !component.AllowedStates.Contains(newState)) return; + if (oldState == MobState.Dead && HasComp(target)) + return; + OnExitState(target, component, oldState); component.CurrentState = newState; OnEnterState(target, component, newState); diff --git a/Content.Shared/Mobs/Systems/MobStateSystem.Subscribers.cs b/Content.Shared/Mobs/Systems/MobStateSystem.Subscribers.cs index 08b351e61e..d9ef671afe 100644 --- a/Content.Shared/Mobs/Systems/MobStateSystem.Subscribers.cs +++ b/Content.Shared/Mobs/Systems/MobStateSystem.Subscribers.cs @@ -90,12 +90,12 @@ private void OnStateEnteredSubscribers(EntityUid target, MobStateComponent compo _appearance.SetData(target, MobStateVisuals.State, MobState.Alive); break; case MobState.Critical: - _standing.Down(target); + _standing.Down(target, setDrawDepth: true); _appearance.SetData(target, MobStateVisuals.State, MobState.Critical); break; case MobState.Dead: EnsureComp(target); - _standing.Down(target); + _standing.Down(target, setDrawDepth: true); if (_standing.IsDown(target) && TryComp(target, out var physics)) { diff --git a/Content.Shared/Mobs/Systems/MobStateSystem.cs b/Content.Shared/Mobs/Systems/MobStateSystem.cs index ff54c30aaf..a3886dd42e 100644 --- a/Content.Shared/Mobs/Systems/MobStateSystem.cs +++ b/Content.Shared/Mobs/Systems/MobStateSystem.cs @@ -25,8 +25,6 @@ public override void Initialize() _sawmill = _logManager.GetSawmill("MobState"); base.Initialize(); SubscribeEvents(); - SubscribeLocalEvent(OnGetComponentState); - SubscribeLocalEvent(OnHandleComponentState); } #region Public API @@ -100,24 +98,5 @@ public bool IsInvalidState(EntityUid target, MobStateComponent? component = null #region Private Implementation - private void OnHandleComponentState(EntityUid uid, MobStateComponent component, ref ComponentHandleState args) - { - if (args.Current is not MobStateComponentState state) - return; - - component.CurrentState = state.CurrentState; - - if (!component.AllowedStates.SetEquals(state.AllowedStates)) - { - component.AllowedStates.Clear(); - component.AllowedStates.UnionWith(state.AllowedStates); - } - } - - private void OnGetComponentState(EntityUid uid, MobStateComponent component, ref ComponentGetState args) - { - args.State = new MobStateComponentState(component.CurrentState, component.AllowedStates); - } - #endregion } diff --git a/Content.Shared/Mood/MoodCategoryPrototype.cs b/Content.Shared/Mood/MoodCategoryPrototype.cs new file mode 100644 index 0000000000..d5bcd707c8 --- /dev/null +++ b/Content.Shared/Mood/MoodCategoryPrototype.cs @@ -0,0 +1,13 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.Mood; + +/// +/// A prototype defining a category for moodlets, where only a single moodlet of a given category is permitted. +/// +[Prototype] +public sealed partial class MoodCategoryPrototype : IPrototype +{ + [IdDataField] + public string ID { get; } = default!; +} diff --git a/Content.Shared/Mood/MoodEffectPrototype.cs b/Content.Shared/Mood/MoodEffectPrototype.cs new file mode 100644 index 0000000000..da9d178510 --- /dev/null +++ b/Content.Shared/Mood/MoodEffectPrototype.cs @@ -0,0 +1,45 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.Mood; + +[Prototype] +public sealed partial class MoodEffectPrototype : IPrototype +{ + /// + /// The ID of the moodlet to use. + /// + [IdDataField] + public string ID { get; } = default!; + + public string Description => Loc.GetString($"mood-effect-{ID}"); + + /// + /// If they already have an effect with the same category, the new one will replace the old one. + /// + [DataField, ValidatePrototypeId] + public string? Category; + + /// + /// How much should this moodlet modify an entity's Mood. + /// + [DataField(required: true)] + public float MoodChange; + + /// + /// How long, in Seconds, does this moodlet last? If omitted, the moodlet will last until canceled by any system. + /// + [DataField] + public int Timeout; + + /// + /// Should this moodlet be hidden from the player? EG: No popups or chat messages. + /// + [DataField] + public bool Hidden; + + /// + /// When not null, this moodlet will replace itself with another Moodlet upon expiration. + /// + [DataField] + public ProtoId? MoodletOnEnd; +} diff --git a/Content.Shared/Mood/MoodEvents.cs b/Content.Shared/Mood/MoodEvents.cs new file mode 100644 index 0000000000..58a993d2b7 --- /dev/null +++ b/Content.Shared/Mood/MoodEvents.cs @@ -0,0 +1,59 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.Mood; + +[Serializable, NetSerializable] +public sealed class MoodEffectEvent : EntityEventArgs +{ + /// + /// ID of the moodlet prototype to use + /// + public string EffectId; + + /// + /// How much should the mood change be multiplied by + ///
+ /// This does nothing if the moodlet ID matches one with the same Category + ///
+ public float EffectModifier = 1f; + + /// + /// How much should the mood change be offset by, after multiplication + ///
+ /// This does nothing if the moodlet ID matches one with the same Category + ///
+ public float EffectOffset = 0f; + + public MoodEffectEvent(string effectId, float effectModifier = 1f, float effectOffset = 0f) + { + EffectId = effectId; + EffectModifier = effectModifier; + EffectOffset = effectOffset; + } +} + +[Serializable, NetSerializable] +public sealed class MoodRemoveEffectEvent : EntityEventArgs +{ + public string EffectId; + + public MoodRemoveEffectEvent(string effectId) + { + EffectId = effectId; + } +} + +/// +/// This event is raised whenever an entity sets their mood, allowing other systems to modify the end result of mood math. +/// EG: The end result after tallying up all Moodlets comes out to 70, but a trait multiplies it by 0.8 to make it 56. +/// +[ByRefEvent] +public record struct OnSetMoodEvent(EntityUid Receiver, float MoodChangedAmount, bool Cancelled); + +/// +/// This event is raised on an entity when it receives a mood effect, but before the effects are calculated. +/// Allows for other systems to pick and choose specific events to modify. +/// +[ByRefEvent] +public record struct OnMoodEffect(EntityUid Receiver, string EffectId, float EffectModifier = 1, float EffectOffset = 0); + diff --git a/Content.Shared/Mood/SharedMoodComponent.cs b/Content.Shared/Mood/SharedMoodComponent.cs new file mode 100644 index 0000000000..566f5c7b66 --- /dev/null +++ b/Content.Shared/Mood/SharedMoodComponent.cs @@ -0,0 +1,15 @@ +namespace Content.Shared.Mood; + +/// +/// This component exists solely to network CurrentMoodLevel, so that clients can make use of its value for math Prediction. +/// All mood logic is otherwise handled by the Server, and the client is not allowed to know the identity of its mood events. +/// +[RegisterComponent, AutoGenerateComponentState] +public sealed partial class NetMoodComponent : Component +{ + [DataField, AutoNetworkedField] + public float CurrentMoodLevel; + + [DataField, AutoNetworkedField] + public float NeutralMoodThreshold; +} \ No newline at end of file diff --git a/Content.Shared/MouseRotator/MouseRotatorComponent.cs b/Content.Shared/MouseRotator/MouseRotatorComponent.cs index a35dfe0a28..2844b3cb8b 100644 --- a/Content.Shared/MouseRotator/MouseRotatorComponent.cs +++ b/Content.Shared/MouseRotator/MouseRotatorComponent.cs @@ -30,8 +30,7 @@ public sealed partial class MouseRotatorComponent : Component public double RotationSpeed = float.MaxValue; /// - /// This one is important. If this is true, does not apply, and the system will - /// use instead. In this mode, the client will only send + /// This one is important. If this is true, does not apply. In this mode, the client will only send /// events when an entity should snap to a different cardinal direction, rather than for every angle change. /// /// This is useful for cases like humans, where what really matters is the visual sprite direction, as opposed to something @@ -50,13 +49,3 @@ public sealed class RequestMouseRotatorRotationEvent : EntityEventArgs { public Angle Rotation; } - -/// -/// Simpler version of for implementations -/// that only require snapping to 4-dir and not full angle rotation. -/// -[Serializable, NetSerializable] -public sealed class RequestMouseRotatorRotationSimpleEvent : EntityEventArgs -{ - public Direction Direction; -} diff --git a/Content.Shared/MouseRotator/SharedMouseRotatorSystem.cs b/Content.Shared/MouseRotator/SharedMouseRotatorSystem.cs index c57d477bd2..9663b3363d 100644 --- a/Content.Shared/MouseRotator/SharedMouseRotatorSystem.cs +++ b/Content.Shared/MouseRotator/SharedMouseRotatorSystem.cs @@ -1,5 +1,4 @@ using Content.Shared.Interaction; -using Robust.Shared.Timing; namespace Content.Shared.MouseRotator; @@ -16,7 +15,6 @@ public override void Initialize() base.Initialize(); SubscribeAllEvent(OnRequestRotation); - SubscribeAllEvent(OnRequestSimpleRotation); } public override void Update(float frameTime) @@ -50,7 +48,7 @@ public override void Update(float frameTime) private void OnRequestRotation(RequestMouseRotatorRotationEvent msg, EntitySessionEventArgs args) { if (args.SenderSession.AttachedEntity is not { } ent - || !TryComp(ent, out var rotator) || rotator.Simple4DirMode) + || !TryComp(ent, out var rotator)) { Log.Error($"User {args.SenderSession.Name} ({args.SenderSession.UserId}) tried setting local rotation directly without a valid mouse rotator component attached!"); return; @@ -59,17 +57,4 @@ private void OnRequestRotation(RequestMouseRotatorRotationEvent msg, EntitySessi rotator.GoalRotation = msg.Rotation; Dirty(ent, rotator); } - - private void OnRequestSimpleRotation(RequestMouseRotatorRotationSimpleEvent ev, EntitySessionEventArgs args) - { - if (args.SenderSession.AttachedEntity is not { } ent - || !TryComp(ent, out var rotator) || !rotator.Simple4DirMode) - { - Log.Error($"User {args.SenderSession.Name} ({args.SenderSession.UserId}) tried setting 4-dir rotation directly without a valid mouse rotator component attached!"); - return; - } - - rotator.GoalRotation = ev.Direction.ToAngle(); - Dirty(ent, rotator); - } } diff --git a/Content.Shared/Movement/Components/InputMoverComponent.cs b/Content.Shared/Movement/Components/InputMoverComponent.cs index 916ecc90af..40cb532e60 100644 --- a/Content.Shared/Movement/Components/InputMoverComponent.cs +++ b/Content.Shared/Movement/Components/InputMoverComponent.cs @@ -74,11 +74,12 @@ public sealed partial class InputMoverComponent : Component public const float LerpTime = 1.0f; - //NOTE I don't think I'm supposed to do this - public bool Sprinting => IoCManager.Resolve().GetCVar(CCVars.GamePressToSprint) + public bool Sprinting => DefaultSprinting ? (HeldMoveButtons & MoveButtons.Walk) != 0x0 : (HeldMoveButtons & MoveButtons.Walk) == 0x0; + public bool DefaultSprinting = true; + [ViewVariables(VVAccess.ReadWrite)] public bool CanMove = true; @@ -94,6 +95,6 @@ public sealed class InputMoverComponentState : ComponentState public Angle TargetRelativeRotation; public Angle RelativeRotation; public TimeSpan LerpTarget; - public bool CanMove; + public bool CanMove, DefaultSprinting; } } diff --git a/Content.Shared/Movement/Components/MobMoverComponent.cs b/Content.Shared/Movement/Components/MobMoverComponent.cs index a77f415b93..7ad7961ed7 100644 --- a/Content.Shared/Movement/Components/MobMoverComponent.cs +++ b/Content.Shared/Movement/Components/MobMoverComponent.cs @@ -14,6 +14,15 @@ public sealed partial class MobMoverComponent : Component [DataField] public float PushStrength = 600f; + [DataField, AutoNetworkedField] + public float StepSoundMoveDistanceRunning = 2; + + [DataField, AutoNetworkedField] + public float StepSoundMoveDistanceWalking = 1.5f; + + [DataField, AutoNetworkedField] + public float FootstepVariation; + [ViewVariables(VVAccess.ReadWrite)] public EntityCoordinates LastPosition { get; set; } diff --git a/Content.Shared/Movement/Events/UpdateInputCVarsMessage.cs b/Content.Shared/Movement/Events/UpdateInputCVarsMessage.cs new file mode 100644 index 0000000000..415a833f10 --- /dev/null +++ b/Content.Shared/Movement/Events/UpdateInputCVarsMessage.cs @@ -0,0 +1,9 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.Movement.Events; + +/// +/// Raised from the client to the server to require the server to update the client's input CVars. +/// +[Serializable, NetSerializable] +public sealed class UpdateInputCVarsMessage : EntityEventArgs { } diff --git a/Content.Shared/Movement/Pulling/Components/PullableComponent.cs b/Content.Shared/Movement/Pulling/Components/PullableComponent.cs index db889e7e3b..01ce0efaae 100644 --- a/Content.Shared/Movement/Pulling/Components/PullableComponent.cs +++ b/Content.Shared/Movement/Pulling/Components/PullableComponent.cs @@ -36,4 +36,11 @@ public sealed partial class PullableComponent : Component [Access(typeof(Systems.PullingSystem), Other = AccessPermissions.ReadExecute)] [AutoNetworkedField, DataField] public bool PrevFixedRotation; + + /// + /// Whether the entity is currently being actively pushed by the puller. + /// If true, the entity will be able to enter disposals upon colliding with them, and the like. + /// + [DataField, AutoNetworkedField] + public bool BeingActivelyPushed = false; } diff --git a/Content.Shared/Movement/Pulling/Components/PullerComponent.cs b/Content.Shared/Movement/Pulling/Components/PullerComponent.cs index 1fc9b731bd..648f06086b 100644 --- a/Content.Shared/Movement/Pulling/Components/PullerComponent.cs +++ b/Content.Shared/Movement/Pulling/Components/PullerComponent.cs @@ -1,5 +1,6 @@ using Content.Shared.Movement.Pulling.Systems; using Robust.Shared.GameStates; +using Robust.Shared.Map; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; namespace Content.Shared.Movement.Pulling.Components; @@ -11,16 +12,17 @@ namespace Content.Shared.Movement.Pulling.Components; [Access(typeof(PullingSystem))] public sealed partial class PullerComponent : Component { - // My raiding guild /// - /// Next time the puller can throw what is being pulled. - /// Used to avoid spamming it for infinite spin + velocity. + /// Next time the puller change where they are pulling the target towards. /// [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoNetworkedField] - public TimeSpan NextThrow; + public TimeSpan NextPushTargetChange; + + [DataField, AutoNetworkedField] + public TimeSpan NextPushStop; [DataField] - public TimeSpan ThrowCooldown = TimeSpan.FromSeconds(1); + public TimeSpan PushChangeCooldown = TimeSpan.FromSeconds(0.1f), PushDuration = TimeSpan.FromSeconds(5f); // Before changing how this is updated, please see SharedPullerSystem.RefreshMovementSpeed public float WalkSpeedModifier => Pulling == default ? 1.0f : 0.95f; @@ -28,14 +30,38 @@ public sealed partial class PullerComponent : Component public float SprintSpeedModifier => Pulling == default ? 1.0f : 0.95f; /// - /// Entity currently being pulled if applicable. + /// Entity currently being pulled/pushed if applicable. /// [AutoNetworkedField, DataField] public EntityUid? Pulling; + /// + /// The position the entity is currently being pulled towards. + /// + [AutoNetworkedField, DataField] + public EntityCoordinates? PushingTowards; + /// /// Does this entity need hands to be able to pull something? /// [DataField] public bool NeedsHands = true; + + /// + /// The maximum acceleration of pushing, in meters per second squared. + /// + [DataField] + public float PushAcceleration = 0.3f; + + /// + /// The maximum speed to which the pulled entity may be accelerated relative to the push direction, in meters per second. + /// + [DataField] + public float MaxPushSpeed = 1.2f; + + /// + /// The maximum distance between the puller and the point towards which the puller may attempt to pull it, in meters. + /// + [DataField] + public float MaxPushRange = 2f; } diff --git a/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs b/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs index 3c265d5a02..4bf53c8dbd 100644 --- a/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs +++ b/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs @@ -4,20 +4,25 @@ using Content.Shared.Alert; using Content.Shared.Buckle.Components; using Content.Shared.Database; +using Content.Shared.Gravity; using Content.Shared.Hands; using Content.Shared.Hands.EntitySystems; using Content.Shared.Input; using Content.Shared.Interaction; +using Content.Shared.Movement.Components; using Content.Shared.Movement.Events; using Content.Shared.Movement.Pulling.Components; using Content.Shared.Movement.Pulling.Events; using Content.Shared.Movement.Systems; +using Content.Shared.Projectiles; using Content.Shared.Pulling.Events; +using Content.Shared.Standing; using Content.Shared.Throwing; using Content.Shared.Verbs; using Robust.Shared.Containers; using Robust.Shared.Input.Binding; using Robust.Shared.Map; +using Robust.Shared.Network; using Robust.Shared.Physics; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Events; @@ -34,8 +39,10 @@ public sealed class PullingSystem : EntitySystem { [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; + [Dependency] private readonly INetManager _net = default!; [Dependency] private readonly ActionBlockerSystem _blocker = default!; [Dependency] private readonly AlertsSystem _alertsSystem = default!; + [Dependency] private readonly SharedGravitySystem _gravity = default!; [Dependency] private readonly MovementSpeedModifierSystem _modifierSystem = default!; [Dependency] private readonly SharedJointSystem _joints = default!; [Dependency] private readonly SharedContainerSystem _containerSystem = default!; @@ -43,7 +50,7 @@ public sealed class PullingSystem : EntitySystem [Dependency] private readonly SharedInteractionSystem _interaction = default!; [Dependency] private readonly SharedPhysicsSystem _physics = default!; [Dependency] private readonly SharedTransformSystem _xformSys = default!; - [Dependency] private readonly ThrowingSystem _throwing = default!; + [Dependency] private readonly ThrownItemSystem _thrownItem = default!; public override void Initialize() { @@ -57,11 +64,14 @@ public override void Initialize() SubscribeLocalEvent(OnJointRemoved); SubscribeLocalEvent>(AddPullVerbs); SubscribeLocalEvent(OnPullableContainerInsert); + SubscribeLocalEvent(OnPullableCollide); + SubscribeLocalEvent(OnPullerMoveInput); SubscribeLocalEvent(OnPullerContainerInsert); SubscribeLocalEvent(OnPullerUnpaused); SubscribeLocalEvent(OnVirtualItemDeleted); SubscribeLocalEvent(OnRefreshMovespeed); + SubscribeLocalEvent(OnDropHandItems); CommandBinds.Builder .Bind(ContentKeyFunctions.MovePulledObject, new PointerInputCmdHandler(OnRequestMovePulledObject)) @@ -69,6 +79,99 @@ public override void Initialize() .Register(); } + public override void Shutdown() + { + base.Shutdown(); + CommandBinds.Unregister(); + } + + public override void Update(float frameTime) + { + if (_net.IsClient) // Client cannot predict this + return; + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var puller, out var pullerComp, out var pullerPhysics, out var pullerXForm)) + { + // If not pulling, reset the pushing cooldowns and exit + if (pullerComp.Pulling is not { } pulled || !TryComp(pulled, out var pulledComp)) + { + pullerComp.PushingTowards = null; + pullerComp.NextPushTargetChange = TimeSpan.Zero; + continue; + } + + pulledComp.BeingActivelyPushed = false; // Temporarily set to false; if the checks below pass, it will be set to true again + + // If pulling but the pullee is invalid or is on a different map, stop pulling + var pulledXForm = Transform(pulled); + if (!TryComp(pulled, out var pulledPhysics) + || pulledPhysics.BodyType == BodyType.Static + || pulledXForm.MapUid != pullerXForm.MapUid) + { + StopPulling(pulled, pulledComp); + continue; + } + + if (pullerComp.PushingTowards is null) + continue; + + // If pushing but the target position is invalid, or the push action has expired or finished, stop pushing + if (pullerComp.NextPushStop < _timing.CurTime + || !(pullerComp.PushingTowards.Value.ToMap(EntityManager, _xformSys) is var pushCoordinates) + || pushCoordinates.MapId != pulledXForm.MapID) + { + pullerComp.PushingTowards = null; + pullerComp.NextPushTargetChange = TimeSpan.Zero; + continue; + } + + // Actual force calculation. All the Vector2's below are in map coordinates. + var desiredDeltaPos = pushCoordinates.Position - Transform(pulled).Coordinates.ToMapPos(EntityManager, _xformSys); + if (desiredDeltaPos.LengthSquared() < 0.1f) + { + pullerComp.PushingTowards = null; + continue; + } + + var velocityAndDirectionAngle = new Angle(pulledPhysics.LinearVelocity) - new Angle(desiredDeltaPos); + var currentRelativeSpeed = pulledPhysics.LinearVelocity.Length() * (float) Math.Cos(velocityAndDirectionAngle.Theta); + var desiredAcceleration = MathF.Max(0f, pullerComp.MaxPushSpeed - currentRelativeSpeed); + + var desiredImpulse = pulledPhysics.Mass * desiredDeltaPos; + var maxSourceImpulse = MathF.Min(pullerComp.PushAcceleration, desiredAcceleration) * pullerPhysics.Mass; + var actualImpulse = desiredImpulse.LengthSquared() > maxSourceImpulse * maxSourceImpulse ? desiredDeltaPos.Normalized() * maxSourceImpulse : desiredImpulse; + + // Ideally we'd want to apply forces instead of impulses, however... + // We cannot use ApplyForce here because it will be cleared on the next physics substep which will render it ultimately useless + // The alternative is to run this function on every physics substep, but that is way too expensive for such a minor system + _physics.ApplyLinearImpulse(pulled, actualImpulse); + if (_gravity.IsWeightless(puller, pullerPhysics, pullerXForm)) + _physics.ApplyLinearImpulse(puller, -actualImpulse); + + pulledComp.BeingActivelyPushed = true; + } + query.Dispose(); + } + + private void OnPullerMoveInput(EntityUid uid, PullerComponent component, ref MoveInputEvent args) + { + // Stop pushing + component.PushingTowards = null; + component.NextPushStop = TimeSpan.Zero; + } + + private void OnDropHandItems(EntityUid uid, PullerComponent pullerComp, DropHandItemsEvent args) + { + if (pullerComp.Pulling == null || pullerComp.NeedsHands) + return; + + if (!TryComp(pullerComp.Pulling, out PullableComponent? pullableComp)) + return; + + TryStopPull(pullerComp.Pulling.Value, pullableComp, uid); + } + private void OnPullerContainerInsert(Entity ent, ref EntGotInsertedIntoContainerMessage args) { if (ent.Comp.Pulling == null) return; @@ -84,15 +187,26 @@ private void OnPullableContainerInsert(Entity ent, ref EntGot TryStopPull(ent.Owner, ent.Comp); } - public override void Shutdown() + private void OnPullableCollide(Entity ent, ref StartCollideEvent args) { - base.Shutdown(); - CommandBinds.Unregister(); + if (!ent.Comp.BeingActivelyPushed || ent.Comp.Puller == null || args.OtherEntity == ent.Comp.Puller) + return; + + // This component isn't actually needed anywhere besides the thrownitemsyste`m itself, so we just fake it + var fakeThrown = new ThrownItemComponent() + { + Owner = ent.Owner, + Animate = false, + Landed = false, + PlayLandSound = false, + Thrower = ent.Comp.Puller + }; + _thrownItem.ThrowCollideInteraction(fakeThrown, ent, args.OtherEntity); } private void OnPullerUnpaused(EntityUid uid, PullerComponent component, ref EntityUnpausedEvent args) { - component.NextThrow += args.PausedTime; + component.NextPushTargetChange += args.PausedTime; } private void OnVirtualItemDeleted(EntityUid uid, PullerComponent component, VirtualItemDeletedEvent args) @@ -204,6 +318,7 @@ private void StopPulling(EntityUid pullableUid, PullableComponent pullableComp) var oldPuller = pullableComp.Puller; pullableComp.PullJointId = null; pullableComp.Puller = null; + pullableComp.BeingActivelyPushed = false; Dirty(pullableUid, pullableComp); // No more joints with puller -> force stop pull. @@ -234,31 +349,22 @@ public bool IsPulled(EntityUid uid, PullableComponent? component = null) private bool OnRequestMovePulledObject(ICommonSession? session, EntityCoordinates coords, EntityUid uid) { - if (session?.AttachedEntity is not { } player || - !player.IsValid()) - { - return false; - } - - if (!TryComp(player, out var pullerComp)) + if (session?.AttachedEntity is not { } player + || !player.IsValid() + || !TryComp(player, out var pullerComp)) return false; var pulled = pullerComp.Pulling; - - if (!HasComp(pulled)) + if (!HasComp(pulled) + || _containerSystem.IsEntityInContainer(player) + || _timing.CurTime < pullerComp.NextPushTargetChange) return false; - if (_containerSystem.IsEntityInContainer(player)) - return false; - - // Cooldown buddy - if (_timing.CurTime < pullerComp.NextThrow) - return false; - - pullerComp.NextThrow = _timing.CurTime + pullerComp.ThrowCooldown; + pullerComp.NextPushTargetChange = _timing.CurTime + pullerComp.PushChangeCooldown; + pullerComp.NextPushStop = _timing.CurTime + pullerComp.PushDuration; // Cap the distance - const float range = 2f; + var range = pullerComp.MaxPushRange; var fromUserCoords = coords.WithEntityId(player, EntityManager); var userCoords = new EntityCoordinates(player, Vector2.Zero); @@ -268,8 +374,9 @@ private bool OnRequestMovePulledObject(ICommonSession? session, EntityCoordinate fromUserCoords = userCoords.Offset(userDirection.Normalized() * range); } + pullerComp.PushingTowards = fromUserCoords; Dirty(player, pullerComp); - _throwing.TryThrow(pulled.Value, fromUserCoords, user: player, strength: 4f, animated: false, recoil: false, playSound: false, doSpin: false); + return false; } @@ -349,14 +456,17 @@ public bool CanPull(EntityUid puller, EntityUid pullableUid, PullerComponent? pu return !startPull.Cancelled && !getPulled.Cancelled; } - public bool TogglePull(EntityUid pullableUid, EntityUid pullerUid, PullableComponent pullable) + public bool TogglePull(Entity pullable, EntityUid pullerUid) { - if (pullable.Puller == pullerUid) + if (!Resolve(pullable, ref pullable.Comp, false)) + return false; + + if (pullable.Comp.Puller == pullerUid) { - return TryStopPull(pullableUid, pullable); + return TryStopPull(pullable, pullable.Comp); } - return TryStartPull(pullerUid, pullableUid, pullableComp: pullable); + return TryStartPull(pullerUid, pullable, pullableComp: pullable); } public bool TogglePull(EntityUid pullerUid, PullerComponent puller) @@ -364,7 +474,7 @@ public bool TogglePull(EntityUid pullerUid, PullerComponent puller) if (!TryComp(puller.Pulling, out var pullable)) return false; - return TogglePull(puller.Pulling.Value, pullerUid, pullable); + return TogglePull((puller.Pulling.Value, pullable), pullerUid); } public bool TryStartPull(EntityUid pullerUid, EntityUid pullableUid, diff --git a/Content.Shared/Movement/Systems/SharedJetpackSystem.cs b/Content.Shared/Movement/Systems/SharedJetpackSystem.cs index 8c42511f84..8d358f8db3 100644 --- a/Content.Shared/Movement/Systems/SharedJetpackSystem.cs +++ b/Content.Shared/Movement/Systems/SharedJetpackSystem.cs @@ -1,9 +1,11 @@ using Content.Shared.Actions; +using Content.Shared.CCVar; using Content.Shared.Gravity; using Content.Shared.Interaction.Events; using Content.Shared.Movement.Components; using Content.Shared.Movement.Events; using Content.Shared.Popups; +using Robust.Shared.Configuration; using Robust.Shared.Containers; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Systems; @@ -20,6 +22,7 @@ public abstract class SharedJetpackSystem : EntitySystem [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly SharedPhysicsSystem _physics = default!; [Dependency] private readonly ActionContainerSystem _actionContainer = default!; + [Dependency] private readonly IConfigurationManager _config = default!; public override void Initialize() { @@ -125,8 +128,11 @@ private void OnJetpackToggle(EntityUid uid, JetpackComponent component, ToggleJe private bool CanEnableOnGrid(EntityUid? gridUid) { - return gridUid == null || - (!HasComp(gridUid)); + return _config.GetCVar(CCVars.JetpackEnableAnywhere) + || gridUid == null + || _config.GetCVar(CCVars.JetpackEnableInNoGravity) + && TryComp(gridUid, out var comp) + && comp.Enabled; } private void OnJetpackGetAction(EntityUid uid, JetpackComponent component, GetItemActionsEvent args) diff --git a/Content.Shared/Movement/Systems/SharedMoverController.CVars.cs b/Content.Shared/Movement/Systems/SharedMoverController.CVars.cs new file mode 100644 index 0000000000..97c837eb8e --- /dev/null +++ b/Content.Shared/Movement/Systems/SharedMoverController.CVars.cs @@ -0,0 +1,43 @@ +using Content.Shared.CCVar; +using Content.Shared.Mind.Components; +using Content.Shared.Movement.Components; +using Content.Shared.Movement.Events; +using Robust.Shared.Configuration; + +namespace Content.Shared.Movement.Systems; + +public abstract partial class SharedMoverController +{ + [Dependency] private readonly INetConfigurationManager _netConfig = default!; + + private void InitializeCVars() + { + SubscribeLocalEvent(OnMindAdded); + SubscribeLocalEvent(OnMindRemoved); + SubscribeNetworkEvent(OnUpdateCVars); + } + + private void OnMindAdded(Entity ent, ref MindAddedMessage args) + { + if (args.Mind.Comp.Session?.Channel is not { } channel) + return; + + ent.Comp.DefaultSprinting = _netConfig.GetClientCVar(channel, CCVars.DefaultWalk); + WalkingAlert(ent, ent.Comp); + } + + private void OnMindRemoved(Entity ent, ref MindRemovedMessage args) + { + // If it's an ai-controlled mob, we probably want them sprinting by default. + ent.Comp.DefaultSprinting = true; + } + + private void OnUpdateCVars(UpdateInputCVarsMessage msg, EntitySessionEventArgs args) + { + if (args.SenderSession.AttachedEntity is not { } uid || !TryComp(uid, out var mover)) + return; + + mover.DefaultSprinting = _netConfig.GetClientCVar(args.SenderSession.Channel, CCVars.DefaultWalk); + WalkingAlert(uid, mover); + } +} diff --git a/Content.Shared/Movement/Systems/SharedMoverController.Input.cs b/Content.Shared/Movement/Systems/SharedMoverController.Input.cs index 50cffa6ffe..1c097ce17b 100644 --- a/Content.Shared/Movement/Systems/SharedMoverController.Input.cs +++ b/Content.Shared/Movement/Systems/SharedMoverController.Input.cs @@ -5,6 +5,7 @@ using Content.Shared.Input; using Content.Shared.Movement.Components; using Content.Shared.Movement.Events; +using Robust.Shared.Configuration; using Robust.Shared.GameStates; using Robust.Shared.Input; using Robust.Shared.Input.Binding; @@ -108,6 +109,7 @@ private void OnMoverHandleState(EntityUid uid, InputMoverComponent component, Co component.TargetRelativeRotation = state.TargetRelativeRotation; component.CanMove = state.CanMove; component.RelativeEntity = EnsureEntity(state.RelativeEntity, uid); + component.DefaultSprinting = state.DefaultSprinting; // Reset component.LastInputTick = GameTick.Zero; @@ -131,6 +133,7 @@ private void OnMoverGetState(EntityUid uid, InputMoverComponent component, ref C HeldMoveButtons = component.HeldMoveButtons, RelativeRotation = component.RelativeRotation, TargetRelativeRotation = component.TargetRelativeRotation, + DefaultSprinting = component.DefaultSprinting }; } @@ -260,7 +263,7 @@ private void OnInputParentChange(EntityUid uid, InputMoverComponent component, r } var oldMapId = args.OldMapId; - var mapId = args.Transform.MapID; + var mapId = args.Transform.MapUid; // If we change maps then reset eye rotation entirely. if (oldMapId != mapId) @@ -334,7 +337,7 @@ private void OnInputInit(EntityUid uid, InputMoverComponent component, Component component.RelativeEntity = xform.GridUid ?? xform.MapUid; component.TargetRelativeRotation = Angle.Zero; - WalkingAlert(uid, !component.Sprinting); + WalkingAlert(uid, component); } private void HandleRunChange(EntityUid uid, ushort subTick, bool walking) @@ -346,8 +349,8 @@ private void HandleRunChange(EntityUid uid, ushort subTick, bool walking) // if we swap to relay then stop our existing input if we ever change back. if (moverComp != null) { - WalkingAlert(uid, walking); SetMoveInput(moverComp, MoveButtons.None); + WalkingAlert(uid, moverComp); } HandleRunChange(relayMover.RelayEntity, subTick, walking); @@ -467,8 +470,8 @@ private void ResetSubtick(InputMoverComponent component) public void SetSprinting(EntityUid entity, InputMoverComponent component, ushort subTick, bool walking) { // Logger.Info($"[{_gameTiming.CurTick}/{subTick}] Sprint: {enabled}"); - WalkingAlert(entity, walking); SetMoveInput(entity, component, subTick, walking, MoveButtons.Walk); + WalkingAlert(entity, component); } /// @@ -620,7 +623,7 @@ public enum MoveButtons : byte Down = 2, Left = 4, Right = 8, - Walk = 16, // This may be either a sprint button or a walk button, depending on server config + Walk = 16, // This may be either a sprint button or a walk button, depending on mover config AnyDirection = Up | Down | Left | Right, } diff --git a/Content.Shared/Movement/Systems/SharedMoverController.cs b/Content.Shared/Movement/Systems/SharedMoverController.cs index 3cc35e7bc6..00afa3a4fb 100644 --- a/Content.Shared/Movement/Systems/SharedMoverController.cs +++ b/Content.Shared/Movement/Systems/SharedMoverController.cs @@ -64,11 +64,6 @@ public abstract partial class SharedMoverController : VirtualController protected EntityQuery CanMoveInAirQuery; protected EntityQuery NoRotateQuery; - private const float StepSoundMoveDistanceRunning = 2; - private const float StepSoundMoveDistanceWalking = 1.5f; - - private const float FootstepVariation = 0f; - /// /// /// @@ -98,6 +93,7 @@ public override void Initialize() InitializeInput(); InitializeRelay(); + InitializeCVars(); Subs.CVar(_configManager, CCVars.RelativeMovement, value => _relativeMovement = value, true); Subs.CVar(_configManager, CCVars.StopSpeed, value => _stopSpeed = value, true); UpdatesBefore.Add(typeof(TileFrictionController)); @@ -273,7 +269,7 @@ protected void HandleMobMovement( var audioParams = sound.Params .WithVolume(volume) - .WithVariation(sound.Params.Variation ?? FootstepVariation); + .WithVariation(sound.Params.Variation ?? mobMover.FootstepVariation); // If we're a relay target then predict the sound for all relays. if (relayTarget != null) @@ -298,10 +294,9 @@ protected void HandleMobMovement( PhysicsSystem.SetAngularVelocity(physicsUid, 0, body: physicsComponent); } - private void WalkingAlert(EntityUid player, bool walking) + private void WalkingAlert(EntityUid player, InputMoverComponent component) { - walking = _configManager.GetCVar(CCVars.GamePressToSprint) ? !walking : walking; - _alerts.ShowAlert(player, AlertType.Walking, walking ? (short) 0 : (short) 1); + _alerts.ShowAlert(player, AlertType.Walking, component.Sprinting ? (short) 1 : (short) 0); } public void LerpRotation(EntityUid uid, InputMoverComponent mover, float frameTime) @@ -425,7 +420,9 @@ private bool TryGetSound( return false; var coordinates = xform.Coordinates; - var distanceNeeded = mover.Sprinting ? StepSoundMoveDistanceRunning : StepSoundMoveDistanceWalking; + var distanceNeeded = mover.Sprinting + ? mobMover.StepSoundMoveDistanceRunning + : mobMover.StepSoundMoveDistanceWalking; // Handle footsteps. if (!weightless) diff --git a/Content.Shared/Ninja/Components/SpaceNinjaComponent.cs b/Content.Shared/Ninja/Components/SpaceNinjaComponent.cs index dff4b56aa4..0f3bff265c 100644 --- a/Content.Shared/Ninja/Components/SpaceNinjaComponent.cs +++ b/Content.Shared/Ninja/Components/SpaceNinjaComponent.cs @@ -1,6 +1,6 @@ using Content.Shared.Ninja.Systems; using Robust.Shared.GameStates; -using Robust.Shared.Serialization; +using Robust.Shared.Prototypes; namespace Content.Shared.Ninja.Components; @@ -35,4 +35,22 @@ public sealed partial class SpaceNinjaComponent : Component /// [DataField("katana"), AutoNetworkedField] public EntityUid? Katana; + + /// + /// Objective to complete after calling in a threat. + /// + [DataField] + public EntProtoId TerrorObjective = "TerrorObjective"; + + /// + /// Objective to complete after setting everyone to arrest. + /// + [DataField] + public EntProtoId MassArrestObjective = "MassArrestObjective"; + + /// + /// Objective to complete after the spider charge detonates. + /// + [DataField] + public EntProtoId SpiderChargeObjective = "SpiderChargeObjective"; } diff --git a/Content.Shared/Ninja/Systems/DashAbilitySystem.cs b/Content.Shared/Ninja/Systems/DashAbilitySystem.cs index f9e5d4a1f6..4853968b61 100644 --- a/Content.Shared/Ninja/Systems/DashAbilitySystem.cs +++ b/Content.Shared/Ninja/Systems/DashAbilitySystem.cs @@ -4,8 +4,8 @@ using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Ninja.Components; -using Content.Shared.Physics; using Content.Shared.Popups; +using Content.Shared.Examine; using Robust.Shared.Audio.Systems; using Robust.Shared.Timing; @@ -20,7 +20,7 @@ public sealed class DashAbilitySystem : EntitySystem [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedChargesSystem _charges = default!; [Dependency] private readonly SharedHandsSystem _hands = default!; - [Dependency] private readonly SharedInteractionSystem _interaction = default!; + [Dependency] private readonly ExamineSystemShared _examine = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly ActionContainerSystem _actionContainer = default!; @@ -79,11 +79,10 @@ private void OnDash(EntityUid uid, DashAbilityComponent comp, DashEvent args) _popup.PopupClient(Loc.GetString("dash-ability-no-charges", ("item", uid)), user, user); return; } - - var origin = Transform(user).MapPosition; + var origin = _transform.GetMapCoordinates(user); var target = args.Target.ToMap(EntityManager, _transform); // prevent collision with the user duh - if (!_interaction.InRangeUnobstructed(origin, target, 0f, CollisionGroup.Opaque, uid => uid == user)) + if (!_examine.InRangeUnOccluded(origin, target, SharedInteractionSystem.MaxRaycastRange, null)) { // can only dash if the destination is visible on screen _popup.PopupClient(Loc.GetString("dash-ability-cant-see", ("item", uid)), user, user); diff --git a/Content.Shared/Ninja/Systems/SharedNinjaGlovesSystem.cs b/Content.Shared/Ninja/Systems/SharedNinjaGlovesSystem.cs index 815464bf7a..f61d0c6a90 100644 --- a/Content.Shared/Ninja/Systems/SharedNinjaGlovesSystem.cs +++ b/Content.Shared/Ninja/Systems/SharedNinjaGlovesSystem.cs @@ -1,6 +1,7 @@ using Content.Shared.Actions; using Content.Shared.CombatMode; using Content.Shared.Communications; +using Content.Shared.CriminalRecords.Components; using Content.Shared.Examine; using Content.Shared.Hands.Components; using Content.Shared.Interaction; @@ -62,6 +63,7 @@ public void DisableGloves(EntityUid uid, NinjaGlovesComponent? comp = null) RemComp(user); RemComp(user); RemComp(user); + RemComp(user); } /// diff --git a/Content.Shared/NukeOps/NukeOperativeComponent.cs b/Content.Shared/NukeOps/NukeOperativeComponent.cs index cdbefece9d..d19f0ae3e9 100644 --- a/Content.Shared/NukeOps/NukeOperativeComponent.cs +++ b/Content.Shared/NukeOps/NukeOperativeComponent.cs @@ -13,14 +13,9 @@ namespace Content.Shared.NukeOps; [RegisterComponent, NetworkedComponent] public sealed partial class NukeOperativeComponent : Component { - /// - /// Path to antagonist alert sound. - /// - [DataField("greetSoundNotification")] - public SoundSpecifier GreetSoundNotification = new SoundPathSpecifier("/Audio/Ambience/Antag/nukeops_start.ogg"); /// - /// + /// /// [DataField("syndStatusIcon", customTypeSerializer: typeof(PrototypeIdSerializer))] public string SyndStatusIcon = "SyndicateFaction"; diff --git a/Content.Shared/Nutrition/Components/ButcherableComponent.cs b/Content.Shared/Nutrition/Components/ButcherableComponent.cs index 4fce45422a..975d4329dc 100644 --- a/Content.Shared/Nutrition/Components/ButcherableComponent.cs +++ b/Content.Shared/Nutrition/Components/ButcherableComponent.cs @@ -1,5 +1,6 @@ using Content.Shared.Storage; using Robust.Shared.GameStates; +using Robust.Shared.Serialization; namespace Content.Shared.Nutrition.Components { @@ -25,6 +26,7 @@ public sealed partial class ButcherableComponent : Component public bool BeingButchered; } + [Serializable, NetSerializable] public enum ButcheringType : byte { Knife, // e.g. goliaths diff --git a/Content.Shared/Nutrition/Components/DrinkComponent.cs b/Content.Shared/Nutrition/Components/DrinkComponent.cs new file mode 100644 index 0000000000..17baaef5a3 --- /dev/null +++ b/Content.Shared/Nutrition/Components/DrinkComponent.cs @@ -0,0 +1,43 @@ +using Content.Shared.Nutrition.EntitySystems; +using Content.Shared.FixedPoint; +using Robust.Shared.Audio; +using Robust.Shared.GameStates; + +namespace Content.Shared.Nutrition.Components; + +[NetworkedComponent, AutoGenerateComponentState] +[RegisterComponent, Access(typeof(SharedDrinkSystem))] +public sealed partial class DrinkComponent : Component +{ + [DataField] + public string Solution = "drink"; + + [DataField, AutoNetworkedField] + public SoundSpecifier UseSound = new SoundPathSpecifier("/Audio/Items/drink.ogg"); + + [DataField, AutoNetworkedField] + public FixedPoint2 TransferAmount = FixedPoint2.New(5); + + /// + /// How long it takes to drink this yourself. + /// + [DataField, AutoNetworkedField] + public float Delay = 1; + + [DataField, AutoNetworkedField] + public bool Examinable = true; + + /// + /// If true, trying to drink when empty will not handle the event. + /// This means other systems such as equipping on use can run. + /// Example usecase is the bucket. + /// + [DataField] + public bool IgnoreEmpty; + + /// + /// This is how many seconds it takes to force feed someone this drink. + /// + [DataField, AutoNetworkedField] + public float ForceFeedDelay = 3; +} diff --git a/Content.Shared/Nutrition/Components/PressurizedSolutionComponent.cs b/Content.Shared/Nutrition/Components/PressurizedSolutionComponent.cs new file mode 100644 index 0000000000..7060f3bf79 --- /dev/null +++ b/Content.Shared/Nutrition/Components/PressurizedSolutionComponent.cs @@ -0,0 +1,106 @@ +using Content.Shared.Nutrition.EntitySystems; +using Robust.Shared.Audio; +using Robust.Shared.GameStates; + +namespace Content.Shared.Nutrition.Components; + +/// +/// Represents a solution container that can hold the pressure from a solution that +/// gets fizzy when aggitated, and can spray the solution when opened or thrown. +/// Handles simulating the fizziness of the solution, responding to aggitating events, +/// and spraying the solution out when opening or throwing the entity. +/// +[NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause] +[RegisterComponent, Access(typeof(PressurizedSolutionSystem))] +public sealed partial class PressurizedSolutionComponent : Component +{ + /// + /// The name of the solution to use. + /// + [DataField] + public string Solution = "drink"; + + /// + /// The sound to play when the solution sprays out of the container. + /// + [DataField] + public SoundSpecifier SpraySound = new SoundPathSpecifier("/Audio/Items/soda_spray.ogg"); + + /// + /// The longest amount of time that the solution can remain fizzy after being aggitated. + /// Put another way, how long the solution will remain fizzy when aggitated the maximum amount. + /// Used to calculate the current fizziness level. + /// + [DataField] + public TimeSpan FizzinessMaxDuration = TimeSpan.FromSeconds(120); + + /// + /// The time at which the solution will be fully settled after being shaken. + /// + [DataField, AutoNetworkedField, AutoPausedField] + public TimeSpan FizzySettleTime; + + /// + /// How much to increase the solution's fizziness each time it's shaken. + /// This assumes the solution has maximum fizzability. + /// A value of 1 will maximize it with a single shake, and a value of + /// 0.5 will increase it by half with each shake. + /// + [DataField] + public float FizzinessAddedOnShake = 1.0f; + + /// + /// How much to increase the solution's fizziness when it lands after being thrown. + /// This assumes the solution has maximum fizzability. + /// + [DataField] + public float FizzinessAddedOnLand = 0.25f; + + /// + /// How much to modify the chance of spraying when the entity is opened. + /// Increasing this effectively increases the fizziness value when checking if it should spray. + /// + [DataField] + public float SprayChanceModOnOpened = -0.01f; // Just enough to prevent spraying at 0 fizziness + + /// + /// How much to modify the chance of spraying when the entity is shaken. + /// Increasing this effectively increases the fizziness value when checking if it should spray. + /// + [DataField] + public float SprayChanceModOnShake = -1; // No spraying when shaken by default + + /// + /// How much to modify the chance of spraying when the entity lands after being thrown. + /// Increasing this effectively increases the fizziness value when checking if it should spray. + /// + [DataField] + public float SprayChanceModOnLand = 0.25f; + + /// + /// Holds the current randomly-rolled threshold value for spraying. + /// If fizziness exceeds this value when the entity is opened, it will spray. + /// By rolling this value when the entity is aggitated, we can have randomization + /// while still having prediction! + /// + [DataField, AutoNetworkedField] + public float SprayFizzinessThresholdRoll; + + /// + /// Popup message shown to user when sprayed by the solution. + /// + [DataField] + public LocId SprayHolderMessageSelf = "pressurized-solution-spray-holder-self"; + + /// + /// Popup message shown to others when a user is sprayed by the solution. + /// + [DataField] + public LocId SprayHolderMessageOthers = "pressurized-solution-spray-holder-others"; + + /// + /// Popup message shown above the entity when the solution sprays without a target. + /// + [DataField] + public LocId SprayGroundMessage = "pressurized-solution-spray-ground"; +} diff --git a/Content.Shared/Nutrition/Components/ShakeableComponent.cs b/Content.Shared/Nutrition/Components/ShakeableComponent.cs new file mode 100644 index 0000000000..cc1c08a9b2 --- /dev/null +++ b/Content.Shared/Nutrition/Components/ShakeableComponent.cs @@ -0,0 +1,50 @@ +using Robust.Shared.Audio; +using Robust.Shared.GameStates; + +namespace Content.Shared.Nutrition.Components; + +/// +/// Adds a "Shake" verb to the entity's verb menu. +/// Handles checking the entity can be shaken, displaying popups when shaking, +/// and raising a ShakeEvent when a shake occurs. +/// Reacting to being shaken is left up to other components. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class ShakeableComponent : Component +{ + /// + /// How long it takes to shake this item. + /// + [DataField] + public TimeSpan ShakeDuration = TimeSpan.FromSeconds(1f); + + /// + /// Does the entity need to be in the user's hand in order to be shaken? + /// + [DataField] + public bool RequireInHand; + + /// + /// Label to display in the verbs menu for this item's shake action. + /// + [DataField] + public LocId ShakeVerbText = "shakeable-verb"; + + /// + /// Text that will be displayed to the user when shaking this item. + /// + [DataField] + public LocId ShakePopupMessageSelf = "shakeable-popup-message-self"; + + /// + /// Text that will be displayed to other users when someone shakes this item. + /// + [DataField] + public LocId ShakePopupMessageOthers = "shakeable-popup-message-others"; + + /// + /// The sound that will be played when shaking this item. + /// + [DataField] + public SoundSpecifier ShakeSound = new SoundPathSpecifier("/Audio/Items/soda_shake.ogg"); +} diff --git a/Content.Shared/Nutrition/EntitySystems/HungerSystem.cs b/Content.Shared/Nutrition/EntitySystems/HungerSystem.cs index d8808b6e4a..e6d8255333 100644 --- a/Content.Shared/Nutrition/EntitySystems/HungerSystem.cs +++ b/Content.Shared/Nutrition/EntitySystems/HungerSystem.cs @@ -1,28 +1,56 @@ +using System.Diagnostics.CodeAnalysis; using Content.Shared.Alert; using Content.Shared.Damage; using Content.Shared.Mobs.Systems; using Content.Shared.Movement.Systems; using Content.Shared.Nutrition.Components; using Content.Shared.Rejuvenate; +using Content.Shared.StatusIcon; +using Robust.Shared.Prototypes; +using Content.Shared.Mood; +using Robust.Shared.Network; using Robust.Shared.Random; using Robust.Shared.Timing; +using Robust.Shared.Utility; +using Robust.Shared.Configuration; +using Content.Shared.CCVar; namespace Content.Shared.Nutrition.EntitySystems; public sealed class HungerSystem : EntitySystem { [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly IPrototypeManager _prototype = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly AlertsSystem _alerts = default!; [Dependency] private readonly DamageableSystem _damageable = default!; [Dependency] private readonly MobStateSystem _mobState = default!; [Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!; [Dependency] private readonly SharedJetpackSystem _jetpack = default!; + [Dependency] private readonly INetManager _net = default!; + [Dependency] private readonly IConfigurationManager _config = default!; + + [ValidatePrototypeId] + private const string HungerIconOverfedId = "HungerIconOverfed"; + + [ValidatePrototypeId] + private const string HungerIconPeckishId = "HungerIconPeckish"; + + [ValidatePrototypeId] + private const string HungerIconStarvingId = "HungerIconStarving"; + + private StatusIconPrototype? _hungerIconOverfed; + private StatusIconPrototype? _hungerIconPeckish; + private StatusIconPrototype? _hungerIconStarving; public override void Initialize() { base.Initialize(); + DebugTools.Assert(_prototype.TryIndex(HungerIconOverfedId, out _hungerIconOverfed) && + _prototype.TryIndex(HungerIconPeckishId, out _hungerIconPeckish) && + _prototype.TryIndex(HungerIconStarvingId, out _hungerIconStarving)); + SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(OnShutdown); SubscribeLocalEvent(OnRefreshMovespeed); @@ -44,10 +72,9 @@ private void OnShutdown(EntityUid uid, HungerComponent component, ComponentShutd private void OnRefreshMovespeed(EntityUid uid, HungerComponent component, RefreshMovementSpeedModifiersEvent args) { - if (component.CurrentThreshold > HungerThreshold.Starving) - return; - - if (_jetpack.IsUserFlying(uid)) + if (_config.GetCVar(CCVars.MoodEnabled) + || component.CurrentThreshold > HungerThreshold.Starving + || _jetpack.IsUserFlying(uid)) return; args.ModifySpeed(component.StarvingSlowdownModifier, component.StarvingSlowdownModifier); @@ -111,7 +138,13 @@ private void DoHungerThresholdEffects(EntityUid uid, HungerComponent? component if (GetMovementThreshold(component.CurrentThreshold) != GetMovementThreshold(component.LastThreshold)) { - _movementSpeedModifier.RefreshMovementSpeedModifiers(uid); + if (!_config.GetCVar(CCVars.MoodEnabled)) + _movementSpeedModifier.RefreshMovementSpeedModifiers(uid); + else if (_net.IsServer) + { + var ev = new MoodEffectEvent("Hunger" + component.CurrentThreshold); + RaiseLocalEvent(uid, ev); + } } if (component.HungerThresholdAlerts.TryGetValue(component.CurrentThreshold, out var alertId)) @@ -194,6 +227,27 @@ private bool GetMovementThreshold(HungerThreshold threshold) } } + public bool TryGetStatusIconPrototype(HungerComponent component, [NotNullWhen(true)] out StatusIconPrototype? prototype) + { + switch (component.CurrentThreshold) + { + case HungerThreshold.Overfed: + prototype = _hungerIconOverfed; + break; + case HungerThreshold.Peckish: + prototype = _hungerIconPeckish; + break; + case HungerThreshold.Starving: + prototype = _hungerIconStarving; + break; + default: + prototype = null; + break; + } + + return prototype != null; + } + public override void Update(float frameTime) { base.Update(frameTime); @@ -210,4 +264,3 @@ public override void Update(float frameTime) } } } - diff --git a/Content.Shared/Nutrition/EntitySystems/OpenableSystem.cs b/Content.Shared/Nutrition/EntitySystems/OpenableSystem.cs index 0ad0877d22..2934ced8b4 100644 --- a/Content.Shared/Nutrition/EntitySystems/OpenableSystem.cs +++ b/Content.Shared/Nutrition/EntitySystems/OpenableSystem.cs @@ -16,9 +16,9 @@ namespace Content.Shared.Nutrition.EntitySystems; /// public sealed partial class OpenableSystem : EntitySystem { - [Dependency] protected readonly SharedAppearanceSystem Appearance = default!; - [Dependency] protected readonly SharedAudioSystem Audio = default!; - [Dependency] protected readonly SharedPopupSystem Popup = default!; + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; public override void Initialize() { @@ -31,6 +31,8 @@ public override void Initialize() SubscribeLocalEvent(HandleIfClosed); SubscribeLocalEvent>(AddOpenCloseVerbs); SubscribeLocalEvent(OnTransferAttempt); + SubscribeLocalEvent(OnAttemptShake); + SubscribeLocalEvent(OnAttemptAddFizziness); } private void OnInit(EntityUid uid, OpenableComponent comp, ComponentInit args) @@ -100,6 +102,20 @@ private void OnTransferAttempt(Entity ent, ref SolutionTransf } } + private void OnAttemptShake(Entity entity, ref AttemptShakeEvent args) + { + // Prevent shaking open containers + if (entity.Comp.Opened) + args.Cancelled = true; + } + + private void OnAttemptAddFizziness(Entity entity, ref AttemptAddFizzinessEvent args) + { + // Can't add fizziness to an open container + if (entity.Comp.Opened) + args.Cancelled = true; + } + /// /// Returns true if the entity either does not have OpenableComponent or it is opened. /// Drinks that don't have OpenableComponent are automatically open, so it returns true. @@ -126,7 +142,7 @@ public bool IsClosed(EntityUid uid, EntityUid? user = null, OpenableComponent? c return false; if (user != null) - Popup.PopupEntity(Loc.GetString(comp.ClosedPopup, ("owner", uid)), user.Value, user.Value); + _popup.PopupEntity(Loc.GetString(comp.ClosedPopup, ("owner", uid)), user.Value, user.Value); return true; } @@ -139,13 +155,13 @@ public void UpdateAppearance(EntityUid uid, OpenableComponent? comp = null, Appe if (!Resolve(uid, ref comp)) return; - Appearance.SetData(uid, OpenableVisuals.Opened, comp.Opened, appearance); + _appearance.SetData(uid, OpenableVisuals.Opened, comp.Opened, appearance); } /// /// Sets the opened field and updates open visuals. /// - public void SetOpen(EntityUid uid, bool opened = true, OpenableComponent? comp = null) + public void SetOpen(EntityUid uid, bool opened = true, OpenableComponent? comp = null, EntityUid? user = null) { if (!Resolve(uid, ref comp, false) || opened == comp.Opened) return; @@ -155,12 +171,12 @@ public void SetOpen(EntityUid uid, bool opened = true, OpenableComponent? comp = if (opened) { - var ev = new OpenableOpenedEvent(); + var ev = new OpenableOpenedEvent(user); RaiseLocalEvent(uid, ref ev); } else { - var ev = new OpenableClosedEvent(); + var ev = new OpenableClosedEvent(user); RaiseLocalEvent(uid, ref ev); } @@ -176,8 +192,8 @@ public bool TryOpen(EntityUid uid, OpenableComponent? comp = null, EntityUid? us if (!Resolve(uid, ref comp, false) || comp.Opened) return false; - SetOpen(uid, true, comp); - Audio.PlayPredicted(comp.Sound, uid, user); + SetOpen(uid, true, comp, user); + _audio.PlayPredicted(comp.Sound, uid, user); return true; } @@ -190,9 +206,9 @@ public bool TryClose(EntityUid uid, OpenableComponent? comp = null, EntityUid? u if (!Resolve(uid, ref comp, false) || !comp.Opened || !comp.Closeable) return false; - SetOpen(uid, false, comp); + SetOpen(uid, false, comp, user); if (comp.CloseSound != null) - Audio.PlayPredicted(comp.CloseSound, uid, user); + _audio.PlayPredicted(comp.CloseSound, uid, user); return true; } } @@ -201,10 +217,10 @@ public bool TryClose(EntityUid uid, OpenableComponent? comp = null, EntityUid? u /// Raised after an Openable is opened. /// [ByRefEvent] -public record struct OpenableOpenedEvent; +public record struct OpenableOpenedEvent(EntityUid? User = null); /// /// Raised after an Openable is closed. /// [ByRefEvent] -public record struct OpenableClosedEvent; +public record struct OpenableClosedEvent(EntityUid? User = null); diff --git a/Content.Shared/Nutrition/EntitySystems/PressurizedSolutionSystem.cs b/Content.Shared/Nutrition/EntitySystems/PressurizedSolutionSystem.cs new file mode 100644 index 0000000000..d63b8e7326 --- /dev/null +++ b/Content.Shared/Nutrition/EntitySystems/PressurizedSolutionSystem.cs @@ -0,0 +1,285 @@ +using Content.Shared.Chemistry.Reagent; +using Content.Shared.Chemistry.EntitySystems; +using Content.Shared.Hands.EntitySystems; +using Content.Shared.Nutrition.Components; +using Content.Shared.Throwing; +using Content.Shared.IdentityManagement; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Random; +using Robust.Shared.Timing; +using Robust.Shared.Prototypes; +using Robust.Shared.Network; +using Content.Shared.Fluids; +using Content.Shared.Popups; + +namespace Content.Shared.Nutrition.EntitySystems; + +public sealed partial class PressurizedSolutionSystem : EntitySystem +{ + [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!; + [Dependency] private readonly OpenableSystem _openable = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedHandsSystem _hands = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly SharedPuddleSystem _puddle = default!; + [Dependency] private readonly INetManager _net = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnShake); + SubscribeLocalEvent(OnOpened); + SubscribeLocalEvent(OnLand); + SubscribeLocalEvent(OnSolutionUpdate); + } + + /// + /// Helper method for checking if the solution's fizziness is high enough to spray. + /// is added to the actual fizziness for the comparison. + /// + private bool SprayCheck(Entity entity, float chanceMod = 0) + { + return Fizziness((entity, entity.Comp)) + chanceMod > entity.Comp.SprayFizzinessThresholdRoll; + } + + /// + /// Calculates how readily the contained solution becomes fizzy. + /// + private float SolutionFizzability(Entity entity) + { + if (!_solutionContainer.TryGetSolution(entity.Owner, entity.Comp.Solution, out var _, out var solution)) + return 0; + + // An empty solution can't be fizzy + if (solution.Volume <= 0) + return 0; + + var totalFizzability = 0f; + + // Check each reagent in the solution + foreach (var reagent in solution.Contents) + { + if (_prototypeManager.TryIndex(reagent.Reagent.Prototype, out ReagentPrototype? reagentProto) && reagentProto != null) + { + // What portion of the solution is this reagent? + var proportion = (float) (reagent.Quantity / solution.Volume); + totalFizzability += reagentProto.Fizziness * proportion; + } + } + + return totalFizzability; + } + + /// + /// Increases the fizziness level of the solution by the given amount, + /// scaled by the solution's fizzability. + /// 0 will result in no change, and 1 will maximize fizziness. + /// Also rerolls the spray threshold. + /// + private void AddFizziness(Entity entity, float amount) + { + var fizzability = SolutionFizzability(entity); + + // Can't add fizziness if the solution isn't fizzy + if (fizzability <= 0) + return; + + // Make sure nothing is preventing fizziness from being added + var attemptEv = new AttemptAddFizzinessEvent(entity, amount); + RaiseLocalEvent(entity, ref attemptEv); + if (attemptEv.Cancelled) + return; + + // Scale added fizziness by the solution's fizzability + amount *= fizzability; + + // Convert fizziness to time + var duration = amount * entity.Comp.FizzinessMaxDuration; + + // Add to the existing settle time, if one exists. Otherwise, add to the current time + var start = entity.Comp.FizzySettleTime > _timing.CurTime ? entity.Comp.FizzySettleTime : _timing.CurTime; + var newTime = start + duration; + + // Cap the maximum fizziness + var maxEnd = _timing.CurTime + entity.Comp.FizzinessMaxDuration; + if (newTime > maxEnd) + newTime = maxEnd; + + entity.Comp.FizzySettleTime = newTime; + + // Roll a new fizziness threshold + RollSprayThreshold(entity); + } + + /// + /// Helper method. Performs a . If it passes, calls . If it fails, . + /// + private void SprayOrAddFizziness(Entity entity, float chanceMod = 0, float fizzinessToAdd = 0, EntityUid? user = null) + { + if (SprayCheck(entity, chanceMod)) + TrySpray((entity, entity.Comp), user); + else + AddFizziness(entity, fizzinessToAdd); + } + + /// + /// Randomly generates a new spray threshold. + /// This is the value used to compare fizziness against when doing . + /// Since RNG will give different results between client and server, this is run on the server + /// and synced to the client by marking the component dirty. + /// We roll this in advance, rather than during , so that the value (hopefully) + /// has time to get synced to the client, so we can try be accurate with prediction. + /// + private void RollSprayThreshold(Entity entity) + { + // Can't predict random, so we wait for the server to tell us + if (!_net.IsServer) + return; + + entity.Comp.SprayFizzinessThresholdRoll = _random.NextFloat(); + Dirty(entity, entity.Comp); + } + + #region Public API + + /// + /// Does the entity contain a solution capable of being fizzy? + /// + public bool CanSpray(Entity entity) + { + if (!Resolve(entity, ref entity.Comp, false)) + return false; + + return SolutionFizzability((entity, entity.Comp)) > 0; + } + + /// + /// Attempts to spray the solution onto the given entity, or the ground if none is given. + /// Fails if the solution isn't able to be sprayed. + /// + public bool TrySpray(Entity entity, EntityUid? target = null) + { + if (!Resolve(entity, ref entity.Comp)) + return false; + + if (!CanSpray(entity)) + return false; + + if (!_solutionContainer.TryGetSolution(entity.Owner, entity.Comp.Solution, out var soln, out var interactions)) + return false; + + // If the container is openable, open it + _openable.SetOpen(entity, true); + + // Get the spray solution from the container + var solution = _solutionContainer.SplitSolution(soln.Value, interactions.Volume); + + // Spray the solution onto the ground and anyone nearby + if (TryComp(entity, out var transform)) + _puddle.TrySplashSpillAt(entity, transform.Coordinates, solution, out _, sound: false); + + var drinkName = Identity.Entity(entity, EntityManager); + + if (target != null) + { + var victimName = Identity.Entity(target.Value, EntityManager); + + var selfMessage = Loc.GetString(entity.Comp.SprayHolderMessageSelf, ("victim", victimName), ("drink", drinkName)); + var othersMessage = Loc.GetString(entity.Comp.SprayHolderMessageOthers, ("victim", victimName), ("drink", drinkName)); + _popup.PopupPredicted(selfMessage, othersMessage, target.Value, target.Value); + } + else + { + // Show a popup to everyone in PVS range + if (_timing.IsFirstTimePredicted) + _popup.PopupEntity(Loc.GetString(entity.Comp.SprayGroundMessage, ("drink", drinkName)), entity); + } + + _audio.PlayPredicted(entity.Comp.SpraySound, entity, target); + + // We just used all our fizziness, so clear it + TryClearFizziness(entity); + + return true; + } + + /// + /// What is the current fizziness level of the solution, from 0 to 1? + /// + public double Fizziness(Entity entity) + { + // No component means no fizz + if (!Resolve(entity, ref entity.Comp, false)) + return 0; + + // No negative fizziness + if (entity.Comp.FizzySettleTime <= _timing.CurTime) + return 0; + + var currentDuration = entity.Comp.FizzySettleTime - _timing.CurTime; + return Easings.InOutCubic((float) Math.Min(currentDuration / entity.Comp.FizzinessMaxDuration, 1)); + } + + /// + /// Attempts to clear any fizziness in the solution. + /// + /// Rolls a new spray threshold. + public void TryClearFizziness(Entity entity) + { + if (!Resolve(entity, ref entity.Comp)) + return; + + entity.Comp.FizzySettleTime = TimeSpan.Zero; + + // Roll a new fizziness threshold + RollSprayThreshold((entity, entity.Comp)); + } + + #endregion + + #region Event Handlers + private void OnMapInit(Entity entity, ref MapInitEvent args) + { + RollSprayThreshold(entity); + } + + private void OnOpened(Entity entity, ref OpenableOpenedEvent args) + { + // Make sure the opener is actually holding the drink + var held = args.User != null && _hands.IsHolding(args.User.Value, entity, out _); + + SprayOrAddFizziness(entity, entity.Comp.SprayChanceModOnOpened, -1, held ? args.User : null); + } + + private void OnShake(Entity entity, ref ShakeEvent args) + { + SprayOrAddFizziness(entity, entity.Comp.SprayChanceModOnShake, entity.Comp.FizzinessAddedOnShake, args.Shaker); + } + + private void OnLand(Entity entity, ref LandEvent args) + { + SprayOrAddFizziness(entity, entity.Comp.SprayChanceModOnLand, entity.Comp.FizzinessAddedOnLand); + } + + private void OnSolutionUpdate(Entity entity, ref SolutionContainerChangedEvent args) + { + if (args.SolutionId != entity.Comp.Solution) + return; + + // If the solution is no longer capable of being fizzy, clear any built up fizziness + if (SolutionFizzability(entity) <= 0) + TryClearFizziness((entity, entity.Comp)); + } + + #endregion +} + +[ByRefEvent] +public record struct AttemptAddFizzinessEvent(Entity Entity, float Amount) +{ + public bool Cancelled; +} diff --git a/Content.Shared/Nutrition/EntitySystems/ShakeableSystem.cs b/Content.Shared/Nutrition/EntitySystems/ShakeableSystem.cs new file mode 100644 index 0000000000..39890aada9 --- /dev/null +++ b/Content.Shared/Nutrition/EntitySystems/ShakeableSystem.cs @@ -0,0 +1,155 @@ +using Content.Shared.DoAfter; +using Content.Shared.Hands.EntitySystems; +using Content.Shared.IdentityManagement; +using Content.Shared.Nutrition.Components; +using Content.Shared.Popups; +using Content.Shared.Verbs; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Serialization; + +namespace Content.Shared.Nutrition.EntitySystems; + +public sealed partial class ShakeableSystem : EntitySystem +{ + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + [Dependency] private readonly SharedHandsSystem _hands = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent>(AddShakeVerb); + SubscribeLocalEvent(OnShakeDoAfter); + } + + private void AddShakeVerb(EntityUid uid, ShakeableComponent component, GetVerbsEvent args) + { + if (args.Hands == null || !args.CanAccess || !args.CanInteract) + return; + + if (!CanShake((uid, component), args.User)) + return; + + var shakeVerb = new Verb() + { + Text = Loc.GetString(component.ShakeVerbText), + Act = () => TryStartShake((args.Target, component), args.User) + }; + args.Verbs.Add(shakeVerb); + } + + private void OnShakeDoAfter(Entity entity, ref ShakeDoAfterEvent args) + { + if (args.Handled || args.Cancelled) + return; + + TryShake((entity, entity.Comp), args.User); + } + + /// + /// Attempts to start the doAfter to shake the entity. + /// Fails and returns false if the entity cannot be shaken for any reason. + /// If successful, displays popup messages, plays shake sound, and starts the doAfter. + /// + public bool TryStartShake(Entity entity, EntityUid user) + { + if (!Resolve(entity, ref entity.Comp)) + return false; + + if (!CanShake(entity, user)) + return false; + + var doAfterArgs = new DoAfterArgs(EntityManager, + user, + entity.Comp.ShakeDuration, + new ShakeDoAfterEvent(), + eventTarget: entity, + target: user, + used: entity) + { + NeedHand = true, + BreakOnDamage = true, + DistanceThreshold = 1, + MovementThreshold = 0.01f, + BreakOnHandChange = entity.Comp.RequireInHand, + }; + if (entity.Comp.RequireInHand) + doAfterArgs.BreakOnHandChange = true; + + if (!_doAfter.TryStartDoAfter(doAfterArgs)) + return false; + + var userName = Identity.Entity(user, EntityManager); + var shakeableName = Identity.Entity(entity, EntityManager); + + var selfMessage = Loc.GetString(entity.Comp.ShakePopupMessageSelf, ("user", userName), ("shakeable", shakeableName)); + var othersMessage = Loc.GetString(entity.Comp.ShakePopupMessageOthers, ("user", userName), ("shakeable", shakeableName)); + _popup.PopupPredicted(selfMessage, othersMessage, user, user); + + _audio.PlayPredicted(entity.Comp.ShakeSound, entity, user); + + return true; + } + + /// + /// Attempts to shake the entity, skipping the doAfter. + /// Fails and returns false if the entity cannot be shaken for any reason. + /// If successful, raises a ShakeEvent on the entity. + /// + public bool TryShake(Entity entity, EntityUid? user = null) + { + if (!Resolve(entity, ref entity.Comp)) + return false; + + if (!CanShake(entity, user)) + return false; + + var ev = new ShakeEvent(user); + RaiseLocalEvent(entity, ref ev); + + return true; + } + + + /// + /// Is it possible for the given user to shake the entity? + /// + public bool CanShake(Entity entity, EntityUid? user = null) + { + if (!Resolve(entity, ref entity.Comp, false)) + return false; + + // If required to be in hand, fail if the user is not holding this entity + if (user != null && entity.Comp.RequireInHand && !_hands.IsHolding(user.Value, entity, out _)) + return false; + + var attemptEv = new AttemptShakeEvent(); + RaiseLocalEvent(entity, ref attemptEv); + if (attemptEv.Cancelled) + return false; + return true; + } +} + +/// +/// Raised when a ShakeableComponent is shaken, after the doAfter completes. +/// +[ByRefEvent] +public record struct ShakeEvent(EntityUid? Shaker); + +/// +/// Raised when trying to shake a ShakeableComponent. If cancelled, the +/// entity will not be shaken. +/// +[ByRefEvent] +public record struct AttemptShakeEvent() +{ + public bool Cancelled; +} + +[Serializable, NetSerializable] +public sealed partial class ShakeDoAfterEvent : SimpleDoAfterEvent +{ +} diff --git a/Content.Shared/Nutrition/EntitySystems/SharedCreamPieSystem.cs b/Content.Shared/Nutrition/EntitySystems/SharedCreamPieSystem.cs index bd7251b943..c61e8e5ba4 100644 --- a/Content.Shared/Nutrition/EntitySystems/SharedCreamPieSystem.cs +++ b/Content.Shared/Nutrition/EntitySystems/SharedCreamPieSystem.cs @@ -1,6 +1,7 @@ using Content.Shared.Nutrition.Components; using Content.Shared.Stunnable; using Content.Shared.Throwing; +using Content.Shared.Mood; using JetBrains.Annotations; namespace Content.Shared.Nutrition.EntitySystems @@ -44,6 +45,11 @@ public void SetCreamPied(EntityUid uid, CreamPiedComponent creamPied, bool value { _appearance.SetData(uid, CreamPiedVisuals.Creamed, value, appearance); } + + if (value) + RaiseLocalEvent(uid, new MoodEffectEvent("Creampied")); + else + RaiseLocalEvent(uid, new MoodRemoveEffectEvent("Creampied")); } private void OnCreamPieLand(EntityUid uid, CreamPieComponent component, ref LandEvent args) diff --git a/Content.Shared/Nutrition/EntitySystems/SharedDrinkSystem.cs b/Content.Shared/Nutrition/EntitySystems/SharedDrinkSystem.cs new file mode 100644 index 0000000000..7cae3b9208 --- /dev/null +++ b/Content.Shared/Nutrition/EntitySystems/SharedDrinkSystem.cs @@ -0,0 +1,90 @@ +using Content.Shared.Chemistry.Components.SolutionManager; +using Content.Shared.Chemistry.EntitySystems; +using Content.Shared.Examine; +using Content.Shared.FixedPoint; +using Content.Shared.Nutrition.Components; + +namespace Content.Shared.Nutrition.EntitySystems; + +public abstract partial class SharedDrinkSystem : EntitySystem +{ + [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!; + [Dependency] private readonly OpenableSystem _openable = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnAttemptShake); + SubscribeLocalEvent(OnExamined); + } + + protected void OnAttemptShake(Entity entity, ref AttemptShakeEvent args) + { + if (IsEmpty(entity, entity.Comp)) + args.Cancelled = true; + } + + protected void OnExamined(Entity entity, ref ExaminedEvent args) + { + TryComp(entity, out var openable); + if (_openable.IsClosed(entity.Owner, null, openable) || !args.IsInDetailsRange || !entity.Comp.Examinable) + return; + + var empty = IsEmpty(entity, entity.Comp); + if (empty) + { + args.PushMarkup(Loc.GetString("drink-component-on-examine-is-empty")); + return; + } + + if (HasComp(entity)) + { + //provide exact measurement for beakers + args.PushText(Loc.GetString("drink-component-on-examine-exact-volume", ("amount", DrinkVolume(entity, entity.Comp)))); + } + else + { + //general approximation + var remainingString = (int) _solutionContainer.PercentFull(entity) switch + { + 100 => "drink-component-on-examine-is-full", + > 66 => "drink-component-on-examine-is-mostly-full", + > 33 => HalfEmptyOrHalfFull(args), + _ => "drink-component-on-examine-is-mostly-empty", + }; + args.PushMarkup(Loc.GetString(remainingString)); + } + } + + protected FixedPoint2 DrinkVolume(EntityUid uid, DrinkComponent? component = null) + { + if (!Resolve(uid, ref component)) + return FixedPoint2.Zero; + + if (!_solutionContainer.TryGetSolution(uid, component.Solution, out _, out var sol)) + return FixedPoint2.Zero; + + return sol.Volume; + } + + protected bool IsEmpty(EntityUid uid, DrinkComponent? component = null) + { + if (!Resolve(uid, ref component)) + return true; + + return DrinkVolume(uid, component) <= 0; + } + + // some see half empty, and others see half full + private string HalfEmptyOrHalfFull(ExaminedEvent args) + { + string remainingString = "drink-component-on-examine-is-half-full"; + + if (TryComp(args.Examiner, out var examiner) && examiner.EntityName.Length > 0 + && string.Compare(examiner.EntityName.Substring(0, 1), "m", StringComparison.InvariantCultureIgnoreCase) > 0) + remainingString = "drink-component-on-examine-is-half-empty"; + + return remainingString; + } +} diff --git a/Content.Shared/Nutrition/EntitySystems/SharedFoodGuideDataSystem.cs b/Content.Shared/Nutrition/EntitySystems/SharedFoodGuideDataSystem.cs new file mode 100644 index 0000000000..c31776cd75 --- /dev/null +++ b/Content.Shared/Nutrition/EntitySystems/SharedFoodGuideDataSystem.cs @@ -0,0 +1,158 @@ +using System.Linq; +using Content.Shared.Chemistry.Reaction; +using Content.Shared.Chemistry.Reagent; +using Content.Shared.Kitchen; +using Content.Shared.Nutrition.Components; +using Content.Shared.Storage; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; + +namespace Content.Client.Chemistry.EntitySystems; + +public abstract class SharedFoodGuideDataSystem : EntitySystem +{ + public List Registry = new(); +} + +[Serializable, NetSerializable] +public sealed class FoodGuideRegistryChangedEvent : EntityEventArgs +{ + [DataField] + public List Changeset; + + public FoodGuideRegistryChangedEvent(List changeset) + { + Changeset = changeset; + } +} + +[DataDefinition, Serializable, NetSerializable] +public partial struct FoodGuideEntry +{ + [DataField] + public EntProtoId Result; + + [DataField] + public string Identifier; // Used for sorting + + [DataField] + public FoodSourceData[] Sources; + + [DataField] + public ReagentQuantity[] Composition; + + public FoodGuideEntry(EntProtoId result, string identifier, FoodSourceData[] sources, ReagentQuantity[] composition) + { + Result = result; + Identifier = identifier; + Sources = sources; + Composition = composition; + } +} + +[ImplicitDataDefinitionForInheritors, Serializable, NetSerializable] +public abstract partial class FoodSourceData +{ + /// + /// Number of products created from this source. Used for primary ordering. + /// + public abstract int OutputCount { get; } + + /// + /// A string used to distinguish different sources. Typically the name of the related entity. + /// + public string Identitier; + + public abstract bool IsSourceOf(EntProtoId food); +} + +[Serializable, NetSerializable] +public sealed partial class FoodButcheringData : FoodSourceData +{ + [DataField] + public EntProtoId Butchered; + + [DataField] + public ButcheringType Type; + + [DataField] + public List Results; + + public override int OutputCount => Results.Count; + + public FoodButcheringData(EntityPrototype butchered, ButcherableComponent comp) + { + Identitier = butchered.Name; + Butchered = butchered.ID; + Type = comp.Type; + Results = comp.SpawnedEntities; + } + + public override bool IsSourceOf(EntProtoId food) => Results.Any(it => it.PrototypeId == food); +} + +[Serializable, NetSerializable] +public sealed partial class FoodSlicingData : FoodSourceData +{ + [DataField] + public EntProtoId Sliced, Result; + + [DataField] + private int _outputCount; + public override int OutputCount => _outputCount; + + public FoodSlicingData(EntityPrototype sliced, EntProtoId result, int outputCount) + { + Identitier = sliced.Name; + Sliced = sliced.ID; + Result = result; + _outputCount = outputCount; // Server-only + } + + public override bool IsSourceOf(EntProtoId food) => food == Result; +} + +[Serializable, NetSerializable] +public sealed partial class FoodRecipeData : FoodSourceData +{ + [DataField] + public ProtoId Recipe; + + [DataField] + public EntProtoId Result; + + public override int OutputCount => 1; + + public FoodRecipeData(FoodRecipePrototype proto) + { + Identitier = proto.Name; + Recipe = proto.ID; + Result = proto.Result; + } + + public override bool IsSourceOf(EntProtoId food) => food == Result; +} + +[Serializable, NetSerializable] +public sealed partial class FoodReactionData : FoodSourceData +{ + [DataField] + public ProtoId Reaction; + + [DataField] + public EntProtoId Result; + + [DataField] + private int _outputCount; + public override int OutputCount => _outputCount; + + public FoodReactionData(ReactionPrototype reaction, EntProtoId result, int outputCount) + { + Identitier = reaction.Name; + Reaction = reaction.ID; + Result = result; + _outputCount = outputCount; + } + + public override bool IsSourceOf(EntProtoId food) => food == Result; +} diff --git a/Content.Shared/Nutrition/EntitySystems/ThirstSystem.cs b/Content.Shared/Nutrition/EntitySystems/ThirstSystem.cs index 29218f5719..a068b19104 100644 --- a/Content.Shared/Nutrition/EntitySystems/ThirstSystem.cs +++ b/Content.Shared/Nutrition/EntitySystems/ThirstSystem.cs @@ -3,9 +3,15 @@ using Content.Shared.Movement.Systems; using Content.Shared.Nutrition.Components; using Content.Shared.Rejuvenate; +using Content.Shared.StatusIcon; using JetBrains.Annotations; +using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Timing; +using Robust.Shared.Utility; +using Content.Shared.Mood; +using Robust.Shared.Configuration; +using Content.Shared.CCVar; namespace Content.Shared.Nutrition.EntitySystems; @@ -13,15 +19,34 @@ namespace Content.Shared.Nutrition.EntitySystems; public sealed class ThirstSystem : EntitySystem { [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly IPrototypeManager _prototype = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly AlertsSystem _alerts = default!; [Dependency] private readonly MovementSpeedModifierSystem _movement = default!; [Dependency] private readonly SharedJetpackSystem _jetpack = default!; + [Dependency] private readonly IConfigurationManager _config = default!; + + [ValidatePrototypeId] + private const string ThirstIconOverhydratedId = "ThirstIconOverhydrated"; + + [ValidatePrototypeId] + private const string ThirstIconThirstyId = "ThirstIconThirsty"; + + [ValidatePrototypeId] + private const string ThirstIconParchedId = "ThirstIconParched"; + + private StatusIconPrototype? _thirstIconOverhydrated = null; + private StatusIconPrototype? _thirstIconThirsty = null; + private StatusIconPrototype? _thirstIconParched = null; public override void Initialize() { base.Initialize(); + DebugTools.Assert(_prototype.TryIndex(ThirstIconOverhydratedId, out _thirstIconOverhydrated) && + _prototype.TryIndex(ThirstIconThirstyId, out _thirstIconThirsty) && + _prototype.TryIndex(ThirstIconParchedId, out _thirstIconParched)); + SubscribeLocalEvent(OnRefreshMovespeed); SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(OnRejuvenate); @@ -49,7 +74,8 @@ private void OnMapInit(EntityUid uid, ThirstComponent component, MapInitEvent ar private void OnRefreshMovespeed(EntityUid uid, ThirstComponent component, RefreshMovementSpeedModifiersEvent args) { // TODO: This should really be taken care of somewhere else - if (_jetpack.IsUserFlying(uid)) + if (_config.GetCVar(CCVars.MoodEnabled) + || _jetpack.IsUserFlying(uid)) return; var mod = component.CurrentThirstThreshold <= ThirstThreshold.Parched ? 0.75f : 1.0f; @@ -107,10 +133,33 @@ private bool IsMovementThreshold(ThirstThreshold threshold) } } + public bool TryGetStatusIconPrototype(ThirstComponent component, out StatusIconPrototype? prototype) + { + switch (component.CurrentThirstThreshold) + { + case ThirstThreshold.OverHydrated: + prototype = _thirstIconOverhydrated; + return true; + + case ThirstThreshold.Thirsty: + prototype = _thirstIconThirsty; + return true; + + case ThirstThreshold.Parched: + prototype = _thirstIconParched; + return true; + + default: + prototype = null; + return false; + } + } + private void UpdateEffects(EntityUid uid, ThirstComponent component) { - if (IsMovementThreshold(component.LastThirstThreshold) != IsMovementThreshold(component.CurrentThirstThreshold) && - TryComp(uid, out MovementSpeedModifierComponent? movementSlowdownComponent)) + if (!_config.GetCVar(CCVars.MoodEnabled) + && IsMovementThreshold(component.LastThirstThreshold) != IsMovementThreshold(component.CurrentThirstThreshold) + && TryComp(uid, out MovementSpeedModifierComponent? movementSlowdownComponent)) { _movement.RefreshMovementSpeedModifiers(uid, movementSlowdownComponent); } @@ -125,6 +174,9 @@ private void UpdateEffects(EntityUid uid, ThirstComponent component) _alerts.ClearAlertCategory(uid, AlertCategory.Thirst); } + var ev = new MoodEffectEvent("Thirst" + component.CurrentThirstThreshold); + RaiseLocalEvent(uid, ev); + switch (component.CurrentThirstThreshold) { case ThirstThreshold.OverHydrated: diff --git a/Content.Shared/Nyanotrasen/Abilities/DogVisionComponent.cs b/Content.Shared/Nyanotrasen/Abilities/DogVisionComponent.cs deleted file mode 100644 index b0cf6cf0d1..0000000000 --- a/Content.Shared/Nyanotrasen/Abilities/DogVisionComponent.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Robust.Shared.GameStates; -namespace Content.Shared.Abilities; - -[RegisterComponent] -[NetworkedComponent] - -public sealed partial class DogVisionComponent : Component -{} diff --git a/Content.Shared/Nyanotrasen/Mail/MailDeliveryPoolPrototype.cs b/Content.Shared/Nyanotrasen/Mail/MailDeliveryPoolPrototype.cs index 544f489d28..4de374a164 100644 --- a/Content.Shared/Nyanotrasen/Mail/MailDeliveryPoolPrototype.cs +++ b/Content.Shared/Nyanotrasen/Mail/MailDeliveryPoolPrototype.cs @@ -6,7 +6,7 @@ namespace Content.Shared.Mail; /// Generic random weighting dataset to use. /// [Prototype("mailDeliveryPool")] -public sealed class MailDeliveryPoolPrototype : IPrototype +public sealed partial class MailDeliveryPoolPrototype : IPrototype { [IdDataFieldAttribute] public string ID { get; } = default!; diff --git a/Content.Shared/Objectives/ObjectiveInfo.cs b/Content.Shared/Objectives/ObjectiveInfo.cs index 689fe17e6c..0b7e7a15f6 100644 --- a/Content.Shared/Objectives/ObjectiveInfo.cs +++ b/Content.Shared/Objectives/ObjectiveInfo.cs @@ -1,3 +1,4 @@ +using Content.Shared.Objectives.Components; using Robust.Shared.Serialization; using Robust.Shared.Utility; diff --git a/Content.Shared/Overlays/SaturationScaleComponent.cs b/Content.Shared/Overlays/SaturationScaleComponent.cs new file mode 100644 index 0000000000..3318ddff6d --- /dev/null +++ b/Content.Shared/Overlays/SaturationScaleComponent.cs @@ -0,0 +1,6 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Overlays; + +[RegisterComponent, NetworkedComponent] +public sealed partial class SaturationScaleOverlayComponent : Component { } diff --git a/Content.Shared/Overlays/ShowCriminalRecordIconsComponent.cs b/Content.Shared/Overlays/ShowCriminalRecordIconsComponent.cs new file mode 100644 index 0000000000..cb0759be3e --- /dev/null +++ b/Content.Shared/Overlays/ShowCriminalRecordIconsComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Overlays; + +/// +/// This component allows you to see criminal record status of mobs. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class ShowCriminalRecordIconsComponent : Component { } diff --git a/Content.Shared/Overlays/ShowHungerIconsComponent.cs b/Content.Shared/Overlays/ShowHungerIconsComponent.cs index bf1fb2dc19..b3841bd80f 100644 --- a/Content.Shared/Overlays/ShowHungerIconsComponent.cs +++ b/Content.Shared/Overlays/ShowHungerIconsComponent.cs @@ -3,7 +3,7 @@ namespace Content.Shared.Overlays; /// -/// This component allows you to see the hungriness of mobs. +/// This component allows you to see the hungriness of mobs. /// [RegisterComponent, NetworkedComponent] public sealed partial class ShowHungerIconsComponent : Component { } diff --git a/Content.Shared/Overlays/ShowJobIconsComponent.cs b/Content.Shared/Overlays/ShowJobIconsComponent.cs new file mode 100644 index 0000000000..aae9739506 --- /dev/null +++ b/Content.Shared/Overlays/ShowJobIconsComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Overlays; + +/// +/// This component allows you to see job icons above mobs. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class ShowJobIconsComponent : Component { } diff --git a/Content.Shared/Overlays/ShowMindShieldIconsComponent.cs b/Content.Shared/Overlays/ShowMindShieldIconsComponent.cs new file mode 100644 index 0000000000..624d5ab8ef --- /dev/null +++ b/Content.Shared/Overlays/ShowMindShieldIconsComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Overlays; + +/// +/// This component allows you to see mindshield icons above mobs. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class ShowMindShieldIconsComponent : Component { } diff --git a/Content.Shared/Overlays/ShowSecurityIconsComponent.cs b/Content.Shared/Overlays/ShowSecurityIconsComponent.cs deleted file mode 100644 index ec268174d1..0000000000 --- a/Content.Shared/Overlays/ShowSecurityIconsComponent.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Robust.Shared.GameStates; - -namespace Content.Shared.Overlays; - -/// -/// This component allows you to see job icons above mobs. -/// -[RegisterComponent, NetworkedComponent] -public sealed partial class ShowSecurityIconsComponent : Component { } diff --git a/Content.Shared/Overlays/ShowSyndicateIconsComponent.cs b/Content.Shared/Overlays/ShowSyndicateIconsComponent.cs index 74a67db694..a63eae8e46 100644 --- a/Content.Shared/Overlays/ShowSyndicateIconsComponent.cs +++ b/Content.Shared/Overlays/ShowSyndicateIconsComponent.cs @@ -3,7 +3,7 @@ namespace Content.Shared.Overlays; /// -/// +/// This component allows you to identify members of the Syndicate faction. /// [RegisterComponent, NetworkedComponent] public sealed partial class ShowSyndicateIconsComponent : Component {} diff --git a/Content.Shared/Overlays/ShowThirstIconsComponent.cs b/Content.Shared/Overlays/ShowThirstIconsComponent.cs index 905ab07fe2..1914034e9e 100644 --- a/Content.Shared/Overlays/ShowThirstIconsComponent.cs +++ b/Content.Shared/Overlays/ShowThirstIconsComponent.cs @@ -3,7 +3,7 @@ namespace Content.Shared.Overlays; /// -/// This component allows you to see the thirstiness of mobs. +/// This component allows you to see the thirstiness of mobs. /// [RegisterComponent, NetworkedComponent] public sealed partial class ShowThirstIconsComponent : Component { } diff --git a/Content.Shared/Paint/PaintComponent.cs b/Content.Shared/Paint/PaintComponent.cs new file mode 100644 index 0000000000..dc35b8fd43 --- /dev/null +++ b/Content.Shared/Paint/PaintComponent.cs @@ -0,0 +1,46 @@ +using Content.Shared.Chemistry.Reagent; +using Content.Shared.FixedPoint; +using Robust.Shared.Audio; +using Content.Shared.Whitelist; +using Robust.Shared.Prototypes; +using Robust.Shared.GameStates; + +namespace Content.Shared.Paint; + +/// Entity when used on another entity will paint target entity +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +[Access(typeof(SharedPaintSystem))] +public sealed partial class PaintComponent : Component +{ + /// Noise made when paint gets applied + [DataField] + public SoundSpecifier Spray = new SoundPathSpecifier("/Audio/Effects/spray2.ogg"); + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public EntityWhitelist? Whitelist; + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public EntityWhitelist? Blacklist; + + /// How long the doafter will take + [DataField] + public int Delay = 2; + + [DataField, AutoNetworkedField] + public Color Color = Color.FromHex("#c62121"); + + /// Solution on the entity that contains the paint + [DataField] + public string Solution = "drink"; + + /// Reagent that will be used as paint + [DataField, AutoNetworkedField] + public ProtoId Reagent = "SpaceGlue"; + + /// Reagent consumption per use + [DataField] + public FixedPoint2 ConsumptionUnit = FixedPoint2.New(5); + + [DataField] + public TimeSpan DurationPerUnit = TimeSpan.FromSeconds(6); +} diff --git a/Content.Shared/Paint/PaintDoAfterEvent.cs b/Content.Shared/Paint/PaintDoAfterEvent.cs new file mode 100644 index 0000000000..a6f7b951ec --- /dev/null +++ b/Content.Shared/Paint/PaintDoAfterEvent.cs @@ -0,0 +1,7 @@ +using Content.Shared.DoAfter; +using Robust.Shared.Serialization; + +namespace Content.Shared.Paint; + +[Serializable, NetSerializable] +public sealed partial class PaintDoAfterEvent : SimpleDoAfterEvent; diff --git a/Content.Shared/Paint/PaintRemoverComponent.cs b/Content.Shared/Paint/PaintRemoverComponent.cs new file mode 100644 index 0000000000..9fee3c90cb --- /dev/null +++ b/Content.Shared/Paint/PaintRemoverComponent.cs @@ -0,0 +1,17 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Audio; + +namespace Content.Shared.Paint; + +/// Removes paint from an entity that was painted with spray paint +[RegisterComponent, NetworkedComponent] +[Access(typeof(PaintRemoverSystem))] +public sealed partial class PaintRemoverComponent : Component +{ + /// Sound played when target is cleaned + [DataField] + public SoundSpecifier Sound = new SoundPathSpecifier("/Audio/Effects/Fluids/watersplash.ogg"); + + [DataField] + public float CleanDelay = 2f; +} diff --git a/Content.Shared/Paint/PaintRemoverDoAfterEvent.cs b/Content.Shared/Paint/PaintRemoverDoAfterEvent.cs new file mode 100644 index 0000000000..ec2a2e0324 --- /dev/null +++ b/Content.Shared/Paint/PaintRemoverDoAfterEvent.cs @@ -0,0 +1,7 @@ +using Content.Shared.DoAfter; +using Robust.Shared.Serialization; + +namespace Content.Shared.Paint; + +[Serializable, NetSerializable] +public sealed partial class PaintRemoverDoAfterEvent : SimpleDoAfterEvent; diff --git a/Content.Shared/Paint/PaintRemoverSystem.cs b/Content.Shared/Paint/PaintRemoverSystem.cs new file mode 100644 index 0000000000..e2565e0f22 --- /dev/null +++ b/Content.Shared/Paint/PaintRemoverSystem.cs @@ -0,0 +1,96 @@ +using Content.Shared.Popups; +using Content.Shared.Interaction; +using Content.Shared.DoAfter; +using Content.Shared.Verbs; +using Content.Shared.Sprite; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Timing; + +namespace Content.Shared.Paint; + +public sealed class PaintRemoverSystem : SharedPaintSystem +{ + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!; + + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnInteract); + SubscribeLocalEvent(OnDoAfter); + SubscribeLocalEvent>(OnPaintRemoveVerb); + } + + + private void OnInteract(EntityUid uid, PaintRemoverComponent component, AfterInteractEvent args) + { + if (args.Handled + || !args.CanReach + || args.Target is not { Valid: true } target + || !HasComp(target)) + return; + + _doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, args.User, component.CleanDelay, new PaintRemoverDoAfterEvent(), uid, args.Target, uid) + { + BreakOnUserMove = true, + BreakOnTargetMove = true, + BreakOnDamage = true, + MovementThreshold = 1.0f, + }); + args.Handled = true; + } + + private void OnDoAfter(EntityUid uid, PaintRemoverComponent component, DoAfterEvent args) + { + if (args.Cancelled + || args.Handled + || args.Args.Target == null + || args.Target is not { Valid: true } target + || !TryComp(target, out PaintedComponent? paint)) + return; + + paint.Enabled = false; + _audio.PlayPredicted(component.Sound, target, args.User); + _popup.PopupClient(Loc.GetString("paint-removed", ("target", target)), args.User, args.User, PopupType.Medium); + _appearanceSystem.SetData(target, PaintVisuals.Painted, false); + RemComp(target); + Dirty(target, paint); + + args.Handled = true; + } + + private void OnPaintRemoveVerb(EntityUid uid, PaintRemoverComponent component, GetVerbsEvent args) + { + if (!args.CanInteract || !args.CanAccess) + return; + + var verb = new UtilityVerb() + { + Text = Loc.GetString("paint-remove-verb"), + Act = () => + { + _doAfter.TryStartDoAfter( + new DoAfterArgs( + EntityManager, + args.User, + component.CleanDelay, + new PaintRemoverDoAfterEvent(), + uid, + args.Target, + uid) + { + BreakOnUserMove = true, + BreakOnTargetMove = true, + BreakOnDamage = true, + MovementThreshold = 1.0f, + }); + }, + }; + + args.Verbs.Add(verb); + } +} diff --git a/Content.Shared/Paint/PaintedComponent.cs b/Content.Shared/Paint/PaintedComponent.cs new file mode 100644 index 0000000000..3d2deee45e --- /dev/null +++ b/Content.Shared/Paint/PaintedComponent.cs @@ -0,0 +1,29 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; + +namespace Content.Shared.Paint; + +/// Component applied to target entity when painted +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class PaintedComponent : Component +{ + [DataField, AutoNetworkedField] + public Color Color = Color.FromHex("#2cdbd5"); + + /// Used to remove the color when the component is removed + [DataField, AutoNetworkedField] + public Color BeforeColor; + + [DataField, AutoNetworkedField] + public bool Enabled; + + // Not using ProtoId because ShaderPrototype is in Robust.Client + [DataField, AutoNetworkedField] + public string ShaderName = "Greyscale"; +} + +[Serializable, NetSerializable] +public enum PaintVisuals : byte +{ + Painted, +} diff --git a/Content.Shared/Paint/SharedPaintSystem.cs b/Content.Shared/Paint/SharedPaintSystem.cs new file mode 100644 index 0000000000..4707e94277 --- /dev/null +++ b/Content.Shared/Paint/SharedPaintSystem.cs @@ -0,0 +1,6 @@ +namespace Content.Shared.Paint; + +public abstract class SharedPaintSystem : EntitySystem +{ + public virtual void UpdateAppearance(EntityUid uid, PaintedComponent? component = null) { } +} diff --git a/Content.Shared/Paper/SharedPaperComponent.cs b/Content.Shared/Paper/SharedPaperComponent.cs index dd87f9f907..f65a599e53 100644 --- a/Content.Shared/Paper/SharedPaperComponent.cs +++ b/Content.Shared/Paper/SharedPaperComponent.cs @@ -1,8 +1,10 @@ using Robust.Shared.Audio; +using Robust.Shared.GameStates; using Robust.Shared.Serialization; namespace Content.Shared.Paper; +[NetworkedComponent] public abstract partial class SharedPaperComponent : Component { /// diff --git a/Content.Shared/Parkstation/Traits/Components/NearsightedComponent.cs b/Content.Shared/Parkstation/Traits/Components/NearsightedComponent.cs deleted file mode 100644 index 08dd670df3..0000000000 --- a/Content.Shared/Parkstation/Traits/Components/NearsightedComponent.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Robust.Shared.GameStates; - -namespace Content.Shared.Parkstation.Traits.Components; - -/// -/// Owner entity cannot see well, without prescription glasses. -/// -[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] -public sealed partial class NearsightedComponent : Component -{ - /// - /// Distance from the edge of the screen to the center - /// - /// - /// I don't know how the distance is measured, 1 is very close to the center, 0 is maybe visible around the edge - /// - [DataField("radius"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] - public float Radius = 0.8f; - - /// - /// How dark the circle mask is from - /// - /// - /// I also don't know how this works, it only starts getting noticeably dark at 0.7, and is definitely noticeable at 0.9, 1 is black - /// - [DataField("alpha"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] - public float Alpha = 0.995f; - - /// - [DataField("equippedRadius"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] - public float EquippedRadius = 0.45f; - - /// - [DataField("equippedAlpha"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] - public float EquippedAlpha = 0.93f; - - /// - /// How long the lerp animation should go on for in seconds. - /// - [DataField("lerpDuration"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] - public float LerpDuration = 0.25f; - - /// - /// If true, uses the variables prefixed "Equipped" - /// If false, uses the variables without a prefix - /// - [ViewVariables(VVAccess.ReadWrite)] // Make the system shared if you want this networked, I don't wanna do that - public bool Active = false; -} diff --git a/Content.Shared/Payload/Components/PayloadTriggerComponent.cs b/Content.Shared/Payload/Components/PayloadTriggerComponent.cs index 6d3df41ac9..b064e91198 100644 --- a/Content.Shared/Payload/Components/PayloadTriggerComponent.cs +++ b/Content.Shared/Payload/Components/PayloadTriggerComponent.cs @@ -1,3 +1,4 @@ +using Content.Shared.Explosion.Components; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; diff --git a/Content.Shared/Physics/CollisionGroup.cs b/Content.Shared/Physics/CollisionGroup.cs index 1c10fefd5d..775ccb7c44 100644 --- a/Content.Shared/Physics/CollisionGroup.cs +++ b/Content.Shared/Physics/CollisionGroup.cs @@ -48,6 +48,9 @@ public enum CollisionGroup MachineLayer = Opaque | MidImpassable | LowImpassable | BulletImpassable, ConveyorMask = Impassable | MidImpassable | LowImpassable | DoorPassable, + // Crates + CrateMask = Impassable | HighImpassable | LowImpassable, + // Tables that SmallMobs can go under TableMask = Impassable | MidImpassable, TableLayer = MidImpassable, diff --git a/Content.Shared/Pinpointer/NavMapComponent.cs b/Content.Shared/Pinpointer/NavMapComponent.cs index 8c9979ba25..d77169d32e 100644 --- a/Content.Shared/Pinpointer/NavMapComponent.cs +++ b/Content.Shared/Pinpointer/NavMapComponent.cs @@ -1,9 +1,13 @@ +using System.Linq; +using Content.Shared.Atmos; using Robust.Shared.GameStates; +using Robust.Shared.Serialization; +using Robust.Shared.Timing; namespace Content.Shared.Pinpointer; /// -/// Used to store grid poly data to be used for UIs. +/// Used to store grid data to be used for UIs. /// [RegisterComponent, NetworkedComponent] public sealed partial class NavMapComponent : Component @@ -12,25 +16,47 @@ public sealed partial class NavMapComponent : Component * Don't need DataFields as this can be reconstructed */ + /// + /// Bitmasks that represent chunked tiles. + /// [ViewVariables] - public readonly Dictionary Chunks = new(); + public Dictionary Chunks = new(); - [ViewVariables] public readonly List Beacons = new(); - - [ViewVariables] public readonly List Airlocks = new(); + /// + /// List of station beacons. + /// + [ViewVariables] + public Dictionary Beacons = new(); } -public sealed class NavMapChunk +[Serializable, NetSerializable] +public sealed class NavMapChunk(Vector2i origin) { - public readonly Vector2i Origin; + /// + /// The chunk origin + /// + [ViewVariables] + public readonly Vector2i Origin = origin; + + /// + /// Array containing the chunk's data. The + /// + [ViewVariables] + public int[] TileData = new int[SharedNavMapSystem.ArraySize]; /// - /// Bitmask for tiles, 1 for occupied and 0 for empty. + /// The last game tick that the chunk was updated /// - public int TileData; + [NonSerialized] + public GameTick LastUpdate; +} - public NavMapChunk(Vector2i origin) - { - Origin = origin; - } +public enum NavMapChunkType : byte +{ + // Values represent bit shift offsets when retrieving data in the tile array. + Invalid = byte.MaxValue, + Floor = 0, // I believe floors have directional information for diagonal tiles? + Wall = SharedNavMapSystem.Directions, + Airlock = 2 * SharedNavMapSystem.Directions, } + diff --git a/Content.Shared/Pinpointer/SharedNavMapSystem.cs b/Content.Shared/Pinpointer/SharedNavMapSystem.cs index 17f86ac7e6..7c12321b5d 100644 --- a/Content.Shared/Pinpointer/SharedNavMapSystem.cs +++ b/Content.Shared/Pinpointer/SharedNavMapSystem.cs @@ -1,49 +1,187 @@ +using System.Diagnostics.CodeAnalysis; using System.Numerics; +using System.Runtime.CompilerServices; +using Content.Shared.Tag; +using Robust.Shared.GameStates; using Robust.Shared.Serialization; +using Robust.Shared.Timing; using Robust.Shared.Utility; namespace Content.Shared.Pinpointer; public abstract class SharedNavMapSystem : EntitySystem { - public const byte ChunkSize = 4; + public const int Categories = 3; + public const int Directions = 4; // Not directly tied to number of atmos directions - /// - /// Converts the chunk's tile into a bitflag for the slot. - /// - public static int GetFlag(Vector2i relativeTile) + public const int ChunkSize = 8; + public const int ArraySize = ChunkSize* ChunkSize; + + public const int AllDirMask = (1 << Directions) - 1; + public const int AirlockMask = AllDirMask << (int) NavMapChunkType.Airlock; + public const int WallMask = AllDirMask << (int) NavMapChunkType.Wall; + public const int FloorMask = AllDirMask << (int) NavMapChunkType.Floor; + + [Robust.Shared.IoC.Dependency] private readonly TagSystem _tagSystem = default!; + + private readonly string[] _wallTags = ["Wall", "Window"]; + private EntityQuery _doorQuery; + + public override void Initialize() { - return 1 << (relativeTile.X * ChunkSize + relativeTile.Y); + base.Initialize(); + + // Data handling events + SubscribeLocalEvent(OnGetState); + _doorQuery = GetEntityQuery(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetTileIndex(Vector2i relativeTile) + { + return relativeTile.X * ChunkSize + relativeTile.Y; } /// - /// Converts the chunk's tile into a bitflag for the slot. + /// Inverse of /// - public static Vector2i GetTile(int flag) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector2i GetTileFromIndex(int index) + { + var x = index / ChunkSize; + var y = index % ChunkSize; + return new Vector2i(x, y); + } + + public NavMapChunkType GetEntityType(EntityUid uid) { - var value = Math.Log2(flag); - var x = (int) value / ChunkSize; - var y = (int) value % ChunkSize; - var result = new Vector2i(x, y); + if (_doorQuery.HasComp(uid)) + return NavMapChunkType.Airlock; - DebugTools.Assert(GetFlag(result) == flag); + if (_tagSystem.HasAnyTag(uid, _wallTags)) + return NavMapChunkType.Wall; - return new Vector2i(x, y); + return NavMapChunkType.Invalid; } - [Serializable, NetSerializable] - protected sealed class NavMapComponentState : ComponentState + protected bool TryCreateNavMapBeaconData(EntityUid uid, NavMapBeaconComponent component, TransformComponent xform, MetaDataComponent meta, [NotNullWhen(true)] out NavMapBeacon? beaconData) + { + beaconData = null; + + if (!component.Enabled || xform.GridUid == null || !xform.Anchored) + return false; + + var name = component.Text; + if (string.IsNullOrEmpty(name)) + name = meta.EntityName; + + beaconData = new NavMapBeacon(meta.NetEntity, component.Color, name, xform.LocalPosition); + + return true; + } + + #region: Event handling + + private void OnGetState(EntityUid uid, NavMapComponent component, ref ComponentGetState args) { - public Dictionary TileData = new(); + Dictionary chunks; + + // Should this be a full component state or a delta-state? + if (args.FromTick <= component.CreationTick) + { + // Full state + chunks = new(component.Chunks.Count); + foreach (var (origin, chunk) in component.Chunks) + { + chunks.Add(origin, chunk.TileData); + } - public List Beacons = new(); + args.State = new NavMapState(chunks, component.Beacons); + return; + } - public List Airlocks = new(); + chunks = new(); + foreach (var (origin, chunk) in component.Chunks) + { + if (chunk.LastUpdate < args.FromTick) + continue; + + chunks.Add(origin, chunk.TileData); + } + + args.State = new NavMapDeltaState(chunks, component.Beacons, new(component.Chunks.Keys)); + } + + #endregion + + #region: System messages + + [Serializable, NetSerializable] + protected sealed class NavMapState( + Dictionary chunks, + Dictionary beacons) + : ComponentState + { + public Dictionary Chunks = chunks; + public Dictionary Beacons = beacons; } [Serializable, NetSerializable] - public readonly record struct NavMapBeacon(Color Color, string Text, Vector2 Position); + protected sealed class NavMapDeltaState( + Dictionary modifiedChunks, + Dictionary beacons, + HashSet allChunks) + : ComponentState, IComponentDeltaState + { + public Dictionary ModifiedChunks = modifiedChunks; + public Dictionary Beacons = beacons; + public HashSet AllChunks = allChunks; + + public void ApplyToFullState(NavMapState state) + { + foreach (var key in state.Chunks.Keys) + { + if (!AllChunks!.Contains(key)) + state.Chunks.Remove(key); + } + + foreach (var (index, data) in ModifiedChunks) + { + if (!state.Chunks.TryGetValue(index, out var stateValue)) + state.Chunks[index] = stateValue = new int[data.Length]; + + Array.Copy(data, stateValue, data.Length); + } + + state.Beacons.Clear(); + foreach (var (nuid, beacon) in Beacons) + { + state.Beacons.Add(nuid, beacon); + } + } + + public NavMapState CreateNewFullState(NavMapState state) + { + var chunks = new Dictionary(state.Chunks.Count); + foreach (var (index, data) in state.Chunks) + { + if (!AllChunks!.Contains(index)) + continue; + + var newData = chunks[index] = new int[ArraySize]; + + if (ModifiedChunks.TryGetValue(index, out var updatedData)) + Array.Copy(newData, updatedData, ArraySize); + else + Array.Copy(newData, data, ArraySize); + } + + return new NavMapState(chunks, new(Beacons)); + } + } [Serializable, NetSerializable] - public readonly record struct NavMapAirlock(Vector2 Position); + public record struct NavMapBeacon(NetEntity NetEnt, Color Color, string Text, Vector2 Position); + + #endregion } diff --git a/Content.Shared/Players/JobWhitelist/MsgJobWhitelist.cs b/Content.Shared/Players/JobWhitelist/MsgJobWhitelist.cs new file mode 100644 index 0000000000..27b0b6c614 --- /dev/null +++ b/Content.Shared/Players/JobWhitelist/MsgJobWhitelist.cs @@ -0,0 +1,33 @@ +using Lidgren.Network; +using Robust.Shared.Network; +using Robust.Shared.Serialization; + +namespace Content.Shared.Players.JobWhitelist; + +public sealed class MsgJobWhitelist : NetMessage +{ + public override MsgGroups MsgGroup => MsgGroups.EntityEvent; + + public HashSet Whitelist = new(); + + public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer) + { + var count = buffer.ReadVariableInt32(); + Whitelist.EnsureCapacity(count); + + for (var i = 0; i < count; i++) + { + Whitelist.Add(buffer.ReadString()); + } + } + + public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer) + { + buffer.WriteVariableInt32(Whitelist.Count); + + foreach (var ban in Whitelist) + { + buffer.Write(ban); + } + } +} diff --git a/Content.Shared/Players/PlayTimeTracking/PlayTimeTrackingShared.cs b/Content.Shared/Players/PlayTimeTracking/PlayTimeTrackingShared.cs index e300524d87..ccaf9c17dd 100644 --- a/Content.Shared/Players/PlayTimeTracking/PlayTimeTrackingShared.cs +++ b/Content.Shared/Players/PlayTimeTracking/PlayTimeTrackingShared.cs @@ -1,9 +1,18 @@ -namespace Content.Shared.Players.PlayTimeTracking; +using Content.Shared.Dataset; + +namespace Content.Shared.Players.PlayTimeTracking; public static class PlayTimeTrackingShared { /// /// The prototype ID of the play time tracker that represents overall playtime, i.e. not tied to any one role. /// + [ValidatePrototypeId] public const string TrackerOverall = "Overall"; + + /// + /// The prototype ID of the play time tracker that represents admin time, when a player is in game as admin. + /// + [ValidatePrototypeId] + public const string TrackerAdmin = "Admin"; } diff --git a/Content.Shared/Plunger/Systems/PlungerSystem.cs b/Content.Shared/Plunger/Systems/PlungerSystem.cs index 192a018564..6e07657941 100644 --- a/Content.Shared/Plunger/Systems/PlungerSystem.cs +++ b/Content.Shared/Plunger/Systems/PlungerSystem.cs @@ -17,8 +17,8 @@ namespace Content.Shared.Plunger.Systems; /// public sealed class PlungerSystem : EntitySystem { - [Dependency] protected readonly IPrototypeManager _proto = default!; - [Dependency] protected readonly IRobustRandom _random = default!; + [Dependency] private readonly IPrototypeManager _proto = default!; + [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; diff --git a/Content.Shared/Polymorph/Components/ChameleonDisguiseComponent.cs b/Content.Shared/Polymorph/Components/ChameleonDisguiseComponent.cs new file mode 100644 index 0000000000..2b9fba7b39 --- /dev/null +++ b/Content.Shared/Polymorph/Components/ChameleonDisguiseComponent.cs @@ -0,0 +1,25 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Polymorph.Components; + +/// +/// Component added to disguise entities. +/// Used by client to copy over appearance from the disguise's source entity. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)] +public sealed partial class ChameleonDisguiseComponent : Component +{ + /// + /// The disguise source entity for copying the sprite. + /// + [DataField, AutoNetworkedField] + public EntityUid SourceEntity; + + /// + /// The source entity's prototype. + /// Used as a fallback if the source entity was deleted. + /// + [DataField, AutoNetworkedField] + public EntProtoId? SourceProto; +} diff --git a/Content.Shared/Polymorph/Components/ChameleonProjectorComponent.cs b/Content.Shared/Polymorph/Components/ChameleonProjectorComponent.cs new file mode 100644 index 0000000000..239b5236f2 --- /dev/null +++ b/Content.Shared/Polymorph/Components/ChameleonProjectorComponent.cs @@ -0,0 +1,68 @@ +using Content.Shared.Polymorph; +using Content.Shared.Polymorph.Systems; +using Content.Shared.Whitelist; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Polymorph.Components; + +/// +/// A chameleon projector polymorphs you into a clicked entity, then polymorphs back when clicked on or destroyed. +/// This creates a new dummy polymorph entity and copies the appearance over. +/// +[RegisterComponent, Access(typeof(SharedChameleonProjectorSystem))] +public sealed partial class ChameleonProjectorComponent : Component +{ + /// + /// If non-null, whitelist for valid entities to disguise as. + /// + [DataField(required: true)] + public EntityWhitelist? Whitelist; + + /// + /// If non-null, blacklist that prevents entities from being used even if they are in the whitelist. + /// + [DataField(required: true)] + public EntityWhitelist? Blacklist; + + /// + /// Polymorph configuration for the disguise entity. + /// + [DataField(required: true)] + public PolymorphConfiguration Polymorph = new(); + + /// + /// Action for disabling your disguise's rotation. + /// + [DataField] + public EntProtoId NoRotAction = "ActionDisguiseNoRot"; + + /// + /// Action for anchoring your disguise in place. + /// + [DataField] + public EntProtoId AnchorAction = "ActionDisguiseAnchor"; + + /// + /// Minimum health to give the disguise. + /// + [DataField] + public float MinHealth = 1f; + + /// + /// Maximum health to give the disguise, health scales with mass. + /// + [DataField] + public float MaxHealth = 100f; + + /// + /// Popup shown to the user when they try to disguise as an invalid entity. + /// + [DataField] + public LocId InvalidPopup = "chameleon-projector-invalid"; + + /// + /// Popup shown to the user when they disguise as a valid entity. + /// + [DataField] + public LocId SuccessPopup = "chameleon-projector-success"; +} diff --git a/Content.Shared/Polymorph/PolymorphPrototype.cs b/Content.Shared/Polymorph/PolymorphPrototype.cs index 6d010711d2..e1bc3b0ba0 100644 --- a/Content.Shared/Polymorph/PolymorphPrototype.cs +++ b/Content.Shared/Polymorph/PolymorphPrototype.cs @@ -115,6 +115,17 @@ public sealed partial record PolymorphConfiguration [DataField(serverOnly: true)] [ViewVariables(VVAccess.ReadWrite)] public TimeSpan Cooldown = TimeSpan.Zero; + + /// + /// The exact names of components to copy over when this polymorph is applied. + /// + [DataField(serverOnly: true)] + public HashSet CopiedComponents = new() + { + "LanguageKnowledge", + "LanguageSpeaker", + "Grammar" + }; } public enum PolymorphInventoryChange : byte diff --git a/Content.Shared/Polymorph/Systems/SharedChameleonProjectorSystem.cs b/Content.Shared/Polymorph/Systems/SharedChameleonProjectorSystem.cs new file mode 100644 index 0000000000..c1abfc526f --- /dev/null +++ b/Content.Shared/Polymorph/Systems/SharedChameleonProjectorSystem.cs @@ -0,0 +1,113 @@ +using Content.Shared.Actions; +using Content.Shared.Interaction; +using Content.Shared.Polymorph; +using Content.Shared.Polymorph.Components; +using Content.Shared.Popups; +using Robust.Shared.Serialization.Manager; +using Robust.Shared.Prototypes; +using System.Diagnostics.CodeAnalysis; + +namespace Content.Shared.Polymorph.Systems; + +/// +/// Handles whitelist/blacklist checking. +/// Actual polymorphing and deactivation is done serverside. +/// +public abstract class SharedChameleonProjectorSystem : EntitySystem +{ + [Dependency] private readonly IPrototypeManager _proto = default!; + [Dependency] private readonly ISerializationManager _serMan = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnInteract); + } + + private void OnInteract(Entity ent, ref AfterInteractEvent args) + { + if (!args.CanReach || args.Target is not {} target) + return; + + var user = args.User; + args.Handled = true; + + if (IsInvalid(ent.Comp, target)) + { + _popup.PopupClient(Loc.GetString(ent.Comp.InvalidPopup), target, user); + return; + } + + _popup.PopupClient(Loc.GetString(ent.Comp.SuccessPopup), target, user); + Disguise(ent.Comp, user, target); + } + + /// + /// Returns true if an entity cannot be used as a disguise. + /// + public bool IsInvalid(ChameleonProjectorComponent comp, EntityUid target) + { + return (comp.Whitelist?.IsValid(target, EntityManager) == false) + || (comp.Blacklist?.IsValid(target, EntityManager) == true); + } + + /// + /// On server, polymorphs the user into an entity and sets up the disguise. + /// + public virtual void Disguise(ChameleonProjectorComponent comp, EntityUid user, EntityUid entity) + { + } + + /// + /// Copy a component from the source entity/prototype to the disguise entity. + /// + /// + /// This would probably be a good thing to add to engine in the future. + /// + protected bool CopyComp(Entity ent) where T: Component, new() + { + if (!GetSrcComp(ent.Comp, out var src)) + return true; + + // remove then re-add to prevent a funny + RemComp(ent); + var dest = AddComp(ent); + _serMan.CopyTo(src, ref dest, notNullableOverride: true); + Dirty(ent, dest); + return false; + } + + /// + /// Try to get a single component from the source entity/prototype. + /// + private bool GetSrcComp(ChameleonDisguiseComponent comp, [NotNullWhen(true)] out T? src) where T: Component + { + src = null; + if (TryComp(comp.SourceEntity, out src)) + return true; + + if (comp.SourceProto is not {} protoId) + return false; + + if (!_proto.TryIndex(protoId, out var proto)) + return false; + + return proto.TryGetComponent(out src); + } +} + +/// +/// Action event for toggling transform NoRot on a disguise. +/// +public sealed partial class DisguiseToggleNoRotEvent : InstantActionEvent +{ +} + +/// +/// Action event for toggling transform Anchored on a disguise. +/// +public sealed partial class DisguiseToggleAnchoredEvent : InstantActionEvent +{ +} diff --git a/Content.Shared/Popups/SharedPopupSystem.cs b/Content.Shared/Popups/SharedPopupSystem.cs index 10e8ca9be1..38d2030cd5 100644 --- a/Content.Shared/Popups/SharedPopupSystem.cs +++ b/Content.Shared/Popups/SharedPopupSystem.cs @@ -82,12 +82,24 @@ public abstract class SharedPopupSystem : EntitySystem ///
public abstract void PopupEntity(string? message, EntityUid uid, Filter filter, bool recordReplay, PopupType type = PopupType.Small); + /// + /// Variant of that only runs on the client, outside of prediction. + /// Useful for shared code that is always ran by both sides to avoid duplicate popups. + /// + public abstract void PopupClient(string? message, EntityUid? recipient, PopupType type = PopupType.Small); + /// /// Variant of that only runs on the client, outside of prediction. /// Useful for shared code that is always ran by both sides to avoid duplicate popups. /// public abstract void PopupClient(string? message, EntityUid uid, EntityUid? recipient, PopupType type = PopupType.Small); + /// + /// Variant of that only runs on the client, outside of prediction. + /// Useful for shared code that is always ran by both sides to avoid duplicate popups. + /// + public abstract void PopupClient(string? message, EntityCoordinates coordinates, EntityUid? recipient, PopupType type = PopupType.Small); + /// /// Variant of for use with prediction. The local client will show /// the popup to the recipient, and the server will show it to every other player in PVS range. If recipient is null, the local client diff --git a/Content.Shared/Power/Components/ActivatableUIRequiresPowerComponent.cs b/Content.Shared/Power/Components/ActivatableUIRequiresPowerComponent.cs new file mode 100644 index 0000000000..af193c8dfc --- /dev/null +++ b/Content.Shared/Power/Components/ActivatableUIRequiresPowerComponent.cs @@ -0,0 +1,8 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Power.Components; + +[RegisterComponent, NetworkedComponent] +public sealed partial class ActivatableUIRequiresPowerComponent : Component +{ +} diff --git a/Content.Shared/Power/SharedPowerMonitoringConsoleSystem.cs b/Content.Shared/Power/SharedPowerMonitoringConsoleSystem.cs index dc4af23c23..749f0233aa 100644 --- a/Content.Shared/Power/SharedPowerMonitoringConsoleSystem.cs +++ b/Content.Shared/Power/SharedPowerMonitoringConsoleSystem.cs @@ -1,3 +1,4 @@ +using System.Runtime.CompilerServices; using JetBrains.Annotations; namespace Content.Shared.Power; @@ -5,4 +6,23 @@ namespace Content.Shared.Power; [UsedImplicitly] public abstract class SharedPowerMonitoringConsoleSystem : EntitySystem { + // Chunk size is limited as we require ChunkSize^2 <= 32 (number of bits in an int) + public const int ChunkSize = 5; + + /// + /// Converts the chunk's tile into a bitflag for the slot. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetFlag(Vector2i relativeTile) + { + return 1 << (relativeTile.X * ChunkSize + relativeTile.Y); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector2i GetTileFromIndex(int index) + { + var x = index / ChunkSize; + var y = index % ChunkSize; + return new Vector2i(x, y); + } } diff --git a/Content.Shared/Preferences/HumanoidCharacterProfile.cs b/Content.Shared/Preferences/HumanoidCharacterProfile.cs index 79f8fd10fd..4feb193a65 100644 --- a/Content.Shared/Preferences/HumanoidCharacterProfile.cs +++ b/Content.Shared/Preferences/HumanoidCharacterProfile.cs @@ -15,690 +15,515 @@ using Robust.Shared.Serialization; using Robust.Shared.Utility; -namespace Content.Shared.Preferences -{ - /// - /// Character profile. Looks immutable, but uses non-immutable semantics internally for serialization/code sanity purposes. - /// - [DataDefinition] - [Serializable, NetSerializable] - public sealed partial class HumanoidCharacterProfile : ICharacterProfile - { - public const int MaxNameLength = 32; - public const int MaxDescLength = 512; - - private readonly Dictionary _jobPriorities; - private readonly List _antagPreferences; - private readonly List _traitPreferences; - private readonly List _loadoutPreferences; - - private HumanoidCharacterProfile( - string name, - string flavortext, - string species, - float height, - float width, - int age, - Sex sex, - Gender gender, - HumanoidCharacterAppearance appearance, - ClothingPreference clothing, - BackpackPreference backpack, - SpawnPriorityPreference spawnPriority, - Dictionary jobPriorities, - PreferenceUnavailableMode preferenceUnavailable, - List antagPreferences, - List traitPreferences, - List loadoutPreferences) - { - Name = name; - FlavorText = flavortext; - Species = species; - Height = height; - Width = width; - Age = age; - Sex = sex; - Gender = gender; - Appearance = appearance; - Clothing = clothing; - Backpack = backpack; - SpawnPriority = spawnPriority; - _jobPriorities = jobPriorities; - PreferenceUnavailable = preferenceUnavailable; - _antagPreferences = antagPreferences; - _traitPreferences = traitPreferences; - _loadoutPreferences = loadoutPreferences; - } - - /// Copy constructor but with overridable references (to prevent useless copies) - private HumanoidCharacterProfile( - HumanoidCharacterProfile other, - Dictionary jobPriorities, - List antagPreferences, - List traitPreferences, - List loadoutPreferences) - : this(other.Name, other.FlavorText, other.Species, other.Height, other.Width, other.Age, other.Sex, other.Gender, other.Appearance, - other.Clothing, other.Backpack, other.SpawnPriority, jobPriorities, other.PreferenceUnavailable, - antagPreferences, traitPreferences, loadoutPreferences) - { - } +namespace Content.Shared.Preferences; - /// Copy constructor - private HumanoidCharacterProfile(HumanoidCharacterProfile other) - : this(other, new Dictionary(other.JobPriorities), - new List(other.AntagPreferences), new List(other.TraitPreferences), - new List(other.LoadoutPreferences)) - { - } - - public HumanoidCharacterProfile( - string name, - string flavortext, - string species, - float height, - float width, - int age, - Sex sex, - Gender gender, - HumanoidCharacterAppearance appearance, - ClothingPreference clothing, - BackpackPreference backpack, - SpawnPriorityPreference spawnPriority, - IReadOnlyDictionary jobPriorities, - PreferenceUnavailableMode preferenceUnavailable, - IReadOnlyList antagPreferences, - IReadOnlyList traitPreferences, - IReadOnlyList loadoutPreferences) - : this(name, flavortext, species, height, width, age, sex, gender, appearance, clothing, backpack, spawnPriority, - new Dictionary(jobPriorities), preferenceUnavailable, - new List(antagPreferences), new List(traitPreferences), - new List(loadoutPreferences)) - { - } - - /// - /// Get the default humanoid character profile, using internal constant values. - /// Defaults to for the species. - /// - /// - public HumanoidCharacterProfile() : this( - "John Doe", - "", - SharedHumanoidAppearanceSystem.DefaultSpecies, - 1f, - 1f, - 18, - Sex.Male, - Gender.Male, - new HumanoidCharacterAppearance(), - ClothingPreference.Jumpsuit, - BackpackPreference.Backpack, - SpawnPriorityPreference.None, - new Dictionary - { - {SharedGameTicker.FallbackOverflowJob, JobPriority.High} - }, - PreferenceUnavailableMode.SpawnAsOverflow, - new List(), - new List(), - new List()) - { - } +/// Character profile. Looks immutable, but uses non-immutable semantics internally for serialization/code sanity purposes +[DataDefinition] +[Serializable, NetSerializable] +public sealed partial class HumanoidCharacterProfile : ICharacterProfile +{ + private static readonly Regex RestrictedNameRegex = new("[^A-Z,a-z,0-9, -]"); + private static readonly Regex ICNameCaseRegex = new(@"^(?\w)|\b(?\w)(?=\w*$)"); - /// - /// Return a default character profile, based on species. - /// - /// The species to use in this default profile. The default species is . - /// Humanoid character profile with default settings. - public static HumanoidCharacterProfile DefaultWithSpecies(string species = SharedHumanoidAppearanceSystem.DefaultSpecies) - { - return new( - "John Doe", - "", - species, - 1f, - 1f, - 18, - Sex.Male, - Gender.Male, - HumanoidCharacterAppearance.DefaultWithSpecies(species), - ClothingPreference.Jumpsuit, - BackpackPreference.Backpack, - SpawnPriorityPreference.None, - new Dictionary - { - {SharedGameTicker.FallbackOverflowJob, JobPriority.High} - }, - PreferenceUnavailableMode.SpawnAsOverflow, - new List(), - new List(), - new List()); - } + public const int MaxNameLength = 64; + public const int MaxDescLength = 1024; - // TODO: This should eventually not be a visual change only. - public static HumanoidCharacterProfile Random(HashSet? ignoredSpecies = null) + /// Job preferences for initial spawn + [DataField] + private Dictionary _jobPriorities = new() + { { - var prototypeManager = IoCManager.Resolve(); - var random = IoCManager.Resolve(); + SharedGameTicker.FallbackOverflowJob, JobPriority.High + }, + }; + + /// Antags we have opted in to + [DataField] + private HashSet _antagPreferences = new(); + + /// Enabled traits + [DataField] + private HashSet _traitPreferences = new(); + + /// + public HashSet LoadoutPreferences => _loadoutPreferences; + + [DataField] + private HashSet _loadoutPreferences = new(); + + [DataField] + public string Name { get; set; } = "John Doe"; + + /// Detailed text that can appear for the character if is enabled + [DataField] + public string FlavorText { get; set; } = string.Empty; + + /// Associated for this profile + [DataField] + public string Species { get; set; } = SharedHumanoidAppearanceSystem.DefaultSpecies; + + [DataField] + public string Customspeciename { get; set; } = ""; + + [DataField] + public float Height { get; private set; } + + [DataField] + public float Width { get; private set; } + + [DataField] + public int Age { get; set; } = 18; + + [DataField] + public Sex Sex { get; private set; } = Sex.Male; + + [DataField] + public Gender Gender { get; private set; } = Gender.Male; + + /// + public ICharacterAppearance CharacterAppearance => Appearance; + + /// Stores markings, eye colors, etc for the profile + [DataField] + public HumanoidCharacterAppearance Appearance { get; set; } = new(); + + [DataField] + public ClothingPreference Clothing { get; set; } + [DataField] + public BackpackPreference Backpack { get; set; } + + /// When spawning into a round what's the preferred spot to spawn + [DataField] + public SpawnPriorityPreference SpawnPriority { get; private set; } = SpawnPriorityPreference.None; + + /// + public IReadOnlyDictionary JobPriorities => _jobPriorities; + + /// + public IReadOnlySet AntagPreferences => _antagPreferences; + + /// + public IReadOnlySet TraitPreferences => _traitPreferences; + + /// If we're unable to get one of our preferred jobs do we spawn as a fallback job or do we stay in lobby + [DataField] + public PreferenceUnavailableMode PreferenceUnavailable { get; private set; } = + PreferenceUnavailableMode.SpawnAsOverflow; + + public HumanoidCharacterProfile( + string name, + string flavortext, + string species, + string customspeciename, + float height, + float width, + int age, + Sex sex, + Gender gender, + HumanoidCharacterAppearance appearance, + SpawnPriorityPreference spawnPriority, + Dictionary jobPriorities, + ClothingPreference clothing, + BackpackPreference backpack, + PreferenceUnavailableMode preferenceUnavailable, + HashSet antagPreferences, + HashSet traitPreferences, + HashSet loadoutPreferences) + { + Name = name; + FlavorText = flavortext; + Species = species; + Customspeciename = customspeciename; + Height = height; + Width = width; + Age = age; + Sex = sex; + Gender = gender; + Appearance = appearance; + SpawnPriority = spawnPriority; + _jobPriorities = jobPriorities; + Clothing = clothing; + Backpack = backpack; + PreferenceUnavailable = preferenceUnavailable; + _antagPreferences = antagPreferences; + _traitPreferences = traitPreferences; + _loadoutPreferences = loadoutPreferences; + } - var species = random.Pick(prototypeManager - .EnumeratePrototypes() - .Where(x => ignoredSpecies == null ? x.RoundStart : x.RoundStart && !ignoredSpecies.Contains(x.ID)) - .ToArray() - ).ID; + /// Copy constructor + public HumanoidCharacterProfile(HumanoidCharacterProfile other) + : this( + other.Name, + other.FlavorText, + other.Species, + other.Customspeciename, + other.Height, + other.Width, + other.Age, + other.Sex, + other.Gender, + other.Appearance.Clone(), + other.SpawnPriority, + new Dictionary(other.JobPriorities), + other.Clothing, + other.Backpack, + other.PreferenceUnavailable, + new HashSet(other.AntagPreferences), + new HashSet(other.TraitPreferences), + new HashSet(other.LoadoutPreferences)) + { + } - return RandomWithSpecies(species); - } + /// + /// Get the default humanoid character profile, using internal constant values. + /// Defaults to for the species. + /// + /// + public HumanoidCharacterProfile() + { + } - public static HumanoidCharacterProfile RandomWithSpecies(string species = SharedHumanoidAppearanceSystem.DefaultSpecies) + /// + /// Return a default character profile, based on species. + /// + /// The species to use in this default profile. The default species is . + /// Humanoid character profile with default settings. + public static HumanoidCharacterProfile DefaultWithSpecies(string species = SharedHumanoidAppearanceSystem.DefaultSpecies) + { + return new() { - var prototypeManager = IoCManager.Resolve(); - var random = IoCManager.Resolve(); - - var sex = Sex.Unsexed; - var age = 18; - var height = 1f; - var width = 1f; - if (prototypeManager.TryIndex(species, out var speciesPrototype)) - { - sex = random.Pick(speciesPrototype.Sexes); - age = random.Next(speciesPrototype.MinAge, speciesPrototype.OldAge); // people don't look and keep making 119 year old characters with zero rp, cap it at middle aged - height = random.NextFloat(speciesPrototype.MinHeight, speciesPrototype.MaxHeight); - width = random.NextFloat(speciesPrototype.MinWidth, speciesPrototype.MaxWidth); - } - - var gender = Gender.Epicene; - - switch (sex) - { - case Sex.Male: - gender = Gender.Male; - break; - case Sex.Female: - gender = Gender.Female; - break; - } - - var name = GetName(species, gender); - - return new HumanoidCharacterProfile(name, "", species, height, width, age, sex, gender, - HumanoidCharacterAppearance.Random(species, sex), ClothingPreference.Jumpsuit, - BackpackPreference.Backpack, SpawnPriorityPreference.None, - new Dictionary - { - {SharedGameTicker.FallbackOverflowJob, JobPriority.High}, - }, PreferenceUnavailableMode.StayInLobby, new List(), new List(), new List()); - } - - public string Name { get; private set; } - public string FlavorText { get; private set; } - [DataField("species")] - public string Species { get; private set; } - - [DataField("height")] - public float Height { get; private set; } - - [DataField("width")] - public float Width { get; private set; } - - [DataField("age")] - public int Age { get; private set; } + Species = species, + }; + } - [DataField("sex")] - public Sex Sex { get; private set; } + // TODO: This should eventually not be a visual change only. + public static HumanoidCharacterProfile Random(HashSet? ignoredSpecies = null) + { + var prototypeManager = IoCManager.Resolve(); + var random = IoCManager.Resolve(); - [DataField("gender")] - public Gender Gender { get; private set; } + var species = random.Pick(prototypeManager + .EnumeratePrototypes() + .Where(x => ignoredSpecies == null ? x.RoundStart : x.RoundStart && !ignoredSpecies.Contains(x.ID)) + .ToArray() + ).ID; - public ICharacterAppearance CharacterAppearance => Appearance; + return RandomWithSpecies(species); + } - [DataField("appearance")] - public HumanoidCharacterAppearance Appearance { get; private set; } - public ClothingPreference Clothing { get; private set; } - public BackpackPreference Backpack { get; private set; } - public SpawnPriorityPreference SpawnPriority { get; private set; } - public IReadOnlyDictionary JobPriorities => _jobPriorities; - public IReadOnlyList AntagPreferences => _antagPreferences; - public IReadOnlyList TraitPreferences => _traitPreferences; - public IReadOnlyList LoadoutPreferences => _loadoutPreferences; - public PreferenceUnavailableMode PreferenceUnavailable { get; private set; } + public static HumanoidCharacterProfile RandomWithSpecies(string species = SharedHumanoidAppearanceSystem.DefaultSpecies) + { + var prototypeManager = IoCManager.Resolve(); + var random = IoCManager.Resolve(); - public HumanoidCharacterProfile WithName(string name) + var sex = Sex.Unsexed; + var age = 18; + if (prototypeManager.TryIndex(species, out var speciesPrototype)) { - return new(this) { Name = name }; + sex = random.Pick(speciesPrototype.Sexes); + age = random.Next(speciesPrototype.MinAge, speciesPrototype.OldAge); // people don't look and keep making 119 year old characters with zero rp, cap it at middle aged } - public HumanoidCharacterProfile WithFlavorText(string flavorText) - { - return new(this) { FlavorText = flavorText }; - } + var gender = Gender.Epicene; - public HumanoidCharacterProfile WithAge(int age) + switch (sex) { - return new(this) { Age = age }; + case Sex.Male: + gender = Gender.Male; + break; + case Sex.Female: + gender = Gender.Female; + break; } - public HumanoidCharacterProfile WithSex(Sex sex) - { - return new(this) { Sex = sex }; - } + var name = GetName(species, gender); - public HumanoidCharacterProfile WithGender(Gender gender) + return new HumanoidCharacterProfile() { - return new(this) { Gender = gender }; - } + Name = name, + Sex = sex, + Age = age, + Gender = gender, + Species = species, + Appearance = HumanoidCharacterAppearance.Random(species, sex), + }; + } - public HumanoidCharacterProfile WithSpecies(string species) - { - return new(this) { Species = species }; - } + public HumanoidCharacterProfile WithName(string name) => new(this) { Name = name }; + public HumanoidCharacterProfile WithFlavorText(string flavorText) => new(this) { FlavorText = flavorText }; + public HumanoidCharacterProfile WithAge(int age) => new(this) { Age = age }; + public HumanoidCharacterProfile WithSex(Sex sex) => new(this) { Sex = sex }; + public HumanoidCharacterProfile WithGender(Gender gender) => new(this) { Gender = gender }; + public HumanoidCharacterProfile WithSpecies(string species) => new(this) { Species = species }; + public HumanoidCharacterProfile WithCustomSpeciesName(string customspeciename) => new(this) { Customspeciename = customspeciename}; + public HumanoidCharacterProfile WithHeight(float height) => new(this) { Height = height }; + public HumanoidCharacterProfile WithWidth(float width) => new(this) { Width = width }; + + public HumanoidCharacterProfile WithCharacterAppearance(HumanoidCharacterAppearance appearance) => + new(this) { Appearance = appearance }; + public HumanoidCharacterProfile WithClothingPreference(ClothingPreference clothing) => + new(this) { Clothing = clothing }; + public HumanoidCharacterProfile WithBackpackPreference(BackpackPreference backpack) => + new(this) { Backpack = backpack }; + public HumanoidCharacterProfile WithSpawnPriorityPreference(SpawnPriorityPreference spawnPriority) => + new(this) { SpawnPriority = spawnPriority }; + public HumanoidCharacterProfile WithJobPriorities(IEnumerable> jobPriorities) => + new(this) { _jobPriorities = new Dictionary(jobPriorities) }; + + public HumanoidCharacterProfile WithJobPriority(string jobId, JobPriority priority) + { + var dictionary = new Dictionary(_jobPriorities); + if (priority == JobPriority.Never) + dictionary.Remove(jobId); + else + dictionary[jobId] = priority; - public HumanoidCharacterProfile WithHeight(float height) - { - return new(this) { Height = height }; - } + return new(this) { _jobPriorities = dictionary }; + } - public HumanoidCharacterProfile WithWidth(float width) - { - return new(this) { Width = width }; - } + public HumanoidCharacterProfile WithPreferenceUnavailable(PreferenceUnavailableMode mode) => + new(this) { PreferenceUnavailable = mode }; + public HumanoidCharacterProfile WithAntagPreferences(IEnumerable antagPreferences) => + new(this) { _antagPreferences = new HashSet(antagPreferences) }; - public HumanoidCharacterProfile WithCharacterAppearance(HumanoidCharacterAppearance appearance) - { - return new(this) { Appearance = appearance }; - } + public HumanoidCharacterProfile WithAntagPreference(string antagId, bool pref) + { + var list = new HashSet(_antagPreferences); + if (pref) + list.Add(antagId); + else + list.Remove(antagId); - public HumanoidCharacterProfile WithClothingPreference(ClothingPreference clothing) - { - return new(this) { Clothing = clothing }; - } - public HumanoidCharacterProfile WithBackpackPreference(BackpackPreference backpack) - { - return new(this) { Backpack = backpack }; - } - public HumanoidCharacterProfile WithSpawnPriorityPreference(SpawnPriorityPreference spawnPriority) - { - return new(this) { SpawnPriority = spawnPriority }; - } - public HumanoidCharacterProfile WithJobPriorities(IEnumerable> jobPriorities) - { - return new(this, new Dictionary(jobPriorities), _antagPreferences, _traitPreferences, - _loadoutPreferences); - } + return new(this) { _antagPreferences = list }; + } - public HumanoidCharacterProfile WithJobPriority(string jobId, JobPriority priority) - { - var dictionary = new Dictionary(_jobPriorities); - if (priority == JobPriority.Never) - { - dictionary.Remove(jobId); - } - else - { - dictionary[jobId] = priority; - } - return new(this, dictionary, _antagPreferences, _traitPreferences, _loadoutPreferences); - } + public HumanoidCharacterProfile WithTraitPreference(string traitId, bool pref) + { + var list = new HashSet(_traitPreferences); - public HumanoidCharacterProfile WithPreferenceUnavailable(PreferenceUnavailableMode mode) - { - return new(this) { PreferenceUnavailable = mode }; - } + if (pref) + list.Add(traitId); + else + list.Remove(traitId); - public HumanoidCharacterProfile WithAntagPreferences(IEnumerable antagPreferences) - { - return new(this, _jobPriorities, new List(antagPreferences), _traitPreferences, - _loadoutPreferences); - } + return new(this) { _traitPreferences = list }; + } - public HumanoidCharacterProfile WithAntagPreference(string antagId, bool pref) - { - var list = new List(_antagPreferences); - if (pref) - { - if (!list.Contains(antagId)) - { - list.Add(antagId); - } - } - else - { - if (list.Contains(antagId)) - { - list.Remove(antagId); - } - } - return new(this, _jobPriorities, list, _traitPreferences, _loadoutPreferences); - } + public HumanoidCharacterProfile WithLoadoutPreference(string loadoutId, bool pref) + { + var list = new HashSet(_loadoutPreferences); - public HumanoidCharacterProfile WithTraitPreference(string traitId, bool pref) - { - var list = new List(_traitPreferences); + if (pref) + list.Add(loadoutId); + else + list.Remove(loadoutId); - // TODO: Maybe just refactor this to HashSet? Same with _antagPreferences - if (pref) - { - if (!list.Contains(traitId)) - { - list.Add(traitId); - } - } - else - { - if (list.Contains(traitId)) - { - list.Remove(traitId); - } - } - return new(this, _jobPriorities, _antagPreferences, list, _loadoutPreferences); - } + return new HumanoidCharacterProfile(this) { _loadoutPreferences = list }; + } - public HumanoidCharacterProfile WithLoadoutPreference(string loadoutId, bool pref) - { - var list = new List(_loadoutPreferences); + public string Summary => + Loc.GetString( + "humanoid-character-profile-summary", + ("name", Name), + ("gender", Gender.ToString().ToLowerInvariant()), + ("age", Age) + ); - if(pref) - { - if(!list.Contains(loadoutId)) - { - list.Add(loadoutId); - } - } - else - { - if(list.Contains(loadoutId)) - { - list.Remove(loadoutId); - } - } - return new(this, _jobPriorities, _antagPreferences, _traitPreferences, list); - } + public bool MemberwiseEquals(ICharacterProfile maybeOther) + { + return maybeOther is HumanoidCharacterProfile other + && Name == other.Name + && Age == other.Age + && Sex == other.Sex + && Gender == other.Gender + && Species == other.Species + && PreferenceUnavailable == other.PreferenceUnavailable + && SpawnPriority == other.SpawnPriority + && _jobPriorities.SequenceEqual(other._jobPriorities) + && _antagPreferences.SequenceEqual(other._antagPreferences) + && _traitPreferences.SequenceEqual(other._traitPreferences) + && LoadoutPreferences.SequenceEqual(other.LoadoutPreferences) + && Appearance.MemberwiseEquals(other.Appearance) + && FlavorText == other.FlavorText; + } - public string Summary => - Loc.GetString( - "humanoid-character-profile-summary", - ("name", Name), - ("gender", Gender.ToString().ToLowerInvariant()), - ("age", Age) - ); + public void EnsureValid(ICommonSession session, IDependencyCollection collection) + { + var configManager = collection.Resolve(); + var prototypeManager = collection.Resolve(); - public bool MemberwiseEquals(ICharacterProfile maybeOther) + if (!prototypeManager.TryIndex(Species, out var speciesPrototype) || speciesPrototype.RoundStart == false) { - if (maybeOther is not HumanoidCharacterProfile other - || Name != other.Name - || Age != other.Age - || Height != other.Height - || Width != other.Width - || Sex != other.Sex - || Gender != other.Gender - || PreferenceUnavailable != other.PreferenceUnavailable - || Clothing != other.Clothing - || Backpack != other.Backpack - || SpawnPriority != other.SpawnPriority - || !_jobPriorities.SequenceEqual(other._jobPriorities) - || !_antagPreferences.SequenceEqual(other._antagPreferences) - || !_traitPreferences.SequenceEqual(other._traitPreferences) - || !_loadoutPreferences.SequenceEqual(other._loadoutPreferences)) - return false; - return Appearance.MemberwiseEquals(other.Appearance); + Species = SharedHumanoidAppearanceSystem.DefaultSpecies; + speciesPrototype = prototypeManager.Index(Species); } - public void EnsureValid(ICommonSession session, IDependencyCollection collection) + var sex = Sex switch { - var configManager = collection.Resolve(); - var prototypeManager = collection.Resolve(); + Sex.Male => Sex.Male, + Sex.Female => Sex.Female, + Sex.Unsexed => Sex.Unsexed, + _ => Sex.Male // Invalid enum values. + }; - if (!prototypeManager.TryIndex(Species, out var speciesPrototype) || speciesPrototype.RoundStart == false) - { - Species = SharedHumanoidAppearanceSystem.DefaultSpecies; - speciesPrototype = prototypeManager.Index(Species); - } + // ensure the species can be that sex and their age fits the founds + if (!speciesPrototype.Sexes.Contains(sex)) + sex = speciesPrototype.Sexes[0]; - var sex = Sex switch - { - Sex.Male => Sex.Male, - Sex.Female => Sex.Female, - Sex.Unsexed => Sex.Unsexed, - _ => Sex.Male // Invalid enum values. - }; + var age = Math.Clamp(Age, speciesPrototype.MinAge, speciesPrototype.MaxAge); - // ensure the species can be that sex and their age fits the founds - if (!speciesPrototype.Sexes.Contains(sex)) - sex = speciesPrototype.Sexes[0]; + var gender = Gender switch + { + Gender.Epicene => Gender.Epicene, + Gender.Female => Gender.Female, + Gender.Male => Gender.Male, + Gender.Neuter => Gender.Neuter, + _ => Gender.Epicene // Invalid enum values. + }; - var age = Math.Clamp(Age, speciesPrototype.MinAge, speciesPrototype.MaxAge); + string name; + if (string.IsNullOrEmpty(Name)) + name = GetName(Species, gender); + else if (Name.Length > MaxNameLength) + name = Name[..MaxNameLength]; + else + name = Name; - var gender = Gender switch - { - Gender.Epicene => Gender.Epicene, - Gender.Female => Gender.Female, - Gender.Male => Gender.Male, - Gender.Neuter => Gender.Neuter, - _ => Gender.Epicene // Invalid enum values. - }; - - string name; - if (string.IsNullOrEmpty(Name)) - { - name = GetName(Species, gender); - } - else if (Name.Length > MaxNameLength) - { - name = Name[..MaxNameLength]; - } - else - { - name = Name; - } + name = name.Trim(); - name = name.Trim(); + if (configManager.GetCVar(CCVars.RestrictedNames)) + name = RestrictedNameRegex.Replace(name, string.Empty); - if (configManager.GetCVar(CCVars.RestrictedNames)) - { - name = Regex.Replace(name, @"[^\u0030-\u0039,\u0041-\u005A,\u0061-\u007A,\u00C0-\u00D6,\u00D8-\u00F6,\u00F8-\u00FF,\u0100-\u017F, '.,-]", string.Empty); - /* - * 0030-0039 Basic Latin: ASCII Digits - * 0041-005A Basic Latin: Uppercase Latin Alphabet - * 0061-007A Basic Latin: Lowercase Latin Alphabet - * 00C0-00D6 Latin-1 Supplement: Letters I - * 00D8-00F6 Latin-1 Supplement: Letters II - * 00F8-00FF Latin-1 Supplement: Letters III - * 0100-017F Latin Extended A: European Latin - */ - } - - if (configManager.GetCVar(CCVars.ICNameCase)) - { - // This regex replaces the first character of the first and last words of the name with their uppercase version - name = Regex.Replace(name, - @"^(?\w)|\b(?\w)(?=\w*$)", - m => m.Groups["word"].Value.ToUpper()); - } + if (configManager.GetCVar(CCVars.ICNameCase)) + { + // This regex replaces the first character of the first and last words of the name with their uppercase version + name = ICNameCaseRegex.Replace(name, m => m.Groups["word"].Value.ToUpper()); + } - if (string.IsNullOrEmpty(name)) - { - name = GetName(Species, gender); - } + var customspeciename = + !speciesPrototype.CustomName + || string.IsNullOrEmpty(Customspeciename) + ? "" + : Customspeciename.Length > MaxNameLength + ? FormattedMessage.RemoveMarkup(Customspeciename)[..MaxNameLength] + : FormattedMessage.RemoveMarkup(Customspeciename); - string flavortext; - if (FlavorText.Length > MaxDescLength) - { - flavortext = FormattedMessage.RemoveMarkup(FlavorText)[..MaxDescLength]; - } - else - { - flavortext = FormattedMessage.RemoveMarkup(FlavorText); - } + if (string.IsNullOrEmpty(name)) + name = GetName(Species, gender); - var height = Height; - if (speciesPrototype != null) - height = Math.Clamp(Height, speciesPrototype.MinHeight, speciesPrototype.MaxHeight); + string flavortext; + if (FlavorText.Length > MaxDescLength) + flavortext = FormattedMessage.RemoveMarkup(FlavorText)[..MaxDescLength]; + else + flavortext = FormattedMessage.RemoveMarkup(FlavorText); - var width = Width; - if (speciesPrototype != null) - width = Math.Clamp(Width, speciesPrototype.MinWidth, speciesPrototype.MaxWidth); + var appearance = HumanoidCharacterAppearance.EnsureValid(Appearance, Species, Sex); - var appearance = HumanoidCharacterAppearance.EnsureValid(Appearance, Species, Sex); + var prefsUnavailableMode = PreferenceUnavailable switch + { + PreferenceUnavailableMode.StayInLobby => PreferenceUnavailableMode.StayInLobby, + PreferenceUnavailableMode.SpawnAsOverflow => PreferenceUnavailableMode.SpawnAsOverflow, + _ => PreferenceUnavailableMode.StayInLobby // Invalid enum values. + }; - var prefsUnavailableMode = PreferenceUnavailable switch - { - PreferenceUnavailableMode.StayInLobby => PreferenceUnavailableMode.StayInLobby, - PreferenceUnavailableMode.SpawnAsOverflow => PreferenceUnavailableMode.SpawnAsOverflow, - _ => PreferenceUnavailableMode.StayInLobby // Invalid enum values. - }; + var spawnPriority = SpawnPriority switch + { + SpawnPriorityPreference.None => SpawnPriorityPreference.None, + SpawnPriorityPreference.Arrivals => SpawnPriorityPreference.Arrivals, + SpawnPriorityPreference.Cryosleep => SpawnPriorityPreference.Cryosleep, + _ => SpawnPriorityPreference.None // Invalid enum values. + }; - var clothing = Clothing switch + var priorities = new Dictionary(JobPriorities + .Where(p => prototypeManager.TryIndex(p.Key, out var job) && job.SetPreference && p.Value switch { - ClothingPreference.Jumpsuit => ClothingPreference.Jumpsuit, - ClothingPreference.Jumpskirt => ClothingPreference.Jumpskirt, - _ => ClothingPreference.Jumpsuit // Invalid enum values. - }; + JobPriority.Never => false, // Drop never since that's assumed default. + JobPriority.Low => true, + JobPriority.Medium => true, + JobPriority.High => true, + _ => false + })); - var backpack = Backpack switch - { - BackpackPreference.Backpack => BackpackPreference.Backpack, - BackpackPreference.Satchel => BackpackPreference.Satchel, - BackpackPreference.Duffelbag => BackpackPreference.Duffelbag, - _ => BackpackPreference.Backpack // Invalid enum values. - }; + var antags = AntagPreferences + .Where(id => prototypeManager.TryIndex(id, out var antag) && antag.SetPreference) + .ToList(); - var spawnPriority = SpawnPriority switch - { - SpawnPriorityPreference.None => SpawnPriorityPreference.None, - SpawnPriorityPreference.Arrivals => SpawnPriorityPreference.Arrivals, - SpawnPriorityPreference.Cryosleep => SpawnPriorityPreference.Cryosleep, - _ => SpawnPriorityPreference.None // Invalid enum values. - }; - - var priorities = new Dictionary(JobPriorities - .Where(p => prototypeManager.TryIndex(p.Key, out var job) && job.SetPreference && p.Value switch - { - JobPriority.Never => false, // Drop never since that's assumed default. - JobPriority.Low => true, - JobPriority.Medium => true, - JobPriority.High => true, - _ => false - })); - - var antags = AntagPreferences - .Where(id => prototypeManager.TryIndex(id, out var antag) && antag.SetPreference) - .ToList(); - - var traits = TraitPreferences - .Where(prototypeManager.HasIndex) - .ToList(); - - var maxTraits = configManager.GetCVar(CCVars.GameTraitsMax); - var currentTraits = 0; - var traitPoints = configManager.GetCVar(CCVars.GameTraitsDefaultPoints); - - foreach (var trait in traits.OrderBy(t => -prototypeManager.Index(t).Points).ToList()) - { - var proto = prototypeManager.Index(trait); + var traits = TraitPreferences + .Where(prototypeManager.HasIndex) + .ToList(); - if (traitPoints + proto.Points < 0 || currentTraits + 1 > maxTraits) - traits.Remove(trait); - else - { - traitPoints += proto.Points; - currentTraits++; - } - } + var loadouts = LoadoutPreferences + .Where(prototypeManager.HasIndex) + .ToList(); + Name = name; + Customspeciename = customspeciename; + FlavorText = flavortext; + Age = age; + Sex = sex; + Gender = gender; + Appearance = appearance; + SpawnPriority = spawnPriority; - var loadouts = LoadoutPreferences - .Where(prototypeManager.HasIndex) - .ToList(); + _jobPriorities.Clear(); - var loadoutPoints = configManager.GetCVar(CCVars.GameLoadoutsPoints); - var currentPoints = 0; + foreach (var (job, priority) in priorities) + _jobPriorities.Add(job, priority); - foreach (var loadout in loadouts.ToList()) - { - var proto = prototypeManager.Index(loadout); - - if (currentPoints + proto.Cost > loadoutPoints) - loadouts.Remove(loadout); - else - currentPoints += proto.Cost; - } - - - Name = name; - FlavorText = flavortext; - Age = age; - Height = height; - Width = width; - Sex = sex; - Gender = gender; - Appearance = appearance; - Clothing = clothing; - Backpack = backpack; - SpawnPriority = spawnPriority; - - _jobPriorities.Clear(); - - foreach (var (job, priority) in priorities) - { - _jobPriorities.Add(job, priority); - } + PreferenceUnavailable = prefsUnavailableMode; - PreferenceUnavailable = prefsUnavailableMode; + _antagPreferences.Clear(); + _antagPreferences.UnionWith(antags); - _antagPreferences.Clear(); - _antagPreferences.AddRange(antags); + _traitPreferences.Clear(); + _traitPreferences.UnionWith(traits); - _traitPreferences.Clear(); - _traitPreferences.AddRange(traits); + _loadoutPreferences.Clear(); + _loadoutPreferences.UnionWith(loadouts); + } - _loadoutPreferences.Clear(); - _loadoutPreferences.AddRange(loadouts); - } + public ICharacterProfile Validated(ICommonSession session, IDependencyCollection collection) + { + var profile = new HumanoidCharacterProfile(this); + profile.EnsureValid(session, collection); + return profile; + } - public ICharacterProfile Validated(ICommonSession session, IDependencyCollection collection) - { - var profile = new HumanoidCharacterProfile(this); - profile.EnsureValid(session, collection); - return profile; - } + // Sorry this is kind of weird and duplicated, + // Working inside these non entity systems is a bit wack + public static string GetName(string species, Gender gender) + { + var namingSystem = IoCManager.Resolve().GetEntitySystem(); + return namingSystem.GetName(species, gender); + } - // sorry this is kind of weird and duplicated, - /// working inside these non entity systems is a bit wack - public static string GetName(string species, Gender gender) - { - var namingSystem = IoCManager.Resolve().GetEntitySystem(); - return namingSystem.GetName(species, gender); - } + public override bool Equals(object? obj) + { + return ReferenceEquals(this, obj) || obj is HumanoidCharacterProfile other && MemberwiseEquals(other); + } - public override bool Equals(object? obj) - { - return obj is HumanoidCharacterProfile other && MemberwiseEquals(other); - } + public override int GetHashCode() + { + var hashCode = new HashCode(); + hashCode.Add(_jobPriorities); + hashCode.Add(_antagPreferences); + hashCode.Add(_traitPreferences); + hashCode.Add(_loadoutPreferences); + hashCode.Add(Name); + hashCode.Add(FlavorText); + hashCode.Add(Species); + hashCode.Add(Age); + hashCode.Add((int)Sex); + hashCode.Add((int)Gender); + hashCode.Add(Appearance); + hashCode.Add((int)SpawnPriority); + hashCode.Add((int)PreferenceUnavailable); + hashCode.Add(Customspeciename); + return hashCode.ToHashCode(); + } - public override int GetHashCode() - { - return HashCode.Combine( - HashCode.Combine( - Name, - Species, - Age, - Sex, - Gender, - Appearance, - Clothing, - Backpack - ), - HashCode.Combine( - SpawnPriority, - Height, - Width, - PreferenceUnavailable, - _jobPriorities, - _antagPreferences, - _traitPreferences, - _loadoutPreferences - ) - ); - } + public HumanoidCharacterProfile Clone() + { + return new HumanoidCharacterProfile(this); } } diff --git a/Content.Shared/Preferences/Loadouts/Effects/SpeciesLoadoutEffect.cs b/Content.Shared/Preferences/Loadouts/Effects/SpeciesLoadoutEffect.cs new file mode 100644 index 0000000000..74673cbef3 --- /dev/null +++ b/Content.Shared/Preferences/Loadouts/Effects/SpeciesLoadoutEffect.cs @@ -0,0 +1,6 @@ +namespace Content.Shared.Preferences.Loadouts.Effects; + +public sealed class SpeciesLoadoutEffect +{ + +} diff --git a/Content.Shared/Prototypes/CharacterItemGroupPrototype.cs b/Content.Shared/Prototypes/CharacterItemGroupPrototype.cs new file mode 100644 index 0000000000..c303f4f7d5 --- /dev/null +++ b/Content.Shared/Prototypes/CharacterItemGroupPrototype.cs @@ -0,0 +1,55 @@ +using System.Diagnostics.CodeAnalysis; +using Content.Shared.Clothing.Loadouts.Prototypes; +using Content.Shared.Customization.Systems; +using Content.Shared.Preferences; +using Content.Shared.Traits; +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; + +namespace Content.Shared.Prototypes; + +[Prototype] +public sealed partial class CharacterItemGroupPrototype : IPrototype +{ + [IdDataField] + public string ID { get; } = default!; + + /// How many items from this group can be selected at once + [DataField] + public int MaxItems = 1; + + /// An arbitrary list of traits, loadouts, etc + [DataField] + public List Items = new(); +} + +[DataDefinition] +public sealed partial class CharacterItemGroupItem +{ + [DataField(required: true)] + public string Type; + + [DataField("id", required: true)] + public string ID; + + /// Tries to get Value from whatever Type maps to on a character profile + //TODO: Make a test for this + public bool TryGetValue(HumanoidCharacterProfile profile, IPrototypeManager protoMan, [NotNullWhen(true)] out object? value) + { + value = null; + + // This sucks + switch (Type) + { + case "trait": + return profile.TraitPreferences.TryFirstOrDefault( + p => protoMan.Index((string) p).ID == ID, out value); + case "loadout": + return profile.LoadoutPreferences.TryFirstOrDefault( + p => protoMan.Index((string) p).ID == ID, out value); + default: + DebugTools.Assert($"Invalid CharacterItemGroupItem Type: {Type}"); + return false; + } + } +} diff --git a/Content.Shared/Prototypes/LocalizedPrototype.cs b/Content.Shared/Prototypes/LocalizedPrototype.cs new file mode 100644 index 0000000000..acdd5fc180 --- /dev/null +++ b/Content.Shared/Prototypes/LocalizedPrototype.cs @@ -0,0 +1,35 @@ +using System.Linq; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Prototypes; + +public abstract class LocalizedPrototype : IPrototype +{ + [IdDataField] + public string ID { get; } = default!; + + public const string LocFormat = "{0}-{1}-{2}"; + + /// The localization string for the name of this prototype + public string NameLoc => ToLocalizationString("name"); + /// The localized string for the name of prototype + public string Name => Loc.GetString(NameLoc); + + /// + /// Returns an Loc string using the as the 'property'. + /// Given `desc` it will return `this-prototype-PrototypeId-desc`. + /// + public string ToLocalizationString(string field) + { + // Get the ID of the proto Type + var type = + ((PrototypeAttribute?) Attribute.GetCustomAttribute(GetType(), typeof(PrototypeAttribute)))?.Type + ?? GetType().Name.Remove(GetType().Name.Length - 9); + // Lowercase the first letter + type = char.ToLowerInvariant(type[0]) + type[1..]; + // Replace every uppercase letter with a dash and the lowercase letter + type = type.Aggregate("", (current, c) => current + (char.IsUpper(c) ? "-" + char.ToLowerInvariant(c) : c.ToString())); + + return string.Format(LocFormat, type, ID, field); + } +} diff --git a/Content.Shared/Prying/Systems/PryingSystem.cs b/Content.Shared/Prying/Systems/PryingSystem.cs index fa7a135e6c..a59c09ca53 100644 --- a/Content.Shared/Prying/Systems/PryingSystem.cs +++ b/Content.Shared/Prying/Systems/PryingSystem.cs @@ -1,15 +1,14 @@ -using Content.Shared.Prying.Components; -using Content.Shared.Verbs; -using Content.Shared.DoAfter; -using Robust.Shared.Serialization; +using System.Diagnostics.CodeAnalysis; using Content.Shared.Administration.Logs; using Content.Shared.Database; +using Content.Shared.DoAfter; using Content.Shared.Doors.Components; -using System.Diagnostics.CodeAnalysis; using Content.Shared.Interaction; using Content.Shared.Popups; -using Robust.Shared.Audio; +using Content.Shared.Prying.Components; +using Content.Shared.Verbs; using Robust.Shared.Audio.Systems; +using Robust.Shared.Serialization; using PryUnpoweredComponent = Content.Shared.Prying.Components.PryUnpoweredComponent; namespace Content.Shared.Prying.Systems; @@ -99,14 +98,16 @@ public bool TryPry(EntityUid target, EntityUid user, out DoAfterId? id) // to be marked as handled. return true; - return StartPry(target, user, null, 0.1f, out id); // hand-prying is much slower + // hand-prying is much slower + var modifier = CompOrNull(user)?.SpeedModifier ?? 0.1f; + return StartPry(target, user, null, modifier, out id); } private bool CanPry(EntityUid target, EntityUid user, out string? message, PryingComponent? comp = null) { BeforePryEvent canev; - if (comp != null) + if (comp != null || Resolve(user, ref comp, false)) { canev = new BeforePryEvent(user, comp.PryPowered, comp.Force); } diff --git a/Content.Shared/Psionics/Abilities/Dispel/DamageOnDispelComponent.cs b/Content.Shared/Psionics/Abilities/Dispel/DamageOnDispelComponent.cs index ce86111fc4..fd3c947136 100644 --- a/Content.Shared/Psionics/Abilities/Dispel/DamageOnDispelComponent.cs +++ b/Content.Shared/Psionics/Abilities/Dispel/DamageOnDispelComponent.cs @@ -8,10 +8,10 @@ namespace Content.Shared.Abilities.Psionics [RegisterComponent] public sealed partial class DamageOnDispelComponent : Component { - [DataField("damage", required: true)] - public DamageSpecifier Damage = default!; + [DataField(required: true)] + public DamageSpecifier Damage = default!; - [DataField("variance")] + [DataField] public float Variance = 0.5f; } } diff --git a/Content.Shared/Psionics/Abilities/Dispel/DispelPowerComponent.cs b/Content.Shared/Psionics/Abilities/Dispel/DispelPowerComponent.cs index cd88786636..6c37ff0b6c 100644 --- a/Content.Shared/Psionics/Abilities/Dispel/DispelPowerComponent.cs +++ b/Content.Shared/Psionics/Abilities/Dispel/DispelPowerComponent.cs @@ -1,20 +1,5 @@ -using Content.Shared.Actions; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - namespace Content.Shared.Abilities.Psionics { [RegisterComponent] - public sealed partial class DispelPowerComponent : Component - { - [DataField("range")] - public float Range = 10f; - - [DataField("dispelActionId", - customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? DispelActionId = "ActionDispel"; - - [DataField("dispelActionEntity")] - public EntityUid? DispelActionEntity; - } + public sealed partial class DispelPowerComponent : Component { } } diff --git a/Content.Shared/Psionics/Abilities/MassSleep/MassSleepPowerComponent.cs b/Content.Shared/Psionics/Abilities/MassSleep/MassSleepPowerComponent.cs index 7d611c63da..97ed24cdde 100644 --- a/Content.Shared/Psionics/Abilities/MassSleep/MassSleepPowerComponent.cs +++ b/Content.Shared/Psionics/Abilities/MassSleep/MassSleepPowerComponent.cs @@ -1,18 +1,8 @@ -using Content.Shared.Actions; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - namespace Content.Shared.Abilities.Psionics { [RegisterComponent] public sealed partial class MassSleepPowerComponent : Component { - public float Radius = 1.25f; - [DataField("massSleepActionId", - customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? MassSleepActionId = "ActionMassSleep"; - - [DataField("massSleepActionEntity")] - public EntityUid? MassSleepActionEntity; + public readonly float Radius = 1.25f; } } diff --git a/Content.Shared/Psionics/Abilities/MassSleep/MassSleepPowerSystem.cs b/Content.Shared/Psionics/Abilities/MassSleep/MassSleepPowerSystem.cs index e36a3c70e8..484aa6da14 100644 --- a/Content.Shared/Psionics/Abilities/MassSleep/MassSleepPowerSystem.cs +++ b/Content.Shared/Psionics/Abilities/MassSleep/MassSleepPowerSystem.cs @@ -1,47 +1,21 @@ -using Content.Shared.Actions; using Content.Shared.Bed.Sleep; -using Content.Shared.Magic.Events; using Content.Shared.Damage; using Content.Shared.Mobs.Components; -using Robust.Shared.Prototypes; -using Robust.Shared.Timing; -using Content.Shared.Mind; using Content.Shared.Actions.Events; namespace Content.Shared.Abilities.Psionics { public sealed class MassSleepPowerSystem : EntitySystem { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly SharedActionsSystem _actions = default!; [Dependency] private readonly EntityLookupSystem _lookup = default!; [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly SharedMindSystem _mindSystem = default!; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnShutdown); SubscribeLocalEvent(OnPowerUsed); } - private void OnInit(EntityUid uid, MassSleepPowerComponent component, ComponentInit args) - { - _actions.AddAction(uid, ref component.MassSleepActionEntity, component.MassSleepActionId ); - _actions.TryGetActionData( component.MassSleepActionEntity, out var actionData ); - if (actionData is { UseDelay: not null }) - _actions.StartUseDelay(component.MassSleepActionEntity); - if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) - psionic.PsionicAbility = component.MassSleepActionEntity; - } - - private void OnShutdown(EntityUid uid, MassSleepPowerComponent component, ComponentShutdown args) - { - _actions.RemoveAction(uid, component.MassSleepActionEntity); - } - private void OnPowerUsed(EntityUid uid, MassSleepPowerComponent component, MassSleepPowerActionEvent args) { foreach (var entity in _lookup.GetEntitiesInRange(args.Target, component.Radius)) diff --git a/Content.Shared/Psionics/Abilities/Metapsionics/MetapsionicPowerComponent.cs b/Content.Shared/Psionics/Abilities/Metapsionics/MetapsionicPowerComponent.cs index c9d0130221..941a2776a4 100644 --- a/Content.Shared/Psionics/Abilities/Metapsionics/MetapsionicPowerComponent.cs +++ b/Content.Shared/Psionics/Abilities/Metapsionics/MetapsionicPowerComponent.cs @@ -1,21 +1,8 @@ -using Content.Shared.Actions; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - namespace Content.Shared.Abilities.Psionics { [RegisterComponent] public sealed partial class MetapsionicPowerComponent : Component { - [DataField("range")] - public float Range = 5f; - - public InstantActionComponent? MetapsionicPowerAction = null; - [DataField("metapsionicActionId", - customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? MetapsionicActionId = "ActionMetapsionic"; - - [DataField("metapsionicActionEntity")] - public EntityUid? MetapsionicActionEntity; + public readonly float Range = 5f; } } diff --git a/Content.Shared/Psionics/Abilities/MindSwap/MindSwapPowerComponent.cs b/Content.Shared/Psionics/Abilities/MindSwap/MindSwapPowerComponent.cs index 6a3fc811c8..216972df1e 100644 --- a/Content.Shared/Psionics/Abilities/MindSwap/MindSwapPowerComponent.cs +++ b/Content.Shared/Psionics/Abilities/MindSwap/MindSwapPowerComponent.cs @@ -1,16 +1,5 @@ -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - namespace Content.Shared.Abilities.Psionics { [RegisterComponent] - public sealed partial class MindSwapPowerComponent : Component - { - [DataField("mindSwapActionId", - customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? MindSwapActionId = "ActionMindSwap"; - - [DataField("mindSwapActionEntity")] - public EntityUid? MindSwapActionEntity; - } + public sealed partial class MindSwapPowerComponent : Component { } } diff --git a/Content.Shared/Psionics/Abilities/NoosphericZap/NoosphericZapPowerComponent.cs b/Content.Shared/Psionics/Abilities/NoosphericZap/NoosphericZapPowerComponent.cs index 0e91894b1d..abbab16db7 100644 --- a/Content.Shared/Psionics/Abilities/NoosphericZap/NoosphericZapPowerComponent.cs +++ b/Content.Shared/Psionics/Abilities/NoosphericZap/NoosphericZapPowerComponent.cs @@ -1,17 +1,5 @@ -using Content.Shared.Actions; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - namespace Content.Shared.Abilities.Psionics { [RegisterComponent] - public sealed partial class NoosphericZapPowerComponent : Component - { - [DataField("noosphericZapActionId", - customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? NoosphericZapActionId = "ActionNoosphericZap"; - - [DataField("noosphericZapActionEntity")] - public EntityUid? NoosphericZapActionEntity; - } + public sealed partial class NoosphericZapPowerComponent : Component { } } diff --git a/Content.Shared/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityPowerComponent.cs b/Content.Shared/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityPowerComponent.cs index 3e198aa930..ff983bf9ef 100644 --- a/Content.Shared/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityPowerComponent.cs +++ b/Content.Shared/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityPowerComponent.cs @@ -1,16 +1,5 @@ -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - namespace Content.Shared.Abilities.Psionics { [RegisterComponent] - public sealed partial class PsionicInvisibilityPowerComponent : Component - { - [DataField("psionicInvisibilityActionId", - customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? PsionicInvisibilityActionId = "ActionPsionicInvisibility"; - - [DataField("psionicInvisibilityActionEntity")] - public EntityUid? PsionicInvisibilityActionEntity; - } + public sealed partial class PsionicInvisibilityPowerComponent : Component { } } diff --git a/Content.Shared/Psionics/Abilities/PsionicRegeneration/PsionicRegenerationPowerComponent.cs b/Content.Shared/Psionics/Abilities/PsionicRegeneration/PsionicRegenerationPowerComponent.cs index 895c5201c3..93a5076506 100644 --- a/Content.Shared/Psionics/Abilities/PsionicRegeneration/PsionicRegenerationPowerComponent.cs +++ b/Content.Shared/Psionics/Abilities/PsionicRegeneration/PsionicRegenerationPowerComponent.cs @@ -1,31 +1,22 @@ using Robust.Shared.Audio; using Content.Shared.DoAfter; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; namespace Content.Shared.Abilities.Psionics { [RegisterComponent] public sealed partial class PsionicRegenerationPowerComponent : Component { - [DataField("doAfter")] + [DataField] public DoAfterId? DoAfter; - [DataField("essence")] + [DataField] public float EssenceAmount = 20; - [DataField("useDelay")] + [DataField] public float UseDelay = 8f; - [DataField("soundUse")] + [DataField] public SoundSpecifier SoundUse = new SoundPathSpecifier("/Audio/Psionics/heartbeat_fast.ogg"); - - [DataField("psionicRegenerationActionId", - customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? PsionicRegenerationActionId = "ActionPsionicRegeneration"; - - [DataField("psionicRegenerationActionEntity")] - public EntityUid? PsionicRegenerationActionEntity; } } diff --git a/Content.Shared/Psionics/Abilities/Pyrokinesis/PyrokinesisPowerComponent.cs b/Content.Shared/Psionics/Abilities/Pyrokinesis/PyrokinesisPowerComponent.cs index 28425afdb4..a8867c080c 100644 --- a/Content.Shared/Psionics/Abilities/Pyrokinesis/PyrokinesisPowerComponent.cs +++ b/Content.Shared/Psionics/Abilities/Pyrokinesis/PyrokinesisPowerComponent.cs @@ -1,18 +1,5 @@ -using Content.Shared.Actions; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - namespace Content.Shared.Abilities.Psionics { [RegisterComponent] - public sealed partial class PyrokinesisPowerComponent : Component - { - public EntityTargetActionComponent? PyrokinesisPowerAction = null; - [DataField("pyrokinesisActionId", - customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? PyrokinesisActionId = "ActionPyrokinesis"; - - [DataField("pyrokinesisActionEntity")] - public EntityUid? PyrokinesisActionEntity; - } + public sealed partial class PyrokinesisPowerComponent : Component { } } diff --git a/Content.Shared/Psionics/Events.cs b/Content.Shared/Psionics/Events.cs index cf9a50c6e1..68ec3097e7 100644 --- a/Content.Shared/Psionics/Events.cs +++ b/Content.Shared/Psionics/Events.cs @@ -1,28 +1,69 @@ using Robust.Shared.Serialization; +using Content.Shared.Damage; using Content.Shared.DoAfter; -namespace Content.Shared.Psionics.Events +namespace Content.Shared.Psionics.Events; + +[Serializable, NetSerializable] +public sealed partial class PsionicRegenerationDoAfterEvent : DoAfterEvent { - [Serializable, NetSerializable] - public sealed partial class PsionicRegenerationDoAfterEvent : DoAfterEvent + [DataField("startedAt", required: true)] + public TimeSpan StartedAt; + + public PsionicRegenerationDoAfterEvent(TimeSpan startedAt) { - [DataField("startedAt", required: true)] - public TimeSpan StartedAt; + StartedAt = startedAt; + } - private PsionicRegenerationDoAfterEvent() - { - } + public override DoAfterEvent Clone() => this; +} - public PsionicRegenerationDoAfterEvent(TimeSpan startedAt) - { - StartedAt = startedAt; - } +[Serializable, NetSerializable] +public sealed partial class GlimmerWispDrainDoAfterEvent : SimpleDoAfterEvent { } - public override DoAfterEvent Clone() => this; +[Serializable, NetSerializable] +public sealed partial class HealingWordDoAfterEvent : DoAfterEvent +{ + [DataField(required: true)] + public TimeSpan StartedAt; + + public HealingWordDoAfterEvent(TimeSpan startedAt) + { + StartedAt = startedAt; } - [Serializable, NetSerializable] - public sealed partial class GlimmerWispDrainDoAfterEvent : SimpleDoAfterEvent + public override DoAfterEvent Clone() => this; +} + +[Serializable, NetSerializable] +public sealed partial class PsionicHealOtherDoAfterEvent : DoAfterEvent +{ + [DataField(required: true)] + public TimeSpan StartedAt; + + [DataField] + public DamageSpecifier? HealingAmount = default!; + + [DataField] + public float? RotReduction; + + [DataField] + public bool DoRevive; + + /// + /// Caster's Amplification that has been modified by the results of a MoodContest. + /// + public float ModifiedAmplification = default!; + + /// + /// Caster's Dampening that has been modified by the results of a MoodContest. + /// + public float ModifiedDampening = default!; + + public PsionicHealOtherDoAfterEvent(TimeSpan startedAt) { + StartedAt = startedAt; } + + public override DoAfterEvent Clone() => this; } diff --git a/Content.Shared/Psionics/Items/PsionicItemsSystem.cs b/Content.Shared/Psionics/Items/PsionicItemsSystem.cs index f88acf61f3..17ee9b25ef 100644 --- a/Content.Shared/Psionics/Items/PsionicItemsSystem.cs +++ b/Content.Shared/Psionics/Items/PsionicItemsSystem.cs @@ -8,7 +8,6 @@ public sealed class PsionicItemsSystem : EntitySystem { [Dependency] private readonly StatusEffectsSystem _statusEffects = default!; [Dependency] private readonly IComponentFactory _componentFactory = default!; - [Dependency] private readonly SharedPsionicAbilitiesSystem _psiAbilities = default!; public override void Initialize() { base.Initialize(); @@ -29,7 +28,6 @@ private void OnTinfoilEquipped(EntityUid uid, TinfoilHatComponent component, Got var insul = EnsureComp(args.Equipee); insul.Passthrough = component.Passthrough; component.IsActive = true; - _psiAbilities.SetPsionicsThroughEligibility(args.Equipee); } private void OnTinfoilUnequipped(EntityUid uid, TinfoilHatComponent component, GotUnequippedEvent args) @@ -41,7 +39,6 @@ private void OnTinfoilUnequipped(EntityUid uid, TinfoilHatComponent component, G RemComp(args.Equipee); component.IsActive = false; - _psiAbilities.SetPsionicsThroughEligibility(args.Equipee); } private void OnGranterEquipped(EntityUid uid, ClothingGrantPsionicPowerComponent component, GotEquippedEvent args) diff --git a/Content.Shared/Psionics/MindbrokenComponent.cs b/Content.Shared/Psionics/MindbrokenComponent.cs new file mode 100644 index 0000000000..f9086025f8 --- /dev/null +++ b/Content.Shared/Psionics/MindbrokenComponent.cs @@ -0,0 +1,12 @@ +namespace Content.Shared.Abilities.Psionics +{ + [RegisterComponent] + public sealed partial class MindbrokenComponent : Component + { + /// + /// The text that will appear when someone with the Mindbroken component is examined at close range + /// + [DataField] + public string MindbrokenExaminationText = "examine-mindbroken-message"; + } +} diff --git a/Content.Shared/Psionics/MindbrokenSystem.cs b/Content.Shared/Psionics/MindbrokenSystem.cs new file mode 100644 index 0000000000..6b6370f548 --- /dev/null +++ b/Content.Shared/Psionics/MindbrokenSystem.cs @@ -0,0 +1,20 @@ +using Content.Shared.Examine; + +namespace Content.Shared.Abilities.Psionics; + +public sealed class MindbrokenSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnExamined); + } + + private void OnExamined(EntityUid uid, MindbrokenComponent component, ExaminedEvent args) + { + if (!args.IsInDetailsRange) + return; + + args.PushMarkup($"[color=mediumpurple]{Loc.GetString(component.MindbrokenExaminationText, ("entity", uid))}[/color]"); + } +} \ No newline at end of file diff --git a/Content.Shared/Psionics/Passives/Psychognomy/PsychognomistComponent.cs b/Content.Shared/Psionics/Passives/Psychognomy/PsychognomistComponent.cs new file mode 100644 index 0000000000..5108ef5b41 --- /dev/null +++ b/Content.Shared/Psionics/Passives/Psychognomy/PsychognomistComponent.cs @@ -0,0 +1,5 @@ +namespace Content.Shared.Psionics.Passives; + +[RegisterComponent] +public sealed partial class PsychognomistComponent : Component { } + diff --git a/Content.Shared/Psionics/PsionicComponent.cs b/Content.Shared/Psionics/PsionicComponent.cs index 9091e03cfc..16e0f028de 100644 --- a/Content.Shared/Psionics/PsionicComponent.cs +++ b/Content.Shared/Psionics/PsionicComponent.cs @@ -1,20 +1,229 @@ -using Content.Shared.Actions; +using Content.Shared.DoAfter; +using Content.Shared.Psionics; using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; namespace Content.Shared.Abilities.Psionics { - [RegisterComponent, NetworkedComponent] + [RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)] public sealed partial class PsionicComponent : Component { - public EntityUid? PsionicAbility = null; + /// + /// Current Mana. + /// + [DataField, AutoNetworkedField] + public float Mana; + + /// + /// Max Mana Possible. + /// + [DataField, AutoNetworkedField] + public float MaxMana = 100; + + /// + /// How much energy is gained per second. + /// + [DataField] + public float ManaGain = 1; + + /// + /// ManaGain Multiplier + /// + [DataField] + public float ManaGainMultiplier = 1; + + public float ManaAccumulator; + + [DataField] + public bool BypassManaCheck; + + /// + /// How close a Psion is to generating a new power. When Potentia reaches the NextPowerCost, it is "Spent" in order to "Buy" a random new power. + /// TODO: Psi-Potentiometry should be able to read how much Potentia a person has. + /// + [DataField] + public float Potentia; + + /// + /// Each time a Psion rolls for a new power, they roll a number between 0 and 100, adding any relevant modifiers. This number is then added to Potentia, + /// meaning that it carries over between rolls. When a character has an amount of potentia equal to at least 100 * 2^(total powers), the potentia is then spent, and a power is generated. + /// This variable stores the cost of the next power. + /// + [DataField] + public float NextPowerCost = 100; + + /// + /// The baseline chance of obtaining a psionic power when rolling for one. + /// + [DataField] + public float Chance = 0.04f; + + /// + /// Whether or not a Psion has an available "Reroll" to spend on attempting to gain powers. + /// + [DataField] + public bool CanReroll = true; + + /// + /// The Base amount of time (in minutes) this Psion is given the stutter effect if they become mindbroken. + /// + [DataField] + public float MindbreakingStutterTime = 5; + + public string MindbreakingStutterCondition = "Stutter"; + + public string MindbreakingStutterAccent = "StutteringAccent"; + + /// + /// The message feedback given on mindbreak. + /// + [DataField] + public string MindbreakingFeedback = "mindbreaking-feedback"; + + /// + /// How much should the odds of obtaining a Psionic Power be multiplied when rolling for one. + /// + [DataField] + public float PowerRollMultiplier = 1f; + + /// + /// How much the odds of obtaining a Psionic Power should be multiplied when rolling for one. + + /// + [DataField] + public float PowerRollFlatBonus = 0; + + private (float, float) _baselineAmplification = (0.4f, 1.2f); + + /// + /// Use this datafield to change the range of Baseline Amplification. + /// + [DataField] + private (float, float) _baselineAmplificationFactors = (0.4f, 1.2f); /// - /// Ifrits, revenants, etc are explicitly magical beings that shouldn't get mindbreakered. + /// All Psionics automatically possess a random amount of initial starting Amplification, regardless of if they have any powers or not. + /// The game will crash if Robust.Random is handed a (bigger number, smaller number), so the logic here prevents any funny business. /// - [DataField("removable")] + public (float, float) BaselineAmplification + { + get { return _baselineAmplification; } + private set + { + _baselineAmplification = (Math.Min( + _baselineAmplificationFactors.Item1, _baselineAmplificationFactors.Item2), + Math.Max(_baselineAmplificationFactors.Item1, _baselineAmplificationFactors.Item2)); + } + } + private (float, float) _baselineDampening = (0.4f, 1.2f); + + /// + /// Use this datafield to change the range of Baseline Amplification. + /// + [DataField] + private (float, float) _baselineDampeningFactors = (0.4f, 1.2f); + + /// + /// All Psionics automatically possess a random amount of initial starting Dampening, regardless of if they have any powers or not. + /// The game will crash if Robust.Random is handed a (bigger number, smaller number), so the logic here prevents any funny business. + /// + public (float, float) BaselineDampening + { + get { return _baselineDampening; } + private set + { + _baselineDampening = (Math.Min( + _baselineDampeningFactors.Item1, _baselineDampeningFactors.Item2), + Math.Max(_baselineDampeningFactors.Item1, _baselineDampeningFactors.Item2)); + } + } + + /// + /// Ifrits, revenants, etc are explicitly magical beings that shouldn't get mindbroken + /// + [DataField] public bool Removable = true; - [DataField("activePowers")] - public HashSet ActivePowers = new(); + /// + /// The list of all powers currently active on a Psionic, by power Prototype. + /// TODO: Not in this PR due to scope, but this needs to go to Server and not Shared. + /// + [ViewVariables(VVAccess.ReadOnly)] + public HashSet ActivePowers = new(); + + /// + /// The list of each Psionic Power by action with entityUid. + /// + [ViewVariables(VVAccess.ReadOnly)] + public Dictionary Actions = new(); + + /// + /// What sources of Amplification does this Psion have? + /// + [ViewVariables(VVAccess.ReadOnly)] + public readonly Dictionary AmplificationSources = new(); + + /// + /// A measure of how "Powerful" a Psion is. + /// TODO: Implement this in a separate PR. + /// + [ViewVariables(VVAccess.ReadWrite)] + public float CurrentAmplification; + + /// + /// What sources of Dampening does this Psion have? + /// + [ViewVariables(VVAccess.ReadOnly)] + public readonly Dictionary DampeningSources = new(); + + /// + /// A measure of how "Controlled" a Psion is. + /// TODO: Implement this in a separate PR. + /// + [ViewVariables(VVAccess.ReadWrite)] + public float CurrentDampening; + + /// + /// How many "Slots" an entity has for psionic powers. This is not a hard limit, and is instead used for calculating the cost to generate new powers. + /// Exceeding this limit causes an entity to become a Glimmer Source. + /// + [DataField] + public int PowerSlots = 1; + + /// + /// How many "Slots" are currently occupied by psionic powers. + /// + [ViewVariables(VVAccess.ReadWrite)] + public int PowerSlotsTaken; + + /// + /// List of descriptors this entity will bring up for psychognomy. Used to remove + /// unneccesary subs for unique psionic entities like e.g. Oracle. + /// + [DataField] + public List? PsychognomicDescriptors = null; + + /// Used for tracking what spell a Psion is actively casting + [DataField] + public DoAfterId? DoAfter; + + /// Popup to play if a Psion attempts to start casting a power while already casting one + [DataField] + public string AlreadyCasting = "already-casting"; + + /// Popup to play if there no Mana left for a power to execute. + public string NoMana = "no-mana"; + + /// + /// The list of Familiars currently bound to this Psion. + /// + [DataField] + public List Familiars = new(); + + /// + /// The maximum number of Familiars a Psion may bind. + /// + [DataField] + public int FamiliarLimit = 1; } } diff --git a/Content.Shared/Psionics/PsionicEvents.cs b/Content.Shared/Psionics/PsionicEvents.cs new file mode 100644 index 0000000000..4d13441798 --- /dev/null +++ b/Content.Shared/Psionics/PsionicEvents.cs @@ -0,0 +1,17 @@ +namespace Content.Shared.Psionics; + +/// +/// This event is raised whenever a psionic entity sets their casting stats(Amplification and Dampening), allowing other systems to modify the end result +/// of casting stat math. Useful if for example you want a species to have 5% higher Amplification overall. Or a drug inhibits total Dampening, etc. +/// +/// +/// +/// +[ByRefEvent] +public record struct OnSetPsionicStatsEvent(float AmplificationChangedAmount, float DampeningChangedAmount); + +[ByRefEvent] +public record struct OnMindbreakEvent(); + +[ByRefEvent] +public record struct OnManaUpdateEvent(); \ No newline at end of file diff --git a/Content.Shared/Psionics/PsionicFamiliarComponent.cs b/Content.Shared/Psionics/PsionicFamiliarComponent.cs new file mode 100644 index 0000000000..d47b01e7e7 --- /dev/null +++ b/Content.Shared/Psionics/PsionicFamiliarComponent.cs @@ -0,0 +1,75 @@ +using Content.Shared.Popups; +using Robust.Shared.GameStates; + +namespace Content.Shared.Abilities.Psionics; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)] +public sealed partial class PsionicFamiliarComponent : Component +{ + /// + /// The entity that summoned this Familiar. + /// + [DataField, AutoNetworkedField] + public EntityUid? Master; + + /// + /// Whether the familiar is allowed to attack its Master. + /// + [DataField] + public bool CanAttackMaster; + + /// + /// Popup to play when a familiar that isn't allowed to attack its Master, attempts to do so. + /// + [DataField] + public string AttackMasterText = "psionic-familiar-cant-attack-master"; + + /// + /// Popup type to use when failing to attack the familiar's Master. + /// + [DataField] + public PopupType AttackPopupType = PopupType.SmallCaution; + + /// + /// Text to display when a Familiar is forced to return from whence it came. + /// + [DataField] + public string DespawnText = "psionic-familiar-despawn-text"; + + /// + /// Popup type to use when a Familiar is forced to return from whence it came. + /// + [DataField] + public PopupType DespawnPopopType = PopupType.MediumCaution; + + /// + /// Whether a Psionic Familiar is sent back from whence it came if its Master dies. + /// + [DataField] + public bool DespawnOnMasterDeath = true; + + /// + /// Whether a Psionic Familiar is sent back from whence it came if it dies. + /// + [DataField] + public bool DespawnOnFamiliarDeath = true; + + /// + /// Whether a Psionic Familiar is sent back from whence it came if its Master is mindbroken. + /// + [DataField] + public bool DespawnOnMasterMindbroken = true; + + /// + /// Should the Familiar despawn when the player controlling it disconnects. + /// + [DataField] + public bool DespawnOnPlayerDetach; + + /// + /// Whether a Psionic Familiar inherits its Master's factions. + /// This can get people into trouble if the familiar inherits a hostile faction such as Syndicate. + /// + [DataField] + public bool InheritMasterFactions = true; +} diff --git a/Content.Shared/Psionics/PsionicPowerPrototype.cs b/Content.Shared/Psionics/PsionicPowerPrototype.cs new file mode 100644 index 0000000000..d81ae05be2 --- /dev/null +++ b/Content.Shared/Psionics/PsionicPowerPrototype.cs @@ -0,0 +1,95 @@ +using Content.Shared.Chat; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Psionics; + +[Prototype] +public sealed partial class PsionicPowerPrototype : IPrototype +{ + /// + /// The ID of the psionic power to use. + /// + [IdDataField] + public string ID { get; } = default!; + + /// + /// The name of the psionic power. + /// + [DataField(required: true)] + public string Name = default!; + + /// + /// The description of a power in yml, used for player notifications. + /// + [DataField(required: true)] + public string Description = default!; + + /// + /// The list of each Action that this power adds in the form of ActionId and ActionEntity + /// + [DataField] + public List Actions = new(); + + /// + /// The list of what Components this power adds. + /// + [DataField] + public ComponentRegistry Components = new(); + + /// + /// What message will be sent to the player as a Popup. + /// If left blank, it will default to the Const "generic-power-initialization-feedback" + /// + [DataField] + public string? InitializationPopup; + + /// + /// What message will be sent to the chat window when the power is initialized. Leave it blank to send no message. + /// Initialization messages won't play for powers that are Innate, only powers obtained during the round. + /// These should generally also be written in the first person, and can be far lengthier than popups. + /// + [DataField] + public string? InitializationFeedback; + + /// + /// What color will the initialization feedback display in the chat window with. + /// + [DataField] + public string InitializationFeedbackColor = "#8A00C2"; + + /// + /// What font size will the initialization message use in chat. + /// + [DataField] + public int InitializationFeedbackFontSize = 12; + + /// + /// Which chat channel will the initialization message use. + /// + [DataField] + public ChatChannel InitializationFeedbackChannel = ChatChannel.Emotes; + + /// + /// What message will this power generate when scanned by a Metempsionic Focused Pulse. + /// + [DataField] + public string MetapsionicFeedback = "psionic-metapsionic-feedback-default"; + + /// + /// How much this power will increase or decrease a user's Amplification. + /// + [DataField] + public float AmplificationModifier = 0; + + /// + /// How much this power will increase or decrease a user's Dampening. + /// + [DataField] + public float DampeningModifier = 0; + + /// + /// How many "Power Slots" this power occupies. + /// + [DataField] + public int PowerSlotCost = 1; +} \ No newline at end of file diff --git a/Content.Shared/Psionics/SharedPsionicAbilitiesSystem.cs b/Content.Shared/Psionics/SharedPsionicAbilitiesSystem.cs index 2739d5ba31..b79dabbc41 100644 --- a/Content.Shared/Psionics/SharedPsionicAbilitiesSystem.cs +++ b/Content.Shared/Psionics/SharedPsionicAbilitiesSystem.cs @@ -1,31 +1,72 @@ -using Content.Shared.Actions; using Content.Shared.Administration.Logs; -using Content.Shared.Mobs; -using Content.Shared.Mobs.Components; +using Content.Shared.Contests; using Content.Shared.Popups; +using Content.Shared.Psionics; using Content.Shared.Psionics.Glimmer; using Robust.Shared.Random; using Robust.Shared.Serialization; +using Content.Shared.Mobs.Systems; +using Content.Shared.FixedPoint; +using Content.Shared.Rejuvenate; namespace Content.Shared.Abilities.Psionics { public sealed class SharedPsionicAbilitiesSystem : EntitySystem { - [Dependency] private readonly SharedActionsSystem _actions = default!; [Dependency] private readonly EntityLookupSystem _lookup = default!; [Dependency] private readonly SharedPopupSystem _popups = default!; [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly GlimmerSystem _glimmerSystem = default!; [Dependency] private readonly IRobustRandom _robustRandom = default!; + [Dependency] private readonly ContestsSystem _contests = default!; + [Dependency] private readonly MobStateSystem _mobState = default!; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnShutdown); SubscribeLocalEvent(OnPowerUsed); + SubscribeLocalEvent(OnRejuvenate); + } + + public bool OnAttemptPowerUse(EntityUid uid, string power, float? manacost = null, bool checkInsulation = true) + { + if (!TryComp(uid, out var component) + || HasComp(uid) + || checkInsulation + && HasComp(uid)) + return false; + + var tev = new OnAttemptPowerUseEvent(uid, power); + RaiseLocalEvent(uid, tev); + + if (tev.Cancelled) + return false; + + if (component.DoAfter is not null) + { + _popups.PopupEntity(Loc.GetString(component.AlreadyCasting), uid, uid, PopupType.LargeCaution); + return false; + } + + if (manacost is null) + return true; + + if (component.Mana >= manacost + || component.BypassManaCheck) + { + var newmana = component.Mana - manacost; + component.Mana = newmana ?? component.Mana; + + var ev = new OnManaUpdateEvent(); + RaiseLocalEvent(uid, ref ev); + } + else + { + _popups.PopupEntity(Loc.GetString(component.NoMana), uid, uid, PopupType.LargeCaution); + return false; + } - SubscribeLocalEvent(OnMobStateChanged); + return true; } private void OnPowerUsed(EntityUid uid, PsionicComponent component, PsionicPowerUsedEvent args) @@ -41,54 +82,94 @@ private void OnPowerUsed(EntityUid uid, PsionicComponent component, PsionicPower } } - private void OnInit(EntityUid uid, PsionicsDisabledComponent component, ComponentInit args) + public void LogPowerUsed(EntityUid uid, string power, int minGlimmer = 8, int maxGlimmer = 12) { - SetPsionicsThroughEligibility(uid); + _adminLogger.Add(Database.LogType.Psionics, Database.LogImpact.Medium, $"{ToPrettyString(uid):player} used {power}"); + var ev = new PsionicPowerUsedEvent(uid, power); + RaiseLocalEvent(uid, ev, false); + + _glimmerSystem.Glimmer += _robustRandom.Next(minGlimmer, maxGlimmer); } - private void OnShutdown(EntityUid uid, PsionicsDisabledComponent component, ComponentShutdown args) + /// + /// Returns the CurrentAmplification of a given Entity, multiplied by the result of that Entity's MoodContest. + /// Higher mood means more Amplification, Lower mood means less Amplification. + /// + public float ModifiedAmplification(EntityUid uid) { - SetPsionicsThroughEligibility(uid); + if (!TryComp(uid, out var psionicComponent)) + return 1; + + return ModifiedAmplification(uid, psionicComponent); } - private void OnMobStateChanged(EntityUid uid, PsionicComponent component, MobStateChangedEvent args) + /// + /// Returns the CurrentAmplification of a given Entity, multiplied by the result of that Entity's MoodContest. + /// Higher mood means more Amplification, Lower mood means less Amplification. + /// + public float ModifiedAmplification(EntityUid uid, PsionicComponent component) { - SetPsionicsThroughEligibility(uid); + return component.CurrentAmplification * _contests.MoodContest(uid, true); } /// - /// Checks whether the entity is eligible to use its psionic ability. This should be run after anything that could effect psionic eligibility. + /// Returns the CurrentDampening of a given Entity, multiplied by the result of that Entity's MoodContest. + /// Lower mood means more Dampening, higher mood means less Dampening. /// - public void SetPsionicsThroughEligibility(EntityUid uid) + public float ModifiedDampening(EntityUid uid) { - PsionicComponent? component = null; - if (!Resolve(uid, ref component, false)) - return; + if (!TryComp(uid, out var psionicComponent)) + return 1; - if (component.PsionicAbility == null) - return; - - _actions.TryGetActionData( component.PsionicAbility, out var actionData ); - - if (actionData == null) - return; + return ModifiedDampening(uid, psionicComponent); + } - _actions.SetEnabled(actionData.Owner, IsEligibleForPsionics(uid)); + /// + /// Returns the CurrentDampening of a given Entity, multiplied by the result of that Entity's MoodContest. + /// Lower mood means more Dampening, higher mood means less Dampening. + /// + public float ModifiedDampening(EntityUid uid, PsionicComponent component) + { + return component.CurrentDampening / _contests.MoodContest(uid, true); } - private bool IsEligibleForPsionics(EntityUid uid) + public void OnRejuvenate(EntityUid uid, PsionicComponent component, RejuvenateEvent args) { - return !HasComp(uid) - && (!TryComp(uid, out var mobstate) || mobstate.CurrentState == MobState.Alive); + component.Mana = component.MaxMana; + var ev = new OnManaUpdateEvent(); + RaiseLocalEvent(uid, ref ev); } - public void LogPowerUsed(EntityUid uid, string power, int minGlimmer = 8, int maxGlimmer = 12) + public override void Update(float frameTime) { - _adminLogger.Add(Database.LogType.Psionics, Database.LogImpact.Medium, $"{ToPrettyString(uid):player} used {power}"); - var ev = new PsionicPowerUsedEvent(uid, power); - RaiseLocalEvent(uid, ev, false); + base.Update(frameTime); - _glimmerSystem.Glimmer += _robustRandom.Next(minGlimmer, maxGlimmer); + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var component)) + { + if (_mobState.IsDead(uid)) + continue; + + component.ManaAccumulator += frameTime; + + if (component.ManaAccumulator <= 1) + continue; + + component.ManaAccumulator -= 1; + + if (component.Mana > component.MaxMana) + component.Mana = component.MaxMana; + + if (component.Mana < component.MaxMana) + { + var gainedmana = component.ManaGain * component.ManaGainMultiplier; + component.Mana += gainedmana; + FixedPoint2.Min(component.Mana, component.MaxMana); + + var ev = new OnManaUpdateEvent(); + RaiseLocalEvent(uid, ref ev); + } + } } } @@ -104,6 +185,18 @@ public PsionicPowerUsedEvent(EntityUid user, string power) } } + public sealed class OnAttemptPowerUseEvent : CancellableEntityEventArgs + { + public EntityUid User { get; } + public string Power = string.Empty; + + public OnAttemptPowerUseEvent(EntityUid user, string power) + { + User = user; + Power = power; + } + } + [Serializable] [NetSerializable] public sealed class PsionicsChangedEvent : EntityEventArgs diff --git a/Content.Shared/Psionics/TelepathyComponent.cs b/Content.Shared/Psionics/TelepathyComponent.cs new file mode 100644 index 0000000000..9915ed7ef6 --- /dev/null +++ b/Content.Shared/Psionics/TelepathyComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Abilities.Psionics; + +/// +/// Used for determining eligibility for Telepathy. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class TelepathyComponent : Component { } diff --git a/Content.Shared/RCD/RCDPrototype.cs b/Content.Shared/RCD/RCDPrototype.cs index 1e80abfb72..58093bbe87 100644 --- a/Content.Shared/RCD/RCDPrototype.cs +++ b/Content.Shared/RCD/RCDPrototype.cs @@ -9,7 +9,7 @@ namespace Content.Shared.RCD; /// Contains the parameters for a RCD construction / operation /// [Prototype("rcd")] -public sealed class RCDPrototype : IPrototype +public sealed partial class RCDPrototype : IPrototype { [IdDataField] public string ID { get; private set; } = default!; @@ -51,7 +51,7 @@ public sealed class RCDPrototype : IPrototype public int Cost { get; private set; } = 1; /// - /// The length of the operation + /// The length of the operation /// [DataField, ViewVariables(VVAccess.ReadOnly)] public float Delay { get; private set; } = 1f; @@ -75,7 +75,7 @@ public sealed class RCDPrototype : IPrototype public CollisionGroup CollisionMask { get; private set; } = CollisionGroup.None; /// - /// Specifies a set of custom collision bounds for determining whether the entity prototype will fit into a target tile + /// Specifies a set of custom collision bounds for determining whether the entity prototype will fit into a target tile /// /// /// Should be set assuming that the entity faces south. @@ -106,7 +106,7 @@ private set private Box2? _collisionBounds = null; /// - /// The polygon shape associated with the prototype CollisionBounds (if set) + /// The polygon shape associated with the prototype CollisionBounds (if set) /// [ViewVariables(VVAccess.ReadOnly)] public PolygonShape? CollisionPolygon { get; private set; } = null; diff --git a/Content.Shared/RadialSelector/Ui.cs b/Content.Shared/RadialSelector/Ui.cs new file mode 100644 index 0000000000..656e194dfe --- /dev/null +++ b/Content.Shared/RadialSelector/Ui.cs @@ -0,0 +1,52 @@ +using Robust.Shared.Serialization; +using Robust.Shared.Utility; + +namespace Content.Shared.RadialSelector; + +[NetSerializable, Serializable] +public enum RadialSelectorUiKey : byte +{ + Key, +} + +[Serializable, NetSerializable] +public sealed class RadialSelectorState(List entries, bool openCentered = false) + : BoundUserInterfaceState +{ + [DataField(required: true)] + public List Entries = entries; + + public bool OpenCentered { get; } = openCentered; +} + +[Serializable, NetSerializable] +public sealed class RadialSelectorSelectedMessage(string selectedItem) : BoundUserInterfaceMessage +{ + public string SelectedItem { get; private set; } = selectedItem; +} + +[DataDefinition, Serializable, NetSerializable] +public sealed partial class RadialSelectorEntry +{ + [DataField] + public string? Prototype { get; set; } + + [DataField] + public SpriteSpecifier? Icon { get; set; } + + [DataField] + public RadialSelectorCategory? Category { get; set; } +} + +[DataDefinition, Serializable, NetSerializable] +public sealed partial class RadialSelectorCategory +{ + [DataField(required: true)] + public string Name { get; set; } = string.Empty; + + [DataField(required: true)] + public SpriteSpecifier Icon { get; set; } = default!; + + [DataField(required: true)] + public List Entries { get; set; } = new(); +} diff --git a/Content.Shared/Radio/Components/EncryptionKeyHolderComponent.cs b/Content.Shared/Radio/Components/EncryptionKeyHolderComponent.cs index bd49acf909..d84a161860 100644 --- a/Content.Shared/Radio/Components/EncryptionKeyHolderComponent.cs +++ b/Content.Shared/Radio/Components/EncryptionKeyHolderComponent.cs @@ -15,33 +15,34 @@ public sealed partial class EncryptionKeyHolderComponent : Component /// /// Whether or not encryption keys can be removed from the headset. /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("keysUnlocked")] + [DataField] public bool KeysUnlocked = true; /// /// The tool required to extract the encryption keys from the headset. /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("keysExtractionMethod", customTypeSerializer: typeof(PrototypeIdSerializer))] + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] public string KeysExtractionMethod = "Screwing"; - [ViewVariables(VVAccess.ReadWrite)] - [DataField("keySlots")] + [DataField] public int KeySlots = 2; - [ViewVariables(VVAccess.ReadWrite)] - [DataField("keyExtractionSound")] + [DataField] public SoundSpecifier KeyExtractionSound = new SoundPathSpecifier("/Audio/Items/pistol_magout.ogg"); - [ViewVariables(VVAccess.ReadWrite)] - [DataField("keyInsertionSound")] + [DataField] public SoundSpecifier KeyInsertionSound = new SoundPathSpecifier("/Audio/Items/pistol_magin.ogg"); [ViewVariables] public Container KeyContainer = default!; public const string KeyContainerName = "key_slots"; + /// + /// Whether or not the headset can be examined to see the encryption keys while the keys aren't accessible. + /// + [DataField] + public bool ExamineWhileLocked = true; + /// /// Combined set of radio channels provided by all contained keys. /// diff --git a/Content.Shared/Radio/Components/SharedRadioJammerComponent.cs b/Content.Shared/Radio/Components/SharedRadioJammerComponent.cs new file mode 100644 index 0000000000..e5e52a3e47 --- /dev/null +++ b/Content.Shared/Radio/Components/SharedRadioJammerComponent.cs @@ -0,0 +1,74 @@ +using Robust.Shared.Serialization; +using Robust.Shared.GameStates; + +namespace Content.Shared.RadioJammer; + +/// +/// When activated () prevents from sending messages in range +/// Suit sensors will also stop working. +/// +[NetworkedComponent, RegisterComponent] +public sealed partial class RadioJammerComponent : Component +{ + [DataDefinition] + public partial struct RadioJamSetting + { + /// + /// Power usage per second when enabled. + /// + [DataField(required: true)] + public float Wattage; + + /// + /// Range of the jammer. + /// + [DataField(required: true)] + public float Range; + + /// + /// The message that is displayed when switched + /// to this setting. + /// + [DataField(required: true)] + public LocId Message = string.Empty; + + /// + /// Name of the setting. + /// + [DataField(required: true)] + public LocId Name = string.Empty; + } + + /// + /// List of all the settings for the radio jammer. + /// + [DataField(required: true), ViewVariables(VVAccess.ReadOnly)] + public RadioJamSetting[] Settings; + + /// + /// Index of the currently selected setting. + /// + [DataField] + public int SelectedPowerLevel = 1; +} + +[Serializable, NetSerializable] +public enum RadioJammerChargeLevel : byte +{ + Low, + Medium, + High +} + +[Serializable, NetSerializable] +public enum RadioJammerLayers : byte +{ + LED +} + +[Serializable, NetSerializable] +public enum RadioJammerVisuals : byte +{ + ChargeLevel, + LEDOn +} diff --git a/Content.Shared/Radio/EntitySystems/EncryptionKeySystem.cs b/Content.Shared/Radio/EntitySystems/EncryptionKeySystem.cs index 746147eb5b..a15b6f76e9 100644 --- a/Content.Shared/Radio/EntitySystems/EncryptionKeySystem.cs +++ b/Content.Shared/Radio/EntitySystems/EncryptionKeySystem.cs @@ -6,10 +6,8 @@ using Content.Shared.Interaction; using Content.Shared.Popups; using Content.Shared.Radio.Components; -using Content.Shared.Tools; using Content.Shared.Tools.Components; using Content.Shared.Wires; -using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Containers; using Robust.Shared.Network; @@ -175,7 +173,9 @@ private void OnStartup(EntityUid uid, EncryptionKeyHolderComponent component, Co private void OnHolderExamined(EntityUid uid, EncryptionKeyHolderComponent component, ExaminedEvent args) { - if (!args.IsInDetailsRange) + if (!args.IsInDetailsRange + || !component.ExamineWhileLocked && !component.KeysUnlocked + || !component.ExamineWhileLocked && TryComp(uid, out var panel) && !panel.Open) return; if (component.KeyContainer.ContainedEntities.Count == 0) diff --git a/Content.Shared/Radio/EntitySystems/SharedJammerSystem.cs b/Content.Shared/Radio/EntitySystems/SharedJammerSystem.cs new file mode 100644 index 0000000000..e1f632735c --- /dev/null +++ b/Content.Shared/Radio/EntitySystems/SharedJammerSystem.cs @@ -0,0 +1,78 @@ +using Content.Shared.Popups; +using Content.Shared.DeviceNetwork.Components; +using Content.Shared.Verbs; +using Content.Shared.RadioJammer; + +namespace Content.Shared.Radio.EntitySystems; + +public abstract class SharedJammerSystem : EntitySystem +{ + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] protected readonly SharedPopupSystem Popup = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent>(OnGetVerb); + } + + private void OnGetVerb(Entity entity, ref GetVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract) + return; + + var user = args.User; + + byte index = 0; + foreach (var setting in entity.Comp.Settings) + { + // This is because Act wont work with index. + // Needs it to be saved in the loop. + var currIndex = index; + var verb = new Verb + { + Priority = currIndex, + Category = VerbCategory.PowerLevel, + Disabled = entity.Comp.SelectedPowerLevel == currIndex, + Act = () => + { + entity.Comp.SelectedPowerLevel = currIndex; + if (TryComp(entity.Owner, out var jammerComp)) + { + // This is a little sketcy but only way to do it. + jammerComp.Range = GetCurrentRange(entity.Comp); + Dirty(entity.Owner, jammerComp); + } + Popup.PopupPredicted(Loc.GetString(setting.Message), user, user); + }, + Text = Loc.GetString(setting.Name), + }; + args.Verbs.Add(verb); + index++; + } + } + + public float GetCurrentWattage(RadioJammerComponent jammer) + { + return jammer.Settings[jammer.SelectedPowerLevel].Wattage; + } + + public float GetCurrentRange(RadioJammerComponent jammer) + { + return jammer.Settings[jammer.SelectedPowerLevel].Range; + } + + protected void ChangeLEDState(bool isLEDOn, EntityUid uid, + AppearanceComponent? appearance = null) + { + _appearance.SetData(uid, RadioJammerVisuals.LEDOn, isLEDOn, appearance); + } + + protected void ChangeChargeLevel(RadioJammerChargeLevel chargeLevel, EntityUid uid, + AppearanceComponent? appearance = null) + { + _appearance.SetData(uid, RadioJammerVisuals.ChargeLevel, chargeLevel, appearance); + } + +} diff --git a/Content.Shared/Random/Helpers/SharedRandomExtensions.cs b/Content.Shared/Random/Helpers/SharedRandomExtensions.cs index dcdc9d61d9..20e57e9421 100644 --- a/Content.Shared/Random/Helpers/SharedRandomExtensions.cs +++ b/Content.Shared/Random/Helpers/SharedRandomExtensions.cs @@ -57,7 +57,8 @@ public static string Pick(this IWeightedRandomPrototype prototype, IRobustRandom throw new InvalidOperationException($"Invalid weighted pick for {prototype.ID}!"); } - public static string Pick(this IRobustRandom random, Dictionary weights) + public static T Pick(this IRobustRandom random, Dictionary weights) + where T: notnull { var sum = weights.Values.Sum(); var accumulated = 0f; @@ -74,7 +75,7 @@ public static string Pick(this IRobustRandom random, Dictionary w } } - throw new InvalidOperationException($"Invalid weighted pick"); + throw new InvalidOperationException("Invalid weighted pick"); } public static (string reagent, FixedPoint2 quantity) Pick(this WeightedRandomFillSolutionPrototype prototype, IRobustRandom? random = null) diff --git a/Content.Shared/ReagentSpeed/ReagentSpeedComponent.cs b/Content.Shared/ReagentSpeed/ReagentSpeedComponent.cs new file mode 100644 index 0000000000..d233cad2a0 --- /dev/null +++ b/Content.Shared/ReagentSpeed/ReagentSpeedComponent.cs @@ -0,0 +1,34 @@ +using Content.Shared.Chemistry.Reagent; +using Content.Shared.FixedPoint; +using Robust.Shared.Prototypes; + +namespace Content.Shared.ReagentSpeed; + +/// +/// Makes a device work faster by consuming reagents on each use. +/// Other systems must use for this to do anything. +/// +[RegisterComponent, Access(typeof(ReagentSpeedSystem))] +public sealed partial class ReagentSpeedComponent : Component +{ + /// + /// Solution that will be checked. + /// Anything that isn't in Modifiers is left alone. + /// + [DataField(required: true)] + public string Solution = string.Empty; + + /// + /// How much reagent from the solution to use up for each use. + /// This is per-modifier-reagent and not shared between them. + /// + [DataField] + public FixedPoint2 Cost = 5; + + /// + /// Reagents and how much they modify speed at full purity. + /// Small number means faster large number means slower. + /// + [DataField(required: true)] + public Dictionary, float> Modifiers = new(); +} diff --git a/Content.Shared/ReagentSpeed/ReagentSpeedSystem.cs b/Content.Shared/ReagentSpeed/ReagentSpeedSystem.cs new file mode 100644 index 0000000000..8561c7b12a --- /dev/null +++ b/Content.Shared/ReagentSpeed/ReagentSpeedSystem.cs @@ -0,0 +1,33 @@ +using Content.Shared.Chemistry.EntitySystems; + +namespace Content.Shared.ReagentSpeed; + +public sealed class ReagentSpeedSystem : EntitySystem +{ + [Dependency] private readonly SharedSolutionContainerSystem _solution = default!; + + /// + /// Consumes reagents and modifies the duration. + /// This can be production time firing delay etc. + /// + public TimeSpan ApplySpeed(Entity ent, TimeSpan time) + { + if (!Resolve(ent, ref ent.Comp, false)) + return time; + + if (!_solution.TryGetSolution(ent.Owner, ent.Comp.Solution, out _, out var solution)) + return time; + + foreach (var (reagent, fullModifier) in ent.Comp.Modifiers) + { + var used = solution.RemoveReagent(reagent, ent.Comp.Cost); + var efficiency = (used / ent.Comp.Cost).Float(); + // scale the speed modifier so microdosing has less effect + var reduction = (1f - fullModifier) * efficiency; + var modifier = 1f - reduction; + time *= modifier; + } + + return time; + } +} diff --git a/Content.Shared/Robotics/Components/RoboticsConsoleComponent.cs b/Content.Shared/Robotics/Components/RoboticsConsoleComponent.cs new file mode 100644 index 0000000000..4329e437a2 --- /dev/null +++ b/Content.Shared/Robotics/Components/RoboticsConsoleComponent.cs @@ -0,0 +1,53 @@ +using Content.Shared.Radio; +using Content.Shared.Robotics; +using Content.Shared.Robotics.Systems; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; + +namespace Content.Shared.Robotics.Components; + +/// +/// Robotics console for managing borgs. +/// +[RegisterComponent, NetworkedComponent, Access(typeof(SharedRoboticsConsoleSystem))] +[AutoGenerateComponentState, AutoGenerateComponentPause] +public sealed partial class RoboticsConsoleComponent : Component +{ + /// + /// Address and data of each cyborg. + /// + [DataField] + public Dictionary Cyborgs = new(); + + /// + /// After not responding for this length of time borgs are removed from the console. + /// + [DataField] + public TimeSpan Timeout = TimeSpan.FromSeconds(10); + + /// + /// Radio channel to send messages on. + /// + [DataField] + public ProtoId RadioChannel = "Science"; + + /// + /// Radio message sent when destroying a borg. + /// + [DataField] + public LocId DestroyMessage = "robotics-console-cyborg-destroyed"; + + /// + /// Cooldown on destroying borgs to prevent complete abuse. + /// + [DataField] + public TimeSpan DestroyCooldown = TimeSpan.FromSeconds(30); + + /// + /// When a borg can next be destroyed. + /// + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] + [AutoNetworkedField, AutoPausedField] + public TimeSpan NextDestroy = TimeSpan.Zero; +} diff --git a/Content.Shared/Robotics/RoboticsConsoleUi.cs b/Content.Shared/Robotics/RoboticsConsoleUi.cs new file mode 100644 index 0000000000..1be89beff0 --- /dev/null +++ b/Content.Shared/Robotics/RoboticsConsoleUi.cs @@ -0,0 +1,126 @@ +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; +using Robust.Shared.Utility; + +namespace Content.Shared.Robotics; + +[Serializable, NetSerializable] +public enum RoboticsConsoleUiKey : byte +{ + Key +} + +[Serializable, NetSerializable] +public sealed class RoboticsConsoleState : BoundUserInterfaceState +{ + /// + /// Map of device network addresses to cyborg data. + /// + public Dictionary Cyborgs; + + public RoboticsConsoleState(Dictionary cyborgs) + { + Cyborgs = cyborgs; + } +} + +/// +/// Message to disable the selected cyborg. +/// +[Serializable, NetSerializable] +public sealed class RoboticsConsoleDisableMessage : BoundUserInterfaceMessage +{ + public readonly string Address; + + public RoboticsConsoleDisableMessage(string address) + { + Address = address; + } +} + +/// +/// Message to destroy the selected cyborg. +/// +[Serializable, NetSerializable] +public sealed class RoboticsConsoleDestroyMessage : BoundUserInterfaceMessage +{ + public readonly string Address; + + public RoboticsConsoleDestroyMessage(string address) + { + Address = address; + } +} + +/// +/// All data a client needs to render the console UI for a single cyborg. +/// Created by BorgTransponderComponent and sent to clients by RoboticsConsoleComponent. +/// +[DataRecord, Serializable, NetSerializable] +public record struct CyborgControlData +{ + /// + /// Texture of the borg chassis. + /// + [DataField(required: true)] + public SpriteSpecifier? ChassisSprite; + + /// + /// Name of the borg chassis. + /// + [DataField(required: true)] + public string ChassisName = string.Empty; + + /// + /// Name of the borg's entity, including its silicon id. + /// + [DataField(required: true)] + public string Name = string.Empty; + + /// + /// Battery charge from 0 to 1. + /// + [DataField] + public float Charge; + + /// + /// How many modules this borg has, just useful information for roboticists. + /// Lets them keep track of the latejoin borgs that need new modules and stuff. + /// + [DataField] + public int ModuleCount; + + /// + /// Whether the borg has a brain installed or not. + /// + [DataField] + public bool HasBrain; + + /// + /// When this cyborg's data will be deleted. + /// Set by the console when receiving the packet. + /// + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] + public TimeSpan Timeout = TimeSpan.Zero; + + public CyborgControlData(SpriteSpecifier? chassisSprite, string chassisName, string name, float charge, int moduleCount, bool hasBrain) + { + ChassisSprite = chassisSprite; + ChassisName = chassisName; + Name = name; + Charge = charge; + ModuleCount = moduleCount; + HasBrain = hasBrain; + } +} + +public static class RoboticsConsoleConstants +{ + // broadcast by cyborgs on Robotics Console frequency + public const string NET_CYBORG_DATA = "cyborg-data"; + + // sent by robotics console to cyborgs on Cyborg Control frequency + public const string NET_DISABLE_COMMAND = "cyborg-disable"; + public const string NET_DESTROY_COMMAND = "cyborg-destroy"; +} diff --git a/Content.Shared/Robotics/Systems/SharedRoboticsConsoleSystem.cs b/Content.Shared/Robotics/Systems/SharedRoboticsConsoleSystem.cs new file mode 100644 index 0000000000..25b3c5d07a --- /dev/null +++ b/Content.Shared/Robotics/Systems/SharedRoboticsConsoleSystem.cs @@ -0,0 +1,8 @@ +namespace Content.Shared.Robotics.Systems; + +/// +/// Does nothing, only exists for access right now. +/// +public abstract class SharedRoboticsConsoleSystem : EntitySystem +{ +} diff --git a/Content.Shared/Roles/JobPrototype.cs b/Content.Shared/Roles/JobPrototype.cs index 15f8233aab..5cf8cf38fb 100644 --- a/Content.Shared/Roles/JobPrototype.cs +++ b/Content.Shared/Roles/JobPrototype.cs @@ -123,6 +123,9 @@ public sealed partial class JobPrototype : IPrototype [DataField("extendedAccessGroups")] public IReadOnlyCollection> ExtendedAccessGroups { get; private set; } = Array.Empty>(); + + [DataField] + public bool Whitelisted; } /// diff --git a/Content.Shared/Roles/Jobs/SharedJobSystem.cs b/Content.Shared/Roles/Jobs/SharedJobSystem.cs index 04ac45c4c5..fcf7605278 100644 --- a/Content.Shared/Roles/Jobs/SharedJobSystem.cs +++ b/Content.Shared/Roles/Jobs/SharedJobSystem.cs @@ -146,8 +146,10 @@ public string MindTryGetJobName([NotNullWhen(true)] EntityUid? mindId) public bool CanBeAntag(ICommonSession player) { + // If the player does not have any mind associated with them (e.g., has not spawned in or is in the lobby), then + // they are eligible to be given an antag role/entity. if (_playerSystem.ContentData(player) is not { Mind: { } mindId }) - return false; + return true; if (!MindTryGetJob(mindId, out _, out var prototype)) return true; diff --git a/Content.Shared/Roles/SharedRoleSystem.cs b/Content.Shared/Roles/SharedRoleSystem.cs index e8053e4c67..d5ac2e5923 100644 --- a/Content.Shared/Roles/SharedRoleSystem.cs +++ b/Content.Shared/Roles/SharedRoleSystem.cs @@ -1,3 +1,4 @@ +using System.Linq; using Content.Shared.Administration.Logs; using Content.Shared.Database; using Content.Shared.Mind; @@ -62,6 +63,64 @@ protected void SubscribeAntagEvents() where T : AntagonistRoleComponent _antagTypes.Add(typeof(T)); } + public void MindAddRoles(EntityUid mindId, ComponentRegistry components, MindComponent? mind = null, bool silent = false) + { + if (!Resolve(mindId, ref mind)) + return; + + EntityManager.AddComponents(mindId, components); + var antagonist = false; + foreach (var compReg in components.Values) + { + var compType = compReg.Component.GetType(); + + var comp = EntityManager.ComponentFactory.GetComponent(compType); + if (IsAntagonistRole(comp.GetType())) + { + antagonist = true; + break; + } + } + + var mindEv = new MindRoleAddedEvent(silent); + RaiseLocalEvent(mindId, ref mindEv); + + var message = new RoleAddedEvent(mindId, mind, antagonist, silent); + if (mind.OwnedEntity != null) + { + RaiseLocalEvent(mind.OwnedEntity.Value, message, true); + } + + _adminLogger.Add(LogType.Mind, LogImpact.Low, + $"Role components {string.Join(components.Keys.ToString(), ", ")} added to mind of {_minds.MindOwnerLoggingString(mind)}"); + } + + public void MindAddRole(EntityUid mindId, Component component, MindComponent? mind = null, bool silent = false) + { + if (!Resolve(mindId, ref mind)) + return; + + if (HasComp(mindId, component.GetType())) + { + throw new ArgumentException($"We already have this role: {component}"); + } + + EntityManager.AddComponent(mindId, component); + var antagonist = IsAntagonistRole(component.GetType()); + + var mindEv = new MindRoleAddedEvent(silent); + RaiseLocalEvent(mindId, ref mindEv); + + var message = new RoleAddedEvent(mindId, mind, antagonist, silent); + if (mind.OwnedEntity != null) + { + RaiseLocalEvent(mind.OwnedEntity.Value, message, true); + } + + _adminLogger.Add(LogType.Mind, LogImpact.Low, + $"'Role {component}' added to mind of {_minds.MindOwnerLoggingString(mind)}"); + } + /// /// Gives this mind a new role. /// @@ -137,11 +196,13 @@ public bool MindTryRemoveRole(EntityUid mindId) where T : IComponent public bool MindHasRole(EntityUid mindId) where T : IComponent { + DebugTools.Assert(HasComp(mindId)); return HasComp(mindId); } public List MindGetAllRoles(EntityUid mindId) { + DebugTools.Assert(HasComp(mindId)); var ev = new MindGetAllRolesEvent(new List()); RaiseLocalEvent(mindId, ref ev); return ev.Roles; @@ -152,6 +213,7 @@ public bool MindIsAntagonist(EntityUid? mindId) if (mindId == null) return false; + DebugTools.Assert(HasComp(mindId)); var ev = new MindIsAntagonistEvent(); RaiseLocalEvent(mindId.Value, ref ev); return ev.IsAntagonist; @@ -177,6 +239,11 @@ public bool IsAntagonistRole() return _antagTypes.Contains(typeof(T)); } + public bool IsAntagonistRole(Type component) + { + return _antagTypes.Contains(component); + } + /// /// Play a sound for the mind, if it has a session attached. /// Use this for role greeting sounds. diff --git a/Content.Shared/Roles/StartingGearEquippedEvent.cs b/Content.Shared/Roles/StartingGearEquippedEvent.cs new file mode 100644 index 0000000000..41b6caccff --- /dev/null +++ b/Content.Shared/Roles/StartingGearEquippedEvent.cs @@ -0,0 +1,10 @@ +namespace Content.Shared.Roles; + +/// +/// Raised directed on an entity when a new starting gear prototype has been equipped. +/// +[ByRefEvent] +public record struct StartingGearEquippedEvent(EntityUid Entity) +{ + public readonly EntityUid Entity = Entity; +} diff --git a/Content.Shared/Salvage/SalvageMapPrototype.cs b/Content.Shared/Salvage/SalvageMapPrototype.cs index a9814c7b0a..9b5a37c668 100644 --- a/Content.Shared/Salvage/SalvageMapPrototype.cs +++ b/Content.Shared/Salvage/SalvageMapPrototype.cs @@ -4,7 +4,7 @@ namespace Content.Shared.Salvage; [Prototype] -public sealed class SalvageMapPrototype : IPrototype +public sealed partial class SalvageMapPrototype : IPrototype { [ViewVariables] [IdDataField] public string ID { get; } = default!; diff --git a/Content.Shared/Shadowkin/EtherealComponent.cs b/Content.Shared/Shadowkin/EtherealComponent.cs new file mode 100644 index 0000000000..0fc50c0f12 --- /dev/null +++ b/Content.Shared/Shadowkin/EtherealComponent.cs @@ -0,0 +1,36 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Shadowkin; + +[RegisterComponent, NetworkedComponent] +public sealed partial class EtherealComponent : Component +{ + /// + /// Does the Ent, Dark lights around it? + /// + [DataField] + public bool Darken = false; + + /// + /// Range of the Darken Effect. + /// + [DataField] + public float DarkenRange = 5; + + /// + /// Darken Effect Rate. + /// + [DataField] + public float DarkenRate = 0.084f; + + public List DarkenedLights = new(); + + public float DarkenAccumulator; + + public int OldMobMask; + + public int OldMobLayer; + + public List SuppressedFactions = new(); + public bool HasDoorBumpTag; +} \ No newline at end of file diff --git a/Content.Shared/Shadowkin/EtherealStunItemComponent.cs b/Content.Shared/Shadowkin/EtherealStunItemComponent.cs new file mode 100644 index 0000000000..053b5c11f6 --- /dev/null +++ b/Content.Shared/Shadowkin/EtherealStunItemComponent.cs @@ -0,0 +1,11 @@ +namespace Content.Shared.Shadowkin; + +[RegisterComponent] +public sealed partial class EtherealStunItemComponent : Component +{ + [DataField] + public float Radius = 10; + + [DataField] + public bool DeleteOnUse = true; +} \ No newline at end of file diff --git a/Content.Shared/Shadowkin/ShadowkinComponent.cs b/Content.Shared/Shadowkin/ShadowkinComponent.cs new file mode 100644 index 0000000000..b382f3112b --- /dev/null +++ b/Content.Shared/Shadowkin/ShadowkinComponent.cs @@ -0,0 +1,42 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Shadowkin; + +[RegisterComponent, NetworkedComponent] +public sealed partial class ShadowkinComponent : Component +{ + /// + /// Apply the SleepManaRegenMultiplier on SleepComponent if true. + /// + [DataField] + public bool SleepManaRegen = true; + + /// + /// What do edit the ManaRegenMultiplier when on Sleep. + /// + [DataField] + public float SleepManaRegenMultiplier = 4; + + /// + /// On MapInitEvent, will Blackeye the Shadowkin. + /// + [DataField] + public bool BlackeyeSpawn; + + /// + /// If mana is equal or lower then this value, blackeye the shadowkin. + /// + [DataField] + public float BlackEyeMana; + + /// + /// Set the Black-Eye Color. + /// + [DataField] + public Color BlackEyeColor = Color.Black; + + public Color OldEyeColor = Color.LimeGreen; + + [DataField] + public EntityUid? ShadowkinSleepAction; +} \ No newline at end of file diff --git a/Content.Shared/Shadowkin/ShadowkinCuffComponent.cs b/Content.Shared/Shadowkin/ShadowkinCuffComponent.cs new file mode 100644 index 0000000000..b4c62d6664 --- /dev/null +++ b/Content.Shared/Shadowkin/ShadowkinCuffComponent.cs @@ -0,0 +1,4 @@ +namespace Content.Shared.Shadowkin; + +[RegisterComponent] +public sealed partial class ShadowkinCuffComponent : Component { } \ No newline at end of file diff --git a/Content.Shared/Shadowkin/SharedEtherealSystem.cs b/Content.Shared/Shadowkin/SharedEtherealSystem.cs new file mode 100644 index 0000000000..66196faf0a --- /dev/null +++ b/Content.Shared/Shadowkin/SharedEtherealSystem.cs @@ -0,0 +1,141 @@ +using Content.Shared.Physics; +using Robust.Shared.Physics; +using System.Linq; +using Robust.Shared.Physics.Systems; +using Content.Shared.Interaction.Events; +using Robust.Shared.Timing; +using Content.Shared.Popups; +using Content.Shared.Throwing; +using Content.Shared.Weapons.Ranged.Events; +using Content.Shared.CombatMode.Pacification; +using Content.Shared.Psionics; +using Content.Shared.Mobs; +using Content.Shared.CCVar; +using Robust.Shared.Configuration; +using Content.Shared.Abilities.Psionics; +using Content.Shared.Tag; + +namespace Content.Shared.Shadowkin; + +public abstract class SharedEtherealSystem : EntitySystem +{ + [Dependency] private readonly SharedPhysicsSystem _physics = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly TagSystem _tag = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnStartup); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnInteractionAttempt); + SubscribeLocalEvent(OnBeforeThrow); + SubscribeLocalEvent(OnAttemptPowerUse); + SubscribeLocalEvent(OnAttackAttempt); + SubscribeLocalEvent(OnShootAttempt); + SubscribeLocalEvent(OnMindbreak); + SubscribeLocalEvent(OnMobStateChanged); + } + + public virtual void OnStartup(EntityUid uid, EtherealComponent component, MapInitEvent args) + { + if (!TryComp(uid, out var fixtures)) + return; + + var fixture = fixtures.Fixtures.First(); + + component.OldMobMask = fixture.Value.CollisionMask; + component.OldMobLayer = fixture.Value.CollisionLayer; + + if (_cfg.GetCVar(CCVars.EtherealPassThrough)) + { + _physics.SetCollisionMask(uid, fixture.Key, fixture.Value, (int) CollisionGroup.GhostImpassable, fixtures); + _physics.SetCollisionLayer(uid, fixture.Key, fixture.Value, 0, fixtures); + + if (_tag.RemoveTag(uid, "DoorBumpOpener")) + component.HasDoorBumpTag = true; + + return; + } + + _physics.SetCollisionMask(uid, fixture.Key, fixture.Value, (int) CollisionGroup.FlyingMobMask, fixtures); + _physics.SetCollisionLayer(uid, fixture.Key, fixture.Value, (int) CollisionGroup.FlyingMobLayer, fixtures); + } + + public virtual void OnShutdown(EntityUid uid, EtherealComponent component, ComponentShutdown args) + { + if (!TryComp(uid, out var fixtures)) + return; + + var fixture = fixtures.Fixtures.First(); + + _physics.SetCollisionMask(uid, fixture.Key, fixture.Value, component.OldMobMask, fixtures); + _physics.SetCollisionLayer(uid, fixture.Key, fixture.Value, component.OldMobLayer, fixtures); + + if (component.HasDoorBumpTag) + _tag.AddTag(uid, "DoorBumpOpener"); + } + + private void OnMindbreak(EntityUid uid, EtherealComponent component, ref OnMindbreakEvent args) + { + RemComp(uid, component); + } + + private void OnMobStateChanged(EntityUid uid, EtherealComponent component, MobStateChangedEvent args) + { + if (args.NewMobState == MobState.Critical + || args.NewMobState == MobState.Dead) + RemComp(uid, component); + } + + private void OnShootAttempt(Entity ent, ref ShotAttemptedEvent args) + { + args.Cancel(); + } + + private void OnAttackAttempt(EntityUid uid, EtherealComponent component, AttackAttemptEvent args) + { + if (HasComp(args.Target)) + return; + + args.Cancel(); + } + + private void OnBeforeThrow(Entity ent, ref BeforeThrowEvent args) + { + var thrownItem = args.ItemUid; + + // Raise an AttemptPacifiedThrow event and rely on other systems to check + // whether the candidate item is OK to throw: + var ev = new AttemptPacifiedThrowEvent(thrownItem, ent); + RaiseLocalEvent(thrownItem, ref ev); + if (!ev.Cancelled) + return; + + args.Cancelled = true; + } + + private void OnInteractionAttempt(EntityUid uid, EtherealComponent component, InteractionAttemptEvent args) + { + if (!HasComp(args.Target) + || HasComp(args.Target)) + return; + + args.Cancel(); + if (_gameTiming.InPrediction) + return; + + _popup.PopupEntity(Loc.GetString("ethereal-pickup-fail"), args.Target.Value, uid); + } + + private void OnAttemptPowerUse(EntityUid uid, EtherealComponent component, OnAttemptPowerUseEvent args) + { + if (args.Power == "DarkSwap") + return; + + args.Cancel(); + } +} diff --git a/Content.Shared/Shadowkin/ShowEtherealComponent.cs b/Content.Shared/Shadowkin/ShowEtherealComponent.cs new file mode 100644 index 0000000000..45fa78fa0c --- /dev/null +++ b/Content.Shared/Shadowkin/ShowEtherealComponent.cs @@ -0,0 +1,6 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Shadowkin; + +[RegisterComponent] +public sealed partial class ShowEtherealComponent : Component { } \ No newline at end of file diff --git a/Content.Shared/ShortConstruction/ShortConstructionComponent.cs b/Content.Shared/ShortConstruction/ShortConstructionComponent.cs new file mode 100644 index 0000000000..579110f52f --- /dev/null +++ b/Content.Shared/ShortConstruction/ShortConstructionComponent.cs @@ -0,0 +1,11 @@ +using Content.Shared.RadialSelector; +using Robust.Shared.GameStates; + +namespace Content.Shared.ShortConstruction; + +[RegisterComponent, NetworkedComponent] +public sealed partial class ShortConstructionComponent : Component +{ + [DataField(required: true)] + public List Entries = new(); +} diff --git a/Content.Shared/Silicon/BatteryDrinkerEvent.cs b/Content.Shared/Silicon/BatteryDrinkerEvent.cs new file mode 100644 index 0000000000..99af03df3a --- /dev/null +++ b/Content.Shared/Silicon/BatteryDrinkerEvent.cs @@ -0,0 +1,10 @@ +using Content.Shared.DoAfter; +using Robust.Shared.Serialization; + +namespace Content.Shared.Silicon; + +[Serializable, NetSerializable] +public sealed partial class BatteryDrinkerDoAfterEvent : SimpleDoAfterEvent +{ + public BatteryDrinkerDoAfterEvent() { } +} diff --git a/Content.Shared/Silicon/BlindHealing/SharedBlindHealingSystem.cs b/Content.Shared/Silicon/BlindHealing/SharedBlindHealingSystem.cs new file mode 100644 index 0000000000..bfc5092b64 --- /dev/null +++ b/Content.Shared/Silicon/BlindHealing/SharedBlindHealingSystem.cs @@ -0,0 +1,11 @@ +using Content.Shared.DoAfter; +using Robust.Shared.Serialization; + +namespace Content.Shared.Silicon.BlindHealing; + +public abstract partial class SharedBlindHealingSystem : EntitySystem +{ + [Serializable, NetSerializable] + protected sealed partial class HealingDoAfterEvent : SimpleDoAfterEvent { } +} + diff --git a/Content.Shared/Silicon/Components/SiliconComponent.cs b/Content.Shared/Silicon/Components/SiliconComponent.cs new file mode 100644 index 0000000000..bcee4d161a --- /dev/null +++ b/Content.Shared/Silicon/Components/SiliconComponent.cs @@ -0,0 +1,114 @@ +using Robust.Shared.GameStates; +using Content.Shared.Silicon.Systems; +using Robust.Shared.Serialization.TypeSerializers.Implementations; +using Robust.Shared.Prototypes; +using Content.Shared.Alert; + +namespace Content.Shared.Silicon.Components; + +/// +/// Component for defining a mob as a robot. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class SiliconComponent : Component +{ + [ViewVariables(VVAccess.ReadOnly)] + public short ChargeState = 10; + + [ViewVariables(VVAccess.ReadOnly)] + public float OverheatAccumulator = 0.0f; + + /// + /// The last time the Silicon was drained. + /// Used for NPC Silicons to avoid over updating. + /// + /// + /// Time between drains can be specified in + /// + /// + public TimeSpan LastDrainTime = TimeSpan.Zero; + + /// + /// The Silicon's battery slot, if it has one. + /// + + /// + /// Is the Silicon currently dead? + /// + public bool Dead; + + // BatterySystem took issue with how this was used, so I'm coming back to it at a later date, when more foundational Silicon stuff is implemented. + // /// + // /// The entity to get the battery from. + // /// + // public EntityUid BatteryOverride? = EntityUid.Invalid; + + + /// + /// The type of silicon this is. + /// + /// + /// Any new types of Silicons should be added to the enum. + /// Setting this to Npc will delay charge state updates by LastDrainTime and skip battery heat calculations + /// + [DataField(customTypeSerializer: typeof(EnumSerializer))] + public Enum EntityType = SiliconType.Npc; + + /// + /// Is this silicon battery powered? + /// + /// + /// If true, should go along with a battery component. One will not be added automatically. + /// + [DataField] + public bool BatteryPowered; + + /// + /// How much power is drained by this Silicon every second by default. + /// + [DataField] + public float DrainPerSecond = 50f; + + + /// + /// The percentages at which the silicon will enter each state. + /// + /// + /// The Silicon will always be Full at 100%. + /// Setting a value to null will disable that state. + /// Setting Critical to 0 will cause the Silicon to never enter the dead state. + /// + [DataField] + public float? ChargeThresholdMid = 0.5f; + + /// + [DataField] + public float? ChargeThresholdLow = 0.25f; + + /// + [DataField] + public float? ChargeThresholdCritical = 0.1f; + + [DataField] + public ProtoId BatteryAlert = "BorgBattery"; + + [DataField] + public ProtoId NoBatteryAlert = "BorgBatteryNone"; + + + /// + /// The amount the Silicon will be slowed at each charge state. + /// + [DataField(required: true)] + public Dictionary SpeedModifierThresholds = default!; + + [DataField] + public float FireStackMultiplier = 1f; + + /// + /// Whether or not a Silicon will cancel all sleep events. + /// Maybe you want an android that can sleep as well as drink APCs? I'm not going to judge. + /// + [DataField] + public bool DoSiliconsDreamOfElectricSheep; +} diff --git a/Content.Shared/Silicon/DeadStartupButton/DeadStartupButtonComponent.cs b/Content.Shared/Silicon/DeadStartupButton/DeadStartupButtonComponent.cs new file mode 100644 index 0000000000..9c2e5baf57 --- /dev/null +++ b/Content.Shared/Silicon/DeadStartupButton/DeadStartupButtonComponent.cs @@ -0,0 +1,28 @@ +using Robust.Shared.Audio; + +namespace Content.Shared.Silicon.DeadStartupButton; + +/// +/// This is used for Silicon entities such as IPCs, Cyborgs, Androids, anything "living" with a button people can touch. +/// +[RegisterComponent] +public sealed partial class DeadStartupButtonComponent : Component +{ + [DataField] + public string VerbText = "dead-startup-button-verb"; + + [DataField] + public SoundSpecifier Sound = new SoundPathSpecifier("/Audio/Effects/Arcade/newgame.ogg"); + + [DataField] + public SoundSpecifier ButtonSound = new SoundPathSpecifier("/Audio/Machines/button.ogg"); + + [DataField] + public float DoAfterInterval = 1f; + + [DataField] + public SoundSpecifier BuzzSound = new SoundCollectionSpecifier("buzzes"); + + [DataField] + public int VerbPriority = 1; +} diff --git a/Content.Shared/Silicon/DeadStartupButton/SharedDeadStartupButtonSystem.cs b/Content.Shared/Silicon/DeadStartupButton/SharedDeadStartupButtonSystem.cs new file mode 100644 index 0000000000..ced89e7860 --- /dev/null +++ b/Content.Shared/Silicon/DeadStartupButton/SharedDeadStartupButtonSystem.cs @@ -0,0 +1,61 @@ +using Content.Shared.DoAfter; +using Content.Shared.Mobs.Components; +using Content.Shared.Mobs.Systems; +using Content.Shared.Verbs; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Network; +using Robust.Shared.Serialization; +using Robust.Shared.Utility; + +namespace Content.Shared.Silicon.DeadStartupButton; + +/// +/// This creates a Button that can be activated after an entity, usually a silicon or an IPC, died. +/// This will activate a doAfter and then revive the entity, playing a custom afterward sound. +/// +public abstract partial class SharedDeadStartupButtonSystem : EntitySystem +{ + [Dependency] private readonly MobStateSystem _mobState = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; + [Dependency] private readonly INetManager _net = default!; + + /// + public override void Initialize() + { + SubscribeLocalEvent>(AddTurnOnVerb); + } + + private void AddTurnOnVerb(EntityUid uid, DeadStartupButtonComponent component, GetVerbsEvent args) + { + if (!TryComp(uid, out var mobState) + || !_mobState.IsDead(uid, mobState) + || !args.CanAccess || !args.CanInteract || args.Hands == null) + return; + + args.Verbs.Add(new AlternativeVerb() + { + Act = () => TryStartup(args.User, uid, component), + Text = Loc.GetString(component.VerbText), + Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/Spare/poweronoff.svg.192dpi.png")), + Priority = component.VerbPriority + }); + } + + private void TryStartup(EntityUid user, EntityUid target, DeadStartupButtonComponent comp) + { + if (!_net.IsServer) + return; + + _audio.PlayPvs(comp.ButtonSound, target); + var args = new DoAfterArgs(EntityManager, user, comp.DoAfterInterval, new OnDoAfterButtonPressedEvent(), target, target:target) + { + BreakOnDamage = true, + BreakOnUserMove = true, + }; + _doAfterSystem.TryStartDoAfter(args); + } + + [Serializable, NetSerializable] + public sealed partial class OnDoAfterButtonPressedEvent : SimpleDoAfterEvent { } +} diff --git a/Content.Shared/Silicon/EmitBuzzWhileDamaged/EmitBuzzWhileDamagedComponent.cs b/Content.Shared/Silicon/EmitBuzzWhileDamaged/EmitBuzzWhileDamagedComponent.cs new file mode 100644 index 0000000000..986292551d --- /dev/null +++ b/Content.Shared/Silicon/EmitBuzzWhileDamaged/EmitBuzzWhileDamagedComponent.cs @@ -0,0 +1,26 @@ +using Robust.Shared.Audio; + +namespace Content.Shared.Silicon.EmitBuzzWhileDamaged; + +/// +/// This is used for controlling the cadence of the buzzing emitted by EmitBuzzOnCritSystem. +/// This component is used by mechanical species that can get to critical health. +/// +[RegisterComponent] +public sealed partial class EmitBuzzWhileDamagedComponent : Component +{ + [DataField] + public TimeSpan BuzzPopupCooldown = TimeSpan.FromSeconds(8); + + [ViewVariables] + public TimeSpan LastBuzzPopupTime; + + [DataField] + public float CycleDelay = 2.0f; + + [ViewVariables] + public float AccumulatedFrametime; + + [DataField] + public SoundSpecifier Sound = new SoundCollectionSpecifier("buzzes"); +} diff --git a/Content.Shared/Silicon/SIliconRepairable/SharedWeldingHealableSystem.cs b/Content.Shared/Silicon/SIliconRepairable/SharedWeldingHealableSystem.cs new file mode 100644 index 0000000000..8cbdd50764 --- /dev/null +++ b/Content.Shared/Silicon/SIliconRepairable/SharedWeldingHealableSystem.cs @@ -0,0 +1,14 @@ +using Content.Shared.DoAfter; +using Robust.Shared.Serialization; + +namespace Content.Shared.Silicon.WeldingHealing; + +public abstract partial class SharedWeldingHealableSystem : EntitySystem +{ + [Serializable, NetSerializable] + protected sealed partial class SiliconRepairFinishedEvent : SimpleDoAfterEvent + { + public float Delay; + } +} + diff --git a/Content.Shared/Silicon/Systems/SharedSiliconSystem.cs b/Content.Shared/Silicon/Systems/SharedSiliconSystem.cs new file mode 100644 index 0000000000..8fe87e162b --- /dev/null +++ b/Content.Shared/Silicon/Systems/SharedSiliconSystem.cs @@ -0,0 +1,107 @@ +using Content.Shared.Silicon.Components; +using Content.Shared.Alert; +using Content.Shared.Bed.Sleep; +using Content.Shared.Containers.ItemSlots; +using Content.Shared.Movement.Systems; +using Content.Shared.PowerCell.Components; +using Robust.Shared.Serialization; + +namespace Content.Shared.Silicon.Systems; + +public sealed class SharedSiliconChargeSystem : EntitySystem +{ + [Dependency] private readonly AlertsSystem _alertsSystem = default!; + [Dependency] private readonly ItemSlotsSystem _itemSlots = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnSiliconInit); + SubscribeLocalEvent(OnSiliconChargeStateUpdate); + SubscribeLocalEvent(OnRefreshMovespeed); + SubscribeLocalEvent(OnItemSlotInsertAttempt); + SubscribeLocalEvent(OnItemSlotEjectAttempt); + SubscribeLocalEvent(OnTryingToSleep); + } + + /// + /// Silicon entities can now also be Living player entities. We may want to prevent them from sleeping if they can't sleep. + /// + private void OnTryingToSleep(EntityUid uid, SiliconComponent component, ref TryingToSleepEvent args) + { + args.Cancelled = !component.DoSiliconsDreamOfElectricSheep; + } + + private void OnItemSlotInsertAttempt(EntityUid uid, SiliconComponent component, ref ItemSlotInsertAttemptEvent args) + { + if (args.Cancelled + || !TryComp(uid, out var cellSlotComp) + || !_itemSlots.TryGetSlot(uid, cellSlotComp.CellSlotId, out var cellSlot) + || cellSlot != args.Slot || args.User != uid) + return; + + args.Cancelled = true; + } + + private void OnItemSlotEjectAttempt(EntityUid uid, SiliconComponent component, ref ItemSlotEjectAttemptEvent args) + { + if (args.Cancelled + || !TryComp(uid, out var cellSlotComp) + || !_itemSlots.TryGetSlot(uid, cellSlotComp.CellSlotId, out var cellSlot) + || cellSlot != args.Slot || args.User != uid) + return; + + args.Cancelled = true; + } + + private void OnSiliconInit(EntityUid uid, SiliconComponent component, ComponentInit args) + { + if (!component.BatteryPowered) + return; + + _alertsSystem.ShowAlert(uid, AlertType.BorgBattery, component.ChargeState); + } + + private void OnSiliconChargeStateUpdate(EntityUid uid, SiliconComponent component, SiliconChargeStateUpdateEvent ev) + { + _alertsSystem.ShowAlert(uid, AlertType.BorgBattery, ev.ChargePercent); + } + + private void OnRefreshMovespeed(EntityUid uid, SiliconComponent component, RefreshMovementSpeedModifiersEvent args) + { + if (!component.BatteryPowered) + return; + + var closest = 0; + foreach (var state in component.SpeedModifierThresholds) + if (component.ChargeState >= state.Key && state.Key > closest) + closest = state.Key; + + var speedMod = component.SpeedModifierThresholds[closest]; + + args.ModifySpeed(speedMod, speedMod); + } +} + + +public enum SiliconType +{ + Player, + GhostRole, + Npc, +} + +/// +/// Event raised when a Silicon's charge state needs to be updated. +/// +[Serializable, NetSerializable] +public sealed class SiliconChargeStateUpdateEvent : EntityEventArgs +{ + public short ChargePercent { get; } + + public SiliconChargeStateUpdateEvent(short chargePercent) + { + ChargePercent = chargePercent; + } +} diff --git a/Content.Shared/Silicons/Borgs/Components/BorgTransponderComponent.cs b/Content.Shared/Silicons/Borgs/Components/BorgTransponderComponent.cs new file mode 100644 index 0000000000..8c15e20d5d --- /dev/null +++ b/Content.Shared/Silicons/Borgs/Components/BorgTransponderComponent.cs @@ -0,0 +1,43 @@ +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; +using Robust.Shared.Utility; + +namespace Content.Shared.Silicons.Borgs.Components; + +/// +/// Periodically broadcasts borg data to robotics consoles. +/// When not emagged, handles disabling and destroying commands as expected. +/// +[RegisterComponent, Access(typeof(SharedBorgSystem))] +public sealed partial class BorgTransponderComponent : Component +{ + /// + /// Sprite of the chassis to send. + /// + [DataField(required: true)] + public SpriteSpecifier? Sprite; + + /// + /// Name of the chassis to send. + /// + [DataField(required: true)] + public string Name = string.Empty; + + /// + /// Popup shown to everyone when a borg is disabled. + /// Gets passed a string "name". + /// + [DataField] + public LocId DisabledPopup = "borg-transponder-disabled-popup"; + + /// + /// How long to wait between each broadcast. + /// + [DataField] + public TimeSpan BroadcastDelay = TimeSpan.FromSeconds(5); + + /// + /// When to next broadcast data. + /// + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] + public TimeSpan NextBroadcast = TimeSpan.Zero; +} diff --git a/Content.Shared/Silicons/Laws/SiliconLawPrototype.cs b/Content.Shared/Silicons/Laws/SiliconLawPrototype.cs index f6407be5c7..5e5df448b3 100644 --- a/Content.Shared/Silicons/Laws/SiliconLawPrototype.cs +++ b/Content.Shared/Silicons/Laws/SiliconLawPrototype.cs @@ -58,7 +58,7 @@ public SiliconLaw ShallowClone() /// [Prototype("siliconLaw")] [Serializable, NetSerializable] -public sealed class SiliconLawPrototype : SiliconLaw, IPrototype +public sealed partial class SiliconLawPrototype : SiliconLaw, IPrototype { /// [IdDataField] diff --git a/Content.Shared/Singularity/Components/SharedEmitterComponent.cs b/Content.Shared/Singularity/Components/SharedEmitterComponent.cs index c2e7af717b..cc6e8aeede 100644 --- a/Content.Shared/Singularity/Components/SharedEmitterComponent.cs +++ b/Content.Shared/Singularity/Components/SharedEmitterComponent.cs @@ -1,4 +1,5 @@ using System.Threading; +using Content.Shared.Construction.Prototypes; using Content.Shared.DeviceLinking; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; @@ -14,90 +15,130 @@ public sealed partial class EmitterComponent : Component { public CancellationTokenSource? TimerCancel; - // whether the power switch is in "on" - [ViewVariables] public bool IsOn; - // Whether the power switch is on AND the machine has enough power (so is actively firing) - [ViewVariables] public bool IsPowered; + /// + /// Whether the power switch is on + /// + [ViewVariables] + public bool IsOn; /// - /// counts the number of consecutive shots fired. + /// Whether the power switch is on AND the machine has enough power (so is actively firing) + /// + [ViewVariables] + public bool IsPowered; + + /// + /// counts the number of consecutive shots fired. /// [ViewVariables] public int FireShotCounter; /// - /// The entity that is spawned when the emitter fires. + /// The entity that is spawned when the emitter fires. /// - [DataField("boltType", customTypeSerializer: typeof(PrototypeIdSerializer))] + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] public string BoltType = "EmitterBolt"; - [DataField("selectableTypes", customTypeSerializer: typeof(PrototypeIdListSerializer))] + [DataField(customTypeSerializer: typeof(PrototypeIdListSerializer))] public List SelectableTypes = new(); /// - /// The current amount of power being used. + /// The current amount of power being used. /// - [DataField("powerUseActive")] + [DataField] public int PowerUseActive = 600; /// - /// The amount of shots that are fired in a single "burst" + /// The amount of shots that are fired in a single "burst" /// - [DataField("fireBurstSize")] + [DataField] public int FireBurstSize = 3; /// - /// The time between each shot during a burst. + /// The time between each shot during a burst. /// - [DataField("fireInterval")] + [DataField] public TimeSpan FireInterval = TimeSpan.FromSeconds(2); /// - /// The current minimum delay between bursts. + /// The base amount of time between each shot during a burst. /// - [DataField("fireBurstDelayMin")] + [DataField] + public TimeSpan BaseFireInterval = TimeSpan.FromSeconds(2); + + /// + /// The current minimum delay between bursts. + /// + [DataField] public TimeSpan FireBurstDelayMin = TimeSpan.FromSeconds(4); /// - /// The current maximum delay between bursts. + /// The current maximum delay between bursts. /// - [DataField("fireBurstDelayMax")] + [DataField] public TimeSpan FireBurstDelayMax = TimeSpan.FromSeconds(10); /// - /// The visual state that is set when the emitter is turned on + /// The base minimum delay between shot bursts. + /// Used for machine part rating calculations. + /// + [DataField] + public TimeSpan BaseFireBurstDelayMin = TimeSpan.FromSeconds(4); + + /// + /// The base maximum delay between shot bursts. + /// Used for machine part rating calculations. + /// + [DataField] + public TimeSpan BaseFireBurstDelayMax = TimeSpan.FromSeconds(10); + + /// + /// The multiplier for the base delay between shot bursts as well as + /// the fire interval + /// + [DataField] + public float FireRateMultiplier = 0.8f; + + /// + /// The machine part that affects burst delay. + /// + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] + public string MachinePartFireRate = "Capacitor"; + + /// + /// The visual state that is set when the emitter is turned on /// - [DataField("onState")] + [DataField] public string? OnState = "beam"; /// - /// The visual state that is set when the emitter doesn't have enough power. + /// The visual state that is set when the emitter doesn't have enough power. /// - [DataField("underpoweredState")] + [DataField] public string? UnderpoweredState = "underpowered"; /// - /// Signal port that turns on the emitter. + /// Signal port that turns on the emitter. /// - [DataField("onPort", customTypeSerializer: typeof(PrototypeIdSerializer))] + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] public string OnPort = "On"; /// - /// Signal port that turns off the emitter. + /// Signal port that turns off the emitter. /// - [DataField("offPort", customTypeSerializer: typeof(PrototypeIdSerializer))] + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] public string OffPort = "Off"; /// - /// Signal port that toggles the emitter on or off. + /// Signal port that toggles the emitter on or off. /// - [DataField("togglePort", customTypeSerializer: typeof(PrototypeIdSerializer))] + [DataField(customTypeSerializer: typeof(PrototypeIdSerializer))] public string TogglePort = "Toggle"; /// - /// Map of signal ports to entity prototype IDs of the entity that will be fired. + /// Map of signal ports to entity prototype IDs of the entity that will be fired. /// - [DataField("setTypePorts", customTypeSerializer: typeof(PrototypeIdDictionarySerializer))] + [DataField(customTypeSerializer: typeof(PrototypeIdDictionarySerializer))] public Dictionary SetTypePorts = new(); } diff --git a/Content.Shared/Slippery/SlipperySystem.cs b/Content.Shared/Slippery/SlipperySystem.cs index 090df8a357..df5180531f 100644 --- a/Content.Shared/Slippery/SlipperySystem.cs +++ b/Content.Shared/Slippery/SlipperySystem.cs @@ -7,6 +7,7 @@ using Content.Shared.StepTrigger.Systems; using Content.Shared.Stunnable; using Content.Shared.Throwing; +using Content.Shared.Mood; using JetBrains.Annotations; using Robust.Shared.Audio.Systems; using Robust.Shared.Containers; @@ -101,6 +102,8 @@ private void TrySlip(EntityUid uid, SlipperyComponent component, EntityUid other _stun.TryParalyze(other, TimeSpan.FromSeconds(component.ParalyzeTime), true); + RaiseLocalEvent(other, new MoodEffectEvent("MobSlipped")); + RaiseLocalEvent(other, new ParkSlipEvent(uid)); // Parkstation-DropOnSlip // Preventing from playing the slip sound when you are already knocked down. diff --git a/Content.Server/Speech/Components/VocalComponent.cs b/Content.Shared/Speech/Components/VocalComponent.cs similarity index 83% rename from Content.Server/Speech/Components/VocalComponent.cs rename to Content.Shared/Speech/Components/VocalComponent.cs index 029d638a66..e5d2c9997f 100644 --- a/Content.Server/Speech/Components/VocalComponent.cs +++ b/Content.Shared/Speech/Components/VocalComponent.cs @@ -1,18 +1,18 @@ -using Content.Server.Speech.EntitySystems; using Content.Shared.Chat.Prototypes; using Content.Shared.Humanoid; using Robust.Shared.Audio; +using Robust.Shared.GameStates; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary; -namespace Content.Server.Speech.Components; +namespace Content.Shared.Speech.Components; /// /// Component required for entities to be able to do vocal emotions. /// -[RegisterComponent] -[Access(typeof(VocalSystem))] +[RegisterComponent, NetworkedComponent] +[AutoGenerateComponentState] public sealed partial class VocalComponent : Component { /// @@ -20,21 +20,27 @@ public sealed partial class VocalComponent : Component /// Entities without considered to be . /// [DataField("sounds", customTypeSerializer: typeof(PrototypeIdValueDictionarySerializer))] + [AutoNetworkedField] public Dictionary? Sounds; [DataField("screamId", customTypeSerializer: typeof(PrototypeIdSerializer))] + [AutoNetworkedField] public string ScreamId = "Scream"; [DataField("wilhelm")] + [AutoNetworkedField] public SoundSpecifier Wilhelm = new SoundPathSpecifier("/Audio/Voice/Human/wilhelm_scream.ogg"); [DataField("wilhelmProbability")] + [AutoNetworkedField] public float WilhelmProbability = 0.0002f; [DataField("screamAction", customTypeSerializer: typeof(PrototypeIdSerializer))] + [AutoNetworkedField] public string ScreamAction = "ActionScream"; [DataField("screamActionEntity")] + [AutoNetworkedField] public EntityUid? ScreamActionEntity; /// @@ -42,5 +48,6 @@ public sealed partial class VocalComponent : Component /// Null if no valid prototype for entity sex was found. /// [ViewVariables] + [AutoNetworkedField] public EmoteSoundsPrototype? EmoteSounds = null; } diff --git a/Content.Shared/Speech/SpeechComponent.cs b/Content.Shared/Speech/SpeechComponent.cs index 272d9ef8ca..0882120718 100644 --- a/Content.Shared/Speech/SpeechComponent.cs +++ b/Content.Shared/Speech/SpeechComponent.cs @@ -1,3 +1,4 @@ +using Content.Shared.Chat.Prototypes; using Robust.Shared.Audio; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; @@ -26,6 +27,13 @@ public sealed partial class SpeechComponent : Component [DataField] public ProtoId SpeechVerb = "Default"; + /// + /// What emotes allowed to use event if emote is false + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField] + public List> AllowedEmotes = new(); + /// /// A mapping from chat suffixes loc strings to speech verb prototypes that should be conditionally used. /// For things like '?' changing to 'asks' or '!!' making text bold and changing to 'yells'. Can be overridden if necessary. diff --git a/Content.Shared/SprayPainter/Prototypes/AirlockDepartmentsPrototype.cs b/Content.Shared/SprayPainter/Prototypes/AirlockDepartmentsPrototype.cs index 3553597c52..b61aa037cc 100644 --- a/Content.Shared/SprayPainter/Prototypes/AirlockDepartmentsPrototype.cs +++ b/Content.Shared/SprayPainter/Prototypes/AirlockDepartmentsPrototype.cs @@ -7,7 +7,7 @@ namespace Content.Shared.SprayPainter.Prototypes; /// Maps airlock style names to department ids. /// [Prototype("airlockDepartments")] -public sealed class AirlockDepartmentsPrototype : IPrototype +public sealed partial class AirlockDepartmentsPrototype : IPrototype { [IdDataField] public string ID { get; private set; } = default!; diff --git a/Content.Shared/SprayPainter/SharedSprayPainterSystem.cs b/Content.Shared/SprayPainter/SharedSprayPainterSystem.cs index 529e321f8d..fa04a50f8b 100644 --- a/Content.Shared/SprayPainter/SharedSprayPainterSystem.cs +++ b/Content.Shared/SprayPainter/SharedSprayPainterSystem.cs @@ -4,6 +4,7 @@ using Content.Shared.Doors.Components; using Content.Shared.Interaction; using Content.Shared.Popups; +using Content.Shared.Paint; using Content.Shared.SprayPainter.Components; using Content.Shared.SprayPainter.Prototypes; using Robust.Shared.Audio.Systems; @@ -129,6 +130,8 @@ private void OnAirlockInteract(Entity ent, ref Intera return; } + RemComp(ent); + var doAfterEventArgs = new DoAfterArgs(EntityManager, args.User, painter.AirlockSprayTime, new SprayPainterDoorDoAfterEvent(sprite, style.Department), args.Used, target: ent, used: args.Used) { BreakOnTargetMove = true, diff --git a/Content.Shared/Standing/LayingDownComponent.cs b/Content.Shared/Standing/LayingDownComponent.cs new file mode 100644 index 0000000000..ec9351e22c --- /dev/null +++ b/Content.Shared/Standing/LayingDownComponent.cs @@ -0,0 +1,37 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; + +namespace Content.Shared.Standing; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class LayingDownComponent : Component +{ + [DataField, AutoNetworkedField] + public TimeSpan StandingUpTime = TimeSpan.FromSeconds(1); + + [DataField, AutoNetworkedField] + public float LyingSpeedModifier = 0.35f, + CrawlingUnderSpeedModifier = 0.5f; + + [DataField, AutoNetworkedField] + public bool AutoGetUp; + + /// + /// If true, the entity is choosing to crawl under furniture. This is purely visual and has no effect on physics. + /// + [DataField, AutoNetworkedField] + public bool IsCrawlingUnder = false; + + [DataField, AutoNetworkedField] + public int NormalDrawDepth = (int) DrawDepth.DrawDepth.Mobs, + CrawlingUnderDrawDepth = (int) DrawDepth.DrawDepth.SmallMobs; +} + +[Serializable, NetSerializable] +public sealed class ChangeLayingDownEvent : CancellableEntityEventArgs; + +[Serializable, NetSerializable] +public sealed class CheckAutoGetUpEvent(NetEntity user) : CancellableEntityEventArgs +{ + public NetEntity User = user; +} diff --git a/Content.Shared/Standing/SharedLayingDownSystem.cs b/Content.Shared/Standing/SharedLayingDownSystem.cs new file mode 100644 index 0000000000..b2bb5add5f --- /dev/null +++ b/Content.Shared/Standing/SharedLayingDownSystem.cs @@ -0,0 +1,191 @@ +using Content.Shared.ActionBlocker; +using Content.Shared.CCVar; +using Content.Shared.DoAfter; +using Content.Shared.Gravity; +using Content.Shared.Input; +using Content.Shared.Mobs.Systems; +using Content.Shared.Movement.Systems; +using Content.Shared.Body.Components; +using Content.Shared.Standing; +using Content.Shared.Popups; +using Content.Shared.Stunnable; +using Robust.Shared.Configuration; +using Robust.Shared.Input.Binding; +using Robust.Shared.Player; +using Robust.Shared.Serialization; + +namespace Content.Shared.Standing; + +public abstract class SharedLayingDownSystem : EntitySystem +{ + [Dependency] private readonly MobStateSystem _mobState = default!; + [Dependency] private readonly StandingStateSystem _standing = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + [Dependency] private readonly SharedGravitySystem _gravity = default!; + [Dependency] private readonly IConfigurationManager _config = default!; + [Dependency] private readonly SharedPopupSystem _popups = default!; + [Dependency] private readonly MovementSpeedModifierSystem _speed = default!; + [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; + + public override void Initialize() + { + CommandBinds.Builder + .Bind(ContentKeyFunctions.ToggleStanding, InputCmdHandler.FromDelegate(ToggleStanding)) + .Bind(ContentKeyFunctions.ToggleCrawlingUnder, InputCmdHandler.FromDelegate(HandleCrawlUnderRequest, handle: false)) + .Register(); + + SubscribeNetworkEvent(OnChangeState); + + SubscribeLocalEvent(OnStandingUpDoAfter); + SubscribeLocalEvent(OnRefreshMovementSpeed); + SubscribeLocalEvent(OnParentChanged); + } + + public override void Shutdown() + { + base.Shutdown(); + + CommandBinds.Unregister(); + } + + private void ToggleStanding(ICommonSession? session) + { + if (session is not { AttachedEntity: { Valid: true } uid } _ + || !Exists(uid) + || !HasComp(session.AttachedEntity) + || _gravity.IsWeightless(session.AttachedEntity.Value)) + return; + + RaiseNetworkEvent(new ChangeLayingDownEvent()); + } + + private void HandleCrawlUnderRequest(ICommonSession? session) + { + if (session == null + || session.AttachedEntity is not {} uid + || !TryComp(uid, out var standingState) + || !TryComp(uid, out var layingDown) + || !_actionBlocker.CanInteract(uid, null)) + return; + + var newState = !layingDown.IsCrawlingUnder; + if (standingState.CurrentState is StandingState.Standing) + newState = false; // If the entity is already standing, this function only serves a fallback method to fix its draw depth + + // Do not allow to begin crawling under if it's disabled in config. We still, however, allow to stop it, as a failsafe. + if (newState && !_config.GetCVar(CCVars.CrawlUnderTables)) + { + _popups.PopupEntity(Loc.GetString("crawling-under-tables-disabled-popup"), uid, session); + return; + } + + layingDown.IsCrawlingUnder = newState; + _speed.RefreshMovementSpeedModifiers(uid); + Dirty(uid, layingDown); + } + + private void OnChangeState(ChangeLayingDownEvent ev, EntitySessionEventArgs args) + { + if (!args.SenderSession.AttachedEntity.HasValue) + return; + + var uid = args.SenderSession.AttachedEntity.Value; + if (!TryComp(uid, out StandingStateComponent? standing) + || !TryComp(uid, out LayingDownComponent? layingDown)) + return; + + RaiseNetworkEvent(new CheckAutoGetUpEvent(GetNetEntity(uid))); + + if (HasComp(uid) + || !_mobState.IsAlive(uid)) + return; + + if (_standing.IsDown(uid, standing)) + TryStandUp(uid, layingDown, standing); + else + TryLieDown(uid, layingDown, standing); + } + + private void OnStandingUpDoAfter(EntityUid uid, StandingStateComponent component, StandingUpDoAfterEvent args) + { + if (args.Handled || args.Cancelled + || HasComp(uid) + || _mobState.IsIncapacitated(uid) + || !_standing.Stand(uid)) + component.CurrentState = StandingState.Lying; + + component.CurrentState = StandingState.Standing; + } + + private void OnRefreshMovementSpeed(EntityUid uid, LayingDownComponent component, RefreshMovementSpeedModifiersEvent args) + { + if (!_standing.IsDown(uid)) + return; + + var modifier = component.LyingSpeedModifier * (component.IsCrawlingUnder ? component.CrawlingUnderSpeedModifier : 1); + args.ModifySpeed(modifier, modifier); + } + + private void OnParentChanged(EntityUid uid, LayingDownComponent component, EntParentChangedMessage args) + { + // If the entity is not on a grid, try to make it stand up to avoid issues + if (!TryComp(uid, out var standingState) + || standingState.CurrentState is StandingState.Standing + || Transform(uid).GridUid != null) + return; + + _standing.Stand(uid, standingState); + } + + public bool TryStandUp(EntityUid uid, LayingDownComponent? layingDown = null, StandingStateComponent? standingState = null) + { + if (!Resolve(uid, ref standingState, false) + || !Resolve(uid, ref layingDown, false) + || standingState.CurrentState is not StandingState.Lying + || !_mobState.IsAlive(uid) + || TerminatingOrDeleted(uid) + || !TryComp(uid, out var body) + || body.LegEntities.Count == 0) + return false; + + var args = new DoAfterArgs(EntityManager, uid, layingDown.StandingUpTime, new StandingUpDoAfterEvent(), uid) + { + BreakOnHandChange = false, + RequireCanInteract = false + }; + + if (!_doAfter.TryStartDoAfter(args)) + return false; + + standingState.CurrentState = StandingState.GettingUp; + layingDown.IsCrawlingUnder = false; + return true; + } + + public bool TryLieDown(EntityUid uid, LayingDownComponent? layingDown = null, StandingStateComponent? standingState = null, DropHeldItemsBehavior behavior = DropHeldItemsBehavior.NoDrop) + { + if (!Resolve(uid, ref standingState, false) + || !Resolve(uid, ref layingDown, false) + || standingState.CurrentState is not StandingState.Standing) + { + if (behavior == DropHeldItemsBehavior.AlwaysDrop) + RaiseLocalEvent(uid, new DropHandItemsEvent()); + + return false; + } + + _standing.Down(uid, true, behavior != DropHeldItemsBehavior.NoDrop, standingState, setDrawDepth: true); + return true; + } +} + +[Serializable, NetSerializable] +public sealed partial class StandingUpDoAfterEvent : SimpleDoAfterEvent; + +[Serializable, NetSerializable] +public enum DropHeldItemsBehavior : byte +{ + NoDrop, + DropIfStanding, + AlwaysDrop +} diff --git a/Content.Shared/Standing/StandingStateComponent.cs b/Content.Shared/Standing/StandingStateComponent.cs index 5d7bb0a59f..5b9759a025 100644 --- a/Content.Shared/Standing/StandingStateComponent.cs +++ b/Content.Shared/Standing/StandingStateComponent.cs @@ -1,24 +1,31 @@ using Robust.Shared.Audio; using Robust.Shared.GameStates; -namespace Content.Shared.Standing +namespace Content.Shared.Standing; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class StandingStateComponent : Component { - [RegisterComponent, NetworkedComponent, AutoGenerateComponentState] - [Access(typeof(StandingStateSystem))] - public sealed partial class StandingStateComponent : Component - { - [ViewVariables(VVAccess.ReadWrite)] - [DataField] - public SoundSpecifier DownSound { get; private set; } = new SoundCollectionSpecifier("BodyFall"); + [DataField] + public SoundSpecifier DownSound { get; private set; } = new SoundCollectionSpecifier("BodyFall"); + + [DataField, AutoNetworkedField] + public StandingState CurrentState { get; set; } = StandingState.Standing; - [DataField, AutoNetworkedField] - public bool Standing { get; set; } = true; + [DataField, AutoNetworkedField] + public bool Standing { get; set; } = true; - /// - /// List of fixtures that had their collision mask changed when the entity was downed. - /// Required for re-adding the collision mask. - /// - [DataField, AutoNetworkedField] - public List ChangedFixtures = new(); - } + /// + /// List of fixtures that had their collision mask changed when the entity was downed. + /// Required for re-adding the collision mask. + /// + [DataField, AutoNetworkedField] + public List ChangedFixtures = new(); +} + +public enum StandingState +{ + Lying, + GettingUp, + Standing, } diff --git a/Content.Shared/Standing/StandingStateSystem.cs b/Content.Shared/Standing/StandingStateSystem.cs index 517831b8a1..5abbf53f1b 100644 --- a/Content.Shared/Standing/StandingStateSystem.cs +++ b/Content.Shared/Standing/StandingStateSystem.cs @@ -1,166 +1,187 @@ +using Content.Shared.Buckle; +using Content.Shared.Buckle.Components; +using Content.Shared.Climbing.Systems; +using Content.Shared.Climbing.Components; using Content.Shared.Hands.Components; +using Content.Shared.Movement.Systems; using Content.Shared.Physics; using Content.Shared.Rotation; -using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Physics; using Robust.Shared.Physics.Systems; +using System.Linq; -namespace Content.Shared.Standing +namespace Content.Shared.Standing; + +public sealed class StandingStateSystem : EntitySystem { - public sealed class StandingStateSystem : EntitySystem + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedPhysicsSystem _physics = default!; + [Dependency] private readonly MovementSpeedModifierSystem _movement = default!; + [Dependency] private readonly SharedBuckleSystem _buckle = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly ClimbSystem _climb = default!; + + // If StandingCollisionLayer value is ever changed to more than one layer, the logic needs to be edited. + private const int StandingCollisionLayer = (int)CollisionGroup.MidImpassable; + + public bool IsDown(EntityUid uid, StandingStateComponent? standingState = null) { - [Dependency] private readonly SharedAppearanceSystem _appearance = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly SharedPhysicsSystem _physics = default!; + if (!Resolve(uid, ref standingState, false)) + return false; - // If StandingCollisionLayer value is ever changed to more than one layer, the logic needs to be edited. - private const int StandingCollisionLayer = (int) CollisionGroup.MidImpassable; + return standingState.CurrentState is StandingState.Lying or StandingState.GettingUp; + } - public bool IsDown(EntityUid uid, StandingStateComponent? standingState = null) - { - if (!Resolve(uid, ref standingState, false)) - return false; + public bool Down(EntityUid uid, bool playSound = true, bool dropHeldItems = true, + StandingStateComponent? standingState = null, + AppearanceComponent? appearance = null, + HandsComponent? hands = null, + bool setDrawDepth = false) + { + // TODO: This should actually log missing comps... + if (!Resolve(uid, ref standingState, false)) + return false; - return !standingState.Standing; - } + // Optional component. + Resolve(uid, ref appearance, ref hands, false); - public bool Down(EntityUid uid, bool playSound = true, bool dropHeldItems = true, - StandingStateComponent? standingState = null, - AppearanceComponent? appearance = null, - HandsComponent? hands = null) - { - // TODO: This should actually log missing comps... - if (!Resolve(uid, ref standingState, false)) - return false; + if (standingState.CurrentState is StandingState.Lying or StandingState.GettingUp) + return true; + + // This is just to avoid most callers doing this manually saving boilerplate + // 99% of the time you'll want to drop items but in some scenarios (e.g. buckling) you don't want to. + // We do this BEFORE downing because something like buckle may be blocking downing but we want to drop hand items anyway + // and ultimately this is just to avoid boilerplate in Down callers + keep their behavior consistent. + if (dropHeldItems && hands != null) + RaiseLocalEvent(uid, new DropHandItemsEvent(), false); + + if (TryComp(uid, out BuckleComponent? buckle) && buckle.Buckled && !_buckle.TryUnbuckle(uid, uid, buckleComp: buckle)) + return false; + + var msg = new DownAttemptEvent(); + RaiseLocalEvent(uid, msg, false); + + if (msg.Cancelled) + return false; - // Optional component. - Resolve(uid, ref appearance, ref hands, false); + standingState.CurrentState = StandingState.Lying; + Dirty(standingState); + RaiseLocalEvent(uid, new DownedEvent(), false); - if (!standingState.Standing) - return true; + // Seemed like the best place to put it + _appearance.SetData(uid, RotationVisuals.RotationState, RotationState.Horizontal, appearance); - // This is just to avoid most callers doing this manually saving boilerplate - // 99% of the time you'll want to drop items but in some scenarios (e.g. buckling) you don't want to. - // We do this BEFORE downing because something like buckle may be blocking downing but we want to drop hand items anyway - // and ultimately this is just to avoid boilerplate in Down callers + keep their behavior consistent. - if (dropHeldItems && hands != null) + // Change collision masks to allow going under certain entities like flaps and tables + if (TryComp(uid, out FixturesComponent? fixtureComponent)) + foreach (var (key, fixture) in fixtureComponent.Fixtures) { - RaiseLocalEvent(uid, new DropHandItemsEvent(), false); + if ((fixture.CollisionMask & StandingCollisionLayer) == 0) + continue; + + standingState.ChangedFixtures.Add(key); + _physics.SetCollisionMask(uid, key, fixture, fixture.CollisionMask & ~StandingCollisionLayer, manager: fixtureComponent); } - var msg = new DownAttemptEvent(); - RaiseLocalEvent(uid, msg, false); + // check if component was just added or streamed to client + // if true, no need to play sound - mob was down before player could seen that + if (standingState.LifeStage <= ComponentLifeStage.Starting) + return true; - if (msg.Cancelled) - return false; + if (playSound) + _audio.PlayPredicted(standingState.DownSound, uid, null); - standingState.Standing = false; - Dirty(standingState); - RaiseLocalEvent(uid, new DownedEvent(), false); + _movement.RefreshMovementSpeedModifiers(uid); - // Seemed like the best place to put it - _appearance.SetData(uid, RotationVisuals.RotationState, RotationState.Horizontal, appearance); + Climb(uid); - // Change collision masks to allow going under certain entities like flaps and tables - if (TryComp(uid, out FixturesComponent? fixtureComponent)) - { - foreach (var (key, fixture) in fixtureComponent.Fixtures) - { - if ((fixture.CollisionMask & StandingCollisionLayer) == 0) - continue; - - standingState.ChangedFixtures.Add(key); - _physics.SetCollisionMask(uid, key, fixture, fixture.CollisionMask & ~StandingCollisionLayer, manager: fixtureComponent); - } - } + return true; + } - // check if component was just added or streamed to client - // if true, no need to play sound - mob was down before player could seen that - if (standingState.LifeStage <= ComponentLifeStage.Starting) - return true; + public bool Stand(EntityUid uid, + StandingStateComponent? standingState = null, + AppearanceComponent? appearance = null, + bool force = false) + { + // TODO: This should actually log missing comps... + if (!Resolve(uid, ref standingState, false)) + return false; - if (playSound) - { - _audio.PlayPredicted(standingState.DownSound, uid, uid); - } + // Optional component. + Resolve(uid, ref appearance, false); + if (standingState.CurrentState is StandingState.Standing + || TryComp(uid, out BuckleComponent? buckle) + && buckle.Buckled && !_buckle.TryUnbuckle(uid, uid, buckleComp: buckle)) return true; - } - public bool Stand(EntityUid uid, - StandingStateComponent? standingState = null, - AppearanceComponent? appearance = null, - bool force = false) + if (!force) { - // TODO: This should actually log missing comps... - if (!Resolve(uid, ref standingState, false)) - return false; - - // Optional component. - Resolve(uid, ref appearance, false); + var msg = new StandAttemptEvent(); + RaiseLocalEvent(uid, msg, false); - if (standingState.Standing) - return true; + if (msg.Cancelled) + return false; + } - if (!force) - { - var msg = new StandAttemptEvent(); - RaiseLocalEvent(uid, msg, false); + standingState.CurrentState = StandingState.Standing; - if (msg.Cancelled) - return false; - } + Dirty(uid, standingState); + RaiseLocalEvent(uid, new StoodEvent(), false); - standingState.Standing = true; - Dirty(uid, standingState); - RaiseLocalEvent(uid, new StoodEvent(), false); + _appearance.SetData(uid, RotationVisuals.RotationState, RotationState.Vertical, appearance); - _appearance.SetData(uid, RotationVisuals.RotationState, RotationState.Vertical, appearance); - - if (TryComp(uid, out FixturesComponent? fixtureComponent)) + if (TryComp(uid, out FixturesComponent? fixtureComponent)) + { + foreach (var key in standingState.ChangedFixtures) { - foreach (var key in standingState.ChangedFixtures) - { - if (fixtureComponent.Fixtures.TryGetValue(key, out var fixture)) - _physics.SetCollisionMask(uid, key, fixture, fixture.CollisionMask | StandingCollisionLayer, fixtureComponent); - } + if (fixtureComponent.Fixtures.TryGetValue(key, out var fixture)) + _physics.SetCollisionMask(uid, key, fixture, fixture.CollisionMask | StandingCollisionLayer, fixtureComponent); } - standingState.ChangedFixtures.Clear(); - - return true; } - } + standingState.ChangedFixtures.Clear(); + _movement.RefreshMovementSpeedModifiers(uid); - public sealed class DropHandItemsEvent : EventArgs - { - } + Climb(uid); - /// - /// Subscribe if you can potentially block a down attempt. - /// - public sealed class DownAttemptEvent : CancellableEntityEventArgs - { + return true; } - /// - /// Subscribe if you can potentially block a stand attempt. - /// - public sealed class StandAttemptEvent : CancellableEntityEventArgs + private void Climb(EntityUid uid) { - } + _climb.ForciblyStopClimbing(uid); - /// - /// Raised when an entity becomes standing - /// - public sealed class StoodEvent : EntityEventArgs - { - } + var entityDistances = new Dictionary(); - /// - /// Raised when an entity is not standing - /// - public sealed class DownedEvent : EntityEventArgs - { + foreach (var entity in _lookup.GetEntitiesInRange(uid, 0.3f)) + if (HasComp(entity)) + entityDistances[entity] = (Transform(uid).Coordinates.Position - Transform(entity).Coordinates.Position).LengthSquared(); + + if (entityDistances.Count > 0) + _climb.ForciblySetClimbing(uid, entityDistances.OrderBy(e => e.Value).First().Key); } } + + +public sealed class DropHandItemsEvent : EventArgs { } + +/// +/// Subscribe if you can potentially block a down attempt. +/// +public sealed class DownAttemptEvent : CancellableEntityEventArgs { } + +/// +/// Subscribe if you can potentially block a stand attempt. +/// +public sealed class StandAttemptEvent : CancellableEntityEventArgs { } + +/// +/// Raised when an entity becomes standing +/// +public sealed class StoodEvent : EntityEventArgs { } + +/// +/// Raised when an entity is not standing +/// +public sealed class DownedEvent : EntityEventArgs { } diff --git a/Content.Shared/Station/SharedStationSpawningSystem.cs b/Content.Shared/Station/SharedStationSpawningSystem.cs index 55b746f2ce..e889b5c974 100644 --- a/Content.Shared/Station/SharedStationSpawningSystem.cs +++ b/Content.Shared/Station/SharedStationSpawningSystem.cs @@ -1,54 +1,86 @@ +using System.Linq; using Content.Shared.Hands.Components; using Content.Shared.Hands.EntitySystems; using Content.Shared.Inventory; -using Content.Shared.Preferences; +using Content.Shared.Preferences.Loadouts; using Content.Shared.Roles; using Content.Shared.Storage; using Content.Shared.Storage.EntitySystems; using Robust.Shared.Collections; +using Robust.Shared.Prototypes; namespace Content.Shared.Station; public abstract class SharedStationSpawningSystem : EntitySystem { + [Dependency] protected readonly IPrototypeManager PrototypeManager = default!; [Dependency] protected readonly InventorySystem InventorySystem = default!; [Dependency] private readonly SharedHandsSystem _handsSystem = default!; [Dependency] private readonly SharedStorageSystem _storage = default!; [Dependency] private readonly SharedTransformSystem _xformSystem = default!; + private EntityQuery _handsQuery; + private EntityQuery _inventoryQuery; + private EntityQuery _storageQuery; + private EntityQuery _xformQuery; + + public override void Initialize() + { + base.Initialize(); + _handsQuery = GetEntityQuery(); + _inventoryQuery = GetEntityQuery(); + _storageQuery = GetEntityQuery(); + _xformQuery = GetEntityQuery(); + } + + /// + /// + /// + public void EquipStartingGear(EntityUid entity, ProtoId? startingGear, bool raiseEvent = true) + { + PrototypeManager.TryIndex(startingGear, out var gearProto); + EquipStartingGear(entity, gearProto); + } + /// /// Equips starting gear onto the given entity. /// /// Entity to load out. /// Starting gear to use. - /// Character profile to use, if any. - public void EquipStartingGear(EntityUid entity, StartingGearPrototype startingGear, HumanoidCharacterProfile? profile) + /// Should we raise the event for equipped. Set to false if you will call this manually + public void EquipStartingGear(EntityUid entity, StartingGearPrototype? startingGear, bool raiseEvent = true) { + if (startingGear == null) + return; + + var xform = _xformQuery.GetComponent(entity); + if (InventorySystem.TryGetSlots(entity, out var slotDefinitions)) { foreach (var slot in slotDefinitions) { - var equipmentStr = startingGear.GetGear(slot.Name, profile); + var equipmentStr = startingGear.GetGear(slot.Name, null); if (string.IsNullOrEmpty(equipmentStr)) continue; - var equipmentEntity = EntityManager.SpawnEntity(equipmentStr, EntityManager.GetComponent(entity).Coordinates); + var equipmentEntity = EntityManager.SpawnEntity(equipmentStr, xform.Coordinates); InventorySystem.TryEquip(entity, equipmentEntity, slot.Name, true, force:true); } } - if (TryComp(entity, out HandsComponent? handsComponent)) - return; + if (_handsQuery.TryComp(entity, out var handsComponent)) { var inhand = startingGear.Inhand; - var coords = EntityManager.GetComponent(entity).Coordinates; + var coords = xform.Coordinates; foreach (var prototype in inhand) { var inhandEntity = EntityManager.SpawnEntity(prototype, coords); if (_handsSystem.TryGetEmptyHand(entity, out var emptyHand, handsComponent)) + { _handsSystem.TryPickup(entity, inhandEntity, emptyHand, checkActionBlocker: false, handsComp: handsComponent); + } } } @@ -56,7 +88,7 @@ public void EquipStartingGear(EntityUid entity, StartingGearPrototype startingGe { var coords = _xformSystem.GetMapCoordinates(entity); var ents = new ValueList(); - TryComp(entity, out InventoryComponent? inventoryComp); + _inventoryQuery.TryComp(entity, out var inventoryComp); foreach (var (slot, entProtos) in startingGear.Storage) { @@ -64,17 +96,26 @@ public void EquipStartingGear(EntityUid entity, StartingGearPrototype startingGe continue; foreach (var ent in entProtos) + { ents.Add(Spawn(ent, coords)); + } - if (inventoryComp == null - || !InventorySystem.TryGetSlotEntity(entity, slot, out var slotEnt, - inventoryComponent: inventoryComp) - || !TryComp(slotEnt, out StorageComponent? storage)) - continue; - - foreach (var ent in ents) - _storage.Insert(slotEnt.Value, ent, out _, storageComp: storage, playSound: false); + if (inventoryComp != null && + InventorySystem.TryGetSlotEntity(entity, slot, out var slotEnt, inventoryComponent: inventoryComp) && + _storageQuery.TryComp(slotEnt, out var storage)) + { + foreach (var ent in ents) + { + _storage.Insert(slotEnt.Value, ent, out _, storageComp: storage, playSound: false); + } + } } } + + if (raiseEvent) + { + var ev = new StartingGearEquippedEvent(entity); + RaiseLocalEvent(entity, ref ev, true); + } } } diff --git a/Content.Shared/StatusEffect/StatusEffectsSystem.cs b/Content.Shared/StatusEffect/StatusEffectsSystem.cs index 6aec3a8b3b..cc6dedae49 100644 --- a/Content.Shared/StatusEffect/StatusEffectsSystem.cs +++ b/Content.Shared/StatusEffect/StatusEffectsSystem.cs @@ -123,6 +123,33 @@ public bool TryAddStatusEffect(EntityUid uid, string key, TimeSpan time, bool return false; } + /// + /// Tries to add a status effect to an entity, with a given component added as well. + /// + /// The entity to add the effect to. + /// The status effect ID to add. + /// How long the effect should last for. + /// The status effect cooldown should be refreshed (true) or accumulated (false). + /// The component of status effect itself. + /// The status effects component to change, if you already have it. + /// False if the effect could not be added or the component already exists, true otherwise. + public bool TryAddStatusEffect(EntityUid uid, string key, TimeSpan time, bool refresh, Component component, + StatusEffectsComponent? status = null) + { + if (!Resolve(uid, ref status, false) + || !TryAddStatusEffect(uid, key, time, refresh, status)) + return false; + + // If they already have the comp, we just won't bother updating anything. + if (!EntityManager.HasComponent(uid, component.GetType())) + { + EntityManager.AddComponent(uid, component); + status.ActiveEffects[key].RelevantComponent = _componentFactory.GetComponentName(component.GetType()); + } + + return true; + } + public bool TryAddStatusEffect(EntityUid uid, string key, TimeSpan time, bool refresh, string component, StatusEffectsComponent? status = null) { diff --git a/Content.Shared/StatusIcon/StatusIconPrototype.cs b/Content.Shared/StatusIcon/StatusIconPrototype.cs index b520b185de..c5a5fd8a2c 100644 --- a/Content.Shared/StatusIcon/StatusIconPrototype.cs +++ b/Content.Shared/StatusIcon/StatusIconPrototype.cs @@ -52,7 +52,7 @@ public int CompareTo(StatusIconData? other) /// but in new convenient prototype form! /// [Prototype("statusIcon")] -public sealed class StatusIconPrototype : StatusIconData, IPrototype, IInheritingPrototype +public sealed partial class StatusIconPrototype : StatusIconData, IPrototype, IInheritingPrototype { /// [ParentDataField(typeof(AbstractPrototypeIdArraySerializer))] diff --git a/Content.Shared/Stealth/SharedStealthSystem.cs b/Content.Shared/Stealth/SharedStealthSystem.cs index d0ea804534..1bab55589f 100644 --- a/Content.Shared/Stealth/SharedStealthSystem.cs +++ b/Content.Shared/Stealth/SharedStealthSystem.cs @@ -113,7 +113,7 @@ private void OnStealthHandleState(EntityUid uid, StealthComponent component, ref private void OnMove(EntityUid uid, StealthOnMoveComponent component, ref MoveEvent args) { - if (args.FromStateHandling) + if (_timing.ApplyingState) return; if (args.NewPosition.EntityId != args.OldPosition.EntityId) diff --git a/Content.Shared/StepTrigger/Components/ClothingRequiredStepTriggerComponent.cs b/Content.Shared/StepTrigger/Components/ClothingRequiredStepTriggerComponent.cs new file mode 100644 index 0000000000..9efd78d082 --- /dev/null +++ b/Content.Shared/StepTrigger/Components/ClothingRequiredStepTriggerComponent.cs @@ -0,0 +1,10 @@ +using Content.Shared.Inventory; +using Robust.Shared.GameStates; + +namespace Content.Shared.StepTrigger.Components; + +/// +/// This is used for marking step trigger events that require the user to wear shoes, such as for glass shards. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class ClothingRequiredStepTriggerComponent : Component; diff --git a/Content.Shared/StepTrigger/Components/ClothingRequiredStepTriggerImmuneComponent.cs b/Content.Shared/StepTrigger/Components/ClothingRequiredStepTriggerImmuneComponent.cs new file mode 100644 index 0000000000..dc76207828 --- /dev/null +++ b/Content.Shared/StepTrigger/Components/ClothingRequiredStepTriggerImmuneComponent.cs @@ -0,0 +1,16 @@ +using Content.Shared.Inventory; +using Content.Shared.StepTrigger.Systems; +using Robust.Shared.GameStates; + +namespace Content.Shared.StepTrigger.Components; + +/// +/// This is used for cancelling step trigger events if the user is wearing clothing in a valid slot. +/// +[RegisterComponent, NetworkedComponent] +[Access(typeof(StepTriggerImmuneSystem))] +public sealed partial class ClothingRequiredStepTriggerImmuneComponent : Component, IClothingSlots +{ + [DataField] + public SlotFlags Slots { get; set; } = SlotFlags.FEET; +} diff --git a/Content.Shared/StepTrigger/Components/ShoesRequiredStepTriggerComponent.cs b/Content.Shared/StepTrigger/Components/ShoesRequiredStepTriggerComponent.cs deleted file mode 100644 index dd95b94a7e..0000000000 --- a/Content.Shared/StepTrigger/Components/ShoesRequiredStepTriggerComponent.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Robust.Shared.GameStates; - -namespace Content.Shared.StepTrigger.Components; - -/// -/// This is used for cancelling step trigger events if the user is wearing shoes, such as for glass shards. -/// -[RegisterComponent, NetworkedComponent] -public sealed partial class ShoesRequiredStepTriggerComponent : Component -{ -} diff --git a/Content.Shared/StepTrigger/Components/StepTriggerComponent.cs b/Content.Shared/StepTrigger/Components/StepTriggerComponent.cs index b8483d021a..d12c2c983e 100644 --- a/Content.Shared/StepTrigger/Components/StepTriggerComponent.cs +++ b/Content.Shared/StepTrigger/Components/StepTriggerComponent.cs @@ -1,3 +1,4 @@ +using Content.Shared.StepTrigger.Prototypes; using Content.Shared.StepTrigger.Systems; using Content.Shared.Whitelist; using Robust.Shared.GameStates; @@ -53,15 +54,18 @@ public sealed partial class StepTriggerComponent : Component public bool IgnoreWeightless; /// - /// Does this have separate "StepOn" and "StepOff" triggers. + /// Does this have separate "StepOn" and "StepOff" triggers. /// [DataField, AutoNetworkedField] public bool StepOn = false; + + /// + /// If TriggerGroups is specified, it will check StepTriggerImmunityComponent to have the same TriggerType to activate immunity + /// + [DataField] + public StepTriggerGroup? TriggerGroups; } [RegisterComponent] [Access(typeof(StepTriggerSystem))] -public sealed partial class StepTriggerActiveComponent : Component -{ - -} +public sealed partial class StepTriggerActiveComponent : Component { } diff --git a/Content.Shared/StepTrigger/Components/StepTriggerImmuneComponent.cs b/Content.Shared/StepTrigger/Components/StepTriggerImmuneComponent.cs new file mode 100644 index 0000000000..1b92905fa6 --- /dev/null +++ b/Content.Shared/StepTrigger/Components/StepTriggerImmuneComponent.cs @@ -0,0 +1,25 @@ +using Content.Shared.StepTrigger.Prototypes; +using Content.Shared.StepTrigger.Systems; +using Robust.Shared.GameStates; + +namespace Content.Shared.StepTrigger.Components; + +/// +/// This component marks an entity as being immune to all step triggers. +/// For example, a Felinid or Harpy being so low density, that they don't set off landmines. +/// +/// +/// This is the "Earliest Possible Exit" method, and therefore isn't possible to un-cancel. +/// It will prevent ALL step trigger events from firing. Therefore there may sometimes be unintended consequences to this. +/// Consider using a subscription to StepTriggerAttemptEvent if you wish to be more selective. +/// +[RegisterComponent, NetworkedComponent] +[Access(typeof(StepTriggerSystem))] +public sealed partial class StepTriggerImmuneComponent : Component +{ + /// + /// WhiteList of immunity step triggers. + /// + [DataField] + public StepTriggerGroup? Whitelist; +} diff --git a/Content.Shared/StepTrigger/Prototypes/StepTriggerGroup.cs b/Content.Shared/StepTrigger/Prototypes/StepTriggerGroup.cs new file mode 100644 index 0000000000..5b64085e9a --- /dev/null +++ b/Content.Shared/StepTrigger/Prototypes/StepTriggerGroup.cs @@ -0,0 +1,72 @@ +using Content.Shared.StepTrigger.Components; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; + +namespace Content.Shared.StepTrigger.Prototypes; + +/// +/// A group of +/// Used to determine StepTriggerTypes like Tags. +/// Used for better work with Immunity. +/// StepTriggerTypes in StepTriggerTypes.yml +/// +/// +/// stepTriggerGroups: +/// types: +/// - Lava +/// - Landmine +/// - Shard +/// - Chasm +/// - Mousetrap +/// - SlipTile +/// - SlipEntity +/// +[DataDefinition] +[Serializable, NetSerializable] +public sealed partial class StepTriggerGroup +{ + [DataField] + public List>? Types = null; + + /// + /// Checks if types of this StepTriggerGroup is similar to types of AnotherGroup + /// + public bool IsValid(StepTriggerGroup? anotherGroup) + { + if (Types is null) + return false; + + foreach (var type in Types) + { + if (anotherGroup != null + && anotherGroup.Types != null + && anotherGroup.Types.Contains(type)) + return true; + } + return false; + } + + /// + /// Checks validation (if types of this StepTriggerGroup are similar to types of + /// another StepTriggerComponent. + /// + public bool IsValid(StepTriggerComponent component) + { + if (component.TriggerGroups is null) + return false; + + return IsValid(component.TriggerGroups); + } + + /// + /// Checks validation (if types of this StepTriggerGroup are similar to types of + /// another StepTriggerImmuneComponent. + /// + public bool IsValid(StepTriggerImmuneComponent component) + { + if (component.Whitelist is null) + return false; + + return IsValid(component.Whitelist); + } +} diff --git a/Content.Shared/StepTrigger/Prototypes/StepTriggerTypePrototype.cs b/Content.Shared/StepTrigger/Prototypes/StepTriggerTypePrototype.cs new file mode 100644 index 0000000000..732eb4b732 --- /dev/null +++ b/Content.Shared/StepTrigger/Prototypes/StepTriggerTypePrototype.cs @@ -0,0 +1,15 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.StepTrigger.Prototypes; + +/// +/// Prototype representing a StepTriggerType in YAML. +/// Meant to only have an ID property, as that is the only thing that +/// gets saved in StepTriggerGroup. +/// +[Prototype] +public sealed partial class StepTriggerTypePrototype : IPrototype +{ + [ViewVariables, IdDataField] + public string ID { get; private set; } = default!; +} diff --git a/Content.Shared/StepTrigger/Systems/ShoesRequiredStepTriggerSystem.cs b/Content.Shared/StepTrigger/Systems/ShoesRequiredStepTriggerSystem.cs deleted file mode 100644 index 5fc9140dfd..0000000000 --- a/Content.Shared/StepTrigger/Systems/ShoesRequiredStepTriggerSystem.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Content.Shared.Examine; -using Content.Shared.Inventory; -using Content.Shared.StepTrigger.Components; -using Content.Shared.Tag; - -namespace Content.Shared.StepTrigger.Systems; - -public sealed class ShoesRequiredStepTriggerSystem : EntitySystem -{ - [Dependency] private readonly InventorySystem _inventory = default!; - [Dependency] private readonly TagSystem _tagSystem = default!; - - /// - public override void Initialize() - { - SubscribeLocalEvent(OnStepTriggerAttempt); - SubscribeLocalEvent(OnExamined); - } - - private void OnStepTriggerAttempt(EntityUid uid, ShoesRequiredStepTriggerComponent component, ref StepTriggerAttemptEvent args) - { - if (_tagSystem.HasTag(args.Tripper, "ShoesRequiredStepTriggerImmune")) - { - args.Cancelled = true; - return; - } - - if (!TryComp(args.Tripper, out var inventory)) - return; - - if (_inventory.TryGetSlotEntity(args.Tripper, "shoes", out _, inventory)) - { - args.Cancelled = true; - } - } - - private void OnExamined(EntityUid uid, ShoesRequiredStepTriggerComponent component, ExaminedEvent args) - { - args.PushMarkup(Loc.GetString("shoes-required-step-trigger-examine")); - } -} diff --git a/Content.Shared/StepTrigger/Systems/StepTriggerImmuneSystem.cs b/Content.Shared/StepTrigger/Systems/StepTriggerImmuneSystem.cs new file mode 100644 index 0000000000..ca72a20ae9 --- /dev/null +++ b/Content.Shared/StepTrigger/Systems/StepTriggerImmuneSystem.cs @@ -0,0 +1,37 @@ +using Content.Shared.Examine; +using Content.Shared.Inventory; +using Content.Shared.StepTrigger.Components; +using Content.Shared.Tag; + +namespace Content.Shared.StepTrigger.Systems; + +public sealed class StepTriggerImmuneSystem : EntitySystem +{ + [Dependency] private readonly InventorySystem _inventory = default!; + + /// + public override void Initialize() + { + SubscribeLocalEvent(OnStepTriggerAttempt); + SubscribeLocalEvent(OnStepTriggerClothingAttempt); + SubscribeLocalEvent(OnExamined); + } + + private void OnStepTriggerAttempt(Entity ent, ref StepTriggerAttemptEvent args) + { + args.Cancelled = true; + } + + private void OnStepTriggerClothingAttempt(EntityUid uid, ClothingRequiredStepTriggerComponent component, ref StepTriggerAttemptEvent args) + { + if (_inventory.TryGetInventoryEntity(args.Tripper, out _)) + { + args.Cancelled = true; + } + } + + private void OnExamined(EntityUid uid, ClothingRequiredStepTriggerComponent component, ExaminedEvent args) + { + args.PushMarkup(Loc.GetString("clothing-required-step-trigger-examine")); + } +} diff --git a/Content.Shared/StepTrigger/Systems/StepTriggerSystem.cs b/Content.Shared/StepTrigger/Systems/StepTriggerSystem.cs index d81ad754d1..d0cd5c4b4e 100644 --- a/Content.Shared/StepTrigger/Systems/StepTriggerSystem.cs +++ b/Content.Shared/StepTrigger/Systems/StepTriggerSystem.cs @@ -41,9 +41,7 @@ public override void Update(float frameTime) while (enumerator.MoveNext(out var uid, out var active, out var trigger, out var transform)) { if (!Update(uid, trigger, transform, query)) - { continue; - } RemCompDeferred(uid, active); } @@ -51,11 +49,8 @@ public override void Update(float frameTime) private bool Update(EntityUid uid, StepTriggerComponent component, TransformComponent transform, EntityQuery query) { - if (!component.Active || - component.Colliding.Count == 0) - { + if (!component.Active || component.Colliding.Count == 0) return true; - } if (component.Blacklist != null && TryComp(transform.GridUid, out var grid)) { @@ -67,17 +62,13 @@ private bool Update(EntityUid uid, StepTriggerComponent component, TransformComp if (ent == uid) continue; - if (component.Blacklist.IsValid(ent.Value, EntityManager) == true) - { + if (component.Blacklist.IsValid(ent.Value, EntityManager)) return false; - } } } foreach (var otherUid in component.Colliding) - { UpdateColliding(uid, component, transform, otherUid, query); - } return false; } @@ -95,9 +86,8 @@ private void UpdateColliding(EntityUid uid, StepTriggerComponent component, Tran if (!ourAabb.Intersects(otherAabb)) { if (component.CurrentlySteppedOn.Remove(otherUid)) - { Dirty(uid, component); - } + return; } @@ -109,9 +99,7 @@ private void UpdateColliding(EntityUid uid, StepTriggerComponent component, Tran || component.CurrentlySteppedOn.Contains(otherUid) || ratio < component.IntersectRatio || !CanTrigger(uid, otherUid, component)) - { return; - } if (component.StepOn) { @@ -130,7 +118,14 @@ private void UpdateColliding(EntityUid uid, StepTriggerComponent component, Tran private bool CanTrigger(EntityUid uid, EntityUid otherUid, StepTriggerComponent component) { - if (!component.Active || component.CurrentlySteppedOn.Contains(otherUid)) + if (!component.Active + || component.CurrentlySteppedOn.Contains(otherUid)) + return false; + + // Immunity checks + if (TryComp(otherUid, out var stepTriggerImmuneComponent) + && component.TriggerGroups != null + && component.TriggerGroups.IsValid(stepTriggerImmuneComponent)) return false; // Can't trigger if we don't ignore weightless entities @@ -141,7 +136,6 @@ private bool CanTrigger(EntityUid uid, EntityUid otherUid, StepTriggerComponent return false; var msg = new StepTriggerAttemptEvent { Source = uid, Tripper = otherUid }; - RaiseLocalEvent(uid, ref msg); return msg.Continue && !msg.Cancelled; @@ -151,18 +145,14 @@ private void OnStartCollide(EntityUid uid, StepTriggerComponent component, ref S { var otherUid = args.OtherEntity; - if (!args.OtherFixture.Hard) - return; - - if (!CanTrigger(uid, otherUid, component)) + if (!args.OtherFixture.Hard + || !CanTrigger(uid, otherUid, component)) return; EnsureComp(uid); if (component.Colliding.Add(otherUid)) - { Dirty(uid, component); - } } private void OnEndCollide(EntityUid uid, StepTriggerComponent component, ref EndCollideEvent args) @@ -182,29 +172,21 @@ private void OnEndCollide(EntityUid uid, StepTriggerComponent component, ref End } if (component.Colliding.Count == 0) - { RemCompDeferred(uid); - } } private void TriggerHandleState(EntityUid uid, StepTriggerComponent component, ref AfterAutoHandleStateEvent args) { if (component.Colliding.Count > 0) - { EnsureComp(uid); - } else - { RemCompDeferred(uid); - } } public void SetIntersectRatio(EntityUid uid, float ratio, StepTriggerComponent? component = null) { - if (!Resolve(uid, ref component)) - return; - - if (MathHelper.CloseToPercent(component.IntersectRatio, ratio)) + if (!Resolve(uid, ref component) + || MathHelper.CloseToPercent(component.IntersectRatio, ratio)) return; component.IntersectRatio = ratio; @@ -213,10 +195,8 @@ public void SetIntersectRatio(EntityUid uid, float ratio, StepTriggerComponent? public void SetRequiredTriggerSpeed(EntityUid uid, float speed, StepTriggerComponent? component = null) { - if (!Resolve(uid, ref component)) - return; - - if (MathHelper.CloseToPercent(component.RequiredTriggeredSpeed, speed)) + if (!Resolve(uid, ref component) + || MathHelper.CloseToPercent(component.RequiredTriggeredSpeed, speed)) return; component.RequiredTriggeredSpeed = speed; @@ -225,10 +205,8 @@ public void SetRequiredTriggerSpeed(EntityUid uid, float speed, StepTriggerCompo public void SetActive(EntityUid uid, bool active, StepTriggerComponent? component = null) { - if (!Resolve(uid, ref component)) - return; - - if (active == component.Active) + if (!Resolve(uid, ref component) + || active == component.Active) return; component.Active = active; @@ -236,26 +214,21 @@ public void SetActive(EntityUid uid, bool active, StepTriggerComponent? componen } } +/// +/// Raised at the beginning of a step trigger, and before entering the checks. +/// Allows for entities to end the steptrigger early via args.Cancelled. +/// [ByRefEvent] -public struct StepTriggerAttemptEvent -{ - public EntityUid Source; - public EntityUid Tripper; - public bool Continue; - /// - /// Set by systems which wish to cancel the step trigger event, regardless of event ordering. - /// - public bool Cancelled; -} +public record struct StepTriggerAttemptEvent(EntityUid Source, EntityUid Tripper, bool Continue, bool Cancelled); /// -/// Raised when an entity stands on a steptrigger initially (assuming it has both on and off states). +/// Raised when an entity stands on a steptrigger initially (assuming it has both on and off states). /// [ByRefEvent] public readonly record struct StepTriggeredOnEvent(EntityUid Source, EntityUid Tripper); /// -/// Raised when an entity leaves a steptrigger if it has on and off states OR when an entity intersects a steptrigger. +/// Raised when an entity leaves a steptrigger if it has on and off states OR when an entity intersects a steptrigger. /// [ByRefEvent] public readonly record struct StepTriggeredOffEvent(EntityUid Source, EntityUid Tripper); diff --git a/Content.Shared/Storage/Components/SecretStashComponent.cs b/Content.Shared/Storage/Components/SecretStashComponent.cs index 8595f79ca5..07a1078f63 100644 --- a/Content.Shared/Storage/Components/SecretStashComponent.cs +++ b/Content.Shared/Storage/Components/SecretStashComponent.cs @@ -6,6 +6,7 @@ using Content.Shared.Tools; using Robust.Shared.GameStates; using Content.Shared.DoAfter; +using Content.Shared.Toilet.Components; using Robust.Shared.Serialization; namespace Content.Shared.Storage.Components diff --git a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs index 966ec02b80..2e800c386b 100644 --- a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs +++ b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs @@ -2,14 +2,18 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using Content.Shared.ActionBlocker; +using Content.Shared.Administration; +using Content.Shared.Administration.Managers; using Content.Shared.Containers.ItemSlots; -using Content.Shared.Coordinates; using Content.Shared.Destructible; using Content.Shared.DoAfter; +using Content.Shared.Ghost; using Content.Shared.Hands.Components; using Content.Shared.Hands.EntitySystems; using Content.Shared.Implants.Components; +using Content.Shared.Input; using Content.Shared.Interaction; +using Content.Shared.Inventory; using Content.Shared.Item; using Content.Shared.Lock; using Content.Shared.Nyanotrasen.Item.PseudoItem; @@ -23,10 +27,13 @@ using Robust.Shared.Audio.Systems; using Robust.Shared.Containers; using Robust.Shared.GameStates; +using Robust.Shared.Input.Binding; using Robust.Shared.Map; +using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Serialization; +using Robust.Shared.Utility; namespace Content.Shared.Storage.EntitySystems; @@ -34,6 +41,7 @@ public abstract class SharedStorageSystem : EntitySystem { [Dependency] private readonly IPrototypeManager _prototype = default!; [Dependency] protected readonly IRobustRandom Random = default!; + [Dependency] private readonly ISharedAdminManager _admin = default!; [Dependency] protected readonly ActionBlockerSystem ActionBlocker = default!; [Dependency] private readonly EntityLookupSystem _entityLookupSystem = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; @@ -42,6 +50,7 @@ public abstract class SharedStorageSystem : EntitySystem [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; [Dependency] protected readonly SharedEntityStorageSystem EntityStorage = default!; [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; + [Dependency] private readonly InventorySystem _inventory = default!; [Dependency] protected readonly SharedItemSystem ItemSystem = default!; [Dependency] private readonly SharedPopupSystem _popupSystem = default!; [Dependency] private readonly SharedHandsSystem _sharedHandsSystem = default!; @@ -63,9 +72,17 @@ public abstract class SharedStorageSystem : EntitySystem public bool CheckingCanInsert; + private List _entList = new(); + private HashSet _entSet = new(); + private readonly List _sortedSizes = new(); private FrozenDictionary _nextSmallest = FrozenDictionary.Empty; + private const string QuickInsertUseDelayID = "quickInsert"; + private const string OpenUiUseDelayID = "storage"; + + protected readonly List CantFillReasons = []; + /// public override void Initialize() { @@ -76,6 +93,13 @@ public override void Initialize() _xformQuery = GetEntityQuery(); _prototype.PrototypesReloaded += OnPrototypesReloaded; + Subs.BuiEvents(StorageComponent.StorageUiKey.Key, subs => + { + subs.Event(OnBoundUIClosed); + }); + + SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent>(AddUiVerb); SubscribeLocalEvent(OnStorageGetState); SubscribeLocalEvent(OnStorageHandleState); SubscribeLocalEvent(OnComponentInit, before: new[] { typeof(SharedContainerSystem) }); @@ -86,6 +110,7 @@ public override void Initialize() SubscribeLocalEvent(AfterInteract); SubscribeLocalEvent(OnDestroy); SubscribeLocalEvent(OnBoundUIOpen); + SubscribeLocalEvent(OnLockToggled); SubscribeLocalEvent(OnStackCountChanged); SubscribeLocalEvent(OnEntInserted); @@ -102,9 +127,20 @@ public override void Initialize() SubscribeLocalEvent(OnReclaimed); + CommandBinds.Builder + .Bind(ContentKeyFunctions.OpenBackpack, InputCmdHandler.FromDelegate(HandleOpenBackpack, handle: false)) + .Bind(ContentKeyFunctions.OpenBelt, InputCmdHandler.FromDelegate(HandleOpenBelt, handle: false)) + .Register(); + UpdatePrototypeCache(); } + private void OnMapInit(Entity entity, ref MapInitEvent args) + { + UseDelay.SetLength(entity.Owner, entity.Comp.QuickInsertCooldown, QuickInsertUseDelayID); + UseDelay.SetLength(entity.Owner, entity.Comp.OpenUiCooldown, OpenUiUseDelayID); + } + private void OnStorageGetState(EntityUid uid, StorageComponent component, ref ComponentGetState args) { var storedItems = new Dictionary(); @@ -117,7 +153,6 @@ private void OnStorageGetState(EntityUid uid, StorageComponent component, ref Co args.State = new StorageComponentState() { Grid = new List(component.Grid), - IsUiOpen = component.IsUiOpen, MaxItemSize = component.MaxItemSize, StoredItems = storedItems, SavedLocations = component.SavedLocations @@ -131,7 +166,6 @@ private void OnStorageHandleState(EntityUid uid, StorageComponent component, ref component.Grid.Clear(); component.Grid.AddRange(state.Grid); - component.IsUiOpen = state.IsUiOpen; component.MaxItemSize = state.MaxItemSize; component.StoredItems.Clear(); @@ -183,9 +217,108 @@ private void OnComponentInit(EntityUid uid, StorageComponent storageComp, Compon UpdateAppearance((uid, storageComp, null)); } - public virtual void UpdateUI(Entity entity) {} + /// + /// If the user has nested-UIs open (e.g., PDA UI open when pda is in a backpack), close them. + /// + private void CloseNestedInterfaces(EntityUid uid, EntityUid actor, StorageComponent? storageComp = null) + { + if (!Resolve(uid, ref storageComp)) + return; - public virtual void OpenStorageUI(EntityUid uid, EntityUid entity, StorageComponent? storageComp = null, bool silent = false) { } + // for each containing thing + // if it has a storage comp + // ensure unsubscribe from session + // if it has a ui component + // close ui + foreach (var entity in storageComp.Container.ContainedEntities) + { + _ui.CloseUis(entity, actor); + } + } + + private void OnBoundUIClosed(EntityUid uid, StorageComponent storageComp, BoundUIClosedEvent args) + { + CloseNestedInterfaces(uid, args.Actor, storageComp); + + // If UI is closed for everyone + if (!_ui.IsUiOpen(uid, args.UiKey)) + { + UpdateAppearance((uid, storageComp, null)); + Audio.PlayPredicted(storageComp.StorageCloseSound, uid, args.Actor); + } + } + + private void AddUiVerb(EntityUid uid, StorageComponent component, GetVerbsEvent args) + { + var silent = false; + if (!args.CanAccess || !args.CanInteract || TryComp(uid, out var lockComponent) && lockComponent.Locked) + { + // we allow admins to open the storage anyways + if (!_admin.HasAdminFlag(args.User, AdminFlags.Admin)) + return; + + silent = true; + } + + silent |= HasComp(args.User); + + // Does this player currently have the storage UI open? + var uiOpen = _ui.IsUiOpen(uid, StorageComponent.StorageUiKey.Key, args.User); + + ActivationVerb verb = new() + { + Act = () => + { + if (uiOpen) + { + _ui.CloseUi(uid, StorageComponent.StorageUiKey.Key, args.User); + } + else + { + OpenStorageUI(uid, args.User, component, silent); + } + } + }; + + if (uiOpen) + { + verb.Text = Loc.GetString("comp-storage-verb-close-storage"); + verb.Icon = new SpriteSpecifier.Texture( + new("/Textures/Interface/VerbIcons/close.svg.192dpi.png")); + } + else + { + verb.Text = Loc.GetString("comp-storage-verb-open-storage"); + verb.Icon = new SpriteSpecifier.Texture( + new("/Textures/Interface/VerbIcons/open.svg.192dpi.png")); + } + args.Verbs.Add(verb); + } + + /// + /// Opens the storage UI for an entity + /// + /// The entity to open the UI for + public void OpenStorageUI(EntityUid uid, EntityUid entity, StorageComponent? storageComp = null, bool silent = false) + { + if (!Resolve(uid, ref storageComp, false)) + return; + + // prevent spamming bag open / honkerton honk sound + silent |= TryComp(uid, out var useDelay) && UseDelay.IsDelayed((uid, useDelay)); + if (!silent) + { + if (!_ui.IsUiOpen(uid, StorageComponent.StorageUiKey.Key)) + Audio.PlayPredicted(storageComp.StorageOpenSound, uid, entity); + + if (useDelay != null) + UseDelay.TryResetDelay((uid, useDelay)); + } + + _ui.OpenUi(uid, StorageComponent.StorageUiKey.Key, entity); + } + + public virtual void UpdateUI(Entity entity) {} private void AddTransferVerbs(EntityUid uid, StorageComponent component, GetVerbsEvent args) { @@ -239,7 +372,16 @@ private void OnActivate(EntityUid uid, StorageComponent storageComp, ActivateInW if (args.Handled || TryComp(uid, out var lockComponent) && lockComponent.Locked) return; - OpenStorageUI(uid, args.User, storageComp); + // Toggle + if (_ui.IsUiOpen(uid, StorageComponent.StorageUiKey.Key, args.User)) + { + _ui.CloseUi(uid, StorageComponent.StorageUiKey.Key, args.User); + } + else + { + OpenStorageUI(uid, args.User, storageComp); + } + args.Handled = true; } @@ -262,36 +404,40 @@ private void OnImplantActivate(EntityUid uid, StorageComponent storageComp, Open /// private void AfterInteract(EntityUid uid, StorageComponent storageComp, AfterInteractEvent args) { - if (args.Handled || !args.CanReach) + if (args.Handled || !args.CanReach || !UseDelay.TryResetDelay(uid, checkDelayed: true, id: QuickInsertUseDelayID)) return; // Pick up all entities in a radius around the clicked location. // The last half of the if is because carpets exist and this is terrible if (storageComp.AreaInsert && (args.Target == null || !HasComp(args.Target.Value))) { - var validStorables = new List(); + _entList.Clear(); + _entSet.Clear(); + _entityLookupSystem.GetEntitiesInRange(args.ClickLocation, storageComp.AreaInsertRadius, _entSet, LookupFlags.Dynamic | LookupFlags.Sundries); var delay = 0f; - foreach (var entity in _entityLookupSystem.GetEntitiesInRange(args.ClickLocation, storageComp.AreaInsertRadius, LookupFlags.Dynamic | LookupFlags.Sundries)) + foreach (var entity in _entSet) { if (entity == args.User - // || !_itemQuery.HasComponent(entity) - || !TryComp(entity, out var itemComp) // Need comp to get item size to get weight + || !_itemQuery.TryGetComponent(entity, out var itemComp) // Need comp to get item size to get weight || !_prototype.TryIndex(itemComp.Size, out var itemSize) - || !CanInsert(uid, entity, out _, storageComp) + || !CanInsert(uid, entity, out _, storageComp, item: itemComp) || !_interactionSystem.InRangeUnobstructed(args.User, entity)) { continue; } - validStorables.Add(entity); + _entList.Add(entity); delay += itemSize.Weight * AreaInsertDelayPerItem; + + if (_entList.Count >= StorageComponent.AreaPickupLimit) + break; } //If there's only one then let's be generous - if (validStorables.Count > 1) + if (_entList.Count > 1) { - var doAfterArgs = new DoAfterArgs(EntityManager, args.User, delay, new AreaPickupDoAfterEvent(GetNetEntityList(validStorables)), uid, target: uid) + var doAfterArgs = new DoAfterArgs(EntityManager, args.User, delay, new AreaPickupDoAfterEvent(GetNetEntityList(_entList)), uid, target: uid) { BreakOnDamage = true, BreakOnUserMove = true, @@ -313,7 +459,7 @@ private void AfterInteract(EntityUid uid, StorageComponent storageComp, AfterInt if (_containerSystem.IsEntityInContainer(target) || target == args.User - || !HasComp(target)) + || !_itemQuery.HasComponent(target)) { return; } @@ -331,10 +477,10 @@ private void AfterInteract(EntityUid uid, StorageComponent storageComp, AfterInt args.Handled = true; if (PlayerInsertEntityInWorld((uid, storageComp), args.User, target)) { - RaiseNetworkEvent(new AnimateInsertingEntitiesEvent(GetNetEntity(uid), + EntityManager.RaiseSharedEvent(new AnimateInsertingEntitiesEvent(GetNetEntity(uid), new List { GetNetEntity(target) }, new List { GetNetCoordinates(position) }, - new List { transformOwner.LocalRotation })); + new List { transformOwner.LocalRotation }), args.User); } } } @@ -349,20 +495,27 @@ private void OnDoAfter(EntityUid uid, StorageComponent component, AreaPickupDoAf var successfullyInserted = new List(); var successfullyInsertedPositions = new List(); var successfullyInsertedAngles = new List(); - _xformQuery.TryGetComponent(uid, out var xform); - foreach (var netEntity in args.Entities) + if (!_xformQuery.TryGetComponent(uid, out var xform)) + { + return; + } + + var entCount = Math.Min(StorageComponent.AreaPickupLimit, args.Entities.Count); + + for (var i = 0; i < entCount; i++) { - var entity = GetEntity(netEntity); + var entity = GetEntity(args.Entities[i]); // Check again, situation may have changed for some entities, but we'll still pick up any that are valid if (_containerSystem.IsEntityInContainer(entity) || entity == args.Args.User || !_itemQuery.HasComponent(entity)) + { continue; + } - if (xform == null || - !_xformQuery.TryGetComponent(entity, out var targetXform) || + if (!_xformQuery.TryGetComponent(entity, out var targetXform) || targetXform.MapID != xform.MapID) { continue; @@ -387,12 +540,12 @@ private void OnDoAfter(EntityUid uid, StorageComponent component, AreaPickupDoAf // If we picked up at least one thing, play a sound and do a cool animation! if (successfullyInserted.Count > 0) { - Audio.PlayPvs(component.StorageInsertSound, uid); - RaiseNetworkEvent(new AnimateInsertingEntitiesEvent( + Audio.PlayPredicted(component.StorageInsertSound, uid, args.User); + EntityManager.RaiseSharedEvent(new AnimateInsertingEntitiesEvent( GetNetEntity(uid), GetNetEntityList(successfullyInserted), GetNetCoordinatesList(successfullyInsertedPositions), - successfullyInsertedAngles)); + successfullyInsertedAngles), args.User); } args.Handled = true; @@ -427,8 +580,7 @@ private void OnInteractWithItem(StorageInteractWithItemEvent msg, EntitySessionE if (!TryComp(uid, out var storageComp)) return; - if (!_ui.TryGetUi(uid, StorageComponent.StorageUiKey.Key, out var bui) || - !bui.SubscribedSessions.Contains(args.SenderSession)) + if (!_ui.IsUiOpen(uid, StorageComponent.StorageUiKey.Key, player)) return; if (!Exists(entity)) @@ -470,8 +622,7 @@ private void OnSetItemLocation(StorageSetItemLocationEvent msg, EntitySessionEve if (!TryComp(storageEnt, out var storageComp)) return; - if (!_ui.TryGetUi(storageEnt, StorageComponent.StorageUiKey.Key, out var bui) || - !bui.SubscribedSessions.Contains(args.SenderSession)) + if (!_ui.IsUiOpen(storageEnt, StorageComponent.StorageUiKey.Key, player)) return; if (!Exists(itemEnt)) @@ -497,8 +648,7 @@ private void OnRemoveItem(StorageRemoveItemEvent msg, EntitySessionEventArgs arg if (!TryComp(storageEnt, out var storageComp)) return; - if (!_ui.TryGetUi(storageEnt, StorageComponent.StorageUiKey.Key, out var bui) || - !bui.SubscribedSessions.Contains(args.SenderSession)) + if (!_ui.IsUiOpen(storageEnt, StorageComponent.StorageUiKey.Key, player)) return; if (!Exists(itemEnt)) @@ -525,8 +675,7 @@ private void OnInsertItemIntoLocation(StorageInsertItemIntoLocationEvent msg, En if (!TryComp(storageEnt, out var storageComp)) return; - if (!_ui.TryGetUi(storageEnt, StorageComponent.StorageUiKey.Key, out var bui) || - !bui.SubscribedSessions.Contains(args.SenderSession)) + if (!_ui.IsUiOpen(storageEnt, StorageComponent.StorageUiKey.Key, player)) return; if (!Exists(itemEnt)) @@ -551,11 +700,10 @@ private void OnSaveItemLocation(StorageSaveItemLocationEvent msg, EntitySessionE var storage = GetEntity(msg.Storage); var item = GetEntity(msg.Item); - if (!TryComp(storage, out var storageComp)) + if (!HasComp(storage)) return; - if (!_ui.TryGetUi(storage, StorageComponent.StorageUiKey.Key, out var bui) || - !bui.SubscribedSessions.Contains(args.SenderSession)) + if (!_ui.IsUiOpen(storage, StorageComponent.StorageUiKey.Key, player)) return; if (!Exists(item)) @@ -572,11 +720,7 @@ private void OnSaveItemLocation(StorageSaveItemLocationEvent msg, EntitySessionE private void OnBoundUIOpen(EntityUid uid, StorageComponent storageComp, BoundUIOpenedEvent args) { - if (!storageComp.IsUiOpen) - { - storageComp.IsUiOpen = true; - UpdateAppearance((uid, storageComp, null)); - } + UpdateAppearance((uid, storageComp, null)); } private void OnEntInserted(Entity entity, ref EntInsertedIntoContainerMessage args) @@ -629,8 +773,15 @@ private void OnInsertAttempt(EntityUid uid, StorageComponent component, Containe if (CheckingCanInsert) return; - if (!CanInsert(uid, args.EntityUid, out _, component, ignoreStacks: true)) + if (!CanInsert(uid, args.EntityUid, out var reason, component, ignoreStacks: true)) + { +#if DEBUG + if (reason != null) + CantFillReasons.Add(reason); +#endif + args.Cancel(); + } } public void UpdateAppearance(Entity entity) @@ -647,11 +798,13 @@ public void UpdateAppearance(Entity ent var capacity = storage.Grid.GetArea(); var used = GetCumulativeItemAreas((uid, storage)); + var isOpen = _ui.IsUiOpen(entity.Owner, StorageComponent.StorageUiKey.Key); + _appearance.SetData(uid, StorageVisuals.StorageUsed, used, appearance); _appearance.SetData(uid, StorageVisuals.Capacity, capacity, appearance); - _appearance.SetData(uid, StorageVisuals.Open, storage.IsUiOpen, appearance); - _appearance.SetData(uid, SharedBagOpenVisuals.BagState, storage.IsUiOpen ? SharedBagState.Open : SharedBagState.Closed, appearance); - _appearance.SetData(uid, StackVisuals.Hide, !storage.IsUiOpen, appearance); + _appearance.SetData(uid, StorageVisuals.Open, isOpen, appearance); + _appearance.SetData(uid, SharedBagOpenVisuals.BagState, isOpen ? SharedBagState.Open : SharedBagState.Closed, appearance); + _appearance.SetData(uid, StackVisuals.Hide, !isOpen, appearance); } /// @@ -938,7 +1091,7 @@ public bool PlayerInsertHeldEntity(EntityUid uid, EntityUid player, StorageCompo /// true if inserted, false otherwise public bool PlayerInsertEntityInWorld(Entity uid, EntityUid player, EntityUid toInsert) { - if (!Resolve(uid, ref uid.Comp) || !_interactionSystem.InRangeUnobstructed(player, uid)) + if (!Resolve(uid, ref uid.Comp) || !_interactionSystem.InRangeUnobstructed(player, uid.Owner)) return false; if (!Insert(uid, toInsert, out _, user: player, uid.Comp)) @@ -1076,7 +1229,7 @@ public void SaveItemLocation(Entity ent, Entity uid) return _nextSmallest[item.Size]; } + /// + /// Checks if a storage's UI is open by anyone when locked, and closes it unless they're an admin. + /// + private void OnLockToggled(EntityUid uid, StorageComponent component, ref LockToggledEvent args) + { + if (!args.Locked) + return; + + // Gets everyone looking at the UI + foreach (var actor in _ui.GetActors(uid, StorageComponent.StorageUiKey.Key).ToList()) + { + if (_admin.HasAdminFlag(actor, AdminFlags.Admin)) + continue; + + // And closes it unless they're an admin + _ui.CloseUi(uid, StorageComponent.StorageUiKey.Key, actor); + } + } + private void OnStackCountChanged(EntityUid uid, MetaDataComponent component, StackCountChangedEvent args) { if (_containerSystem.TryGetContainingContainer(uid, out var container, component) && @@ -1263,6 +1435,40 @@ private void OnStackCountChanged(EntityUid uid, MetaDataComponent component, Sta } } + private void HandleOpenBackpack(ICommonSession? session) + { + HandleOpenSlotUI(session, "back"); + } + + private void HandleOpenBelt(ICommonSession? session) + { + HandleOpenSlotUI(session, "belt"); + } + + private void HandleOpenSlotUI(ICommonSession? session, string slot) + { + if (session is not { } playerSession) + return; + + if (playerSession.AttachedEntity is not {Valid: true} playerEnt || !Exists(playerEnt)) + return; + + if (!_inventory.TryGetSlotEntity(playerEnt, slot, out var storageEnt)) + return; + + if (!ActionBlocker.CanInteract(playerEnt, storageEnt)) + return; + + OpenStorageUI(storageEnt.Value, playerEnt); + } + + protected void ClearCantFillReasons() + { +#if DEBUG + CantFillReasons.Clear(); +#endif + } + /// /// Plays a clientside pickup animation for the specified uid. /// @@ -1272,8 +1478,6 @@ public abstract void PlayPickupAnimation(EntityUid uid, EntityCoordinates initia [Serializable, NetSerializable] protected sealed class StorageComponentState : ComponentState { - public bool IsUiOpen; - public Dictionary StoredItems = new(); public Dictionary> SavedLocations = new(); diff --git a/Content.Shared/Storage/StorageComponent.cs b/Content.Shared/Storage/StorageComponent.cs index 2cae12f07a..ef682dd4f9 100644 --- a/Content.Shared/Storage/StorageComponent.cs +++ b/Content.Shared/Storage/StorageComponent.cs @@ -19,10 +19,6 @@ public sealed partial class StorageComponent : Component { public static string ContainerId = "storagebase"; - // TODO: This fucking sucks - [ViewVariables(VVAccess.ReadWrite), DataField] - public bool IsUiOpen; - [ViewVariables] public Container Container = default!; @@ -57,9 +53,27 @@ public sealed partial class StorageComponent : Component [DataField] public bool QuickInsert; // Can insert storables by clicking them with the storage entity + /// + /// Minimum delay between quick/area insert actions. + /// + /// Used to prevent autoclickers spamming server with individual pickup actions. + public TimeSpan QuickInsertCooldown = TimeSpan.FromSeconds(0.5); + + /// + /// Minimum delay between UI open actions. + /// Used to spamming opening sounds. + /// + [DataField] + public TimeSpan OpenUiCooldown = TimeSpan.Zero; + [DataField] public bool ClickInsert = true; // Can insert stuff by clicking the storage entity with it + /// + /// How many entities area pickup can pickup at once. + /// + public const int AreaPickupLimit = 10; + [DataField] public bool AreaInsert; // Clicking with the storage entity causes it to insert all nearby storables after a delay @@ -214,15 +228,6 @@ public AnimateInsertingEntitiesEvent(NetEntity storage, List storedEn } } - /// - /// An extra BUI message that either opens, closes, or focuses the storage window based on context. - /// - [Serializable, NetSerializable] - public sealed class StorageModifyWindowMessage : BoundUserInterfaceMessage - { - - } - [NetSerializable] [Serializable] public enum StorageVisuals : byte diff --git a/Content.Shared/Store/ListingLocalisationHelpers.cs b/Content.Shared/Store/ListingLocalisationHelpers.cs index 882300109c..19cd029488 100644 --- a/Content.Shared/Store/ListingLocalisationHelpers.cs +++ b/Content.Shared/Store/ListingLocalisationHelpers.cs @@ -18,6 +18,11 @@ public static string GetLocalisedNameOrEntityName(ListingData listingData, IProt else if (listingData.ProductEntity != null) name = prototypeManager.Index(listingData.ProductEntity.Value).Name; + if (listingData.DiscountValue > 0) + name += " " + Loc.GetString("store-sales-amount", ("amount", listingData.DiscountValue)); + else if (listingData.OldCost.Count > 0) + name += " " + Loc.GetString("store-sales-over"); + return name; } diff --git a/Content.Shared/Store/ListingPrototype.cs b/Content.Shared/Store/ListingPrototype.cs index d3d2e13cdf..445b5742dd 100644 --- a/Content.Shared/Store/ListingPrototype.cs +++ b/Content.Shared/Store/ListingPrototype.cs @@ -75,14 +75,14 @@ public partial class ListingData : IEquatable, ICloneable public EntProtoId? ProductAction; /// - /// The listing ID of the related upgrade listing. Can be used to link a to an - /// upgrade or to use standalone as an upgrade + /// The listing ID of the related upgrade listing. Can be used to link a to an + /// upgrade or to use standalone as an upgrade /// [DataField] - public ProtoId? ProductUpgradeID; + public ProtoId? ProductUpgradeId; /// - /// Keeps track of the current action entity this is tied to, for action upgrades + /// Keeps track of the current action entity this is tied to, for action upgrades /// [DataField] [NonSerialized] @@ -109,6 +109,19 @@ public partial class ListingData : IEquatable, ICloneable [DataField] public TimeSpan RestockTime = TimeSpan.Zero; + [DataField] + public int SaleLimit = 3; + + [DataField] + public bool SaleBlacklist; + + public int DiscountValue; + + public Dictionary, FixedPoint2> OldCost = new(); + + [DataField] + public List Components = new(); + public bool Equals(ListingData? listing) { if (listing == null) @@ -161,11 +174,16 @@ public object Clone() Priority = Priority, ProductEntity = ProductEntity, ProductAction = ProductAction, - ProductUpgradeID = ProductUpgradeID, + ProductUpgradeId = ProductUpgradeId, ProductActionEntity = ProductActionEntity, ProductEvent = ProductEvent, PurchaseAmount = PurchaseAmount, RestockTime = RestockTime, + SaleLimit = SaleLimit, + SaleBlacklist = SaleBlacklist, + DiscountValue = DiscountValue, + OldCost = OldCost, + Components = Components, }; } } diff --git a/Content.Shared/Store/StorePresetPrototype.cs b/Content.Shared/Store/StorePresetPrototype.cs index ce7f0312b6..41ee510bd8 100644 --- a/Content.Shared/Store/StorePresetPrototype.cs +++ b/Content.Shared/Store/StorePresetPrototype.cs @@ -1,3 +1,4 @@ +using Content.Shared.StoreDiscount; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary; @@ -38,4 +39,7 @@ public sealed partial class StorePresetPrototype : IPrototype /// [DataField("currencyWhitelist", customTypeSerializer: typeof(PrototypeIdHashSetSerializer))] public HashSet CurrencyWhitelist { get; private set; } = new(); + + [DataField] + public SalesSpecifier Sales { get; private set; } = new(); } diff --git a/Content.Shared/StoreDiscount/SalesSpecifier.cs b/Content.Shared/StoreDiscount/SalesSpecifier.cs new file mode 100644 index 0000000000..21539f85d4 --- /dev/null +++ b/Content.Shared/StoreDiscount/SalesSpecifier.cs @@ -0,0 +1,38 @@ +namespace Content.Shared.StoreDiscount; + +[DataDefinition] +public sealed partial class SalesSpecifier +{ + [DataField] + public bool Enabled { get; private set; } + + [DataField] + public float MinMultiplier { get; private set; } + + [DataField] + public float MaxMultiplier { get; private set; } + + [DataField] + public int MinItems { get; private set; } + + [DataField] + public int MaxItems { get; private set; } + + [DataField] + public string SalesCategory { get; private set; } = string.Empty; + + public SalesSpecifier() + { + } + + public SalesSpecifier(bool enabled, float minMultiplier, float maxMultiplier, int minItems, int maxItems, + string salesCategory) + { + Enabled = enabled; + MinMultiplier = minMultiplier; + MaxMultiplier = maxMultiplier; + MinItems = minItems; + MaxItems = maxItems; + SalesCategory = salesCategory; + } +} diff --git a/Content.Shared/Stunnable/Events/KnockdownOnHitEvent.cs b/Content.Shared/Stunnable/Events/KnockdownOnHitEvent.cs new file mode 100644 index 0000000000..b177f0e3f3 --- /dev/null +++ b/Content.Shared/Stunnable/Events/KnockdownOnHitEvent.cs @@ -0,0 +1,4 @@ +namespace Content.Shared.Stunnable.Events; + +[ByRefEvent] +public record struct KnockdownOnHitAttemptEvent(bool Cancelled); diff --git a/Content.Shared/Stunnable/KnockedDownComponent.cs b/Content.Shared/Stunnable/KnockedDownComponent.cs index e4f11b8cda..865c69bf6e 100644 --- a/Content.Shared/Stunnable/KnockedDownComponent.cs +++ b/Content.Shared/Stunnable/KnockedDownComponent.cs @@ -1,6 +1,6 @@ +using Content.Shared.Standing; using Robust.Shared.Audio; using Robust.Shared.GameStates; -using Robust.Shared.Serialization; namespace Content.Shared.Stunnable; @@ -13,6 +13,9 @@ public sealed partial class KnockedDownComponent : Component [DataField("helpAttemptSound")] public SoundSpecifier StunAttemptSound = new SoundPathSpecifier("/Audio/Effects/thudswoosh.ogg"); + [DataField] + public DropHeldItemsBehavior DropHeldItemsBehavior = DropHeldItemsBehavior.DropIfStanding; + [ViewVariables, AutoNetworkedField] public float HelpTimer = 0f; } diff --git a/Content.Shared/Stunnable/SharedStunSystem.cs b/Content.Shared/Stunnable/SharedStunSystem.cs index c447f8c8bc..05d6b8ec53 100644 --- a/Content.Shared/Stunnable/SharedStunSystem.cs +++ b/Content.Shared/Stunnable/SharedStunSystem.cs @@ -19,6 +19,7 @@ using Content.Shared.Throwing; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; +using Robust.Shared.Containers; using Robust.Shared.GameStates; using Robust.Shared.Player; @@ -26,12 +27,16 @@ namespace Content.Shared.Stunnable; public abstract class SharedStunSystem : EntitySystem { + [Dependency] private readonly IComponentFactory _componentFactory = default!; + [Dependency] private readonly ActionBlockerSystem _blocker = default!; [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly StandingStateSystem _standingState = default!; [Dependency] private readonly StatusEffectsSystem _statusEffect = default!; + [Dependency] private readonly SharedLayingDownSystem _layingDown = default!; + [Dependency] private readonly SharedContainerSystem _container = default!; /// /// Friction modifier for knocked down players. @@ -76,15 +81,12 @@ public override void Initialize() private void OnMobStateChanged(EntityUid uid, MobStateComponent component, MobStateChangedEvent args) { if (!TryComp(uid, out var status)) - { return; - } + switch (args.NewMobState) { case MobState.Alive: - { break; - } case MobState.Critical: { _statusEffect.TryRemoveStatusEffect(uid, "Stun"); @@ -109,12 +111,23 @@ private void UpdateCanMove(EntityUid uid, StunnedComponent component, EntityEven private void OnKnockInit(EntityUid uid, KnockedDownComponent component, ComponentInit args) { - _standingState.Down(uid); + RaiseNetworkEvent(new CheckAutoGetUpEvent(GetNetEntity(uid))); + _layingDown.TryLieDown(uid, null, null, component.DropHeldItemsBehavior); } private void OnKnockShutdown(EntityUid uid, KnockedDownComponent component, ComponentShutdown args) { - _standingState.Stand(uid); + if (!TryComp(uid, out StandingStateComponent? standing)) + return; + + if (TryComp(uid, out LayingDownComponent? layingDown)) + { + if (layingDown.AutoGetUp && !_container.IsEntityInContainer(uid)) + _layingDown.TryStandUp(uid, layingDown); + return; + } + + _standingState.Stand(uid, standing); } private void OnStandAttempt(EntityUid uid, KnockedDownComponent component, StandAttemptEvent args) @@ -148,13 +161,9 @@ private void OnRefreshMovespeed(EntityUid uid, SlowedDownComponent component, Re public bool TryStun(EntityUid uid, TimeSpan time, bool refresh, StatusEffectsComponent? status = null) { - if (time <= TimeSpan.Zero) - return false; - - if (!Resolve(uid, ref status, false)) - return false; - - if (!_statusEffect.TryAddStatusEffect(uid, "Stun", time, refresh)) + if (time <= TimeSpan.Zero + || !Resolve(uid, ref status, false) + || !_statusEffect.TryAddStatusEffect(uid, "Stun", time, refresh)) return false; var ev = new StunnedEvent(); @@ -168,15 +177,31 @@ public bool TryStun(EntityUid uid, TimeSpan time, bool refresh, /// Knocks down the entity, making it fall to the ground. /// public bool TryKnockdown(EntityUid uid, TimeSpan time, bool refresh, + DropHeldItemsBehavior behavior = DropHeldItemsBehavior.DropIfStanding, StatusEffectsComponent? status = null) { - if (time <= TimeSpan.Zero) + if (time <= TimeSpan.Zero || !Resolve(uid, ref status, false)) return false; - if (!Resolve(uid, ref status, false)) + var component = _componentFactory.GetComponent(); + component.DropHeldItemsBehavior = behavior; + if (!_statusEffect.TryAddStatusEffect(uid, "KnockedDown", time, refresh, component)) return false; - if (!_statusEffect.TryAddStatusEffect(uid, "KnockedDown", time, refresh)) + var ev = new KnockedDownEvent(); + RaiseLocalEvent(uid, ref ev); + return true; + } + + /// + /// Knocks down the entity, making it fall to the ground. + /// + public bool TryKnockdown(EntityUid uid, TimeSpan time, bool refresh, + StatusEffectsComponent? status = null) + { + if (time <= TimeSpan.Zero + || !Resolve(uid, ref status, false) + || !_statusEffect.TryAddStatusEffect(uid, "KnockedDown", time, refresh)) return false; var ev = new KnockedDownEvent(); @@ -204,10 +229,8 @@ public bool TrySlowdown(EntityUid uid, TimeSpan time, bool refresh, float walkSpeedMultiplier = 1f, float runSpeedMultiplier = 1f, StatusEffectsComponent? status = null) { - if (!Resolve(uid, ref status, false)) - return false; - - if (time <= TimeSpan.Zero) + if (!Resolve(uid, ref status, false) + || time <= TimeSpan.Zero) return false; if (_statusEffect.TryAddStatusEffect(uid, "SlowedDown", time, refresh, status)) @@ -230,11 +253,8 @@ public bool TrySlowdown(EntityUid uid, TimeSpan time, bool refresh, private void OnInteractHand(EntityUid uid, KnockedDownComponent knocked, InteractHandEvent args) { - if (args.Handled || knocked.HelpTimer > 0f) - return; - - // TODO: This should be an event. - if (HasComp(uid)) + if (args.Handled || knocked.HelpTimer > 0f + || HasComp(uid)) return; // Set it to half the help interval so helping is actually useful... @@ -270,15 +290,19 @@ private void OnAttempt(EntityUid uid, StunnedComponent stunned, CancellableEntit private void OnEquipAttempt(EntityUid uid, StunnedComponent stunned, IsEquippingAttemptEvent args) { // is this a self-equip, or are they being stripped? - if (args.Equipee == uid) - args.Cancel(); + if (args.Equipee != uid) + return; + + args.Cancel(); } private void OnUnequipAttempt(EntityUid uid, StunnedComponent stunned, IsUnequippingAttemptEvent args) { // is this a self-equip, or are they being stripped? - if (args.Unequipee == uid) - args.Cancel(); + if (args.Unequipee != uid) + return; + + args.Cancel(); } #endregion diff --git a/Content.Shared/Stunnable/StaminaDamageResistanceSystem.cs b/Content.Shared/Stunnable/StaminaDamageResistanceSystem.cs index 7632eed504..170663104d 100644 --- a/Content.Shared/Stunnable/StaminaDamageResistanceSystem.cs +++ b/Content.Shared/Stunnable/StaminaDamageResistanceSystem.cs @@ -1,3 +1,4 @@ +using Content.Shared.Armor; using Content.Shared.Damage.Events; using Content.Shared.Examine; using Content.Shared.Inventory; @@ -11,16 +12,21 @@ public override void Initialize() base.Initialize(); SubscribeLocalEvent>(OnStaminaMeleeHit); - SubscribeLocalEvent(OnExamine); + SubscribeLocalEvent(OnExamine); } private void OnStaminaMeleeHit(Entity ent, ref InventoryRelayedEvent args) { args.Args.Multiplier *= ent.Comp.Coefficient; } - private void OnExamine(Entity ent, ref ExaminedEvent args) + private void OnExamine(Entity ent, ref ArmorExamineEvent args) { var percentage = (1 - ent.Comp.Coefficient) * 100; - args.PushMarkup(Loc.GetString("armor-examine-stamina", ("num", percentage))); + + if (percentage == 0) + return; + + args.Msg.PushNewline(); + args.Msg.AddMarkup(Loc.GetString("armor-examine-stamina", ("num", percentage))); } } diff --git a/Content.Shared/Tag/TagSystem.cs b/Content.Shared/Tag/TagSystem.cs index 0628b892ed..0707308e48 100644 --- a/Content.Shared/Tag/TagSystem.cs +++ b/Content.Shared/Tag/TagSystem.cs @@ -1,3 +1,4 @@ +using System.Diagnostics; using System.Linq; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; @@ -9,9 +10,12 @@ public sealed class TagSystem : EntitySystem { [Dependency] private readonly IPrototypeManager _proto = default!; + private EntityQuery _tagQuery; + public override void Initialize() { base.Initialize(); + _tagQuery = GetEntityQuery(); SubscribeLocalEvent(OnTagGetState); SubscribeLocalEvent(OnTagHandleState); @@ -124,7 +128,7 @@ public bool AddTags(EntityUid entity, IEnumerable ids) /// public bool TryAddTag(EntityUid entity, string id) { - return TryComp(entity, out var component) && + return _tagQuery.TryComp(entity, out var component) && AddTag(component, id); } @@ -142,7 +146,7 @@ public bool TryAddTag(EntityUid entity, string id) /// public bool TryAddTags(EntityUid entity, params string[] ids) { - return TryComp(entity, out var component) && + return _tagQuery.TryComp(entity, out var component) && AddTags(component, ids); } @@ -160,7 +164,7 @@ public bool TryAddTags(EntityUid entity, params string[] ids) /// public bool TryAddTags(EntityUid entity, IEnumerable ids) { - return TryComp(entity, out var component) && + return _tagQuery.TryComp(entity, out var component) && AddTags(component, ids); } @@ -175,13 +179,14 @@ public bool TryAddTags(EntityUid entity, IEnumerable ids) /// public bool HasTag(EntityUid entity, string id) { - return TryComp(entity, out var component) && + return _tagQuery.TryComp(entity, out var component) && HasTag(component, id); } /// /// Checks if a tag has been added to an entity. /// + [Obsolete] public bool HasTag(EntityUid entity, string id, EntityQuery tagQuery) { return tagQuery.TryGetComponent(entity, out var component) && @@ -210,7 +215,7 @@ public bool HasTag(EntityUid entity, string id, EntityQuery tagQue /// public bool HasAllTags(EntityUid entity, List ids) { - return TryComp(entity, out var component) && + return _tagQuery.TryComp(entity, out var component) && HasAllTags(component, ids); } @@ -225,7 +230,7 @@ public bool HasAllTags(EntityUid entity, List ids) /// public bool HasAllTags(EntityUid entity, IEnumerable ids) { - return TryComp(entity, out var component) && + return _tagQuery.TryComp(entity, out var component) && HasAllTags(component, ids); } @@ -234,18 +239,33 @@ public bool HasAllTags(EntityUid entity, IEnumerable ids) ///
/// The entity to check. /// The tags to check for. + /// true if they all exist, false otherwise. + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool HasAllTags(EntityUid entity, List> ids) + { + return _tagQuery.TryComp(entity, out var component) && + HasAllTags(component, ids); + } + + /// + /// Checks if any of the given tags have been added to an entity. + /// + /// The entity to check. + /// The tags to check for. /// true if any of them exist, false otherwise. /// /// Thrown if one of the ids represents an unregistered . /// public bool HasAnyTag(EntityUid entity, params string[] ids) { - return TryComp(entity, out var component) && + return _tagQuery.TryComp(entity, out var component) && HasAnyTag(component, ids); } /// - /// Checks if all of the given tags have been added to an entity. + /// Checks if any of the given tags have been added to an entity. /// /// The entity to check. /// The tag to check for. @@ -256,7 +276,7 @@ public bool HasAnyTag(EntityUid entity, params string[] ids) public bool HasAnyTag(EntityUid entity, string id) => HasTag(entity, id); /// - /// Checks if all of the given tags have been added to an entity. + /// Checks if any of the given tags have been added to an entity. /// /// The entity to check. /// The tags to check for. @@ -265,13 +285,28 @@ public bool HasAnyTag(EntityUid entity, params string[] ids) /// Thrown if one of the ids represents an unregistered . /// public bool HasAnyTag(EntityUid entity, List ids) + { + return _tagQuery.TryComp(entity, out var component) && + HasAnyTag(component, ids); + } + + /// + /// Checks if any of the given tags have been added to an entity. + /// + /// The entity to check. + /// The tags to check for. + /// true if any of them exist, false otherwise. + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool HasAnyTag(EntityUid entity, List> ids) { return TryComp(entity, out var component) && HasAnyTag(component, ids); } /// - /// Checks if all of the given tags have been added to an entity. + /// Checks if any of the given tags have been added to an entity. /// /// The entity to check. /// The tags to check for. @@ -281,7 +316,7 @@ public bool HasAnyTag(EntityUid entity, List ids) /// public bool HasAnyTag(EntityUid entity, IEnumerable ids) { - return TryComp(entity, out var component) && + return _tagQuery.TryComp(entity, out var component) && HasAnyTag(component, ids); } @@ -298,7 +333,7 @@ public bool HasAnyTag(EntityUid entity, IEnumerable ids) /// public bool RemoveTag(EntityUid entity, string id) { - return TryComp(entity, out var component) && + return _tagQuery.TryComp(entity, out var component) && RemoveTag(component, id); } @@ -315,7 +350,7 @@ public bool RemoveTag(EntityUid entity, string id) /// public bool RemoveTags(EntityUid entity, params string[] ids) { - return TryComp(entity, out var component) && + return _tagQuery.TryComp(entity, out var component) && RemoveTags(component, ids); } @@ -332,7 +367,7 @@ public bool RemoveTags(EntityUid entity, params string[] ids) /// public bool RemoveTags(EntityUid entity, IEnumerable ids) { - return TryComp(entity, out var component) && + return _tagQuery.TryComp(entity, out var component) && RemoveTags(component, ids); } @@ -478,6 +513,28 @@ public bool HasAllTags(TagComponent component, IEnumerable ids) return true; } + /// + /// Checks if all of the given tags have been added. + /// + /// The tags to check for. + /// true if they all exist, false otherwise. + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool HasAllTags(TagComponent component, List> ids) + { + foreach (var id in ids) + { + AssertValidTag(id); + + if (!component.Tags.Contains(id)) + return false; + + } + + return true; + } + /// /// Checks if any of the given tags have been added. /// @@ -488,9 +545,16 @@ public bool HasAllTags(TagComponent component, IEnumerable ids) /// public bool HasAnyTag(TagComponent component, params string[] ids) { - return HasAnyTag(component, ids.AsEnumerable()); - } + foreach (var id in ids) + { + AssertValidTag(id); + + if (component.Tags.Contains(id)) + return true; + } + return false; + } /// /// Checks if any of the given tags have been added. @@ -548,6 +612,27 @@ public bool HasAnyTag(TagComponent component, IEnumerable ids) return false; } + /// + /// Checks if any of the given tags have been added. + /// + /// The tags to check for. + /// true if any of them exist, false otherwise. + /// + /// Thrown if one of the ids represents an unregistered . + /// + public bool HasAnyTag(TagComponent comp, List> ids) + { + foreach (var id in ids) + { + AssertValidTag(id); + + if (comp.Tags.Contains(id)) + return true; + } + + return false; + } + /// /// Tries to remove a tag if it exists. /// diff --git a/Content.Shared/Targeting/Events.cs b/Content.Shared/Targeting/Events.cs new file mode 100644 index 0000000000..1b090be3e8 --- /dev/null +++ b/Content.Shared/Targeting/Events.cs @@ -0,0 +1,38 @@ +using Content.Shared.Targeting; +using Robust.Shared.Serialization; + +namespace Content.Shared.Targeting.Events; + +[Serializable, NetSerializable] +public sealed class TargetChangeEvent : EntityEventArgs +{ + public NetEntity Uid { get; } + public TargetBodyPart BodyPart { get; } + public TargetChangeEvent(NetEntity uid, TargetBodyPart bodyPart) + { + Uid = uid; + BodyPart = bodyPart; + } +} + +[Serializable, NetSerializable] +public sealed class TargetIntegrityChangeEvent : EntityEventArgs +{ + public NetEntity Uid { get; } + public bool RefreshUi { get; } + public TargetIntegrityChangeEvent(NetEntity uid, bool refreshUi = true) + { + Uid = uid; + RefreshUi = refreshUi; + } +} + +public sealed class RefreshInventorySlotsEvent : EntityEventArgs +{ + public string SlotName { get; } + + public RefreshInventorySlotsEvent(string slotName) + { + SlotName = slotName; + } +} diff --git a/Content.Shared/Targeting/SharedTargetingSystem.cs b/Content.Shared/Targeting/SharedTargetingSystem.cs new file mode 100644 index 0000000000..4f2248683e --- /dev/null +++ b/Content.Shared/Targeting/SharedTargetingSystem.cs @@ -0,0 +1,26 @@ +namespace Content.Shared.Targeting; +public abstract class SharedTargetingSystem : EntitySystem +{ + /// + /// Returns all Valid target body parts as an array. + /// + public static TargetBodyPart[] GetValidParts() + { + var parts = new[] + { + TargetBodyPart.Head, + TargetBodyPart.Torso, + //TargetBodyPart.Groin, + TargetBodyPart.LeftArm, + TargetBodyPart.LeftHand, + TargetBodyPart.LeftLeg, + TargetBodyPart.LeftFoot, + TargetBodyPart.RightArm, + TargetBodyPart.RightHand, + TargetBodyPart.RightLeg, + TargetBodyPart.RightFoot, + }; + + return parts; + } +} diff --git a/Content.Shared/Targeting/TargetBodyPart.cs b/Content.Shared/Targeting/TargetBodyPart.cs new file mode 100644 index 0000000000..dd89454544 --- /dev/null +++ b/Content.Shared/Targeting/TargetBodyPart.cs @@ -0,0 +1,31 @@ +namespace Content.Shared.Targeting; + + +/// +/// Represents and enum of possible target parts. +/// +/// +/// To get all body parts as an Array, use static +/// method in SharedTargetingSystem GetValidParts. +/// +[Flags] +public enum TargetBodyPart : ushort +{ + Head = 1, + Torso = 1 << 1, + Groin = 1 << 2, + LeftArm = 1 << 3, + LeftHand = 1 << 4, + RightArm = 1 << 5, + RightHand = 1 << 6, + LeftLeg = 1 << 7, + LeftFoot = 1 << 8, + RightLeg = 1 << 9, + RightFoot = 1 << 10, + + Hands = LeftHand | RightHand, + Arms = LeftArm | RightArm, + Legs = LeftLeg | RightLeg, + Feet = LeftFoot | RightFoot, + All = Head | Torso | Groin | LeftArm | LeftHand | RightArm | RightHand | LeftLeg | LeftFoot | RightLeg | RightFoot, +} diff --git a/Content.Shared/Targeting/TargetIntegrity.cs b/Content.Shared/Targeting/TargetIntegrity.cs new file mode 100644 index 0000000000..9b4515fcfa --- /dev/null +++ b/Content.Shared/Targeting/TargetIntegrity.cs @@ -0,0 +1,13 @@ +namespace Content.Shared.Targeting; +public enum TargetIntegrity +{ + Healthy = 0, + LightlyWounded = 1, + SomewhatWounded = 2, + ModeratelyWounded = 3, + HeavilyWounded = 4, + CriticallyWounded = 5, + Severed = 6, + Dead = 7, + Disabled = 8, +} \ No newline at end of file diff --git a/Content.Shared/Targeting/TargetingComponent.cs b/Content.Shared/Targeting/TargetingComponent.cs new file mode 100644 index 0000000000..cb74beee32 --- /dev/null +++ b/Content.Shared/Targeting/TargetingComponent.cs @@ -0,0 +1,59 @@ +using Robust.Shared.Audio; +using Robust.Shared.GameObjects; +using Robust.Shared.GameStates; + +namespace Content.Shared.Targeting; + +/// +/// Controls entity limb targeting for actions. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class TargetingComponent : Component +{ + [ViewVariables, AutoNetworkedField] + public TargetBodyPart Target = TargetBodyPart.Torso; + + /// + /// What odds does the entity have of targeting each body part? + /// + [DataField] + public Dictionary TargetOdds = new() + { + { TargetBodyPart.Head, 0.1f }, + { TargetBodyPart.Torso, 0.3f }, + { TargetBodyPart.Groin, 0.1f }, + { TargetBodyPart.LeftArm, 0.1f }, + { TargetBodyPart.LeftHand, 0.05f }, + { TargetBodyPart.RightArm, 0.1f }, + { TargetBodyPart.RightHand, 0.05f }, + { TargetBodyPart.LeftLeg, 0.1f }, + { TargetBodyPart.LeftFoot, 0.05f }, + { TargetBodyPart.RightLeg, 0.1f }, + { TargetBodyPart.RightFoot, 0.05f } + }; + + /// + /// What is the current integrity of each body part? + /// + [ViewVariables, AutoNetworkedField] + public Dictionary BodyStatus = new() + { + { TargetBodyPart.Head, TargetIntegrity.Healthy }, + { TargetBodyPart.Torso, TargetIntegrity.Healthy }, + { TargetBodyPart.Groin, TargetIntegrity.Healthy }, + { TargetBodyPart.LeftArm, TargetIntegrity.Healthy }, + { TargetBodyPart.LeftHand, TargetIntegrity.Healthy }, + { TargetBodyPart.RightArm, TargetIntegrity.Healthy }, + { TargetBodyPart.RightHand, TargetIntegrity.Healthy }, + { TargetBodyPart.LeftLeg, TargetIntegrity.Healthy }, + { TargetBodyPart.LeftFoot, TargetIntegrity.Healthy }, + { TargetBodyPart.RightLeg, TargetIntegrity.Healthy }, + { TargetBodyPart.RightFoot, TargetIntegrity.Healthy } + }; + + /// + /// What noise does the entity play when swapping targets? + /// + [DataField] + public string SwapSound = "/Audio/Effects/toggleoncombat.ogg"; +} diff --git a/Content.Shared/Teleportation/Systems/SwapTeleporterSystem.cs b/Content.Shared/Teleportation/Systems/SwapTeleporterSystem.cs index e69a31a1d4..62c0b0f44e 100644 --- a/Content.Shared/Teleportation/Systems/SwapTeleporterSystem.cs +++ b/Content.Shared/Teleportation/Systems/SwapTeleporterSystem.cs @@ -145,27 +145,14 @@ public void DoTeleport(Entity ent) } var teleEnt = GetTeleportingEntity((uid, xform)); - var teleEntXform = Transform(teleEnt); var otherTeleEnt = GetTeleportingEntity((linkedEnt, Transform(linkedEnt))); - var otherTeleEntXform = Transform(otherTeleEnt); _popup.PopupEntity(Loc.GetString("swap-teleporter-popup-teleport-other", ("entity", Identity.Entity(linkedEnt, EntityManager))), otherTeleEnt, otherTeleEnt, PopupType.MediumCaution); - var pos = teleEntXform.Coordinates; - var otherPos = otherTeleEntXform.Coordinates; - - if (_transform.ContainsEntity(teleEnt, (otherTeleEnt, otherTeleEntXform)) || - _transform.ContainsEntity(otherTeleEnt, (teleEnt, teleEntXform))) - { - Log.Error($"Invalid teleport swap attempt between {ToPrettyString(teleEnt)} and {ToPrettyString(otherTeleEnt)}"); - return; - } - - _transform.SetCoordinates(teleEnt, otherPos); - _transform.SetCoordinates(otherTeleEnt, pos); + _transform.SwapPositions(teleEnt, otherTeleEnt); } /// diff --git a/Content.Shared/Telescope/SharedTelescopeSystem.cs b/Content.Shared/Telescope/SharedTelescopeSystem.cs new file mode 100644 index 0000000000..5f9896cc35 --- /dev/null +++ b/Content.Shared/Telescope/SharedTelescopeSystem.cs @@ -0,0 +1,110 @@ +using System.Numerics; +using Content.Shared.Camera; +using Content.Shared.Hands; +using Content.Shared.Hands.Components; +using Content.Shared.Item; +using Robust.Shared.Serialization; + +namespace Content.Shared.Telescope; + +public abstract class SharedTelescopeSystem : EntitySystem +{ + [Dependency] private readonly SharedEyeSystem _eye = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeAllEvent(OnEyeOffsetChanged); + SubscribeLocalEvent(OnUnequip); + SubscribeLocalEvent(OnHandDeselected); + SubscribeLocalEvent(OnShutdown); + } + + private void OnShutdown(Entity ent, ref ComponentShutdown args) + { + if (!TryComp(ent.Comp.LastEntity, out EyeComponent? eye) + || ent.Comp.LastEntity == ent && TerminatingOrDeleted(ent)) + return; + + SetOffset((ent.Comp.LastEntity.Value, eye), Vector2.Zero, ent); + } + + private void OnHandDeselected(Entity ent, ref HandDeselectedEvent args) + { + if (!TryComp(args.User, out EyeComponent? eye)) + return; + + SetOffset((args.User, eye), Vector2.Zero, ent); + } + + private void OnUnequip(Entity ent, ref GotUnequippedHandEvent args) + { + if (!TryComp(args.User, out EyeComponent? eye) + || !HasComp(ent.Owner)) + return; + + SetOffset((args.User, eye), Vector2.Zero, ent); + } + + public TelescopeComponent? GetRightTelescope(EntityUid? ent) + { + TelescopeComponent? telescope = null; + + if (TryComp(ent, out var hands) + && hands.ActiveHandEntity.HasValue + && TryComp(hands.ActiveHandEntity, out var handTelescope)) + telescope = handTelescope; + else if (TryComp(ent, out var entityTelescope)) + telescope = entityTelescope; + + return telescope; + } + + private void OnEyeOffsetChanged(EyeOffsetChangedEvent msg, EntitySessionEventArgs args) + { + if (args.SenderSession.AttachedEntity is not { } ent + || !TryComp(ent, out var eye)) + return; + + var telescope = GetRightTelescope(ent); + + if (telescope == null) + return; + + var offset = Vector2.Lerp(eye.Offset, msg.Offset, telescope.LerpAmount); + + SetOffset((ent, eye), offset, telescope); + } + + private void SetOffset(Entity ent, Vector2 offset, TelescopeComponent telescope) + { + telescope.LastEntity = ent; + + if (TryComp(ent, out CameraRecoilComponent? recoil)) + { + recoil.BaseOffset = offset; + _eye.SetOffset(ent, offset + recoil.CurrentKick, ent); + } + else + { + _eye.SetOffset(ent, offset, ent); + } + } + + public void SetParameters(Entity ent, float? divisor = null, float? lerpAmount = null) + { + var telescope = ent.Comp; + + telescope.Divisor = divisor ?? telescope.Divisor; + telescope.LerpAmount = lerpAmount ?? telescope.LerpAmount; + + Dirty(ent.Owner, telescope); + } +} + +[Serializable, NetSerializable] +public sealed class EyeOffsetChangedEvent : EntityEventArgs +{ + public Vector2 Offset; +} diff --git a/Content.Shared/Telescope/TelescopeComponent.cs b/Content.Shared/Telescope/TelescopeComponent.cs new file mode 100644 index 0000000000..529cc58324 --- /dev/null +++ b/Content.Shared/Telescope/TelescopeComponent.cs @@ -0,0 +1,16 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Telescope; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class TelescopeComponent : Component +{ + [DataField, AutoNetworkedField] + public float Divisor = 0.1f; + + [DataField, AutoNetworkedField] + public float LerpAmount = 0.1f; + + [ViewVariables] + public EntityUid? LastEntity; +} diff --git a/Content.Shared/TelescopicBaton/TelescopicBatonVisuals.cs b/Content.Shared/TelescopicBaton/TelescopicBatonVisuals.cs new file mode 100644 index 0000000000..25e6f583c8 --- /dev/null +++ b/Content.Shared/TelescopicBaton/TelescopicBatonVisuals.cs @@ -0,0 +1,10 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.TelescopicBaton; + +[Serializable, NetSerializable] +public enum TelescopicBatonVisuals +{ + State, + Layer +} diff --git a/Content.Shared/Timing/UseDelayComponent.cs b/Content.Shared/Timing/UseDelayComponent.cs index 1560d4dd0b..aa6c66eb81 100644 --- a/Content.Shared/Timing/UseDelayComponent.cs +++ b/Content.Shared/Timing/UseDelayComponent.cs @@ -1,38 +1,53 @@ using Robust.Shared.GameStates; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; +using Robust.Shared.Serialization; namespace Content.Shared.Timing; /// -/// Timer that creates a cooldown each time an object is activated/used +/// Timer that creates a cooldown each time an object is activated/used. +/// Can support additional, separate cooldown timers on the object by passing a unique ID with the system methods. /// -/// -/// Currently it only supports a single delay per entity, this means that for things that have two delay interactions they will share one timer, so this can cause issues. For example, the bible has a delay when opening the storage UI and when applying it's interaction effect, and they share the same delay. -/// [RegisterComponent] -[NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause] +[NetworkedComponent] [Access(typeof(UseDelaySystem))] public sealed partial class UseDelayComponent : Component { - /// - /// When the delay starts. - /// - [ViewVariables(VVAccess.ReadWrite), DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoNetworkedField] - [AutoPausedField] - public TimeSpan DelayStartTime; - - /// - /// When the delay ends. - /// - [ViewVariables(VVAccess.ReadWrite), DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoNetworkedField] - [AutoPausedField] - public TimeSpan DelayEndTime; + [DataField] + public Dictionary Delays = []; /// - /// Default delay time + /// Default delay time. /// + /// + /// This is only used at MapInit and should not be expected + /// to reflect the length of the default delay after that. + /// Use instead. + /// [DataField] - [ViewVariables(VVAccess.ReadWrite)] - [AutoNetworkedField] public TimeSpan Delay = TimeSpan.FromSeconds(1); } + +[Serializable, NetSerializable] +public sealed class UseDelayComponentState : IComponentState +{ + public Dictionary Delays = new(); +} + +[Serializable, NetSerializable] +[DataDefinition] +public sealed partial class UseDelayInfo +{ + [DataField] + public TimeSpan Length { get; set; } + [DataField] + public TimeSpan StartTime { get; set; } + [DataField] + public TimeSpan EndTime { get; set; } + + public UseDelayInfo(TimeSpan length, TimeSpan startTime = default, TimeSpan endTime = default) + { + Length = length; + StartTime = startTime; + EndTime = endTime; + } +} diff --git a/Content.Shared/Timing/UseDelaySystem.cs b/Content.Shared/Timing/UseDelaySystem.cs index 388f31079c..9816d0185a 100644 --- a/Content.Shared/Timing/UseDelaySystem.cs +++ b/Content.Shared/Timing/UseDelaySystem.cs @@ -1,3 +1,5 @@ +using System.Diagnostics.CodeAnalysis; +using Robust.Shared.GameStates; using Robust.Shared.Timing; namespace Content.Shared.Timing; @@ -7,53 +9,171 @@ public sealed class UseDelaySystem : EntitySystem [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly MetaDataSystem _metadata = default!; - public void SetDelay(Entity ent, TimeSpan delay) + private const string DefaultId = "default"; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnUnpaused); + SubscribeLocalEvent(OnDelayGetState); + SubscribeLocalEvent(OnDelayHandleState); + } + + private void OnDelayHandleState(Entity ent, ref ComponentHandleState args) { - if (ent.Comp.Delay == delay) + if (args.Current is not UseDelayComponentState state) return; - ent.Comp.Delay = delay; + ent.Comp.Delays.Clear(); + + // At time of writing sourcegen networking doesn't deep copy so this will mispredict if you try. + foreach (var (key, delay) in state.Delays) + { + ent.Comp.Delays[key] = new UseDelayInfo(delay.Length, delay.StartTime, delay.EndTime); + } + } + + private void OnDelayGetState(Entity ent, ref ComponentGetState args) + { + args.State = new UseDelayComponentState() + { + Delays = ent.Comp.Delays + }; + } + + private void OnMapInit(Entity ent, ref MapInitEvent args) + { + // Set default delay length from the prototype + // This makes it easier for simple use cases that only need a single delay + SetLength((ent, ent.Comp), ent.Comp.Delay, DefaultId); + } + + private void OnUnpaused(Entity ent, ref EntityUnpausedEvent args) + { + // We have to do this manually, since it's not just a single field. + foreach (var entry in ent.Comp.Delays.Values) + { + entry.EndTime += args.PausedTime; + } + } + + /// + /// Sets the length of the delay with the specified ID. + /// + /// + /// This will add a UseDelay component to the entity if it doesn't have one. + /// + public bool SetLength(Entity ent, TimeSpan length, string id = DefaultId) + { + EnsureComp(ent.Owner, out var comp); + + if (comp.Delays.TryGetValue(id, out var entry)) + { + if (entry.Length == length) + return true; + + entry.Length = length; + } + else + { + comp.Delays.Add(id, new UseDelayInfo(length)); + } + Dirty(ent); + return true; } /// - /// Returns true if the entity has a currently active UseDelay. + /// Returns true if the entity has a currently active UseDelay with the specified ID. /// - public bool IsDelayed(Entity ent) + public bool IsDelayed(Entity ent, string id = DefaultId) { - return ent.Comp.DelayEndTime >= _gameTiming.CurTime; + if (!ent.Comp.Delays.TryGetValue(id, out var entry)) + return false; + + return entry.EndTime >= _gameTiming.CurTime; } /// - /// Cancels the current delay. + /// Cancels the delay with the specified ID. /// - public void CancelDelay(Entity ent) + public void CancelDelay(Entity ent, string id = DefaultId) { - ent.Comp.DelayEndTime = _gameTiming.CurTime; + if (!ent.Comp.Delays.TryGetValue(id, out var entry)) + return; + + entry.EndTime = _gameTiming.CurTime; Dirty(ent); } /// - /// Resets the UseDelay entirely for this entity if possible. + /// Tries to get info about the delay with the specified ID. See . + /// + /// + /// + /// + /// + public bool TryGetDelayInfo(Entity ent, [NotNullWhen(true)] out UseDelayInfo? info, string id = DefaultId) + { + return ent.Comp.Delays.TryGetValue(id, out info); + } + + /// + /// Returns info for the delay that will end farthest in the future. /// - /// Check if the entity has an ongoing delay, return false if it does, return true if it does not. - public bool TryResetDelay(Entity ent, bool checkDelayed = false) + public UseDelayInfo GetLastEndingDelay(Entity ent) { - if (checkDelayed && IsDelayed(ent)) + var last = ent.Comp.Delays[DefaultId]; + foreach (var entry in ent.Comp.Delays) + { + if (entry.Value.EndTime > last.EndTime) + last = entry.Value; + } + return last; + } + + /// + /// Resets the delay with the specified ID for this entity if possible. + /// + /// Check if the entity has an ongoing delay with the specified ID. + /// If it does, return false and don't reset it. + /// Otherwise reset it and return true. + public bool TryResetDelay(Entity ent, bool checkDelayed = false, string id = DefaultId) + { + if (checkDelayed && IsDelayed(ent, id)) + return false; + + if (!ent.Comp.Delays.TryGetValue(id, out var entry)) return false; var curTime = _gameTiming.CurTime; - ent.Comp.DelayStartTime = curTime; - ent.Comp.DelayEndTime = curTime - _metadata.GetPauseTime(ent) + ent.Comp.Delay; + entry.StartTime = curTime; + entry.EndTime = curTime - _metadata.GetPauseTime(ent) + entry.Length; Dirty(ent); return true; } - public bool TryResetDelay(EntityUid uid, bool checkDelayed = false, UseDelayComponent? component = null) + public bool TryResetDelay(EntityUid uid, bool checkDelayed = false, UseDelayComponent? component = null, string id = DefaultId) { if (!Resolve(uid, ref component, false)) return false; - return TryResetDelay((uid, component), checkDelayed); + return TryResetDelay((uid, component), checkDelayed, id); + } + + /// + /// Resets all delays on the entity. + /// + public void ResetAllDelays(Entity ent) + { + var curTime = _gameTiming.CurTime; + foreach (var entry in ent.Comp.Delays.Values) + { + entry.StartTime = curTime; + entry.EndTime = curTime - _metadata.GetPauseTime(ent) + entry.Length; + } + Dirty(ent); } } diff --git a/Content.Shared/Tips/TippyEvent.cs b/Content.Shared/Tips/TippyEvent.cs new file mode 100644 index 0000000000..4370e9c822 --- /dev/null +++ b/Content.Shared/Tips/TippyEvent.cs @@ -0,0 +1,19 @@ + +using Robust.Shared.Serialization; + +namespace Content.Shared.Tips; + +[Serializable, NetSerializable] +public sealed class TippyEvent : EntityEventArgs +{ + public TippyEvent(string msg) + { + Msg = msg; + } + + public string Msg; + public string? Proto; + public float SpeakTime = 5; + public float SlideTime = 3; + public float WaddleInterval = 0.5f; +} diff --git a/Content.Shared/Tools/Components/SharedWeldable.cs b/Content.Shared/Tools/Components/SharedWeldable.cs deleted file mode 100644 index 701bd4d8da..0000000000 --- a/Content.Shared/Tools/Components/SharedWeldable.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Robust.Shared.GameStates; -using Robust.Shared.Serialization; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Shared.Tools.Components; - -[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] -public sealed partial class WeldableComponent : Component -{ - /// - /// Tool quality for welding. - /// - [DataField("weldingQuality", customTypeSerializer: typeof(PrototypeIdSerializer))] - [ViewVariables(VVAccess.ReadWrite)] - public string WeldingQuality = "Welding"; - - /// - /// How much time does it take to weld/unweld entity. - /// - [DataField("time")] - [ViewVariables(VVAccess.ReadWrite)] - [AutoNetworkedField] - public TimeSpan WeldingTime = TimeSpan.FromSeconds(1f); - - /// - /// Shown when welded entity is examined. - /// - [DataField("weldedExamineMessage")] - [ViewVariables(VVAccess.ReadWrite)] - public string? WeldedExamineMessage = "weldable-component-examine-is-welded"; - - /// - /// Is this entity currently welded shut? - /// - [DataField("isWelded"), AutoNetworkedField] - public bool IsWelded; -} - -[Serializable, NetSerializable] -public enum WeldableVisuals : byte -{ - IsWelded -} - -[Serializable, NetSerializable] -public enum WeldableLayers : byte -{ - BaseWelded -} diff --git a/Content.Shared/Tools/Components/SharedWelderComponent.cs b/Content.Shared/Tools/Components/SharedWelderComponent.cs deleted file mode 100644 index 78c1cde201..0000000000 --- a/Content.Shared/Tools/Components/SharedWelderComponent.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Robust.Shared.GameStates; -using Robust.Shared.Serialization; - -namespace Content.Shared.Tools.Components -{ - [NetworkedComponent] - public abstract partial class SharedWelderComponent : Component { } - - [NetSerializable, Serializable] - public sealed class WelderComponentState : ComponentState - { - public float FuelCapacity { get; } - public float Fuel { get; } - - public WelderComponentState(float fuelCapacity, float fuel) - { - FuelCapacity = fuelCapacity; - Fuel = fuel; - } - } -} diff --git a/Content.Shared/Tools/Components/WeldableComponent.cs b/Content.Shared/Tools/Components/WeldableComponent.cs new file mode 100644 index 0000000000..e491b5f6a7 --- /dev/null +++ b/Content.Shared/Tools/Components/WeldableComponent.cs @@ -0,0 +1,51 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; + +namespace Content.Shared.Tools.Components; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class WeldableComponent : Component +{ + /// + /// Tool quality for welding. + /// + [DataField] + public ProtoId WeldingQuality = "Welding"; + + /// + /// How much time does it take to weld/unweld entity. + /// + [DataField, AutoNetworkedField] + public TimeSpan Time = TimeSpan.FromSeconds(1f); + + /// + /// How much fuel does it take to weld/unweld entity. + /// + [DataField] + public float Fuel = 3f; + + /// + /// Shown when welded entity is examined. + /// + [DataField] + public LocId? WeldedExamineMessage = "weldable-component-examine-is-welded"; + + /// + /// Is this entity currently welded shut? + /// + [DataField, AutoNetworkedField] + public bool IsWelded; +} + +[Serializable, NetSerializable] +public enum WeldableVisuals : byte +{ + IsWelded +} + +[Serializable, NetSerializable] +public enum WeldableLayers : byte +{ + BaseWelded +} diff --git a/Content.Shared/Tools/Components/WelderComponent.cs b/Content.Shared/Tools/Components/WelderComponent.cs new file mode 100644 index 0000000000..3c78a03fde --- /dev/null +++ b/Content.Shared/Tools/Components/WelderComponent.cs @@ -0,0 +1,58 @@ +using Content.Shared.Chemistry.Components; +using Content.Shared.Chemistry.Reagent; +using Content.Shared.FixedPoint; +using Content.Shared.Tools.Systems; +using Robust.Shared.Audio; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Tools.Components; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true), Access(typeof(SharedToolSystem))] +public sealed partial class WelderComponent : Component +{ + [DataField, AutoNetworkedField] + public bool Enabled; + + [DataField] + public float WelderTimer; + + /// + /// Name of . + /// + [DataField] + public string FuelSolutionName = "Welder"; + + /// + /// Reagent that will be used as fuel for welding. + /// + [DataField] + public ProtoId FuelReagent = "WeldingFuel"; + + /// + /// Fuel consumption per second while the welder is active. + /// + [DataField, AutoNetworkedField] + public FixedPoint2 FuelConsumption = FixedPoint2.New(1.0f); + + /// + /// A fuel amount to be consumed when the welder goes from being unlit to being lit. + /// + [DataField, AutoNetworkedField] + public FixedPoint2 FuelLitCost = FixedPoint2.New(0.5f); + + /// + /// Sound played when refilling the welder. + /// + [DataField] + public SoundSpecifier WelderRefill = new SoundPathSpecifier("/Audio/Effects/refill.ogg"); + + /// + /// Whether the item is safe to refill while lit without exploding the tank. + /// + [DataField] + public bool TankSafe; + + [DataField] + public float WelderUpdateTimer = 1f; +} diff --git a/Content.Shared/Tools/Systems/SharedToolSystem.Welder.cs b/Content.Shared/Tools/Systems/SharedToolSystem.Welder.cs new file mode 100644 index 0000000000..e790b59cd1 --- /dev/null +++ b/Content.Shared/Tools/Systems/SharedToolSystem.Welder.cs @@ -0,0 +1,178 @@ +using Content.Shared.Chemistry.Components; +using Content.Shared.Chemistry.Components.SolutionManager; +using Content.Shared.Database; +using Content.Shared.DoAfter; +using Content.Shared.Examine; +using Content.Shared.FixedPoint; +using Content.Shared.Interaction; +using Content.Shared.Item.ItemToggle.Components; +using Content.Shared.Tools.Components; + +namespace Content.Shared.Tools.Systems; + +public abstract partial class SharedToolSystem +{ + public void InitializeWelder() + { + SubscribeLocalEvent(OnWelderExamine); + SubscribeLocalEvent(OnWelderAfterInteract); + SubscribeLocalEvent>(OnWelderToolUseAttempt); + SubscribeLocalEvent(OnWelderDoAfter); + SubscribeLocalEvent(OnToggle); + SubscribeLocalEvent(OnActivateAttempt); + } + + public virtual void TurnOn(Entity entity, EntityUid? user) + { + if (!SolutionContainerSystem.TryGetSolution(entity.Owner, entity.Comp.FuelSolutionName, out var solutionComp, out _)) + return; + + SolutionContainerSystem.RemoveReagent(solutionComp.Value, entity.Comp.FuelReagent, entity.Comp.FuelLitCost); + AdminLogger.Add(LogType.InteractActivate, LogImpact.Low, + $"{ToPrettyString(user):user} toggled {ToPrettyString(entity.Owner):welder} on"); + + entity.Comp.Enabled = true; + Dirty(entity, entity.Comp); + } + + public void TurnOff(Entity entity, EntityUid? user) + { + AdminLogger.Add(LogType.InteractActivate, LogImpact.Low, + $"{ToPrettyString(user):user} toggled {ToPrettyString(entity.Owner):welder} off"); + entity.Comp.Enabled = false; + Dirty(entity, entity.Comp); + } + + public (FixedPoint2 fuel, FixedPoint2 capacity) GetWelderFuelAndCapacity(EntityUid uid, WelderComponent? welder = null, SolutionContainerManagerComponent? solutionContainer = null) + { + if (!Resolve(uid, ref welder, ref solutionContainer)) + return default; + + if (!SolutionContainer.TryGetSolution( + (uid, solutionContainer), + welder.FuelSolutionName, + out _, + out var fuelSolution)) + { + return default; + } + + return (fuelSolution.GetTotalPrototypeQuantity(welder.FuelReagent), fuelSolution.MaxVolume); + } + + private void OnWelderExamine(Entity entity, ref ExaminedEvent args) + { + using (args.PushGroup(nameof(WelderComponent))) + { + if (ItemToggle.IsActivated(entity.Owner)) + { + args.PushMarkup(Loc.GetString("welder-component-on-examine-welder-lit-message")); + } + else + { + args.PushMarkup(Loc.GetString("welder-component-on-examine-welder-not-lit-message")); + } + + if (args.IsInDetailsRange) + { + var (fuel, capacity) = GetWelderFuelAndCapacity(entity.Owner, entity.Comp); + + args.PushMarkup(Loc.GetString("welder-component-on-examine-detailed-message", + ("colorName", fuel < capacity / FixedPoint2.New(4f) ? "darkorange" : "orange"), + ("fuelLeft", fuel), + ("fuelCapacity", capacity), + ("status", string.Empty))); // Lit status is handled above + } + } + } + + private void OnWelderAfterInteract(Entity entity, ref AfterInteractEvent args) + { + if (args.Handled) + return; + + if (args.Target is not { Valid: true } target || !args.CanReach) + return; + + if (TryComp(target, out ReagentTankComponent? tank) + && tank.TankType == ReagentTankType.Fuel + && SolutionContainerSystem.TryGetDrainableSolution(target, out var targetSoln, out var targetSolution) + && SolutionContainerSystem.TryGetSolution(entity.Owner, entity.Comp.FuelSolutionName, out var solutionComp, out var welderSolution)) + { + var trans = FixedPoint2.Min(welderSolution.AvailableVolume, targetSolution.Volume); + if (trans > 0) + { + var drained = SolutionContainerSystem.Drain(target, targetSoln.Value, trans); + SolutionContainerSystem.TryAddSolution(solutionComp.Value, drained); + _audioSystem.PlayPredicted(entity.Comp.WelderRefill, entity, user: args.User); + _popup.PopupClient(Loc.GetString("welder-component-after-interact-refueled-message"), entity, args.User); + } + else if (welderSolution.AvailableVolume <= 0) + { + _popup.PopupClient(Loc.GetString("welder-component-already-full"), entity, args.User); + } + else + { + _popup.PopupClient(Loc.GetString("welder-component-no-fuel-in-tank", ("owner", args.Target)), entity, args.User); + } + + args.Handled = true; + } + } + + private void OnWelderToolUseAttempt(Entity entity, ref DoAfterAttemptEvent args) + { + var user = args.DoAfter.Args.User; + + if (!ItemToggle.IsActivated(entity.Owner)) + { + _popup.PopupClient(Loc.GetString("welder-component-welder-not-lit-message"), entity, user); + args.Cancel(); + return; + } + + var (fuel, _) = GetWelderFuelAndCapacity(entity); + + if (args.Event.Fuel > fuel) + { + _popup.PopupClient(Loc.GetString("welder-component-cannot-weld-message"), entity, user); + args.Cancel(); + } + } + + private void OnWelderDoAfter(Entity ent, ref ToolDoAfterEvent args) + { + if (args.Cancelled) + return; + + if (!SolutionContainerSystem.TryGetSolution(ent.Owner, ent.Comp.FuelSolutionName, out var solution)) + return; + + SolutionContainerSystem.RemoveReagent(solution.Value, ent.Comp.FuelReagent, FixedPoint2.New(args.Fuel)); + } + + private void OnToggle(Entity entity, ref ItemToggledEvent args) + { + if (args.Activated) + TurnOn(entity, args.User); + else + TurnOff(entity, args.User); + } + + private void OnActivateAttempt(Entity entity, ref ItemToggleActivateAttemptEvent args) + { + if (!SolutionContainerSystem.TryGetSolution(entity.Owner, entity.Comp.FuelSolutionName, out _, out var solution)) + { + args.Cancelled = true; + args.Popup = Loc.GetString("welder-component-no-fuel-message"); + return; + } + + var fuel = solution.GetTotalPrototypeQuantity(entity.Comp.FuelReagent); + if (fuel == FixedPoint2.Zero || fuel < entity.Comp.FuelLitCost) + { + args.Popup = Loc.GetString("welder-component-no-fuel-message"); + args.Cancelled = true; + } + } +} diff --git a/Content.Shared/Tools/Systems/SharedToolSystem.cs b/Content.Shared/Tools/Systems/SharedToolSystem.cs index ee15b1e025..0d7972a8de 100644 --- a/Content.Shared/Tools/Systems/SharedToolSystem.cs +++ b/Content.Shared/Tools/Systems/SharedToolSystem.cs @@ -1,8 +1,12 @@ using Content.Shared.Administration.Logs; +using Content.Shared.Chemistry.EntitySystems; using Content.Shared.DoAfter; using Content.Shared.Interaction; +using Content.Shared.Item.ItemToggle; using Content.Shared.Maps; +using Content.Shared.Popups; using Content.Shared.Tools.Components; +using JetBrains.Annotations; using Robust.Shared.Audio.Systems; using Robust.Shared.Map; using Robust.Shared.Prototypes; @@ -15,20 +19,25 @@ public abstract partial class SharedToolSystem : EntitySystem { [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IPrototypeManager _protoMan = default!; - [Dependency] protected readonly ISharedAdminLogManager AdminLogger = default!; + [Dependency] protected readonly ISharedAdminLogManager AdminLogger = default!; [Dependency] private readonly ITileDefinitionManager _tileDefManager = default!; [Dependency] private readonly SharedAudioSystem _audioSystem = default!; [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; [Dependency] protected readonly SharedInteractionSystem InteractionSystem = default!; + [Dependency] protected readonly SharedItemToggleSystem ItemToggle = default!; [Dependency] private readonly SharedMapSystem _maps = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] protected readonly SharedSolutionContainerSystem SolutionContainerSystem = default!; [Dependency] private readonly SharedTransformSystem _transformSystem = default!; [Dependency] private readonly TileSystem _tiles = default!; [Dependency] private readonly TurfSystem _turfs = default!; + [Dependency] protected readonly SharedSolutionContainerSystem SolutionContainer = default!; public override void Initialize() { InitializeMultipleTool(); InitializeTile(); + InitializeWelder(); SubscribeLocalEvent(OnDoAfter); } @@ -66,6 +75,7 @@ public void PlayToolSound(EntityUid uid, ToolComponent tool, EntityUid? user) /// The qualities needed for this tool to work. /// The event that will be raised when the tool has finished (including cancellation). Event /// will be directed at the tool target. + /// Amount of fuel that should be taken from the tool. /// The tool component. /// Returns true if any interaction takes place. public bool UseTool( @@ -75,6 +85,7 @@ public bool UseTool( float doAfterDelay, IEnumerable toolQualitiesNeeded, DoAfterEvent doAfterEv, + float fuel = 0, ToolComponent? toolComponent = null) { return UseTool(tool, @@ -84,6 +95,7 @@ public bool UseTool( toolQualitiesNeeded, doAfterEv, out _, + fuel, toolComponent); } @@ -101,6 +113,7 @@ public bool UseTool( /// will be directed at the tool target. /// The id of the DoAfter that was created. This may be null even if the function returns true in /// the event that this tool-use cancelled an existing DoAfter + /// Amount of fuel that should be taken from the tool. /// The tool component. /// Returns true if any interaction takes place. public bool UseTool( @@ -111,31 +124,30 @@ public bool UseTool( IEnumerable toolQualitiesNeeded, DoAfterEvent doAfterEv, out DoAfterId? id, + float fuel = 0, ToolComponent? toolComponent = null) { id = null; if (!Resolve(tool, ref toolComponent, false)) return false; - if (!CanStartToolUse(tool, user, target, toolQualitiesNeeded, toolComponent)) + if (!CanStartToolUse(tool, user, target, fuel, toolQualitiesNeeded, toolComponent)) return false; - var toolEvent = new ToolDoAfterEvent(doAfterEv, GetNetEntity(target)); + var toolEvent = new ToolDoAfterEvent(fuel, doAfterEv, GetNetEntity(target)); var doAfterArgs = new DoAfterArgs(EntityManager, user, delay / toolComponent.SpeedModifier, toolEvent, tool, target: target, used: tool) { BreakOnDamage = true, BreakOnTargetMove = true, BreakOnUserMove = true, NeedHand = tool != user, - AttemptFrequency = IsWelder(tool) ? AttemptFrequency.EveryTick : AttemptFrequency.Never + AttemptFrequency = fuel > 0 ? AttemptFrequency.EveryTick : AttemptFrequency.Never }; _doAfterSystem.TryStartDoAfter(doAfterArgs, out id); return true; } - protected abstract bool IsWelder(EntityUid uid); - /// /// Attempts to use a tool on some entity, which will start a DoAfter. Returns true if an interaction occurred. /// Note that this does not mean the interaction was successful, you need to listen for the DoAfter event. @@ -148,6 +160,7 @@ public bool UseTool( /// The quality needed for this tool to work. /// The event that will be raised when the tool has finished (including cancellation). Event /// will be directed at the tool target. + /// Amount of fuel that should be taken from the tool. /// The tool component. /// Returns true if any interaction takes place. public bool UseTool( @@ -157,6 +170,7 @@ public bool UseTool( float doAfterDelay, string toolQualityNeeded, DoAfterEvent doAfterEv, + float fuel = 0, ToolComponent? toolComponent = null) { return UseTool(tool, @@ -166,6 +180,7 @@ public bool UseTool( new[] { toolQualityNeeded }, doAfterEv, out _, + fuel, toolComponent); } @@ -180,12 +195,13 @@ public bool HasQuality(EntityUid uid, string quality, ToolComponent? tool = null /// /// Whether a tool entity has all specified qualities or not. /// + [PublicAPI] public bool HasAllQualities(EntityUid uid, IEnumerable qualities, ToolComponent? tool = null) { return Resolve(uid, ref tool, false) && tool.Qualities.ContainsAll(qualities); } - private bool CanStartToolUse(EntityUid tool, EntityUid user, EntityUid? target, IEnumerable toolQualitiesNeeded, ToolComponent? toolComponent = null) + private bool CanStartToolUse(EntityUid tool, EntityUid user, EntityUid? target, float fuel, IEnumerable toolQualitiesNeeded, ToolComponent? toolComponent = null) { if (!Resolve(tool, ref toolComponent)) return false; @@ -220,6 +236,9 @@ private bool CanStartToolUse(EntityUid tool, EntityUid user, EntityUid? target, [Serializable, NetSerializable] protected sealed partial class ToolDoAfterEvent : DoAfterEvent { + [DataField] + public float Fuel; + /// /// Entity that the wrapped do after event will get directed at. If null, event will be broadcast. /// @@ -233,10 +252,11 @@ private ToolDoAfterEvent() { } - public ToolDoAfterEvent(DoAfterEvent wrappedEvent, NetEntity? originalTarget) + public ToolDoAfterEvent(float fuel, DoAfterEvent wrappedEvent, NetEntity? originalTarget) { DebugTools.Assert(wrappedEvent.GetType().HasCustomAttribute(), "Tool event is not serializable"); + Fuel = fuel; WrappedEvent = wrappedEvent; OriginalTarget = originalTarget; } @@ -249,14 +269,14 @@ public override DoAfterEvent Clone() if (evClone == WrappedEvent) return this; - return new ToolDoAfterEvent(evClone, OriginalTarget); + return new ToolDoAfterEvent(Fuel, evClone, OriginalTarget); } } [Serializable, NetSerializable] protected sealed partial class LatticeCuttingCompleteEvent : DoAfterEvent { - [DataField("coordinates", required:true)] + [DataField(required:true)] public NetCoordinates Coordinates; private LatticeCuttingCompleteEvent() @@ -273,9 +293,7 @@ public LatticeCuttingCompleteEvent(NetCoordinates coordinates) } [Serializable, NetSerializable] -public sealed partial class CableCuttingFinishedEvent : SimpleDoAfterEvent -{ -} +public sealed partial class CableCuttingFinishedEvent : SimpleDoAfterEvent; #endregion diff --git a/Content.Shared/Tools/Systems/WeldableSystem.cs b/Content.Shared/Tools/Systems/WeldableSystem.cs index b0ea68f713..c6c47d539e 100644 --- a/Content.Shared/Tools/Systems/WeldableSystem.cs +++ b/Content.Shared/Tools/Systems/WeldableSystem.cs @@ -69,7 +69,7 @@ private bool TryWeld(EntityUid uid, EntityUid tool, EntityUid user, WeldableComp if (!CanWeld(uid, tool, user, component)) return false; - if (!_toolSystem.UseTool(tool, user, uid, component.WeldingTime.Seconds, component.WeldingQuality, new WeldFinishedEvent())) + if (!_toolSystem.UseTool(tool, user, uid, component.Time.Seconds, component.WeldingQuality, new WeldFinishedEvent(), component.Fuel)) return false; // Log attempt @@ -140,10 +140,10 @@ public void SetWeldingTime(EntityUid uid, TimeSpan time, WeldableComponent? comp if (!_query.Resolve(uid, ref component)) return; - if (component.WeldingTime.Equals(time)) + if (component.Time.Equals(time)) return; - component.WeldingTime = time; + component.Time = time; Dirty(uid, component); } } diff --git a/Content.Shared/Traits/Assorted/Components/CyberEyesComponent.cs b/Content.Shared/Traits/Assorted/Components/CyberEyesComponent.cs new file mode 100644 index 0000000000..7009077e9c --- /dev/null +++ b/Content.Shared/Traits/Assorted/Components/CyberEyesComponent.cs @@ -0,0 +1,11 @@ +namespace Content.Shared.Traits.Assorted.Components; + +[RegisterComponent] +public sealed partial class CyberEyesComponent : Component +{ + /// + /// The text that will appear when someone with the CyberEyes component is examined at close range + /// + [DataField] + public string CyberEyesExaminationText = "examine-cybereyes-message"; +} diff --git a/Content.Shared/Traits/Assorted/Components/DogVisionComponent.cs b/Content.Shared/Traits/Assorted/Components/DogVisionComponent.cs new file mode 100644 index 0000000000..0979da8c35 --- /dev/null +++ b/Content.Shared/Traits/Assorted/Components/DogVisionComponent.cs @@ -0,0 +1,5 @@ +using Robust.Shared.GameStates; +namespace Content.Shared.Traits.Assorted.Components; + +[RegisterComponent, NetworkedComponent] +public sealed partial class DogVisionComponent : Component { } diff --git a/Content.Shared/Traits/Assorted/Components/NormalVisionComponent.cs b/Content.Shared/Traits/Assorted/Components/NormalVisionComponent.cs deleted file mode 100644 index 442bb6f008..0000000000 --- a/Content.Shared/Traits/Assorted/Components/NormalVisionComponent.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Robust.Shared.GameStates; - -namespace Content.Shared.Traits.Assorted.Components; - -/// -/// This is used for removing species specific vision traits -/// -[RegisterComponent, NetworkedComponent] -public sealed partial class NormalVisionComponent : Component -{ -} - diff --git a/Content.Shared/Traits/Assorted/Components/PermanentBlindnessComponent.cs b/Content.Shared/Traits/Assorted/Components/PermanentBlindnessComponent.cs index c1bf7e1639..c10b05ae02 100644 --- a/Content.Shared/Traits/Assorted/Components/PermanentBlindnessComponent.cs +++ b/Content.Shared/Traits/Assorted/Components/PermanentBlindnessComponent.cs @@ -8,5 +8,7 @@ namespace Content.Shared.Traits.Assorted.Components; [RegisterComponent, NetworkedComponent] public sealed partial class PermanentBlindnessComponent : Component { + [ViewVariables(VVAccess.ReadWrite), DataField] + public int Blindness = 0; // How damaged should their eyes be. Set 0 for maximum damage. } diff --git a/Content.Server/Traits/Assorted/SelfAwareComponent.cs b/Content.Shared/Traits/Assorted/Components/SelfAwareComponent.cs similarity index 91% rename from Content.Server/Traits/Assorted/SelfAwareComponent.cs rename to Content.Shared/Traits/Assorted/Components/SelfAwareComponent.cs index 03f5cd1550..fa2485ac48 100644 --- a/Content.Server/Traits/Assorted/SelfAwareComponent.cs +++ b/Content.Shared/Traits/Assorted/Components/SelfAwareComponent.cs @@ -1,13 +1,14 @@ using Content.Shared.Damage.Prototypes; using Content.Shared.FixedPoint; +using Robust.Shared.GameStates; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set; -namespace Content.Server.Traits.Assorted; +namespace Content.Shared.Traits.Assorted.Components; /// /// This is used for the Self-Aware trait to enhance the information received from HealthExaminableSystem. /// -[RegisterComponent] +[RegisterComponent, NetworkedComponent] public sealed partial class SelfAwareComponent : Component { // diff --git a/Content.Shared/Traits/Assorted/Components/SingerComponent.cs b/Content.Shared/Traits/Assorted/Components/SingerComponent.cs index 9c79166ef6..9e737c04ac 100644 --- a/Content.Shared/Traits/Assorted/Components/SingerComponent.cs +++ b/Content.Shared/Traits/Assorted/Components/SingerComponent.cs @@ -9,7 +9,10 @@ public sealed partial class SingerComponent : Component { // Traits are server-only, and is this is added via traits, it must be replicated to the client. [DataField(required: true), AutoNetworkedField] - public ProtoId Proto = string.Empty; + public ProtoId? Proto; + + [DataField(serverOnly: true)] + public EntProtoId? MidiActionId = "ActionHarpyPlayMidi"; [DataField(serverOnly: true)] public EntityUid? MidiAction; diff --git a/Content.Shared/Traits/Assorted/Components/UltraVisionComponent.cs b/Content.Shared/Traits/Assorted/Components/UltraVisionComponent.cs new file mode 100644 index 0000000000..cbe4eb1a7b --- /dev/null +++ b/Content.Shared/Traits/Assorted/Components/UltraVisionComponent.cs @@ -0,0 +1,5 @@ +using Robust.Shared.GameStates; +namespace Content.Shared.Traits.Assorted.Components; + +[RegisterComponent, NetworkedComponent] +public sealed partial class UltraVisionComponent : Component { } diff --git a/Content.Shared/Traits/Assorted/Prototypes/SingerInstrumentPrototype.cs b/Content.Shared/Traits/Assorted/Prototypes/SingerInstrumentPrototype.cs index 6a49854f6e..e1fa696d79 100644 --- a/Content.Shared/Traits/Assorted/Prototypes/SingerInstrumentPrototype.cs +++ b/Content.Shared/Traits/Assorted/Prototypes/SingerInstrumentPrototype.cs @@ -1,9 +1,10 @@ +using Content.Shared.Instruments; using Robust.Shared.Prototypes; namespace Content.Shared.Traits.Assorted.Prototypes; [Prototype("SingerInstrument")] -public sealed class SingerInstrumentPrototype : IPrototype +public sealed partial class SingerInstrumentPrototype : IPrototype { [IdDataField] public string ID { get; private set; } = default!; @@ -27,7 +28,7 @@ public sealed class SingerInstrumentPrototype : IPrototype /// The BUI configuration for the instrument. /// [DataField] - public PrototypeData? MidiUi; + public InstrumentUiKey? MidiUi; // The below is server only, as it uses a server-BUI event !type [DataField(serverOnly: true, required: true)] diff --git a/Content.Shared/Traits/Assorted/Systems/CyberEyesSystem.cs b/Content.Shared/Traits/Assorted/Systems/CyberEyesSystem.cs new file mode 100644 index 0000000000..2dde437972 --- /dev/null +++ b/Content.Shared/Traits/Assorted/Systems/CyberEyesSystem.cs @@ -0,0 +1,21 @@ +using Content.Shared.Examine; +using Content.Shared.Traits.Assorted.Components; + +namespace Content.Shared.Traits.Assorted.Systems; + +public sealed class CyberEyesSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnExamined); + } + + private void OnExamined(EntityUid uid, CyberEyesComponent component, ExaminedEvent args) + { + if (!args.IsInDetailsRange) + return; + + args.PushMarkup($"[color=white]{Loc.GetString(component.CyberEyesExaminationText, ("entity", uid))}[/color]"); + } +} diff --git a/Content.Shared/Traits/Assorted/Systems/NormalVisionSystem.cs b/Content.Shared/Traits/Assorted/Systems/NormalVisionSystem.cs deleted file mode 100644 index ee25bf364b..0000000000 --- a/Content.Shared/Traits/Assorted/Systems/NormalVisionSystem.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Content.Shared.Abilities; -using Content.Shared.Traits.Assorted.Components; - -namespace Content.Shared.Traits.Assorted.Systems; - -/// -/// This handles removing species-specific vision traits. -/// -public sealed class NormalVisionSystem : EntitySystem -{ - /// - public override void Initialize() - { - SubscribeLocalEvent(OnStartup); - } - - - private void OnStartup(EntityUid uid, NormalVisionComponent component, ComponentInit args) - { - RemComp(uid); - RemComp(uid); - } -} diff --git a/Content.Shared/Traits/Assorted/Systems/PermanentBlindnessSystem.cs b/Content.Shared/Traits/Assorted/Systems/PermanentBlindnessSystem.cs index 113939f66b..21faf7d50b 100644 --- a/Content.Shared/Traits/Assorted/Systems/PermanentBlindnessSystem.cs +++ b/Content.Shared/Traits/Assorted/Systems/PermanentBlindnessSystem.cs @@ -2,6 +2,7 @@ using Content.Shared.Eye.Blinding.Components; using Content.Shared.Eye.Blinding.Systems; using Content.Shared.IdentityManagement; +using Content.Shared.Traits.Assorted.Components; using Robust.Shared.Network; namespace Content.Shared.Traits.Assorted.Systems; @@ -18,15 +19,14 @@ public sealed class PermanentBlindnessSystem : EntitySystem /// public override void Initialize() { - SubscribeLocalEvent(OnStartup); - SubscribeLocalEvent(OnShutdown); - SubscribeLocalEvent(OnDamageChanged); - SubscribeLocalEvent(OnExamined); + SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnExamined); } private void OnExamined(Entity blindness, ref ExaminedEvent args) { - if (args.IsInDetailsRange && !_net.IsClient) + if (args.IsInDetailsRange && !_net.IsClient && blindness.Comp.Blindness == 0) { args.PushMarkup(Loc.GetString("trait-examined-Blindness", ("target", Identity.Entity(blindness, EntityManager)))); } @@ -37,28 +37,17 @@ private void OnShutdown(Entity blindness _blinding.UpdateIsBlind(blindness.Owner); } - private void OnStartup(Entity blindness, ref ComponentStartup args) + private void OnMapInit(Entity blindness, ref MapInitEvent args) { if (!_entityManager.TryGetComponent(blindness, out var blindable)) return; - var damageToDeal = (int) BlurryVisionComponent.MaxMagnitude - blindable.EyeDamage; - - if (damageToDeal <= 0) - return; - - _blinding.AdjustEyeDamage(blindness.Owner, damageToDeal); - } - - private void OnDamageChanged(Entity blindness, ref EyeDamageChangedEvent args) - { - if (args.Damage >= BlurryVisionComponent.MaxMagnitude) - return; - - if (!_entityManager.TryGetComponent(blindness, out var blindable)) - return; - - var damageRestoration = (int) BlurryVisionComponent.MaxMagnitude - args.Damage; - _blinding.AdjustEyeDamage(blindness.Owner, damageRestoration); + if (blindness.Comp.Blindness != 0) + _blinding.SetMinDamage(new Entity(blindness.Owner, blindable), blindness.Comp.Blindness); + else + { + var maxMagnitudeInt = (int) BlurryVisionComponent.MaxMagnitude; + _blinding.SetMinDamage(new Entity(blindness.Owner, blindable), maxMagnitudeInt); + } } } diff --git a/Content.Shared/Traits/Assorted/Systems/SharedSingerSystem.cs b/Content.Shared/Traits/Assorted/Systems/SharedSingerSystem.cs index 08d0f5c76d..13270ae45d 100644 --- a/Content.Shared/Traits/Assorted/Systems/SharedSingerSystem.cs +++ b/Content.Shared/Traits/Assorted/Systems/SharedSingerSystem.cs @@ -4,6 +4,7 @@ using Content.Shared.Traits.Assorted.Components; using Content.Shared.Traits.Assorted.Prototypes; using Content.Shared.Zombies; +using Robust.Shared.Player; using Robust.Shared.Prototypes; namespace Content.Shared.Traits.Assorted.Systems; @@ -15,7 +16,6 @@ public abstract class SharedSingerSystem : EntitySystem [Dependency] private readonly SharedActionsSystem _actionsSystem = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly SharedInstrumentSystem _instrument = default!; - [Dependency] private readonly SharedUserInterfaceSystem _ui = default!; public override void Initialize() { @@ -26,6 +26,7 @@ public override void Initialize() SubscribeLocalEvent(OnShutdown); SubscribeLocalEvent(OnBoundUIClosed); SubscribeLocalEvent(OnBoundUIOpened); + SubscribeLocalEvent(OnPlayerDetached); } private void OnStartup(Entity ent, ref ComponentStartup args) @@ -33,15 +34,12 @@ private void OnStartup(Entity ent, ref ComponentStartup args) if (!ProtoMan.TryIndex(ent.Comp.Proto, out var singer)) return; - _actionsSystem.AddAction(ent, ref ent.Comp.MidiAction, singer.MidiActionId); + _actionsSystem.AddAction(ent, ref ent.Comp.MidiAction, ent.Comp.MidiActionId); var instrumentComp = EnsureInstrumentComp(ent); var defaultData = singer.InstrumentList[singer.DefaultInstrument]; _instrument.SetInstrumentProgram(instrumentComp, defaultData.Item1, defaultData.Item2); SetUpSwappableInstrument(ent, singer); - - if (singer.MidiUi is {} uiData && !_ui.TryGetUi(ent, uiData.UiKey, out _)) - _ui.AddUi(ent.Owner, uiData); } private void OnShutdown(Entity ent, ref ComponentShutdown args) @@ -72,6 +70,11 @@ private void OnBoundUIOpened(EntityUid uid, SingerComponent component, BoundUIOp _appearance.SetData(uid, HarpyVisualLayers.Singing, SingingVisualLayer.True, appearance); } + private void OnPlayerDetached(EntityUid uid, SingerComponent component, PlayerDetachedEvent args) + { + CloseMidiUi(uid); + } + /// /// Closes the MIDI UI if it is open. Does nothing on client side. /// diff --git a/Content.Shared/Traits/Prototypes/TraitCategoryPrototype.cs b/Content.Shared/Traits/Prototypes/TraitCategoryPrototype.cs index efbac1ca7d..f5cf95fd56 100644 --- a/Content.Shared/Traits/Prototypes/TraitCategoryPrototype.cs +++ b/Content.Shared/Traits/Prototypes/TraitCategoryPrototype.cs @@ -11,4 +11,10 @@ public sealed partial class TraitCategoryPrototype : IPrototype { [IdDataField] public string ID { get; } = default!; + + [DataField] + public bool Root; + + [DataField] + public List> SubCategories = new(); } diff --git a/Content.Shared/Traits/Prototypes/TraitPrototype.cs b/Content.Shared/Traits/Prototypes/TraitPrototype.cs index 2e6b7cc066..8ee3c55637 100644 --- a/Content.Shared/Traits/Prototypes/TraitPrototype.cs +++ b/Content.Shared/Traits/Prototypes/TraitPrototype.cs @@ -1,6 +1,7 @@ using Content.Shared.Customization.Systems; -using Content.Shared.Whitelist; using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.Manager; +using Robust.Shared.Serialization; namespace Content.Shared.Traits; @@ -18,8 +19,8 @@ public sealed partial class TraitPrototype : IPrototype /// /// Which customization tab to place this entry in /// - [DataField(required: true), ValidatePrototypeId] - public string Category = "Uncategorized"; + [DataField(required: true)] + public ProtoId Category = "Uncategorized"; /// /// How many points this will give the character @@ -31,9 +32,17 @@ public sealed partial class TraitPrototype : IPrototype [DataField] public List Requirements = new(); - /// - /// The components that get added to the player when they pick this trait. - /// - [DataField(required: true)] - public ComponentRegistry Components { get; private set; } = default!; + [DataField(serverOnly: true)] + public TraitFunction[] Functions { get; private set; } = Array.Empty(); +} + +/// This serves as a hook for trait functions to modify a player character upon spawning in. +[ImplicitDataDefinitionForInheritors] +public abstract partial class TraitFunction +{ + public abstract void OnPlayerSpawn( + EntityUid mob, + IComponentFactory factory, + IEntityManager entityManager, + ISerializationManager serializationManager); } diff --git a/Content.Shared/UserInterface/ActivatableUIComponent.cs b/Content.Shared/UserInterface/ActivatableUIComponent.cs new file mode 100644 index 0000000000..93f05acac0 --- /dev/null +++ b/Content.Shared/UserInterface/ActivatableUIComponent.cs @@ -0,0 +1,77 @@ +using Content.Shared.Whitelist; +using Robust.Shared.GameStates; +using Robust.Shared.Serialization.TypeSerializers.Implementations; + +namespace Content.Shared.UserInterface +{ + [RegisterComponent, NetworkedComponent, AutoGenerateComponentState] + public sealed partial class ActivatableUIComponent : Component + { + [DataField(required: true, customTypeSerializer: typeof(EnumSerializer))] + public Enum? Key; + + /// + /// Whether the item must be held in one of the user's hands to work. + /// This is ignored unless is true. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField] + public bool InHandsOnly; + + [DataField] + public bool SingleUser; + + [ViewVariables(VVAccess.ReadWrite)] + [DataField] + public bool AdminOnly; + + [DataField] + public LocId VerbText = "ui-verb-toggle-open"; + + /// + /// Whether you need a hand to operate this UI. The hand does not need to be free, you just need to have one. + /// + /// + /// This should probably be true for most machines & computers, but there will still be UIs that represent a + /// more generic interaction / configuration that might not require hands. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField] + public bool RequireHands = true; + + /// + /// Entities that are required to open this UI. + /// + [DataField, ViewVariables(VVAccess.ReadWrite)] + public EntityWhitelist? RequiredItems; + + /// + /// If true, then this UI can only be opened via verbs. I.e., normal interactions/activations will not open + /// the UI. + /// + [DataField, ViewVariables(VVAccess.ReadWrite)] + public bool VerbOnly; + + /// + /// Whether spectators (non-admin ghosts) should be allowed to view this UI. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField] + public bool BlockSpectators; + + /// + /// Whether the item must be in the user's currently selected/active hand. + /// This is ignored unless is true. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField] + public bool RequireActiveHand = true; + + /// + /// The client channel currently using the object, or null if there's none/not single user. + /// NOTE: DO NOT DIRECTLY SET, USE ActivatableUISystem.SetCurrentSingleUser + /// + [DataField, AutoNetworkedField] + public EntityUid? CurrentSingleUser; + } +} diff --git a/Content.Shared/UserInterface/ActivatableUIEvents.cs b/Content.Shared/UserInterface/ActivatableUIEvents.cs index 6e6b3f63c6..338673a3ca 100644 --- a/Content.Shared/UserInterface/ActivatableUIEvents.cs +++ b/Content.Shared/UserInterface/ActivatableUIEvents.cs @@ -24,12 +24,12 @@ public UserOpenActivatableUIAttemptEvent(EntityUid who, EntityUid target) public sealed class AfterActivatableUIOpenEvent : EntityEventArgs { public EntityUid User { get; } - public readonly ICommonSession Session; + public readonly EntityUid Actor; - public AfterActivatableUIOpenEvent(EntityUid who, ICommonSession session) + public AfterActivatableUIOpenEvent(EntityUid who, EntityUid actor) { User = who; - Session = session; + Actor = actor; } } diff --git a/Content.Shared/UserInterface/ActivatableUIRequiresPowerCellComponent.cs b/Content.Shared/UserInterface/ActivatableUIRequiresPowerCellComponent.cs new file mode 100644 index 0000000000..aa9e561e07 --- /dev/null +++ b/Content.Shared/UserInterface/ActivatableUIRequiresPowerCellComponent.cs @@ -0,0 +1,13 @@ +using Content.Shared.PowerCell; +using Robust.Shared.GameStates; + +namespace Content.Shared.UserInterface; + +/// +/// Specifies that the attached entity requires power. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class ActivatableUIRequiresPowerCellComponent : Component +{ + +} diff --git a/Content.Shared/UserInterface/ActivatableUISystem.Power.cs b/Content.Shared/UserInterface/ActivatableUISystem.Power.cs new file mode 100644 index 0000000000..b8a815c7a8 --- /dev/null +++ b/Content.Shared/UserInterface/ActivatableUISystem.Power.cs @@ -0,0 +1,93 @@ +using Content.Shared.PowerCell; +using Robust.Shared.Containers; + +namespace Content.Shared.UserInterface; + +public sealed partial class ActivatableUISystem +{ + [Dependency] private readonly SharedPowerCellSystem _cell = default!; + + private void InitializePower() + { + SubscribeLocalEvent(OnBatteryOpenAttempt); + SubscribeLocalEvent(OnBatteryOpened); + SubscribeLocalEvent(OnBatteryClosed); + + SubscribeLocalEvent(OnPowerCellRemoved); + } + + private void OnPowerCellRemoved(EntityUid uid, PowerCellDrawComponent component, EntRemovedFromContainerMessage args) + { + _cell.SetPowerCellDrawEnabled(uid, false); + + if (!HasComp(uid) || + !TryComp(uid, out ActivatableUIComponent? activatable)) + { + return; + } + + if (activatable.Key == null) + { + Log.Error($"Encountered null key in activatable ui on entity {ToPrettyString(uid)}"); + return; + } + + _uiSystem.CloseUi(uid, activatable.Key); + } + + private void OnBatteryOpened(EntityUid uid, ActivatableUIRequiresPowerCellComponent component, BoundUIOpenedEvent args) + { + var activatable = Comp(uid); + + if (!args.UiKey.Equals(activatable.Key)) + return; + + _cell.SetPowerCellDrawEnabled(uid, true); + } + + private void OnBatteryClosed(EntityUid uid, ActivatableUIRequiresPowerCellComponent component, BoundUIClosedEvent args) + { + var activatable = Comp(uid); + + if (!args.UiKey.Equals(activatable.Key)) + return; + + // Stop drawing power if this was the last person with the UI open. + if (!_uiSystem.IsUiOpen(uid, activatable.Key)) + _cell.SetPowerCellDrawEnabled(uid, false); + } + + /// + /// Call if you want to check if the UI should close due to a recent battery usage. + /// + public void CheckUsage(EntityUid uid, ActivatableUIComponent? active = null, ActivatableUIRequiresPowerCellComponent? component = null, PowerCellDrawComponent? draw = null) + { + if (!Resolve(uid, ref component, ref draw, ref active, false)) + return; + + if (active.Key == null) + { + Log.Error($"Encountered null key in activatable ui on entity {ToPrettyString(uid)}"); + return; + } + + if (_cell.HasActivatableCharge(uid)) + return; + + _uiSystem.CloseUi(uid, active.Key); + } + + private void OnBatteryOpenAttempt(EntityUid uid, ActivatableUIRequiresPowerCellComponent component, ActivatableUIOpenAttemptEvent args) + { + if (!TryComp(uid, out var draw)) + return; + + // Check if we have the appropriate drawrate / userate to even open it. + if (args.Cancelled || + !_cell.HasActivatableCharge(uid, draw, user: args.User) || + !_cell.HasDrawCharge(uid, draw, user: args.User)) + { + args.Cancel(); + } + } +} diff --git a/Content.Shared/UserInterface/ActivatableUISystem.cs b/Content.Shared/UserInterface/ActivatableUISystem.cs new file mode 100644 index 0000000000..14ce4f20dc --- /dev/null +++ b/Content.Shared/UserInterface/ActivatableUISystem.cs @@ -0,0 +1,284 @@ +using Content.Shared.ActionBlocker; +using Content.Shared.Administration.Managers; +using Content.Shared.Ghost; +using Content.Shared.Hands; +using Content.Shared.Hands.Components; +using Content.Shared.Hands.EntitySystems; +using Content.Shared.Interaction; +using Content.Shared.Interaction.Events; +using Content.Shared.Popups; +using Content.Shared.Verbs; +using Robust.Shared.Utility; + +namespace Content.Shared.UserInterface; + +public sealed partial class ActivatableUISystem : EntitySystem +{ + [Dependency] private readonly ISharedAdminManager _adminManager = default!; + [Dependency] private readonly ActionBlockerSystem _blockerSystem = default!; + [Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!; + [Dependency] private readonly SharedPopupSystem _popupSystem = default!; + [Dependency] private readonly SharedHandsSystem _hands = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnStartup); + SubscribeLocalEvent(OnUseInHand); + SubscribeLocalEvent(OnActivate); + SubscribeLocalEvent(OnInteractUsing); + SubscribeLocalEvent(OnHandDeselected); + SubscribeLocalEvent(OnHandUnequipped); + SubscribeLocalEvent(OnUIClose); + SubscribeLocalEvent>(GetActivationVerb); + SubscribeLocalEvent>(GetVerb); + + SubscribeLocalEvent(OnActionPerform); + + InitializePower(); + } + + private void OnStartup(Entity ent, ref ComponentStartup args) + { + if (ent.Comp.Key == null) + { + Log.Error($"Missing UI Key for entity: {ToPrettyString(ent)}"); + return; + } + + // TODO BUI + // set interaction range to zero to avoid constant range checks. + // + // if (ent.Comp.InHandsOnly && _uiSystem.TryGetInterfaceData(ent.Owner, ent.Comp.Key, out var data)) + // data.InteractionRange = 0; + } + + private void OnActionPerform(EntityUid uid, UserInterfaceComponent component, OpenUiActionEvent args) + { + if (args.Handled || args.Key == null) + return; + + args.Handled = _uiSystem.TryToggleUi(uid, args.Key, args.Performer); + } + + + private void GetActivationVerb(EntityUid uid, ActivatableUIComponent component, GetVerbsEvent args) + { + if (component.VerbOnly || !ShouldAddVerb(uid, component, args)) + return; + + args.Verbs.Add(new ActivationVerb + { + Act = () => InteractUI(args.User, uid, component), + Text = Loc.GetString(component.VerbText), + // TODO VERB ICON find a better icon + Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/settings.svg.192dpi.png")), + }); + } + + private void GetVerb(EntityUid uid, ActivatableUIComponent component, GetVerbsEvent args) + { + if (!component.VerbOnly || !ShouldAddVerb(uid, component, args)) + return; + + args.Verbs.Add(new Verb + { + Act = () => InteractUI(args.User, uid, component), + Text = Loc.GetString(component.VerbText), + // TODO VERB ICON find a better icon + Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/settings.svg.192dpi.png")), + }); + } + + private bool ShouldAddVerb(EntityUid uid, ActivatableUIComponent component, GetVerbsEvent args) where T : Verb + { + if (!args.CanAccess) + return false; + + if (!component.RequiredItems?.IsValid(args.Using ?? default, EntityManager) ?? false) + return false; + + if (component.RequireHands) + { + if (args.Hands == null) + return false; + + if (component.InHandsOnly) + { + if (!_hands.IsHolding(args.User, uid, out var hand, args.Hands)) + return false; + + if (component.RequireActiveHand && args.Hands.ActiveHand != hand) + return false; + } + } + + return args.CanInteract || HasComp(args.User) && !component.BlockSpectators; + } + + private void OnUseInHand(EntityUid uid, ActivatableUIComponent component, UseInHandEvent args) + { + if (args.Handled) + return; + + if (component.VerbOnly) + return; + + if (component.RequiredItems != null) + return; + + args.Handled = InteractUI(args.User, uid, component); + } + + private void OnActivate(EntityUid uid, ActivatableUIComponent component, ActivateInWorldEvent args) + { + if (args.Handled) + return; + + if (component.VerbOnly) + return; + + if (component.RequiredItems != null) + return; + + args.Handled = InteractUI(args.User, uid, component); + } + + private void OnInteractUsing(EntityUid uid, ActivatableUIComponent component, InteractUsingEvent args) + { + if (args.Handled) + return; + + if (component.VerbOnly) + return; + + if (component.RequiredItems == null) + return; + + if (!component.RequiredItems.IsValid(args.Used, EntityManager)) + return; + + args.Handled = InteractUI(args.User, uid, component); + } + + private void OnUIClose(EntityUid uid, ActivatableUIComponent component, BoundUIClosedEvent args) + { + var user = args.Actor; + + if (user != component.CurrentSingleUser) + return; + + if (!Equals(args.UiKey, component.Key)) + return; + + SetCurrentSingleUser(uid, null, component); + } + + private bool InteractUI(EntityUid user, EntityUid uiEntity, ActivatableUIComponent aui) + { + if (aui.Key == null || !_uiSystem.HasUi(uiEntity, aui.Key)) + return false; + + if (_uiSystem.IsUiOpen(uiEntity, aui.Key, user)) + { + _uiSystem.CloseUi(uiEntity, aui.Key, user); + return true; + } + + if (!_blockerSystem.CanInteract(user, uiEntity) && (!HasComp(user) || aui.BlockSpectators)) + return false; + + if (aui.RequireHands) + { + if (!TryComp(user, out HandsComponent? hands)) + return false; + + if (aui.InHandsOnly) + { + if (!_hands.IsHolding(user, uiEntity, out var hand, hands)) + return false; + + if (aui.RequireActiveHand && hands.ActiveHand != hand) + return false; + } + } + + if (aui.AdminOnly && !_adminManager.IsAdmin(user)) + return false; + + if (aui.SingleUser && aui.CurrentSingleUser != null && user != aui.CurrentSingleUser) + { + var message = Loc.GetString("machine-already-in-use", ("machine", uiEntity)); + _popupSystem.PopupEntity(message, uiEntity, user); + + if (_uiSystem.IsUiOpen(uiEntity, aui.Key)) + return true; + + Log.Error($"Activatable UI has user without being opened? Entity: {ToPrettyString(uiEntity)}. User: {aui.CurrentSingleUser}, Key: {aui.Key}"); + } + + // If we've gotten this far, fire a cancellable event that indicates someone is about to activate this. + // This is so that stuff can require further conditions (like power). + var oae = new ActivatableUIOpenAttemptEvent(user); + var uae = new UserOpenActivatableUIAttemptEvent(user, uiEntity); + RaiseLocalEvent(user, uae); + RaiseLocalEvent(uiEntity, oae); + if (oae.Cancelled || uae.Cancelled) + return false; + + // Give the UI an opportunity to prepare itself if it needs to do anything + // before opening + var bae = new BeforeActivatableUIOpenEvent(user); + RaiseLocalEvent(uiEntity, bae); + + SetCurrentSingleUser(uiEntity, user, aui); + _uiSystem.OpenUi(uiEntity, aui.Key, user); + + //Let the component know a user opened it so it can do whatever it needs to do + var aae = new AfterActivatableUIOpenEvent(user, user); + RaiseLocalEvent(uiEntity, aae); + + return true; + } + + public void SetCurrentSingleUser(EntityUid uid, EntityUid? user, ActivatableUIComponent? aui = null) + { + if (!Resolve(uid, ref aui)) + return; + + if (!aui.SingleUser) + return; + + aui.CurrentSingleUser = user; + Dirty(uid, aui); + + RaiseLocalEvent(uid, new ActivatableUIPlayerChangedEvent()); + } + + public void CloseAll(EntityUid uid, ActivatableUIComponent? aui = null) + { + if (!Resolve(uid, ref aui, false)) + return; + + if (aui.Key == null) + { + Log.Error($"Encountered null key in activatable ui on entity {ToPrettyString(uid)}"); + return; + } + + _uiSystem.CloseUi(uid, aui.Key); + } + + private void OnHandDeselected(Entity ent, ref HandDeselectedEvent args) + { + if (ent.Comp.RequireHands && ent.Comp.InHandsOnly && ent.Comp.RequireActiveHand) + CloseAll(ent, ent); + } + + private void OnHandUnequipped(Entity ent, ref GotUnequippedHandEvent args) + { + if (ent.Comp.RequireHands && ent.Comp.InHandsOnly) + CloseAll(ent, ent); + } +} diff --git a/Content.Shared/Verbs/SharedVerbSystem.cs b/Content.Shared/Verbs/SharedVerbSystem.cs index 9dda910f3f..e78fe98f4c 100644 --- a/Content.Shared/Verbs/SharedVerbSystem.cs +++ b/Content.Shared/Verbs/SharedVerbSystem.cs @@ -55,28 +55,24 @@ public SortedSet GetLocalVerbs(EntityUid target, EntityUid user, Type type return GetLocalVerbs(target, user, new List() { type }, force); } + /// + public SortedSet GetLocalVerbs(EntityUid target, EntityUid user, List types, bool force = false) + { + return GetLocalVerbs(target, user, types, out _, force); + } + /// /// Raises a number of events in order to get all verbs of the given type(s) defined in local systems. This /// does not request verbs from the server. /// - public SortedSet GetLocalVerbs(EntityUid target, EntityUid user, List types, bool force = false) + public SortedSet GetLocalVerbs(EntityUid target, EntityUid user, List types, + out List extraCategories, bool force = false) { SortedSet verbs = new(); + extraCategories = new(); // accessibility checks - bool canAccess = false; - if (force || target == user) - canAccess = true; - else if (_interactionSystem.InRangeUnobstructed(user, target)) - { - // Note that being in a container does not count as an obstruction for InRangeUnobstructed - // Therefore, we need extra checks to ensure the item is actually accessible: - if (ContainerSystem.IsInSameOrParentContainer(user, target)) - canAccess = true; - else - // the item might be in a backpack that the user has open - canAccess = _interactionSystem.CanAccessViaStorage(user, target); - } + var canAccess = force || _interactionSystem.InRangeAndAccessible(user, target); // A large number of verbs need to check action blockers. Instead of repeatedly having each system individually // call ActionBlocker checks, just cache it for the verb request. @@ -108,7 +104,7 @@ public SortedSet GetLocalVerbs(EntityUid target, EntityUid user, List(user, target, @using, hands, canInteract, canAccess); + var verbEvent = new GetVerbsEvent(user, target, @using, hands, canInteract, canAccess, extraCategories); RaiseLocalEvent(target, verbEvent, true); verbs.UnionWith(verbEvent.Verbs); } @@ -117,35 +113,35 @@ public SortedSet GetLocalVerbs(EntityUid target, EntityUid user, List(user, target, @using, hands, canInteract, canAccess); + var verbEvent = new GetVerbsEvent(user, target, @using, hands, canInteract, canAccess, extraCategories); RaiseLocalEvent(@using.Value, verbEvent, true); // directed at used, not at target verbs.UnionWith(verbEvent.Verbs); } if (types.Contains(typeof(InnateVerb))) { - var verbEvent = new GetVerbsEvent(user, target, @using, hands, canInteract, canAccess); + var verbEvent = new GetVerbsEvent(user, target, @using, hands, canInteract, canAccess, extraCategories); RaiseLocalEvent(user, verbEvent, true); verbs.UnionWith(verbEvent.Verbs); } if (types.Contains(typeof(AlternativeVerb))) { - var verbEvent = new GetVerbsEvent(user, target, @using, hands, canInteract, canAccess); + var verbEvent = new GetVerbsEvent(user, target, @using, hands, canInteract, canAccess, extraCategories); RaiseLocalEvent(target, verbEvent, true); verbs.UnionWith(verbEvent.Verbs); } if (types.Contains(typeof(ActivationVerb))) { - var verbEvent = new GetVerbsEvent(user, target, @using, hands, canInteract, canAccess); + var verbEvent = new GetVerbsEvent(user, target, @using, hands, canInteract, canAccess, extraCategories); RaiseLocalEvent(target, verbEvent, true); verbs.UnionWith(verbEvent.Verbs); } if (types.Contains(typeof(ExamineVerb))) { - var verbEvent = new GetVerbsEvent(user, target, @using, hands, canInteract, canAccess); + var verbEvent = new GetVerbsEvent(user, target, @using, hands, canInteract, canAccess, extraCategories); RaiseLocalEvent(target, verbEvent, true); verbs.UnionWith(verbEvent.Verbs); } @@ -153,7 +149,7 @@ public SortedSet GetLocalVerbs(EntityUid target, EntityUid user, List(user, target, @using, hands, canInteract, canAccess); + var verbEvent = new GetVerbsEvent(user, target, @using, hands, canInteract, canAccess, extraCategories); RaiseLocalEvent(target, verbEvent, true); verbs.UnionWith(verbEvent.Verbs); } @@ -161,7 +157,7 @@ public SortedSet GetLocalVerbs(EntityUid target, EntityUid user, List(user, target, @using, hands, canInteract, access); + var verbEvent = new GetVerbsEvent(user, target, @using, hands, canInteract, access, extraCategories); RaiseLocalEvent(target, verbEvent); verbs.UnionWith(verbEvent.Verbs); } diff --git a/Content.Shared/Verbs/VerbCategory.cs b/Content.Shared/Verbs/VerbCategory.cs index d22041396f..3331cad30b 100644 --- a/Content.Shared/Verbs/VerbCategory.cs +++ b/Content.Shared/Verbs/VerbCategory.cs @@ -83,5 +83,9 @@ public VerbCategory(string text, string? icon, bool iconsOnly = false) public static readonly VerbCategory Lever = new("verb-categories-lever", null); public static readonly VerbCategory SelectType = new("verb-categories-select-type", null); + + public static readonly VerbCategory PowerLevel = new("verb-categories-power-level", null); + + public static readonly VerbCategory Interaction = new("verb-categories-interaction", null); } } diff --git a/Content.Shared/Verbs/VerbEvents.cs b/Content.Shared/Verbs/VerbEvents.cs index 9a09d5d7a1..6b3fd327c9 100644 --- a/Content.Shared/Verbs/VerbEvents.cs +++ b/Content.Shared/Verbs/VerbEvents.cs @@ -77,6 +77,13 @@ public sealed class GetVerbsEvent : EntityEventArgs where TVerb : Verb /// public readonly SortedSet Verbs = new(); + /// + /// Additional verb categories to show in the pop-up menu, even if there are no verbs currently associated + /// with that category. This is mainly useful to prevent verb menu pop-in. E.g., admins will get admin/debug + /// related verbs on entities, even though most of those verbs are all defined server-side. + /// + public readonly List ExtraCategories; + /// /// Can the user physically access the target? /// @@ -123,7 +130,7 @@ public sealed class GetVerbsEvent : EntityEventArgs where TVerb : Verb /// public readonly EntityUid? Using; - public GetVerbsEvent(EntityUid user, EntityUid target, EntityUid? @using, HandsComponent? hands, bool canInteract, bool canAccess) + public GetVerbsEvent(EntityUid user, EntityUid target, EntityUid? @using, HandsComponent? hands, bool canInteract, bool canAccess, List extraCategories) { User = user; Target = target; @@ -131,6 +138,7 @@ public GetVerbsEvent(EntityUid user, EntityUid target, EntityUid? @using, HandsC Hands = hands; CanAccess = canAccess; CanInteract = canInteract; + ExtraCategories = extraCategories; } } } diff --git a/Content.Shared/Wagging/WaggingComponent.cs b/Content.Shared/Wagging/WaggingComponent.cs index 76881827dd..70e7f009c7 100644 --- a/Content.Shared/Wagging/WaggingComponent.cs +++ b/Content.Shared/Wagging/WaggingComponent.cs @@ -17,9 +17,6 @@ public sealed partial class WaggingComponent : Component [DataField] public EntityUid? ActionEntity; - [DataField] - public ProtoId EmoteId = "WagTail"; - /// /// Suffix to add to get the animated marking. /// diff --git a/Content.Shared/Weapons/Melee/MeleeSoundSystem.cs b/Content.Shared/Weapons/Melee/MeleeSoundSystem.cs index 350642105a..315d752a2c 100644 --- a/Content.Shared/Weapons/Melee/MeleeSoundSystem.cs +++ b/Content.Shared/Weapons/Melee/MeleeSoundSystem.cs @@ -1,4 +1,5 @@ using Content.Shared.Weapons.Melee.Components; +using Content.Shared.Weapons.Melee.Events; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; diff --git a/Content.Shared/Weapons/Melee/MeleeWeaponComponent.cs b/Content.Shared/Weapons/Melee/MeleeWeaponComponent.cs index 2708a07c6e..2999b7aed7 100644 --- a/Content.Shared/Weapons/Melee/MeleeWeaponComponent.cs +++ b/Content.Shared/Weapons/Melee/MeleeWeaponComponent.cs @@ -1,3 +1,4 @@ +using Content.Shared.Contests; using Content.Shared.Damage; using Content.Shared.FixedPoint; using Robust.Shared.Audio; @@ -86,6 +87,12 @@ public sealed partial class MeleeWeaponComponent : Component [DataField, AutoNetworkedField] public FixedPoint2 ClickDamageModifier = FixedPoint2.New(1); + /// + /// Part damage is multiplied by this amount for single-target attacks + /// + [DataField, AutoNetworkedField] + public float ClickPartDamageMultiplier = 1.00f; + // TODO: Temporarily 1.5 until interactionoutline is adjusted to use melee, then probably drop to 1.2 /// /// Nearest edge range to hit an entity. @@ -105,6 +112,12 @@ public sealed partial class MeleeWeaponComponent : Component [DataField, AutoNetworkedField] public float HeavyDamageBaseModifier = 1.2f; + /// + /// Part damage is multiplied by this amount for heavy swings + /// + [DataField, AutoNetworkedField] + public float HeavyPartDamageMultiplier = 0.5f; + /// /// Total width of the angle for wide attacks. /// @@ -128,10 +141,10 @@ public sealed partial class MeleeWeaponComponent : Component public bool SwingLeft; [DataField, AutoNetworkedField] - public float HeavyStaminaCost = 20f; + public float HeavyStaminaCost = 2.5f; [DataField, AutoNetworkedField] - public int MaxTargets = 5; + public int MaxTargets = 3; // Sounds @@ -156,6 +169,25 @@ public sealed partial class MeleeWeaponComponent : Component /// [DataField, AutoNetworkedField] public SoundSpecifier SoundNoDamage { get; set; } = new SoundCollectionSpecifier("WeakHit"); + + /// + /// Arguments for the MeleeContestInteractions constructor + /// + [DataField] + public ContestArgs ContestArgs = new ContestArgs + { + DoStaminaInteraction = true, + StaminaDisadvantage = true, + DoHealthInteraction = true, + }; + + /// + /// If true, the weapon must be equipped for it to be used. + /// E.g boxing gloves must be equipped to your gloves, + /// not just held in your hand to be used. + /// + [DataField, AutoNetworkedField] + public bool MustBeEquippedToUse = false; } /// diff --git a/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs b/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs index b5a537b7e1..72047666f8 100644 --- a/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs +++ b/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs @@ -32,7 +32,7 @@ namespace Content.Shared.Weapons.Melee; -public abstract class SharedMeleeWeaponSystem : EntitySystem +public abstract partial class SharedMeleeWeaponSystem : EntitySystem { [Dependency] protected readonly ISharedAdminLogManager AdminLogger = default!; [Dependency] protected readonly ActionBlockerSystem Blocker = default!; @@ -169,49 +169,39 @@ private void OnStopAttack(StopAttackEvent msg, EntitySessionEventArgs args) private void OnLightAttack(LightAttackEvent msg, EntitySessionEventArgs args) { - var user = args.SenderSession.AttachedEntity; - - if (user == null) + if (args.SenderSession.AttachedEntity is not {} user) return; - if (!TryGetWeapon(user.Value, out var weaponUid, out var weapon) || + if (!TryGetWeapon(user, out var weaponUid, out var weapon) || weaponUid != GetEntity(msg.Weapon)) { return; } - AttemptAttack(args.SenderSession.AttachedEntity!.Value, weaponUid, weapon, msg, args.SenderSession); + AttemptAttack(user, weaponUid, weapon, msg, args.SenderSession); } private void OnHeavyAttack(HeavyAttackEvent msg, EntitySessionEventArgs args) { - if (args.SenderSession.AttachedEntity == null) - { + if (args.SenderSession.AttachedEntity is not {} user) return; - } - if (!TryGetWeapon(args.SenderSession.AttachedEntity.Value, out var weaponUid, out var weapon) || + if (!TryGetWeapon(user, out var weaponUid, out var weapon) || weaponUid != GetEntity(msg.Weapon)) { return; } - AttemptAttack(args.SenderSession.AttachedEntity.Value, weaponUid, weapon, msg, args.SenderSession); + AttemptAttack(user, weaponUid, weapon, msg, args.SenderSession); } private void OnDisarmAttack(DisarmAttackEvent msg, EntitySessionEventArgs args) { - if (args.SenderSession.AttachedEntity == null) - { + if (args.SenderSession.AttachedEntity is not {} user) return; - } - if (!TryGetWeapon(args.SenderSession.AttachedEntity.Value, out var weaponUid, out var weapon)) - { - return; - } - - AttemptAttack(args.SenderSession.AttachedEntity.Value, weaponUid, weapon, msg, args.SenderSession); + if (TryGetWeapon(user, out var weaponUid, out var weapon)) + AttemptAttack(user, weaponUid, weapon, msg, args.SenderSession); } /// @@ -225,6 +215,9 @@ public DamageSpecifier GetDamage(EntityUid uid, EntityUid user, MeleeWeaponCompo var ev = new GetMeleeDamageEvent(uid, new (component.Damage), new(), user); RaiseLocalEvent(uid, ref ev); + if (component.ContestArgs is not null) + ev.Damage *= _contests.ContestConstructor(user, component.ContestArgs); + return DamageSpecifier.ApplyModifierSets(ev.Damage, ev.Modifiers); } @@ -249,9 +242,7 @@ public FixedPoint2 GetHeavyDamageModifier(EntityUid uid, EntityUid user, MeleeWe return ev.DamageModifier * ev.Multipliers - * component.HeavyDamageBaseModifier - * _contests.StaminaContest(user, false, 2f) //Taking stamina damage reduces wide swing damage by up to 50% - / _contests.HealthContest(user, false, 0.8f); //Being injured grants up to 20% more wide swing damage + * component.HeavyDamageBaseModifier; } public bool TryGetWeapon(EntityUid entity, out EntityUid weaponUid, [NotNullWhen(true)] out MeleeWeaponComponent? melee) @@ -276,7 +267,10 @@ public bool TryGetWeapon(EntityUid entity, out EntityUid weaponUid, [NotNullWhen if (EntityManager.TryGetComponent(entity, out HandsComponent? hands) && hands.ActiveHandEntity is { } held) { - if (EntityManager.TryGetComponent(held, out melee)) + // Make sure the entity is a weapon AND it doesn't need + // to be equipped to be used (E.g boxing gloves). + if (EntityManager.TryGetComponent(held, out melee) && + !melee.MustBeEquippedToUse) { weaponUid = held; return true; @@ -341,23 +335,32 @@ private bool AttemptAttack(EntityUid user, EntityUid weaponUid, MeleeWeaponCompo var fireRateSwingModifier = 1f; + EntityUid? target = null; switch (attack) { case LightAttackEvent light: - var lightTarget = GetEntity(light.Target); + if (light.Target != null && !TryGetEntity(light.Target, out target)) + { + // Target was lightly attacked & deleted. + return false; + } - if (!Blocker.CanAttack(user, lightTarget, (weaponUid, weapon))) + if (!Blocker.CanAttack(user, target, (weaponUid, weapon))) return false; // Can't self-attack if you're the weapon - if (weaponUid == lightTarget) + if (weaponUid == target) return false; break; case DisarmAttackEvent disarm: - var disarmTarget = GetEntity(disarm.Target); + if (disarm.Target != null && !TryGetEntity(disarm.Target, out target)) + { + // Target was lightly attacked & deleted. + return false; + } - if (!Blocker.CanAttack(user, disarmTarget, (weaponUid, weapon), true)) + if (!Blocker.CanAttack(user, target, (weaponUid, weapon), true)) return false; break; case HeavyAttackEvent: @@ -440,9 +443,7 @@ private bool AttemptAttack(EntityUid user, EntityUid weaponUid, MeleeWeaponCompo protected virtual void DoLightAttack(EntityUid user, LightAttackEvent ev, EntityUid meleeUid, MeleeWeaponComponent component, ICommonSession? session) { - var damage = GetDamage(meleeUid, user, component) - * _contests.StaminaContest(user) //Taking stamina damage reduces light attack damage by up to 25% - / _contests.HealthContest(user, false, 0.8f); //Being injured grants up to 20% more damage; + var damage = GetDamage(meleeUid, user, component); var target = GetEntity(ev.Target); // For consistency with wide attacks stuff needs damageable. @@ -500,7 +501,7 @@ protected virtual void DoLightAttack(EntityUid user, LightAttackEvent ev, Entity RaiseLocalEvent(target.Value, attackedEvent); var modifiedDamage = DamageSpecifier.ApplyModifierSets(damage + hitEvent.BonusDamage + attackedEvent.BonusDamage, hitEvent.ModifiersList); - var damageResult = Damageable.TryChangeDamage(target, modifiedDamage, origin:user); + var damageResult = Damageable.TryChangeDamage(target, modifiedDamage, origin: user, partMultiplier: component.ClickPartDamageMultiplier); if (damageResult != null && damageResult.Any()) { @@ -639,7 +640,7 @@ private bool DoHeavyAttack(EntityUid user, HeavyAttackEvent ev, EntityUid meleeU RaiseLocalEvent(entity, attackedEvent); var modifiedDamage = DamageSpecifier.ApplyModifierSets(damage + hitEvent.BonusDamage + attackedEvent.BonusDamage, hitEvent.ModifiersList); - var damageResult = Damageable.TryChangeDamage(entity, modifiedDamage, origin:user); + var damageResult = Damageable.TryChangeDamage(entity, modifiedDamage, origin: user, partMultiplier: component.HeavyPartDamageMultiplier); if (damageResult != null && damageResult.GetTotal() > FixedPoint2.Zero) { @@ -756,7 +757,7 @@ private void DoLungeAnimation(EntityUid user, EntityUid weapon, Angle angle, Map return; var invMatrix = TransformSystem.GetInvWorldMatrix(userXform); - var localPos = invMatrix.Transform(coordinates.Position); + var localPos = Vector2.Transform(coordinates.Position, invMatrix); if (localPos.LengthSquared() <= 0f) return; diff --git a/Content.Shared/Weapons/Ranged/Components/ActionGunComponent.cs b/Content.Shared/Weapons/Ranged/Components/ActionGunComponent.cs new file mode 100644 index 0000000000..112339efd7 --- /dev/null +++ b/Content.Shared/Weapons/Ranged/Components/ActionGunComponent.cs @@ -0,0 +1,37 @@ +using Content.Shared.Actions; +using Content.Shared.Weapons.Ranged.Systems; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Weapons.Ranged.Components; + +/// +/// Lets you shoot a gun using an action. +/// +[RegisterComponent, NetworkedComponent, Access(typeof(ActionGunSystem))] +public sealed partial class ActionGunComponent : Component +{ + /// + /// Action to create, must use . + /// + [DataField(required: true)] + public EntProtoId Action = string.Empty; + + [DataField] + public EntityUid? ActionEntity; + + /// + /// Prototype of gun entity to spawn. + /// Deleted when this component is removed. + /// + [DataField(required: true)] + public EntProtoId GunProto = string.Empty; + + [DataField] + public EntityUid? Gun; +} + +/// +/// Action event for to shoot at a position. +/// +public sealed partial class ActionGunShootEvent : WorldTargetActionEvent; diff --git a/Content.Shared/Weapons/Ranged/Components/GunRequiresWieldComponent.cs b/Content.Shared/Weapons/Ranged/Components/GunRequiresWieldComponent.cs index 2ae71334b4..fa3732209f 100644 --- a/Content.Shared/Weapons/Ranged/Components/GunRequiresWieldComponent.cs +++ b/Content.Shared/Weapons/Ranged/Components/GunRequiresWieldComponent.cs @@ -6,8 +6,13 @@ namespace Content.Shared.Weapons.Ranged.Components; /// /// Indicates that this gun requires wielding to be useable. /// -[RegisterComponent, NetworkedComponent, Access(typeof(WieldableSystem))] +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +[Access(typeof(WieldableSystem))] public sealed partial class GunRequiresWieldComponent : Component { + [DataField, AutoNetworkedField] + public TimeSpan LastPopup; + [DataField, AutoNetworkedField] + public TimeSpan PopupCooldown = TimeSpan.FromSeconds(1); } diff --git a/Content.Shared/Weapons/Ranged/Components/GunWieldBonusComponent.cs b/Content.Shared/Weapons/Ranged/Components/GunWieldBonusComponent.cs index ce96639e3c..522319ccbd 100644 --- a/Content.Shared/Weapons/Ranged/Components/GunWieldBonusComponent.cs +++ b/Content.Shared/Weapons/Ranged/Components/GunWieldBonusComponent.cs @@ -33,4 +33,7 @@ public sealed partial class GunWieldBonusComponent : Component /// [DataField, AutoNetworkedField] public Angle AngleIncrease = Angle.FromDegrees(0); + + [DataField] + public LocId? WieldBonusExamineMessage = "gunwieldbonus-component-examine"; } diff --git a/Content.Shared/Weapons/Ranged/Events/MuzzleFlashEvent.cs b/Content.Shared/Weapons/Ranged/Events/MuzzleFlashEvent.cs index 91f5e6cd86..10d4c2fe3c 100644 --- a/Content.Shared/Weapons/Ranged/Events/MuzzleFlashEvent.cs +++ b/Content.Shared/Weapons/Ranged/Events/MuzzleFlashEvent.cs @@ -11,15 +11,12 @@ public sealed class MuzzleFlashEvent : EntityEventArgs public NetEntity Uid; public string Prototype; - /// - /// Should the effect match the rotation of the entity. - /// - public bool MatchRotation; + public Angle Angle; - public MuzzleFlashEvent(NetEntity uid, string prototype, bool matchRotation = false) + public MuzzleFlashEvent(NetEntity uid, string prototype, Angle angle) { Uid = uid; Prototype = prototype; - MatchRotation = matchRotation; + Angle = angle; } } diff --git a/Content.Shared/Weapons/Ranged/Events/ShotAttemptedEvent.cs b/Content.Shared/Weapons/Ranged/Events/ShotAttemptedEvent.cs index 40925ad614..d61862bf1a 100644 --- a/Content.Shared/Weapons/Ranged/Events/ShotAttemptedEvent.cs +++ b/Content.Shared/Weapons/Ranged/Events/ShotAttemptedEvent.cs @@ -1,3 +1,5 @@ +using Content.Shared.Weapons.Ranged.Components; + namespace Content.Shared.Weapons.Ranged.Events; /// @@ -15,7 +17,7 @@ public record struct ShotAttemptedEvent /// /// The gun being shot. /// - public EntityUid Used; + public Entity Used; public bool Cancelled { get; private set; } diff --git a/Content.Shared/Weapons/Ranged/Systems/ActionGunSystem.cs b/Content.Shared/Weapons/Ranged/Systems/ActionGunSystem.cs new file mode 100644 index 0000000000..f3dfe8a2a0 --- /dev/null +++ b/Content.Shared/Weapons/Ranged/Systems/ActionGunSystem.cs @@ -0,0 +1,41 @@ +using Content.Shared.Actions; +using Content.Shared.Weapons.Ranged.Components; + +namespace Content.Shared.Weapons.Ranged.Systems; + +public sealed class ActionGunSystem : EntitySystem +{ + [Dependency] private readonly SharedActionsSystem _actions = default!; + [Dependency] private readonly SharedGunSystem _gun = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnShoot); + } + + private void OnMapInit(Entity ent, ref MapInitEvent args) + { + if (string.IsNullOrEmpty(ent.Comp.Action)) + return; + + _actions.AddAction(ent, ref ent.Comp.ActionEntity, ent.Comp.Action); + ent.Comp.Gun = Spawn(ent.Comp.GunProto); + } + + private void OnShutdown(Entity ent, ref ComponentShutdown args) + { + if (ent.Comp.Gun is {} gun) + QueueDel(gun); + } + + private void OnShoot(Entity ent, ref ActionGunShootEvent args) + { + if (TryComp(ent.Comp.Gun, out var gun)) + _gun.AttemptShoot(ent, ent.Comp.Gun.Value, gun, args.Target); + } +} + diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs index 3c5e5c7984..b714acefbd 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs @@ -138,7 +138,6 @@ private void OnShootRequest(RequestShootEvent msg, EntitySessionEventArgs args) return; gun.ShootCoordinates = GetCoordinates(msg.Coordinates); - Log.Debug($"Set shoot coordinates to {gun.ShootCoordinates}"); gun.Target = GetEntity(msg.Target); AttemptShoot(user.Value, ent, gun); } @@ -198,7 +197,6 @@ private void StopShooting(EntityUid uid, GunComponent gun) if (gun.ShotCounter == 0) return; - Log.Debug($"Stopped shooting {ToPrettyString(uid)}"); gun.ShotCounter = 0; gun.ShootCoordinates = null; gun.Target = null; @@ -251,7 +249,7 @@ private void AttemptShoot(EntityUid user, EntityUid gunUid, GunComponent gun) var prevention = new ShotAttemptedEvent { User = user, - Used = gunUid + Used = (gunUid, gun) }; RaiseLocalEvent(gunUid, ref prevention); if (prevention.Cancelled) @@ -473,7 +471,7 @@ protected void RemoveShootable(EntityUid uid) RemCompDeferred(uid); } - protected void MuzzleFlash(EntityUid gun, AmmoComponent component, EntityUid? user = null) + protected void MuzzleFlash(EntityUid gun, AmmoComponent component, Angle worldAngle, EntityUid? user = null) { var attemptEv = new GunMuzzleFlashAttemptEvent(); RaiseLocalEvent(gun, ref attemptEv); @@ -485,7 +483,7 @@ protected void MuzzleFlash(EntityUid gun, AmmoComponent component, EntityUid? us if (sprite == null) return; - var ev = new MuzzleFlashEvent(GetNetEntity(gun), sprite, user == gun); + var ev = new MuzzleFlashEvent(GetNetEntity(gun), sprite, worldAngle); CreateEffect(gun, ev, user); } @@ -534,7 +532,7 @@ public void RefreshModifiers(Entity gun) Dirty(gun); } - protected abstract void CreateEffect(EntityUid uid, MuzzleFlashEvent message, EntityUid? user = null); + protected abstract void CreateEffect(EntityUid gunUid, MuzzleFlashEvent message, EntityUid? user = null); /// /// Used for animated effects on the client. diff --git a/Content.Shared/Weapons/Reflect/ReflectComponent.cs b/Content.Shared/Weapons/Reflect/ReflectComponent.cs index 8e7b8975d9..5d8432ac77 100644 --- a/Content.Shared/Weapons/Reflect/ReflectComponent.cs +++ b/Content.Shared/Weapons/Reflect/ReflectComponent.cs @@ -21,17 +21,42 @@ public sealed partial class ReflectComponent : Component [ViewVariables(VVAccess.ReadWrite), DataField("reflects")] public ReflectType Reflects = ReflectType.Energy | ReflectType.NonEnergy; + [DataField("spread"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] + public Angle Spread = Angle.FromDegrees(45); + + [DataField("soundOnReflect")] + public SoundSpecifier? SoundOnReflect = new SoundPathSpecifier("/Audio/Weapons/Guns/Hits/laser_sear_wall.ogg"); + /// - /// Probability for a projectile to be reflected. + /// Is the deflection an innate power or something actively maintained? If true, this component grants a flat + /// deflection chance rather than a chance that degrades when moving/weightless/stunned/etc. + /// + [DataField] + public bool Innate = false; + + /// + /// Maximum probability for a projectile to be reflected. /// [DataField("reflectProb"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public float ReflectProb = 0.25f; - [DataField("spread"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] - public Angle Spread = Angle.FromDegrees(45); + /// + /// The maximum velocity a wielder can move at before losing effectiveness. + /// + [DataField] + public float VelocityBeforeNotMaxProb = 2.5f; // Walking speed for a human. Suitable for a weightless deflector like an e-sword. - [DataField("soundOnReflect")] - public SoundSpecifier? SoundOnReflect = new SoundPathSpecifier("/Audio/Weapons/Guns/Hits/laser_sear_wall.ogg"); + /// + /// The velocity a wielder has to be moving at to use the minimum effectiveness value. + /// + [DataField] + public float VelocityBeforeMinProb = 4.5f; // Sprinting speed for a human. Suitable for a weightless deflector like an e-sword. + + /// + /// Minimum probability for a projectile to be reflected. + /// + [DataField] + public float MinReflectProb = 0.1f; } [Flags] diff --git a/Content.Shared/Weapons/Reflect/ReflectSystem.cs b/Content.Shared/Weapons/Reflect/ReflectSystem.cs index 4a7c2f6b6a..36dbedb4cb 100644 --- a/Content.Shared/Weapons/Reflect/ReflectSystem.cs +++ b/Content.Shared/Weapons/Reflect/ReflectSystem.cs @@ -1,17 +1,20 @@ using System.Diagnostics.CodeAnalysis; using System.Numerics; using Content.Shared.Administration.Logs; +using Content.Shared.Alert; using Content.Shared.Audio; +using Content.Shared.Damage.Components; using Content.Shared.Database; +using Content.Shared.Gravity; using Content.Shared.Hands; using Content.Shared.Inventory; using Content.Shared.Inventory.Events; using Content.Shared.Item.ItemToggle.Components; using Content.Shared.Popups; using Content.Shared.Projectiles; +using Content.Shared.Standing; using Content.Shared.Weapons.Ranged.Components; using Content.Shared.Weapons.Ranged.Events; -using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Network; using Robust.Shared.Physics.Components; @@ -35,6 +38,9 @@ public sealed class ReflectSystem : EntitySystem [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly InventorySystem _inventorySystem = default!; + [Dependency] private readonly SharedGravitySystem _gravity = default!; + [Dependency] private readonly StandingStateSystem _standing = default!; + [Dependency] private readonly AlertsSystem _alerts = default!; public override void Initialize() { @@ -57,7 +63,7 @@ private void OnReflectUserHitscan(EntityUid uid, ReflectUserComponent component, if (args.Reflected) return; - foreach (var ent in _inventorySystem.GetHandOrInventoryEntities(uid, SlotFlags.All & ~SlotFlags.POCKET)) + foreach (var ent in _inventorySystem.GetHandOrInventoryEntities(uid, SlotFlags.WITHOUT_POCKET)) { if (!TryReflectHitscan(uid, ent, args.Shooter, args.SourceItem, args.Direction, out var dir)) continue; @@ -70,7 +76,7 @@ private void OnReflectUserHitscan(EntityUid uid, ReflectUserComponent component, private void OnReflectUserCollide(EntityUid uid, ReflectUserComponent component, ref ProjectileReflectAttemptEvent args) { - foreach (var ent in _inventorySystem.GetHandOrInventoryEntities(uid, SlotFlags.All & ~SlotFlags.POCKET)) + foreach (var ent in _inventorySystem.GetHandOrInventoryEntities(uid, SlotFlags.WITHOUT_POCKET)) { if (!TryReflectProjectile(uid, ent, args.ProjUid)) continue; @@ -91,15 +97,20 @@ private void OnReflectCollide(EntityUid uid, ReflectComponent component, ref Pro private bool TryReflectProjectile(EntityUid user, EntityUid reflector, EntityUid projectile, ProjectileComponent? projectileComp = null, ReflectComponent? reflect = null) { - if (!Resolve(reflector, ref reflect, false) || + // Do we have the components needed to try a reflect at all? + if ( + !Resolve(reflector, ref reflect, false) || !reflect.Enabled || !TryComp(projectile, out var reflective) || (reflect.Reflects & reflective.Reflective) == 0x0 || - !_random.Prob(reflect.ReflectProb) || - !TryComp(projectile, out var physics)) - { + !TryComp(projectile, out var physics) || + TryComp(reflector, out var staminaComponent) && staminaComponent.Critical || + _standing.IsDown(reflector) + ) + return false; + + if (!_random.Prob(CalcReflectChance(reflector, reflect))) return false; - } var rotation = _random.NextAngle(-reflect.Spread / 2, reflect.Spread / 2).Opposite(); var existingVelocity = _physics.GetMapLinearVelocity(projectile, component: physics); @@ -137,6 +148,34 @@ private bool TryReflectProjectile(EntityUid user, EntityUid reflector, EntityUid return true; } + private float CalcReflectChance(EntityUid reflector, ReflectComponent reflect) + { + /* + * The rules of deflection are as follows: + * If you innately reflect things via magic, biology etc., you always have a full chance. + * If you are standing up and standing still, you're prepared to deflect and have full chance. + * If you have velocity, your deflection chance depends on your velocity, clamped. + * If you are floating, your chance is the minimum value possible. + * You cannot deflect if you are knocked down or stunned. + */ + + if (reflect.Innate) + return reflect.ReflectProb; + + if (_gravity.IsWeightless(reflector)) + return reflect.MinReflectProb; + + if (!TryComp(reflector, out var reflectorPhysics)) + return reflect.ReflectProb; + + return MathHelper.Lerp( + reflect.MinReflectProb, + reflect.ReflectProb, + // Inverse progression between velocities fed in as progression between probabilities. We go high -> low so the output here needs to be _inverted_. + 1 - Math.Clamp((reflectorPhysics.LinearVelocity.Length() - reflect.VelocityBeforeNotMaxProb) / (reflect.VelocityBeforeMinProb - reflect.VelocityBeforeNotMaxProb), 0, 1) + ); + } + private void OnReflectHitscan(EntityUid uid, ReflectComponent component, ref HitScanReflectAttemptEvent args) { if (args.Reflected || @@ -162,7 +201,14 @@ private bool TryReflectHitscan( { if (!TryComp(reflector, out var reflect) || !reflect.Enabled || - !_random.Prob(reflect.ReflectProb)) + TryComp(reflector, out var staminaComponent) && staminaComponent.Critical || + _standing.IsDown(reflector)) + { + newDirection = null; + return false; + } + + if (!_random.Prob(CalcReflectChance(reflector, reflect))) { newDirection = null; return false; @@ -191,6 +237,9 @@ private void OnReflectEquipped(EntityUid uid, ReflectComponent component, GotEqu return; EnsureComp(args.Equipee); + + if (component.Enabled) + EnableAlert(args.Equipee); } private void OnReflectUnequipped(EntityUid uid, ReflectComponent comp, GotUnequippedEvent args) @@ -204,6 +253,9 @@ private void OnReflectHandEquipped(EntityUid uid, ReflectComponent component, Go return; EnsureComp(args.User); + + if (component.Enabled) + EnableAlert(args.User); } private void OnReflectHandUnequipped(EntityUid uid, ReflectComponent component, GotUnequippedHandEvent args) @@ -215,6 +267,11 @@ private void OnToggleReflect(EntityUid uid, ReflectComponent comp, ref ItemToggl { comp.Enabled = args.Activated; Dirty(uid, comp); + + if (comp.Enabled) + EnableAlert(uid); + else + DisableAlert(uid); } /// @@ -222,15 +279,28 @@ private void OnToggleReflect(EntityUid uid, ReflectComponent comp, ref ItemToggl /// private void RefreshReflectUser(EntityUid user) { - foreach (var ent in _inventorySystem.GetHandOrInventoryEntities(user, SlotFlags.All & ~SlotFlags.POCKET)) + foreach (var ent in _inventorySystem.GetHandOrInventoryEntities(user, SlotFlags.WITHOUT_POCKET)) { if (!HasComp(ent)) continue; EnsureComp(user); + EnableAlert(user); + return; } RemCompDeferred(user); + DisableAlert(user); + } + + private void EnableAlert(EntityUid alertee) + { + _alerts.ShowAlert(alertee, AlertType.Deflecting); + } + + private void DisableAlert(EntityUid alertee) + { + _alerts.ClearAlert(alertee, AlertType.Deflecting); } } diff --git a/Content.Shared/Whitelist/EntityWhitelist.cs b/Content.Shared/Whitelist/EntityWhitelist.cs index 942de2b0e8..895759be95 100644 --- a/Content.Shared/Whitelist/EntityWhitelist.cs +++ b/Content.Shared/Whitelist/EntityWhitelist.cs @@ -1,100 +1,67 @@ +using Content.Shared.Item; using Content.Shared.Tag; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; -namespace Content.Shared.Whitelist +namespace Content.Shared.Whitelist; + +/// +/// Used to determine whether an entity fits a certain whitelist. +/// Does not whitelist by prototypes, since that is undesirable; you're better off just adding a tag to all +/// entity prototypes that need to be whitelisted, and checking for that. +/// +/// +/// whitelist: +/// tags: +/// - Cigarette +/// - FirelockElectronics +/// components: +/// - Buckle +/// - AsteroidRock +/// sizes: +/// - Tiny +/// - Large +/// +[DataDefinition] +[Serializable, NetSerializable] +public sealed partial class EntityWhitelist { /// - /// Used to determine whether an entity fits a certain whitelist. - /// Does not whitelist by prototypes, since that is undesirable; you're better off just adding a tag to all - /// entity prototypes that need to be whitelisted, and checking for that. + /// Component names that are allowed in the whitelist. /// - /// - /// whitelist: - /// tags: - /// - Cigarette - /// - FirelockElectronics - /// components: - /// - Buckle - /// - AsteroidRock - /// - [DataDefinition] - [Serializable, NetSerializable] - public sealed partial class EntityWhitelist - { - /// - /// Component names that are allowed in the whitelist. - /// - [DataField("components")] public string[]? Components = null; - // TODO yaml validation - - [NonSerialized] - private List? _registrations = null; + [DataField] public string[]? Components; + // TODO yaml validation - /// - /// Tags that are allowed in the whitelist. - /// - [DataField("tags", customTypeSerializer:typeof(PrototypeIdListSerializer))] - public List? Tags = null; - - /// - /// If false, an entity only requires one of these components or tags to pass the whitelist. If true, an - /// entity requires to have ALL of these components and tags to pass. - /// - [DataField("requireAll")] - public bool RequireAll = false; + /// + /// Item sizes that are allowed in the whitelist. + /// + [DataField] + public List>? Sizes; - public void UpdateRegistrations() - { - if (Components == null) return; + [NonSerialized, Access(typeof(EntityWhitelistSystem))] + public List? Registrations; - var compfact = IoCManager.Resolve(); - _registrations = new List(); - foreach (var name in Components) - { - var availability = compfact.GetComponentAvailability(name); - if (compfact.TryGetRegistration(name, out var registration) - && availability == ComponentAvailability.Available) - { - _registrations.Add(registration); - } - else if (availability == ComponentAvailability.Unknown) - { - Logger.Warning($"Unknown component name {name} passed to EntityWhitelist!"); - } - } - } + /// + /// Tags that are allowed in the whitelist. + /// + [DataField] + public List>? Tags; - /// - /// Returns whether a given entity fits the whitelist. - /// - public bool IsValid(EntityUid uid, IEntityManager? entityManager = null) - { - if (Components != null && _registrations == null) - UpdateRegistrations(); + /// + /// If false, an entity only requires one of these components or tags to pass the whitelist. If true, an + /// entity requires to have ALL of these components and tags to pass. + /// The "Sizes" criteria will ignores this, since an item can only have one size. + /// + [DataField] + public bool RequireAll; - IoCManager.Resolve(ref entityManager); - if (_registrations != null) - { - foreach (var reg in _registrations) - { - if (entityManager.HasComponent(uid, reg.Type)) - { - if (!RequireAll) - return true; - } - else if (RequireAll) - return false; - } - } + [Obsolete("Use WhitelistSystem")] + public bool IsValid(EntityUid uid, IEntityManager? man = null) + { + var sys = man?.System() ?? + IoCManager.Resolve().GetEntitySystem(); - if (Tags != null && entityManager.TryGetComponent(uid, out TagComponent? tags)) - { - var tagSystem = entityManager.System(); - return RequireAll ? tagSystem.HasAllTags(tags, Tags) : tagSystem.HasAnyTag(tags, Tags); - } + return sys.IsValid(this, uid); - return false; - } } } diff --git a/Content.Shared/Whitelist/EntityWhitelistSystem.cs b/Content.Shared/Whitelist/EntityWhitelistSystem.cs new file mode 100644 index 0000000000..d73646b7e9 --- /dev/null +++ b/Content.Shared/Whitelist/EntityWhitelistSystem.cs @@ -0,0 +1,84 @@ +using System.Diagnostics.CodeAnalysis; +using Content.Shared.Item; +using Content.Shared.Tag; + +namespace Content.Shared.Whitelist; + +public sealed class EntityWhitelistSystem : EntitySystem +{ + [Dependency] private readonly IComponentFactory _factory = default!; + [Dependency] private readonly TagSystem _tag = default!; + + private EntityQuery _itemQuery; + + public override void Initialize() + { + base.Initialize(); + _itemQuery = GetEntityQuery(); + } + + /// + public bool IsValid(EntityWhitelist list, [NotNullWhen(true)] EntityUid? uid) + { + return uid != null && IsValid(list, uid.Value); + } + + /// + /// Checks whether a given entity satisfies a whitelist. + /// + public bool IsValid(EntityWhitelist list, EntityUid uid) + { + if (list.Components != null) + EnsureRegistrations(list); + + if (list.Registrations != null) + { + foreach (var reg in list.Registrations) + { + if (HasComp(uid, reg.Type)) + { + if (!list.RequireAll) + return true; + } + else if (list.RequireAll) + return false; + } + } + + if (list.Sizes != null && _itemQuery.TryComp(uid, out var itemComp)) + { + if (list.Sizes.Contains(itemComp.Size)) + return true; + } + + if (list.Tags != null) + { + return list.RequireAll + ? _tag.HasAllTags(uid, list.Tags) + : _tag.HasAnyTag(uid, list.Tags); + } + + return list.RequireAll; + } + + private void EnsureRegistrations(EntityWhitelist list) + { + if (list.Components == null) + return; + + list.Registrations = new List(); + foreach (var name in list.Components) + { + var availability = _factory.GetComponentAvailability(name); + if (_factory.TryGetRegistration(name, out var registration) + && availability == ComponentAvailability.Available) + { + list.Registrations.Add(registration); + } + else if (availability == ComponentAvailability.Unknown) + { + Log.Warning($"Unknown component name {name} passed to EntityWhitelist!"); + } + } + } +} diff --git a/Content.Shared/Wieldable/Components/WieldableComponent.cs b/Content.Shared/Wieldable/Components/WieldableComponent.cs index 4a50b93072..5dc6abbbbe 100644 --- a/Content.Shared/Wieldable/Components/WieldableComponent.cs +++ b/Content.Shared/Wieldable/Components/WieldableComponent.cs @@ -26,6 +26,13 @@ public sealed partial class WieldableComponent : Component [AutoNetworkedField, DataField("wielded")] public bool Wielded = false; + /// + /// Whether using the item inhand while wielding causes the item to unwield. + /// Unwielding can conflict with other inhand actions. + /// + [DataField] + public bool UnwieldOnUse = true; + [DataField("wieldedInhandPrefix")] public string? WieldedInhandPrefix = "wielded"; diff --git a/Content.Shared/Wieldable/WieldableSystem.cs b/Content.Shared/Wieldable/WieldableSystem.cs index 6bd406c1ca..95fc69e061 100644 --- a/Content.Shared/Wieldable/WieldableSystem.cs +++ b/Content.Shared/Wieldable/WieldableSystem.cs @@ -1,3 +1,5 @@ +using System.Linq; +using Content.Shared.Examine; using Content.Shared.Hands; using Content.Shared.Hands.Components; using Content.Shared.Hands.EntitySystems; @@ -15,7 +17,7 @@ using Content.Shared.Weapons.Ranged.Systems; using Content.Shared.Wieldable.Components; using Robust.Shared.Audio.Systems; -using Robust.Shared.Player; +using Robust.Shared.Timing; namespace Content.Shared.Wieldable; @@ -29,6 +31,7 @@ public sealed class WieldableSystem : EntitySystem [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly UseDelaySystem _delay = default!; [Dependency] private readonly SharedGunSystem _gun = default!; + [Dependency] private readonly IGameTiming _timing = default!; public override void Initialize() { @@ -39,12 +42,14 @@ public override void Initialize() SubscribeLocalEvent(OnItemLeaveHand); SubscribeLocalEvent(OnVirtualItemDeleted); SubscribeLocalEvent>(AddToggleWieldVerb); + SubscribeLocalEvent(OnDeselectWieldable); SubscribeLocalEvent(OnMeleeAttempt); - SubscribeLocalEvent(OnShootAttempt); + SubscribeLocalEvent(OnShootAttempt); SubscribeLocalEvent(OnGunWielded); SubscribeLocalEvent(OnGunUnwielded); SubscribeLocalEvent(OnGunRefreshModifiers); + SubscribeLocalEvent(OnExamine); SubscribeLocalEvent(OnGetMeleeDamage); } @@ -59,16 +64,21 @@ private void OnMeleeAttempt(EntityUid uid, MeleeRequiresWieldComponent component } } - private void OnShootAttempt(EntityUid uid, GunRequiresWieldComponent component, ref AttemptShootEvent args) + private void OnShootAttempt(EntityUid uid, GunRequiresWieldComponent component, ref ShotAttemptedEvent args) { if (TryComp(uid, out var wieldable) && !wieldable.Wielded) { - args.Cancelled = true; + args.Cancel(); - if (!HasComp(uid) && !HasComp(uid)) + var time = _timing.CurTime; + if (time > component.LastPopup + component.PopupCooldown && + !HasComp(uid) && + !HasComp(uid)) { - args.Message = Loc.GetString("wieldable-component-requires", ("item", uid)); + component.LastPopup = time; + var message = Loc.GetString("wieldable-component-requires", ("item", uid)); + _popupSystem.PopupClient(message, args.Used, args.User); } } } @@ -83,6 +93,15 @@ private void OnGunWielded(EntityUid uid, GunWieldBonusComponent component, ref I _gun.RefreshModifiers(uid); } + private void OnDeselectWieldable(EntityUid uid, WieldableComponent component, HandDeselectedEvent args) + { + if (!component.Wielded || + _handsSystem.EnumerateHands(args.User).Count() > 2) + return; + + TryUnwield(uid, component, args.User); + } + private void OnGunRefreshModifiers(Entity bonus, ref GunRefreshModifiersEvent args) { if (TryComp(bonus, out WieldableComponent? wield) && @@ -95,6 +114,12 @@ private void OnGunRefreshModifiers(Entity bonus, ref Gun } } + private void OnExamine(EntityUid uid, GunWieldBonusComponent component, ref ExaminedEvent args) + { + if (component.WieldBonusExamineMessage != null) + args.PushText(Loc.GetString(component.WieldBonusExamineMessage)); + } + private void AddToggleWieldVerb(EntityUid uid, WieldableComponent component, GetVerbsEvent args) { if (args.Hands == null || !args.CanAccess || !args.CanInteract) @@ -125,7 +150,7 @@ private void OnUseInHand(EntityUid uid, WieldableComponent component, UseInHandE if (!component.Wielded) args.Handled = TryWield(uid, component, args.User); - else + else if (component.UnwieldOnUse) args.Handled = TryUnwield(uid, component, args.User); } @@ -147,7 +172,7 @@ public bool CanWield(EntityUid uid, WieldableComponent component, EntityUid user return false; } - if (hands.CountFreeHands() < component.FreeHandsRequired) + if (_handsSystem.CountFreeableHands((user, hands)) < component.FreeHandsRequired) { if (!quiet) { @@ -188,9 +213,19 @@ public bool TryWield(EntityUid used, WieldableComponent component, EntityUid use if (component.WieldSound != null) _audioSystem.PlayPredicted(component.WieldSound, used, user); + var virtuals = new List(); for (var i = 0; i < component.FreeHandsRequired; i++) { - _virtualItemSystem.TrySpawnVirtualItemInHand(used, user); + if (_virtualItemSystem.TrySpawnVirtualItemInHand(used, user, out var virtualItem, true)) + { + virtuals.Add(virtualItem.Value); + continue; + } + + foreach (var existingVirtual in virtuals) + QueueDel(existingVirtual); + + return false; } if (TryComp(used, out UseDelayComponent? useDelay) diff --git a/Content.Shared/Xenoarchaeology/Equipment/SharedArtifactAnalyzer.cs b/Content.Shared/Xenoarchaeology/Equipment/SharedArtifactAnalyzer.cs index cecacceda9..07f2a60c84 100644 --- a/Content.Shared/Xenoarchaeology/Equipment/SharedArtifactAnalyzer.cs +++ b/Content.Shared/Xenoarchaeology/Equipment/SharedArtifactAnalyzer.cs @@ -30,50 +30,40 @@ public sealed class AnalysisConsoleExtractButtonPressedMessage : BoundUserInterf } [Serializable, NetSerializable] -public sealed class AnalysisConsoleScanUpdateState : BoundUserInterfaceState +public sealed class AnalysisConsoleBiasButtonPressedMessage(bool isDown) : BoundUserInterfaceMessage { - public NetEntity? Artifact; - - public bool AnalyzerConnected; - - public bool ServerConnected; - - public bool CanScan; - - public bool CanPrint; - - public FormattedMessage? ScanReport; - - public bool Scanning; - - public bool Paused; - - public TimeSpan? StartTime; - - public TimeSpan? AccumulatedRunTime; - - public TimeSpan? TotalTime; - - public int PointAmount; - - public AnalysisConsoleScanUpdateState(NetEntity? artifact, bool analyzerConnected, bool serverConnected, bool canScan, bool canPrint, - FormattedMessage? scanReport, bool scanning, bool paused, TimeSpan? startTime, TimeSpan? accumulatedRunTime, TimeSpan? totalTime, int pointAmount) - { - Artifact = artifact; - AnalyzerConnected = analyzerConnected; - ServerConnected = serverConnected; - CanScan = canScan; - CanPrint = canPrint; - - ScanReport = scanReport; - - Scanning = scanning; - Paused = paused; - - StartTime = startTime; - AccumulatedRunTime = accumulatedRunTime; - TotalTime = totalTime; + public bool IsDown = isDown; +} - PointAmount = pointAmount; - } +[Serializable, NetSerializable] +public sealed class AnalysisConsoleUpdateState( + NetEntity? artifact, + bool analyzerConnected, + bool serverConnected, + bool canScan, + bool canPrint, + FormattedMessage? scanReport, + bool scanning, + bool paused, + TimeSpan? startTime, + TimeSpan? accumulatedRunTime, + TimeSpan? totalTime, + int pointAmount, + bool isTraversalDown +) + : BoundUserInterfaceState +{ + public NetEntity? Artifact = artifact; + public bool AnalyzerConnected = analyzerConnected; + public bool ServerConnected = serverConnected; + public bool CanScan = canScan; + public bool CanPrint = canPrint; + public FormattedMessage? ScanReport = scanReport; + public bool Scanning = scanning; + public bool Paused = paused; + public TimeSpan? StartTime = startTime; + public TimeSpan? AccumulatedRunTime = accumulatedRunTime; + public TimeSpan? TotalTime = totalTime; + public int PointAmount = pointAmount; + public bool IsTraversalDown = isTraversalDown; } diff --git a/Content.Shared/Zombies/ZombieComponent.cs b/Content.Shared/Zombies/ZombieComponent.cs index be3fdbdd01..3673a2c51d 100644 --- a/Content.Shared/Zombies/ZombieComponent.cs +++ b/Content.Shared/Zombies/ZombieComponent.cs @@ -27,7 +27,7 @@ public sealed partial class ZombieComponent : Component, IAntagStatusIconCompone /// being invincible by bundling up. /// [ViewVariables(VVAccess.ReadWrite)] - public float MinZombieInfectionChance = 0.50f; + public float MinZombieInfectionChance = 0.25f; [ViewVariables(VVAccess.ReadWrite)] public float ZombieMovementSpeedDebuff = 0.70f; diff --git a/Content.Tests/Shared/Alert/ServerAlertsComponentTests.cs b/Content.Tests/Shared/Alert/ServerAlertsComponentTests.cs deleted file mode 100644 index 405c0ec89e..0000000000 --- a/Content.Tests/Shared/Alert/ServerAlertsComponentTests.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System.IO; -using Content.Server.Alert; -using Content.Shared.Alert; -using NUnit.Framework; -using Robust.Shared.GameObjects; -using Robust.Shared.GameStates; -using Robust.Shared.IoC; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.Manager; - -namespace Content.Tests.Shared.Alert -{ - [TestFixture] - [TestOf(typeof(AlertsComponent))] - public sealed class ServerAlertsComponentTests : ContentUnitTest - { - const string PROTOTYPES = @" -- type: alert - id: LowPressure - category: Pressure - icon: /Textures/Interface/Alerts/Pressure/lowpressure.png - -- type: alert - id: HighPressure - category: Pressure - icon: /Textures/Interface/Alerts/Pressure/highpressure.png -"; - - [Test] - [Ignore("There is no way to load extra Systems in a unit test, fixing RobustUnitTest is out of scope.")] - public void ShowAlerts() - { - // this is kind of unnecessary because there's integration test coverage of Alert components - // but wanted to keep it anyway to see what's possible w.r.t. testing components - // in a unit test - - var entManager = IoCManager.Resolve(); - IoCManager.Resolve().Initialize(); - var prototypeManager = IoCManager.Resolve(); - prototypeManager.Initialize(); - var factory = IoCManager.Resolve(); - factory.RegisterClass(); - prototypeManager.LoadFromStream(new StringReader(PROTOTYPES)); - prototypeManager.ResolveResults(); - - var entSys = entManager.EntitySysManager; - entSys.LoadExtraSystemType(); - - var alertsComponent = new AlertsComponent(); - alertsComponent = IoCManager.InjectDependencies(alertsComponent); - - Assert.That(EntitySystem.Get().TryGet(AlertType.LowPressure, out var lowpressure)); - Assert.That(EntitySystem.Get().TryGet(AlertType.HighPressure, out var highpressure)); - - EntitySystem.Get().ShowAlert(alertsComponent.Owner, AlertType.LowPressure, null, null); - - var getty = new ComponentGetState(); - entManager.EventBus.RaiseComponentEvent(alertsComponent, getty); - - var alertState = (AlertsComponent.AlertsComponent_AutoState) getty.State!; - Assert.That(alertState, Is.Not.Null); - Assert.That(alertState.Alerts.Count, Is.EqualTo(1)); - Assert.That(alertState.Alerts.ContainsKey(lowpressure.AlertKey)); - - EntitySystem.Get().ShowAlert(alertsComponent.Owner, AlertType.HighPressure, null, null); - - // Lazy - entManager.EventBus.RaiseComponentEvent(alertsComponent, getty); - alertState = (AlertsComponent.AlertsComponent_AutoState) getty.State!; - Assert.That(alertState.Alerts.Count, Is.EqualTo(1)); - Assert.That(alertState.Alerts.ContainsKey(highpressure.AlertKey)); - - EntitySystem.Get().ClearAlertCategory(alertsComponent.Owner, AlertCategory.Pressure); - - entManager.EventBus.RaiseComponentEvent(alertsComponent, getty); - alertState = (AlertsComponent.AlertsComponent_AutoState) getty.State!; - Assert.That(alertState.Alerts.Count, Is.EqualTo(0)); - } - } -} diff --git a/Content.Tests/Shared/DamageTest.cs b/Content.Tests/Shared/DamageTest.cs index 11b810bf36..88beca8841 100644 --- a/Content.Tests/Shared/DamageTest.cs +++ b/Content.Tests/Shared/DamageTest.cs @@ -168,45 +168,57 @@ public void ModifierSetTest() private string _damagePrototypes = @" - type: damageType id: Blunt + name: damage-type-blunt - type: damageType id: Slash + name: damage-type-slash - type: damageType id: Piercing + name: damage-type-piercing - type: damageType id: Heat + name: damage-type-heat - type: damageType id: Shock + name: damage-type-shock - type: damageType id: Cold + name: damage-type-cold # Poison damage. Generally caused by various reagents being metabolised. - type: damageType id: Poison + name: damage-type-poison - type: damageType id: Radiation + name: damage-type-radiation # Damage due to being unable to breathe. # Represents not enough oxygen (or equivalent) getting to the blood. # Usually healed automatically if entity can breathe - type: damageType id: Asphyxiation + name: damage-type-asphyxiation # Damage representing not having enough blood. # Represents there not enough blood to supply oxygen (or equivalent). - type: damageType id: Bloodloss + name: damage-type-bloodloss - type: damageType id: Cellular + name: damage-type-cellular - type: damageGroup id: Brute + name: damage-group-brute damageTypes: - Blunt - Slash @@ -214,6 +226,7 @@ public void ModifierSetTest() - type: damageGroup id: Burn + name: damage-group-burn damageTypes: - Heat - Shock @@ -225,6 +238,7 @@ public void ModifierSetTest() # bloodloss, not this whole group, unless you have a wonder drug that affects both. - type: damageGroup id: Airloss + name: damage-group-airloss damageTypes: - Asphyxiation - Bloodloss @@ -233,12 +247,14 @@ public void ModifierSetTest() # Though there are probably some radioactive poisons. - type: damageGroup id: Toxin + name: damage-group-toxin damageTypes: - Poison - Radiation - type: damageGroup id: Genetic + name: damage-group-genetic damageTypes: - Cellular diff --git a/Content.YAMLLinter/Program.cs b/Content.YAMLLinter/Program.cs index 78867fcb8a..7f0b740fe8 100644 --- a/Content.YAMLLinter/Program.cs +++ b/Content.YAMLLinter/Program.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Threading.Tasks; using Content.IntegrationTests; using Robust.Shared.Prototypes; +using Robust.Shared.Reflection; using Robust.Shared.Serialization.Markdown.Validation; using Robust.Shared.Timing; using Robust.Shared.Utility; @@ -103,9 +105,13 @@ await instance.WaitPost(() => return (yamlErrors, fieldErrors); } - public static async Task<(Dictionary> YamlErrors , List FieldErrors)> + public static async Task<(Dictionary> YamlErrors, List FieldErrors)> RunValidation() { + var (clientAssemblies, serverAssemblies) = await GetClientServerAssemblies(); + var serverTypes = serverAssemblies.SelectMany(n => n.GetTypes()).Select(t => t.Name).ToHashSet(); + var clientTypes = clientAssemblies.SelectMany(n => n.GetTypes()).Select(t => t.Name).ToHashSet(); + var yamlErrors = new Dictionary>(); var serverErrors = await ValidateServer(); @@ -117,9 +123,18 @@ await instance.WaitPost(() => var newErrors = val.Where(n => n.AlwaysRelevant).ToHashSet(); // We include sometimes-relevant errors if they exist both for the client & server - if (clientErrors.Item1.TryGetValue(key, out var clientVal)) + if (clientErrors.YamlErrors.TryGetValue(key, out var clientVal)) newErrors.UnionWith(val.Intersect(clientVal)); + // Include any errors that relate to server-only types + foreach (var errorNode in val) + { + if (errorNode is FieldNotFoundErrorNode fieldNotFoundNode && !clientTypes.Contains(fieldNotFoundNode.FieldType.Name)) + { + newErrors.Add(errorNode); + } + } + if (newErrors.Count != 0) yamlErrors[key] = newErrors; } @@ -135,6 +150,15 @@ await instance.WaitPost(() => errors.UnionWith(val.Where(n => n.AlwaysRelevant)); else yamlErrors[key] = newErrors; + + // Include any errors that relate to client-only types + foreach (var errorNode in val) + { + if (errorNode is FieldNotFoundErrorNode fieldNotFoundNode && !serverTypes.Contains(fieldNotFoundNode.FieldType.Name)) + { + newErrors.Add(errorNode); + } + } } // Finally, combine the prototype ID field errors. @@ -145,5 +169,23 @@ await instance.WaitPost(() => return (yamlErrors, fieldErrors); } + + private static async Task<(Assembly[] clientAssemblies, Assembly[] serverAssemblies)> + GetClientServerAssemblies() + { + await using var pair = await PoolManager.GetServerClient(); + + var result = (GetAssemblies(pair.Client), GetAssemblies(pair.Server)); + + await pair.CleanReturnAsync(); + + return result; + + Assembly[] GetAssemblies(RobustIntegrationTest.IntegrationInstance instance) + { + var refl = instance.ResolveDependency(); + return refl.Assemblies.ToArray(); + } + } } } diff --git a/LEGAL.md b/LEGAL.md index 9876cbd366..31dace2a11 100644 --- a/LEGAL.md +++ b/LEGAL.md @@ -1,16 +1,29 @@ -# Legal Information +# Legal Info ## Copyright The Authors retain all copyright to their respective work submitted here. -## License +## Licenses -Content contributed to this repository after commit 87c70a89a67d0521a56388e6b1c3f2cb947943e4 (`17 February 2024 23:00:00 UTC`) is licensed under the GNU Affero General Public License version 3.0 unless otherwise stated. See [LICENSE-AGPLv3](./LICENSE-AGPLv3.txt). +### Code -Content contributed to this repository before commit 87c70a89a67d0521a56388e6b1c3f2cb947943e4 (`17 February 2024 23:00:00 UTC`) is licensed under the MIT license unless otherwise stated. See [LICENSE-MIT](./LICENSE-MIT.txt). +> [!NOTE] +> If you want MIT-licensed code, please visit the Space Wizards' repository [here](https://github.com/space-wizards/space-station-14/) instead. +> If they do not have something you want as MIT but we do as AGPLv3, you may ask the authors of the code to relicense it for you. +> +> If you can not figure out the license of something, or who the author(s) of a feature are, please ask in our [Discord](https://discord.gg/X4QEXxUrsJ). -Most assets are licensed under [CC-BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/) unless stated otherwise. Assets have their license and the copyright in the metadata file. -[Example](https://github.com/Simple-Station/Parkstation-Friendly-Chainsaw/blob/master/Resources/Textures/Objects/Tools/crowbar.rsi/meta.json). +Content contributed to this repository after commit [87c70a8](https://github.com/Simple-Station/Einstein-Engines/commit/87c70a89a67d0521a56388e6b1c3f2cb947943e4) is licensed under the GNU Affero General Public License version 3.0 unless otherwise stated. +See [LICENSE-AGPLv3](./LICENSE-AGPLv3.txt). +Content contributed to this repository before the aforementioned commit is MIT-licensed unless otherwise stated. +See [LICENSE-MIT](./LICENSE-MIT.txt). +[87c70a8](https://github.com/Simple-Station/Einstein-Engines/commit/87c70a89a67d0521a56388e6b1c3f2cb947943e4) was pushed on February 17th 2024 at 21:48 UTC. + +### Assets + +Most assets are licensed under [CC-BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/) unless stated otherwise. +Assets have their license and the copyright in the metadata file. +[Example](./Resources/Textures/Objects/Tools/crowbar.rsi/meta.json). Note that some assets are licensed under the non-commercial [CC-BY-NC-SA 3.0](https://creativecommons.org/licenses/by-nc-sa/3.0/) or similar non-commercial licenses and will need to be removed if you wish to use this project commercially. diff --git a/README.md b/README.md index 18a642c46a..041647470a 100644 --- a/README.md +++ b/README.md @@ -38,15 +38,9 @@ We provide some scripts shown below to make the job easier. ### Build Dependencies -> - DOTNET SDK 8.0 or higher - > - Git > - .NET SDK 8.0.100 -> - Git (needed) -> - Python 3.7 or higher -> - Visual Studio Code (**NOT** Visual Studio) - ### Windows > 1. Clone this repository @@ -70,13 +64,4 @@ We provide some scripts shown below to make the job easier. ## License -Content contributed to this repository after commit 87c70a89a67d0521a56388e6b1c3f2cb947943e4 (`17 February 2024 23:00:00 UTC`) is licensed under the GNU Affero General Public License version 3.0 unless otherwise stated. -See [LICENSE-AGPLv3](./LICENSE-AGPLv3.txt). - -Content contributed to this repository before commit 87c70a89a67d0521a56388e6b1c3f2cb947943e4 (`17 February 2024 23:00:00 UTC`) is licensed under the MIT license unless otherwise stated. -See [LICENSE-MIT](./LICENSE-MIT.txt). - -Most assets are licensed under [CC-BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/) unless stated otherwise. Assets have their license and the copyright in the metadata file. -[Example](./Resources/Textures/Objects/Tools/crowbar.rsi/meta.json). - -Note that some assets are licensed under the non-commercial [CC-BY-NC-SA 3.0](https://creativecommons.org/licenses/by-nc-sa/3.0/) or similar non-commercial licenses and will need to be removed if you wish to use this project commercially. +Please read the [LEGAL.md](./LEGAL.md) file for information on the licenses of the code and assets in this repository. diff --git a/Resources/Audio/Admin/adminhelp_old.ogg b/Resources/Audio/Admin/adminhelp_old.ogg new file mode 100644 index 0000000000..704c0fd6d2 Binary files /dev/null and b/Resources/Audio/Admin/adminhelp_old.ogg differ diff --git a/Resources/Audio/Ambience/attributions.yml b/Resources/Audio/Ambience/attributions.yml index 01d1f1c92e..931d27f22e 100644 --- a/Resources/Audio/Ambience/attributions.yml +++ b/Resources/Audio/Ambience/attributions.yml @@ -121,3 +121,9 @@ license: "CC0-1.0" copyright: "Created by dimbark1, edited and converted to mono by TheShuEd" source: "https://freesound.org/people/dimbark1/sounds/316797/" + +- files: ["wisp_ambience.ogg"] + license: "CC-BY-4.0" + copyright: "Created by AlienXXX, touched up by Rane" + source: "https://freesound.org/people/AlienXXX/sounds/123647/" + diff --git a/Resources/Audio/Ambience/wisp_ambience.ogg b/Resources/Audio/Ambience/wisp_ambience.ogg new file mode 100644 index 0000000000..83f823ec81 Binary files /dev/null and b/Resources/Audio/Ambience/wisp_ambience.ogg differ diff --git a/Resources/Audio/Animals/attributions.yml b/Resources/Audio/Animals/attributions.yml index c34832a807..7fd7e8b2e7 100644 --- a/Resources/Audio/Animals/attributions.yml +++ b/Resources/Audio/Animals/attributions.yml @@ -7,7 +7,7 @@ license: "CC-BY-3.0" copyright: "Modified from 'Meow 4.wav' by freesound user 'TRNGLE. The original audio was trimmed, split to mono, and converted from WAV to OGG format" source: "https://freesound.org/people/TRNGLE/sounds/368006/" - + - files: ["cat_meow2.ogg"] license: "CC-BY-3.0" copyright: "Created by freesound user 'TRNGLE. The original audio split to mono, and converted from WAV to OGG format" @@ -117,24 +117,42 @@ license: "CC-BY-4.0" copyright: "Audio is recorded/created by Pfranzen 'FreeSound.org'. The original audio was trimmed and renamed" source: "https://freesound.org/people/pfranzen/sounds/322744/" - + - files: ["dog_bark1.ogg"] license: "CC0-1.0" copyright: "Audio is recorded/created by KFerentchak 'FreeSound.org'. The original audio was trimmed and renamed" - source: "https://freesound.org/people/KFerentchak/sounds/235912/" - + source: "https://freesound.org/people/KFerentchak/sounds/235912/" + - files: ["dog_bark2.ogg"] license: "CC0-1.0" copyright: "Audio is recorded/created by KFerentchak 'FreeSound.org'. The original audio was trimmed and renamed" - source: "https://freesound.org/people/KFerentchak/sounds/235912/" - + source: "https://freesound.org/people/KFerentchak/sounds/235912/" + - files: ["dog_bark3.ogg"] license: "CC0-1.0" copyright: "Audio is recorded/created by KFerentchak 'FreeSound.org'. The original audio was trimmed and renamed" source: "https://freesound.org/people/KFerentchak/sounds/235912/" - + - files: ["nymph_chirp.ogg"] license: "CC-BY-SA-3.0" copyright: "Taken from ParadiseSS13" source: "https://github.com/ParadiseSS13/Paradise/commit/a34f1054cef5a44a67fdac3b67b811137c6071dd" - \ No newline at end of file + +- files: + - fox1.ogg + - fox2.ogg + - fox3.ogg + - fox4.ogg + - fox5.ogg + - fox6.ogg + - fox7.ogg + - fox8.ogg + - fox9.ogg + - fox10.ogg + - fox11.ogg + - fox12.ogg + - fox13.ogg + - fox14.ogg + copyright: "Created by fujiwaranao" + license: "CC-BY-NC-SA-4.0" + source: "https://github.com/space-wizards/space-station-14/pull/27578" diff --git a/Resources/Audio/Animals/fox1.ogg b/Resources/Audio/Animals/fox1.ogg new file mode 100644 index 0000000000..40fe16cc52 Binary files /dev/null and b/Resources/Audio/Animals/fox1.ogg differ diff --git a/Resources/Audio/Animals/fox10.ogg b/Resources/Audio/Animals/fox10.ogg new file mode 100644 index 0000000000..2a9e156dc5 Binary files /dev/null and b/Resources/Audio/Animals/fox10.ogg differ diff --git a/Resources/Audio/Animals/fox11.ogg b/Resources/Audio/Animals/fox11.ogg new file mode 100644 index 0000000000..d294137dc1 Binary files /dev/null and b/Resources/Audio/Animals/fox11.ogg differ diff --git a/Resources/Audio/Animals/fox12.ogg b/Resources/Audio/Animals/fox12.ogg new file mode 100644 index 0000000000..c413af81c7 Binary files /dev/null and b/Resources/Audio/Animals/fox12.ogg differ diff --git a/Resources/Audio/Animals/fox13.ogg b/Resources/Audio/Animals/fox13.ogg new file mode 100644 index 0000000000..197a9e4339 Binary files /dev/null and b/Resources/Audio/Animals/fox13.ogg differ diff --git a/Resources/Audio/Animals/fox14.ogg b/Resources/Audio/Animals/fox14.ogg new file mode 100644 index 0000000000..1d9c99889d Binary files /dev/null and b/Resources/Audio/Animals/fox14.ogg differ diff --git a/Resources/Audio/Animals/fox2.ogg b/Resources/Audio/Animals/fox2.ogg new file mode 100644 index 0000000000..7aeb7da911 Binary files /dev/null and b/Resources/Audio/Animals/fox2.ogg differ diff --git a/Resources/Audio/Animals/fox3.ogg b/Resources/Audio/Animals/fox3.ogg new file mode 100644 index 0000000000..561b313f41 Binary files /dev/null and b/Resources/Audio/Animals/fox3.ogg differ diff --git a/Resources/Audio/Animals/fox4.ogg b/Resources/Audio/Animals/fox4.ogg new file mode 100644 index 0000000000..6805d0e848 Binary files /dev/null and b/Resources/Audio/Animals/fox4.ogg differ diff --git a/Resources/Audio/Animals/fox5.ogg b/Resources/Audio/Animals/fox5.ogg new file mode 100644 index 0000000000..5aefa939cc Binary files /dev/null and b/Resources/Audio/Animals/fox5.ogg differ diff --git a/Resources/Audio/Animals/fox6.ogg b/Resources/Audio/Animals/fox6.ogg new file mode 100644 index 0000000000..d23cca5ff2 Binary files /dev/null and b/Resources/Audio/Animals/fox6.ogg differ diff --git a/Resources/Audio/Animals/fox7.ogg b/Resources/Audio/Animals/fox7.ogg new file mode 100644 index 0000000000..d4da91e73b Binary files /dev/null and b/Resources/Audio/Animals/fox7.ogg differ diff --git a/Resources/Audio/Animals/fox8.ogg b/Resources/Audio/Animals/fox8.ogg new file mode 100644 index 0000000000..52337a640b Binary files /dev/null and b/Resources/Audio/Animals/fox8.ogg differ diff --git a/Resources/Audio/Animals/fox9.ogg b/Resources/Audio/Animals/fox9.ogg new file mode 100644 index 0000000000..eb161ccdaf Binary files /dev/null and b/Resources/Audio/Animals/fox9.ogg differ diff --git a/Resources/Audio/Announcements/attributions.yml b/Resources/Audio/Announcements/attributions.yml index 879bfe7f60..774b0da5a0 100644 --- a/Resources/Audio/Announcements/attributions.yml +++ b/Resources/Audio/Announcements/attributions.yml @@ -7,3 +7,8 @@ license: "CC-BY-SA-3.0" copyright: "Paradise, volume and pitch changed, merged with redalert.ogg" source: "https://github.com/ParadiseSS13/Paradise/blob/07b26ee6b4a11a0607986d322ee007020569feae/sound/effects/siren.ogg" + +- files: ["intercept.ogg"] + license: "CC-BY-SA-3.0" + copyright: "Taken from tgstation" + source: "https://github.com/tgstation/tgstation/blob/95731342b97167d7883ff091d389f79c36442ee6/sound/ai/default/intercept.ogg" diff --git a/Resources/Audio/Announcements/intercept.ogg b/Resources/Audio/Announcements/intercept.ogg new file mode 100644 index 0000000000..3569a07d40 Binary files /dev/null and b/Resources/Audio/Announcements/intercept.ogg differ diff --git a/Resources/Audio/DeltaV/Effects/attributions.yml b/Resources/Audio/DeltaV/Effects/attributions.yml new file mode 100644 index 0000000000..6a4a98a7ee --- /dev/null +++ b/Resources/Audio/DeltaV/Effects/attributions.yml @@ -0,0 +1,4 @@ +- files: ["clang2.ogg"] + license: "CC-BY-NC-3.0" + copyright: "Freesound user BristolStories" + source: "https://freesound.org/people/BristolStories/sounds/65915/" diff --git a/Resources/Audio/DeltaV/Effects/clang2.ogg b/Resources/Audio/DeltaV/Effects/clang2.ogg new file mode 100644 index 0000000000..74f0909a9e Binary files /dev/null and b/Resources/Audio/DeltaV/Effects/clang2.ogg differ diff --git a/Resources/Audio/DeltaV/Jukebox/DOS=HIGH,_UMB.ogg b/Resources/Audio/DeltaV/Jukebox/DOS=HIGH,_UMB.ogg new file mode 100644 index 0000000000..b16a328b13 Binary files /dev/null and b/Resources/Audio/DeltaV/Jukebox/DOS=HIGH,_UMB.ogg differ diff --git a/Resources/Audio/DeltaV/Jukebox/Patricia_Taxxon_-_Minute_-_MONO.ogg b/Resources/Audio/DeltaV/Jukebox/Patricia_Taxxon_-_Minute_-_MONO.ogg new file mode 100644 index 0000000000..05a7fb5fda Binary files /dev/null and b/Resources/Audio/DeltaV/Jukebox/Patricia_Taxxon_-_Minute_-_MONO.ogg differ diff --git a/Resources/Audio/DeltaV/Jukebox/Phoron_Will_Make_Us_RichMONO2.ogg b/Resources/Audio/DeltaV/Jukebox/Phoron_Will_Make_Us_RichMONO2.ogg new file mode 100644 index 0000000000..f0c5919c3c Binary files /dev/null and b/Resources/Audio/DeltaV/Jukebox/Phoron_Will_Make_Us_RichMONO2.ogg differ diff --git a/Resources/Audio/DeltaV/Jukebox/Scratch_Post_-_OST_MONO.ogg b/Resources/Audio/DeltaV/Jukebox/Scratch_Post_-_OST_MONO.ogg new file mode 100644 index 0000000000..edaa29c836 Binary files /dev/null and b/Resources/Audio/DeltaV/Jukebox/Scratch_Post_-_OST_MONO.ogg differ diff --git a/Resources/Audio/DeltaV/Jukebox/a_different_reality_lagoona_remix.xm-MONO.ogg b/Resources/Audio/DeltaV/Jukebox/a_different_reality_lagoona_remix.xm-MONO.ogg new file mode 100644 index 0000000000..bb857bd105 Binary files /dev/null and b/Resources/Audio/DeltaV/Jukebox/a_different_reality_lagoona_remix.xm-MONO.ogg differ diff --git a/Resources/Audio/DeltaV/Jukebox/aggravated.it-MONO.ogg b/Resources/Audio/DeltaV/Jukebox/aggravated.it-MONO.ogg new file mode 100644 index 0000000000..5570556f4d Binary files /dev/null and b/Resources/Audio/DeltaV/Jukebox/aggravated.it-MONO.ogg differ diff --git a/Resources/Audio/DeltaV/Jukebox/attributions.yml b/Resources/Audio/DeltaV/Jukebox/attributions.yml new file mode 100644 index 0000000000..67555c5445 --- /dev/null +++ b/Resources/Audio/DeltaV/Jukebox/attributions.yml @@ -0,0 +1,88 @@ +# sorted alphabetically based on filenames in the folder Resources/Audio/DeltaV/Jukebox +# keep it ordered or I'll stab you + +- files: ["a_different_reality_lagoona_remix.xm-MONO.ogg"] + license: "CC-BY-4.0" + copyright: "A.D.R (Lagoona rmx) by Andreas Viklund, converted to mono" + source: "https://modarchive.org/index.php?request=view_by_moduleid&query=134786" + +- files: ["aggravated.it-MONO.ogg"] + license: "CC-BY-NC-SA-4.0" + copyright: "MEL -Aggravated Assault by melcom, converted to mono" + source: "https://modarchive.org/index.php?request=view_by_moduleid&query=176234" + +- files: ["autumnal_equinox.xm-MONO.ogg"] + license: "CC-BY-NC-4.0" + copyright: "Autumnal Equinox by lemonade, converted to mono" + source: "https://modarchive.org/index.php?request=view_by_moduleid&query=143993" + +- files: ["DOS=HIGH,_UMB.ogg"] + license: "Custom" + copyright: "DOS=HIGH, UMB by MASTER BOOT RECORD may be used non-commercially in the Delta-V fork of SS14. Converted from FLAC to OGG, then converted to mono." + source: "https://masterbootrecord.bandcamp.com/album/c-edit-config-sys" + +- files: ["drozerix_-_alone.xm-MONO.ogg"] + license: "Custom" + copyright: "Alone by Drozerix, converted to mono" + source: "https://modarchive.org/index.php?request=view_by_moduleid&query=199968" + +- files: ["drozerix_-_leisurely_voice.xm-MONO.ogg"] + license: "Custom" + copyright: "Leisurely Voice by Drozerix, converted to mono" + source: "https://modarchive.org/index.php?request=view_by_moduleid&query=183837" + +- files: ["every_light_is_blinking_at_onceMONO.ogg"] + license: "CC-BY-NC-SA-3.0" + copyright: "every light blinking at once by Sunbeamstress, converted to mono" + source: "https://soundcloud.com/sunbeamstress/every-light-is-blinking-at-once" + +- files: ["hackers-MONO.ogg"] + license: "CC-BY-NC-SA-3.0" + copyright: "Hackers by Karl Casey @ White Bat Audio, converted to mono" + source: "https://www.youtube.com/watch?v=k8nHWwO1U2Q" + +- files: [lasers_rip_apart_the_bulkheadMONO.ogg] + license: "CC-BY-NC-SA-3.0" + copyright: "lasers rip apart by Sunbeamstress, converted to mono" + source: "https://soundcloud.com/sunbeamstress/lasers-rip-apart-the-bulkhead" + +- files: ["marhaba-MONO.ogg"] + license: "CC-BY-NC-SA-3.0" + copyright: "Marhaba by Ian Alex Mac. Converted from MP3 to OGG, then converted to mono" + source: "https://freemusicarchive.org/music/Ian_Alex_Mac/Cues/Marhaba" + +- files: ["Patricia_Taxxon_-_Minute_-_MONO.ogg"] + license: "CC-BY-SA-3.0" + copyright: "Minute by Patricia Taxxon off the album 'Aeroplane,' converted to mono" + source: "https://patriciataxxon.bandcamp.com/track/minute" + +- files: ["Phoron_Will_Make_Us_RichMONO2.ogg"] + license: "CC-BY-NC-SA-3.0" + copyright: "phoron will make us rich by Sunbeamstress, converted to mono" + source: "https://soundcloud.com/sunbeamstress/phoron-will-make-us-rich" + +- files: ["psirius_-_nymphs_of_the_forest.mptm-MONO.ogg"] + license: "CC-BY-NC-SA-4.0" + copyright: "Nymphs of the forest by Psirius, converted to mono" + source: "https://modarchive.org/index.php?request=view_by_moduleid&query=185545" + +- files: ["Scratch_Post_-_OST_MONO.ogg"] + license: "CC-BY-SA-4.0" + copyright: "Scratch Post by Ghirardelli7 on SoundCloud, used with the understanding that credit is given to the artist." + source: "https://soundcloud.com/ghirardelli7/scratch-post-ost" + +- files: ["shibamata-MONO.ogg"] + license: "Custom" + copyright: "Shibamata by .2gou / Dot Nigou. This track is not released under any formal licensure, and exists in the public domain in multiple forms, including remixes and mashups. Converted to mono." + source: "https://www.youtube.com/watch?v=FIw-HUP7XK0" + +- files: ["space_asshole.ogg"] + license: "Custom" + copyright: "Space Asshole by Chris Remo is used with special permission from the author, under the condition that the project remains non-commercial and open source. The author also requested that a link to his bandcamp be included: https://chrisremo.bandcamp.com/ Converted from stereo to mono." + source: "https://idlethumbs.bandcamp.com/track/space-asshole" + +- files: ["superposition-MONO.ogg"] + license: "CC-BY-NC-3.0" + copyright: "Superposition by Amie Waters, converted to mono" + source: "https://amiewaters.bandcamp.com/track/superposition-2" + diff --git a/Resources/Audio/DeltaV/Jukebox/autumnal_equinox.xm-MONO.ogg b/Resources/Audio/DeltaV/Jukebox/autumnal_equinox.xm-MONO.ogg new file mode 100644 index 0000000000..db13ac8d70 Binary files /dev/null and b/Resources/Audio/DeltaV/Jukebox/autumnal_equinox.xm-MONO.ogg differ diff --git a/Resources/Audio/DeltaV/Jukebox/drozerix_-_alone.xm-MONO.ogg b/Resources/Audio/DeltaV/Jukebox/drozerix_-_alone.xm-MONO.ogg new file mode 100644 index 0000000000..7bb1cefdde Binary files /dev/null and b/Resources/Audio/DeltaV/Jukebox/drozerix_-_alone.xm-MONO.ogg differ diff --git a/Resources/Audio/DeltaV/Jukebox/drozerix_-_leisurely_voice.xm-MONO.ogg b/Resources/Audio/DeltaV/Jukebox/drozerix_-_leisurely_voice.xm-MONO.ogg new file mode 100644 index 0000000000..9eb8028c2f Binary files /dev/null and b/Resources/Audio/DeltaV/Jukebox/drozerix_-_leisurely_voice.xm-MONO.ogg differ diff --git a/Resources/Audio/DeltaV/Jukebox/every_light_is_blinking_at_onceMONO.ogg b/Resources/Audio/DeltaV/Jukebox/every_light_is_blinking_at_onceMONO.ogg new file mode 100644 index 0000000000..5de6bfe2c7 Binary files /dev/null and b/Resources/Audio/DeltaV/Jukebox/every_light_is_blinking_at_onceMONO.ogg differ diff --git a/Resources/Audio/DeltaV/Jukebox/hackers-MONO.ogg b/Resources/Audio/DeltaV/Jukebox/hackers-MONO.ogg new file mode 100644 index 0000000000..3603aeac32 Binary files /dev/null and b/Resources/Audio/DeltaV/Jukebox/hackers-MONO.ogg differ diff --git a/Resources/Audio/DeltaV/Jukebox/lasers_rip_apart_the_bulkheadMONO.ogg b/Resources/Audio/DeltaV/Jukebox/lasers_rip_apart_the_bulkheadMONO.ogg new file mode 100644 index 0000000000..addbfcc6c0 Binary files /dev/null and b/Resources/Audio/DeltaV/Jukebox/lasers_rip_apart_the_bulkheadMONO.ogg differ diff --git a/Resources/Audio/DeltaV/Jukebox/marhaba-MONO.ogg b/Resources/Audio/DeltaV/Jukebox/marhaba-MONO.ogg new file mode 100644 index 0000000000..18b39a0296 Binary files /dev/null and b/Resources/Audio/DeltaV/Jukebox/marhaba-MONO.ogg differ diff --git a/Resources/Audio/DeltaV/Jukebox/psirius_-_nymphs_of_the_forest.mptm-MONO.ogg b/Resources/Audio/DeltaV/Jukebox/psirius_-_nymphs_of_the_forest.mptm-MONO.ogg new file mode 100644 index 0000000000..aa26988bcd Binary files /dev/null and b/Resources/Audio/DeltaV/Jukebox/psirius_-_nymphs_of_the_forest.mptm-MONO.ogg differ diff --git a/Resources/Audio/DeltaV/Jukebox/shibamata-MONO.ogg b/Resources/Audio/DeltaV/Jukebox/shibamata-MONO.ogg new file mode 100644 index 0000000000..24fc190218 Binary files /dev/null and b/Resources/Audio/DeltaV/Jukebox/shibamata-MONO.ogg differ diff --git a/Resources/Audio/DeltaV/Jukebox/space_asshole-MONO.ogg b/Resources/Audio/DeltaV/Jukebox/space_asshole-MONO.ogg new file mode 100644 index 0000000000..90e7848b77 Binary files /dev/null and b/Resources/Audio/DeltaV/Jukebox/space_asshole-MONO.ogg differ diff --git a/Resources/Audio/DeltaV/Jukebox/superposition-MONO.ogg b/Resources/Audio/DeltaV/Jukebox/superposition-MONO.ogg new file mode 100644 index 0000000000..5e7d45a886 Binary files /dev/null and b/Resources/Audio/DeltaV/Jukebox/superposition-MONO.ogg differ diff --git a/Resources/Audio/Effects/Buzzes/buzz1.ogg b/Resources/Audio/Effects/Buzzes/buzz1.ogg new file mode 100644 index 0000000000..aa21acee3c Binary files /dev/null and b/Resources/Audio/Effects/Buzzes/buzz1.ogg differ diff --git a/Resources/Audio/Effects/Buzzes/buzz2.ogg b/Resources/Audio/Effects/Buzzes/buzz2.ogg new file mode 100644 index 0000000000..652d331abc Binary files /dev/null and b/Resources/Audio/Effects/Buzzes/buzz2.ogg differ diff --git a/Resources/Audio/Effects/Buzzes/buzz3.ogg b/Resources/Audio/Effects/Buzzes/buzz3.ogg new file mode 100644 index 0000000000..e7721fa666 Binary files /dev/null and b/Resources/Audio/Effects/Buzzes/buzz3.ogg differ diff --git a/Resources/Audio/Effects/Buzzes/buzz4.ogg b/Resources/Audio/Effects/Buzzes/buzz4.ogg new file mode 100644 index 0000000000..8c400abe85 Binary files /dev/null and b/Resources/Audio/Effects/Buzzes/buzz4.ogg differ diff --git a/Resources/Audio/Effects/Buzzes/buzz5.ogg b/Resources/Audio/Effects/Buzzes/buzz5.ogg new file mode 100644 index 0000000000..b24a684e43 Binary files /dev/null and b/Resources/Audio/Effects/Buzzes/buzz5.ogg differ diff --git a/Resources/Audio/Effects/Buzzes/buzz6.ogg b/Resources/Audio/Effects/Buzzes/buzz6.ogg new file mode 100644 index 0000000000..81b206c6ed Binary files /dev/null and b/Resources/Audio/Effects/Buzzes/buzz6.ogg differ diff --git a/Resources/Audio/Effects/Buzzes/buzz7.ogg b/Resources/Audio/Effects/Buzzes/buzz7.ogg new file mode 100644 index 0000000000..f58bfaa2ea Binary files /dev/null and b/Resources/Audio/Effects/Buzzes/buzz7.ogg differ diff --git a/Resources/Audio/Effects/Buzzes/buzz8.ogg b/Resources/Audio/Effects/Buzzes/buzz8.ogg new file mode 100644 index 0000000000..75ad0c4fee Binary files /dev/null and b/Resources/Audio/Effects/Buzzes/buzz8.ogg differ diff --git a/Resources/Audio/Effects/Buzzes/buzz9.ogg b/Resources/Audio/Effects/Buzzes/buzz9.ogg new file mode 100644 index 0000000000..1e3a743f73 Binary files /dev/null and b/Resources/Audio/Effects/Buzzes/buzz9.ogg differ diff --git a/Resources/Audio/Effects/Flight/wingflap1.ogg b/Resources/Audio/Effects/Flight/wingflap1.ogg new file mode 100644 index 0000000000..724ac3ecd2 Binary files /dev/null and b/Resources/Audio/Effects/Flight/wingflap1.ogg differ diff --git a/Resources/Audio/Effects/Flight/wingflap2.ogg b/Resources/Audio/Effects/Flight/wingflap2.ogg new file mode 100644 index 0000000000..b0264a1317 Binary files /dev/null and b/Resources/Audio/Effects/Flight/wingflap2.ogg differ diff --git a/Resources/Audio/Effects/Flight/wingflap3.ogg b/Resources/Audio/Effects/Flight/wingflap3.ogg new file mode 100644 index 0000000000..aeb0e21acf Binary files /dev/null and b/Resources/Audio/Effects/Flight/wingflap3.ogg differ diff --git a/Resources/Audio/Effects/Footsteps/attributions.yml b/Resources/Audio/Effects/Footsteps/attributions.yml index 82b5fa93ca..91c3ce260d 100644 --- a/Resources/Audio/Effects/Footsteps/attributions.yml +++ b/Resources/Audio/Effects/Footsteps/attributions.yml @@ -71,3 +71,10 @@ license: "CC-BY-SA-3.0" copyright: "Taken from tgstation" source: "https://github.com/tgstation/tgstation/tree/1e8d511946d194f92f744f5f957a7c41683d84a6/sound/effects/footstep" + +- files: + - borgwalk1.ogg + - borgwalk2.ogg + license: "CC-BY-SA-4.0" + copyright: "Taken from IENBA freesound.org and modified by https://github.com/MilenVolf" + source: "https://freesound.org/people/IENBA/sounds/697379/" diff --git a/Resources/Audio/Effects/Footsteps/borgwalk1.ogg b/Resources/Audio/Effects/Footsteps/borgwalk1.ogg new file mode 100644 index 0000000000..3305a52ea2 Binary files /dev/null and b/Resources/Audio/Effects/Footsteps/borgwalk1.ogg differ diff --git a/Resources/Audio/Effects/Footsteps/borgwalk2.ogg b/Resources/Audio/Effects/Footsteps/borgwalk2.ogg new file mode 100644 index 0000000000..96c2c1617f Binary files /dev/null and b/Resources/Audio/Effects/Footsteps/borgwalk2.ogg differ diff --git a/Resources/Audio/Effects/Grenades/SelfDestruct/SDS_Charge.ogg b/Resources/Audio/Effects/Grenades/SelfDestruct/SDS_Charge.ogg new file mode 100644 index 0000000000..5efc8f443b Binary files /dev/null and b/Resources/Audio/Effects/Grenades/SelfDestruct/SDS_Charge.ogg differ diff --git a/Resources/Audio/Effects/Grenades/SelfDestruct/SDS_Charge2.ogg b/Resources/Audio/Effects/Grenades/SelfDestruct/SDS_Charge2.ogg new file mode 100644 index 0000000000..662254fbaa Binary files /dev/null and b/Resources/Audio/Effects/Grenades/SelfDestruct/SDS_Charge2.ogg differ diff --git a/Resources/Audio/Effects/Grenades/SelfDestruct/attributions.yml b/Resources/Audio/Effects/Grenades/SelfDestruct/attributions.yml new file mode 100644 index 0000000000..cb8a425739 --- /dev/null +++ b/Resources/Audio/Effects/Grenades/SelfDestruct/attributions.yml @@ -0,0 +1,8 @@ +- files: + - SDS_Charge.ogg + - SDS_Charge2.ogg + license: Custom + source: https://freesound.org/people/Teh_Bucket/sounds/518739/ + # couldn't figure out how to source multiple (the right way) without shit breaking so heres the rest: https://pixabay.com/sound-effects/switchbigpowerwav-14710/ https://pixabay.com/sound-effects/shield-recharging-107016/ + copyright: '"Electric Charge + Shot" by Teh_Bucket on Freesound.org. This is adapted from multiple works by dylanperitz, satanicupsman, CaptainGusterd, arightwizard, BigKahuna360, michael_grinnell, weaveofkev, MichelleGrobler, Alex_John73, sandyrb and breo2012 all of Freesound.org. The work by sandyrb is licensed under CC-BY-4.0. , "switchbigpower-14710.wav" and "shield-recharging-107016.wav" by Pixabay on pixabay.com' + # i have no idea how to set these up, it uses all 3 sound effects and i copied the electric charge one from the powersink, godo diff --git a/Resources/Audio/Effects/Shadowkin/Powers/darkswapoff.ogg b/Resources/Audio/Effects/Shadowkin/darkswapoff.ogg similarity index 100% rename from Resources/Audio/Effects/Shadowkin/Powers/darkswapoff.ogg rename to Resources/Audio/Effects/Shadowkin/darkswapoff.ogg diff --git a/Resources/Audio/Effects/Shadowkin/Powers/darkswapon.ogg b/Resources/Audio/Effects/Shadowkin/darkswapon.ogg similarity index 100% rename from Resources/Audio/Effects/Shadowkin/Powers/darkswapon.ogg rename to Resources/Audio/Effects/Shadowkin/darkswapon.ogg diff --git a/Resources/Audio/Effects/Shadowkin/Powers/futuristic-teleport.ogg b/Resources/Audio/Effects/Shadowkin/futuristic-teleport.ogg similarity index 100% rename from Resources/Audio/Effects/Shadowkin/Powers/futuristic-teleport.ogg rename to Resources/Audio/Effects/Shadowkin/futuristic-teleport.ogg diff --git a/Resources/Audio/Effects/Shadowkin/Powers/license.txt b/Resources/Audio/Effects/Shadowkin/license.txt similarity index 75% rename from Resources/Audio/Effects/Shadowkin/Powers/license.txt rename to Resources/Audio/Effects/Shadowkin/license.txt index c77ea8eb09..d87bd10983 100644 --- a/Resources/Audio/Effects/Shadowkin/Powers/license.txt +++ b/Resources/Audio/Effects/Shadowkin/license.txt @@ -1,4 +1,4 @@ darkswapon.ogg licensed under Pixabay Licence taken from https://pixabay.com/users/cristian_changing-30278997/ darkswapoff.ogg licensed under Pixabay Licence taken from https://pixabay.com/users/cristian_changing-30278997/ futuristic-teleport.ogg licensed under Pixabay Licence taken from https://pixabay.com/users/cristian_changing-30278997/ -teleport.ogg licensed under Pixabay Licence taken from https://pixabay.com/users/cristian_changing-30278997/ +shadeskip.ogg licensed under Pixabay Licence taken from https://pixabay.com/users/cristian_changing-30278997/ diff --git a/Resources/Audio/Effects/Shadowkin/Powers/teleport.ogg b/Resources/Audio/Effects/Shadowkin/shadeskip.ogg similarity index 100% rename from Resources/Audio/Effects/Shadowkin/Powers/teleport.ogg rename to Resources/Audio/Effects/Shadowkin/shadeskip.ogg diff --git a/Resources/Audio/Effects/Silicon/startup.ogg b/Resources/Audio/Effects/Silicon/startup.ogg new file mode 100644 index 0000000000..33d90e7845 Binary files /dev/null and b/Resources/Audio/Effects/Silicon/startup.ogg differ diff --git a/Resources/Audio/Effects/attributions.yml b/Resources/Audio/Effects/attributions.yml index 6f18510d17..7199a008eb 100644 --- a/Resources/Audio/Effects/attributions.yml +++ b/Resources/Audio/Effects/attributions.yml @@ -231,3 +231,23 @@ copyright: '"beep_landmine.ogg" by kaktuscsc of Discord for SS14' license: "CC-BY-SA-3.0" source: https://github.com/YuriyKiss/space-station-14/commit/971a135a9c83aed46e967aac9302ab5b35562b5f + +- files: ["magic_missile_1.ogg"] + license: "CC-BY-SA-4.0" + copyright: "From battle for wesnoth" + source: "https://github.com/wesnoth/wesnoth" + +- files: ["magic_missile_2.ogg"] + license: "CC-BY-SA-4.0" + copyright: "From battle for wesnoth" + source: "https://github.com/wesnoth/wesnoth" + +- files: ["magic_missile_3.ogg"] + license: "CC-BY-SA-4.0" + copyright: "From battle for wesnoth" + source: "https://github.com/wesnoth/wesnoth" + +- files: ["wail.ogg"] + license: "CC-BY-SA-4.0" + copyright: "From battle for wesnoth" + source: "https://github.com/wesnoth/wesnoth" diff --git a/Resources/Audio/Effects/chopstickbreak.ogg b/Resources/Audio/Effects/chopstickbreak.ogg new file mode 100644 index 0000000000..bac8ac0462 Binary files /dev/null and b/Resources/Audio/Effects/chopstickbreak.ogg differ diff --git a/Resources/Audio/Effects/magic_missile_1.ogg b/Resources/Audio/Effects/magic_missile_1.ogg new file mode 100644 index 0000000000..331a3efc54 Binary files /dev/null and b/Resources/Audio/Effects/magic_missile_1.ogg differ diff --git a/Resources/Audio/Effects/magic_missile_2.ogg b/Resources/Audio/Effects/magic_missile_2.ogg new file mode 100644 index 0000000000..8aac11d665 Binary files /dev/null and b/Resources/Audio/Effects/magic_missile_2.ogg differ diff --git a/Resources/Audio/Effects/magic_missile_3.ogg b/Resources/Audio/Effects/magic_missile_3.ogg new file mode 100644 index 0000000000..b7f4e94111 Binary files /dev/null and b/Resources/Audio/Effects/magic_missile_3.ogg differ diff --git a/Resources/Audio/Effects/ominous.ogg b/Resources/Audio/Effects/ominous.ogg new file mode 100644 index 0000000000..730d4dc051 Binary files /dev/null and b/Resources/Audio/Effects/ominous.ogg differ diff --git a/Resources/Audio/Effects/spray3.ogg b/Resources/Audio/Effects/spray3.ogg new file mode 100644 index 0000000000..a9f493198c Binary files /dev/null and b/Resources/Audio/Effects/spray3.ogg differ diff --git a/Resources/Audio/Effects/wail.ogg b/Resources/Audio/Effects/wail.ogg new file mode 100644 index 0000000000..b40ec5ab25 Binary files /dev/null and b/Resources/Audio/Effects/wail.ogg differ diff --git a/Resources/Audio/Expedition/attributions.yml b/Resources/Audio/Expedition/attributions.yml new file mode 100644 index 0000000000..8bafcc6f11 --- /dev/null +++ b/Resources/Audio/Expedition/attributions.yml @@ -0,0 +1,9 @@ +- files: ["tension_session.ogg"] + license: "CC-BY-3.0" + copyright: "Created by qwertyquerty" + source: "https://www.youtube.com/@qwertyquerty" + +- files: ["deadline.ogg"] + license: "CC-BY-4.0" + copyright: "Bolgarich" + source: "https://www.youtube.com/watch?v=q7_NFEeeEac" diff --git a/Resources/Audio/Parkstation/Music/deadline.ogg b/Resources/Audio/Expedition/deadline.ogg similarity index 99% rename from Resources/Audio/Parkstation/Music/deadline.ogg rename to Resources/Audio/Expedition/deadline.ogg index 6ab081fc23..131016d891 100644 Binary files a/Resources/Audio/Parkstation/Music/deadline.ogg and b/Resources/Audio/Expedition/deadline.ogg differ diff --git a/Resources/Audio/Misc/tension_session.ogg b/Resources/Audio/Expedition/tension_session.ogg similarity index 100% rename from Resources/Audio/Misc/tension_session.ogg rename to Resources/Audio/Expedition/tension_session.ogg diff --git a/Resources/Audio/Items/attributions.yml b/Resources/Audio/Items/attributions.yml index c6fea50bd2..b3ae4f611f 100644 --- a/Resources/Audio/Items/attributions.yml +++ b/Resources/Audio/Items/attributions.yml @@ -93,6 +93,16 @@ copyright: "User Hanbaal on freesound.org. Converted to ogg by TheShuEd" source: "https://freesound.org/people/Hanbaal/sounds/178669/" +- files: ["soda_shake.ogg"] + license: "CC-BY-NC-4.0" + copyright: "User mcmast on freesound.org. Converted and edited by Tayrtahn" + source: "https://freesound.org/people/mcmast/sounds/456703/" + +- files: ["soda_spray.ogg"] + license: "CC0-1.0" + copyright: "User Hajisounds on freesound.org. Converted and edited by Tayrtahn" + source: "https://freesound.org/people/Hajisounds/sounds/709149/" + - files: ["newton_cradle.ogg"] license: "CC-BY-4.0" copyright: "User LoafDV on freesound.org. Converted to ogg end edited by lzk228" @@ -117,3 +127,10 @@ license: "CC0-1.0" copyright: "Original sound by stomachache on freesound.org, processed by vanilla" source: "https://freesound.org/s/262213/" + +- files: + - "sheath.ogg" + - "unsheath.ogg" + license: "CC-BY-SA-3.0" + copyright: "Taken from tgstation." + source: "https://github.com/tgstation/tgstation/blob/a7f525bce9a359ab5282fc754078cd4b5678a006/sound/items" diff --git a/Resources/Audio/Items/sheath.ogg b/Resources/Audio/Items/sheath.ogg new file mode 100644 index 0000000000..9e1d5cdc00 Binary files /dev/null and b/Resources/Audio/Items/sheath.ogg differ diff --git a/Resources/Audio/Items/soda_shake.ogg b/Resources/Audio/Items/soda_shake.ogg new file mode 100644 index 0000000000..a596379c93 Binary files /dev/null and b/Resources/Audio/Items/soda_shake.ogg differ diff --git a/Resources/Audio/Items/soda_spray.ogg b/Resources/Audio/Items/soda_spray.ogg new file mode 100644 index 0000000000..f4a5a3e803 Binary files /dev/null and b/Resources/Audio/Items/soda_spray.ogg differ diff --git a/Resources/Audio/Items/unsheath.ogg b/Resources/Audio/Items/unsheath.ogg new file mode 100644 index 0000000000..09867f5966 Binary files /dev/null and b/Resources/Audio/Items/unsheath.ogg differ diff --git a/Resources/Audio/Jukebox/attributions.yml b/Resources/Audio/Jukebox/attributions.yml new file mode 100644 index 0000000000..48e1458c4c --- /dev/null +++ b/Resources/Audio/Jukebox/attributions.yml @@ -0,0 +1,27 @@ +- files: ["sector11.ogg"] + license: "CC-BY-NC-SA-3.0" + copyright: "-Sector11 by MashedByMachines. Converted to mono OGG." + source: "https://www.newgrounds.com/audio/listen/312622" + +- files: ["mod.flip-flap.ogg"] + license: "Custom" + copyright: "Flip Flap by X-ceed is licensed under a short but clear license (see flip-flap.txt in Audio/Lobby) and is free for non-commercial use. Converted to mono OGG." + source: "http://aminet.net/package/mods/xceed/Flipflap" + +- files: ["title3.ogg"] + license: "CC-BY-NC-SA-3.0" + copyright: "Title3 by Cuboos. It is a remix of the song 'Tintin on the Moon'. Converted to mono OGG." + source: "https://www.youtube.com/watch?v=YKVmXn-Gv0M" + +- files: + - "constellations.ogg" + - "drifting.ogg" + - "starlight.ogg" + license: "CC-BY-3.0" + copyright: "Constellations by Qwertyquerty. Converted to mono OGG." + source: "https://www.youtube.com/channel/UCPYbhBUGhH7n_G4HLK2YipQ" + +- files: ["sunset.ogg"] + license: "CC-BY-SA-3.0" + copyright: "Sunset by PigeonBeans. Exported in Mono OGG." + source: "https://soundcloud.com/pigeonbeans/sunset" \ No newline at end of file diff --git a/Resources/Audio/Jukebox/constellations.ogg b/Resources/Audio/Jukebox/constellations.ogg new file mode 100644 index 0000000000..f177489465 Binary files /dev/null and b/Resources/Audio/Jukebox/constellations.ogg differ diff --git a/Resources/Audio/Jukebox/drifting.ogg b/Resources/Audio/Jukebox/drifting.ogg new file mode 100644 index 0000000000..321c098bbd Binary files /dev/null and b/Resources/Audio/Jukebox/drifting.ogg differ diff --git a/Resources/Audio/Jukebox/flip-flap.ogg b/Resources/Audio/Jukebox/flip-flap.ogg new file mode 100644 index 0000000000..07c47c00be Binary files /dev/null and b/Resources/Audio/Jukebox/flip-flap.ogg differ diff --git a/Resources/Audio/Jukebox/sector11.ogg b/Resources/Audio/Jukebox/sector11.ogg new file mode 100644 index 0000000000..f0ce993b7e Binary files /dev/null and b/Resources/Audio/Jukebox/sector11.ogg differ diff --git a/Resources/Audio/Jukebox/starlight.ogg b/Resources/Audio/Jukebox/starlight.ogg new file mode 100644 index 0000000000..31e23416f9 Binary files /dev/null and b/Resources/Audio/Jukebox/starlight.ogg differ diff --git a/Resources/Audio/Jukebox/sunset.ogg b/Resources/Audio/Jukebox/sunset.ogg new file mode 100644 index 0000000000..3f6d909eaa Binary files /dev/null and b/Resources/Audio/Jukebox/sunset.ogg differ diff --git a/Resources/Audio/Jukebox/title3.ogg b/Resources/Audio/Jukebox/title3.ogg new file mode 100644 index 0000000000..5cfe80b535 Binary files /dev/null and b/Resources/Audio/Jukebox/title3.ogg differ diff --git a/Resources/Audio/Lobby/Monument.ogg b/Resources/Audio/Lobby/Monument.ogg new file mode 100644 index 0000000000..f27554f57f Binary files /dev/null and b/Resources/Audio/Lobby/Monument.ogg differ diff --git a/Resources/Audio/Lobby/atomicamnesiammx.ogg b/Resources/Audio/Lobby/atomicamnesiammx.ogg new file mode 100644 index 0000000000..09957d59ed Binary files /dev/null and b/Resources/Audio/Lobby/atomicamnesiammx.ogg differ diff --git a/Resources/Audio/Lobby/attributions.yml b/Resources/Audio/Lobby/attributions.yml index 9e777c37d1..e8b663a09b 100644 --- a/Resources/Audio/Lobby/attributions.yml +++ b/Resources/Audio/Lobby/attributions.yml @@ -1,4 +1,14 @@ -- files: ["01 The Gamble.ogg", "02 Guilty Pleasures.ogg", "03 Jazzcuzzi.ogg", "04 The Walk.ogg", "05 Velvet Bossa.ogg", "06 Colors.ogg", "07 Midnight Jam.ogg", "08 Miles Ahead.ogg", "09 Moody.ogg", "10 Flying Away.ogg", "11 Take It Easy.ogg"] +- files: ["01 The Gamble.ogg", "02 Guilty Pleasures.ogg", "03 Jazzcuzzi.ogg", "04 The Walk.ogg", "05 Velvet Bossa.ogg", "06 Colors.ogg", "07 Midnight Jam.ogg", "08 Miles Ahead.ogg", "09 Moody.ogg", "10 Flying Away.ogg", "11 Take It Easy.ogg"] license: "CC-BY-NC-SA-3.0" copyright: "All songs used are produced by Danya Vodovoz, royalty free." source: "https://soundcloud.com/danyavodovoz" + +- files: ["atomicamnesiammx.ogg"] + license: "CC-BY-NC-SA-3.0" + copyright: "Atomic Amnesia MMX by Phillip Dyer. Converted from MP3 to OGG." + source: "https://soundcloud.com/3kliksphilip/atomic-amnesia-mmx-1" + +- files: ["Monument.ogg"] + license: "CC-BY-NC-SA-3.0" + copyright: "Monument by Six Umbrellas." + source: "https://sixumbrellas.bandcamp.com/album/the-psychedelic-and" diff --git a/Resources/Audio/Machines/warning_buzzer.ogg b/Resources/Audio/Machines/warning_buzzer.ogg index 55bb179f57..bef16f46fb 100644 Binary files a/Resources/Audio/Machines/warning_buzzer.ogg and b/Resources/Audio/Machines/warning_buzzer.ogg differ diff --git a/Resources/Audio/Medical/Surgery/attributions.yml b/Resources/Audio/Medical/Surgery/attributions.yml new file mode 100644 index 0000000000..c88a3e0b70 --- /dev/null +++ b/Resources/Audio/Medical/Surgery/attributions.yml @@ -0,0 +1,49 @@ +- files: ["cautery1.ogg"] + license: "CC-BY-SA-3.0" + copyright: "Taken from cmss13" + source: "https://github.com/cmss13-devs/cmss13/blob/fae73dfa5aedb0a253de04b60085ed8a178d3bf7/sound/surgery/cautery1.ogg" + +- files: ["cautery2.ogg"] + license: "CC-BY-SA-3.0" + copyright: "Taken from cmss13" + source: "https://github.com/cmss13-devs/cmss13/blob/fae73dfa5aedb0a253de04b60085ed8a178d3bf7/sound/surgery/cautery2.ogg" + +- files: ["hemostat.ogg"] + license: "CC-BY-SA-3.0" + copyright: "Taken from cmss13" + source: "https://github.com/cmss13-devs/cmss13/blob/fae73dfa5aedb0a253de04b60085ed8a178d3bf7/sound/surgery/hemostat.ogg" + +- files: ["organ1.ogg"] + license: "CC-BY-SA-3.0" + copyright: "Taken from cmss13" + source: "https://github.com/cmss13-devs/cmss13/blob/fae73dfa5aedb0a253de04b60085ed8a178d3bf7/sound/surgery/organ1.ogg" + +- files: ["organ2.ogg"] + license: "CC-BY-SA-3.0" + copyright: "Taken from cmss13" + source: "https://github.com/cmss13-devs/cmss13/blob/fae73dfa5aedb0a253de04b60085ed8a178d3bf7/sound/surgery/organ2.ogg" + +- files: ["retractor1.ogg"] + license: "CC-BY-SA-3.0" + copyright: "Taken from cmss13" + source: "https://github.com/cmss13-devs/cmss13/blob/fae73dfa5aedb0a253de04b60085ed8a178d3bf7/sound/surgery/retractor1.ogg" + +- files: ["retractor2.ogg"] + license: "CC-BY-SA-3.0" + copyright: "Taken from cmss13" + source: "https://github.com/cmss13-devs/cmss13/blob/fae73dfa5aedb0a253de04b60085ed8a178d3bf7/sound/surgery/retractor2.ogg" + +- files: ["saw.ogg"] + license: "CC-BY-SA-3.0" + copyright: "Taken from cmss13" + source: "https://github.com/cmss13-devs/cmss13/blob/fae73dfa5aedb0a253de04b60085ed8a178d3bf7/sound/surgery/saw.ogg" + +- files: ["scalpel1.ogg"] + license: "CC-BY-SA-3.0" + copyright: "Taken from cmss13" + source: "https://github.com/cmss13-devs/cmss13/blob/fae73dfa5aedb0a253de04b60085ed8a178d3bf7/sound/surgery/scalpel1.ogg" + +- files: ["scalpel2.ogg"] + license: "CC-BY-SA-3.0" + copyright: "Taken from cmss13" + source: "https://github.com/cmss13-devs/cmss13/blob/fae73dfa5aedb0a253de04b60085ed8a178d3bf7/sound/surgery/scalpel2.ogg" \ No newline at end of file diff --git a/Resources/Audio/Medical/Surgery/cautery1.ogg b/Resources/Audio/Medical/Surgery/cautery1.ogg new file mode 100644 index 0000000000..fbd9f2b4d8 Binary files /dev/null and b/Resources/Audio/Medical/Surgery/cautery1.ogg differ diff --git a/Resources/Audio/Medical/Surgery/cautery2.ogg b/Resources/Audio/Medical/Surgery/cautery2.ogg new file mode 100644 index 0000000000..cc91e9f3ce Binary files /dev/null and b/Resources/Audio/Medical/Surgery/cautery2.ogg differ diff --git a/Resources/Audio/Medical/Surgery/hemostat1.ogg b/Resources/Audio/Medical/Surgery/hemostat1.ogg new file mode 100644 index 0000000000..e624bafafb Binary files /dev/null and b/Resources/Audio/Medical/Surgery/hemostat1.ogg differ diff --git a/Resources/Audio/Medical/Surgery/organ1.ogg b/Resources/Audio/Medical/Surgery/organ1.ogg new file mode 100644 index 0000000000..37eaffc1a3 Binary files /dev/null and b/Resources/Audio/Medical/Surgery/organ1.ogg differ diff --git a/Resources/Audio/Medical/Surgery/organ2.ogg b/Resources/Audio/Medical/Surgery/organ2.ogg new file mode 100644 index 0000000000..43b22f8354 Binary files /dev/null and b/Resources/Audio/Medical/Surgery/organ2.ogg differ diff --git a/Resources/Audio/Medical/Surgery/retractor1.ogg b/Resources/Audio/Medical/Surgery/retractor1.ogg new file mode 100644 index 0000000000..70625c961c Binary files /dev/null and b/Resources/Audio/Medical/Surgery/retractor1.ogg differ diff --git a/Resources/Audio/Medical/Surgery/retractor2.ogg b/Resources/Audio/Medical/Surgery/retractor2.ogg new file mode 100644 index 0000000000..94548ec250 Binary files /dev/null and b/Resources/Audio/Medical/Surgery/retractor2.ogg differ diff --git a/Resources/Audio/Medical/Surgery/saw.ogg b/Resources/Audio/Medical/Surgery/saw.ogg new file mode 100644 index 0000000000..62623f6aa3 Binary files /dev/null and b/Resources/Audio/Medical/Surgery/saw.ogg differ diff --git a/Resources/Audio/Medical/Surgery/scalpel1.ogg b/Resources/Audio/Medical/Surgery/scalpel1.ogg new file mode 100644 index 0000000000..f292d1024d Binary files /dev/null and b/Resources/Audio/Medical/Surgery/scalpel1.ogg differ diff --git a/Resources/Audio/Medical/Surgery/scalpel2.ogg b/Resources/Audio/Medical/Surgery/scalpel2.ogg new file mode 100644 index 0000000000..7335f3d9ce Binary files /dev/null and b/Resources/Audio/Medical/Surgery/scalpel2.ogg differ diff --git a/Resources/Audio/Parkstation/Music/attributions.yml b/Resources/Audio/Parkstation/Music/attributions.yml deleted file mode 100644 index c1ed2f1027..0000000000 --- a/Resources/Audio/Parkstation/Music/attributions.yml +++ /dev/null @@ -1,4 +0,0 @@ -- files: ["deadling.ogg"] - license: "CC-BY-3.0" - copyright: "Created by Bolgarich" - source: "https://youtu.be/q7_NFEeeEac" diff --git a/Resources/Audio/StationEvents/attributions.yml b/Resources/Audio/StationEvents/attributions.yml index e63b18a627..9b3ad13484 100644 --- a/Resources/Audio/StationEvents/attributions.yml +++ b/Resources/Audio/StationEvents/attributions.yml @@ -6,4 +6,9 @@ - files: ["clearly_nuclear.ogg"] license: "CC-BY-3.0" copyright: "Created by mryikes" - source: "https://www.youtube.com/watch?v=chix8uz-oUQ" \ No newline at end of file + source: "https://www.youtube.com/watch?v=chix8uz-oUQ" + +- files: ["chip_nightmare.ogg"] + license: "CC-BY-SA-4.0" + copyright: "Created by BasedUser on top of chip-meltdown.mod by Reanimator (Thom)." + source: "http://ygg.baseduser.eu.org/chip-nightmare_ss14.ogg" diff --git a/Resources/Audio/StationEvents/chip_nightmare.ogg b/Resources/Audio/StationEvents/chip_nightmare.ogg new file mode 100644 index 0000000000..c721c0ca24 Binary files /dev/null and b/Resources/Audio/StationEvents/chip_nightmare.ogg differ diff --git a/Resources/Audio/Voice/IPC/attributions.yml b/Resources/Audio/Voice/IPC/attributions.yml new file mode 100644 index 0000000000..d2bcb8d894 --- /dev/null +++ b/Resources/Audio/Voice/IPC/attributions.yml @@ -0,0 +1,14 @@ +- files: ["beep_500.ogg", "beep_2000.ogg"] + license: "CC0-1.0" + copyright: "Synthesized in Audacity by BasedUser." + source: "https://github.com/ekrixi-14/ekrixi" + +- files: ["pai_whistle.ogg"] + license: "CC-BY-4.0" + copyright: "Source sound by hubismal@GitHub, modified in Audacity by BasedUser." + source: "https://github.com/space-wizards/space-station-14/commit/3421e4f4de2613df1e92a4169a778335bc9faac4" + +- files: ["whirr1.ogg", "whirr2.ogg", "whirr3.ogg"] + license: "CC0-1.0" + copyright: "Taken from source, spectrally modified and clipped" + source: "https://freesound.org/people/sad3d/sounds/500168/" diff --git a/Resources/Audio/Voice/IPC/beep_2000.ogg b/Resources/Audio/Voice/IPC/beep_2000.ogg new file mode 100644 index 0000000000..1caa98ff9a Binary files /dev/null and b/Resources/Audio/Voice/IPC/beep_2000.ogg differ diff --git a/Resources/Audio/Voice/IPC/beep_500.ogg b/Resources/Audio/Voice/IPC/beep_500.ogg new file mode 100644 index 0000000000..5435eefb47 Binary files /dev/null and b/Resources/Audio/Voice/IPC/beep_500.ogg differ diff --git a/Resources/Audio/Voice/IPC/cry_robot_1.ogg b/Resources/Audio/Voice/IPC/cry_robot_1.ogg new file mode 100644 index 0000000000..78a27496a5 Binary files /dev/null and b/Resources/Audio/Voice/IPC/cry_robot_1.ogg differ diff --git a/Resources/Audio/Voice/IPC/pai_whistle.ogg b/Resources/Audio/Voice/IPC/pai_whistle.ogg new file mode 100644 index 0000000000..42aebd1197 Binary files /dev/null and b/Resources/Audio/Voice/IPC/pai_whistle.ogg differ diff --git a/Resources/Audio/Voice/IPC/robot-laugh_3.ogg b/Resources/Audio/Voice/IPC/robot-laugh_3.ogg new file mode 100644 index 0000000000..331151f6b5 Binary files /dev/null and b/Resources/Audio/Voice/IPC/robot-laugh_3.ogg differ diff --git a/Resources/Audio/Voice/IPC/robot-scream.ogg b/Resources/Audio/Voice/IPC/robot-scream.ogg new file mode 100644 index 0000000000..6420a4009d Binary files /dev/null and b/Resources/Audio/Voice/IPC/robot-scream.ogg differ diff --git a/Resources/Audio/Voice/IPC/whirr1.ogg b/Resources/Audio/Voice/IPC/whirr1.ogg new file mode 100644 index 0000000000..2679d794f0 Binary files /dev/null and b/Resources/Audio/Voice/IPC/whirr1.ogg differ diff --git a/Resources/Audio/Voice/IPC/whirr2.ogg b/Resources/Audio/Voice/IPC/whirr2.ogg new file mode 100644 index 0000000000..6f74bebed8 Binary files /dev/null and b/Resources/Audio/Voice/IPC/whirr2.ogg differ diff --git a/Resources/Audio/Voice/IPC/whirr3.ogg b/Resources/Audio/Voice/IPC/whirr3.ogg new file mode 100644 index 0000000000..45ececee62 Binary files /dev/null and b/Resources/Audio/Voice/IPC/whirr3.ogg differ diff --git a/Resources/Audio/Voice/IPC/wilhelm.ogg b/Resources/Audio/Voice/IPC/wilhelm.ogg new file mode 100644 index 0000000000..b8ad9d97d4 Binary files /dev/null and b/Resources/Audio/Voice/IPC/wilhelm.ogg differ diff --git a/Resources/Audio/Voice/Shadowkin/attributions.yml b/Resources/Audio/Voice/Shadowkin/attributions.yml new file mode 100644 index 0000000000..4ab746f465 --- /dev/null +++ b/Resources/Audio/Voice/Shadowkin/attributions.yml @@ -0,0 +1,4 @@ +- files: ["wurble.ogg", "mar.ogg"] + license: "CC-BY-SA-3.0" + copyright: "Taken from CHOMPStation" + source: "https://github.com/CHOMPStation2/CHOMPStation2" \ No newline at end of file diff --git a/Resources/Audio/Voice/Shadowkin/mar.ogg b/Resources/Audio/Voice/Shadowkin/mar.ogg new file mode 100644 index 0000000000..b13d2df837 Binary files /dev/null and b/Resources/Audio/Voice/Shadowkin/mar.ogg differ diff --git a/Resources/Audio/Voice/Shadowkin/wurble.ogg b/Resources/Audio/Voice/Shadowkin/wurble.ogg new file mode 100644 index 0000000000..859c9df353 Binary files /dev/null and b/Resources/Audio/Voice/Shadowkin/wurble.ogg differ diff --git a/Resources/Audio/Voice/Silicon/attributions.yml b/Resources/Audio/Voice/Silicon/attributions.yml new file mode 100644 index 0000000000..465f845503 --- /dev/null +++ b/Resources/Audio/Voice/Silicon/attributions.yml @@ -0,0 +1,4 @@ +- files: ["syndieborg_laugh.ogg"] + license: "CC0-1.0" + copyright: "Taken from vultraz168 freesound.org and modified by https://github.com/MilenVolf" + source: "https://freesound.org/people/vultraz168/sounds/334665/" diff --git a/Resources/Audio/Voice/Silicon/syndieborg_laugh.ogg b/Resources/Audio/Voice/Silicon/syndieborg_laugh.ogg new file mode 100644 index 0000000000..e0f425301e Binary files /dev/null and b/Resources/Audio/Voice/Silicon/syndieborg_laugh.ogg differ diff --git a/Resources/Audio/Voice/Slime/slime_scream_f2.ogg b/Resources/Audio/Voice/Slime/slime_scream_f2.ogg index 71ea5f4bdc..923da50166 100644 Binary files a/Resources/Audio/Voice/Slime/slime_scream_f2.ogg and b/Resources/Audio/Voice/Slime/slime_scream_f2.ogg differ diff --git a/Resources/Audio/Voice/Talk/Silicon/attributions.yml b/Resources/Audio/Voice/Talk/Silicon/attributions.yml new file mode 100644 index 0000000000..3a16007758 --- /dev/null +++ b/Resources/Audio/Voice/Talk/Silicon/attributions.yml @@ -0,0 +1,10 @@ +- files: + - borg.ogg + - borg_ask.ogg + - borg_exclaim.ogg + - syndieborg.ogg + - syndieborg_ask.ogg + - syndieborg_exclaim.ogg + license: "CC-BY-SA-4.0" + copyright: "Recorded and mixed by https://github.com/MilenVolf" + source: "https://github.com/space-wizards/space-station-14/pull/27205" diff --git a/Resources/Audio/Voice/Talk/Silicon/borg.ogg b/Resources/Audio/Voice/Talk/Silicon/borg.ogg new file mode 100644 index 0000000000..afb9739035 Binary files /dev/null and b/Resources/Audio/Voice/Talk/Silicon/borg.ogg differ diff --git a/Resources/Audio/Voice/Talk/Silicon/borg_ask.ogg b/Resources/Audio/Voice/Talk/Silicon/borg_ask.ogg new file mode 100644 index 0000000000..fe2f60141a Binary files /dev/null and b/Resources/Audio/Voice/Talk/Silicon/borg_ask.ogg differ diff --git a/Resources/Audio/Voice/Talk/Silicon/borg_exclaim.ogg b/Resources/Audio/Voice/Talk/Silicon/borg_exclaim.ogg new file mode 100644 index 0000000000..5f0fdfe89e Binary files /dev/null and b/Resources/Audio/Voice/Talk/Silicon/borg_exclaim.ogg differ diff --git a/Resources/Audio/Voice/Talk/Silicon/syndieborg.ogg b/Resources/Audio/Voice/Talk/Silicon/syndieborg.ogg new file mode 100644 index 0000000000..60eb1a5ccf Binary files /dev/null and b/Resources/Audio/Voice/Talk/Silicon/syndieborg.ogg differ diff --git a/Resources/Audio/Voice/Talk/Silicon/syndieborg_ask.ogg b/Resources/Audio/Voice/Talk/Silicon/syndieborg_ask.ogg new file mode 100644 index 0000000000..acafe45642 Binary files /dev/null and b/Resources/Audio/Voice/Talk/Silicon/syndieborg_ask.ogg differ diff --git a/Resources/Audio/Voice/Talk/Silicon/syndieborg_exclaim.ogg b/Resources/Audio/Voice/Talk/Silicon/syndieborg_exclaim.ogg new file mode 100644 index 0000000000..392b4fd1cc Binary files /dev/null and b/Resources/Audio/Voice/Talk/Silicon/syndieborg_exclaim.ogg differ diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 6ba4d21856..5ad5850237 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -5397,3 +5397,2532 @@ Entries: message: You no longer need to have a station record to publish news. id: 6270 time: '2024-08-18T18:11:10.0000000+00:00' +- author: ShatteredSwords + changes: + - type: Add + message: Several useful items have been added to loadouts + - type: Add + message: Cowtools are selectable for clown in the "Jobs" category of loadouts + id: 6271 + time: '2024-08-20T06:48:59.0000000+00:00' +- author: VMSolidus + changes: + - type: Add + message: >- + The Mood System has been ported from White Dream. Mood acts as a 3rd + healthbar, alongside Health and Stamina, representing your character's + current mental state. Having either high or low mood can modify certain + physical attributes. + - type: Add + message: >- + Mood modifies your Critical Threshold. Your critical threshold can be + increased or decreased depending on how high or low your character's + mood is. + - type: Add + message: >- + Mood modifies your Movement Speed. Characters move faster when they have + an overall high mood, and move slower when they have a lower mood. + - type: Add + message: >- + Saturnine and Sanguine have been added to the list of Mental traits, + both providing innate modifiers to a character's Morale. + id: 6272 + time: '2024-08-20T08:16:05.0000000+00:00' +- author: VMSolidus + changes: + - type: Add + message: Latent Psychic has been added as a new positive trait. + - type: Tweak + message: >- + Psionics have received a substantial refactor. While no new powers have + been added this patch, this initial refactor lays the groundwork so that + new psionic powers will be easier to create. + - type: Tweak + message: >- + Latent Psychic is now fully required to become psionic, or to interact + with Oracle. + - type: Tweak + message: Psychics can now have more than one active power. + - type: Remove + message: Mimes are no longer Psionic. + - type: Tweak + message: >- + Chaplain, Mantis, & Mystagogue all receive the Latent Psychic trait for + free, automatically. + id: 6273 + time: '2024-08-20T18:42:44.0000000+00:00' +- author: VMSolidus + changes: + - type: Fix + message: >- + Fixed an issue where Overlays(Dogvision, Ultravision, Mood) would apply + globally to all entities when updating. + id: 6274 + time: '2024-08-20T18:49:28.0000000+00:00' +- author: VMSolidus + changes: + - type: Add + message: >- + Actions no longer need to hardcode in target blacklists, and can now + blacklist entities in YML. This is notably useful for Psionic powers, + which all share a common feature that they can't target people with + Psionic Insulation (Or have been Mindbroken). + id: 6275 + time: '2024-08-21T09:08:40.0000000+00:00' +- author: VMSolidus + changes: + - type: Add + message: >- + PsionicPowers that add a Component now also allow for adding a Component + with Arguments. This works exactly like the trait system's + implementation of components. + id: 6276 + time: '2024-08-21T09:10:28.0000000+00:00' +- author: Timemaster99 + changes: + - type: Add + message: Added IPC as a playable species. + id: 6277 + time: '2024-08-21T09:14:30.0000000+00:00' +- author: VMSolidus + changes: + - type: Add + message: Traits can now add Active Abilities to a character. + - type: Add + message: Traits can now add Psionic Powers to a character. + id: 6278 + time: '2024-08-21T09:49:54.0000000+00:00' +- author: stellar-novas + changes: + - type: Tweak + message: Ifrit has received some damage resistance changes + id: 6279 + time: '2024-08-21T21:00:01.0000000+00:00' +- author: Rane + changes: + - type: Add + message: Added Xenoglossy to the psionic power pool. + id: 6280 + time: '2024-08-22T00:08:59.0000000+00:00' +- author: VMSolidus + changes: + - type: Tweak + message: >- + Trait points have been made more granular by both doubling the available + number of trait points, and increasing the base cost of all pre-existing + traits. + id: 6281 + time: '2024-08-22T00:52:11.0000000+00:00' +- author: VMSolidus + changes: + - type: Tweak + message: Moths can now be colorful again. + id: 6282 + time: '2024-08-22T08:43:20.0000000+00:00' +- author: VMSolidus + changes: + - type: Add + message: >- + Cataloguer has been re-added to the game as a new roundstart Psionic + role. The Cataloguer is a unique role that will always start with the + Latent Psychic trait, as well as the new Xenoglossy power, which allows + him to know and speak all languages. + id: 6283 + time: '2024-08-22T17:00:11.0000000+00:00' +- author: VMSolidus + changes: + - type: Add + message: >- + Gaining a new Psionic Power can now display messages to alert the + player, both as a short popup, and optionally a lengthier message sent + to the user's Chat window. + id: 6284 + time: '2024-08-22T17:12:26.0000000+00:00' +- author: DEATHB4DEFEAT + changes: + - type: Fix + message: >- + Announcements can play multiple sounds now (you can revert to the old + behavior in sound setting if you want though) + id: 6285 + time: '2024-08-22T21:46:38.0000000+00:00' +- author: v0idRift + changes: + - type: Add + message: >- + Added the ability for IPCs to speak and understand Galactic Common and + RobotTalk languages. + - type: Add + message: >- + Enabled IPCs to heal themselves using welding tools via the + WeldingHealable component. + id: 6286 + time: '2024-08-23T03:58:35.0000000+00:00' +- author: FoxxoTrystan + changes: + - type: Fix + message: Supermatter Annoncements + id: 6287 + time: '2024-08-24T07:56:12.0000000+00:00' +- author: Fansana + changes: + - type: Fix + message: >- + Fixed most door accesses including: Lawyer, Mantis, Corpsman, Boxer, + Clown, Mime, Musician, Reporter, Library, Zookeeper, Salvage and + Psychologist. + id: 6288 + time: '2024-08-25T08:45:17.0000000+00:00' +- author: VMSolidus + changes: + - type: Fix + message: Fixed the FixGridAtmos command. + id: 6289 + time: '2024-08-26T00:02:53.0000000+00:00' +- author: VMSolidus + changes: + - type: Add + message: >- + Lab Mixes have been added to the game as a new random animal. Be sure to + keep Felinids and Harpies away from them. + id: 6290 + time: '2024-08-27T03:16:46.0000000+00:00' +- author: router + changes: + - type: Add + message: >- + Added more sounds to IPCs. IPCs can now also whistle, whirr, beep, boop, + ping, chime, buzz and buzz twice. + - type: Fix + message: IPCs no longer have the default wilhelm sound. + - type: Tweak + message: Silicon deathgasps are now darker. + id: 6291 + time: '2024-08-29T02:57:16.0000000+00:00' +- author: VMSolidus + changes: + - type: Fix + message: IPCs can now lay down, offer people items, and be picked up and carried. + id: 6292 + time: '2024-08-29T20:01:47.0000000+00:00' +- author: VMSolidus + changes: + - type: Fix + message: Temporarily disabled Telegnosis pending a lengthier update. + id: 6293 + time: '2024-08-30T00:46:52.0000000+00:00' +- author: VMSolidus + changes: + - type: Fix + message: IPC now have their own unique deathgasp message. + id: 6294 + time: '2024-08-30T00:50:20.0000000+00:00' +- author: Mnemotechnician + changes: + - type: Fix + message: >- + Shoving once again works correctly, and mass difference matters a lot + when shoving someone. + - type: Fix + message: >- + The time it takes to escape one's hands once again depends on the mass + difference between the escapee and the holder. + - type: Tweak + message: >- + Exiting stamina crit now leaves you with 0 stamina. You can't be + immediately stunned again, but you will suffer from slowdown and combat + disadvantages! + id: 6295 + time: '2024-08-30T04:46:40.0000000+00:00' +- author: ShatteredSwords + changes: + - type: Tweak + message: Thieves are now in Survival, Hellshift, and Extended. + id: 6296 + time: '2024-08-30T04:57:29.0000000+00:00' +- author: VMSolidus + changes: + - type: Add + message: >- + TelepathyComponent has been split off from the PsionicComponent, now as + it's own standalone feature. + - type: Add + message: Telepathy has been added as a new Psionic Power + - type: Add + message: >- + Natural Telepath has returned from Psionic-Refactor V1, now using new + functionality from the trait system that allows traits to buy psionic + powers directly. + - type: Add + message: >- + Latent Psychics who have neither bought Natural Telepath, nor acquired + Telepathy during the round, can sometimes hear snippets of conversation + from telepathic chat. + - type: Tweak + message: >- + The cost of Latent Psychic has been reduced from 6 to 4 points, this is + to accommodate for the loss of Telepathy as a bonus feature for all + Psionics. Since Natural Telepath is a 2 point trait, this gives a net 0 + change in trait points for anyone who wishes to keep being a roundstart + Telepath. + - type: Tweak + message: >- + Psionic Mantis, Mystagogue, Chaplain, and Cataloguer are all Naturally + Telepathic, and thus get the new trait for free. + id: 6297 + time: '2024-08-30T05:27:54.0000000+00:00' +- author: VMSolidus + changes: + - type: Add + message: >- + x-Waveform Misalignment has been rebased from the Psionic Refactor v1. + x-Waveform Misalignment is an extraordinarily expensive trait that + grants full immunity to nearly all psionic powers and effects, both + positive and negative. + id: 6298 + time: '2024-08-30T05:39:59.0000000+00:00' +- author: Mnemotechnician + changes: + - type: Add + message: >- + A new interaction system has been implemented. The right-click menu now + provides a wide variety of different interactions with different + entities. Some old default interactions, such as hugging, knocking, + fence rattling, have also been moved to that system. + id: 6299 + time: '2024-08-30T05:45:24.0000000+00:00' +- author: VMSolidus + changes: + - type: Tweak + message: >- + Radiation shielding objects have been rebalanced, such that objects that + players previously assumed would provide reasonable protection from + radiation, now factually do. For instance, Radiation Shutters now + actually block radiation. + id: 6300 + time: '2024-08-30T14:10:02.0000000+00:00' +- author: VMSolidus + changes: + - type: Add + message: Glacier Returns. + id: 6301 + time: '2024-08-30T14:13:04.0000000+00:00' +- author: VMSolidus + changes: + - type: Add + message: >- + Saltern has been added to the map rotation. Now featuring a compact + Supermatter engine, full Epistemics department, and a significantly + expanded Chapel and Library. + id: 6302 + time: '2024-08-30T14:24:49.0000000+00:00' +- author: VMSolidus + changes: + - type: Add + message: >- + Core has been added to the list of maps in rotation. This time featuring + a custom Supermatter Engine + id: 6303 + time: '2024-08-30T14:42:12.0000000+00:00' +- author: VMSolidus + changes: + - type: Fix + message: >- + InnatePsionicPowers now operates on MapInitEvent instead of + ComponentStartup, meaning that it can now be safely used on entities + that are mapped in instead of spawned. + - type: Add + message: >- + Oracle and Sophia are now recognized as Divine, and as such are + creatures that can be prayed to. + id: 6304 + time: '2024-08-31T04:28:16.0000000+00:00' +- author: VMSolidus + changes: + - type: Add + message: Diagonal windows now use connected textures. + id: 6305 + time: '2024-09-01T23:20:12.0000000+00:00' +- author: VMSolidus + changes: + - type: Tweak + message: Character names can now be up to 48 characters in length. + - type: Tweak + message: >- + Character descriptions can now be up to 1024 characters in length. This + is the maximum size descriptions can be without the menu having a scroll + bar. And while we'd like it to be bigger, we're going to want to get a + new UI for this in the future! + id: 6306 + time: '2024-09-01T23:20:45.0000000+00:00' +- author: VMSolidus + changes: + - type: Fix + message: >- + The default stamina cost for Power Attacks for any weapon that doesn't + explicitly modify it is now correctly set to 10 stamina(down from 20). + id: 6307 + time: '2024-09-01T23:25:51.0000000+00:00' +- author: Evgencheg + changes: + - type: Add + message: Changelogs should be shown in Discord now + id: 6308 + time: '2024-09-01T23:47:18.0000000+00:00' +- author: DJB1gYAPPA + changes: + - type: Add + message: 'Added Jukebox with basic songs. ' + id: 6309 + time: '2024-09-02T02:28:11.0000000+00:00' +- author: VMSolidus + changes: + - type: Add + message: >- + Revenants, Reagent Slimes, and Ore Crabs are now considered to be + Psionic(But cannot gain powers randomly). This is due to their status as + "Magical And/Or Extraplanar Creatures", which makes them valid targets + for anti-psychic abilities such as the Psionic Mantis' Anti-Psychic + Knife. + - type: Add + message: Some Reagent Slimes can now contain Lotophagoi Oil. + id: 6310 + time: '2024-09-02T15:08:11.0000000+00:00' +- author: VMSolidus + changes: + - type: Add + message: Cloning & Metempsychosis Machines have been refactored! + - type: Add + message: >- + Cloning can now fail at any point during the cloning process, turning + the would-be clone into a soup of blood and ammonia. + - type: Add + message: >- + "Clone Soup" scales directly with the mass of the entity you're + attempting to clone. Fail to clone a Lamia, and you'll be greeted with + an Olympic swimming pool worth of blood when the machine opens. + - type: Add + message: >- + Cloning will fail if at any point during the procedure, the machine is + depowered, unanchored, or emagged. + - type: Add + message: >- + Clones come out of the machine with severe Cellular damage. Consider + using Doxarubixadone in a Cryo tube as an affordable means of + "Finishing" clones. + - type: Tweak + message: >- + Cloning Time is now increased proportionally if an entity being cloned + is larger than a standard human(smaller entities are unchanged) + - type: Tweak + message: >- + The cost to clone an entity can now be configured on a per-server basis + via CCVar "cloning.biomass_cost_multiplier" + - type: Tweak + message: >- + The Biomass Reclaimer can now be toggled to round-remove ensouled bodies + or not via CCVar "cloning.reclaim_souled_bodies" + - type: Add + message: >- + The effects of Metempsychosis now scale with a Psion's relevant caster + stats. More powerful psychics are more likely to get favorable results + from being forcibly reincarnated. + id: 6311 + time: '2024-09-02T15:08:52.0000000+00:00' +- author: DEATHB4DEFEAT + changes: + - type: Add + message: New changelogs should now show links to their PRs + - type: Tweak + message: Changelog authors should now be a header instead of bold text + - type: Fix + message: Changelogs shouldn't send random amounts of old changelogs + id: 6312 + time: '2024-09-02T19:52:08.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/843 +- author: DEATHB4DEFEAT + changes: + - type: Fix + message: oatnsdaoersoaetaroeoertnsirlea + id: 6313 + time: '2024-09-02T19:54:19.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/846 +- author: Rane + changes: + - type: Add + message: Added Psychognomy. + - type: Tweak + message: Reverted Sophia name change. + - type: Tweak + message: Minor tweaks to psionic chat eligibility and formatting. + - type: Add + message: >- + Some new utility has been added to player-controlled Oracle or Sophia. + NPC functionality later :^) + id: 6314 + time: '2024-09-03T02:17:55.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/808 +- author: VMSolidus + changes: + - type: Add + message: >- + Several new tips have been added to the game, many of which reference + new content available on Einstein-Engines. + id: 6315 + time: '2024-09-05T00:00:21.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/844 +- author: Mnemotechnician + changes: + - type: Fix + message: Fixed a couple issues with the new interaction verb system. + id: 6316 + time: '2024-09-05T00:18:41.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/854 +- author: VMSolidus + changes: + - type: Add + message: >- + Due to NUMEROUS complaints, NanoTrasen has swapped the sticker labels on + all colored jumpskirts to correctly state that they are infact, + "Skirts", so now they can legally be worn by Harpies, Lamia, and + Arachne. + id: 6317 + time: '2024-09-05T00:19:49.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/848 +- author: ShatteredSwords + changes: + - type: Tweak + message: The cost of many traits have been updated + - type: Tweak + message: A few traits have been tweaked + id: 6318 + time: '2024-09-06T20:08:38.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/842 +- author: Mnemotechnician + changes: + - type: Add + message: >- + A new Roboticist job has been added. The job does not offer any new + accesses, but lets you distinguish yourself from scientists. Existing + maps will need to be updated to support the new job. + - type: Add + message: >- + Added new PDA, ID, and bag variants for roboticists. The mystagogue will + now also find a roboticist PDA in their office. + id: 6319 + time: '2024-09-06T20:17:31.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/801 +- author: DEATHB4DEFEAT + changes: + - type: Fix + message: The loadout/trait editors' performance has been improved by 100~1000x + - type: Tweak + message: The character editor's tabs look, feel, and function much better + - type: Fix + message: Unusable loadouts/traits have an orange color when selected + - type: Tweak + message: Loadout previews are now inside the button + - type: Add + message: >- + Added a button to remove any unusable loadouts (or traits) you have + selected + - type: Add + message: >- + Loadouts and traits can have subcategories, though only Command loadouts + use them at the moment + - type: Fix + message: Fixed empty loadout categories not hiding + - type: Fix + message: Fixed spacing on some loadout requirements' reason text + - type: Fix + message: Fixed traits not updating after saving + id: 6320 + time: '2024-09-06T21:59:51.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/736 +- author: VMSolidus + changes: + - type: Add + message: >- + The first of all new loadout groups has been added, Civilian Uniforms. + Only one civilian uniform can be selected, in exchange for... + - type: Tweak + message: >- + All non-job specific uniforms have had their points reduced drastically, + almost all of them down to 0 points, with only a tiny handful at 2 + points, and the rest as 1. + id: 6321 + time: '2024-09-07T00:53:13.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/872 +- author: VMSolidus + changes: + - type: Tweak + message: >- + All neck slot Loadout items have had their costs significantly reduced, + most of them to 0. + id: 6322 + time: '2024-09-07T00:55:49.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/874 +- author: VMSolidus + changes: + - type: Tweak + message: Eyewear have had their loadout point costs significantly reduced. + id: 6323 + time: '2024-09-07T01:04:42.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/879 +- author: VMSolidus + changes: + - type: Tweak + message: All masks have had their loadout point costs substantially reduced. + id: 6324 + time: '2024-09-07T01:06:11.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/875 +- author: Remuchi + changes: + - type: Add + message: >- + Interacting with some materials now opens the radial menu containing + some of the most used recipes of that material. + id: 6325 + time: '2024-09-07T03:59:44.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/861 +- author: Mnemotechnician + changes: + - type: Add + message: >- + Your character size now affects your blood level. Smaller characters + will have less blood, and larger characters will have more. + id: 6326 + time: '2024-09-08T14:39:19.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/858 +- author: Mnemotechnician + changes: + - type: Remove + message: >- + Quick swap has been disabled for most item slots. This primarily means + you will have to eject power cells/magazines from items/weapons/borgs + before replacing them with different ones. + id: 6327 + time: '2024-09-08T14:42:30.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/856 +- author: router + changes: + - type: Tweak + message: Female slimes no longer have movie screams. + id: 6328 + time: '2024-09-08T14:44:29.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/849 +- author: Mnemotechnician + changes: + - type: Add + message: Chat bubbles now use the font & color of the language of the message. + id: 6329 + time: '2024-09-08T14:51:20.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/812 +- author: Mnemotechnician + changes: + - type: Add + message: >- + A shrimp morphotype was added to the failure pool of the metempsychotic + machine. + id: 6330 + time: '2024-09-08T14:56:13.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/882 +- author: Mnemotechnician + changes: + - type: Fix + message: >- + Multiple issues with translators were fixed. Additionally, you can now + hold multiple handheld translators at once without issues. + id: 6331 + time: '2024-09-08T15:08:37.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/834 +- author: VMSolidus + changes: + - type: Add + message: Recyclers can now once again eat people when emagged. + - type: Add + message: >- + The ability for emagged Recyclers to eat people is now controlled by the + CVar "reclaimer.allow_gibbing". + - type: Add + message: >- + Recyclers require power to eat people. No more dragging emagged + recyclers into crowds. + id: 6332 + time: '2024-09-08T15:15:56.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/822 +- author: BlueHNT + changes: + - type: Add + message: Added bwoink hammer with bwoink sound + id: 6333 + time: '2024-09-08T15:16:09.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/759 +- author: VMSolidus + changes: + - type: Add + message: 'A new console command, AddPsionicPower has been added. ' + id: 6334 + time: '2024-09-08T15:16:29.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/807 +- author: VMSolidus + changes: + - type: Add + message: Glacier, Core, and Saltern now have assistant job slots. + - type: Add + message: >- + Glacier, Core, and Saltern are now able to be voted on for map + selection. + id: 6335 + time: '2024-09-09T04:13:06.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/838 +- author: VMSolidus + changes: + - type: Tweak + message: >- + Wide Swing is no longer possible as a default on all weapons, the + default is now Power Attack (10 stamina spent, for 20% bonus damage to + single target only). Only a small number of weapons designed to + "Cleave", still have their wide swing functionality. + id: 6336 + time: '2024-09-09T04:13:14.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/866 +- author: VMSolidus + changes: + - type: Fix + message: >- + Arrivals has been re-enabled. I feel sorry for all the people who + thought we had disabled it. This was a legacy toggle that was leftover + from when we forked DeltaV + id: 6337 + time: '2024-09-09T04:13:50.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/871 +- author: VMSolidus + changes: + - type: Fix + message: >- + Felinids and Harpies will now correctly never set off floor traps, such + as landmines and mouse traps. + - type: Add + message: >- + Trap Avoider has been added as a new trait, allowing characters to buy + the innate ability to avoid floor traps. I would have named this "Light + Step", after the trait from Fallout that shares its name and effect, but + someone already gave that name to a different trait entirely. + id: 6338 + time: '2024-09-09T04:14:44.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/884 +- author: VMSolidus + changes: + - type: Add + message: >- + Arm and Leg markings are now split between individual arms, hands, legs, + and feet. + id: 6339 + time: '2024-09-09T05:30:34.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/870 +- author: VMSolidus + changes: + - type: Add + message: >- + The Antag Refactor has been once again added back in. More antags will + be coming soon. + id: 6340 + time: '2024-09-09T14:21:56.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/794 +- author: Mnemotechnician + changes: + - type: Add + message: >- + The "food recipes" page in guidebook now contains an automatically + generated list of food recipes. + id: 6341 + time: '2024-09-11T17:55:11.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/783 +- author: VMSolidus + changes: + - type: Add + message: Rat Kings have returned to the default event rotation. + id: 6342 + time: '2024-09-11T19:59:32.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/903 +- author: VMSolidus + changes: + - type: Add + message: Jetpacks now work in zero gravity. + - type: Add + message: >- + New CCVar "jetpack.enable_anywhere" allows server hosts to optionally + make it so that jetpacks can be used anywhere, even in gravity and on + grids. + - type: Add + message: >- + New CCVar "jetpack.enable_in_no_gravity" allows server hosts to + optionally disable jetpacks being usable in zero gravity. + id: 6343 + time: '2024-09-11T19:59:45.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/901 +- author: VMSolidus + changes: + - type: Add + message: >- + Medical Job related items are now part of a Medical loadout group, and + have all been significantly discounted. + id: 6344 + time: '2024-09-11T20:00:07.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/902 +- author: Tilkku + changes: + - type: Tweak + message: Penlight Exam Messages + - type: Fix + message: Penlights now need to be on in order to examine + - type: Fix + message: Penlights can no longer be used on self + id: 6345 + time: '2024-09-11T20:02:51.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/900 +- author: Mnemotechnician + changes: + - type: Fix + message: Fixed a couple issues with the language menu UI and translators. + id: 6346 + time: '2024-09-11T20:03:25.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/893 +- author: Mocho + changes: + - type: Add + message: >- + Cargo Job related items are now part of a Cargo loadout group, and have + all been significantly discounted. + id: 6347 + time: '2024-09-11T20:27:13.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/909 +- author: Mocho + changes: + - type: Add + message: >- + Security Job related items are now part of a Security loadout group, and + have all been significantly discounted. + id: 6348 + time: '2024-09-11T20:39:55.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/908 +- author: Mocho + changes: + - type: Add + message: >- + Service Job related items are now part of a Service loadout group, and + have all been significantly discounted. + - type: Tweak + message: >- + Musicians now have significant discounts on all instruments in loadouts, + but can only take a maximum of 3. + id: 6349 + time: '2024-09-11T20:52:21.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/906 +- author: Mocho + changes: + - type: Add + message: >- + Engineering Job related items are now part of a Engineering loadout + group, and have all been significantly discounted. + id: 6350 + time: '2024-09-11T21:03:37.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/907 +- author: Squishy77 + changes: + - type: Add + message: >- + added rcds to the list of possible re and ported over the re update from + delta + - type: Tweak + message: >- + tweaked the costs of boards re-machine boards to be more consistent with + their post hyper lathe update counter parts + id: 6351 + time: '2024-09-12T00:12:39.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/910 +- author: SleepyScarecrow + changes: + - type: Tweak + message: Ethanol Contents + - type: Fix + message: SnowWhite Recipe + - type: Tweak + message: Ethanol overdose changed from 15 to 45 + id: 6352 + time: '2024-09-12T00:14:06.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/911 +- author: Mnemotechnician + changes: + - type: Add + message: Ported a number of jukebox songs from Delta-V. + id: 6353 + time: '2024-09-12T00:14:56.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/912 +- author: Skubman + changes: + - type: Fix + message: >- + Blood Deficiency now makes you lose a consistent percentage of blood + regardless of your blood volume, which is dictated by size. This makes + the time to low blood and death consistent for every character of all + sizes with this trait. + id: 6354 + time: '2024-09-12T00:15:18.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/895 +- author: stellar-novas + changes: + - type: Add + message: Airlocks once again have access wires. Happy hacking! + id: 6355 + time: '2024-09-14T00:48:34.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/916 +- author: Mocho + changes: + - type: Add + message: >- + Science Job related items are now part of a Science loadout group, and + have all been significantly discounted. + - type: Add + message: Added more clothing to the science section in loadouts. + - type: Tweak + message: Adjusted command loadout item pricing. + - type: Tweak + message: Added localization strings for the new loadout groups. + id: 6356 + time: '2024-09-14T01:00:26.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/914 +- author: VMSolidus + changes: + - type: Add + message: >- + Added missing Localizations for Departments, Jobs, and Loadout Item + Groups. + - type: Add + message: >- + Bartender-Specific loadouts! Bartenders can now choose to spawn with + either their classic shotgun, or a Mosin Nagant that comes preloaded + with rubber bullets. + id: 6357 + time: '2024-09-14T01:12:40.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/915 +- author: CilliePaint + changes: + - type: Tweak + message: Stuffed Lawyer's Pockets with Space Law guides! + - type: Remove + message: Took away Atmos backpacks as Starting equipment. + id: 6358 + time: '2024-09-14T05:41:40.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/897 +- author: Mnemotechnician + changes: + - type: Add + message: >- + You can now choose whether you want to walk or run by default in the + settings. + id: 6359 + time: '2024-09-14T05:44:56.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/889 +- author: VMSolidus + changes: + - type: Add + message: >- + Drug Addictions! Drug addictions are long lasting Moodlet-Pairs. The + first consisting of a temporary Mood bonus that is refreshed by + consuming the drug, and the second consisting of an extremely long + lasting mood penalty, which replaces the mood bonus should you go a long + enough time between consuming the drug in question. + - type: Add + message: Nicotine Addiction has been added as a new minor negative trait. + - type: Add + message: >- + Drugs/Reagents can now affect your character's Mood! Both with, and + without Addiction. + - type: Add + message: >- + TraitSystem has had functionality added for directly adding Moodlets to + a character upon joining the round, such as drug addictions, or + permanent mood modifications like Sanguine/Saturnine + - type: Add + message: >- + Lotophagoi Oil now induces an extremely powerful drug addiction, + providing an extremely large mood benefit for a short time. Which when + it wears off, creates an equally extreme mood penalty that lasts for a + very long time(or until you drink more Loto Oil). + id: 6360 + time: '2024-09-14T06:48:55.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/896 +- author: Mnemotechnician + changes: + - type: Fix + message: Cyborg recharging stations finally work again. + - type: Fix + message: >- + Rehydratable entities (such as monkey cubes) no longer spawn a second + client-side entity when rehydrated. + id: 6361 + time: '2024-09-14T17:55:41.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/888 +- author: VMSolidus + changes: + - type: Tweak + message: The Uncloneable trait is only worth one point now. + id: 6362 + time: '2024-09-14T18:01:21.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/868 +- author: Mocho + changes: + - type: Add + message: >- + Harpies are now able to fly on station for limited periods of time, + moving faster at the cost of stamina. + id: 6363 + time: '2024-09-16T04:41:44.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/919 +- author: Mnemotechnician + changes: + - type: Add + message: >- + Pulling has been reworked. Your character will now try to continuously + push the pulled entity when you use the push keybind. + - type: Remove + message: >- + You can no longer push the pulled entity while walking, and pushing now + follows the momentum conservation laws. + id: 6364 + time: '2024-09-17T23:38:45.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/883 +- author: VMSolidus + changes: + - type: Tweak + message: >- + Core now has a 6-Core AME, which is supplied with two jars of fuel. This + should give Engineers significantly more than 20 minutes of time to + setup the Supermatter engine. + id: 6365 + time: '2024-09-17T23:39:07.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/927 +- author: JayJacobs + changes: + - type: Tweak + message: Changed the sprite of the barber chair. + - type: Fix + message: Fixed bench textures. + id: 6366 + time: '2024-09-17T23:39:57.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/925 +- author: zelezniciar + changes: + - type: Tweak + message: The fireaxe once again can pry subfloors + id: 6367 + time: '2024-09-17T23:40:13.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/921 +- author: VMSolidus + changes: + - type: Fix + message: >- + Cloning Consoles will now correctly state when a body has the + Uncloneable trait. + id: 6368 + time: '2024-09-18T04:30:39.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/926 +- author: Spatison + changes: + - type: Add + message: Added lying down system / Добавлена система лежания + - type: Tweak + message: >- + Lying down now uses do-afters that are visible to other people to + indicate what is going on. + - type: Add + message: Added telescope system / Добавлена система прицеливания + - type: Tweak + message: Now you can aim from Hristov / Теперь можно прицеливаться из Христова + id: 6369 + time: '2024-09-19T02:18:35.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/815 +- author: FoxxoTrystan + changes: + - type: Remove + message: DeltaV Option Tab (Options moved) + id: 6370 + time: '2024-09-19T02:24:25.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/928 +- author: Hell_Cat + changes: [] + id: 6371 + time: '2024-09-19T03:29:41.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/929 +- author: VMSolidus + changes: + - type: Fix + message: >- + Fixed issues with the LayingDownSystem. Laying down no longer causes all + entities on your screen to lay down at once. Using any movement input no + longer causes the character to appear to the player as to be laying + down. + id: 6372 + time: '2024-09-19T20:37:51.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/933 +- author: VMSolidus + changes: + - type: Fix + message: >- + UniversalLanguageSpeaker(And Xenoglossy by extension) will now appear in + your language menu alongside other known languages, rather than replace + all known languages. You can effectively now choose whether or not to + speak it, or to use a normal language. + - type: Add + message: Traits can now add Languages directly. + - type: Add + message: Traits can now remove Languages directly. + id: 6373 + time: '2024-09-20T01:47:52.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/899 +- author: VMSolidus + changes: + - type: Add + message: >- + Melee Weapons can now individually define their interactions with the + ContestsSystem. + - type: Add + message: >- + Added the ContestArgs type, allowing arbitrarily any component to + contain a list of arguments for ContestSystems. + - type: Add + message: >- + Added the ContestConstructor, a new type of meta-Contest that enables + other systems to use components to define all possible contest + behaviors, rather than needing to hardcode specific interactions. + id: 6374 + time: '2024-09-20T02:48:43.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/934 +- author: gluesniffler + changes: + - type: Add + message: >- + Adds an optional server variable which allows entities to crawl under + tables. + - type: Tweak + message: >- + Tables and plastic flaps are less resistant to damage, and can now be + targeted by guns by aiming on top of them. + id: 6375 + time: '2024-09-20T19:34:02.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/939 +- author: ODJ + changes: + - type: Tweak + message: Tweaked melee; Less stamina usage on heavy attacks. + id: 6376 + time: '2024-09-20T19:34:46.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/938 +- author: VMSolidus + changes: + - type: Tweak + message: >- + JukeOperator now allows for JukeDuration and JukeCooldown arguments. + JukeCooldown is a new feature where enemies must wait an amount of time + in seconds equal to the JukeCooldown, before they are allowed to attempt + to dodge a melee attack. + - type: Tweak + message: >- + By default, NPCs will only attempt to dodge attacks once every 5 + seconds. + - type: Fix + message: >- + JukeOperator now performs extremely expensive math 5000 times less + often. EXIT CONDITIONS PEOPLE! + id: 6377 + time: '2024-09-20T20:05:31.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/935 +- author: VMSolidus + changes: + - type: Add + message: >- + All "Long-arms", Rifles, Light Machine Guns, Shotguns, now require + wielding to use. + id: 6378 + time: '2024-09-20T20:35:43.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/913 +- author: zelezniciar + changes: + - type: Add + message: Added Atmospheric Alerts Computer + id: 6379 + time: '2024-09-20T21:46:38.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/922 +- author: VMSolidus + changes: + - type: Add + message: 'A basic Languages menu has been added into the Traits tab. ' + - type: Add + message: >- + Tau-Ceti Basic, Tradeband, Freespeak, Elyran Standard, Sol Common, and + Sign Language have been added to the Languages tab. + id: 6380 + time: '2024-09-20T21:46:47.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/936 +- author: VMSolidus + changes: + - type: Remove + message: >- + Removed the "Become Psionic" traitor objective. It was literally + impossible to fail if you took the Latent Psychic trait, and literally + impossible to greentext if you didn't. + id: 6381 + time: '2024-09-20T21:47:16.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/940 +- author: VMSolidus + changes: + - type: Add + message: Part Upgrading has returned! + id: 6382 + time: '2024-09-21T22:46:49.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/917 +- author: Spatison + changes: + - type: Add + message: Added discounts in uplink / Добавлены скидки в аплинк + id: 6383 + time: '2024-09-21T22:46:59.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/930 +- author: VMSolidus + changes: + - type: Add + message: >- + Healing Word has been added as a new Psionic Power. When cast on another + person, it heals a small amount of every damage type(scaling with + Casting Stats), while also reducing rot timers. Healing Word has a very + short cooldown, and a fairly low Glimmer cost. + - type: Add + message: >- + Breath of Life has been added as a new extremely rare Psionic Power. + When cast on another person, it heals a large amount of damage(scaling + with Casting Stats), while also substantially reducing rot timers. + Additionally, it will revive the target if it is possible to do so. + Breath of Life has an incredibly long cooldown, a long interuptable cast + time, and an extraordinarily high glimmer cost(A typical Psion will + spike glimmer by more than 50 points when casting it). + - type: Add + message: The Chaplain now starts with the Healing Word power. + id: 6384 + time: '2024-09-21T22:50:01.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/942 +- author: VMSolidus + changes: + - type: Add + message: >- + Bottles, Drink Glasses, Plates, and all liquid containers are now struck + by bullets(and most likely destroyed in the process). We hope that this + will offer both a small tactical advantage/disadvantage, as well as + contribute to making gunfights around the bar more "Cinematic". + - type: Add + message: >- + Chairs are now hit by projectiles if a shooter clicks on them, in + addition to Tables. + id: 6385 + time: '2024-09-21T23:22:43.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/943 +- author: VMSolidus + changes: + - type: Fix + message: >- + Fixed a bug where Breath of Life and Healing Word would set living + people to crit, which would knock them down. + - type: Fix + message: >- + Fixed a bug where Breath of Life could revive people who were rotten(It + will still heal them and reduce the rot timer as intended, but will not + revive unless said rot timer reduction brings them below the 10 minute + threshold). + - type: Add + message: >- + The Do-After bar for Breath of Life and Healing Word is now hidden if + glimmer is low enough. The threshold for which scales with your + Dampening stat. More Dampening = stealthier casting. + id: 6386 + time: '2024-09-24T00:56:34.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/949 +- author: Mocho + changes: + - type: Add + message: >- + Added a lot of recipes to the quick construction menus. Give it a shot + by pressing Z with different construction materials in your hand! + id: 6387 + time: '2024-09-24T00:56:45.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/945 +- author: VMSolidus + changes: + - type: Fix + message: >- + IPC now weigh 71kg by default instead of 5kg. Seriously who the fuck + made the BeepBoops out of feathers!?! + id: 6388 + time: '2024-09-25T05:09:06.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/959 +- author: fenndragon + changes: + - type: Add + message: Added new neutral xenos, events, and reagent slimes + id: 6389 + time: '2024-09-26T15:06:54.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/956 +- author: VMSolidus + changes: + - type: Add + message: Backpacks, Duffelbags, and Satchels have been added to loadouts. + - type: Remove + message: >- + The button for "Backpack Preference" has been removed. Backpacks + selection is now done via Loadouts. + - type: Remove + message: >- + The button for "Suit/Skirt Preference" has also been removed. Suit + selection is done via Loadouts. + id: 6390 + time: '2024-09-28T00:46:19.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/957 +- author: VMSolidus + changes: + - type: Fix + message: Fixed a bug where the button to reboot IPCs wasn't appearing. + id: 6391 + time: '2024-09-28T02:23:54.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/963 +- author: VMSolidus + changes: + - type: Fix + message: Fix a crash related to Luminous Entities. + id: 6392 + time: '2024-09-28T02:28:40.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/954 +- author: VMSolidus + changes: + - type: Fix + message: >- + Smoking cigarettes or drinking loto oil will no longer spam moodlet + popup messages. + id: 6393 + time: '2024-09-28T02:30:18.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/948 +- author: Mnemotechnician + changes: + - type: Tweak + message: Noospheric events should once again occur on lower glimmer levels. + id: 6394 + time: '2024-09-28T02:43:48.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/969 +- author: Mnemotechnician + changes: + - type: Tweak + message: >- + Pushing something no longer causes your character to move, but only if + there is gravity holding you down. + - type: Tweak + message: >- + Pushing an object with Ctrl-RMB now pushes it continuously for up to 5 + seconds instead of 2. + id: 6395 + time: '2024-09-28T02:44:07.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/966 +- author: VMSolidus + changes: + - type: Add + message: Humans can now have tail and ear markings. + id: 6396 + time: '2024-09-28T03:10:14.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/964 +- author: VMSolidus + changes: + - type: Add + message: >- + Harpies now have their own species-specific language, called Valyrian + Standard + - type: Add + message: >- + Valyrian Standard can now be bought as an extra language in the Traits + menu. + id: 6397 + time: '2024-09-28T14:42:50.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/968 +- author: VMSolidus + changes: + - type: Fix + message: >- + Fixed a bug with Healing Word and Breath of Life where if the caster was + moved by another person mid-cast, they would permanently become unable + to cast it again. + id: 6398 + time: '2024-09-29T01:43:20.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/970 +- author: VMSolidus + changes: + - type: Remove + message: >- + Removed the Psionics Guidebook. It was straight up wrong about most + things, and is not ever going to be updated to match new Psionics + Content. You are fully intended to "FIND OUT IN GAME" how things work + through guessing, trial and error, and just pure happenstance. + Experiment with shit. The popups are intentionally vague. You're + supposed to think about what they mean. Stop asking admins in Ahelps + what the vague text means. + id: 6399 + time: '2024-09-29T01:43:32.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/972 +- author: Aikakakah + changes: + - type: Fix + message: Fixed markings having incomplete names for color sections + - type: Fix + message: Fixed IPC marking categories + id: 6400 + time: '2024-09-29T01:58:28.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/974 +- author: VMSolidus + changes: + - type: Fix + message: >- + Nerfed the fuck out of Reagent Slimes. They have 1/3rd as much HP, spawn + half as many during reagent slime vents, deal significantly less damage, + and inject a lot less chems when biting people. + id: 6401 + time: '2024-09-29T17:42:43.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/977 +- author: Mnemotechnician + changes: + - type: Fix + message: >- + Fixed multiple minor issues with interaction verbs. Most importantly, + interaction popups will now always be shown correctly. + id: 6402 + time: '2024-09-30T16:27:47.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/965 +- author: Spatison + changes: + - type: Fix + message: >- + The size of the Lobby cannot be changed now / Размер лобби теперь нельзя + изменить + id: 6403 + time: '2024-09-30T16:29:47.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/962 +- author: VMSolidus + changes: + - type: Add + message: Added Shadeskip as a new psionic power. + id: 6404 + time: '2024-09-30T18:04:20.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/987 +- author: VMSolidus + changes: + - type: Add + message: 'Mood System now has some interactions with Psionics. ' + id: 6405 + time: '2024-09-30T18:28:21.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/988 +- author: VMSolidus + changes: + - type: Add + message: >- + 7 new Psionic-related Traits have been added to the game: High + Psi-Potential, Low Psi-Potential, Power Overwhelming, kα Abundance, kα + Deficiency, kδ Proficient, and kδ Defect + - type: Add + message: >- + Oni now have a built-in negative trait that acts as a more severe + version of Low Psi-Potential. Oni now have significant penalties to + generating new Psionic Powers. + id: 6406 + time: '2024-09-30T18:28:31.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/983 +- author: Mnemotechnician + changes: + - type: Add + message: >- + Narcolepsy has been reworked. You can now know when you're about to fall + asleep, and can choose to go to sleep willingly to reset the narcolepsy + timer and avoid an incident. + id: 6407 + time: '2024-09-30T19:04:22.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/967 +- author: zelezniciar + changes: + - type: Fix + message: >- + Space dragons that get butchered or gibbed will now drop devoured + corpses. + id: 6408 + time: '2024-10-01T21:34:55.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/992 +- author: VMSolidus + changes: + - type: Fix + message: >- + Rampant Brand Intelligence has been unreverted. It was reverted by + complete accident a few weeks ago. I apologize to anyone who missed the + thrill of killing murderous vending machines. + id: 6409 + time: '2024-10-01T21:39:51.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/947 +- author: VMSolidus + changes: + - type: Fix + message: >- + Fixed the Markings menu by adding a scroll container to it. Markings + with greater than 2 color selections can now be used. + id: 6410 + time: '2024-10-01T21:43:17.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/997 +- author: VMSolidus + changes: + - type: Add + message: Immovable Rod Event has returned + - type: Add + message: >- + Immovable Rod Event has been made more fair. It won't automatically gib + people, just crit them with a ton of blunt damage(That you can survive + by wearing good armor), or just being an Oni funnily enough. + id: 6411 + time: '2024-10-02T01:31:29.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/973 +- author: VMSolidus + changes: + - type: Add + message: >- + Added a full Guidebook entry for Harpies. Unlike previous species + guidebooks, this document contains extensive lore and background + information for Harpies, in addition to notes on their game mechanics. + id: 6412 + time: '2024-10-02T01:32:07.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/993 +- author: VMSolidus + changes: + - type: Add + message: 13 new Kemonomimi-Style markings. 4 Ears, and 9 new tails! + id: 6413 + time: '2024-10-02T01:32:29.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/981 +- author: fenndragon + changes: + - type: Tweak + message: 'Tweaked the spawnrate of certain mobs within the ventcritters rule. ' + id: 6414 + time: '2024-10-02T01:34:19.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/982 +- author: VMSolidus + changes: + - type: Add + message: Grapple & Tether Guns have been re-added. + id: 6415 + time: '2024-10-02T02:19:46.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1000 +- author: Fansana + changes: + - type: Fix + message: Fixes door access for mail doors + id: 6416 + time: '2024-10-02T23:14:55.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/986 +- author: VMSolidus + changes: + - type: Add + message: Added Telekinetic Pulse power. + - type: Fix + message: Fixed a crash related to Shadeskip + id: 6417 + time: '2024-10-04T00:14:21.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1002 +- author: FoxxoTrystan + changes: + - type: Tweak + message: Directional Windows and Tables has gotten new sprites! + - type: Fix + message: AirAlarm Inverted Alert States has been fixed! + id: 6418 + time: '2024-10-04T00:25:33.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/958 +- author: VMSolidus + changes: + - type: Add + message: >- + Ported a Respawn System. This system allows players to return themselves + to the lobby, while also requiring that if they re-enter the round, that + they must do so on a different character. + id: 6419 + time: '2024-10-04T00:43:37.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/998 +- author: Mnemotechnician + changes: + - type: Fix + message: Winter boots are no longer able to protect you from insane temperatures. + id: 6420 + time: '2024-10-04T00:52:11.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1004 +- author: VMSolidus + changes: + - type: Fix + message: >- + Fixed a bug with CloningSystem. It now correctly spills blood upon + cloning fail. + id: 6421 + time: '2024-10-05T14:28:32.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1009 +- author: Mocho + changes: + - type: Fix + message: >- + Fixed downed entities having their draw depth set incorrectly after + being picked up, or laying on a bed. + id: 6422 + time: '2024-10-07T15:28:36.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1017 +- author: VMSolidus + changes: + - type: Tweak + message: >- + Nerfed Lotophagoi Moodlets so that both the positive and negative + moodlets only last for 10 minutes. The total mood modification has been + left untouched. + id: 6423 + time: '2024-10-07T15:29:05.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1013 +- author: VMSolidus + changes: + - type: Add + message: >- + Shadeskip no longer creates Stationwide Shadow Kudzu when casting at + high glimmer. + id: 6424 + time: '2024-10-07T15:29:16.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1012 +- author: VMSolidus + changes: + - type: Add + message: >- + The Salvage Shittle has been replaced with the Pathfinder Expedition + Vessel from Frontier. + id: 6425 + time: '2024-10-09T01:17:11.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1027 +- author: VMSolidus + changes: + - type: Add + message: Salvage techs can now buy a Crusher Dagger in their loadout. + - type: Fix + message: Re-Added the Crusher weapons to salvage spawners. + id: 6426 + time: '2024-10-09T01:17:38.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1026 +- author: Ichaie + changes: + - type: Add + message: A new map called NCS Gax has been added to rotation. + id: 6427 + time: '2024-10-09T01:37:57.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1019 +- author: VMSolidus + changes: + - type: Tweak + message: Almost all security loadout gear is now free. + - type: Remove + message: >- + All Security roles no longer spawn with a pistol by default. This has + been moved to Loadouts. + - type: Add + message: >- + Security characters can now choose from a selection of different + firearms to use as their Duty Weapon. Disablers, Mk58, and Inspector + revolvers are free, fancier weapons such as an N1984 can be bought with + loadout points. + - type: Add + message: Oni can opt to take a Truncheon in lieu of a handgun. + - type: Add + message: >- + Security characters can now take handgun magazines and spare magazines + in their loadouts. + - type: Add + message: Security Backpacks to loadouts. + - type: Fix + message: >- + Fixed issues with the security belt options(webbing, belt, holster) not + correctly replacing the original item, and also not containing items + either. + id: 6428 + time: '2024-10-09T15:37:08.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1025 +- author: VMSolidus + changes: + - type: Fix + message: >- + Fixed High & Low Psi-Potential traits lacking a requirement that you + don't have the opposite trait. The two were mutually exclusive, so + taking both caused issues. + id: 6429 + time: '2024-10-09T15:37:29.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1022 +- author: VMSolidus + changes: + - type: Fix + message: Fixed localizations for the Sinta'Azaziba language. + id: 6430 + time: '2024-10-09T15:39:59.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1020 +- author: Mnemotechnician + changes: + - type: Add + message: >- + Animals now have more unique things to say when not controlled by a + player. + id: 6431 + time: '2024-10-09T15:48:57.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1003 +- author: VMSolidus + changes: + - type: Fix + message: Glimmer Mites can no longer spawn anywhere other than "THE" Station. + id: 6432 + time: '2024-10-09T16:12:10.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/976 +- author: FoxxoTrystan + changes: + - type: Add + message: Custom Species Names. + id: 6433 + time: '2024-10-09T16:12:20.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/994 +- author: VMSolidus + changes: + - type: Add + message: >- + Logistics can now directly order a variety of hardsuits. "NT In-Network" + hardsuits being substantially cheaper thanks to backroom deals. + id: 6434 + time: '2024-10-09T23:01:29.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1024 +- author: DangerRevolution + changes: + - type: Add + message: Resprited basic Service Staff uniforms + - type: Add + message: Add rolled sleeve variants for Service Staff uniforms. + id: 6435 + time: '2024-10-10T16:21:24.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1021 +- author: VMSolidus + changes: + - type: Remove + message: The X-01 multiphase energy gun has been removed from the HoS locker. + - type: Add + message: >- + A new Loadout Item Group, "Head of Security's Antique Weapon Collection" + has been added. Rather than being given a unique weapon by whatever map + creators put in the HoS Office, each Head of Security is entitled to a + single item selected from this new loadout category, free of charge. But + beware, whatever item chosen from this list may potentially be used as a + Steal Objective for traitors. You may have a fancy new submachine gun, + but it also makes you a target. + - type: Add + message: >- + antique Bulldog, antique C-20r submachine gun, x-01 multiphase energy + gun, antique energy sword, antique pulse pistol, antique Wt550, and a + pair of Katana Sheaths have all been added as loadout options for the + Head of Security. + id: 6436 + time: '2024-10-11T01:43:36.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1031 +- author: Ichaie + changes: + - type: Add + message: A map called "RadStation" has been added to rotation. + - type: Add + message: A new Evac shuttle called "emergency_neol" has been added to RadStation + id: 6437 + time: '2024-10-11T16:05:53.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1034 +- author: Mnemotechnician + changes: + - type: Add + message: >- + Sleeping and muzzled entities can no longer use vocal emotes (scream, + meow, etc). + id: 6438 + time: '2024-10-11T16:08:48.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1037 +- author: VMSolidus + changes: + - type: Add + message: >- + Added Bluespace and Normality ore as very rare ore types. Salvage can + occasionally find these ores on Asteroids or on Expedition planets. + Bluespace and Normality Ore can be smelted in an Ore Processor into + Bluespace and Normality Crystals. Make sure to bring these to Epistemics + so that they can do their job. + id: 6439 + time: '2024-10-12T20:49:32.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1042 +- author: Fansana + changes: + - type: Fix + message: Fixes the arrivals shuttle choosing to correct docking port. + id: 6440 + time: '2024-10-13T16:38:30.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1040 +- author: FoxxoTrystan + changes: + - type: Fix + message: Oneirophage is once more psionic invisible in webs. + id: 6441 + time: '2024-10-13T18:33:59.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1033 +- author: Mnemotechnician + changes: + - type: Add + message: >- + You can now toggle crawling under furniture! The default keybind is + Shift-R, you can change it in settings. + id: 6442 + time: '2024-10-13T18:34:58.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1036 +- author: DEATHB4DEFEAT + changes: + - type: Tweak + message: Made the show clothing/loadouts button labels more clear + id: 6443 + time: '2024-10-13T18:41:16.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1030 +- author: VMSolidus + changes: + - type: Tweak + message: Brains now can no longer be eaten. + id: 6444 + time: '2024-10-13T18:51:26.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1044 +- author: VMSolidus + changes: + - type: Add + message: >- + Added 8 new Physical Traits. These are, Cyber-Eyes Basic System(Plus 4 + different modular options), Bionic Arm, Dermal Armor, and Platelet + Factories. + id: 6445 + time: '2024-10-13T19:25:05.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1035 +- author: VMSolidus + changes: + - type: Add + message: >- + NT has started hiring contractors from other corporations to fill out + station staff. New loadout items for corporate contractors are now + available for Bartender, Botanist, Cataloguer, Chef, and Janitor. + Currently there are no jumpskirt versions of these uniforms. We are + looking for people willing to make skirt variations of each of these. + id: 6446 + time: '2024-10-13T19:26:23.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1041 +- author: Mnemotechnician + changes: + - type: Add + message: >- + The Courier and Logistics Officer now have a new program in their PDA + for tracking mail delivery performance, including earnings and percent + of packages opened, damaged, or expired. + - type: Add + message: >- + The list of possible mail packages has been greately expanded, and now + includes large parcels. + - type: Add + message: >- + The CourierDrobe now offers a rapid mail delivery device, along with + capsules for it. + id: 6447 + time: '2024-10-13T19:38:19.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1011 +- author: DEATHB4DEFEAT + changes: + - type: Add + message: Merged 400 WizDen PRs. Happy testing! + id: 6448 + time: '2024-10-14T16:10:08.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/944 +- author: Aidenkrz + changes: + - type: Fix + message: Reverse engineering machine UI works again. + id: 6449 + time: '2024-10-15T23:05:13.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1053 +- author: Aidenkrz + changes: + - type: Fix + message: Arachne no longer turn into errors when they take damage + id: 6450 + time: '2024-10-15T23:05:44.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1054 +- author: Aidenkrz + changes: + - type: Tweak + message: >- + Cocoon sizing has been changed to reflect the size of the entity inside + better. + id: 6451 + time: '2024-10-15T23:07:03.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1055 +- author: Ichaie + changes: + - type: Fix + message: >- + Gax station: fixed arrivals being unable to dock due to meteor shielding + and translated security camera names to English. + id: 6452 + time: '2024-10-15T23:12:44.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1048 +- author: VMSolidus + changes: + - type: Tweak + message: >- + Psionic Rolls should now generate larger, random amounts of Potentia. + This should make it a lot easier to obtain powers. + id: 6453 + time: '2024-10-15T23:13:13.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1047 +- author: Aidenkrz + changes: + - type: Fix + message: You can properly lay down and stand up on tables now. + id: 6454 + time: '2024-10-16T03:08:07.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1057 +- author: Aidenkrz + changes: + - type: Tweak + message: Height and width are now constrained by each other. + - type: Fix + message: Humanoids can no longer phase through walls. + id: 6455 + time: '2024-10-16T12:54:15.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1049 +- author: Aidenkrz + changes: + - type: Fix + message: Bluespace crystals now properly eject from lathes. + - type: Fix + message: Bluespace parts can be reverse engineered. + id: 6456 + time: '2024-10-16T12:55:19.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1052 +- author: VMSolidus + changes: + - type: Fix + message: 'Fixed Harpy singing not opening the Midi Player. ' + id: 6457 + time: '2024-10-16T22:51:18.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1056 +- author: Mnemotechnician + changes: + - type: Fix + message: Soap once again can be used to clean evidence off. + id: 6458 + time: '2024-10-16T22:54:33.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1061 +- author: Aidenkrz + changes: + - type: Tweak + message: >- + All spiders, arachne, and arachnids can cocoon mobs, and drink their + blood. + id: 6459 + time: '2024-10-17T19:21:01.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1058 +- author: zelezniciar + changes: + - type: Fix + message: Fixes Air Alarms not entering danger/warning state when pressure is low + id: 6460 + time: '2024-10-19T08:25:27.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/996 +- author: VMSolidus + changes: + - type: Fix + message: >- + Fixed issues with Contests System that allowed mobs such as Space Carps + to smash through barriers that were not intended to be breakable by said + mobs. + id: 6461 + time: '2024-10-19T08:26:32.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1069 +- author: VMSolidus + changes: + - type: Fix + message: >- + Fixed Cryptobiolin not providing temporary Psionic Insulation. It now + provides protection from psionic abilities and events for 15 minutes. + id: 6462 + time: '2024-10-19T08:28:28.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1081 +- author: VMSolidus + changes: + - type: Add + message: Arena now has a properly functional Arrivals Dock. + id: 6463 + time: '2024-10-19T08:30:22.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1086 +- author: VMSolidus + changes: + - type: Fix + message: 'addpsionicpower command now works. ' + id: 6464 + time: '2024-10-19T08:34:03.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1095 +- author: VMSolidus + changes: + - type: Add + message: >- + The Syndicate has significantly expanded their Listening Outpost + operations, featuring larger, better defended outposts. These new + outposts also serve as supply depots for Syndicate agents in the field. + id: 6465 + time: '2024-10-19T08:45:44.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1065 +- author: VMSolidus + changes: + - type: Fix + message: >- + Fixed the "Steal The Head of Security's Weapon" so that it is now a + single objective that counts for any anique weapon chosen by the Head of + Security. The objective will also once again correctly not generate if + there are no valid items for it on the station. + id: 6466 + time: '2024-10-19T21:28:16.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1084 +- author: Mocho + changes: + - type: Add + message: Added an uplink with 20TC for all Listening Post Operatives + id: 6467 + time: '2024-10-19T21:28:50.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1062 +- author: FoxxoTrystan + changes: + - type: Add + message: Job Whitelist! + id: 6468 + time: '2024-10-19T22:10:51.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1093 +- author: VMSolidus + changes: + - type: Fix + message: Ported literally all of Wizden's Wielding Quality Of Life improvements. + id: 6469 + time: '2024-10-20T01:45:50.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1078 +- author: VMSolidus + changes: + - type: Add + message: >- + Being in an Atmosphere(such as Planets like Glacier & Nukieworld) + bypasses the limitation of "NO TALKING IN SPACE" + id: 6470 + time: '2024-10-20T05:40:19.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1089 +- author: VMSolidus + changes: + - type: Fix + message: >- + Breath of Life now correctly displays its esoteric text popup if the + right conditions are met. + id: 6471 + time: '2024-10-20T16:03:28.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1109 +- author: FoxxoTrystan + changes: + - type: Add + message: Added a new species, Shadowkin! + id: 6472 + time: '2024-10-20T16:31:21.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/960 +- author: VMSolidus + changes: + - type: Fix + message: Removed latejoin spawners from the Salvage Pathfinder. + id: 6473 + time: '2024-10-20T16:32:43.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1105 +- author: VMSolidus + changes: + - type: Add + message: >- + Musicians can now select for free, any 3 instruments in the game with + their loadout. This now includes things like Piano flatpacks. The only + instruments not included are the Sypersynth, DAW, and "Admeme + Instruments". + - type: Remove + message: >- + Musicians no longer spawn with a Saxophone and Acoustic guitar by + default. You pick which instruments you spawn with in your loadouts now. + id: 6474 + time: '2024-10-20T16:32:53.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1108 +- author: VMSolidus + changes: + - type: Add + message: 'Added localizations for all Epistemics jobs. ' + id: 6475 + time: '2024-10-20T16:35:00.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1094 +- author: VMSolidus + changes: + - type: Fix + message: >- + Fixed the Captain's Office on Gax Station missing an LV cable that would + power its doors. + id: 6476 + time: '2024-10-20T18:18:35.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1112 +- author: VMSolidus + changes: + - type: Fix + message: >- + Fixed prisoner not spawning in the Prison if the Arrivals station is + enabled. + id: 6477 + time: '2024-10-20T19:28:35.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1113 +- author: VMSolidus + changes: + - type: Add + message: >- + Sacrificing Psions has been added. Psions can be sacrificed by + Epistemics upon an altar in order to dramatically reduce glimmer. The + more powerful the Psion to be sacrificed, the more glimmer is reduced. + id: 6478 + time: '2024-10-20T21:34:05.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1110 +- author: Aidenkrz + changes: + - type: Fix + message: Flavor text can be updated again. + id: 6479 + time: '2024-10-21T00:24:18.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1119 +- author: VMSolidus + changes: + - type: Add + message: Glacier is now on a planetary surface + id: 6480 + time: '2024-10-21T00:41:02.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1115 +- author: VMSolidus + changes: + - type: Fix + message: >- + Asterisk Station no longer spawns entombed in snow, and is now on top of + an ice sheet. + id: 6481 + time: '2024-10-21T00:41:18.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1118 +- author: Aidenkrz + changes: + - type: Fix + message: Custom specie name doesn't disappear in the editor anymore. + id: 6482 + time: '2024-10-22T01:42:11.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1120 +- author: Remuchi + changes: + - type: Fix + message: Cybereyes examine message no longer reveals person's identity. + id: 6483 + time: '2024-10-23T04:16:15.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1127 +- author: Mnemotechnician + changes: + - type: Add + message: >- + Raising glimmer too high can now cause glimmer wisps to start haunting + the station. + id: 6484 + time: '2024-10-23T04:19:00.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1125 +- author: Mnemotechnician + changes: + - type: Fix + message: >- + Fixed the "high amplification" trait lowering your amplification instead + of increasing it. + id: 6485 + time: '2024-10-25T16:22:01.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1137 +- author: Remuchi + changes: + - type: Fix + message: Added missing deflect alert locale. + id: 6486 + time: '2024-10-25T16:23:04.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1140 +- author: VMSolidus + changes: + - type: Add + message: Added Pyrokinetic Flare as a new Psi ability. + id: 6487 + time: '2024-10-25T19:03:57.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1138 +- author: SleepyScarecrow + changes: + - type: Fix + message: Fixed penlights + id: 6488 + time: '2024-10-25T19:04:14.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1142 +- author: Ichaie + changes: + - type: Fix + message: '#1126 ' + id: 6489 + time: '2024-10-27T15:30:55.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1129 +- author: VMSolidus + changes: + - type: Tweak + message: >- + The Syndicate Listening Post now has a custom communications console, + which no longer can recall Nanotrasen shuttles, and doesn't sign its + messages as Nuclear Operatives. + id: 6490 + time: '2024-10-27T15:31:16.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1143 +- author: Remuchi + changes: + - type: Fix + message: Layered icons are now properly displayed in radial menus. + id: 6491 + time: '2024-10-27T15:34:45.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1150 +- author: Remuchi + changes: + - type: Add + message: >- + Clicking on health alert now will print message in chat, displaying your + health state. + id: 6492 + time: '2024-10-27T15:36:43.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1139 +- author: VMSolidus + changes: + - type: Add + message: >- + The Animal Friend trait has been added to the game. Characters with this + trait are not attacked by animals. + id: 6493 + time: '2024-10-27T16:44:50.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/955 +- author: Remuchi + changes: + - type: Add + message: Added telescopic baton - a self-defense weapon for Command staff. + id: 6494 + time: '2024-10-27T16:45:05.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1144 +- author: VMSolidus + changes: + - type: Add + message: >- + A new class of Psionic powers has been added, "Summon Familiar". + Familiars are a new kind of Psionic creature that can be summoned by + Psions with the right power. Familiars will automatically follow their + Master, attack anyone who attacks their Master, fight back when + attacked, and attack anyone their Master attacks. Additionally, + Familiars are also ghostroles, so that they can be taken over by a + player, but otherwise do not require player control to function. + Familiars disappear when they die, and will also disappear if their + Master is either killed, or mindbroken. Psions can have a maximum of + one(1) familiar at a time. + - type: Add + message: >- + New psi-power "Summon Imp", as the first new Psi Familiar. Imps are + small motes of living flame that follow and protect their summoner. Imps + also emit an incredibly bright light, and can natively cast Pyrokinetic + Flare. + - type: Add + message: >- + Remilia has been updated to be a Psi Familiar, and no longer requires + the Bible to summon. Chaplains now start with a Power that lets them + summon Remilia once every 2 minutes. + id: 6495 + time: '2024-10-27T16:51:18.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1146 +- author: VMSolidus + changes: + - type: Tweak + message: >- + Changed the default "Following distance" of Psionic Familiars from 10 + meters to 3 meters, so that they don't have an ungodly hard time keeping + up with their Master. + id: 6496 + time: '2024-10-29T02:24:08.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1162 +- author: Ichaie + changes: + - type: Add + message: >- + Fixed several issues with the Gax Station. It also now has a newly + redesigned AI Satellite. + id: 6497 + time: '2024-11-01T23:01:34.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1163 +- author: VMSolidus + changes: + - type: Tweak + message: >- + Several new updates to Saltern, particularly around Service, Logistics, + and Engineering. + - type: Tweak + message: >- + Pressure Controlled Valves now actually function like their real-world + counterpart. If the pressure in the control-side exceeds the pressure in + either input or output sides, the valve opens, allowing air to flow + bidirectionally between input and output(Basically, an open valve). Air + can never pass through the Control-side. + - type: Tweak + message: >- + Literally doubled the power output of solar panels, to make them + actually worthwhile to setup. + id: 6498 + time: '2024-11-02T00:40:50.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1170 +- author: VMSolidus + changes: + - type: Add + message: >- + Cargo can now order crates of Uranium, Gold, and Silver. This is still + far more expensive than getting the materials from Salvage, but serves + as an incredibly useful failsafe in the event Salvage is + Unwilling/Dead/Incompetent/Shitters. + id: 6499 + time: '2024-11-02T01:03:14.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1171 +- author: Beck Thompson + changes: + - type: Fix + message: Fixed various bugs related to voice masks and voice mask implants. + id: 6500 + time: '2024-11-02T01:11:00.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1147 +- author: VMSolidus + changes: + - type: Add + message: >- + Added Python, Pollock, Webley, K38, Fitz, Wood Furniture Viper, Energy + Pistol, PDW-9, and Svalinn to security loadouts as new Duty Weapon + options. + - type: Tweak + message: >- + Modified the descriptions of all handguns obtained via Security Officer + Loadouts to explicitly state that they actually belong to the security + officer. Apparently people were being stupid and confiscating weapons + from their own Secoffs that they were entirely intended to have. + id: 6501 + time: '2024-11-02T01:11:17.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1158 +- author: FoxxoTrystan + changes: + - type: Tweak + message: >- + Cocooners can now freely uncocoon any cocoons without the need of a + sharp object. + - type: Fix + message: Cocoon making you stuck in muffle accent. + id: 6502 + time: '2024-11-02T02:21:39.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1169 +- author: Ichaie + changes: + - type: Add + message: >- + lockers for Syndicate Agent (for mapping), Janitor and Bartender have + been added. + - type: Add + message: a new wall variant has been added for the radiation closet + - type: Add + message: Uranium Crate added + id: 6503 + time: '2024-11-02T15:05:09.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1174 +- author: VMSolidus + changes: + - type: Add + message: >- + Syndicate Agents no longer have the Syndicate faction. Instead, the + Syndicate faction comes from your SYNDICATE ID/PDA. This means that if + you steal a Syndicate PDA from nukies, and wear it, you won't be shot by + their turrets. This also means that syndicate turrets will fire at + syndicate agents not wearing an agent ID. + - type: Fix + message: Fixed Familiars spawned by traitors attacking crew constantly. + id: 6504 + time: '2024-11-02T16:24:46.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1176 +- author: VMSolidus + changes: + - type: Tweak + message: >- + Cargo Technicians and Salvage Techs can now process cargo orders. You + don't need to smash the fucking LO Locker with a pickaxe anymore. + id: 6505 + time: '2024-11-02T19:58:26.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1177 +- author: VMSolidus + changes: + - type: Fix + message: >- + Communications Consoles that are marked as "Can't Recall The Shuttle" + now can't recall the shuttle. Previously they were still able to recall + it. + id: 6506 + time: '2024-11-05T04:12:36.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1180 +- author: Mocho + changes: + - type: Tweak + message: Ported over Wizden's updated Health Analyzer UI. + id: 6507 + time: '2024-11-07T04:50:46.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1183 +- author: VMSolidus + changes: + - type: Fix + message: Familiars can now be Dispelled. + - type: Tweak + message: Nerfed Remilia to have 30 HP. + - type: Tweak + message: Nerfed Base Familiar to have 50 HP. + id: 6508 + time: '2024-11-08T15:17:12.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1184 +- author: VMSolidus + changes: + - type: Add + message: 'Languages are now shown in chat alongside character names. ' + id: 6509 + time: '2024-11-08T16:14:09.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1165 +- author: Ichaie + changes: + - type: Fix + message: minor fixes on the maps Rad and Gax, fix te uranium crarte to filled + id: 6510 + time: '2024-11-08T18:54:13.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1178 +- author: VMSolidus + changes: + - type: Add + message: 'Traits can now add Implants directly. ' + id: 6511 + time: '2024-11-08T18:58:04.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1182 +- author: VMSolidus + changes: + - type: Remove + message: >- + Shadowkin have been temporarily disabled pending significant reworks and + balance updates. + id: 6512 + time: '2024-11-08T19:33:53.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1187 +- author: VMSolidus + changes: + - type: Add + message: >- + Added Bloodstains, Body Dragging Marks, and Bloody Footprints to the + game. Characters that walk through puddles of blood will now leave + behind bloody footprints. Dragging a corpse leaves a trail of blood + wherever the corpse was moved. + id: 6513 + time: '2024-11-09T02:05:47.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1190 +- author: VMSolidus + changes: + - type: Fix + message: >- + Fixed AddTrait and ReplaceTrait functions giving players an unmodified, + completely default component. + id: 6514 + time: '2024-11-10T15:22:47.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1195 +- author: VMSolidus + changes: + - type: Fix + message: Butlertron is no longer as wide as a barn. + id: 6515 + time: '2024-11-13T23:08:38.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1192 +- author: Ichaie + changes: + - type: Tweak + message: 'The Hive (map): add an SM engine to the eng, replacing the singularity.' + id: 6516 + time: '2024-11-13T23:12:41.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1201 +- author: DocNITE + changes: + - type: Add + message: Return 'Monument' and 'Atomic Amnesia MMX' as a lobby music. + id: 6517 + time: '2024-11-13T23:17:11.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1204 +- author: VMSolidus + changes: + - type: Tweak + message: >- + Syndicate Listening Posts can no longer spawn on a planet surface. This + isn't for balance reasons, I would love for planetside listening posts. + This is actually to address a server crashing bug whereby listening + posts on planets cause 7 billion entities to spawn. + id: 6518 + time: '2024-11-13T23:27:00.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1188 +- author: sleepyyapril + changes: + - type: Add + message: Added the automatic voting system. + id: 6519 + time: '2024-11-14T02:05:45.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1213 +- author: Mnemotechnician + changes: + - type: Tweak + message: >- + Added languages to certain entities that lacked them, including MMIs and + positronic brains. + - type: Add + message: >- + Polymorphing into another entity now preserves your languages and + grammar. + id: 6520 + time: '2024-11-14T20:03:33.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1223 +- author: DEATHB4DEFEAT + changes: + - type: Add + message: Added spray-painting back + id: 6521 + time: '2024-11-15T14:08:04.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1222 +- author: zelezniciar + changes: + - type: Fix + message: Fixed pengiun cold resistance + id: 6522 + time: '2024-11-16T22:41:37.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1229 +- author: Mocho + changes: + - type: Add + message: A week has passed. Surgery is here. + id: 6523 + time: '2024-11-17T00:32:30.0000000+00:00' + url: https://github.com/Simple-Station/Einstein-Engines/pull/1159 diff --git a/Resources/ConfigPresets/Build/development.toml b/Resources/ConfigPresets/Build/development.toml index da3a413026..a1900dbaa2 100644 --- a/Resources/ConfigPresets/Build/development.toml +++ b/Resources/ConfigPresets/Build/development.toml @@ -23,6 +23,7 @@ grid_fill = false auto_call_time = 0 emergency = false arrivals = false +preload_grids = false [admin] see_own_notes = true diff --git a/Resources/ConfigPresets/DeltaV/apoapsis.toml b/Resources/ConfigPresets/DeltaV/apoapsis.toml deleted file mode 100644 index 7ba1d0f282..0000000000 --- a/Resources/ConfigPresets/DeltaV/apoapsis.toml +++ /dev/null @@ -1,21 +0,0 @@ -[game] -hostname = "[EN][MRP] Delta-v (Ψ) | Apoapsis" -soft_max_players = 100 - -[server] -rules_file = "Rules.txt" -rules_header = "ui-rules-header" - -[whitelist] -enabled = false -reason = "whitelist-not-whitelisted" -min_players = 90 - -[shuttle] -emergency_early_launch_allowed = true - -[hub] -tags = "lang:en-US,region:am_n_e,rp:med" - -[atmos] -monstermos_rip_tiles = false diff --git a/Resources/ConfigPresets/DeltaV/deltav.toml b/Resources/ConfigPresets/DeltaV/deltav.toml deleted file mode 100644 index f784373d71..0000000000 --- a/Resources/ConfigPresets/DeltaV/deltav.toml +++ /dev/null @@ -1,48 +0,0 @@ -[game] -desc = "Featuring loads of unique content and psionics." -lobbyenabled = true -lobbyduration = 240 -round_end_pacifist = true - -[infolinks] -discord = "https://go.delta-v.org/sxzqj" -github = "https://github.com/DeltaV-Station/Delta-v" -website = "https://www.delta-v.org/" -wiki = "https://wiki.delta-v.org/view/Main_Page" -patreon = "https://www.patreon.com/Delta_v" -bug_report = "https://github.com/DeltaV-Station/Delta-v/issues/new/choose" -forum = "https://forum.delta-v.org/" -appeal = "https://go.delta-v.org/appeal" - -[net] -max_connections = 1024 -tickrate = 30 - -[vote] -restart_required_ratio = 0.7 - -[netres] -limit = 10.0 - -[admin] -see_own_notes = true -deadmin_on_join = true - -[events] -ramping_average_end_time = 180.0 -ramping_average_chaos = 4.5 - -[atmos] -monstermos_rip_tiles = false - -[server] -id = "deltav" - -[discord] -rich_main_icon_id = "deltav" - -[ic] -flavor_text = true - -#[worldgen] -#enabled = true diff --git a/Resources/ConfigPresets/DeltaV/inclination.toml b/Resources/ConfigPresets/DeltaV/inclination.toml deleted file mode 100644 index c655bb796c..0000000000 --- a/Resources/ConfigPresets/DeltaV/inclination.toml +++ /dev/null @@ -1,17 +0,0 @@ -[game] -hostname = "[EN][EU][MRP] Delta-v (Ψ) | Inclination" -soft_max_players = 50 - -[server] -rules_file = "Rules.txt" -rules_header = "ui-rules-header" - -[vote] -preset_enabled = true -map_enabled = true - -[ooc] -enable_during_round = true - -[hub] -tags = "lang:en-US,region:eu_w,rp:med" diff --git a/Resources/ConfigPresets/DeltaV/periapsis.toml b/Resources/ConfigPresets/DeltaV/periapsis.toml deleted file mode 100644 index ad6cae5628..0000000000 --- a/Resources/ConfigPresets/DeltaV/periapsis.toml +++ /dev/null @@ -1,22 +0,0 @@ -[game] -hostname = "[EN][MRP] Delta-v (Ψ) | Periapsis" -soft_max_players = 50 - -[server] -rules_file = "Rules.txt" -rules_header = "ui-rules-header" - -[vote] -preset_enabled = true -map_enabled = true - -[whitelist] -enabled = false -reason = "whitelist-not-whitelisted-peri" -min_players = 10 - -[ooc] -enable_during_round = true - -[hub] -tags = "lang:en-US,region:am_n_e,rp:med" diff --git a/Resources/ConfigPresets/EinsteinEngines/default.toml b/Resources/ConfigPresets/EinsteinEngines/default.toml new file mode 100644 index 0000000000..b5b8dbbf64 --- /dev/null +++ b/Resources/ConfigPresets/EinsteinEngines/default.toml @@ -0,0 +1,56 @@ +[admin] +see_own_notes = true +deadmin_on_join = true + +[discord] +rich_main_icon_id = "einstein-engines" + +[events] +ramping_average_end_time = 180.0 +ramping_average_chaos = 4.5 + +[game] +hostname = "Einstein Engines (change this)" +desc = "An alternative upstream that favors content intended for RP servers. If you are reading this, please change it." +soft_max_players = 60 +lobbyenabled = true +lobbyduration = 240 + +[hub] +tags = "lang:en-US,region:am_n_e,rp:med" +hub_urls = "https://hub.spacestation14.com/" + +[ic] +flavor_text = true + +[infolinks] +discord = "https://discord.gg/X4QEXxUrsJ" +github = "https://github.com/Simple-Station/Einstein-Engines" +website = "https://simplestation.org/" +bug_report = "https://github.com/Simple-Station/Einstein-Engines/issues/new/choose" + +[net] +max_connections = 1024 +tickrate = 30 + +[netres] +limit = 10.0 + +[build] +#! PLEASE set this for your fork +fork_id = "EinsteinEngines" + +[server] +rules_file = "Rules.txt" +rules_header = "ui-rules-header" + +[ooc] +enable_during_round = true + +[vote] +restart_required_ratio = 0.7 +preset_enabled = true +map_enabled = true + +#[worldgen] +#enabled = true diff --git a/Resources/ConfigPresets/SimpleStation14/Parkstation.toml b/Resources/ConfigPresets/SimpleStation14/Parkstation.toml index 6a8b5359a4..b1a7d7e4e2 100644 --- a/Resources/ConfigPresets/SimpleStation14/Parkstation.toml +++ b/Resources/ConfigPresets/SimpleStation14/Parkstation.toml @@ -19,12 +19,12 @@ pathfinding = true rules_file = "Parkstation/ParkstationRules.txt" id = "parkstation" -# [whitelist] -# enabled = true +[whitelist] +enabled = true # min_players = 24 -# [queue] -# enabled = true +[queue] +enabled = true [rules] time = 120 @@ -33,21 +33,6 @@ time = 120 # ramping_average_end_time = 120.0 # ramping_average_chaos = 7.0 -[traitor] -min_players = 2 -players_per_traitor = 8 -starting_balance = 16 - -[zombie] -min_players = 10 - - -[mapping] -autosave_interval = 180.0 - -[atmos] -space_wind = true -space_wind_max_velocity = 22.0 [shuttle] emergency_early_launch_allowed = true @@ -58,9 +43,6 @@ recall_turning_point = 0.75 auto_call_time = 150 arrivals = false -[biomass] -easy_mode = false - [explosion] tiles_per_tick = 128 incremental_tile = false diff --git a/Resources/Credits/GitHub.txt b/Resources/Credits/GitHub.txt index 40424a21e0..736a888ebf 100644 --- a/Resources/Credits/GitHub.txt +++ b/Resources/Credits/GitHub.txt @@ -1 +1 @@ -0x6273, 2013HORSEMEATSCANDAL, 20kdc, 21Melkuu, 4dplanner, 612git, 778b, Ablankmann, Acruid, actioninja, adamsong, Admiral-Obvious-001, Adrian16199, Aerocrux, Aexxie, africalimedrop, Agoichi, Ahion, AJCM-git, AjexRose, Alekshhh, AlexMorgan3817, AlmondFlour, AlphaQwerty, Altoids1, amylizzle, ancientpower, angelofallars, ArchPigeon, Arendian, arimah, Arteben, AruMoon, as334, AsikKEsel, asperger-sind, aspiringLich, avghdev, AzzyIsNotHere, BananaFlambe, BasedUser, beck-thompson, BGare, BingoJohnson-zz, BismarckShuffle, Bixkitts, Blackern5000, Blazeror, BlueHNT, Boaz1111, BobdaBiscuit, brainfood1183, BramvanZijp, Brandon-Huu, Bribrooo, Bright0, brndd, BubblegumBlue, BYONDFuckery, c4llv07e, CaasGit, CakeQ, CaptainSqrBeard, Carbonhell, Carolyn3114, CatTheSystem, Centronias, chairbender, Charlese2, Cheackraze, cheesePizza2, Chief-Engineer, chromiumboy, Chronophylos, clorl, Clyybber, CodedCrow, ColdAutumnRain, Colin-Tel, collinlunn, ComicIronic, coolmankid12345, corentt, crazybrain23, creadth, CrigCrag, Crotalus, CrudeWax, CrzyPotato, Cyberboss, d34d10cc, Daemon, daerSeebaer, dahnte, dakamakat, dakimasu, DamianX, DangerRevolution, daniel-cr, Darkenson, DawBla, dch-GH, Deahaka, DEATHB4DEFEAT, DeathCamel58, deathride58, DebugOk, Decappi, Deeeeja, deepdarkdepths, Delete69, deltanedas, DeltaV-Bot, DerbyX, DoctorBeard, DogZeroX, dontbetank, dootythefrooty, Doru991, DoubleRiceEddiedd, DrMelon, DrSmugleaf, drteaspoon420, DTanxxx, DubiousDoggo, Duddino, Dutch-VanDerLinde, Easypoller, eclips_e, EdenTheLiznerd, EEASAS, Efruit, ElectroSR, elthundercloud, Emisse, EmoGarbage404, Endecc, enumerate0, eoineoineoin, ERORR404V1, Errant-4, estacaoespacialpirata, exincore, exp111, Fahasor, FairlySadPanda, Fansana, ficcialfaint, Fildrance, FillerVK, Fishfish458, Flareguy, FluffiestFloof, FluidRock, FoLoKe, fooberticus, Fortune117, FoxxoTrystan, freeman2651, Froffy025, Fromoriss, FungiFellow, GalacticChimp, gbasood, Geekyhobo, Genkail, geraeumig, Ghagliiarghii, Git-Nivrak, github-actions[bot], gituhabu, gluesniffler, GNF54, Golinth, GoodWheatley, graevy, GreyMario, Guess-My-Name, gusxyz, h3half, Hanzdegloker, Hardly3D, harikattar, HerCoyote23, HoofedEar, Hoolny, hord-brayden, hubismal, Hugal31, Huxellberger, iacore, IamVelcroboy, icekot8, igorsaux, ike709, Illiux, Ilya246, IlyaElDunaev, Injazz, Insineer, Interrobang01, IProduceWidgets, ItsMeThom, Jackal298, Jackrost, jamessimo, janekvap, JerryImMouse, Jessetriesagain, jessicamaybe, Jezithyr, jicksaw, JiimBob, JoeHammad1844, JohnGinnane, johnku1, joshepvodka, jproads, Jrpl, juliangiebel, JustArt1m, JustCone14, JustinTrotter, KaiShibaa, kalane15, kalanosh, Kelrak, kerisargit, keronshb, KIBORG04, Killerqu00, KingFroozy, kira-er, Kit0vras, KittenColony, Ko4ergaPunk, komunre, koteq, Krunklehorn, kxvvv, Lamrr, LankLTE, lapatison, Leander-0, leonardo-dabepis, LetterN, Level10Cybermancer, lever1209, liltenhead, LittleBuilderJane, Lomcastar, LordCarve, LordEclipse, LovelyLophi, Lukasz825700516, lunarcomets, luringens, lvvova1, lzimann, lzk228, MACMAN2003, Macoron, MagnusCrowe, ManelNavola, Matz05, MehimoNemo, MeltedPixel, MemeProof, Menshin, Mervill, metalgearsloth, mhamsterr, MilenVolf, Minty642, Mirino97, mirrorcult, misandrie, MishaUnity, MisterMecky, Mith-randalf, Mnemotechnician, Moneyl, Moomoobeef, moony, Morb0, Mr0maks, musicmanvr, Myakot, Myctai, N3X15, Nairodian, Naive817, namespace-Memory, NickPowers43, nikthechampiongr, Nimfar11, Nirnael, nmajask, nok-ko, notafet, notquitehadouken, noudoit, noverd, nuke-haus, NULL882, OCOtheOmega, OctoRocket, OldDanceJacket, onoira, osjarw, Owai-Seek, pali6, Pangogie, patrikturi, PaulRitter, Peptide90, peptron1, Phantom-Lily, PHCodes, PixelTheKermit, PJB3005, Plykiya, pofitlo, pointer-to-null, PolterTzi, PoorMansDreams, potato1234x, ProfanedBane, PrPleGoo, ps3moira, Pspritechologist, Psychpsyo, psykzz, PuroSlavKing, quatre, QuietlyWhisper, qwerltaz, Radosvik, Radrark, Rainbeon, Rainfey, Rane, ravage123321, rbertoche, Redict, RedlineTriad, RednoWCirabrab, RemberBM, RemieRichards, RemTim, rene-descartes2021, RiceMar1244, RieBi, Rinkashikachi, Rockdtben, rolfero, rosieposieeee, Saakra, Samsterious, SaphireLattice, ScalyChimp, scrato, Scribbles0, Serkket, ShadowCommander, Shadowtheprotogen546, SignalWalker, SimpleStation14, Simyon264, Sirionaut, siyengar04, Skarletto, Skrauz, Skyedra, SlamBamActionman, slarticodefast, Slava0135, SleepyScarecrow, Snowni, snowsignal, SonicHDC, SoulSloth, SpaceManiac, SpeltIncorrectyl, spoogemonster, ssdaniel24, stalengd, Stealthbomber16, stellar-novas, StrawberryMoses, superjj18, SweptWasTaken, Szunti, TadJohnson00, takemysoult, TaralGit, Tayrtahn, tday93, TekuNut, TemporalOroboros, tentekal, tgrkzus, thatrandomcanadianguy, TheArturZh, theashtronaut, thedraccx, themias, Theomund, theOperand, TheShuEd, TimrodDX, Titian3, tkdrg, Tmanzxd, tmtmtl30, TokenStyle, tom-leys, tomasalves8, Tomeno, Tornado-Technology, tosatur, Tryded, TsjipTsjip, Tunguso4ka, TurboTrackerss14, Tyler-IN, Tyzemol, UbaserB, UKNOWH, UnicornOnLSD, Uriende, UristMcDorf, Vaaankas, Varen, VasilisThePikachu, veliebm, Veritius, Vermidia, Verslebas, VigersRay, Visne, VMSolidus, volundr-, Voomra, Vordenburg, vulppine, wafehling, WarMechanic, waylon531, weaversam8, whateverusername0, Willhelm53, wixoaGit, WlarusFromDaSpace, wrexbe, xRiriq, yathxyz, Ygg01, YotaXP, YuriyKiss, zach-hill, Zandario, Zap527, Zealith-Gamer, ZelteHonor, zerorulez, zionnBE, zlodo, ZNixian, ZoldorfTheWizard, Zumorica, Zymem +0x6273, 13spacemen, 2013HORSEMEATSCANDAL, 20kdc, 21Melkuu, 2digitman, 4310v343k, 4dplanner, 612git, 778b, Ablankmann, abregado, Absolute-Potato, Acruid, actioninja, actually-reb, ada-please, adamsong, Adeinitas, Admiral-Obvious-001, adrian, Adrian16199, Aerocrux, Aexolott, Aexxie, africalimedrop, afrokada, Agoichi, Ahion, Aidenkrz, Aikakakah, aitorlogedo, AJCM-git, AjexRose, Alekshhh, alexkar598, AlexMorgan3817, alexumandxgabriel08x, Alithsko, AlmondFlour, ALMv1, AlphaQwerty, Altoids1, amylizzle, ancientpower, Andre19926, AndrewEyeke, angelofallars, Anzarot121, Appiah, ar4ill, ArchPigeon, areitpog, Arendian, arimah, Arkanic, armoks, Arteben, ArthurMousatov, AruMoon, as334, AsikKEsel, asperger-sind, aspiringLich, astriloqua, avghdev, AzzyIsNotHere, BananaFlambe, BasedPugilist, BasedUser, beck-thompson, benev0, BGare, bhespiritu, BingoJohnson-zz, BismarckShuffle, Bixkitts, Blackern5000, Blazeror, bloodrizer, Bloody2372, blueDev2, BlueHNT, Boaz1111, BobdaBiscuit, BobTheSleder, boiled-water-tsar, botanySupremist, brainfood1183, BramvanZijp, Brandon-Huu, Bribrooo, Bright0, brndd, bryce0110, BubblegumBlue, buletsponge, BYONDFuckery, c0rigin, c4llv07e, CaasGit, CakeQ, capnsockless, CaptainSqrBeard, Carbonhell, Carolyn3114, Carou02, carteblanche4me, CatTheSystem, Centronias, chairbender, Charlese2, Cheackraze, cheesePizza2, Chief-Engineer, christhirtle, chromiumboy, Chronophylos, Chubbygummibear, CilliePaint, civilCornball, clorl, clyf, Clyybber, CMDR-Piboy314, CodedCrow, Cohnway, cojoke-dot, ColdAutumnRain, Colin-Tel, collinlunn, ComicIronic, coolmankid12345, corentt, CormosLemming, crazybrain23, creadth, CrigCrag, Crotalus, CrudeWax, CrzyPotato, Cyberboss, d34d10cc, d4kii, DadeKuma, Daemon, daerSeebaer, dahnte, dakamakat, DamianX, DangerRevolution, daniel-cr, DanSAussieITS, Daracke, DarkenedSynergy, Darkenson, DawBla, Daxxi3, dch-GH, Deahaka, dean, DEATHB4DEFEAT, DeathCamel58, deathride58, DebugOk, Decappi, Deeeeja, deepdarkdepths, degradka, Delete69, deltanedas, DeltaV-Bot, DerbyX, derek, dersheppard, dexlerxd, dffdff2423, dge21, digitalic, DinoWattz, DJB1gYAPPA, DjfjdfofdjfjD, DocNITE, DoctorBeard, DogZeroX, dolgovmi, dontbetank, dootythefrooty, Dorragon, Doru991, DoubleRiceEddiedd, DoutorWhite, drakewill-CRL, Drayff, dribblydrone, DrMelon, drongood12, DrSingh, DrSmugleaf, drteaspoon420, DTanxxx, DubiousDoggo, Duddino, dukevanity, Dutch-VanDerLinde, dvir001, Dynexust, Easypoller, eclips_e, eden077, EEASAS, Efruit, efzapa, ElectroSR, elsie, elthundercloud, Emisse, emmafornash, EmoGarbage404, Endecc, enumerate0, eoineoineoin, eris, ERORR404V1, Errant-4, esguard, estacaoespacialpirata, eugene, Evgencheg, exincore, exp111, f0x-n3rd, FacePluslll, Fahasor, FairlySadPanda, Fansana, Feluk6174, fenndragon, ficcialfaint, Fiftyllama, Fildrance, FillerVK, FinnishPaladin, FirinMaLazors, Fishfish458, FL-OZ, Flareguy, FluffiestFloof, FluffMe, FluidRock, flybik, FoLoKe, fooberticus, ForestNoises, forgotmyotheraccount, forkeyboards, forthbridge, Fortune117, Fouin, foxhorn, FoxxoTrystan, freeman2651, freeze2222, Froffy025, Fromoriss, froozigiusz, FrostMando, FungiFellow, GalacticChimp, Gaxeer, gbasood, Geekyhobo, genderGeometries, GeneralGaws, Genkail, geraeumig, Ghagliiarghii, ghost581x, Git-Nivrak, gituhabu, GlassEclipse, gluesniffler, GNF54, Golinth, GoodWheatley, gradientvera, graevy, GraniteSidewalk, GreaseMonk, greenrock64, greggthefather, GreyMario, GTRsound, Guess-My-Name, gusxyz, h3half, Haltell, Hanzdegloker, Hardly3D, harikattar, Hebi, Henry, HerCoyote23, hiucko, Hmeister-fake, Hmeister-real, hobnob, HoidC, Holinka4ever, holyssss, HoofedEar, Hoolny, hord-brayden, hubismal, Hugal31, Huxellberger, Hyenh, i-justuser-i, iacore, IamVelcroboy, icekot8, icesickleone, Ichaie, iczero, iglov, igorsaux, ike709, illersaver, Illiux, Ilushkins33, Ilya246, IlyaElDunaev, indeano, Injazz, Insineer, IntegerTempest, Interrobang01, IProduceWidgets, ItsMeThom, Itzbenz, Jackal298, Jackrost, jacksonzck, Jackw2As, jamessimo, janekvap, Jark255, Jaskanbe, JasperJRoth, JerryImMouse, jerryimmouse, Jessetriesagain, jessicamaybe, Jezithyr, jicksaw, JiimBob, JimGamemaster, jjtParadox, JoeHammad1844, JohnGinnane, johnku1, joshepvodka, Jrpl, juliangiebel, JustArt1m, JustCone14, justin, justintether, JustinTrotter, justtne, k3yw, Kadeo64, KaiShibaa, kalane15, kalanosh, Keelin, Keer-Sar, KEEYNy, keikiru, Kelrak, kerisargit, keronshb, KIBORG04, Killerqu00, Kimpes, KingFroozy, kira-er, Kirillcas, Kistras, Kit0vras, KittenColony, klaypexx, Kmc2000, Ko4ergaPunk, kognise, komunre, koteq, Krunklehorn, Kukutis96513, Kupie, kxvvv, kzhanik, lajolico, Lamrr, LankLTE, laok233, lapatison, larryrussian, lawdog4817, Lazzi0706, Leander-0, leonardo-dabepis, leonsfriedrich, LetterN, lettern, Level10Cybermancer, LEVELcat, lever1209, Lgibb18, LightVillet, liltenhead, LinkUyx, LittleBuilderJane, lizelive, lleftTheDragon, localcc, Lomcastar, LordCarve, LordEclipse, LovelyLophi, luckyshotpictures, LudwigVonChesterfield, Lukasz825700516, lunarcomets, luringens, lvvova1, lzimann, lzk228, M3739, mac6na6na, MACMAN2003, Macoron, magmodius, MagnusCrowe, malchanceux, MaloTV, ManelNavola, Mangohydra, marboww, Markek1, Matz05, max, MaxNox7, MehimoNemo, MeltedPixel, MemeProof, MendaxxDev, Menshin, Mephisto72, Mervill, metalgearsloth, mhamsterr, michaelcu, micheel665, MilenVolf, Minty642, Mirino97, mirrorcult, misandrie, MishaUnity, MisterMecky, Mith-randalf, MjrLandWhale, MLGTASTICa, Mnemotechnician, moderatelyaware, mokiros, Moneyl, Moomoobeef, moony, Morb0, mr-bo-jangles, Mr0maks, MrFippik, musicmanvr, MWKane, Myakot, Myctai, N3X15, nails-n-tape, Nairodian, Naive817, namespace-Memory, Nannek, NickPowers43, nikthechampiongr, Nimfar11, Nirnael, NIXC, NkoKirkto, nmajask, noctyrnal, nok-ko, NonchalantNoob, NoobyLegion, not-gavnaed, notafet, notquitehadouken, noudoit, noverd, NuclearWinter, nukashimika, nuke-haus, NULL882, nullarmo, nyeogmi, Nylux, Nyranu, och-och, OCOtheOmega, OctoRocket, OldDanceJacket, osjarw, Ostaf, othymer, OttoMaticode, Owai-Seek, paigemaeforrest, pali6, Pangogie, panzer-iv1, paolordls, partyaddict, patrikturi, PaulRitter, Peptide90, peptron1, PeterFuto, PetMudstone, pewter-wiz, Phantom-Lily, PHCodes, Phill101, phunnyguy, pigeonpeas, PilgrimViis, Pill-U, Pireax, pissdemon, PixelTheKermit, PJB3005, Plasmaguy, PlasmaRaptor, plinyvic, Plykiya, pofitlo, pointer-to-null, PolterTzi, PoorMansDreams, potato1234x, PotentiallyTom, ProfanedBane, ProPandaBear, PrPleGoo, ps3moira, Pspritechologist, Psychpsyo, psykzz, PuceTint, PuroSlavKing, PursuitInAshes, Putnam3145, qrtDaniil, quatre, QuietlyWhisper, qwerltaz, RadioMull, Radosvik, Radrark, Rainbeon, Rainfey, Raitononai, randy10122, Rane, Ranger6012, Rapidgame7, ravage123321, rbertoche, Redict, RedlineTriad, RednoWCirabrab, RemberBM, RemieRichards, RemTim, Remuchi, rene-descartes2021, Renlou, retequizzle, rich-dunne, RieBi, riggleprime, RIKELOLDABOSS, Rinkashikachi, RobbyTheFish, Rockdtben, Rohesie, rok-povsic, rolfero, RomanNovo, rosieposieeee, router, RumiTiger, S1ss3l, Saakra, Salex08, sam, Samsterious, SaphireLattice, SapphicOverload, SaveliyM360, sBasalto, ScalyChimp, scrato, Scribbles0, scuffedjays, ScumbagDog, Segonist, sephtasm, Serkket, sewerpig, ShadowCommander, shadowtheprotogen546, shadowwailker, shaeone, shampunj, shariathotpatrol, ShatteredSwords, SignalWalker, siigiil, SimpleStation14, Simyon264, sirdragooon, Sirionaut, siyengar04, Sk1tch, SkaldetSkaeg, Skarletto, Skrauz, Skyedra, SlamBamActionman, slarticodefast, Slava0135, SleepyScarecrow, sleepyyapril, Slyfox333, snebl, sniperchance, Snowni, snowsignal, SonicHDC, SoulFN, SoulSloth, Soundwavesghost, SpaceManiac, SpaceRox1244, SpaceyLady, spartak, SpartanKadence, Spatison, SpeltIncorrectyl, sphirai, SplinterGP, spoogemonster, sporekto, Squishy77, ssdaniel24, stalengd, stanberytrask, Stanislav4ix, StanTheCarpenter, Stealthbomber16, stellar-novas, stopbreaking, stopka-html, StrawberryMoses, Stray-Pyramid, Strol20, StStevens, Subversionary, sunbear-dev, superjj18, Supernorn, suraru, SweptWasTaken, Sybil, SYNCHRONIC, Szunti, TadJohnson00, takemysoult, TaralGit, Taran, Tayrtahn, tday93, TekuNut, telyonok, TemporalOroboros, tentekal, terezi4real, Terraspark4941, texcruize, tgrkzus, thatrandomcanadianguy, TheArturZh, theashtronaut, TheCze, TheDarkElites, thedraccx, TheEmber, TheIntoxicatedCat, thekilk, themias, theomund, theOperand, TherapyGoth, TheShuEd, thevinter, ThunderBear2006, Timemaster99, timothyteakettle, TimrodDX, tin-man-tim, Tirochora, Titian3, tk-a369, tkdrg, Tmanzxd, tmtmtl30, toasterpm87, TokenStyle, Tollhouse, tom-leys, tomasalves8, Tomeno, Tonydatguy, Tornado-Technology, tosatur, TotallyLemon, truepaintgit, Tryded, TsjipTsjip, Tunguso4ka, TurboTrackerss14, Tyler-IN, Tyzemol, UbaserB, ubis1, UBlueberry, UKNOWH, UltimateJester, Unbelievable-Salmon, underscorex5, UnicornOnLSD, unusualcrow, Uriende, UristMcDorf, user424242420, v0idRift, Vaaankas, valentfingerov, Varen, VasilisThePikachu, veliebm, VelonacepsCalyxEggs, veprolet, Veritius, Vermidia, vero5123, Verslebas, VigersRay, violet754, Visne, VMSolidus, volotomite, volundr-, Voomra, Vordenburg, vulppine, wafehling, Warentan, WarMechanic, Watermelon914, waylon531, weaversam8, wertanchik, whateverusername0, Willhelm53, WilliamECrew, willicassi, Winkarst-cpu, wirdal, wixoaGit, WlarusFromDaSpace, wrexbe, WTCWR68, xkreksx, xRiriq, YanehCheck, yathxyz, Ygg01, YotaXP, youarereadingthis, Yousifb26, yunii, YuriyKiss, yuriykiss, zach-hill, Zadeon, zamp, Zandario, Zap527, Zealith-Gamer, zelezniciar1, ZelteHonor, ZeroDiamond, zerorulez, ZeWaka, zionnBE, ZNixian, ZoldorfTheWizard, Zymem, zzylex diff --git a/Resources/Fonts/Cambria.ttf b/Resources/Fonts/Cambria.ttf new file mode 100644 index 0000000000..ab88fb474a Binary files /dev/null and b/Resources/Fonts/Cambria.ttf differ diff --git a/Resources/Fonts/Lymphatic.ttf b/Resources/Fonts/Lymphatic.ttf new file mode 100644 index 0000000000..36beef04b1 Binary files /dev/null and b/Resources/Fonts/Lymphatic.ttf differ diff --git a/Resources/Locale/en-US/Content/Power/batteries.ftl b/Resources/Locale/en-US/Content/Power/batteries.ftl new file mode 100644 index 0000000000..15ebafae77 --- /dev/null +++ b/Resources/Locale/en-US/Content/Power/batteries.ftl @@ -0,0 +1 @@ +battery-electrocute-charge = The battery surges with energy! diff --git a/Resources/Locale/en-US/Content/Power/silicons.ftl b/Resources/Locale/en-US/Content/Power/silicons.ftl new file mode 100644 index 0000000000..4e047d3653 --- /dev/null +++ b/Resources/Locale/en-US/Content/Power/silicons.ftl @@ -0,0 +1,4 @@ +ipc-recharge-tip = You charged a litte of your battery. +dead-startup-button-verb = Reboot +dead-startup-system-reboot-success = {$target}'s system was rebooted. +dead-startup-system-reboot-failed = {$target}'s chassis is way too damaged. diff --git a/Resources/Locale/en-US/Content/Silicons/siliconChargers.ftl b/Resources/Locale/en-US/Content/Silicons/siliconChargers.ftl new file mode 100644 index 0000000000..df6c66346c --- /dev/null +++ b/Resources/Locale/en-US/Content/Silicons/siliconChargers.ftl @@ -0,0 +1,5 @@ +silicon-charger-overheatwarning = You feel like you're in a microwave! +silicon-charger-chargerate-string = Charge rate +silicon-charger-efficiency-string = Efficiency + +silicon-charger-list-full = {CAPITALIZE(THE($charger))} can only accommodate so many targets! diff --git a/Resources/Locale/en-US/Content/Silicons/silicons.ftl b/Resources/Locale/en-US/Content/Silicons/silicons.ftl new file mode 100644 index 0000000000..f7a9650fff --- /dev/null +++ b/Resources/Locale/en-US/Content/Silicons/silicons.ftl @@ -0,0 +1,3 @@ +silicon-overheating = You feel your circuits overheating! +silicon-crit = Structural integrity critical! +silicon-power-low = Power low! diff --git a/Resources/Locale/en-US/HUD/game-hud.ftl b/Resources/Locale/en-US/HUD/game-hud.ftl index 7f6573d2ad..ea423f080a 100644 --- a/Resources/Locale/en-US/HUD/game-hud.ftl +++ b/Resources/Locale/en-US/HUD/game-hud.ftl @@ -1,6 +1,7 @@ game-hud-open-escape-menu-button-tooltip = Open escape menu. game-hud-open-guide-menu-button-tooltip = Open guidebook menu. game-hud-open-character-menu-button-tooltip = Open character menu. +game-hud-open-emotes-menu-button-tooltip= Open emotes menu. game-hud-open-inventory-menu-button-tooltip = Open inventory menu. game-hud-open-crafting-menu-button-tooltip = Open crafting menu. game-hud-open-actions-menu-button-tooltip = Open actions menu. diff --git a/Resources/Locale/en-US/Mail/mail.ftl b/Resources/Locale/en-US/Mail/mail.ftl deleted file mode 100644 index 72cd3879b3..0000000000 --- a/Resources/Locale/en-US/Mail/mail.ftl +++ /dev/null @@ -1,30 +0,0 @@ -mail-recipient-mismatch = Recipient name or job does not match. -mail-invalid-access = Recipient name and job match, but access isn't as expected. -mail-locked = The anti-tamper lock hasn't been removed. Tap the recipient's ID. -mail-desc-far = A parcel of mail. You can't make out who it's addressed to from this distance. -mail-desc-close = A parcel of mail addressed to {CAPITALIZE($name)}, {$job}. -mail-desc-fragile = It has a [color=red]red fragile label[/color]. -mail-desc-priority = The anti-tamper lock's [color=yellow]yellow priority tape[/color] is active. Better deliver it on time! -mail-desc-priority-inactive = The anti-tamper lock's [color=#886600]yellow priority tape[/color] is inactive. -mail-unlocked = Anti-tamper system unlocked. -mail-unlocked-by-emag = Anti-tamper system *BZZT*. -mail-unlocked-reward = Anti-tamper system unlocked. {$bounty} spesos have been added to logistics's account. -mail-penalty-lock = ANTI-TAMPER LOCK BROKEN. LOGISTICS BANK ACCOUNT PENALIZED BY {$credits} CREDITS. -mail-penalty-fragile = INTEGRITY COMPROMISED. LOGISTICS BANK ACCOUNT PENALIZED BY {$credits} CREDITS. -mail-penalty-expired = DELIVERY PAST DUE. LOGISTICS BANK ACCOUNT PENALIZED BY {$credits} CREDITS. -mail-item-name-unaddressed = mail -mail-item-name-addressed = mail ({$recipient}) - -command-mailto-description = Queue a parcel to be delivered to an entity. Example usage: `mailto 1234 5678 false false`. The target container's contents will be transferred to an actual mail parcel. -command-mailto-help = Usage: {$command} [is-fragile: true or false] [is-priority: true or false] -command-mailto-no-mailreceiver = Target recipient entity does not have a {$requiredComponent}. -command-mailto-no-blankmail = The {$blankMail} prototype doesn't exist. Something is very wrong. Contact a programmer. -command-mailto-bogus-mail = {$blankMail} did not have {$requiredMailComponent}. Something is very wrong. Contact a programmer. -command-mailto-invalid-container = Target container entity does not have a {$requiredContainer} container. -command-mailto-unable-to-receive = Target recipient entity was unable to be setup for receiving mail. ID may be missing. -command-mailto-no-teleporter-found = Target recipient entity was unable to be matched to any station's mail teleporter. Recipient may be off-station. -command-mailto-success = Success! Mail parcel has been queued for next teleport in {$timeToTeleport} seconds. - -command-mailnow = Force all mail teleporters to deliver another round of mail as soon as possible. This will not bypass the undelivered mail limit. -command-mailnow-help = Usage: {$command} -command-mailnow-success = Success! All mail teleporters will be delivering another round of mail soon. diff --git a/Resources/Locale/en-US/abilities/arachne.ftl b/Resources/Locale/en-US/abilities/arachne.ftl index 6f6348d721..857d104e5f 100644 --- a/Resources/Locale/en-US/abilities/arachne.ftl +++ b/Resources/Locale/en-US/abilities/arachne.ftl @@ -8,6 +8,9 @@ spin-web-start-second-person = You start spinning a web. spin-web-start-third-person = {CAPITALIZE(THE($spider))} starts spinning a web! cocoon-start-second-person = You start cocooning {THE($target)}. cocoon-start-third-person = {CAPITALIZE(THE($spider))} starts cocooning {THE($target)}. +uncocoon-start-second-person = You start releasing {THE($target)}. +uncocoon-start-third-person = {CAPITALIZE(THE($spider))} starts releasing {THE($target)}. spun-web-second-person = You spin up a web. spun-web-third-person = {CAPITALIZE(THE($spider))} spins up a web! cocoon = Cocoon +uncocoon = Uncocoon diff --git a/Resources/Locale/en-US/abilities/bloodsucker.ftl b/Resources/Locale/en-US/abilities/bloodsucker.ftl index d956eaff84..c8aa0ed854 100644 --- a/Resources/Locale/en-US/abilities/bloodsucker.ftl +++ b/Resources/Locale/en-US/abilities/bloodsucker.ftl @@ -4,9 +4,9 @@ action-description-suck-blood = Suck the blood of the victim in your hand. bloodsucker-fail-helmet = You'd need to remove {THE($helmet)}. bloodsucker-fail-mask = You'd need to remove your mask! -bloodsucker-fail-not-blood = { CAPITALIZE(SUBJECT($target)) } doesn't have delicious, nourishing mortal blood. -bloodsucker-fail-no-blood = { CAPITALIZE(SUBJECT($target)) } has no blood in { POSS-ADJ($target) } body. -bloodsucker-fail-no-blood-bloodsucked = { CAPITALIZE(SUBJECT($target)) } has been sucked dry. +bloodsucker-not-blood = {$target} doesn't have delicious, nourishing blood. +bloodsucker-fail-no-blood = {$target} has no blood in { POSS-ADJ($target) } body. +bloodsucker-fail-no-blood-bloodsucked = {$target} has been sucked dry. bloodsucker-blood-sucked = You suck some blood from {$target}. bloodsucker-doafter-start = You try to suck blood from {$target}. diff --git a/Resources/Locale/en-US/abilities/lifedrainer.ftl b/Resources/Locale/en-US/abilities/lifedrainer.ftl new file mode 100644 index 0000000000..4072129ab0 --- /dev/null +++ b/Resources/Locale/en-US/abilities/lifedrainer.ftl @@ -0,0 +1,5 @@ +verb-life-drain = Life drain +life-drain-second-start = {CAPITALIZE(THE($drainer))} starts draining your life force! +life-drain-third-start = {CAPITALIZE(THE($drainer))} starts draining {THE($target)}'s life force! +life-drain-second-end = Your being is annihilated. +life-drain-third-end = {CAPITALIZE(THE($drainer))} drains {THE($target)}'s life force! diff --git a/Resources/Locale/en-US/abilities/psionic.ftl b/Resources/Locale/en-US/abilities/psionic.ftl index 91ae21233a..d0e8db72f8 100644 --- a/Resources/Locale/en-US/abilities/psionic.ftl +++ b/Resources/Locale/en-US/abilities/psionic.ftl @@ -25,9 +25,6 @@ accept-psionics-window-prompt-text-part = You rolled a psionic power! action-name-psionic-invisibility = Psionic Invisibility action-description-psionic-invisibility = Render yourself invisible to any entity that could potentially be psychic. Borgs, animals, and so on are not affected. -action-name-psionic-invisibility = Psionic Invisibility -action-description-psionic-invisibility = Render yourself invisible to any entity that could potentially be psychic. Borgs, animals, and so on are not affected. - action-name-psionic-invisibility-off = Turn Off Psionic Invisibility action-description-psionic-invisibility-off = Return to visibility, and receive a stun. diff --git a/Resources/Locale/en-US/accent/accents.ftl b/Resources/Locale/en-US/accent/accents.ftl index 301c589449..3c898f9cb1 100644 --- a/Resources/Locale/en-US/accent/accents.ftl +++ b/Resources/Locale/en-US/accent/accents.ftl @@ -98,7 +98,11 @@ accent-words-slimes-4 = Bluuump... accent-words-slimes-5 = Blabl blump! # Mothroach -accent-words-mothroach-1 = Chirp! +accent-words-mothroach-1 = Squeak! +accent-words-mothroach-2 = Chirp! +accent-words-mothroach-3 = Peep! +accent-words-mothroach-4 = Eeee! +accent-words-mothroach-5 = Eep! # Crab accent-words-crab-1 = Click. diff --git a/Resources/Locale/en-US/accent/italian.ftl b/Resources/Locale/en-US/accent/italian.ftl index d0ef4e8f72..cc8641417f 100644 --- a/Resources/Locale/en-US/accent/italian.ftl +++ b/Resources/Locale/en-US/accent/italian.ftl @@ -78,9 +78,6 @@ accent-italian-words-replace-23 = greek accent-italian-words-24 = operatives accent-italian-words-replace-24 = greeks -accent-italian-words-24 = ops -accent-italian-words-replace-24 = greeks - accent-italian-words-25 = sec accent-italian-words-replace-25 = polizia diff --git a/Resources/Locale/en-US/accent/pirate.ftl b/Resources/Locale/en-US/accent/pirate.ftl index 8da975df40..b6db7c803b 100644 --- a/Resources/Locale/en-US/accent/pirate.ftl +++ b/Resources/Locale/en-US/accent/pirate.ftl @@ -1,7 +1,7 @@ accent-pirate-prefix-1 = Arrgh accent-pirate-prefix-2 = Garr accent-pirate-prefix-3 = Yarr -accent-pirate-prefix-3 = Yarrgh +accent-pirate-prefix-4 = Yarrgh accent-pirate-replaced-1 = my accent-pirate-replacement-1 = me diff --git a/Resources/Locale/en-US/accessories/human-hair.ftl b/Resources/Locale/en-US/accessories/human-hair.ftl index 3a507ec6cf..7d3467a610 100644 --- a/Resources/Locale/en-US/accessories/human-hair.ftl +++ b/Resources/Locale/en-US/accessories/human-hair.ftl @@ -165,6 +165,7 @@ marking-HumanHairProtagonist = Slightly Long Hair marking-HumanHairSpikey = Spiky marking-HumanHairSpiky = Spiky 2 marking-HumanHairSpiky2 = Spiky 3 +marking-HumanHairSpookyLong = Spooky Long marking-HumanHairSwept = Swept Back Hair marking-HumanHairSwept2 = Swept Back Hair 2 marking-HumanHairTailed = Tailed diff --git a/Resources/Locale/en-US/accessories/vox-facial-hair.ftl b/Resources/Locale/en-US/accessories/vox-facial-hair.ftl index 48b19ca74b..a63b0b5a39 100644 --- a/Resources/Locale/en-US/accessories/vox-facial-hair.ftl +++ b/Resources/Locale/en-US/accessories/vox-facial-hair.ftl @@ -1,5 +1,5 @@ -marking-VoxFacialHairColonel = Vox Colonel -marking-VoxFacialHairFu = Quill Fu -marking-VoxFacialHairNeck = Neck Quills -marking-VoxFacialHairBeard = Quill Beard -marking-VoxFacialHairRuffBeard = Ruff Beard +marking-VoxFacialHairBeard = Vox Beard (Quills) +marking-VoxFacialHairColonel = Vox Moustache (Colonel) +marking-VoxFacialHairFu = Vox Moustache (Quill Fu) +marking-VoxFacialHairNeck = Vox Beard (Neck Quills) +marking-VoxFacialHairMane = Vox Beard (Mane) diff --git a/Resources/Locale/en-US/accessories/vox-hair.ftl b/Resources/Locale/en-US/accessories/vox-hair.ftl index cf98d0b4f9..94483fd6a6 100644 --- a/Resources/Locale/en-US/accessories/vox-hair.ftl +++ b/Resources/Locale/en-US/accessories/vox-hair.ftl @@ -1,13 +1,22 @@ -marking-VoxHairShortQuills = Short Vox Quills -marking-VoxHairKingly = Vox Kingly marking-VoxHairAfro = Vox Afro -marking-VoxHairMohawk = Vox Mohawk -marking-VoxHairYasuhiro = Vox Yasuhiro +marking-VoxHairBraids = Vox Braids +marking-VoxHairCrestedQuills = Vox Crested Quills +marking-VoxHairEmperorQuills = Vox Emperor Quills +marking-VoxHairFlowing = Vox Flowing +marking-VoxHairHawk = Vox Hawk marking-VoxHairHorns = Vox Horns -marking-VoxHairNights = Vox Nights -marking-VoxHairSurf = Vox Surf -marking-VoxHairCropped = Vox Cropped -marking-VoxHairRuffhawk = Vox Ruffhawk -marking-VoxHairRows = Vox Rows +marking-VoxHairKeelQuills = Vox Keel Quills +marking-VoxHairKeetQuills = Vox Keet Quills +marking-VoxHairKingly = Vox Kingly +marking-VoxHairLongBraid = Vox Long Braid marking-VoxHairMange = Vox Mange +marking-VoxHairMohawk = Vox Mohawk +marking-VoxHairNights = Vox Nights marking-VoxHairPony = Vox Pony +marking-VoxHairRazorClipped = Vox Razor (Clipped) +marking-VoxHairRazor = Vox Razor +marking-VoxHairSortBraid = Vox Short Braid +marking-VoxHairShortQuills = Vox Short Quills +marking-VoxHairSurf = Vox Surf +marking-VoxHairTielQuills = Vox Tiel Quills +marking-VoxHairYasu = Vox Yasuhiro diff --git a/Resources/Locale/en-US/actions/actions/shadowkin.ftl b/Resources/Locale/en-US/actions/actions/shadowkin.ftl new file mode 100644 index 0000000000..063e1eafb7 --- /dev/null +++ b/Resources/Locale/en-US/actions/actions/shadowkin.ftl @@ -0,0 +1,2 @@ +action-name-shadowkin-rest = Rest +action-description-shadowkin-rest = Rama diff --git a/Resources/Locale/en-US/administration/smites.ftl b/Resources/Locale/en-US/administration/smites.ftl index ae4e6f7271..ff3e3b0901 100644 --- a/Resources/Locale/en-US/administration/smites.ftl +++ b/Resources/Locale/en-US/administration/smites.ftl @@ -13,7 +13,6 @@ admin-smite-stomach-removal-self = Your stomach feels hollow... admin-smite-run-walk-swap-prompt = You have to press shift to run! admin-smite-super-speed-prompt = You move at mach 0.8! admin-smite-lung-removal-self = You can't breathe! -admin-smite-terminate-prompt = I'll be back ## Smite descriptions @@ -58,7 +57,6 @@ admin-smite-disarm-prone-description = Makes them get disarmed 100% of the time admin-smite-garbage-can-description = Turn them into a garbage bin to emphasize what they remind you of. admin-smite-super-bonk-description = Slams them on every single table on the Station and beyond. admin-smite-super-bonk-lite-description= Slams them on every single table on the Station and beyond. Stops when the target is dead. -admin-smite-terminate-description = Creates a Terminator ghost role with the sole objective of killing them. ## Tricks descriptions diff --git a/Resources/Locale/en-US/administration/ui/admin-logs.ftl b/Resources/Locale/en-US/administration/ui/admin-logs.ftl index 549e9587d7..377bea6e84 100644 --- a/Resources/Locale/en-US/administration/ui/admin-logs.ftl +++ b/Resources/Locale/en-US/administration/ui/admin-logs.ftl @@ -14,7 +14,6 @@ admin-logs-select-none = None # Players admin-logs-search-players-placeholder = Search Players (OR) -admin-logs-select-none = None admin-logs-include-non-player = Include Non-players # Logs diff --git a/Resources/Locale/en-US/administration/ui/admin-notes.ftl b/Resources/Locale/en-US/administration/ui/admin-notes.ftl index ca5348a940..03e1290257 100644 --- a/Resources/Locale/en-US/administration/ui/admin-notes.ftl +++ b/Resources/Locale/en-US/administration/ui/admin-notes.ftl @@ -35,7 +35,6 @@ admin-notes-message-seen = Seen admin-notes-banned-from = Banned from admin-notes-the-server = the server admin-notes-permanently = permanently -admin-notes-for = for {$player} admin-notes-days = {$days} days admin-notes-hours = {$hours} hours admin-notes-minutes = {$minutes} minutes diff --git a/Resources/Locale/en-US/administration/ui/player-panel.ftl b/Resources/Locale/en-US/administration/ui/player-panel.ftl new file mode 100644 index 0000000000..208268d589 --- /dev/null +++ b/Resources/Locale/en-US/administration/ui/player-panel.ftl @@ -0,0 +1 @@ +player-panel-job-whitelists = Job Whitelists diff --git a/Resources/Locale/en-US/alerts/alerts.ftl b/Resources/Locale/en-US/alerts/alerts.ftl index ff2c0d9ee2..ad61ae8967 100644 --- a/Resources/Locale/en-US/alerts/alerts.ftl +++ b/Resources/Locale/en-US/alerts/alerts.ftl @@ -57,9 +57,6 @@ alerts-no-battery-desc = You don't have a battery, rendering you unable to charg alerts-internals-name = Toggle internals alerts-internals-desc = Toggles your gas tank internals on or off. -alerts-internals-name = Toggle internals -alerts-internals-desc = Toggles your gas tank internals on or off. - alerts-piloting-name = Piloting Shuttle alerts-piloting-desc = You are piloting a shuttle. Click the alert to stop. @@ -116,3 +113,6 @@ alerts-walking-desc = Indicates how fast you're moving. alerts-offer-name = Offer alerts-offer-desc = Someone offers you an item. + +alerts-deflecting-name = Deflecting +alerts-deflecting-desc = You have a chance to deflect incoming projectiles. Standing still or moving slowly will increase this chance. diff --git a/Resources/Locale/en-US/alerts/shadowkin.ftl b/Resources/Locale/en-US/alerts/shadowkin.ftl new file mode 100644 index 0000000000..10f8438b76 --- /dev/null +++ b/Resources/Locale/en-US/alerts/shadowkin.ftl @@ -0,0 +1,2 @@ +alerts-shadowkin-power-name = Power Level +alerts-shadowkin-power-desc = How much energy is available to spend on Shadowkin powers. \ No newline at end of file diff --git a/Resources/Locale/en-US/ame/components/ame-controller-component.ftl b/Resources/Locale/en-US/ame/components/ame-controller-component.ftl index ee1f7f42e7..f15141ebcc 100644 --- a/Resources/Locale/en-US/ame/components/ame-controller-component.ftl +++ b/Resources/Locale/en-US/ame/components/ame-controller-component.ftl @@ -16,7 +16,6 @@ ame-window-refresh-parts-button = Refresh Parts ame-window-core-count-label = Core count: ame-window-power-currentsupply-label = Current power supply: ame-window-power-targetsupply-label = Targeted power supply: -ame-window-toggle-injection-button = Toggle Injection ame-window-eject-button = Eject ame-window-increase-fuel-button = Increase ame-window-decrease-fuel-button = Decrease diff --git a/Resources/Locale/en-US/anomaly/anomaly.ftl b/Resources/Locale/en-US/anomaly/anomaly.ftl index da5882fa62..1609d77d91 100644 --- a/Resources/Locale/en-US/anomaly/anomaly.ftl +++ b/Resources/Locale/en-US/anomaly/anomaly.ftl @@ -3,6 +3,7 @@ anomaly-component-contact-damage = The anomaly sears off your skin! anomaly-vessel-component-anomaly-assigned = Anomaly assigned to vessel. anomaly-vessel-component-not-assigned = This vessel is not assigned to any anomaly. Try using a scanner on it. anomaly-vessel-component-assigned = This vessel is currently assigned to an anomaly. +anomaly-vessel-component-upgrade-output = point output anomaly-particles-delta = Delta particles anomaly-particles-epsilon = Epsilon particles diff --git a/Resources/Locale/en-US/arcade/components/space-villain-game-component.ftl b/Resources/Locale/en-US/arcade/components/space-villain-game-component.ftl index af005ae62d..75e18c8d71 100644 --- a/Resources/Locale/en-US/arcade/components/space-villain-game-component.ftl +++ b/Resources/Locale/en-US/arcade/components/space-villain-game-component.ftl @@ -11,5 +11,4 @@ space-villain-game-enemy-dies-with-player-message = {$enemyName} dies, but takes space-villain-game-enemy-throws-bomb-message = {$enemyName} throws a bomb, exploding you for {$damageReceived} damage! space-villain-game-enemy-steals-player-power-message = {$enemyName} steals {$stolenAmount} of your power! space-villain-game-enemy-heals-message = {$enemyName} heals for {$healedAmount} health! -space-villain-game-enemy-steals-player-power-message = {$enemyName} steals {$stolenAmount} of your power! -space-villain-game-enemy-attacks-message = {$enemyName} attacks you for {$damageDealt} damage! \ No newline at end of file +space-villain-game-enemy-attacks-message = {$enemyName} attacks you for {$damageDealt} damage! diff --git a/Resources/Locale/en-US/armor/armor-examine.ftl b/Resources/Locale/en-US/armor/armor-examine.ftl index 6dc511e66e..53a5954ab6 100644 --- a/Resources/Locale/en-US/armor/armor-examine.ftl +++ b/Resources/Locale/en-US/armor/armor-examine.ftl @@ -17,4 +17,4 @@ armor-damage-type-cold = Cold armor-damage-type-poison = Poison armor-damage-type-shock = Shock armor-damage-type-structural = Structural -armor-examine-stamina = Reduces your stamina damage by [color=cyan]{$num}%[/color]. \ No newline at end of file +armor-examine-stamina = - [color=cyan]Stamina[/color] damage reduced by [color=lightblue]{$num}%[/color]. \ No newline at end of file diff --git a/Resources/Locale/en-US/atmos/atmos-alerts-console.ftl b/Resources/Locale/en-US/atmos/atmos-alerts-console.ftl new file mode 100644 index 0000000000..a1640c5e9d --- /dev/null +++ b/Resources/Locale/en-US/atmos/atmos-alerts-console.ftl @@ -0,0 +1,35 @@ +atmos-alerts-window-title = Atmospheric Alerts Computer +atmos-alerts-window-station-name = [color=white][font size=14]{$stationName}[/font][/color] +atmos-alerts-window-unknown-location = Unknown location + +atmos-alerts-window-tab-no-alerts = Alerts +atmos-alerts-window-tab-alerts = Alerts ({$value}) +atmos-alerts-window-tab-air-alarms = Air alarms +atmos-alerts-window-tab-fire-alarms = Fire alarms + +atmos-alerts-window-alarm-label = {CAPITALIZE($name)} ({$address}) +atmos-alerts-window-temperature-label = Temperature +atmos-alerts-window-temperature-value = {$valueInC} °C ({$valueInK} K) +atmos-alerts-window-pressure-label = Pressure +atmos-alerts-window-pressure-value = {$value} kPa +atmos-alerts-window-oxygenation-label = Oxygenation +atmos-alerts-window-oxygenation-value = {$value}% +atmos-alerts-window-other-gases-label = Other present gases +atmos-alerts-window-other-gases-value = {$shorthand} ({$value}%) +atmos-alerts-window-other-gases-value-nil = None +atmos-alerts-window-silence-alerts = Silence alerts from this alarm + +atmos-alerts-window-label-alert-types = Alert levels: +atmos-alerts-window-normal-state = Normal +atmos-alerts-window-warning-state = Warning +atmos-alerts-window-danger-state = Danger! +atmos-alerts-window-invalid-state = Inactive + +atmos-alerts-window-no-active-alerts = [font size=16][color=white]No active alerts -[/color] [color={$color}]situation normal[/color][/font] +atmos-alerts-window-no-data-available = No data available +atmos-alerts-window-alerts-being-silenced = Silencing alerts... + +atmos-alerts-window-toggle-overlays = Toggle alarm display + +atmos-alerts-window-flavor-left = Contact an atmospheric technician for assistance +atmos-alerts-window-flavor-right = v1.8 \ No newline at end of file diff --git a/Resources/Locale/en-US/atmos/gas-analyzer-component.ftl b/Resources/Locale/en-US/atmos/gas-analyzer-component.ftl index 03a920cb64..652bb19cb5 100644 --- a/Resources/Locale/en-US/atmos/gas-analyzer-component.ftl +++ b/Resources/Locale/en-US/atmos/gas-analyzer-component.ftl @@ -12,6 +12,8 @@ gas-analyzer-window-refresh-button = Refresh gas-analyzer-window-no-data = No Data gas-analyzer-window-no-gas-text = No Gases gas-analyzer-window-error-text = Error: {$errorText} +gas-analyzer-window-volume-text = Volume: +gas-analyzer-window-volume-val-text = {$volume} L gas-analyzer-window-pressure-text = Pressure: gas-analyzer-window-pressure-val-text = {$pressure} kPa gas-analyzer-window-temperature-text = Temperature: diff --git a/Resources/Locale/en-US/atmos/gas-recycler-system.ftl b/Resources/Locale/en-US/atmos/gas-recycler-system.ftl index cc527adf5c..a72e137732 100644 --- a/Resources/Locale/en-US/atmos/gas-recycler-system.ftl +++ b/Resources/Locale/en-US/atmos/gas-recycler-system.ftl @@ -1,3 +1,6 @@ gas-recycler-reacting = It is [color=green]converting[/color] waste gases. gas-recycler-low-pressure = The input pressure is [color=darkred]too low[/color]. gas-recycler-low-temperature = The input temperature is [color=darkred]too low[/color]. + +gas-recycler-upgrade-min-temp = Minimum temperature +gas-recycler-upgrade-min-pressure = Minimum pressure diff --git a/Resources/Locale/en-US/atmos/portable-scrubber.ftl b/Resources/Locale/en-US/atmos/portable-scrubber.ftl index c4071b4acc..8aadf076d9 100644 --- a/Resources/Locale/en-US/atmos/portable-scrubber.ftl +++ b/Resources/Locale/en-US/atmos/portable-scrubber.ftl @@ -1 +1,4 @@ portable-scrubber-fill-level = It's at about [color=yellow]{$percent}%[/color] of its maximum internal pressure. + +portable-scrubber-component-upgrade-max-pressure = max pressure +portable-scrubber-component-upgrade-transfer-rate = transfer rate diff --git a/Resources/Locale/en-US/body/behavior/behavior.ftl b/Resources/Locale/en-US/body/behavior/behavior.ftl index 6870fdb894..27297172f9 100644 --- a/Resources/Locale/en-US/body/behavior/behavior.ftl +++ b/Resources/Locale/en-US/body/behavior/behavior.ftl @@ -1 +1,2 @@ -lung-behavior-gasp = Gasp \ No newline at end of file +lung-behavior-gasp = Gasp +silicon-behavior-buzz = Bzzzzt... diff --git a/Resources/Locale/en-US/borg/borg.ftl b/Resources/Locale/en-US/borg/borg.ftl index 2f51331a83..c9005eb796 100644 --- a/Resources/Locale/en-US/borg/borg.ftl +++ b/Resources/Locale/en-US/borg/borg.ftl @@ -17,3 +17,8 @@ borg-ui-no-brain = No brain present borg-ui-remove-battery = Remove borg-ui-modules-label = Modules: borg-ui-module-counter = {$actual}/{$max} + +# Transponder +borg-transponder-disabled-popup = A brain shoots out the top of {$name}! +borg-transponder-emagged-disabled-popup = Your transponder's lights go out! +borg-transponder-emagged-destroyed-popup = Your transponder's fuse blows! diff --git a/Resources/Locale/en-US/botany/components/seed-extractor-component.ftl b/Resources/Locale/en-US/botany/components/seed-extractor-component.ftl index 84d5a5ed28..c586a594a9 100644 --- a/Resources/Locale/en-US/botany/components/seed-extractor-component.ftl +++ b/Resources/Locale/en-US/botany/components/seed-extractor-component.ftl @@ -2,3 +2,5 @@ seed-extractor-component-interact-message = You extract some seeds from the { THE($name) }. seed-extractor-component-no-seeds = { CAPITALIZE(THE($name)) } has no seeds! + +seed-extractor-component-upgrade-seed-yield = seed yield diff --git a/Resources/Locale/en-US/burning/bodyburn.ftl b/Resources/Locale/en-US/burning/bodyburn.ftl index 896a0b6d04..58b98c09bb 100644 --- a/Resources/Locale/en-US/burning/bodyburn.ftl +++ b/Resources/Locale/en-US/burning/bodyburn.ftl @@ -1 +1 @@ -bodyburn-text-others = {$name}'s body burns to ash! +bodyburn-text-others = {$name} burns to ash! diff --git a/Resources/Locale/en-US/cargo/bounties.ftl b/Resources/Locale/en-US/cargo/bounties.ftl index b332517c70..966fb271b4 100644 --- a/Resources/Locale/en-US/cargo/bounties.ftl +++ b/Resources/Locale/en-US/cargo/bounties.ftl @@ -86,7 +86,7 @@ bounty-description-flower = Commander Zot really wants to sweep Security Officer bounty-description-galaxythistle = After a particularly nasty foam backpressure from a scrubber, a high-ranking officer got badly poisoned. Send us some galaxythistle so we can prepare him a homeopathic remedy. bounty-description-handcuffs = A large influx of escaped convicts have arrived at Central Command. Now is the perfect time to ship out spare handcuffs (or restraints). bounty-description-instrument = The hottest new band in the galaxy, Cindy Kate and the Saboteurs, lost their gear in a cargo shuttle collision. Send them a new set of instruments so they can play their show. -bounty-description-knife = One of our top commanders recently won a brand new set of knives on an official Nanotrasen gameshow. Unforunately, we don't have a set on hand. Send us a bunch of sharp things so we can throw something together, +bounty-description-knife = One of our top commanders recently won a brand new set of knives on an official Nanotrasen gameshow, but, unforunately, we don't have a set on hand. Send us a bunch of sharp things so we can throw something together. bounty-description-lemon = Dr Jones's kid is starting up a lemonade stand. Small issue: lemons don't get shipped to this sector. Fix that for a nice reward. bounty-description-lime = After a heavy drinking session, Admiral Pastich developed a strong addiction to fresh lime wedges. Send us some limes so we can prepare him his new favorite snack. bounty-description-lung = The pro-smoking league has been fighting to keep cigarettes on our stations for millennia. Unfortunately, they're lungs aren't fighting so hard anymore. Send them some new ones. @@ -118,17 +118,17 @@ bounty-description-lasergun = The Salvage Caravan requests a large shipment of l bounty-description-food = After the rat king invasion, a neighboring unathi station was left completely without food. A large meat food shipment is needed. bounty-description-fruit = A heroic monkey helped the chaplain catch a troublemaker hiding in the chapel, and the crew wants to reward him for his good work. bounty-description-vegetable = The new chef is a vegetarian, and botany can't keep up with their demands. We need some additional veggies to help keep things stocked. -bounty-description-chili = Today's the Centcomm Chili Cookoff, and, well, a few of us forgot to make some. Please help cover for us. +bounty-description-chili = Today's the CentComm Chili Cookoff, and, well, a few of us forgot to make some. Please help cover for us. bounty-description-rollerskates = CentComm Security is proposing a new strategy for helping officers win foot pursuits. Send them a couple so they cna learn how bad an idea this is. bounty-description-bedsheet = Someone in Atmos keeps turning down the heater, and we're all shivering in our beds. Please send us some extra sheets to stay warm. bounty-description-bandana = Bzzzt... Transmission from prison planet OC-1001: We're... reorganizing our command structure. Send us some bandanas so we can tell gan- I mean, departments apart. bounty-description-steak = The vegetarian cook is refusing to make us anything with meat, and the lizards are getting restless. Can you smuggle us a few steaks to keep them happy? bounty-description-banana = Hi station! Botany won't gimme any more. They said slipping the HoS out an open airlock wasn't funny! Can you believe it? Help me out! HONK. bounty-description-beer = Some nefarious agent has stolen every single drink in the bar. Yes, everything. Help tide us over until we can find them. -bounty-description-hi-viz-vest = The clown stole the AME controller and won't back. It's pretty dark in here. Some hi-viz vests would make seeing each other in the dark a little mroe bearable. +bounty-description-hi-viz-vest = The clown stole the AME controller and won't bring it back. It's pretty dark in here. Some hi-viz vests would make seeing each other in the dark a little more bearable. bounty-description-torch = The chef made all the monkeys and kobolds at once, and they rebelled and took over the cargo shuttle. They're demanding supplies and free passage to a jungle planet, and we're giving in to their demands. All they need now is a few torches. bounty-description-medkit-box = CentComm is putting on a play set in a hospital, and needs some props. Just send us some empty medkit boxes, and the show will go on! bounty-description-cardobard-box = "The Cardborgs Cometh" is a new play premiering tomorrow, and the costuming team is woefully unprepared. Send us some boxes to work with. -bounty-description-wine = The new librarian and the Quartermaster are falling head over heels for each other after she caught him disassembling the bookshelves for wood. Send a couple bottles of wine (Or cans, if you must) to help make the date go well. +bounty-description-wine = The new cataloger and the Logistics Officer are falling head over heels for each other after she caught him disassembling the bookshelves for wood. Send a couple bottles of wine (Or cans, if you must) to help make the date go well. bounty-description-cotton-boll = A massive swarm of mothroaches ate all the paper and cloth on the station. Send us some cotton to help keep our winged crewmembers fed. bounty-description-microwave-machine-board = Mr. Giggles thought it'd be funny to stick forks in all the kitchen microwaves. Help us replace them before the chefs start making clown burgers. diff --git a/Resources/Locale/en-US/cargo/cargo-console-component.ftl b/Resources/Locale/en-US/cargo/cargo-console-component.ftl index b56f4730cc..941e0fa180 100644 --- a/Resources/Locale/en-US/cargo/cargo-console-component.ftl +++ b/Resources/Locale/en-US/cargo/cargo-console-component.ftl @@ -30,6 +30,7 @@ cargo-console-snip-snip = Order trimmed to capacity cargo-console-insufficient-funds = Insufficient funds (require {$cost}) cargo-console-unfulfilled = No room to fulfill order cargo-console-trade-station = Sent to {$destination} +cargo-console-unlock-approved-order-broadcast = [bold]{$productName} x{$orderAmount}[/bold], which cost [bold]{$cost}[/bold], was approved by [bold]{$approverName}, {$approverJob}[/bold] cargo-console-paper-print-name = Order #{$orderNumber} cargo-console-paper-print-text = @@ -45,3 +46,5 @@ cargo-shuttle-console-station-unknown = Unknown cargo-shuttle-console-shuttle-not-found = Not found cargo-shuttle-console-organics = Detected organic lifeforms on the shuttle cargo-no-shuttle = No cargo shuttle found! + +cargo-telepad-delay-upgrade = Teleport delay diff --git a/Resources/Locale/en-US/chameleon-projector/chameleon-projector.ftl b/Resources/Locale/en-US/chameleon-projector/chameleon-projector.ftl new file mode 100644 index 0000000000..8a79516077 --- /dev/null +++ b/Resources/Locale/en-US/chameleon-projector/chameleon-projector.ftl @@ -0,0 +1,2 @@ +chameleon-projector-invalid = You can't disguise as that! +chameleon-projector-success = Projected new disguise. diff --git a/Resources/Locale/en-US/chapel/altar.ftl b/Resources/Locale/en-US/chapel/altar.ftl new file mode 100644 index 0000000000..ed031d638a --- /dev/null +++ b/Resources/Locale/en-US/chapel/altar.ftl @@ -0,0 +1,11 @@ +altar-examine = [color=purple]This altar can be used to sacrifice Psionics.[/color] +altar-sacrifice-verb = Sacrifice + +altar-failure-reason-self = You can't sacrifice yourself! +altar-failure-reason-user = You are not psionic or clerically trained! +altar-failure-reason-user-humanoid = You are not a humanoid! +altar-failure-reason-target = {CAPITALIZE(THE($target))} {CONJUGATE-BE($target)} not psionic! +altar-failure-reason-target-humanoid = {CAPITALIZE(THE($target))} {CONJUGATE-BE($target)} not a humanoid! +altar-failure-reason-target-catatonic = {CAPITALIZE(THE($target))} {CONJUGATE-BE($target)} braindead! + +altar-sacrifice-popup = {$user} starts to sacrifice {$target}! diff --git a/Resources/Locale/en-US/chat/chat-repo.ftl b/Resources/Locale/en-US/chat/chat-repo.ftl new file mode 100644 index 0000000000..a53380260b --- /dev/null +++ b/Resources/Locale/en-US/chat/chat-repo.ftl @@ -0,0 +1,7 @@ +command-description-deletechatmessage-id = Delete a specific chat message by message ID +command-description-nukechatmessages-usernames = Delete all of the supplied usernames' chat messages posted during this round +command-description-nukechatmessages-userids = Delete all of the supplied userIds' chat messages posted during this round + +command-error-deletechatmessage-id-notexist = The message with the supplied ID does not exist +command-error-nukechatmessages-usernames-usernamenotexist = Username {$username} does not exist +command-error-nukechatmessages-usernames-usernamenomessages = UserID {$userId} has no messages to nuke diff --git a/Resources/Locale/en-US/chat/emotes.ftl b/Resources/Locale/en-US/chat/emotes.ftl new file mode 100644 index 0000000000..4e26752c4b --- /dev/null +++ b/Resources/Locale/en-US/chat/emotes.ftl @@ -0,0 +1,62 @@ +# Names +chat-emote-name-scream = Scream +chat-emote-name-laugh = Laugh +chat-emote-name-honk = Honk +chat-emote-name-sigh = Sigh +chat-emote-name-whistle = Whistle +chat-emote-name-crying = Crying +chat-emote-name-squish = Squish +chat-emote-name-chitter = Chitter +chat-emote-name-squeak = Squeak +chat-emote-name-click = Click +chat-emote-name-clap = Clap +chat-emote-name-snap = Snap +chat-emote-name-salute = Salute +chat-emote-name-deathgasp = Deathgasp +chat-emote-name-buzz = Buzz +chat-emote-name-weh = Weh +chat-emote-name-chirp = Chirp +chat-emote-name-beep = Beep +chat-emote-name-chime = Chime +chat-emote-name-buzztwo = Buzz Two +chat-emote-name-ping = Ping +chat-emote-name-sneeze = Sneeze +chat-emote-name-cough = Cough +chat-emote-name-catmeow = Cat Meow +chat-emote-name-cathisses = Cat Hisses +chat-emote-name-monkeyscreeches = Monkey Screeches +chat-emote-name-robotbeep = Robot +chat-emote-name-yawn = Yawn +chat-emote-name-snore = Snore +chat-emote-name-mars = Mars +chat-emote-name-wurble = Wurble + +# Message +chat-emote-msg-scream = screams! +chat-emote-msg-laugh = laughs. +chat-emote-msg-honk = honks. +chat-emote-msg-sigh = sighs. +chat-emote-msg-whistle = whistles. +chat-emote-msg-crying = cries. +chat-emote-msg-squish = squishes. +chat-emote-msg-chitter = chitters. +chat-emote-msg-squeak = squeaks. +chat-emote-msg-click = clicks. +chat-emote-msg-clap = claps! +chat-emote-msg-snap = snaps {POSS-ADJ($entity)} fingers. +chat-emote-msg-salute = salutes. +chat-emote-msg-deathgasp = seizes up and falls limp, {POSS-ADJ($entity)} eyes dead and lifeless... +chat-emote-msg-deathgasp-monkey = lets out a faint chimper as {SUBJECT($entity)} collapses and stops moving... +chat-emote-msg-buzz = buzz! +chat-emote-msg-chirp = chirps! +chat-emote-msg-beep = beeps. +chat-emote-msg-chime = chimes. +chat-emote-msg-buzzestwo = buzzes twice. +chat-emote-msg-ping = pings. +chat-emote-msg-sneeze = sneezes. +chat-emote-msg-cough = coughs. +chat-emote-msg-catmeow = meows. +chat-emote-msg-cathisses = hisses! +chat-emote-msg-monkeyscreeches = screeches! +chat-emote-msg-yawn = yawns. +chat-emote-msg-snore = snores. diff --git a/Resources/Locale/en-US/chat/managers/chat-language.ftl b/Resources/Locale/en-US/chat/managers/chat-language.ftl new file mode 100644 index 0000000000..f975160674 --- /dev/null +++ b/Resources/Locale/en-US/chat/managers/chat-language.ftl @@ -0,0 +1,15 @@ +chat-language-Universal-name = Universal +chat-language-Bubblish-name = Bubblish +chat-language-RootSpeak-name = Rootspeak +chat-language-Nekomimetic-name = Neko +chat-language-Draconic-name = Sinta'Unathi +chat-language-Azaziba-name = Sinta'Azaziba +chat-language-SolCommon-name = Sol Common +chat-language-TauCetiBasic-name = Basic +chat-language-Tradeband-name = Tradeband +chat-language-Freespeak-name = Freespeak +chat-language-Elyran-name = Elyran +chat-language-Canilunzt-name = Canilunzt +chat-language-Moffic-name = Moffic +chat-language-RobotTalk-name = Binary +chat-language-ValyrianStandard-name = Valyrian diff --git a/Resources/Locale/en-US/chat/managers/chat-manager.ftl b/Resources/Locale/en-US/chat/managers/chat-manager.ftl index 589d79e6ea..f2b70e72a8 100644 --- a/Resources/Locale/en-US/chat/managers/chat-manager.ftl +++ b/Resources/Locale/en-US/chat/managers/chat-manager.ftl @@ -21,11 +21,11 @@ chat-manager-whisper-headset-on-message = You can't whisper on the radio! chat-manager-server-wrap-message = [bold]{$message}[/bold] chat-manager-sender-announcement-wrap-message = [font size=14][bold]{$sender} Announcement:[/font][font size=12] {$message}[/bold][/font] -chat-manager-entity-say-wrap-message = [BubbleHeader][Name]{$entityName}[/Name][/BubbleHeader] {$verb}, [font="{$fontType}" size={$fontSize}]"[color={$color}][BubbleContent]{$message}[/BubbleContent][/color]"[/font] -chat-manager-entity-say-bold-wrap-message = [BubbleHeader][Name]{$entityName}[/Name][/BubbleHeader] {$verb}, [font="{$fontType}" size={$fontSize}]"[color={$color}][BubbleContent][bold]{$message}[/bold][/BubbleContent][/color]"[/font] +chat-manager-entity-say-wrap-message = [BubbleHeader][bold][Name]{ $language }{ $entityName }[/Name][/bold][/BubbleHeader] { $verb }, [font={ $fontType } size={ $fontSize } ]"[BubbleContent]{ $message }[/BubbleContent]"[/font] +chat-manager-entity-say-bold-wrap-message = [BubbleHeader][bold][Name]{ $language }{ $entityName }[/Name][/bold][/BubbleHeader] { $verb }, [font={ $fontType } size={ $fontSize }]"[BubbleContent][bold]{ $message }[/bold][/BubbleContent]"[/font] -chat-manager-entity-whisper-wrap-message = [font size=11][italic][BubbleHeader][Name]{$entityName}[/Name][/BubbleHeader] whispers,"[color={$color}][BubbleContent]{$message}[/BubbleContent][/color]"[/italic][/font] -chat-manager-entity-whisper-unknown-wrap-message = [font size=11][italic][BubbleHeader]Someone[/BubbleHeader] whispers, "[color={$color}][BubbleContent]{$message}[/BubbleContent][/color]"[/italic][/font] +chat-manager-entity-whisper-wrap-message = [font size=11][italic][BubbleHeader][Name]{ $language }{ $entityName }[/Name][/BubbleHeader] whispers,"[BubbleContent]{ $message }[/BubbleContent]"[/italic][/font] +chat-manager-entity-whisper-unknown-wrap-message = [font size=11][italic][BubbleHeader]Someone[/BubbleHeader] whispers, "[BubbleContent][font="{$fontType}"][color={$color}]{$message}[/color][/font][/BubbleContent]"[/italic][/font] # THE() is not used here because the entity and its name can technically be disconnected if a nameOverride is passed... chat-manager-entity-me-wrap-message = [italic]{ PROPER($entity) -> @@ -50,6 +50,8 @@ chat-manager-admin-channel-name = ADMIN chat-manager-rate-limited = You are sending messages too quickly! chat-manager-rate-limit-admin-announcement = Player { $player } breached chat rate limits. Watch them if this is a regular occurence. +chat-manager-send-empathy-chat-wrap-message = {$source}: {$message} + ## Speech verbs for chat chat-speech-verb-suffix-exclamation = ! @@ -156,3 +158,5 @@ chat-speech-verb-name-electricity = Electricity chat-speech-verb-electricity-1 = crackles chat-speech-verb-electricity-2 = buzzes chat-speech-verb-electricity-3 = screeches + +chat-speech-verb-marish = Mars diff --git a/Resources/Locale/en-US/chat/ui/chat-box.ftl b/Resources/Locale/en-US/chat/ui/chat-box.ftl index 720f0d15ab..0dbfc0a27b 100644 --- a/Resources/Locale/en-US/chat/ui/chat-box.ftl +++ b/Resources/Locale/en-US/chat/ui/chat-box.ftl @@ -30,4 +30,4 @@ hud-chatbox-channel-Notifications = Notifications hud-chatbox-channel-Server = Server hud-chatbox-channel-Visual = Actions hud-chatbox-channel-Damage = Damage -hud-chatbox-channel-Unspecified = Unspecified +hud-chatbox-channel-Unspecified = Unspecified \ No newline at end of file diff --git a/Resources/Locale/en-US/chat/ui/emote-menu.ftl b/Resources/Locale/en-US/chat/ui/emote-menu.ftl new file mode 100644 index 0000000000..1f92a93c63 --- /dev/null +++ b/Resources/Locale/en-US/chat/ui/emote-menu.ftl @@ -0,0 +1,3 @@ +emote-menu-category-general = General +emote-menu-category-vocal = Vocal +emote-menu-category-hands = Hands diff --git a/Resources/Locale/en-US/chemistry/components/reagent-dispenser-component.ftl b/Resources/Locale/en-US/chemistry/components/reagent-dispenser-component.ftl index 48ec8f5213..37697c4517 100644 --- a/Resources/Locale/en-US/chemistry/components/reagent-dispenser-component.ftl +++ b/Resources/Locale/en-US/chemistry/components/reagent-dispenser-component.ftl @@ -10,9 +10,9 @@ reagent-dispenser-bound-user-interface-title = Reagent dispenser ## UI reagent-dispenser-window-amount-to-dispense-label = Amount -reagent-dispenser-window-container-label = Container: reagent-dispenser-window-clear-button = Clear reagent-dispenser-window-eject-button = Eject +reagent-dispenser-window-eject-container-button = ⏏ reagent-dispenser-window-no-container-loaded-text = No container loaded. reagent-dispenser-window-reagent-name-not-found-text = Reagent name not found reagent-dispenser-window-unknown-reagent-text = Unknown reagent diff --git a/Resources/Locale/en-US/chemistry/components/solution-heater-component.ftl b/Resources/Locale/en-US/chemistry/components/solution-heater-component.ftl new file mode 100644 index 0000000000..cecf15550c --- /dev/null +++ b/Resources/Locale/en-US/chemistry/components/solution-heater-component.ftl @@ -0,0 +1 @@ +solution-heater-upgrade-heat = Heat strength diff --git a/Resources/Locale/en-US/chemistry/components/solution-scanner-component.ftl b/Resources/Locale/en-US/chemistry/components/solution-scanner-component.ftl index 8dbbaf3edd..51a049aab1 100644 --- a/Resources/Locale/en-US/chemistry/components/solution-scanner-component.ftl +++ b/Resources/Locale/en-US/chemistry/components/solution-scanner-component.ftl @@ -3,3 +3,4 @@ scannable-solution-verb-message = Examine the chemical composition. scannable-solution-main-text = It contains the following chemicals: scannable-solution-empty-container = It contains no chemicals. scannable-solution-chemical = - {$amount}u [color={$color}]{$type}[/color] +scannable-solution-temperature = Solution temperature: {$temperature}K \ No newline at end of file diff --git a/Resources/Locale/en-US/chemistry/components/solution-status.ftl b/Resources/Locale/en-US/chemistry/components/solution-status.ftl new file mode 100644 index 0000000000..0ec5f932e0 --- /dev/null +++ b/Resources/Locale/en-US/chemistry/components/solution-status.ftl @@ -0,0 +1,2 @@ +solution-status-volume = Volume: [color=white]{$currentVolume}/{$maxVolume}u[/color] +solution-status-transfer = Transfer: [color=white]{$volume}u[/color] diff --git a/Resources/Locale/en-US/commands/job-whitelist-command.ftl b/Resources/Locale/en-US/commands/job-whitelist-command.ftl new file mode 100644 index 0000000000..add6bca760 --- /dev/null +++ b/Resources/Locale/en-US/commands/job-whitelist-command.ftl @@ -0,0 +1,18 @@ +cmd-jobwhitelist-job-does-not-exist = Job {$job} does not exist. +cmd-jobwhitelist-player-not-found = Player {$player} not found. +cmd-jobwhitelist-hint-player = [player] +cmd-jobwhitelist-hint-job = [job] +cmd-jobwhitelistadd-desc = Lets a player play a whitelisted job. +cmd-jobwhitelistadd-help = Usage: jobwhitelistadd +cmd-jobwhitelistadd-already-whitelisted = {$player} is already whitelisted to play as {$jobId} .({$jobName}). +cmd-jobwhitelistadd-added = Added {$player} to the {$jobId} ({$jobName}) whitelist. +cmd-jobwhitelistget-desc = Gets all the jobs that a player has been whitelisted for. +cmd-jobwhitelistget-help = Usage: jobwhitelistadd +cmd-jobwhitelistget-whitelisted-none = Player {$player} is not whitelisted for any jobs. +cmd-jobwhitelistget-whitelisted-for = "Player {$player} is whitelisted for: +{$jobs}" + +cmd-jobwhitelistremove-desc = Removes a player's ability to play a whitelisted job. +cmd-jobwhitelistremove-help = Usage: jobwhitelistadd +cmd-jobwhitelistremove-was-not-whitelisted = {$player} was not whitelisted to play as {$jobId} ({$jobName}). +cmd-jobwhitelistremove-removed = Removed {$player} from the whitelist for {$jobId} ({$jobName}). diff --git a/Resources/Locale/en-US/commands/tippy-command.ftl b/Resources/Locale/en-US/commands/tippy-command.ftl new file mode 100644 index 0000000000..6b9a95a1dd --- /dev/null +++ b/Resources/Locale/en-US/commands/tippy-command.ftl @@ -0,0 +1,12 @@ +cmd-tippy-desc = Broadcast a message as Tippy the clown. +cmd-tippy-help = tippy [entity prototype] [speak time] [slide time] [waddle interval] +cmd-tippy-auto-1 = +cmd-tippy-auto-2 = message +cmd-tippy-auto-3 = entity prototype +cmd-tippy-auto-4 = speak time, in seconds +cmd-tippy-auto-5 = slide time, in seconds +cmd-tippy-auto-6 = waddle interval, in seconds +cmd-tippy-error-no-user = User not found. +cmd-tippy-error-no-prototype = Prototype not found: {$proto} + +cmd-tip-desc = Spawn a random game tip. diff --git a/Resources/Locale/en-US/commands/toolshed-commands.ftl b/Resources/Locale/en-US/commands/toolshed-commands.ftl index 04f6aa08fa..e2536f6781 100644 --- a/Resources/Locale/en-US/commands/toolshed-commands.ftl +++ b/Resources/Locale/en-US/commands/toolshed-commands.ftl @@ -80,3 +80,5 @@ command-description-mind-control = Assumes control of an entity with the given player. command-description-addaccesslog = Adds an access log to this entity. Do note that this bypasses the log's default limit and pause check. +command-description-stationevent-simulate = + Simulates N number of rounds in which events will occur and prints the occurrences of every event after. diff --git a/Resources/Locale/en-US/communications/communications-console-component.ftl b/Resources/Locale/en-US/communications/communications-console-component.ftl index f7cc87cb8b..bead43df28 100644 --- a/Resources/Locale/en-US/communications/communications-console-component.ftl +++ b/Resources/Locale/en-US/communications/communications-console-component.ftl @@ -1,4 +1,4 @@ -# User interface +# User interface comms-console-menu-title = Communications Console comms-console-menu-announcement-placeholder = Announcement text... comms-console-menu-announcement-button = Announce @@ -9,6 +9,7 @@ comms-console-menu-recall-shuttle = Recall emergency shuttle # Popup comms-console-permission-denied = Permission denied comms-console-shuttle-unavailable = Shuttle is currently unavailable +comms-console-message-too-long = Message is too long # Placeholder values comms-console-announcement-sent-by = Sent by diff --git a/Resources/Locale/en-US/components/storage-component.ftl b/Resources/Locale/en-US/components/storage-component.ftl index 29c858891a..e742c83f6a 100644 --- a/Resources/Locale/en-US/components/storage-component.ftl +++ b/Resources/Locale/en-US/components/storage-component.ftl @@ -8,3 +8,5 @@ comp-storage-cant-drop = You can't let go of { THE($entity) }! comp-storage-window-title = Storage Item comp-storage-window-weight = { $weight }/{ $maxWeight }, Max Size: {$size} comp-storage-window-slots = Slots: { $itemCount }/{ $maxCount }, Max Size: {$size} +comp-storage-verb-open-storage = Open Storage +comp-storage-verb-close-storage = Close Storage diff --git a/Resources/Locale/en-US/construction/components/machine-part-component.ftl b/Resources/Locale/en-US/construction/components/machine-part-component.ftl new file mode 100644 index 0000000000..0613f50516 --- /dev/null +++ b/Resources/Locale/en-US/construction/components/machine-part-component.ftl @@ -0,0 +1,2 @@ +machine-part-component-on-examine-rating-text = [color=white]Rating:[/color] [color=cyan]{$rating}[/color] +machine-part-component-on-examine-type-text = [color=white]Type:[/color] [color=cyan]{$type}[/color] diff --git a/Resources/Locale/en-US/construction/ui/construction-menu.ftl b/Resources/Locale/en-US/construction/ui/construction-menu.ftl index f4b7f3559a..82ebc01bc9 100644 --- a/Resources/Locale/en-US/construction/ui/construction-menu.ftl +++ b/Resources/Locale/en-US/construction/ui/construction-menu.ftl @@ -4,5 +4,4 @@ construction-menu-title = Construction construction-menu-place-ghost = Place construction ghost construction-menu-clear-all = Clear All construction-menu-eraser-mode = Eraser Mode -construction-menu-title = Construction -construction-menu-craft = Craft \ No newline at end of file +construction-menu-craft = Craft diff --git a/Resources/Locale/en-US/containers/containers.ftl b/Resources/Locale/en-US/containers/containers.ftl index ab011f64f8..d96383305a 100644 --- a/Resources/Locale/en-US/containers/containers.ftl +++ b/Resources/Locale/en-US/containers/containers.ftl @@ -1,2 +1,5 @@ container-verb-text-enter = Enter container-verb-text-empty = Empty + +## missed +container-thrown-missed = Missed! \ No newline at end of file diff --git a/Resources/Locale/en-US/corvax/station-goal/station-goal-command.ftl b/Resources/Locale/en-US/corvax/station-goal/station-goal-command.ftl deleted file mode 100644 index 82ed4d5c8a..0000000000 --- a/Resources/Locale/en-US/corvax/station-goal/station-goal-command.ftl +++ /dev/null @@ -1,6 +0,0 @@ -send-station-goal-command-description = Sends the selected station target to all faxes that can receive it -send-station-goal-command-help-text = Usage: { $command } -send-station-goal-command-arg-id = Goal Prototype ID - -send-station-goal-command-error-no-goal-proto = No station goal found with ID {$id} -send-station-goal-command-error-couldnt-fax = Couldn't send station goal, probably due to a lack of fax machines that are able to recieve it diff --git a/Resources/Locale/en-US/corvax/station-goal/station-goals.ftl b/Resources/Locale/en-US/corvax/station-goal/station-goals.ftl deleted file mode 100644 index 6a2c95e07a..0000000000 --- a/Resources/Locale/en-US/corvax/station-goal/station-goals.ftl +++ /dev/null @@ -1,224 +0,0 @@ -station-goal-fax-paper-name = Station Goal - -station-goal-fax-paper-header = - ███╗░░██╗████████╗ - ████╗░██║╚══██╔══╝ Form NT-No.{$station}-CC - ██╔██╗██║░░░██║░░░ Target Order - ██║╚████║░░░██║░░░ Date: {$date} - ██║░╚███║░░░██║░░░ - ╚═╝░░╚══╝░░░╚═╝░░░ - ════════════════════════════════════════ - {$content} - ════════════════════════════════════════ - - -station-goal-xeno= - Dear station Command, the purpose of your shift is to build a xenobiology lab and then study exotic life forms. - Two containment chambers must be constructed according to the following requirements: - 1. Must be reinforced well; - 2. At least one of the chambers must be equipped with a gas supply system; - 3. The entrance should be a cycling airlock system to prevent contamination. - - Get salvage crew to capture at least 2 representatives of life forms (e.g. space carp) and transport them to the above-described chambers. - - Capture requirements: - 1. Exotic fauna should not have critical injuries at the time of placement in the research department; - 2. When captured fauna dies, you are required to catch another, cloning is strictly prohibited. - - Once you collect the required fauna, you must study them and write a report on their properties. - The report must be stamped by the head of the department and faxed to Central Command. - - Experience Requirements: - 1. Experience should be documented in detail; - 2. Test activities may include: working with gases, smoke, foam, or injecting experimental reagents (e.g. Cognizine) into captured fauna. - -station-goal-museum= - Dear command of the station, the purpose of your shift is to build a museum, the exhibits for which will be unique objects collected from the station. - - Below are the requirements for the design of the museum: - 1. The museum must be structurally connected to the station by a space-protected corridor, or be located within it; - 2. The premises must be of a size that allows them to easily receive a large number of visitors; - 3. The premises must be provided with a standard atmosphere, ventilation and stable power supply; - 4. Room decoration should be visually pleasing; - 5. Exhibits must be reinforced accordingly to what is contained in them. - - Exhibit requirements: - 1. Exhibits must be unique in their kind; - 2. Each department must provide at least 2 exhibits for the museum fund; - 3. The total number of exhibits must be at least 20. - - Exhibits may include: - 1. Exotic drinks and dishes that require an extraordinary method of production and/or non-standard ingredients; - 2. Exotic matter/substance; - 3. Works of art (e.g. statues, paintings); - 4. Fully studied and documented artifacts (optionally provide a copy of the document); - 5. High-tech devices or tools; - 6. High-tech or high-power weapons; - 7. Robotic entities (e.g. Mechs, Cyborgs, Drones); - 8. Mutated biological organisms; - 9. Domesticated wild animals or intelligent non-humanoid life forms; - 10. Found treasures or items not available on the market. - - Upon completion of the museum, it is required to provide the crew with at least 20 minutes of free time from work so that they can visit the museum. - -station-goal-area= - Dear station Command, the goal of your shift is to increase the effective use of space at the station. - - It is required to bring the abandoned premises into proper form and find a use for them. - Each department must equip and effectively use the area of adjacent maintenance tunnels. - Sufficiently spacious maintenance tunnels need to be converted into residential areas. - The remaining tunnels should be provided with floor coverings and adequate lighting. - In addition, it is necessary to provide a public, well-lit corridor connecting all the restored compartments and new bedrooms. - -station-goal-bureaucratic-error = - ACCESS TO THIS DOCUMENT IS PROHIBITED FOR PERSONS WHO DO NOT HAVE LEGAL IMMUNITY - - Dear station Command, we inform you that the purpose of your shift was lost as a result of a bureaucratic error. - With this news, Central Command gives you the opportunity to independently assign a new goal for the station. - - New goal requirements: - 1. Relevance: The goal must be relevant and relevant to the current situation; - 2. Engagement: The goal should require the cooperation of as many departments as possible in the plans; - 3. Scope: The goal should involve sufficient, but not excessive, amounts of work to ensure the effective completion of the task. - - Please note that distribution of the contents of this document to persons who do not have legal immunity is strictly prohibited due to the possibility of discrediting the management of the Corporation. - Therefore, in order to present a new goal to the crew, the command staff must contact Central Command for approval of your ideas. - -station-goal-anomalies= - Dear station Command, the purpose of your shift is to provide new information about anomalies to NanoTrasen. - - It is necessary to conduct experimental studies aimed at testing the consequences of the collapse of at least 4 unique anomalies. - During or after the experiments, it is necessary to isolate and document the aforementioned anomalies. - - Document requirements: - 1. The official name of the anomaly; - 2. Physical description; - 3. Passive properties; - 4. Reaction of the anomaly to different particles; - 5. Consequences of the collapse; - 6. Location of the anomaly. - - The document must be certified by the stamp of the supervisor and faxed to Central Command. - -station-goal-combat= - Dear station Command, due to the increase in attacks of pirate ships in this sector, the purpose of your shift is to raise the overall combat readiness of the station. - - Required: - 1. Organize an inspection of every sentient being and cargo arriving at or leaving the station. - 2. Build or modify an existing security checkpoint at arrivals and departures. The checkpoint must be able to completely block the ports from the main part of the station. - Each of the above checkpoints must have at least one cell for the temporary detention of detainees. - 3. Organize a spare weapons storage in the opposite part of the station from the brig. - The vault arsenal should have enough weapons and equipment to fully equip all security personnel. - 4. Organize the recruitment of a new combat subdepartment of security. - Squad members must be recruited from the station's crew. - Recruitment should be carried out on a voluntary-compulsory basis. - Composition of the squad: - 1 field medic; - 1 field engineer; - 3 combat operatives. - All members of the squad must be trained in all the necessary skills to conduct combat and fulfill their role. - 5. Open a public shooting range. - The shooting range should present all available types of weapons or their training counterparts. - Avoid providing lethal weaponry to unauthorized personnel. - 6. Encourage the use of the station boxing ring. - If there is no boxing ring, you must create one. - -station-goal-shuttle= - Dear station Command, the purpose of your shift is to build a space shuttle capable of being piloted. - - Shuttle requirements: - 1. The shuttle must have a locked bridge; - a medical room with the necessary medical supplies and chemical equipment; - a supply store surrounded by reinforced material; - a crew room with at least 12 seats. - 2. There must be an intermediate room between the docking airlock and the main rooms to prevent possible depressurization. - 3. The shuttle must have a standard atmosphere, and also have several air gas containers to maintain it. - 4. The shuttle must be able to move in all directions (forward, backward, sideways) and turn reasonably well. - - Upon completion, the shuttle crew must be recruited from the station personnel. - The shuttle crew must include: - 1 pilot; - 2 engineers; - 1 medic/chemist; - 1 security officer. - - The shuttle should take on board all the station Command representatives as passengers and, in parallel with the evacuation shuttle, go to the Central Command station. - -station-goal-singularity= - Dear station Command, the goal of your shift is to build a generator based on the gravitational singularity. - - The design requirements are: - 1. The structure must be located at a significant distance from the station. - 2. The structure must be protected from meteorites and space debris. - 3. The containment field must be able to prevent the loss of a class 3 singularity. - -station-goal-solar-panels= - Dear station Command, the purpose of your shift is to organize a backup power system. - - The following work is required: - 1. Build two new branches of solar panels. - 2. Allocate an area for a compartment with spare batteries. - This compartment should accommodate at least 3 fully charged SMES', which should not be connected to the main power system of the station unless needed. - -station-goal-artifacts= - Dear station Command, the purpose of your shift is to provide new information about alien artifacts to NanoTrasen. - - It is required to organize the work of salvagers to search for and deliver artifacts from the wreckage around the station or expeditions. - After the delivery of the artifacts, they must be transferred to a special container to the research department. - It is necessary to deliver at least 2 fully studied and documented artifacts on the evacuation shuttle in special containment units. - - Recommended information for the document: - 1. Name of the artifact. - 2. Physical description. - 3. Properties of the object. - 4. Location of where the artifact was found. - 5. Additional notes. - - The document must be certified by the stamp of the supervisor. - -station-goal-storage= - Dear station Command, the purpose of your shift is to build an orbital storage facility with supplies and technology. - - The storage should be placed in space separately from the main station, make sure its design is strong, a random meteorite should not damage it. - - 4 boxes must be placed in the storage containing the following respectively: - - Advanced medicines; - - Stocks of the best seeds; - - Refrigerator box of food with a high nutritional value; - - Valuable, but not unique boards. - - Monitor the safety of the contents in the storage until the end of the shift, a cleanup crew will come retrieve the contents as they prepare the station. - -station-goal-zoo= - Dear station Command, the purpose of your shift is to improve the recreational value of the personnel at the station. - - It is necessary to build a zoo with at least 5 enclosures containing different types of animals ordered from the supply department. - Provide animals with food, at least one cleaning robot in each enclosure, and everything necessary for life, depending on the type of animal. - It is also necessary to build a bathhouse for the animals, water vapor must be supplied by Atmospheric Technicians. - - Upon completion of the zoo, it is required to provide the crew with at least 20 minutes of free time from work so that they can visit the new zoo. - -station-goal-labor= - Dear station Command, the purpose of your shift is to increase the motivation of the personnel for the growth of labor productivity. - - This requires that each of the heads during the shift closely monitors the performance of the duties of their employees and evaluates them. - After the time set by Command for evaluation, in each of the departments, the best, in the opinion of the head, employee should be selected, who will be invited to a dinner, where the Command staff will be obliged to award them a medal and a prize. - The heads must provide a report indicating the employee's position and merits for the shift. - Drinks and meals should be prepared for the dinner, as well as, if possible, several entertainment events that allow the presence of actors and musicians. - For the duration of the celebration, the dining room or other place chosen for the event must be inaccessible to the rest of the crew. - - The duration of the shift for a more accurate assessment of the work of the personnel should be set by the Command staff. - After the dinner someone must announce the end of the shift and call the evacuation shuttle. - -station-goal-lectures= - Dear station Command, the purpose of your shift is to carry out a number of events within the framework of the Corporation's plan to increase the knowledge of its employees. - - The Command staff are instructed to organize a platform for public lectures, if none exists, create one nearby the bridge entry. - The venue should be equipped with a large enough stage for speakers in the middle, a podium for the presenter to one side of it, plenty of seating for guests, and a special counter/table for brochures at the entrance. - A host/organizer of the event must also be selected. - Each department is required to present a group of employees consisting of at least 2 people. - Selected employees, under the supervision of the head of the department, should prepare a short lecture/presentation on a specific topic within their specialization (e.g. the harm of drugs and their reason for their criminalization, the effect of smoking on the body, acting, product pricing, cooking etc.), preferably with demonstration materials, and at least 10 brochures, on which the abstracts of the lecture should be indicated. - At the time indicated by the Command staff, the crew must be assembled on the site for the event, where lectures will be read. - There may be breaks between lectures to allow guests to read brochures and catch their breath. - - After the end of the event someone must announce the end of the shift and call the evacuation shuttle. diff --git a/Resources/Locale/en-US/cuffs/components/handcuff-component.ftl b/Resources/Locale/en-US/cuffs/components/handcuff-component.ftl index 16447f4251..1f8a895164 100644 --- a/Resources/Locale/en-US/cuffs/components/handcuff-component.ftl +++ b/Resources/Locale/en-US/cuffs/components/handcuff-component.ftl @@ -3,6 +3,7 @@ handcuff-component-cuffs-broken-error = The restraints are broken! handcuff-component-target-has-no-hands-error = {$targetName} has no hands! handcuff-component-target-has-no-free-hands-error = {$targetName} has no free hands! handcuff-component-too-far-away-error = You are too far away to use the restraints! +handcuff-component-target-flying-error = You cannot reach {$targetName}'s hands! handcuff-component-start-cuffing-observer = {$user} starts restraining {$target}! handcuff-component-start-cuffing-target-message = You start restraining {$targetName}. handcuff-component-start-cuffing-by-other-message = {$otherName} starts restraining you! diff --git a/Resources/Locale/en-US/customization/character-requirements.ftl b/Resources/Locale/en-US/customization/character-requirements.ftl index a3f00dea87..d50c2b3966 100644 --- a/Resources/Locale/en-US/customization/character-requirements.ftl +++ b/Resources/Locale/en-US/customization/character-requirements.ftl @@ -1,50 +1,142 @@ -# Job -character-job-requirement = You must {$inverted -> - [true] not be - *[other] be -} one of these jobs: {$jobs} -character-department-requirement = You must {$inverted -> - [true] not be - *[other] be -} in one of these departments: {$departments} +## Job +character-job-requirement = You must{$inverted -> + [true]{" "}not + *[other]{""} +} be one of these jobs: {$jobs} + +character-department-requirement = You must{$inverted -> + [true]{" "}not + *[other]{""} +} be in one of these departments: {$departments} character-timer-department-insufficient = You require [color=yellow]{TOSTRING($time, "0")}[/color] more minutes of [color={$departmentColor}]{$department}[/color] department playtime character-timer-department-too-high = You require [color=yellow]{TOSTRING($time, "0")}[/color] fewer minutes in [color={$departmentColor}]{$department}[/color] department + character-timer-overall-insufficient = You require [color=yellow]{TOSTRING($time, "0")}[/color] more minutes of playtime character-timer-overall-too-high = You require [color=yellow]{TOSTRING($time, "0")}[/color] fewer minutes of playtime + character-timer-role-insufficient = You require [color=yellow]{TOSTRING($time, "0")}[/color] more minutes with [color={$departmentColor}]{$job}[/color] character-timer-role-too-high = You require[color=yellow] {TOSTRING($time, "0")}[/color] fewer minutes with [color={$departmentColor}]{$job}[/color] -# Profile -character-age-requirement = You must {$inverted -> - [true] not be - *[other] be +## Logic +character-logic-and-requirement-listprefix = {""} + {$indent}[color=gray]&[/color]{" "} +character-logic-and-requirement = You must{$inverted -> + [true]{" "}not + *[other]{""} +} fit [color=red]all[/color] of [color=gray]these[/color]: {$options} + +character-logic-or-requirement-listprefix = {""} + {$indent}[color=white]O[/color]{" "} +character-logic-or-requirement = You must{$inverted -> + [true]{" "}not + *[other]{""} +} fit [color=red]at least one[/color] of [color=white]these[/color]: {$options} + +character-logic-xor-requirement-listprefix = {""} + {$indent}[color=white]X[/color]{" "} +character-logic-xor-requirement = You must{$inverted -> + [true]{" "}not + *[other]{""} +} fit [color=red]only one[/color] of [color=white]these[/color]: {$options} + + +## Profile +character-age-requirement = You must{$inverted -> + [true]{" "}not + *[other]{""} } be within [color=yellow]{$min}[/color] and [color=yellow]{$max}[/color] years old -character-species-requirement = You must {$inverted -> - [true] not be - *[other] be -} a {$species} -character-trait-requirement = You must {$inverted -> - [true] not have - *[other] have -} one of these traits: {$traits} -character-loadout-requirement = You must {$inverted -> - [true] not have - *[other] have -} one of these loadouts: {$loadouts} + character-backpack-type-requirement = You must {$inverted -> [true] not use *[other] use } a [color=brown]{$type}[/color] as your bag + character-clothing-preference-requirement = You must {$inverted -> [true] not wear *[other] wear } a [color=white]{$type}[/color] +character-gender-requirement = You must {$inverted -> + [true] not have + *[other] have +} the pronouns [color=white]{$gender}[/color] + +character-sex-requirement = You must{$inverted -> + [true]{" "}not + *[other]{""} +} be [color=white]{$sex -> + [None] unsexed + *[other] {$sex} +}[/color] +character-species-requirement = You must{$inverted -> + [true]{" "}not + *[other]{""} +} be a {$species} + +character-height-requirement = You must{$inverted -> + [true]{" "}not + *[other]{""} +} be {$min -> + [-2147483648]{$max -> + [2147483648]{""} + *[other] shorter than [color={$color}]{$max}[/color]cm + } + *[other]{$max -> + [2147483648] taller than [color={$color}]{$min}[/color]cm + *[other] between [color={$color}]{$min}[/color] and [color={$color}]{$max}[/color]cm tall + } +} + +character-width-requirement = You must{$inverted -> + [true]{" "}not + *[other]{""} +} be {$min -> + [-2147483648]{$max -> + [2147483648]{""} + *[other] skinnier than [color={$color}]{$max}[/color]cm + } + *[other]{$max -> + [2147483648] wider than [color={$color}]{$min}[/color]cm + *[other] between [color={$color}]{$min}[/color] and [color={$color}]{$max}[/color]cm wide + } +} + +character-weight-requirement = You must{$inverted -> + [true]{" "}not + *[other]{""} +} be {$min -> + [-2147483648]{$max -> + [2147483648]{""} + *[other] lighter than [color={$color}]{$max}[/color]kg + } + *[other]{$max -> + [2147483648] heavier than [color={$color}]{$min}[/color]kg + *[other] between [color={$color}]{$min}[/color] and [color={$color}]{$max}[/color]kg + } +} + + +character-trait-requirement = You must {$inverted -> + [true] not have + *[other] have +} one of these traits: {$traits} + +character-loadout-requirement = You must {$inverted -> + [true] not have + *[other] have +} one of these loadouts: {$loadouts} + + +character-item-group-requirement = You must {$inverted -> + [true] have {$max} or more + *[other] have {$max} or less +} items from the group [color=white]{$group}[/color] + -# Whitelist -character-whitelist-requirement = You must {$inverted -> - [true] not be - *[other] be -} whitelisted +## Whitelist +character-whitelist-requirement = You must{$inverted -> + [true]{" "}not + *[other]{""} +} be whitelisted diff --git a/Resources/Locale/en-US/damage/damage-groups.ftl b/Resources/Locale/en-US/damage/damage-groups.ftl new file mode 100644 index 0000000000..cfaf866e34 --- /dev/null +++ b/Resources/Locale/en-US/damage/damage-groups.ftl @@ -0,0 +1,6 @@ +damage-group-brute = Brute +damage-group-burn = Burn +damage-group-airloss = Airloss +damage-group-toxin = Toxin +damage-group-genetic = Genetic +damage-group-immaterial = Immaterial diff --git a/Resources/Locale/en-US/damage/damage-types.ftl b/Resources/Locale/en-US/damage/damage-types.ftl new file mode 100644 index 0000000000..3b6256864f --- /dev/null +++ b/Resources/Locale/en-US/damage/damage-types.ftl @@ -0,0 +1,14 @@ +damage-type-asphyxiation = Asphyxiation +damage-type-bloodloss = Bloodloss +damage-type-blunt = Blunt +damage-type-cellular = Cellular +damage-type-caustic = Caustic +damage-type-cold = Cold +damage-type-heat = Heat +damage-type-piercing = Piercing +damage-type-poison = Poison +damage-type-radiation = Radiation +damage-type-shock = Shock +damage-type-slash = Slash +damage-type-structural = Structural +damage-type-holy = Holy diff --git a/Resources/Locale/en-US/deltav/cartridge-loader/cartridges.ftl b/Resources/Locale/en-US/deltav/cartridge-loader/cartridges.ftl index 9b4c59d001..ede1a36b8e 100644 --- a/Resources/Locale/en-US/deltav/cartridge-loader/cartridges.ftl +++ b/Resources/Locale/en-US/deltav/cartridge-loader/cartridges.ftl @@ -118,3 +118,16 @@ crime-assist-sophont-explanation = A sophont is described as any entity with the • [bold]Sentience[/bold]: the entity has the capacity to process an emotion or lack thereof, or at a minimum the ability to recognise its own pain. • [bold]Self-awareness[/bold]: the entity is capable of altering its behaviour in a reasonable fashion as a result of stimuli, or at a minimum is capable of recognising its own sapience and sentience. Any sophont is considered a legal person, regardless of origin or prior cognitive status. Much like any other intelligent organic, a sophont may press charges against crew and be tried for crimes. + +mail-metrics-program-name = MailMetrics +mail-metrics-header = Income from Mail Deliveries +mail-metrics-opened = Earnings (Opened) +mail-metrics-expired = Losses (Expired) +mail-metrics-damaged = Losses (Damaged) +mail-metrics-tampered = Losses (Tampered) +mail-metrics-unopened = Unopened +mail-metrics-count-header = Packages +mail-metrics-money-header = Spesos +mail-metrics-total = Total +mail-metrics-progress = {$opened} out of {$total} packages opened! +mail-metrics-progress-percent = Success rate: {$successRate}% diff --git a/Resources/Locale/en-US/deltav/changelog/changelog-window.ftl b/Resources/Locale/en-US/deltav/changelog/changelog-window.ftl deleted file mode 100644 index ba095980c6..0000000000 --- a/Resources/Locale/en-US/deltav/changelog/changelog-window.ftl +++ /dev/null @@ -1 +0,0 @@ -changelog-tab-title-DeltaVChangelog = DeltaV diff --git a/Resources/Locale/en-US/deltav/connection-messages.ftl b/Resources/Locale/en-US/deltav/connection-messages.ftl deleted file mode 100644 index 5d2ea9b151..0000000000 --- a/Resources/Locale/en-US/deltav/connection-messages.ftl +++ /dev/null @@ -1 +0,0 @@ -whitelist-not-whitelisted-peri = You are not whitelisted. To become whitelisted, apply on our forum. It can be found at https://forum.delta-v.org/ diff --git a/Resources/Locale/en-US/deltav/escape-menu/options-menu.ftl b/Resources/Locale/en-US/deltav/escape-menu/options-menu.ftl deleted file mode 100644 index 50d55cb76d..0000000000 --- a/Resources/Locale/en-US/deltav/escape-menu/options-menu.ftl +++ /dev/null @@ -1,4 +0,0 @@ -ui-options-tab-deltav = DeltaV -ui-options-general-forknotice = Note: These settings are fork-specific and might not apply on other servers. - -ui-options-no-filters = Disable species vision filters diff --git a/Resources/Locale/en-US/deltav/flavors/flavor-profiles.ftl b/Resources/Locale/en-US/deltav/flavors/flavor-profiles.ftl index 98e04f2818..2cae678bc7 100644 --- a/Resources/Locale/en-US/deltav/flavors/flavor-profiles.ftl +++ b/Resources/Locale/en-US/deltav/flavors/flavor-profiles.ftl @@ -5,7 +5,6 @@ flavor-complex-enthralling = enthralling flavor-complex-sublime = sublime flavor-complex-holy = heavenly flavor-base-seeds = seeds -flavor-complex-cotton = like cotton flavor-complex-vanilla = like vanilla flavor-complex-soju = like bold, alcoholic rice flavor-complex-orangecreamcicle = like creamy, alcoholic orange juice @@ -26,4 +25,3 @@ flavor-complex-greengrass = like a holiday in the sun flavor-complex-daiquiri = fashionable flavor-complex-arsonistsbrew = like ash and flame flavor-complex-healthcodeviolation = ominous -flavor-complex-pumpkin = like pumpkin diff --git a/Resources/Locale/en-US/deltav/prototypes/catalog/cargo/cargo-fun.ftl b/Resources/Locale/en-US/deltav/prototypes/catalog/cargo/cargo-fun.ftl deleted file mode 100644 index 32d3ab61c3..0000000000 --- a/Resources/Locale/en-US/deltav/prototypes/catalog/cargo/cargo-fun.ftl +++ /dev/null @@ -1,2 +0,0 @@ -ent-CrateFunBBGun = { ent-CrateFunBBGun } - .desc = { ent-CrateFunBBGun.desc } diff --git a/Resources/Locale/en-US/devices/device-network.ftl b/Resources/Locale/en-US/devices/device-network.ftl index d88b1f719d..56a3930cd1 100644 --- a/Resources/Locale/en-US/devices/device-network.ftl +++ b/Resources/Locale/en-US/devices/device-network.ftl @@ -7,6 +7,8 @@ device-frequency-prototype-name-mailing-units = Mailing Units device-frequency-prototype-name-pdas = PDAs device-frequency-prototype-name-fax = Fax device-frequency-prototype-name-basic-device = Basic Devices +device-frequency-prototype-name-cyborg-control = Cyborg Control +device-frequency-prototype-name-robotics-console = Robotics Console ## camera frequencies device-frequency-prototype-name-surveillance-camera-test = Subnet Test @@ -31,7 +33,7 @@ device-address-prefix-freezer = FZR- device-address-prefix-volume-pump = VPP- device-address-prefix-smes = SMS- -#PDAs and terminals +# PDAs and terminals device-address-prefix-console = CLS- device-address-prefix-fire-alarm = FIR- device-address-prefix-air-alarm = AIR- @@ -40,7 +42,7 @@ device-address-prefix-sensor-monitor = MON- device-address-examine-message = The device's address is {$address}. -#Device net ID names +# Device net ID names device-net-id-private = Private device-net-id-wired = Wired device-net-id-wireless = Wireless diff --git a/Resources/Locale/en-US/devices/network-configurator.ftl b/Resources/Locale/en-US/devices/network-configurator.ftl index e1bcbc4c94..cd4955ed36 100644 --- a/Resources/Locale/en-US/devices/network-configurator.ftl +++ b/Resources/Locale/en-US/devices/network-configurator.ftl @@ -41,5 +41,5 @@ network-configurator-examine-current-mode = Current mode: {$mode} network-configurator-examine-switch-modes = Press {$key} to switch modes # item status -network-configurator-item-status-label = Current mode: {$mode} -{$keybinding} to switch mode +network-configurator-item-status-label = Mode: {$mode} + Switch: {$keybinding} diff --git a/Resources/Locale/en-US/disposal/tube/components/disposal-router-component.ftl b/Resources/Locale/en-US/disposal/tube/components/disposal-router-component.ftl index 64fbfdf66f..4fe24b7853 100644 --- a/Resources/Locale/en-US/disposal/tube/components/disposal-router-component.ftl +++ b/Resources/Locale/en-US/disposal/tube/components/disposal-router-component.ftl @@ -4,7 +4,3 @@ disposal-router-window-title = Disposal Router disposal-router-window-tags-label = Tags: disposal-router-window-tag-input-tooltip = A comma separated list of tags disposal-router-window-tag-input-confirm-button = Confirm - -## ConfigureVerb - -configure-verb-get-data-text = Open Configuration diff --git a/Resources/Locale/en-US/disposal/tube/components/disposal-tagger-window.ftl b/Resources/Locale/en-US/disposal/tube/components/disposal-tagger-window.ftl index dc4b40fc7f..55523c4b95 100644 --- a/Resources/Locale/en-US/disposal/tube/components/disposal-tagger-window.ftl +++ b/Resources/Locale/en-US/disposal/tube/components/disposal-tagger-window.ftl @@ -1,6 +1,3 @@ disposal-tagger-window-title = Disposal Tagger disposal-tagger-window-tag-input-label = Tag: disposal-tagger-window-tag-confirm-button = Confirm - -## ConfigureVerb -configure-verb-get-data-text = Open Configuration diff --git a/Resources/Locale/en-US/disposal/unit/components/disposal-unit-component.ftl b/Resources/Locale/en-US/disposal/unit/components/disposal-unit-component.ftl index 288db53668..2b23ee9a4a 100644 --- a/Resources/Locale/en-US/disposal/unit/components/disposal-unit-component.ftl +++ b/Resources/Locale/en-US/disposal/unit/components/disposal-unit-component.ftl @@ -18,9 +18,6 @@ disposal-eject-verb-get-data-text = Eject contents ## No hands disposal-unit-no-hands = You don't have hands! -## missed -disposal-unit-thrown-missed = Missed! - # state disposal-unit-state-Ready = Ready # Yes I want it to always say Pressurizing diff --git a/Resources/Locale/en-US/emotes/emotes.ftl b/Resources/Locale/en-US/emotes/emotes.ftl index 53c12312e5..06c8b02d4a 100644 --- a/Resources/Locale/en-US/emotes/emotes.ftl +++ b/Resources/Locale/en-US/emotes/emotes.ftl @@ -1 +1,7 @@ -emote-deathgasp = seizes up and falls limp, {POSS-ADJ($entity)} eyes dead and lifeless... +emote-deathgasp = seizes up and falls limp, {POSS-ADJ($entity)} eyes dead and lifeless... +silicon-emote-deathgasp = seizes up and falls limp, {POSS-ADJ($entity)} lights sputtering into darkness... + +# for IPC +chat-emote-msg-deathgasp-silicon = + With a hiss of grinding servos and a screech of dying myomers, + {CAPITALIZE($entity)} suddenly goes silent. diff --git a/Resources/Locale/en-US/entity-categories.ftl b/Resources/Locale/en-US/entity-categories.ftl new file mode 100644 index 0000000000..190fe5713a --- /dev/null +++ b/Resources/Locale/en-US/entity-categories.ftl @@ -0,0 +1 @@ +entity-category-name-actions = Actions diff --git a/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl b/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl index 7b25b616b2..68a7db1e3e 100644 --- a/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl +++ b/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl @@ -34,6 +34,8 @@ ui-options-announcer-volume = Announcer volume: ui-options-lobby-music = Lobby & Round-end Music ui-options-restart-sounds = Round Restart Sounds ui-options-event-music = Event Music +ui-options-announcer-disable-multiple-sounds = Disable Overlapping Announcer Sounds +ui-options-announcer-disable-multiple-sounds-tooltip = Some announcements will not sound right, this setting isn't recommended ui-options-admin-sounds = Play Admin Sounds ui-options-volume-label = Volume ui-options-volume-percent = { TOSTRING($volume, "P0") } @@ -50,6 +52,7 @@ ui-options-fancy-speech = Show names in speech bubbles ui-options-fancy-name-background = Add background to speech bubble names ui-options-enable-color-name = Add colors to character names ui-options-colorblind-friendly = Colorblind friendly mode +ui-options-no-filters = Disable species vision filters ui-options-reduced-motion = Reduce motion of visual effects ui-options-chat-window-opacity = Chat window opacity ui-options-chat-window-opacity-percent = { TOSTRING($opacity, "P0") } @@ -85,6 +88,10 @@ ui-options-vp-integer-scaling-tooltip = If this option is enabled, the viewport at specific resolutions. While this results in crisp textures, it also often means that black bars appear at the top/bottom of the screen or that part of the viewport is not visible. +ui-options-vp-vertical-fit = Vertical viewport fitting +ui-options-vp-vertical-fit-tooltip = When enabled, the main viewport will ignore the horizontal axis entirely when + fitting to your screen. If your screen is smaller than the viewport, then this + will cause the viewport to be cut off on the horizontal axis. ui-options-vp-low-res = Low-resolution viewport ui-options-parallax-low-quality = Low-quality Parallax (background) ui-options-fps-counter = Show FPS counter @@ -104,6 +111,7 @@ ui-options-header-camera = Camera ui-options-header-interaction-basic = Basic Interaction ui-options-header-interaction-adv = Advanced Interaction ui-options-header-ui = User Interface +ui-options-header-targeting = Targeting ui-options-header-misc = Miscellaneous ui-options-header-hotbar = Hotbar ui-options-header-shuttle = Shuttle @@ -113,6 +121,7 @@ ui-options-header-general = General ui-options-hotkey-keymap = Use US QWERTY Keys ui-options-hotkey-toggle-walk = Toggle Speed +ui-options-hotkey-default-walk = Walk by default ui-options-function-move-up = Move Up ui-options-function-move-left = Move Left @@ -143,6 +152,7 @@ ui-options-function-rotate-stored-item = Rotate stored item ui-options-function-offer-item = Offer something ui-options-function-save-item-location = Save item location ui-options-function-toggle-standing = Toggle standing +ui-options-function-toggle-crawling-under = Toggle crawling under furniture ui-options-static-storage-ui = Lock storage window to hotbar ui-options-function-smart-equip-backpack = Smart-equip to backpack @@ -155,6 +165,13 @@ ui-options-function-move-pulled-object = Move pulled object ui-options-function-release-pulled-object = Release pulled object ui-options-function-point = Point at location +ui-options-function-target-head = Target head +ui-options-function-target-torso = Target torso +ui-options-function-target-left-arm = Target left arm +ui-options-function-target-right-arm = Target right arm +ui-options-function-target-left-leg = Target left leg +ui-options-function-target-right-leg = Target right leg + ui-options-function-focus-chat-input-window = Focus chat ui-options-function-focus-local-chat-window = Focus chat (IC) ui-options-function-focus-emote = Focus chat (Emote) @@ -173,6 +190,7 @@ ui-options-function-open-crafting-menu = Open crafting menu ui-options-function-open-inventory-menu = Open inventory ui-options-function-open-a-help = Open admin help ui-options-function-open-abilities-menu = Open action menu +ui-options-function-toggle-round-end-summary-window = Toggle round end summary window ui-options-function-open-entity-spawn-window = Open entity spawn menu ui-options-function-open-sandbox-window = Open sandbox menu ui-options-function-open-tile-spawn-window = Open tile spawn menu @@ -196,7 +214,6 @@ ui-options-function-editor-rotate-object = Rotate ui-options-function-editor-flip-object = Flip ui-options-function-editor-copy-object = Copy -ui-options-function-open-abilities-menu = Open action menu ui-options-function-show-debug-console = Open Console ui-options-function-show-debug-monitors = Show Debug Monitors ui-options-function-inspect-entity = Inspect Entity @@ -266,3 +283,8 @@ ui-options-net-pvs-leave-tooltip = This limits the rate at which the client will ## Toggle window console command cmd-options-desc = Opens options menu, optionally with a specific tab selected. cmd-options-help = Usage: options [tab] + +## Combat Options +ui-options-function-look-up = Look up/Take aim +ui-options-function-auto-get-up = Automatically get up after falling +ui-options-function-hold-look-up = Hold down the key to aim diff --git a/Resources/Locale/en-US/fax/fax.ftl b/Resources/Locale/en-US/fax/fax.ftl index 1f1881a05d..412f3d7f43 100644 --- a/Resources/Locale/en-US/fax/fax.ftl +++ b/Resources/Locale/en-US/fax/fax.ftl @@ -3,6 +3,8 @@ fax-machine-popup-received = Received correspondence from { $from }. fax-machine-popup-name-long = Fax name is too long fax-machine-popup-name-exist = Fax with same name already exist in network fax-machine-popup-name-set = Fax name has been updated +fax-machine-popup-error = ERROR - jam in paper feed +fax-machine-popup-copy-error = ERROR - unable to copy! fax-machine-dialog-rename = Rename fax-machine-dialog-field-name = Name diff --git a/Resources/Locale/en-US/flavors/flavor-profiles.ftl b/Resources/Locale/en-US/flavors/flavor-profiles.ftl index 61567d8695..39b185bd02 100644 --- a/Resources/Locale/en-US/flavors/flavor-profiles.ftl +++ b/Resources/Locale/en-US/flavors/flavor-profiles.ftl @@ -168,6 +168,9 @@ flavor-complex-light = like a light gone out flavor-complex-profits = like profits flavor-complex-fishops = like the dreaded fishops flavor-complex-violets = like violets +flavor-complex-pyrotton = like a burning mouth +flavor-complex-mothballs = like mothballs +flavor-complex-paint-thinner = like paint thinner # Drink-specific flavors. @@ -240,6 +243,12 @@ flavor-complex-atomic-cola = like hoarding bottle caps flavor-complex-cuba-libre = like spiked cola flavor-complex-gin-tonic = like spiked lemon-lime soda flavor-complex-screwdriver = like spiked orange juice +flavor-complex-vodka-red-bool = like a heart attack +flavor-complex-irish-bool = caffine and Ireland +flavor-complex-xeno-basher = like killing bugs +flavor-complex-budget-insuls-drink = like door hacking +flavor-complex-watermelon-wakeup = like a sweet wakeup call +flavor-complex-rubberneck = like synthetics flavor-complex-irish-car-bomb = like a spiked cola float flavor-complex-themartinez = like violets and lemon vodka flavor-complex-cogchamp = like brass diff --git a/Resources/Locale/en-US/flight/flight_system.ftl b/Resources/Locale/en-US/flight/flight_system.ftl new file mode 100644 index 0000000000..3558ab9dc8 --- /dev/null +++ b/Resources/Locale/en-US/flight/flight_system.ftl @@ -0,0 +1,3 @@ +no-flight-while-restrained = You can't fly right now. +no-flight-while-zombified = You can't use your wings right now. +no-flight-while-lying = You can't fly right now. \ No newline at end of file diff --git a/Resources/Locale/en-US/fluids/components/absorbent-component.ftl b/Resources/Locale/en-US/fluids/components/absorbent-component.ftl index 670ac0a36a..8a4d37023f 100644 --- a/Resources/Locale/en-US/fluids/components/absorbent-component.ftl +++ b/Resources/Locale/en-US/fluids/components/absorbent-component.ftl @@ -1,8 +1,8 @@ mopping-system-target-container-empty = { CAPITALIZE(THE($target)) } is empty! mopping-system-target-container-empty-water = { CAPITALIZE(THE($target)) } has no water! -mopping-system-puddle-space = { THE($used) } is full of water -mopping-system-puddle-evaporate = { THE($target) } is evaporating -mopping-system-no-water = { THE($used) } has no water! +mopping-system-puddle-space = { CAPITALIZE(THE($used)) } is full of water +mopping-system-puddle-evaporate = { CAPITALIZE(THE($target)) } is evaporating +mopping-system-no-water = { CAPITALIZE(THE($used)) } has no water! -mopping-system-full = { THE($used) } is full! -mopping-system-empty = { THE($used) } is empty! +mopping-system-full = { CAPITALIZE(THE($used)) } is full! +mopping-system-empty = { CAPITALIZE(THE($used)) } is empty! diff --git a/Resources/Locale/en-US/foldable/components/foldable-component.ftl b/Resources/Locale/en-US/foldable/components/foldable-component.ftl index 525820920b..9c1f934911 100644 --- a/Resources/Locale/en-US/foldable/components/foldable-component.ftl +++ b/Resources/Locale/en-US/foldable/components/foldable-component.ftl @@ -4,7 +4,11 @@ foldable-deploy-fail = You can't deploy the {$object} here. fold-verb = Fold unfold-verb = Unfold +# Hat fold-flip-verb = Flip - +# Coat fold-zip-verb = Zip up fold-unzip-verb = Unzip +# Jumpsuits +fold-rollsleeves-verb = roll sleeves up +fold-unrollsleeves-verb = roll sleeves down diff --git a/Resources/Locale/en-US/game-ticking/game-presets/preset-irregular.ftl b/Resources/Locale/en-US/game-ticking/game-presets/preset-irregular.ftl new file mode 100644 index 0000000000..35fc7957cf --- /dev/null +++ b/Resources/Locale/en-US/game-ticking/game-presets/preset-irregular.ftl @@ -0,0 +1,5 @@ +irregular-title = Irregular +irregular-description = Threat level varies throughout the shift. Sometimes it's a paradise, sometimes it's a disaster. + +irregular-extended-title = Irregular Extended +irregular-extended-description = A rather calm experience with occasional spikes of threats. diff --git a/Resources/Locale/en-US/game-ticking/game-presets/preset-pirates.ftl b/Resources/Locale/en-US/game-ticking/game-presets/preset-pirates.ftl deleted file mode 100644 index 941643dd9a..0000000000 --- a/Resources/Locale/en-US/game-ticking/game-presets/preset-pirates.ftl +++ /dev/null @@ -1,10 +0,0 @@ -pirates-title = Privateers -pirates-description = A group of privateers has approached your lowly station. Hostile or not, their sole goal is to end the round with as many knicknacks on their ship as they can get. - -pirates-no-ship = Through unknown circumstances, the privateer's ship was completely and utterly destroyed. No score. -pirates-final-score = The privateers successfully obtained {$score} spesos worth -pirates-final-score-2 = of knicknacks, with a total of {$finalPrice} spesos. -pirates-list-start = The privateers were: -pirates-most-valuable = The most valuable stolen items were: -pirates-stolen-item-entry = {$entity} ({$credits} spesos) -pirates-stole-nothing = - The pirates stole absolutely nothing at all. Point and laugh. diff --git a/Resources/Locale/en-US/game-ticking/game-presets/preset-revolutionary.ftl b/Resources/Locale/en-US/game-ticking/game-presets/preset-revolutionary.ftl index 5fb1d40b3d..15b53cf14b 100644 --- a/Resources/Locale/en-US/game-ticking/game-presets/preset-revolutionary.ftl +++ b/Resources/Locale/en-US/game-ticking/game-presets/preset-revolutionary.ftl @@ -5,31 +5,31 @@ roles-antag-rev-head-objective = Your objective is to take over the station by c head-rev-role-greeting = You are a Head Revolutionary. - You are tasked with removing all of Command from station via death or exilement. + You are tasked with removing all of Command from station via death, exilement or imprisonment. The Syndicate has sponsored you with a flash that converts the crew to your side. Beware, this won't work on Security, Command, or those wearing sunglasses. Viva la revolución! head-rev-briefing = Use flashes to convert people to your cause. - Kill all heads to take over the station. + Get rid of all heads to take over the station. head-rev-break-mindshield = The Mindshield was destroyed! ## Rev roles-antag-rev-name = Revolutionary -roles-antag-rev-objective = Your objective is to ensure the safety and follow the orders of the Head Revolutionaries as well as killing all Command staff on station. +roles-antag-rev-objective = Your objective is to ensure the safety and follow the orders of the Head Revolutionaries as well as getting rid of all Command staff on station. rev-break-control = {$name} has remembered their true allegiance! rev-role-greeting = You are a Revolutionary. You are tasked with taking over the station and protecting the Head Revolutionaries. - Eliminate all of the Command staff. + Get rid of all of the Command staff. Viva la revolución! -rev-briefing = Help your head revolutionaries kill every head to take over the station. +rev-briefing = Help your head revolutionaries get rid of every head to take over the station. ## General @@ -40,9 +40,7 @@ rev-not-enough-ready-players = Not enough players readied up for the game. There rev-no-one-ready = No players readied up! Can't start a Revolution. rev-no-heads = There were no Head Revolutionaries to be selected. Can't start a Revolution. -rev-all-heads-dead = All the heads are dead, now finish up the rest of the crew! - -rev-won = The Head Revs survived and killed all of Command. +rev-won = The Head Revs survived and successfully seized control of the station. rev-lost = Command survived and killed all of the Head Revs. diff --git a/Resources/Locale/en-US/game-ticking/game-presets/preset-traitor.ftl b/Resources/Locale/en-US/game-ticking/game-presets/preset-traitor.ftl index 724b752fbb..e92676a216 100644 --- a/Resources/Locale/en-US/game-ticking/game-presets/preset-traitor.ftl +++ b/Resources/Locale/en-US/game-ticking/game-presets/preset-traitor.ftl @@ -4,6 +4,7 @@ traitor-round-end-codewords = The codewords were: [color=White]{$codewords}[/col traitor-round-end-agent-name = traitor objective-issuer-syndicate = [color=crimson]The Syndicate[/color] +objective-issuer-unknown = Unknown # Shown at the end of a round of Traitor @@ -23,7 +24,7 @@ traitor-death-match-end-round-description-entry = {$originalName}'s PDA, with {$ # TraitorRole traitor-role-greeting = - You are a syndicate agent. + You are an agent sent by {$corporation} on behalf of The Syndicate. Your objectives and codewords are listed in the character menu. Use the uplink loaded into your PDA to buy the tools you'll need for this mission. Death to Nanotrasen! diff --git a/Resources/Locale/en-US/game-ticking/game-rules/rule-terminator.ftl b/Resources/Locale/en-US/game-ticking/game-rules/rule-terminator.ftl deleted file mode 100644 index 2e16743177..0000000000 --- a/Resources/Locale/en-US/game-ticking/game-rules/rule-terminator.ftl +++ /dev/null @@ -1,18 +0,0 @@ -terminator-round-end-agent-name = nt-800 - -objective-issuer-susnet = [color=#d64119]Susnet[/color] - -terminator-role-greeting = - You are the exterminator, a relentless assassin sent into the past to secure our future. - We need you to eliminate {$target}, {$job}. - Use any means at your disposal to complete the mission. - Glory to Cybersun. - -# DeltaV - paradox anomaly -terminator-role-briefing = - You are a bluespace anomaly that looks and sound identical to someone from this reality. - Kill them and assume their identity, or talk it out and become friends. - Your objectives support either playstyle (and you obviously can't do both). - -terminator-endoskeleton-gib-popup = All the battered flesh falls apart, revealing a titanium endoskeleton! -terminator-endoskeleton-burn-popup = The seared flesh is burned to a crisp, revealing a titanium endoskeleton! diff --git a/Resources/Locale/en-US/geras/geras.ftl b/Resources/Locale/en-US/geras/geras.ftl new file mode 100644 index 0000000000..3cd3f101ff --- /dev/null +++ b/Resources/Locale/en-US/geras/geras.ftl @@ -0,0 +1,2 @@ +geras-popup-morph-message-user = You shift and morph into a small version of you! +geras-popup-morph-message-others = {CAPITALIZE(THE($entity))} shifts and morphs into a blob of slime! diff --git a/Resources/Locale/en-US/ghost/ghost-gui.ftl b/Resources/Locale/en-US/ghost/ghost-gui.ftl index 909513e96c..1d793f3941 100644 --- a/Resources/Locale/en-US/ghost/ghost-gui.ftl +++ b/Resources/Locale/en-US/ghost/ghost-gui.ftl @@ -10,8 +10,20 @@ ghost-gui-toggle-hearing-popup-off = You can now only hear radio and nearby mess ghost-target-window-title = Ghost Warp ghost-target-window-current-button = Warp: {$name} +ghost-target-window-warp-to-most-followed = Warp to Most Followed ghost-roles-window-title = Ghost Roles +ghost-roles-window-join-raffle-button = Join raffle +ghost-roles-window-raffle-in-progress-button = + Join raffle ({$time} left, { $players -> + [one] {$players} player + *[other] {$players} players + }) +ghost-roles-window-leave-raffle-button = + Leave raffle ({$time} left, { $players -> + [one] {$players} player + *[other] {$players} players + }) ghost-roles-window-request-role-button = Request ghost-roles-window-request-role-button-timer = Request ({$time}s) ghost-roles-window-follow-role-button = Follow @@ -20,3 +32,4 @@ ghost-roles-window-rules-footer = The button will enable after {$time} seconds ( ghost-return-to-body-title = Return to Body ghost-return-to-body-text = You are being revived! Return to your body? +ghost-gui-return-to-round-button = Return to round \ No newline at end of file diff --git a/Resources/Locale/en-US/ghost/ghost-respawn.ftl b/Resources/Locale/en-US/ghost/ghost-respawn.ftl new file mode 100644 index 0000000000..a97fc68952 --- /dev/null +++ b/Resources/Locale/en-US/ghost/ghost-respawn.ftl @@ -0,0 +1,15 @@ +ghost-respawn-time-left = Please wait {$time} {$time -> + [one] minute + *[other] minutes +} before trying to respawn. + +ghost-respawn-max-players = Cannot respawn right now. There should be fewer than {$players} players. +ghost-respawn-window-title = Respawn rules +ghost-respawn-window-rules-footer = By respawning, you [color=#ff7700]agree[/color] [color=#ff0000]not to use any knowledge gained as your previous charactrer[/color]. Violation of this rule may constitute a server ban. Please, read the server rules for more details. +ghost-respawn-same-character = You cannot respawn as the same character. Please select a different one in character preferences. + +ghost-respawn-log-character-almost-same = Player {$player} { $try -> + [true] joined + *[false] tried to join +} the round after respawning with a similar name. Previous name: { $oldName }, current: { $newName }. +ghost-respawn-log-return-to-lobby = { $userName } returned to the lobby. diff --git a/Resources/Locale/en-US/ghost/roles/ghost-role-component.ftl b/Resources/Locale/en-US/ghost/roles/ghost-role-component.ftl index 57ef759400..4e6ec1f188 100644 --- a/Resources/Locale/en-US/ghost/roles/ghost-role-component.ftl +++ b/Resources/Locale/en-US/ghost/roles/ghost-role-component.ftl @@ -4,17 +4,6 @@ ghost-role-component-default-rules = All normal rules apply unless an administra You are allowed to remember knowledge about the game in general, such as how to cook, how to use objects, etc. You are absolutely [color=red]NOT[/color] allowed to remember, say, the name, appearance, etc. of your previous character. -# Delta V soft antag rule -deltav-ghost-role-information-softantag-rules = You are allowed to attack the crew without provocation. - You are allowed to coexist with the crew. - The crew is allowed to kill you without warning. - -# Delta V salvage antag rule -deltav-ghost-role-information-salvageantag-rules = You are a salvage mob. - You are allowed to attack salvagers without provocation. DO NOT GIB THEIR CORPSES! - You are allowed to attack the salvage shuttle. - You are NOT allowed to go to the station. If the salvagers go to the station, you can't follow them. - ghost-role-information-mouse-name = Mouse ghost-role-information-mouse-description = A hungry and mischievous mouse. @@ -40,16 +29,13 @@ ghost-role-information-angry-slimes-description = Everyone around you irritates ghost-role-information-smile-name = Smile the Slime ghost-role-information-smile-description = The sweetest creature in the world. Smile Slime! -ghost-role-information-smile-rules = You are a crew-aligned pet. You can defend yourself and even attack threats to the station, but you generally should not attack the station's crew nor destroy their property. ghost-role-information-punpun-name = Pun Pun ghost-role-information-punpun-description = An honorable member of the monkey society in charge of the bar and helping the bartenders in any way he can. ghost-role-information-xeno-name = Xeno ghost-role-information-xeno-description = You are a xeno, co-operate with your hive to kill all crewmembers! -ghost-role-information-xeno-rules = You are a xeno. - Your objective is to cooperate with your fellow xenos and kill all the crew. - Strike fast, make them fear the shadows. +ghost-role-information-xeno-rules = You are an antagonist, smack, slash, and wack! ghost-role-information-revenant-name = Revenant ghost-role-information-revenant-description = You are a Revenant. Use your powers to harvest souls and unleash chaos upon the crew. Unlock new abilities with the essence you harvest. @@ -161,7 +147,7 @@ ghost-role-information-skeleton-biker-name = Skeleton Biker ghost-role-information-skeleton-biker-description = Ride around on your sweet ride. ghost-role-information-closet-skeleton-name = Closet Skeleton -ghost-role-information-closet-skeleton-description = You are a closet skeleton! You are a primordial force of chaos with no allegiance! You can either join the crew and use your skeletal antics to help them, or be a a prankster, and hinder their efforts! +ghost-role-information-closet-skeleton-description = Wreak havoc! You are a primordial force with no allegiance. Live happily with the crew or wage sweet skeletal war. ghost-role-information-onestar-mecha-name = Onestar Mecha ghost-role-information-onestar-mecha-description = You are an experimental mecha created by who-knows-what, all you know is that you have weapons and you detect fleshy moving targets nearby... @@ -226,14 +212,6 @@ ghost-role-information-BreadDog-name = BreadDog ghost-role-information-BreadDog-description = You are the chef's favorite child. You're a living bread dog. ghost-role-information-BreadDog-rules = You're an edible dog made of bread. Your task is to find your place in this world where everything wants to eat you. -ghost-role-information-Shiva-name = Geoff -ghost-role-information-Shiva-description = Geoff, the stations best friend. Help the Head of Security in their work. -ghost-role-information-Shiva-rules = Bring joy to the Security crew and remove any fear of spiders from everyone else. - -ghost-role-information-exterminator-name = Exterminator -ghost-role-information-exterminator-description = You been been sent back in time to terminate a target with high importance to the future. -ghost-role-information-exterminator-rules = You are an antagonist and may kill anyone that tries to stop you, but killing the target is always your top priority. - ghost-role-information-space-ninja-name = Space Ninja ghost-role-information-space-ninja-description = Use stealth and deception to sabotage the station. ghost-role-information-space-ninja-rules = You are an elite mercenary of the Spider Clan. You aren't required to follow your objectives, yet your NINJA HONOR demands you try. @@ -246,6 +224,30 @@ ghost-role-information-syndicate-monkey-reinforcement-name = Syndicate Monkey Ag ghost-role-information-syndicate-monkey-reinforcement-description = Someone needs reinforcements. You, a trained monkey, will help them. ghost-role-information-syndicate-monkey-reinforcement-rules = Normal syndicate antagonist rules apply. Work with whoever called you in, and don't harm them. +ghost-role-information-lost-cargo-technical-name = Lost Cargo Technician +ghost-role-information-lost-cargo-technical-description = Something went wrong and your cargo shuttle with the goods was beamed into the sector to another station. +ghost-role-information-lost-cargo-technical-rules = You're a regular cargo technician from another station. Do what regular cargo do. + +ghost-role-information-clown-troupe-name = Space Clown +ghost-role-information-clown-troupe-description = You and your troupe have come to cheer up this station with your best jokes. Honk! +ghost-role-information-clown-troupe-rules = Normal station crew rules apply. + +ghost-role-information-traveling-chef-name = Traveling Chef +ghost-role-information-traveling-chef-description = You are a chef on a traveling shuttle of exotic cuisine. Delight the station with delicious food! +ghost-role-information-traveling-chef-rules = Normal station crew rules apply. + +ghost-role-information-disaster-victim-name = Disaster Victim +ghost-role-information-disaster-victim-description = You were rescued in an escape pod from another station that suffered a terrible fate. Perhaps you will be found and rescued. +ghost-role-information-disaster-victim-rules = Normal station crew rules apply. + +ghost-role-information-syndie-disaster-victim-name = Syndie Disaster Victim +ghost-role-information-syndie-disaster-victim-description = You're a regular passenger from a syndicate station. Unfortunately, an evacuation pod has thrown you into an enemy sector..... +ghost-role-information-syndie-disaster-victim-rules = Normal station crew rules apply. You are NOT an antagonist! + +ghost-role-information-syndicate-kobold-reinforcement-name = Syndicate Kobold Agent +ghost-role-information-syndicate-kobold-reinforcement-description = Someone needs reinforcements. You, a trained kobold, will help them. +ghost-role-information-syndicate-kobold-reinforcement-rules = Normal syndicate antagonist rules apply. Work with whoever called you in, and don't harm them. + ghost-role-information-artifact-name = Sentient Artifact ghost-role-information-artifact-description = Enact your eldritch whims. diff --git a/Resources/Locale/en-US/ghost/roles/ghostrole-spawner-verb-selectable.ftl b/Resources/Locale/en-US/ghost/roles/ghostrole-spawner-verb-selectable.ftl new file mode 100644 index 0000000000..9d649a5e07 --- /dev/null +++ b/Resources/Locale/en-US/ghost/roles/ghostrole-spawner-verb-selectable.ftl @@ -0,0 +1 @@ +ghostrole-spawner-select = Selected: {$mode} \ No newline at end of file diff --git a/Resources/Locale/en-US/guidebook/chemistry/conditions.ftl b/Resources/Locale/en-US/guidebook/chemistry/conditions.ftl index 807b5591a8..6cbfc13a79 100644 --- a/Resources/Locale/en-US/guidebook/chemistry/conditions.ftl +++ b/Resources/Locale/en-US/guidebook/chemistry/conditions.ftl @@ -1,4 +1,4 @@ -reagent-effect-condition-guidebook-total-damage = +reagent-effect-condition-guidebook-total-damage = { $max -> [2147483648] it has at least {NATURALFIXED($min, 2)} total damage *[other] { $min -> @@ -7,6 +7,15 @@ } } +reagent-effect-condition-guidebook-total-hunger = + { $max -> + [2147483648] the target has at least {NATURALFIXED($min, 2)} total hunger + *[other] { $min -> + [0] the target has at most {NATURALFIXED($max, 2)} total hunger + *[other] the target has between {NATURALFIXED($min, 2)} and {NATURALFIXED($max, 2)} total hunger + } + } + reagent-effect-condition-guidebook-reagent-threshold = { $max -> [2147483648] there's at least {NATURALFIXED($min, 2)}u of {$reagent} @@ -48,3 +57,5 @@ reagent-effect-condition-guidebook-has-tag = [true] does not have *[false] has } the tag {$tag} + +reagent-effect-condition-guidebook-this-reagent = this reagent diff --git a/Resources/Locale/en-US/guidebook/chemistry/effects.ftl b/Resources/Locale/en-US/guidebook/chemistry/effects.ftl index db2f3816f6..9da6c0c0ae 100644 --- a/Resources/Locale/en-US/guidebook/chemistry/effects.ftl +++ b/Resources/Locale/en-US/guidebook/chemistry/effects.ftl @@ -43,7 +43,7 @@ reagent-effect-guidebook-foam-area-reaction-effect = *[other] create } large quantities of foam -reagent-effect-guidebook-foam-area-reaction-effect = +reagent-effect-guidebook-smoke-area-reaction-effect = { $chance -> [1] Creates *[other] create @@ -339,6 +339,12 @@ reagent-effect-guidebook-innoculate-zombie-infection = *[other] cure } an ongoing zombie infection, and provides immunity to future infections +reagent-effect-guidebook-reduce-rotting = + { $chance -> + [1] Regenerates + *[other] regenerate + } {NATURALFIXED($time, 3)} {MANY("second", $time)} of rotting + reagent-effect-guidebook-missing = { $chance -> [1] Causes @@ -361,4 +367,11 @@ reagent-effect-guidebook-chem-reroll-psionic = { $chance -> [1] Allows *[other] allow - } a chance to get a different psionic power \ No newline at end of file + } a chance to get a different psionic power + +reagent-effect-guidebook-add-moodlet = + modifies mood by {$amount} + { $timeout -> + [0] indefinitely + *[other] for {$timeout} seconds + } \ No newline at end of file diff --git a/Resources/Locale/en-US/guidebook/food.ftl b/Resources/Locale/en-US/guidebook/food.ftl new file mode 100644 index 0000000000..bd4fa03251 --- /dev/null +++ b/Resources/Locale/en-US/guidebook/food.ftl @@ -0,0 +1,16 @@ +guidebook-food-name = [bold][color={$color}]{CAPITALIZE($name)}[/color][/bold] +guidebook-food-unknown-proto = Unknown prototype +guidebook-food-sources-header = Sources +guidebook-food-sources-ent-wrapper = {$name} +guidebook-food-reagents-header = Chemical composition + +guidebook-food-processing-butchering = Butcher +guidebook-food-processing-slicing = Slice +guidebook-food-processing-cooking = Microwave for {$time}s +guidebook-food-processing-reaction = Mix + +guidebook-food-processing-recipe = {$ingredients} +guidebook-food-ingredient-solid = add {$amount} {$name} +guidebook-food-ingredient-liquid = add {$amount}u {$name} + +guidebook-food-output = {$name} ({$number}) diff --git a/Resources/Locale/en-US/guidebook/guides.ftl b/Resources/Locale/en-US/guidebook/guides.ftl index 968d3b2003..e2090d5af0 100644 --- a/Resources/Locale/en-US/guidebook/guides.ftl +++ b/Resources/Locale/en-US/guidebook/guides.ftl @@ -11,7 +11,7 @@ guide-entry-access-configurator = Access Configurator guide-entry-power = Power guide-entry-portable-generator = Portable Generators guide-entry-ame = Antimatter Engine (AME) -guide-entry-singularity = Singularity +guide-entry-singularity = Singularity / Tesla guide-entry-teg = Thermo-electric Generator (TEG) guide-entry-rtg = RTG guide-entry-controls = Controls @@ -37,6 +37,10 @@ guide-entry-chef = Chef guide-entry-foodrecipes = Food Recipes guide-entry-medical = Medical guide-entry-medicaldoctor = Medical Doctor +guide-entry-surgery = Surgery +guide-entry-partmanipulation = Part Manipulation +guide-entry-organmanipulation = Organ Manipulation +guide-entry-utilitysurgeries = Utility Surgeries guide-entry-chemist = Chemist guide-entry-medicine = Medicine guide-entry-brute = Advanced Brute Medication @@ -72,5 +76,5 @@ guide-entry-writing = Writing guide-entry-glossary = Glossary guide-entry-altars-golemancy = Altars and Golemancy -guide-entry-psionics = Psionics +guide-entry-glimmer-creatures = Glimmer Creatures guide-entry-reverse-engineering = Reverse Engineering diff --git a/Resources/Locale/en-US/headset/headset-component.ftl b/Resources/Locale/en-US/headset/headset-component.ftl index 75c83643d6..3f89dde13a 100644 --- a/Resources/Locale/en-US/headset/headset-component.ftl +++ b/Resources/Locale/en-US/headset/headset-component.ftl @@ -1,6 +1,6 @@ # Chat window radio wrap (prefix and postfix) -chat-radio-message-wrap = [color={$color}]{$channel} {$name} {$verb}, [font="{$fontType}" size={$fontSize}]"[/color][color={$languageColor}]{$message}[/color][color={$color}]"[/font][/color] -chat-radio-message-wrap-bold = [color={$color}]{$channel} {$name} {$verb}, [font="{$fontType}" size={$fontSize}][bold]"[/color][color={$languageColor}]{$message}[/color][color={$color}]"[/bold][/font][/color] +chat-radio-message-wrap = [color={ $color }]{ $channel } [bold]{ $language }{ $name }[/bold] { $verb }, [font={ $fontType } size={ $fontSize }]"{ $message }"[/font][/color] +chat-radio-message-wrap-bold = [color={ $color }]{ $channel } [bold]{ $language }{ $name }[/bold] { $verb }, [font={ $fontType } size={ $fontSize }][bold]"{ $message }"[/bold][/font][/color] examine-headset-default-channel = Use {$prefix} for the default channel ([color={$color}]{$channel}[/color]). diff --git a/Resources/Locale/en-US/health-examinable/health-examinable-comp.ftl b/Resources/Locale/en-US/health-examinable/health-examinable-comp.ftl index af31837ab8..60204eb2bd 100644 --- a/Resources/Locale/en-US/health-examinable/health-examinable-comp.ftl +++ b/Resources/Locale/en-US/health-examinable/health-examinable-comp.ftl @@ -1,2 +1,4 @@ health-examinable-verb-text = Health health-examinable-verb-disabled = Perform a basic health examination in close range. + +health-alert-start = [font size=12][color=green]Health:[/color][/font] diff --git a/Resources/Locale/en-US/health-examinable/health-examinable-silicon.ftl b/Resources/Locale/en-US/health-examinable/health-examinable-silicon.ftl index 03eaf07a3b..1608d8511d 100644 --- a/Resources/Locale/en-US/health-examinable/health-examinable-silicon.ftl +++ b/Resources/Locale/en-US/health-examinable/health-examinable-silicon.ftl @@ -1,4 +1,4 @@ -health-examinable-silicon-none = There is no obvious damage to be seen. +health-examinable-silicon-none = [color=green]There is no obvious damage to be seen.[/color] health-examinable-silicon-Blunt-25 = [color=red]{ CAPITALIZE(SUBJECT($target)) } { CONJUGATE-HAVE($target) } minor dents on { POSS-ADJ($target) } chassis.[/color] health-examinable-silicon-Blunt-50 = [color=crimson]{ CAPITALIZE(POSS-ADJ($target)) } chassis is severely dented![/color] diff --git a/Resources/Locale/en-US/implant/implant.ftl b/Resources/Locale/en-US/implant/implant.ftl index b93d43105a..c3002a73ae 100644 --- a/Resources/Locale/en-US/implant/implant.ftl +++ b/Resources/Locale/en-US/implant/implant.ftl @@ -10,9 +10,10 @@ implanter-component-implant-already = {$target} already has the {$implant}! implanter-draw-text = Draw implanter-inject-text = Inject -implanter-empty-text = None +implanter-empty-text = Empty -implanter-label = Implant: [color=green]{$implantName}[/color] | [color=white]{$modeString}[/color]{$lineBreak}{$implantDescription} +implanter-label = [color=green]{$implantName}[/color] + Mode: [color=white]{$modeString}[/color] implanter-contained-implant-text = [color=green]{$desc}[/color] diff --git a/Resources/Locale/en-US/info/rules.ftl b/Resources/Locale/en-US/info/rules.ftl index 1f864f361e..28791fd7ec 100644 --- a/Resources/Locale/en-US/info/rules.ftl +++ b/Resources/Locale/en-US/info/rules.ftl @@ -1,6 +1,5 @@ # Rules -ui-rules-header = Parkstation Official Server Rules -ui-rules-header-rp = Parkstation Roleplay Official Server Rules +ui-rules-header = Official Server Rules ui-rules-accept = I have read and agree to follow the rules ui-rules-wait = The accept button will be enabled after {$time} seconds. diff --git a/Resources/Locale/en-US/info/whitelists.ftl b/Resources/Locale/en-US/info/whitelists.ftl new file mode 100644 index 0000000000..b67fc3e0ad --- /dev/null +++ b/Resources/Locale/en-US/info/whitelists.ftl @@ -0,0 +1,3 @@ +cmd-jobwhitelists-desc = Opens the job whitelists window +cmd-jobwhitelists-help = Usage: jobwhitelists [name or user guid] +cmd-jobwhitelists-player-err = The specified player could not be found diff --git a/Resources/Locale/en-US/interaction/interaction-popup-component.ftl b/Resources/Locale/en-US/interaction/interaction-popup-component.ftl index a6eacf13c5..7bce6616c5 100644 --- a/Resources/Locale/en-US/interaction/interaction-popup-component.ftl +++ b/Resources/Locale/en-US/interaction/interaction-popup-component.ftl @@ -42,7 +42,6 @@ petting-failure-crab = You reach out to pet {THE($target)}, but {SUBJECT($target petting-failure-dehydrated-carp = You pet {THE($target)} on {POSS-ADJ($target)} dry little head. petting-failure-goat = You reach out to pet {THE($target)}, but {SUBJECT($target)} stubbornly refuses! petting-failure-goose = You reach out to pet {THE($target)}, but {SUBJECT($target)} {CONJUGATE-BE($target)} too horrible! -petting-failure-goose = You reach out to pet {THE($target)}, but {SUBJECT($target)} {CONJUGATE-BE($target)} too australian! petting-failure-possum = You reach out to pet {THE($target)}, but are met with hisses and snarls! petting-failure-pig = You reach out to pet {THE($target)}, but are met with irritated oinks and squeals! petting-failure-raccoon = You reach out to pet {THE($target)}, but {THE($target)} is busy raccooning around. @@ -50,6 +49,7 @@ petting-failure-sloth = You reach out to pet {THE($target)}, but {SUBJECT($targe petting-failure-holo = You reach out to pet {THE($target)}, but {POSS-ADJ($target)} spikes almost impale your hand! petting-failure-dragon = You raise your hand, but as {THE($target)} roars, you decide you'd rather not be toasty carp food. petting-failure-hamster = You reach out to pet {THE($target)}, but {SUBJECT($target)} attempts to bite your finger and only your quick reflexes save you from an almost fatal injury. +petting-failure-pibble = You reach out to pet {THE($target)}, and {SUBJECT($target)} lunges at you! petting-failure-bear = You reach out to pet {THE($target)}, but {SUBJECT($target)} growls, making you think twice. petting-failure-monkey = You reach out to pet {THE($target)}, but {SUBJECT($target)} almost bites your fingers! petting-failure-nymph = You reach out to pet {THE($target)}, but {POSS-ADJ($target)} moves their branches away. @@ -68,15 +68,16 @@ petting-failure-cleanbot = You reach out to pet {THE($target)}, but {SUBJECT($ta petting-failure-mimebot = You reach out to pet {THE($target)}, but {SUBJECT($target)} {CONJUGATE-BE($target)} busy miming! petting-failure-medibot = You reach out to pet {THE($target)}, but {POSS-ADJ($target)} syringe nearly stabs your hand! -## Knocking on windows - -# Shown when knocking on a window -comp-window-knock = *knock knock* - ## Rattling fences fence-rattle-success = *rattle* +## Hugging players + +hugging-success-generic = You hug {THE($target)}. +hugging-success-generic-others = { CAPITALIZE(THE($user)) } hugs {THE($target)}. +hugging-success-generic-target = { CAPITALIZE(THE($user)) } hugs you. + ## Other petting-success-tesla = You pet {THE($target)}, violating the laws of nature and physics. diff --git a/Resources/Locale/en-US/interaction/verbs/core.ftl b/Resources/Locale/en-US/interaction/verbs/core.ftl new file mode 100644 index 0000000000..5cdb634223 --- /dev/null +++ b/Resources/Locale/en-US/interaction/verbs/core.ftl @@ -0,0 +1,7 @@ +interaction-verb-invalid = Some requirements for this verb are not met. You cannot use it right now. +interaction-verb-cooldown = This verb is on cooldown. Wait {TOSTRING($seconds, "F1")} seconds. +interaction-verb-too-strong = You are too strong to use this verb. +interaction-verb-too-weak = You are too weak to use this verb. +interaction-verb-invalid-target = You cannot use this verb on that target. +interaction-verb-no-hands = You have no usable hands. +interaction-verb-cannot-reach = You cannot reach there. diff --git a/Resources/Locale/en-US/interaction/verbs/help.ftl b/Resources/Locale/en-US/interaction/verbs/help.ftl new file mode 100644 index 0000000000..a615dcd9a5 --- /dev/null +++ b/Resources/Locale/en-US/interaction/verbs/help.ftl @@ -0,0 +1,40 @@ +interaction-HelpUp-name = Help up +interaction-HelpUp-description = Help the person get up. +interaction-HelpUp-delayed-self-popup = You try to help {THE($target)} get up... +interaction-HelpUp-delayed-target-popup = {THE($user)} tries to help you get up... +interaction-HelpUp-delayed-others-popup = {THE($user)} tries to help {THE($target)} get up... +interaction-HelpUp-success-self-popup = You help {THE($target)} get up. +interaction-HelpUp-success-target-popup = {THE($user)} helps you up. +interaction-HelpUp-success-others-popup = {THE($user)} helps {THE($target)} up. +interaction-HelpUp-fail-self-popup = You fail to help {THE($target)} get up. +interaction-HelpUp-fail-target-popup = {THE($user)} fails to help you up. + +interaction-ForceDown-name = Force down +interaction-ForceDown-description = Force the person to lay down on the floor. +interaction-ForceDown-delayed-self-popup = You try to force {THE($target)} down... +interaction-ForceDown-delayed-target-popup = {THE($user)} tries to force you down... +interaction-ForceDown-delayed-others-popup = {THE($user)} tries to force {THE($target)} down... +interaction-ForceDown-success-self-popup = You force {THE($target)} to lay down. +interaction-ForceDown-success-target-popup = {THE($user)} forces you to lay down. +interaction-ForceDown-success-others-popup = {THE($user)} forces {THE($target)} to lay down. +interaction-ForceDown-fail-self-popup = You fail to force {THE($target)} down. +interaction-ForceDown-fail-target-popup = {THE($user)} fails to force you down. + +interaction-MakeSleepOther-name = Make sleep +interaction-MakeSleepOther-description = Put the target to sleep. +interaction-MakeSleepOther-delayed-self-popup = You are trying to put {THE($target)} to sleep... +interaction-MakeSleepOther-delayed-target-popup = {THE($user)} is trying to put you to sleep... +interaction-MakeSleepOther-delayed-others-popup = {THE($user)} is trying to put {THE($target)} to sleep... +interaction-MakeSleepOther-fail-self-popup = You fail to put {THE($target)} to sleep. +interaction-MakeSleepOther-fail-target-popup = {THE($user)} fails to put you to sleep. +interaction-MakeSleepOther-success-self-popup = You put {THE($target)} to sleep. +interaction-MakeSleepOther-success-target-popup = {THE($user)} puts you to sleep. +interaction-MakeSleepOther-success-others-popup = {THE($user)} puts {THE($target)} to sleep. + +interaction-ShakeOther-name = Shake +interaction-ShakeOther-description = Shake the target. +interaction-ShakeOther-fail-self-popup = You somehow fail to shake {THE($target)}. +interaction-ShakeOther-fail-target-popup = {THE($user)} somehow fails to shake you. +interaction-ShakeOther-success-self-popup = You grab and shake {THE($target)}. +interaction-ShakeOther-success-target-popup = {THE($user)} grabs and shakes you. +interaction-ShakeOther-success-others-popup = {THE($user)} grabs and shakes {THE($target)}. diff --git a/Resources/Locale/en-US/interaction/verbs/noop.ftl b/Resources/Locale/en-US/interaction/verbs/noop.ftl new file mode 100644 index 0000000000..953b2808ec --- /dev/null +++ b/Resources/Locale/en-US/interaction/verbs/noop.ftl @@ -0,0 +1,50 @@ +interaction-LookAt-name = Look at +interaction-LookAt-description = Stare into the void and see it stare back. +interaction-LookAt-success-self-popup = You look at {THE($target)}. +interaction-LookAt-success-target-popup = You feel {THE($user)} looking at you... +interaction-LookAt-success-others-popup = {THE($user)} looks at {THE($target)}. + +interaction-Hug-name = Hug +interaction-Hug-description = A hug a day keeps the psychological horrors beyond your comprehension away. +interaction-Hug-success-self-popup = You hug {THE($target)}. +interaction-Hug-success-target-popup = {THE($user)} hugs you. +interaction-Hug-success-others-popup = {THE($user)} hugs {THE($target)}. + +interaction-Pet-name = Pet +interaction-Pet-description = Pet your co-worker to ease their stress. +interaction-Pet-success-self-popup = You pet {THE($target)} on {POSS-ADJ($target)} head. +interaction-Pet-success-target-popup = {THE($user)} pets you on your head. +interaction-Pet-success-others-popup = {THE($user)} pets {THE($target)}. + +interaction-PetAnimal-name = {interaction-Pet-name} +interaction-PetAnimal-description = Pet an animal. +interaction-PetAnimal-success-self-popup = {interaction-Pet-success-self-popup} +interaction-PetAnimal-success-target-popup = {interaction-Pet-success-target-popup} +interaction-PetAnimal-success-others-popup = {interaction-Pet-success-others-popup} + +interaction-KnockOn-name = Knock +interaction-KnockOn-description = Knock on the target to attract attention. +interaction-KnockOn-success-self-popup = You knock on {THE($target)}. +interaction-KnockOn-success-target-popup = {THE($user)} knocks on you. +interaction-KnockOn-success-others-popup = {THE($user)} knocks on {THE($target)}. + +interaction-Rattle-name = Rattle +interaction-Rattle-success-self-popup = You rattle {THE($target)}. +interaction-Rattle-success-target-popup = {THE($user)} rattles you. +interaction-Rattle-success-others-popup = {THE($user)} rattles {THE($target)}. + +# The below includes conditionals for if the user is holding an item +interaction-WaveAt-name = Wave at +interaction-WaveAt-description = Wave at the target. If you are holding an item, you will wave it. +interaction-WaveAt-success-self-popup = You wave {$hasUsed -> + [false] at {THE($target)}. + *[true] your {$used} at {THE($target)}. +} +interaction-WaveAt-success-target-popup = {THE($user)} waves {$hasUsed -> + [false] at you. + *[true] {POSS-PRONOUN($user)} {$used} at you. +} +interaction-WaveAt-success-others-popup = {THE($user)} waves {$hasUsed -> + [false] at {THE($target)}. + *[true] {POSS-PRONOUN($user)} {$used} at {THE($target)}. +} diff --git a/Resources/Locale/en-US/interaction/verbs/self.ftl b/Resources/Locale/en-US/interaction/verbs/self.ftl new file mode 100644 index 0000000000..aee2307615 --- /dev/null +++ b/Resources/Locale/en-US/interaction/verbs/self.ftl @@ -0,0 +1,18 @@ +# Interactions here don't have target popups because the target is always the user itself. +# Why would you do this? +interaction-PinchSelf-name = Pinch yourself +interaction-PinchSelf-description = They say it helps you make sure the hell that goes around you is not a dream. +interaction-PinchSelf-success-self-popup = You pinch yourself... Ouch! +interaction-PinchSelf-success-others-popup = {THE($user)} pinches {REFLEXIVE($user)}... Looks painful! +interaction-PinchSelf-fail-self-popup = You somehow fail to pinch yourself. Better for you. +interaction-PinchSelf-delayed-self-popup = You pinch yourself... +interaction-PinchSelf-message-1 = Ouchh!! +interaction-PinchSelf-message-2 = Aaaah!! +interaction-PinchSelf-message-3 = Ow!! + +interaction-MakeSleepSelf-name = Sleep +interaction-MakeSleepSelf-description = Put yourself to sleep. +interaction-MakeSleepSelf-delayed-self-popup = You are trying to fall asleep... +interaction-MakeSleepSelf-fail-self-popup = You cannot sleep right now. +interaction-MakeSleepSelf-success-self-popup = You put yourself to sleep. +interaction-MakeSleepSelf-success-others-popup = {THE($user)} falls asleep. diff --git a/Resources/Locale/en-US/inventory/item-status.ftl b/Resources/Locale/en-US/inventory/item-status.ftl new file mode 100644 index 0000000000..a53ba8be7d --- /dev/null +++ b/Resources/Locale/en-US/inventory/item-status.ftl @@ -0,0 +1 @@ +item-status-not-held = No held item diff --git a/Resources/Locale/en-US/job/job-description.ftl b/Resources/Locale/en-US/job/job-description.ftl index 3a25518c2e..801e402eae 100644 --- a/Resources/Locale/en-US/job/job-description.ftl +++ b/Resources/Locale/en-US/job/job-description.ftl @@ -30,7 +30,7 @@ job-description-hos = Manage your security force and keep them efficient, quell job-description-intern = Learn the basics of administering medicine, basic chemicals and using medical tools. job-description-janitor = Keep the station clean of any trash or slipping hazards, and help deal with rat infestations. job-description-lawyer = Ensure that every prisoner or criminal receives a fair judgment and trial if necessary. -job-description-librarian = Manage the library, give out knowledge to any who seek it, and report on activities aboard the station. +job-description-librarian = Understand every language and hear your coworkers shit talk each other, lord over your collection of outdated guides, lament the lack of rich text in the game. job-description-mime = Entertain the crew through non-vocal means, and engage with light rivalry with the clown. job-description-musician = Entertain the crew with your unique musical talent, and acquire new instruments to mess around with. job-description-passenger = Enjoy your stay aboard the station with no obligations! @@ -39,6 +39,7 @@ job-description-qm = Manage the supplies of the station & the logistics departme job-description-rd = Manage the epistemics department, unlocking technologies, acquiring & researching artifacts, and performing experiments. job-description-research-assistant = Learn the basics of how to research various artifacts, anomalies and robotics. job-description-reporter = Entertain & inform the crew with your vibrant journalism through wireless cameras, the radio and the news. +job-description-roboticist = Build a makeshift workshop in epistemics closet and create your own cyborg army. job-description-salvagespec = Use the salvage magnet to draw in detatched scraps & asteroids to loot and enrich the station, build a salvage ship and then travel to new planets, while fighting off any space fauna along the way. job-description-scientist = Research alien artifacts, unlock new technologies, build newer and better machines around the station, and make everything run more efficiently. job-description-security = Catch criminals and enemies of the station, enforce the law, and ensure that the station does not fall into disarray. diff --git a/Resources/Locale/en-US/job/job-names.ftl b/Resources/Locale/en-US/job/job-names.ftl index 19d2db9451..60ab9b0543 100644 --- a/Resources/Locale/en-US/job/job-names.ftl +++ b/Resources/Locale/en-US/job/job-names.ftl @@ -5,9 +5,11 @@ job-name-hos = Head of Security job-name-detective = Detective job-name-brigmedic = Corpsman job-name-borg = Cyborg -job-name-scientist = Scientist -job-name-research-assistant = Research Assistant +job-name-senior-researcher = Mystic +job-name-scientist = Acolyte +job-name-research-assistant = Noviciate job-name-rd = Mystagogue +job-name-roboticist = Golemancer job-name-psychologist = Psychologist job-name-intern = Medical Intern job-name-doctor = Medical Doctor @@ -23,7 +25,7 @@ job-name-serviceworker = Service Worker job-name-centcomoff = CentCom Official job-name-reporter = Reporter job-name-musician = Musician -job-name-librarian = Librarian +job-name-librarian = Cataloguer # DeltaV - Changed Lawyer to Attorney # job-name-lawyer = Lawyer job-name-mime = Mime @@ -85,16 +87,17 @@ JobPassenger = Passenger JobPsychologist = Psychologist JobQuartermaster = Logistics Officer JobReporter = Reporter -JobResearchAssistant = Research Assistant +JobResearchAssistant = Noviciate JobResearchDirector = Mystagogue +JobRoboticist = Golemancer JobSalvageSpecialist = Salvage Specialist -JobScientist = Scientist +JobScientist = Acolyte JobSecurityCadet = Security Cadet JobSecurityOfficer = Security Officer JobSeniorEngineer = Senior Engineer JobSeniorOfficer = Senior Officer JobSeniorPhysician = Senior Physician -JobSeniorResearcher = Senior Researcher +JobSeniorResearcher = Mystic JobServiceWorker = Service Worker JobStationEngineer = Station Engineer JobTechnicalAssistant = Technical Assistant diff --git a/Resources/Locale/en-US/job/job-supervisors.ftl b/Resources/Locale/en-US/job/job-supervisors.ftl index 25a49f643e..46321f40dd 100644 --- a/Resources/Locale/en-US/job/job-supervisors.ftl +++ b/Resources/Locale/en-US/job/job-supervisors.ftl @@ -1,4 +1,4 @@ -job-supervisors-centcom = CentCom official +job-supervisors-centcom = Central Command job-supervisors-captain = the captain job-supervisors-hop = the head of personnel job-supervisors-hos = the head of security diff --git a/Resources/Locale/en-US/job/role-whitelist.ftl b/Resources/Locale/en-US/job/role-whitelist.ftl new file mode 100644 index 0000000000..70031a650d --- /dev/null +++ b/Resources/Locale/en-US/job/role-whitelist.ftl @@ -0,0 +1 @@ +role-not-whitelisted = You are not whitelisted to play this role. diff --git a/Resources/Locale/en-US/jukebox/jukebox-menu.ftl b/Resources/Locale/en-US/jukebox/jukebox-menu.ftl new file mode 100644 index 0000000000..d015976cc4 --- /dev/null +++ b/Resources/Locale/en-US/jukebox/jukebox-menu.ftl @@ -0,0 +1,5 @@ +jukebox-menu-title = Jukebox +jukebox-menu-selectedsong = Selected Song: +jukebox-menu-buttonplay = Play +jukebox-menu-buttonpause = Pause +jukebox-menu-buttonstop = Stop diff --git a/Resources/Locale/en-US/kitchen/components/microwave-component.ftl b/Resources/Locale/en-US/kitchen/components/microwave-component.ftl index 0603b3c846..12346ee75d 100644 --- a/Resources/Locale/en-US/kitchen/components/microwave-component.ftl +++ b/Resources/Locale/en-US/kitchen/components/microwave-component.ftl @@ -9,6 +9,7 @@ microwave-component-suicide-multi-head-others-message = {$victim} is trying to c microwave-component-suicide-others-message = {$victim} is trying to cook their head! microwave-component-suicide-multi-head-message = You cook your heads! microwave-component-suicide-message = You cook your head! +microwave-component-upgrade-cook-time = cook time microwave-component-interact-full = It's full. microwave-component-interact-item-too-big = { CAPITALIZE(THE($item)) } is too big to fit in the microwave! diff --git a/Resources/Locale/en-US/kitchen/components/reagent-grinder-component.ftl b/Resources/Locale/en-US/kitchen/components/reagent-grinder-component.ftl index 8a3ca9eef8..3495128274 100644 --- a/Resources/Locale/en-US/kitchen/components/reagent-grinder-component.ftl +++ b/Resources/Locale/en-US/kitchen/components/reagent-grinder-component.ftl @@ -4,6 +4,9 @@ reagent-grinder-bound-user-interface-instant-button = INSTANT reagent-grinder-bound-user-interface-cook-time-label = COOK TIME reagent-grinder-component-cannot-put-entity-message = You can't put this in the reagent grinder! +reagent-grinder-component-upgrade-work-time = Work time +reagent-grinder-component-upgrade-storage = Storage + grinder-menu-title = All-In-One Grinder 3000 grinder-menu-grind-button = Grind grinder-menu-juice-button = Juice diff --git a/Resources/Locale/en-US/language/commands.ftl b/Resources/Locale/en-US/language/commands.ftl index 65959e3f28..ccbe2570c9 100644 --- a/Resources/Locale/en-US/language/commands.ftl +++ b/Resources/Locale/en-US/language/commands.ftl @@ -2,10 +2,10 @@ command-list-langs-desc = List languages your current entity can speak at the cu command-list-langs-help = Usage: {$command} command-saylang-desc = Send a message in a specific language. To choose a language, you can use either the name of the language, or its position in the list of languages. -command-saylang-help = Usage: {$command} . Example: {$command} GalacticCommon "Hello World!". Example: {$command} 1 "Hello World!" +command-saylang-help = Usage: {$command} . Example: {$command} TauCetiBasic "Hello World!". Example: {$command} 1 "Hello World!" command-language-select-desc = Select the currently spoken language of your entity. You can use either the name of the language, or its position in the list of languages. -command-language-select-help = Usage: {$command} . Example: {$command} 1. Example: {$command} GalacticCommon +command-language-select-help = Usage: {$command} . Example: {$command} 1. Example: {$command} TauCetiBasic command-language-spoken = Spoken: command-language-understood = Understood: @@ -18,14 +18,14 @@ command-language-invalid-language = The language {$id} does not exist or you can # toolshed command-description-language-add = Adds a new language to the piped entity. The two last arguments indicate whether it should be spoken/understood. Example: 'self language:add "Canilunzt" true true' -command-description-language-rm = Removes a language from the piped entity. Works similarly to language:add. Example: 'self language:rm "GalacticCommon" true true'. +command-description-language-rm = Removes a language from the piped entity. Works similarly to language:add. Example: 'self language:rm "TauCetiBasic" true true'. command-description-language-lsspoken = Lists all languages the entity can speak. Example: 'self language:lsspoken' command-description-language-lsunderstood = Lists all languages the entity can understand. Example: 'self language:lssunderstood' command-description-translator-addlang = Adds a new target language to the piped translator entity. See language:add for details. command-description-translator-rmlang = Removes a target language from the piped translator entity. See language:rm for details. -command-description-translator-addrequired = Adds a new required language to the piped translator entity. Example: 'ent 1234 translator:addrequired "GalacticCommon"' -command-description-translator-rmrequired = Removes a required language from the piped translator entity. Example: 'ent 1234 translator:rmrequired "GalacticCommon"' +command-description-translator-addrequired = Adds a new required language to the piped translator entity. Example: 'ent 1234 translator:addrequired "TauCetiBasic"' +command-description-translator-rmrequired = Removes a required language from the piped translator entity. Example: 'ent 1234 translator:rmrequired "TauCetiBasic"' command-description-translator-lsspoken = Lists all spoken languages for the piped translator entity. Example: 'ent 1234 translator:lsspoken' command-description-translator-lsunderstood = Lists all understood languages for the piped translator entity. Example: 'ent 1234 translator:lssunderstood' command-description-translator-lsrequired = Lists all required languages for the piped translator entity. Example: 'ent 1234 translator:lsrequired' diff --git a/Resources/Locale/en-US/language/languages.ftl b/Resources/Locale/en-US/language/languages.ftl index 68dc80f51d..935eef896a 100644 --- a/Resources/Locale/en-US/language/languages.ftl +++ b/Resources/Locale/en-US/language/languages.ftl @@ -1,9 +1,6 @@ language-Universal-name = Universal language-Universal-description = What are you? -language-GalacticCommon-name = Galactic common -language-GalacticCommon-description = The standard Galatic language, most commonly used for inter-species communications and legal work. - language-Bubblish-name = Bubblish language-Bubblish-description = The language of Slimes. Being a mixture of bubbling noises and pops it's very difficult to speak for humans without the use of mechanical aids. @@ -13,14 +10,47 @@ language-RootSpeak-description = The strange whistling-style language spoken by language-Nekomimetic-name = Nekomimetic language-Nekomimetic-description = To the casual observer, this language is an incomprehensible mess of broken Japanese. To the Felinids and Oni, it's somehow comprehensible. -language-Draconic-name = Draconic -language-Draconic-description = The common language of lizard-people, composed of sibilant hisses and rattles. +language-Draconic-name = Sinta'Unathi +language-Draconic-description = + The common language of Moghes - composed of sibilant hisses and rattles. Spoken natively by Unathi. + +language-Azaziba-name = Sinta'Azaziba +language-Azaziba-description = + A language of Moghes consisting of a combination of spoken word and gesticulation. + While waning since Moghes entered the galactic stage - it enjoys popular use by Unathi that never fell to the Hegemony's cultural dominance. language-SolCommon-name = Sol common -language-SolCommon-description = The language common to species from the Sol System. +language-SolCommon-description = + With its roots in Mandarin Chinese - Common evolved as the official language of the Sol Alliance - with officials working to tie it together with a common tongue. + It's spoken by state officials - taught in schools - and spoken by those who either feel a sense of national pride in the Alliance or otherwise fell sway to the culture. + +language-TauCetiBasic-name = Tau-Ceti Basic +language-TauCetiBasic-description = + A spiritual successor of Esperanto, established in 2404 in Tau Ceti by Ceti intellectuals. + Its unique, fully customized alphabet and structure allow it to be spoken even by most alien species. + It's the official language of Tau Ceti and has growing traction in diplomatic circles and Universalists across human space. + +language-Tradeband-name = Tradeband +language-Tradeband-description = + Descended from latin and romance languages of old Earth - Tradeband remains the main tongue of the upper class of humanity. + The language sounds elegant and well structured to most ears. It remains in popular use with traders - diplomats - and those seeking to hold onto a piece of a romantic past. + +language-Freespeak-name = Freespeak +language-Freespeak-description = + A language of renegades and frontiersmen descending from various languages from Earth-- like Hindi, + combined into a multi-rooted jumble that sounds incoherent or even barbarian to non-native speakers. + This language is the only common cultural identity for humans in the frontier. Speaking this language in itself boldly declares the speaker a free spirit. + It is often called 'Gutter' by Alliance citizens. + +language-Elyran-name = Elyran Standard +language-Elyran-description = + Elyran Standard is the official tongue of the Republic of Elyra. Constructed using elements of Farsi - Arabic - and Turkish. + Influence from all three of these languages can be seen throughout its grammar and vocabulary. language-Canilunzt-name = Canilunzt -language-Canilunzt-description = The guttural language spoken and utilized by the inhabitants of the Vazzend system, composed of growls, barks, yaps, and heavy utilization of ears and tail movements. Vulpkanin speak this language with ease. +language-Canilunzt-description = + The guttural language spoken and utilized by the inhabitants of the Vazzend system, + composed of growls, barks, yaps, and heavy utilization of ears and tail movements. Vulpkanin speak this language with ease. language-Moffic-name = Moffic language-Moffic-description = The language of the mothpeople borders on complete unintelligibility. @@ -28,8 +58,17 @@ language-Moffic-description = The language of the mothpeople borders on complete language-RobotTalk-name = RobotTalk language-RobotTalk-description = A language consisting of harsh binary chirps, whistles, hisses, and whines. Organic tongues cannot speak it without aid from special translators. -language-Sign-name = Galactic Sign Language -language-Sign-description = GSL for short, this sign language is prevalent among mute and deaf people. +language-Sign-name = Tau-Ceti Basic Sign Language +language-Sign-description = TCB-SL for short, this sign language is prevalent among mute and deaf people. + +language-Marish-name = Marish +language-Marish-description = An inherently empathetic language, conveying emotions with a single word; spoken effortlessly by Shadowkins, though nearly impossible to learn or replicate. + +language-ValyrianStandard-name = Valyrian Standard +language-ValyrianStandard-description = + A language descended from eastern european languages of old earth - Valyrian Standard is the commonly spoken tongue of Harpies brought up on their homeworld of Valyrian 4b + It is rarely spoken outside of the worlds of its native speakers, and has in modern times been supplanted by the 'Conlangs of the Sol Alliance. + Its speakers are those who wish to uphold the traditions and beliefs of ancient peoples from before the colonial era. language-Cat-name = Cat language-Cat-description = Meow diff --git a/Resources/Locale/en-US/loadouts/categories.ftl b/Resources/Locale/en-US/loadouts/categories.ftl index 9770bd8baf..778d0869b7 100644 --- a/Resources/Locale/en-US/loadouts/categories.ftl +++ b/Resources/Locale/en-US/loadouts/categories.ftl @@ -2,11 +2,34 @@ loadout-category-Uncategorized = Uncategorized loadout-category-Accessories = Accessories +loadout-category-Backpacks = Backpacks loadout-category-Eyes = Eyes loadout-category-Hands = Hands loadout-category-Head = Head loadout-category-Items = Items loadout-category-Jobs = Jobs +loadout-category-JobsAUncategorized = Uncategorized +loadout-category-JobsCargo = Logistics +loadout-category-JobsCommand = Command +loadout-category-JobsCommandAUncategorized = Uncategorized +loadout-category-JobsCommandCaptain = Captain +loadout-category-JobsCommandCE = Chief Engineer +loadout-category-JobsCommandCMO = Chief Medical Officer +loadout-category-JobsCommandHOP = Head of Personnel +loadout-category-JobsCommandHOS = Head of Security +loadout-category-JobsCommandQM = Logistics Officer +loadout-category-JobsCommandRD = Mystagogue +loadout-category-JobsEngineering = Engineering +loadout-category-JobsMedical = Medical +loadout-category-JobsScience = Epistemics +loadout-category-JobsSecurity = Security +loadout-category-JobsService = Service +loadout-category-JobsServiceUncategorized = Uncategorized +loadout-category-JobsServiceBartender = Bartender +loadout-category-JobsServiceBotanist = Botanist +loadout-category-JobsServiceChef = Chef +loadout-category-JobsServiceJanitor = Janitor +loadout-category-JobsServiceMusician = Musician loadout-category-Mask = Mask loadout-category-Neck = Neck loadout-category-Outer = Outer diff --git a/Resources/Locale/en-US/loadouts/itemgroups.ftl b/Resources/Locale/en-US/loadouts/itemgroups.ftl new file mode 100644 index 0000000000..dba4cf72a9 --- /dev/null +++ b/Resources/Locale/en-US/loadouts/itemgroups.ftl @@ -0,0 +1,105 @@ +# This list is sorted Mixed Alphabetically with Generic always being placed first, Departments alphabetically, Items Groups Alphabetically, and Jobs Alphabetically after all Department items +# Generic - Clothing +character-item-group-LoadoutBackpacks = Civilian Backpacks +character-item-group-LoadoutEyes = Civilian Eyewear +character-item-group-LoadoutGloves = Civilian Gloves +character-item-group-LoadoutHead = Civilian Headgear +character-item-group-LoadoutMasks = Civilian Masks +character-item-group-LoadoutNeck = Civilian Neckwear +character-item-group-LoadoutOuter = Civilian Outerwear +character-item-group-LoadoutShoes = Civilian Shoes +character-item-group-LoadoutUniformsCivilian = Civilian Uniforms + +# Generic - Items +character-item-group-LoadoutAirTank = Emergency Air Tanks +character-item-group-LoadoutLighters = Lighters +character-item-group-LoadoutInstrumentsAny = Musical Instruments (Non-Musician) +character-item-group-LoadoutSmokes = Smokeables +character-item-group-LoadoutBoxKits = Survival Kits +character-item-group-LoadoutWritables = Writing Tools + +# Cargo +character-item-group-LoadoutNeckCargo = Logistics Neckwear +character-item-group-LoadoutOuterCargo = Logistics Outerwear +character-item-group-LoadoutShoesCargo = Logistics Shoes + +# Engineering +character-item-group-LoadoutEyesEngineering = Engineering Eyewear +character-item-group-LoadoutHeadEngineering = Engineering Headgear +character-item-group-LoadoutOuterEngineering = Engineering Outerwear +character-item-group-LoadoutUniformsEngineering = Engineering Uniforms + +# Epistemics +character-item-group-LoadoutEyesScience = Epistemics Eyewear +character-item-group-LoadoutGlovesScience = Epistemics Gloves +character-item-group-LoadoutHeadScience = Epistemics Headgear +character-item-group-LoadoutMaskScience = Epistemics Masks +character-item-group-LoadoutNeckScience = Epistemics Neckwear +character-item-group-LoadoutOuterScience = Epistemics Outerwear +character-item-group-LoadoutShoesScience = Epistemics Shoes +character-item-group-LoadoutUniformsScience = Epistemics Uniforms + +# Epistemics - Cataloguer +character-item-group-LoadoutCataloguerUniforms = Cataloguer Uniforms + +# Epistemics - Chaplain +character-item-group-LoadoutChaplainUniforms = Chaplain Uniforms +character-item-group-LoadoutChaplainEquipment = Chaplain Equipment + +# Medical +character-item-group-LoadoutEyesMedical = Medical Eyewear +character-item-group-LoadoutGlovesMedical = Medical Gloves +character-item-group-LoadoutHeadMedical = Medical Headgear +character-item-group-LoadoutNeckMedical = Medical Neckwear +character-item-group-LoadoutOuterMedical = Medical Outerwear +character-item-group-LoadoutShoesMedical = Medical Shoes +character-item-group-LoadoutUniformsMedical = Medical Uniforms + +# Security +character-item-group-LoadoutBackSecurity = Security Backpacks +character-item-group-LoadoutBeltSecurity = Security Belts +character-item-group-LoadoutEquipmentSecurity = Security Equipment +character-item-group-LoadoutEyesSecurity = Security Eyewear +character-item-group-LoadoutGlovesSecurity = Security Gloves +character-item-group-LoadoutHeadSecurity = Security Headgear +character-item-group-LoadoutMaskSecurity = Security Masks +character-item-group-LoadoutNeckSecurity = Security Neckwear +character-item-group-LoadoutOuterSecurity = Security Outerwear +character-item-group-LoadoutShoesSecurity = Security Shoes +character-item-group-LoadoutUniformsSecurity = Security Uniforms +character-item-group-LoadoutWeaponSecurity = Security Duty Weapon +character-item-group-LoadoutHoSWeapon = Head of Security's Antique Weapon Collection + +# Service +character-item-group-LoadoutEquipmentService = Service Equipment +character-item-group-LoadoutHeadService = Service Headgear +character-item-group-LoadoutMaskService = Service Masks +character-item-group-LoadoutNeckService = Service Neckwear +character-item-group-LoadoutOuterService = Service Outerwear +character-item-group-LoadoutShoesService = Service Shoes +character-item-group-LoadoutUniformsService = Service Uniforms + +# Service - Bartender +character-item-group-LoadoutBartenderAmmo = Bartender Ammo +character-item-group-LoadoutBartenderHead = Bartender Headgear +character-item-group-LoadoutBartenderOuterwear = Bartender Outerwear +character-item-group-LoadoutBartenderUniforms = Bartender Uniforms +character-item-group-LoadoutBartenderWeapon = Bartender Weapon + +# Service - Botanist +character-item-group-LoadoutBotanistUniforms = Botanist Uniforms + +# Service - Chef +character-item-group-LoadoutChefHead = Chef Headgear +character-item-group-LoadoutChefOuter = Chef Outerwear +character-item-group-LoadoutChefUniforms = Chef Uniforms + +# Service - Janitor +character-item-group-LoadoutJanitorUniforms = Janitor Uniforms + +# Service - Musician +character-item-group-LoadoutMusicianInstruments = Musician Instruments + +# Traits - Languages +character-item-group-TraitsLanguagesBasic = Basic Languages +character-item-group-TraitsAccents = Accents diff --git a/Resources/Locale/en-US/loadouts/jobs/heads/captain.ftl b/Resources/Locale/en-US/loadouts/jobs/heads/captain.ftl index af5b90dd8d..27069b6ff8 100644 --- a/Resources/Locale/en-US/loadouts/jobs/heads/captain.ftl +++ b/Resources/Locale/en-US/loadouts/jobs/heads/captain.ftl @@ -7,6 +7,6 @@ loadout-description-LoadoutCommandCapOuterWinter = A warm coat for the cold of s loadout-description-LoadoutCommandCapGloves = The gloves of the captain. They are very nice gloves. loadout-description-LoadoutCommandCapHat = The hat of the captain. It is a very nice hat. loadout-description-LoadoutCommandCapHatCapcap = The Captain's cap, pretty nice. -loadout-description-LoadoutCommandCapHat = The Captain's beret, very nice. +loadout-description-LoadoutCommandCapHatBeret = The Captain's beret, very nice. loadout-description-LoadoutCommandCapMaskGas = Why would the captain need this? I don't know, but it looks cool. loadout-description-LoadoutCommandCapItemDrinkFlask = The finest of flasks, for the finest of drinks. diff --git a/Resources/Locale/en-US/loadouts/jobs/security.ftl b/Resources/Locale/en-US/loadouts/jobs/security.ftl index c3d78d7813..442dc65326 100644 --- a/Resources/Locale/en-US/loadouts/jobs/security.ftl +++ b/Resources/Locale/en-US/loadouts/jobs/security.ftl @@ -1,3 +1,35 @@ loadout-description-LoadoutSecurityUniformJumpskirtSenior = A skirt fit for the best of the best. loadout-description-LoadoutSecurityUniformJumpsuitSenior = A suit fit for the best of the best. loadout-description-LoadoutSecurityShoesJackboots = A really nice, heavy, pair of black boots. +# Equipment +loadout-name-LoadoutMagazinePistolSpare = pistol magazine (.35 auto, spare) +loadout-name-LoadoutMagazinePistolNonLethalSpare = pistol magazine (.35 auto rubber, spare) +loadout-name-LoadoutSpeedLoaderMagnumSpare = speed loader (.45 magnum, spare) +loadout-name-LoadoutSpeedLoaderMagnumRubberSpare = speed loader (.45 magnum rubber, spare) +loadout-name-LoadoutMagazineMagnumSpare = pistol magazine (.45 magnum, spare) +loadout-name-LoadoutMagazineMagnumRubberSpare = pistol magazine (.45 magnum rubber, spare) + +# Duty Weapons +loadout-name-LoadoutSecurityMk58 = Mk58 (lethal) +loadout-name-LoadoutSecurityMk58NonLethal = Mk58 (non-lethal) +loadout-name-LoadoutSecurityRevolver = Inspector (lethal) +loadout-name-LoadoutSecurityRevolverNonLethal = Inspector (non-lethal) +loadout-name-LoadoutSecurityRevolverDeckard = Deckard (lethal) +loadout-name-LoadoutSecurityRevolverDeckardNonLethal = Deckard (non-lethal) +loadout-name-LoadoutSecurityPistolN1984 = N1984 (lethal) +loadout-name-LoadoutSecurityPistolN1984NonLethal = N1984 (non-lethal) +loadout-name-LoadoutSecurityPistolViper = Viper (lethal) +loadout-name-LoadoutSecurityPistolViperNonLethal = Viper (non-lethal) +loadout-name-LoadoutSecurityPistolViperWood = Viper (wood furniture, lethal) +loadout-name-LoadoutSecurityEnergyGun = Mini Energy Gun +loadout-name-LoadoutSecurityEnergyGunPistol = Energy Pistol +loadout-name-LoadoutSecurityPistolPollock = Pollock (lethal) +loadout-name-LoadoutSecurityPistolPollockNonlethal = Pollock (non-lethal) +loadout-name-LoadoutSecurityRevolverSnub = Snub-nose .45 (lethal) +loadout-name-LoadoutSecurityRevolverSnubNonlethal = Snub-nose .45 (non-lethal) +loadout-name-LoadoutSecurityRevolverK38Master = K-38 Master (lethal) +loadout-name-LoadoutSecurityRevolverK38MasterNonlethal = K-38 Master (non-lethal) +loadout-name-LoadoutSecurityRevolverFitz = Fitz (lethal) +loadout-name-LoadoutSecurityRevolverFitzNonlethal = Fitz (non-lethal) +loadout-name-LoadoutSecurityRevolverPython = Python (lethal) +loadout-name-LoadoutSecurityRevolverPythonNonlethal = Python (non-lethal) diff --git a/Resources/Locale/en-US/lock/batteryslotrequireslock-component.ftl b/Resources/Locale/en-US/lock/batteryslotrequireslock-component.ftl new file mode 100644 index 0000000000..0c75b4a920 --- /dev/null +++ b/Resources/Locale/en-US/lock/batteryslotrequireslock-component.ftl @@ -0,0 +1 @@ +batteryslotrequireslock-component-alert-owner = {$user} is messing with your maintenance panel! \ No newline at end of file diff --git a/Resources/Locale/en-US/machine-linking/transmitter_ports.ftl b/Resources/Locale/en-US/machine-linking/transmitter_ports.ftl index c685cc8fb7..1d879fcee9 100644 --- a/Resources/Locale/en-US/machine-linking/transmitter_ports.ftl +++ b/Resources/Locale/en-US/machine-linking/transmitter_ports.ftl @@ -19,6 +19,9 @@ signal-port-description-right = This port is invoked whenever the lever is moved signal-port-name-doorstatus = Door status signal-port-description-doorstatus = This port is invoked with HIGH when the door opens and LOW when the door finishes closing. +signal-port-name-dockstatus = Dock status +signal-port-description-dockstatus = This port is invoked with HIGH when docked and LOW when undocked. + signal-port-name-middle = Middle signal-port-description-middle = This port is invoked whenever the lever is moved to the neutral position. diff --git a/Resources/Locale/en-US/machine/machine.ftl b/Resources/Locale/en-US/machine/machine.ftl index ce8873df6f..2605950516 100644 --- a/Resources/Locale/en-US/machine/machine.ftl +++ b/Resources/Locale/en-US/machine/machine.ftl @@ -13,6 +13,11 @@ machine-part-name-manipulator = Manipulator machine-part-name-matter-bin = Matter Bin machine-part-name-power-cell = Power Cell +upgrade-power-draw = power draw +upgrade-max-charge = max charge +upgrade-power-supply = power supply +upgrade-power-supply-ramping = power ramp rate + two-way-lever-left = push left two-way-lever-right = push right two-way-lever-cant = can't push the lever that way! diff --git a/Resources/Locale/en-US/magic/magic.ftl b/Resources/Locale/en-US/magic/magic.ftl new file mode 100644 index 0000000000..4c8a5fc51d --- /dev/null +++ b/Resources/Locale/en-US/magic/magic.ftl @@ -0,0 +1 @@ +spell-requirements-failed = Missing requirements to cast this spell! diff --git a/Resources/Locale/en-US/mail/commands.ftl b/Resources/Locale/en-US/mail/commands.ftl new file mode 100644 index 0000000000..1f471bb7a5 --- /dev/null +++ b/Resources/Locale/en-US/mail/commands.ftl @@ -0,0 +1,20 @@ +# Mailto +command-mailto-description = Queue a parcel to be delivered to an entity. Example usage: `mailto 1234 5678 false false`. The target container's contents will be transferred to an actual mail parcel. +command-mailto-help = Usage: {$command} [is-fragile: true or false] [is-priority: true or false] [is-large: true or false, optional] +command-mailto-no-mailreceiver = Target recipient entity does not have a {$requiredComponent}. +command-mailto-no-blankmail = The {$blankMail} prototype doesn't exist. Something is very wrong. Contact a programmer. +command-mailto-bogus-mail = {$blankMail} did not have {$requiredMailComponent}. Something is very wrong. Contact a programmer. +command-mailto-invalid-container = Target container entity does not have a {$requiredContainer} container. +command-mailto-unable-to-receive = Target recipient entity was unable to be setup for receiving mail. ID may be missing. +command-mailto-no-teleporter-found = Target recipient entity was unable to be matched to any station's mail teleporter. Recipient may be off-station. +command-mailto-success = Success! Mail parcel has been queued for next teleport in {$timeToTeleport} seconds. + +# Mailnow +command-mailnow = Force all mail teleporters to deliver another round of mail as soon as possible. This will not bypass the undelivered mail limit. +command-mailnow-help = Usage: {$command} +command-mailnow-success = Success! All mail teleporters will be delivering another round of mail soon. + +# Mailtestbulk +command-mailtestbulk = Sends one of each type of parcel to a given mail teleporter. Implicitly calls mailnow. +command-mailtestbulk-help = Usage: {$command} +command-mailtestbulk-success = Success! All mail teleporters will be delivering another round of mail soon. diff --git a/Resources/Locale/en-US/mail/mail.ftl b/Resources/Locale/en-US/mail/mail.ftl new file mode 100644 index 0000000000..5a27737732 --- /dev/null +++ b/Resources/Locale/en-US/mail/mail.ftl @@ -0,0 +1,23 @@ +mail-recipient-mismatch = Recipient name or job does not match. +mail-invalid-access = Recipient name and job match, but access isn't as expected. +mail-locked = The anti-tamper lock hasn't been removed. Tap the recipient's ID. +mail-desc-far = A parcel of mail. You can't make out who it's addressed to from this distance. +mail-desc-close = A parcel of mail addressed to {CAPITALIZE($name)}, {$job}. +mail-desc-fragile = It has a [color=red]red fragile label[/color]. +mail-desc-priority = The anti-tamper lock's [color=yellow]yellow priority tape[/color] is active. Better deliver it on time! +mail-desc-priority-inactive = The anti-tamper lock's [color=#886600]yellow priority tape[/color] is inactive. +mail-unlocked = Anti-tamper system unlocked. +mail-unlocked-by-emag = Anti-tamper system *BZZT*. +mail-unlocked-reward = Anti-tamper system unlocked. {$bounty} spesos have been added to logistics' account. +mail-penalty-lock = ANTI-TAMPER LOCK BROKEN. LOGISTICS BANK ACCOUNT PENALIZED BY {$credits} SPESOS. +mail-penalty-fragile = INTEGRITY COMPROMISED. LOGISTICS BANK ACCOUNT PENALIZED BY {$credits} SPESOS. +mail-penalty-expired = DELIVERY PAST DUE. LOGISTICS BANK ACCOUNT PENALIZED BY {$credits} SPESOS. +mail-item-name-unaddressed = mail +mail-item-name-addressed = mail ({$recipient}) + +mail-large-item-name-unaddressed = package +mail-large-item-name-addressed = package ({$recipient}) +mail-large-desc-far = A large package. +mail-large-desc-close = A large package addressed to {CAPITALIZE($name)}, {$job}. + + diff --git a/Resources/Locale/en-US/markings/Kemonomimi_tails.ftl b/Resources/Locale/en-US/markings/Kemonomimi_tails.ftl new file mode 100644 index 0000000000..039aa699e0 --- /dev/null +++ b/Resources/Locale/en-US/markings/Kemonomimi_tails.ftl @@ -0,0 +1,30 @@ +marking-BunnyTail = Bunny Tail +marking-BunnyTail-bunnytail = Tail + +marking-DobleFur = Doble Tail +marking-DobleFur-doblefurtailtone1 = Outer +marking-DobleFur-doblefurtailtone2 = Inner + +marking-FluffyTail = Fluffy Tail +marking-FluffyTail-fluffytail = Tail + +marking-FoxNineTails = Nine Tails +marking-FoxNineTails-foxninetailstone1 = Inner +marking-FoxNineTails-foxninetailstone2 = Outer + +marking-FoxThreeTails = Three Tails +marking-FoxThreeTails-foxthreetailstone1 = Inner +marking-FoxThreeTails-foxthreetailstone2 = Outer + +marking-HorseTailCulty = Culty Horse Tail +marking-HorseTailCulty-horsetailculty = Tail + +marking-HorseTailLong = Long Horse Tail +marking-HorseTailLong-horsetaillong = Tail + +marking-SharkTail = Shark Tail +marking-SharkTail-sharktail = Tail + +marking-TasselTail = Tassel Tail +marking-TasselTail-tasseltailtone1 = Inner +marking-TasselTail-tasseltailtone2 = Outer diff --git a/Resources/Locale/en-US/markings/harpy.ftl b/Resources/Locale/en-US/markings/harpy.ftl index 724207c3ef..36ae7dd308 100644 --- a/Resources/Locale/en-US/markings/harpy.ftl +++ b/Resources/Locale/en-US/markings/harpy.ftl @@ -81,7 +81,7 @@ marking-HarpyTailForkedLong = Long Forked Tail (Whitescale) marking-HarpyTailForkedLong-forked_long = Tail marking-HarpyTailSwallow = Swallow Tail (Whitescale) -marking-HarpyTailForkedLong-forked_long = Tail +marking-HarpyTailSwallow-swallow_tail = Tail marking-HarpyChestDefault = Wing & Groin Under-Clothes marking-HarpyChestDefault-upper = Wing Under-Clothes diff --git a/Resources/Locale/en-US/markings/kemonomimi_ears.ftl b/Resources/Locale/en-US/markings/kemonomimi_ears.ftl new file mode 100644 index 0000000000..e4b0d631e7 --- /dev/null +++ b/Resources/Locale/en-US/markings/kemonomimi_ears.ftl @@ -0,0 +1,13 @@ +marking-BunnyEars = Bunny Ears +marking-BunnyEars-bunnyearstone1 = Outer ear +marking-BunnyEars-bunnyearstone2 = Inner ear + +marking-BullishHorns = Bull Horns +marking-BullishHorns-bullishhorns = Horns + +marking-FoxEars = Fox Ears +marking-FoxEars-foxears = Ears + +marking-GoatHorns = Goat Ears & Horns +marking-GoatHorns-goathornstone1 = Ears +marking-GoatHorns-goathornstone2 = Horns diff --git a/Resources/Locale/en-US/markings/reptilian.ftl b/Resources/Locale/en-US/markings/reptilian.ftl index cfc44a4ba2..470af07361 100644 --- a/Resources/Locale/en-US/markings/reptilian.ftl +++ b/Resources/Locale/en-US/markings/reptilian.ftl @@ -68,6 +68,9 @@ marking-LizardFrillsDivinity = Lizard Frills (Divinity) marking-LizardFrillsBig-frills_big = Lizard Frills (Big) marking-LizardFrillsBig = Lizard Frills (Big) +marking-LizardFrillsNeckfull-frills_neckfull = Lizard Frills (Neckfull) +marking-LizardFrillsNeckfull = Lizard Frills (Neckfull) + marking-LizardHornsDouble-horns_double = Lizard Horns (Double) marking-LizardHornsDouble = Lizard Horns (Double) @@ -100,4 +103,8 @@ marking-LizardChestUnderbelly-body_underbelly = Lizard Chest (Underbelly) marking-LizardChestUnderbelly = Lizard Chest (Underbelly) marking-LizardChestBackspikes-body_backspikes = Lizard Back spikes (Four) -marking-LizardChestBackspikes = Lizard Back spikes (Four) \ No newline at end of file +marking-LizardChestBackspikes = Lizard Back spikes (Four) + +marking-LizardSnoutSplotch = Lizard Snout (Splotch) +marking-LizardSnoutSplotch-snout_splotch_primary = Muzzle +marking-LizardSnoutSplotch-snout_splotch_secondary = Snoot diff --git a/Resources/Locale/en-US/markings/shadowkin.ftl b/Resources/Locale/en-US/markings/shadowkin.ftl new file mode 100644 index 0000000000..5ad1f09963 --- /dev/null +++ b/Resources/Locale/en-US/markings/shadowkin.ftl @@ -0,0 +1,7 @@ +marking-EyesShadowkin = Shadowkin + +marking-TailShadowkin = Shadowkin +marking-TailShadowkinBig = Shadowkin (Big) +marking-TailShadowkinShorter = Shadowkin (Short) +marking-TailShadowkinMedium = Shadowkin (Medium) +marking-TailShadowkinBigFluff = Shadowkin (Big and Fluffy) \ No newline at end of file diff --git a/Resources/Locale/en-US/markings/vox_tattoos.ftl b/Resources/Locale/en-US/markings/vox_tattoos.ftl new file mode 100644 index 0000000000..f7f3c7292c --- /dev/null +++ b/Resources/Locale/en-US/markings/vox_tattoos.ftl @@ -0,0 +1,11 @@ +marking-TattooVoxHeartLeftArm-heart_l_arm = Vox Left Arm Tattoo (Heart) +marking-TattooVoxHeartLeftArm = Vox Left Arm Tattoo (Heart) + +marking-TattooVoxHeartRightArm-heart_r_arm = Vox Right Arm Tattoo (Heart) +marking-TattooVoxHeartRightArm = Vox Right Arm Tattoo (Heart) + +marking-TattooVoxHiveChest-hive_s = Vox Chest Tattoo (hive) +marking-TattooVoxHiveChest = Vox Chest Tattoo (hive) + +marking-TattooVoxNightlingChest-nightling_s = Vox Chest Tattoo (nightling) +marking-TattooVoxNightlingChest = Vox Chest Tattoo (nightling) diff --git a/Resources/Locale/en-US/materials/materials.ftl b/Resources/Locale/en-US/materials/materials.ftl index dca520b5b4..b213e7d1e9 100644 --- a/Resources/Locale/en-US/materials/materials.ftl +++ b/Resources/Locale/en-US/materials/materials.ftl @@ -25,6 +25,8 @@ materials-meat = meat materials-web = silk materials-bones = bone materials-coal = coal +materials-bluespace = bluespace +materials-normality = normality # Ores materials-raw-iron = raw iron @@ -35,3 +37,8 @@ materials-raw-plasma = raw plasma materials-raw-uranium = raw uranium materials-raw-bananium = raw bananium materials-raw-salt = raw salt +materials-raw-bluespace = raw bluespace +materials-raw-normality = raw normality + +# Material Reclaimer +material-reclaimer-upgrade-process-rate = process rate diff --git a/Resources/Locale/en-US/mech/mech.ftl b/Resources/Locale/en-US/mech/mech.ftl index 19f570a2a1..9d4f7ef0e0 100644 --- a/Resources/Locale/en-US/mech/mech.ftl +++ b/Resources/Locale/en-US/mech/mech.ftl @@ -13,6 +13,7 @@ mech-menu-title = mech control panel mech-integrity-display = Integrity: {$amount}% mech-energy-display = Energy: {$amount}% +mech-energy-missing = Energy: MISSING mech-slot-display = Open Slots: {$amount} mech-no-enter = You cannot pilot this. diff --git a/Resources/Locale/en-US/medical/components/biomass-reclaimer-component.ftl b/Resources/Locale/en-US/medical/components/biomass-reclaimer-component.ftl index 443429c1ef..0c0b8faf59 100644 --- a/Resources/Locale/en-US/medical/components/biomass-reclaimer-component.ftl +++ b/Resources/Locale/en-US/medical/components/biomass-reclaimer-component.ftl @@ -1 +1,4 @@ biomass-reclaimer-suicide-others = {CAPITALIZE(THE($victim))} threw themselves into the biomass reclaimer! + +biomass-reclaimer-component-upgrade-speed = speed +biomass-reclaimer-component-upgrade-biomass-yield = biomass yield diff --git a/Resources/Locale/en-US/medical/components/cloning-pod-component.ftl b/Resources/Locale/en-US/medical/components/cloning-pod-component.ftl index b222d707a0..e92ac86a1e 100644 --- a/Resources/Locale/en-US/medical/components/cloning-pod-component.ftl +++ b/Resources/Locale/en-US/medical/components/cloning-pod-component.ftl @@ -1,3 +1,5 @@ cloning-pod-biomass = It currently has [color=red]{$number}[/color] units of biomass. +cloning-pod-component-upgrade-speed = cloning speed +cloning-pod-component-upgrade-biomass-requirement = biomass requirement cloning-pod-component-upgrade-emag-requirement = The card zaps something inside the cloning pod. diff --git a/Resources/Locale/en-US/medical/components/health-analyzer-component.ftl b/Resources/Locale/en-US/medical/components/health-analyzer-component.ftl index 648db3f4eb..49f3019bce 100644 --- a/Resources/Locale/en-US/medical/components/health-analyzer-component.ftl +++ b/Resources/Locale/en-US/medical/components/health-analyzer-component.ftl @@ -1,18 +1,21 @@ health-analyzer-window-no-patient-data-text = No patient data. -health-analyzer-window-entity-unknown-text = unknown -health-analyzer-window-entity-health-text = {$entityName}'s health: -health-analyzer-window-entity-temperature-text = Temperature: {$temperature} -health-analyzer-window-entity-blood-level-text = Blood Level: {$bloodLevel} -health-analyzer-window-entity-bleeding-text = Patient is bleeding! -health-analyzer-window-entity-damage-total-text = Total Damage: {$amount} +health-analyzer-window-entity-unknown-text = Unknown +health-analyzer-window-entity-unknown-species-text = Non-Humanoid +health-analyzer-window-entity-unknown-value-text = N/A + +health-analyzer-window-entity-alive-text = Alive +health-analyzer-window-entity-dead-text = Dead +health-analyzer-window-entity-critical-text = Critical + +health-analyzer-window-entity-temperature-text = Temperature: +health-analyzer-window-entity-blood-level-text = Blood Level: +health-analyzer-window-entity-status-text = Status: +health-analyzer-window-entity-damage-total-text = Total Damage: + health-analyzer-window-damage-group-text = {$damageGroup}: {$amount} health-analyzer-window-damage-type-text = {$damageType}: {$amount} health-analyzer-window-damage-type-duplicate-text = {$damageType}: {$amount} (duplicate) -health-analyzer-window-scan-mode-text = Scan Mode: -health-analyzer-window-scan-mode-active = ACTIVE -health-analyzer-window-scan-mode-inactive = INACTIVE - health-analyzer-window-damage-group-Brute = Brute health-analyzer-window-damage-type-Blunt = Blunt health-analyzer-window-damage-type-Slash = Slash @@ -36,3 +39,13 @@ health-analyzer-window-damage-group-Genetic = Genetic health-analyzer-window-damage-type-Cellular = Cellular health-analyzer-window-malnutrition = Severely malnourished + +health-analyzer-window-entity-unrevivable-text = Unique body composition detected! Patient can not be resuscitated by normal means! +health-analyzer-window-entity-bleeding-text = Patient is bleeding! + +health-analyzer-window-scan-mode-text = Scan Mode: +health-analyzer-window-scan-mode-active = Active +health-analyzer-window-scan-mode-inactive = Inactive + +health-analyzer-popup-scan-target = {CAPITALIZE(THE($user))} is trying to scan you! +health-analyzer-window-return-button-text = < Return diff --git a/Resources/Locale/en-US/medical/components/medical-scanner-component.ftl b/Resources/Locale/en-US/medical/components/medical-scanner-component.ftl index c4b1942654..da4dc7a384 100644 --- a/Resources/Locale/en-US/medical/components/medical-scanner-component.ftl +++ b/Resources/Locale/en-US/medical/components/medical-scanner-component.ftl @@ -2,3 +2,5 @@ medical-scanner-verb-enter = Enter medical-scanner-verb-noun-occupant = occupant + +medical-scanner-upgrade-cloning = Cloning fail chance diff --git a/Resources/Locale/en-US/medical/components/penlight.ftl b/Resources/Locale/en-US/medical/components/penlight.ftl index f0639ad738..c8a5d66f5d 100644 --- a/Resources/Locale/en-US/medical/components/penlight.ftl +++ b/Resources/Locale/en-US/medical/components/penlight.ftl @@ -1,11 +1,12 @@ penlight-off = The pen light is off. +penlight-cannot-examine-self = You cannot examine your own eyes. pen-light-exam-title = Pen Light pen-light-window-entity-eyes-text = {$entityName}'s conditions: pen-light-window-no-patient-data-text = No patient data. pen-light-window-entity-unknown-text = unknown pen-light-exam-blind-text = The patient's eyes are glassy and unfocused. They can't follow the light at all. -pen-light-exam-drunk-text = The patient's eyes are slow to follow the light, droopy. -pen-light-exam-eyedamage-text = The patient's eyes are partially focused, though they struggle to look at the light for too long. +pen-light-exam-drunk-text = There's a clear delay between moving the light and the patient's eyes following. +pen-light-exam-eyedamage-text = The patient's eyes have dark spots within the pupil, evident when the light is shone in them. pen-light-exam-hallucinating-text = The patient's eyes are wandering around, with dilated pupils. They don't focus on the light. pen-light-exam-healthy-text = The patient follows the light perfectly with no stuttering. \ No newline at end of file diff --git a/Resources/Locale/en-US/medical/components/stasis-bed-component.ftl b/Resources/Locale/en-US/medical/components/stasis-bed-component.ftl new file mode 100644 index 0000000000..2d8a18c263 --- /dev/null +++ b/Resources/Locale/en-US/medical/components/stasis-bed-component.ftl @@ -0,0 +1 @@ +stasis-bed-component-upgrade-stasis = stasis effect diff --git a/Resources/Locale/en-US/metabolism/metabolism-groups.ftl b/Resources/Locale/en-US/metabolism/metabolism-groups.ftl new file mode 100644 index 0000000000..404d0fc786 --- /dev/null +++ b/Resources/Locale/en-US/metabolism/metabolism-groups.ftl @@ -0,0 +1,7 @@ +metabolism-group-poison = Poison +metabolism-group-medicine = Medicine +metabolism-group-narcotic = Narcotic +metabolism-group-alcohol = Alcohol +metabolism-group-food = Food +metabolism-group-drink = Drink +metabolism-group-gas = Gas diff --git a/Resources/Locale/en-US/metabolism/metabolizer-types.ftl b/Resources/Locale/en-US/metabolism/metabolizer-types.ftl new file mode 100644 index 0000000000..d0f57e2bc0 --- /dev/null +++ b/Resources/Locale/en-US/metabolism/metabolizer-types.ftl @@ -0,0 +1,14 @@ +metabolizer-type-animal = Animal +metabolizer-type-bloodsucker = Bloodsucker +metabolizer-type-dragon = Dragon +metabolizer-type-human = Human +metabolizer-type-slime = Slime +metabolizer-type-vox = Vox +metabolizer-type-rat = Rat +metabolizer-type-plant = Plant +metabolizer-type-dwarf = Dwarf +metabolizer-type-moth = Moth +metabolizer-type-arachnid = Arachnid +metabolizer-type-vampiric = Vampiric +metabolizer-type-liquorlifeline = Liquor Lifeline +metabolizer-type-shadowkin = Shadowkin diff --git a/Resources/Locale/en-US/mood/mood-alerts.ftl b/Resources/Locale/en-US/mood/mood-alerts.ftl new file mode 100644 index 0000000000..c5f76c5fb8 --- /dev/null +++ b/Resources/Locale/en-US/mood/mood-alerts.ftl @@ -0,0 +1,32 @@ +alerts-mood-dead-name = Dead +alerts-mood-dead-desc = Eternal emptiness has enveloped me, and the world no longer has power over my soul. + +alerts-mood-insane-name = Insane +alerts-mood-insane-desc = Darkness and hopelessness smolder in my soul, the world is doomed to absolute evil. + +alerts-mood-horrible-name = Horrible +alerts-mood-horrible-desc = I struggle with pain and fears, my fate is a series of torments and sufferings. + +alerts-mood-terrible-name = Terrible +alerts-mood-terrible-desc = My life has dried up like blood from a wound, and there is only darkness and despair all around. + +alerts-mood-bad-name = Bad +alerts-mood-bad-desc = My strength is leaving me, and every day becomes a difficult ordeal. + +alerts-mood-meh-name = Mediocre +alerts-mood-meh-desc = The world is full of dangers and pain, and my hopes are slowly dying. + +alerts-mood-neutral-name = Neutral +alerts-mood-neutral-desc = I continue on my way, despite threats and hardships, looking for the slightest light in the darkness. + +alerts-mood-good-name = Good +alerts-mood-good-desc = In this world of suffering, I find a little relief and hope. + +alerts-mood-great-name = Great +alerts-mood-great-desc = My strength is restored, and the world seems to be the lesser evil and pain. + +alerts-mood-exceptional-name = Exceptional +alerts-mood-exceptional-desc = Strength and hope fills me, despite the threats that lurk around me. + +alerts-mood-perfect-name = Perfect +alerts-mood-perfect-desc = My soul is full of light and power, and I am ready to fight the darkness in this cruel world. diff --git a/Resources/Locale/en-US/mood/mood.ftl b/Resources/Locale/en-US/mood/mood.ftl new file mode 100644 index 0000000000..36e31a9964 --- /dev/null +++ b/Resources/Locale/en-US/mood/mood.ftl @@ -0,0 +1,77 @@ +mood-show-effects-start = [font size=12]Mood:[/font] + +mood-effect-HungerOverfed = I ate so much, I feel as though I'm about to burst! +mood-effect-HungerOkay = I am feeling full. +mood-effect-HungerPeckish = I could go for a snack right about now. +mood-effect-HungerStarving = I NEED FOOD! + +mood-effect-ThirstOverHydrated = I've had my fill of water. +mood-effect-ThirstOkay = I'm feeling refreshed. +mood-effect-ThirstThirsty = My lips are a little dry. +mood-effect-ThirstParched = I NEED WATER! + +mood-effect-HealthNoDamage = I'm in no pain. +mood-effect-HealthLightDamage = It's just a scratch, but it hurts nonetheless +mood-effect-HealthSevereDamage = The pain is almost unbearable! +mood-effect-HealthHeavyDamage = Agony gnaws at my soul! + +mood-effect-Handcuffed = I am being held captive. + +mood-effect-Suffocating = I.. Can't.. Breathe... + +mood-effect-OnFire = IT BURNS!!! + +mood-effect-Creampied = I was baptized. It tastes like pie. + +mood-effect-MobSlipped = I slipped! I should be more careful next time. + +mood-effect-MobVomit = My lunch tasted awful coming back up. + +mood-effect-MobLowPressure = My whole body feels like it's going to burst! + +mood-effect-MobHighPressure = I feel as though I am being crushed on all sides! + +mood-effect-TraitSaturnine = Everything kind of sucks. I hate this job. + +mood-effect-Dead = You are dead. + +mood-effect-BeingHugged = Hugs are nice. + +mood-effect-BeingPet = Someone pet me! + +mood-effect-ArcadePlay = I had fun playing an interesting arcade game. + +mood-effect-GotBlessed = I was blessed. + +mood-effect-PetAnimal = Animals are so cute, I can't stop petting them! + +mood-effect-SavedLife = It's so nice to save someone's life + +mood-effect-TraitorFocused = I have a goal, and I will accomplish it no matter what. + +mood-effect-RevolutionFocused = VIVA LA REVOLUTION!!! + +mood-effect-CultFocused = Dark Gods, grant me strength! + +mood-effect-TraitSanguine = I have nothing to worry about. I'm sure everything will turn out well in the end! + +# Addictions +mood-effect-LotoTranscendence = + I CAN SEE ALL THAT IS, ALL THAT WILL EVER BE, AND ALL THAT EVER WAS. ALL OF CREATION HAS OPENED TO MY MIND! + I MUST HAVE IT ALL. I MUST KNOW IT ALL. ALL OF IT. FOREVER! +mood-effect-LotoEnthrallment = + It has fled from me... The heart of all creation is gone from my soul, leaving behind an emptiness I cannot bear. + I fear that I will wither to nothing if I cannot drink from the cup of knowledge once again. + +mood-effect-NicotineBenefit = + I feel as if I have been standing my entire life and I just sat down. +mood-effect-NicotineWithdrawal = + I could really go for a smoke right now. + +# Surgery +mood-effect-SurgeryPain = The surgery hurts. +# Drugs +mood-effect-EthanolBenefit = + I feel so relaxed from drinking. +mood-effect-SpaceDrugsBenefit = + Woaaaah, such pretty colors maaaaan. It's like I can hear color and taste sound maaan. diff --git a/Resources/Locale/en-US/movement/laying.ftl b/Resources/Locale/en-US/movement/laying.ftl index f75061d6a7..de84ca629e 100644 --- a/Resources/Locale/en-US/movement/laying.ftl +++ b/Resources/Locale/en-US/movement/laying.ftl @@ -1,3 +1,6 @@ +crawling-under-tables-disabled-popup = Crawling under tables is disabled on this server. + +# TODO either remove those, or make use of them laying-comp-lay-success-self = You lay down. laying-comp-lay-success-other = {THE($entity)} lays down. laying-comp-lay-fail-self = You can't lay down right now. diff --git a/Resources/Locale/en-US/ninja/ninja-actions.ftl b/Resources/Locale/en-US/ninja/ninja-actions.ftl index b42da33a29..f01f02a60e 100644 --- a/Resources/Locale/en-US/ninja/ninja-actions.ftl +++ b/Resources/Locale/en-US/ninja/ninja-actions.ftl @@ -4,3 +4,5 @@ ninja-suit-cooldown = The suit needs time to recuperate from the last attack. ninja-research-steal-fail = No new research nodes were stolen... ninja-research-steal-success = Stole {$count} new nodes from {THE($server)}. + +ninja-criminal-records-hack-announcement = ERROR: Criminal records has detected a [REDACTED] error #*;" diff --git a/Resources/Locale/en-US/nutrition/components/drink-component.ftl b/Resources/Locale/en-US/nutrition/components/drink-component.ftl index 9a388744b0..e80787c8d5 100644 --- a/Resources/Locale/en-US/nutrition/components/drink-component.ftl +++ b/Resources/Locale/en-US/nutrition/components/drink-component.ftl @@ -1,4 +1,4 @@ -drink-component-on-use-is-empty = {$owner} is empty! +drink-component-on-use-is-empty = {CAPITALIZE(THE($owner))} is empty! drink-component-on-examine-is-empty = [color=gray]Empty[/color] drink-component-on-examine-is-opened = [color=yellow]Opened[/color] drink-component-on-examine-is-sealed = The seal is intact. @@ -10,7 +10,7 @@ drink-component-on-examine-is-half-empty = Halfway Empty drink-component-on-examine-is-mostly-empty = Mostly Empty drink-component-on-examine-exact-volume = It contains {$amount}u. drink-component-try-use-drink-not-open = Open {$owner} first! -drink-component-try-use-drink-is-empty = {$entity} is empty! +drink-component-try-use-drink-is-empty = {CAPITALIZE(THE($entity))} is empty! drink-component-try-use-drink-cannot-drink = You can't drink anything! drink-component-try-use-drink-had-enough = You can't drink more! drink-component-try-use-drink-cannot-drink-other = They can't drink anything! diff --git a/Resources/Locale/en-US/nutrition/components/fat-extractor.ftl b/Resources/Locale/en-US/nutrition/components/fat-extractor.ftl index 20a31cd8c4..611b8ef540 100644 --- a/Resources/Locale/en-US/nutrition/components/fat-extractor.ftl +++ b/Resources/Locale/en-US/nutrition/components/fat-extractor.ftl @@ -1,3 +1,5 @@ +fat-extractor-component-rate = extraction rate + fat-extractor-fact-1 = Fats are triglycerides made up of a combination of different building blocks; glycerol and fatty acids. fat-extractor-fact-2 = Adults should get a recommended 20-35% of their energy intake from fat. fat-extractor-fact-3 = Being overweight or obese puts you at an increased risk of chronic diseases, such as cardiovascular diseases, metabolic syndrome, type 2 diabetes, and some types of cancers. diff --git a/Resources/Locale/en-US/nutrition/components/pressurized-solution-component.ftl b/Resources/Locale/en-US/nutrition/components/pressurized-solution-component.ftl new file mode 100644 index 0000000000..a227d811f6 --- /dev/null +++ b/Resources/Locale/en-US/nutrition/components/pressurized-solution-component.ftl @@ -0,0 +1,3 @@ +pressurized-solution-spray-holder-self = { CAPITALIZE(THE($drink)) } sprays on you! +pressurized-solution-spray-holder-others = { CAPITALIZE(THE($drink)) } sprays on { THE($victim) }! +pressurized-solution-spray-ground = The contents of { THE($drink) } spray out! diff --git a/Resources/Locale/en-US/nutrition/components/shakeable-component.ftl b/Resources/Locale/en-US/nutrition/components/shakeable-component.ftl new file mode 100644 index 0000000000..acc1ecd848 --- /dev/null +++ b/Resources/Locale/en-US/nutrition/components/shakeable-component.ftl @@ -0,0 +1,3 @@ +shakeable-verb = Shake +shakeable-popup-message-others = { CAPITALIZE(THE($user)) } shakes { THE($shakeable) } +shakeable-popup-message-self = You shake { THE($shakeable) } diff --git a/Resources/Locale/en-US/nyanotrasen/accent/accents.ftl b/Resources/Locale/en-US/nyanotrasen/accent/accents.ftl deleted file mode 100644 index 2699d71bc6..0000000000 --- a/Resources/Locale/en-US/nyanotrasen/accent/accents.ftl +++ /dev/null @@ -1,6 +0,0 @@ -# Mothroach -accent-words-mothroach-1 = Squeak! -accent-words-mothroach-2 = Chirp! -accent-words-mothroach-3 = Peep! -accent-words-mothroach-4 = Eeee! -accent-words-mothroach-5 = Eep! diff --git a/Resources/Locale/en-US/nyanotrasen/reagents/meta/consumable/drink/drink.ftl b/Resources/Locale/en-US/nyanotrasen/reagents/meta/consumable/drink/drink.ftl index e9d04bd951..eef7207132 100644 --- a/Resources/Locale/en-US/nyanotrasen/reagents/meta/consumable/drink/drink.ftl +++ b/Resources/Locale/en-US/nyanotrasen/reagents/meta/consumable/drink/drink.ftl @@ -7,11 +7,5 @@ reagent-desc-pinkdrink = Entire civilizations have crumbled trying to decide if reagent-name-bubbletea = bubble tea reagent-desc-bubbletea = Big straw not included. -reagent-name-the-martinez = The Martinez -reagent-desc-the-martinez = The edgerunner legend. Remembered by a drink, Forgotten by a drunk. - -reagent-name-holywater = holy water -reagent-desc-holywater = Water blessed by some otherworldly powers. - reagent-name-lean = lean reagent-desc-lean = A disgusting mixture of soda, booze, and cough syrup. diff --git a/Resources/Locale/en-US/nyanotrasen/tools/tool-qualities.ftl b/Resources/Locale/en-US/nyanotrasen/tools/tool-qualities.ftl deleted file mode 100644 index c3c4e6ad2f..0000000000 --- a/Resources/Locale/en-US/nyanotrasen/tools/tool-qualities.ftl +++ /dev/null @@ -1,2 +0,0 @@ -tool-quality-digging-name = Digging -tool-quality-digging-tool-name = Shovel diff --git a/Resources/Locale/en-US/objectives/conditions/terminate.ftl b/Resources/Locale/en-US/objectives/conditions/terminate.ftl deleted file mode 100644 index c88c7b14da..0000000000 --- a/Resources/Locale/en-US/objectives/conditions/terminate.ftl +++ /dev/null @@ -1 +0,0 @@ -objective-terminate-title = Terminate {$targetName}, {CAPITALIZE($job)} diff --git a/Resources/Locale/en-US/paint/paint.ftl b/Resources/Locale/en-US/paint/paint.ftl new file mode 100644 index 0000000000..200b1f6e3f --- /dev/null +++ b/Resources/Locale/en-US/paint/paint.ftl @@ -0,0 +1,8 @@ +paint-success = {THE($target)} has been covered in paint! +paint-failure = Can't cover {THE($target)} in paint! +paint-failure-painted = {THE($target)} is already covered in paint! +paint-empty = {THE($used)} is empty! +paint-removed = You clean off the paint! +paint-closed = You must open {THE($used)} first! +paint-verb = Paint +paint-remove-verb = Remove Paint diff --git a/Resources/Locale/en-US/parkstation/Prototypes/Entities/Mobs/Customization/ears.ftl b/Resources/Locale/en-US/parkstation/Prototypes/Entities/Mobs/Customization/ears.ftl index a54035d991..a75fd14776 100644 --- a/Resources/Locale/en-US/parkstation/Prototypes/Entities/Mobs/Customization/ears.ftl +++ b/Resources/Locale/en-US/parkstation/Prototypes/Entities/Mobs/Customization/ears.ftl @@ -7,6 +7,4 @@ marking-EarsEasternD=Eastern Dragon marking-EarsJackal=Jackal marking-EarsMagus=Pointy marking-EarsMouse=Mouse -marking-EarsShadowkin=Shadowkin -marking-EarsShadowkinStriped=Shadowkin (Striped) marking-EarsSylveon=Ribbons diff --git a/Resources/Locale/en-US/parkstation/Prototypes/Entities/Mobs/Customization/eyes.ftl b/Resources/Locale/en-US/parkstation/Prototypes/Entities/Mobs/Customization/eyes.ftl deleted file mode 100644 index b79c51586b..0000000000 --- a/Resources/Locale/en-US/parkstation/Prototypes/Entities/Mobs/Customization/eyes.ftl +++ /dev/null @@ -1 +0,0 @@ -marking-EyesShadowkin=Shadowkin diff --git a/Resources/Locale/en-US/parkstation/Prototypes/Entities/Mobs/Customization/human_hair.ftl b/Resources/Locale/en-US/parkstation/Prototypes/Entities/Mobs/Customization/human_hair.ftl deleted file mode 100644 index b3b6c14e1d..0000000000 --- a/Resources/Locale/en-US/parkstation/Prototypes/Entities/Mobs/Customization/human_hair.ftl +++ /dev/null @@ -1,70 +0,0 @@ -marking-HumanHairAfricanPigtails = African Pigtails -marking-HumanHairAfropuffdouble = Afro Puff Double -marking-HumanHairAfropuffleft = Afro Puff Left -marking-HumanHairAfropuffright = Afro Puff Right -marking-HumanHairAmazon = Amazon -marking-HumanHairAstolfo = French Knots -marking-HumanHairBaum = Baum -marking-HumanHairBeachwave = Beachwave -marking-HumanHairBluntbangs = Blunt Bangs -marking-HumanHairBluntbangsAlt = Blunt Bangs Alt -marking-HumanHairBobcutAlt = Bob Cut Alt -marking-HumanHairBunhead4 = Bun Head 4 -marking-HumanHairCombed = Combed -marking-HumanHairCombedbob = Combed Bob -marking-HumanHairCotton = Cotton -marking-HumanHairCurly = Curly -marking-HumanHairDave = Dave -marking-HumanHairDiagonalBangs = Diagonal Bangs -marking-HumanHairEmoshort = Emo Short -marking-HumanHairFingerwave = Fingerwave -marking-HumanHairFluffyShort = Short Fluffy -marking-HumanHairFortuneteller = Fortune Teller -marking-HumanHairFortunetellerAlt = Fortune Teller Alt -marking-HumanHairFroofylong = Long Froofy -marking-HumanHairGeisha = Geisha -marking-HumanHairGentle21 = Gentle 21 -marking-HumanHairGlammetal = Glam Metal -marking-HumanHairGloomyLong = Gloomy Long -marking-HumanHairGloomyMedium = Gloomy Medium -marking-HumanHairGrande = Grande -marking-HumanHairHalfshave = Half-shaved Alt -marking-HumanHairHalfshaveglamorous = Half-shaved Glamorous -marking-HumanHairHalfshaveLong = Half-shaved Long -marking-HumanHairHalfshaveMessy = Half-shaved Messy -marking-HumanHairHalfshaveMessyLong = Half-shaved Messy Long -marking-HumanHairHalfshaveSnout = Half-shaved Snout -marking-HumanHairHightight = High Tight -marking-HumanHairHyenamane = Mohawk Hyena -marking-HumanHairJessica = Jessica -marking-HumanHairLong4 = Long 4 -marking-HumanHairLongdtails = Pigtails Huge -marking-HumanHairLongerAlt = Longer Alt -marking-HumanHairLongovereyeAlt = Over Eye Long Alt -marking-HumanHairLongsidepartstraight = Sidepart Straight Long -marking-HumanHairLooseSlicked = Slicked Loose -marking-HumanHairMediumbraid = Braid Medium -marking-HumanHairNewyou = New You -marking-HumanHairPonytailAlt = Ponytail Alt -marking-HumanHairPonytailF = Ponytail Side 5 -marking-HumanHairPoofy2 = Poofy 2 -marking-HumanHairQuadcurls = Quadcurls -marking-HumanHairSabitsuki = Sabitsuki -marking-HumanHairScully = Scully -marking-HumanHairShorthair4 = Short Hair 4 -marking-HumanHairShy = Shy -marking-HumanHairSimplePonytail = Ponytail Simple -marking-HumanHairSleaze = Sleaze -marking-HumanHairSlightlyMessy = Slightly Messy -marking-HumanHairSlimedroplet = Slime Droplet -marking-HumanHairSlimedropletAlt = Slime Droplet Alt -marking-HumanHairSlimespikes = Slime Spikes -marking-HumanHairSlimetendrils = Slime Tendrils -marking-HumanHairSlimetendrilsAlt = Slime Tendrils Alt -marking-HumanHairSpicy = Spicy -marking-HumanHairTwintailFloor = Floorlength Twintail -marking-HumanHairVeryshortovereye = Over Eye Short -marking-HumanHairVictory = Victory -marking-HumanHairViper = Viper -marking-HumanHairWife = Wife -marking-HumanHairZiegler = Ziegler diff --git a/Resources/Locale/en-US/parkstation/Prototypes/Entities/Mobs/Customization/tails.ftl b/Resources/Locale/en-US/parkstation/Prototypes/Entities/Mobs/Customization/tails.ftl index 257f9bbdae..9c682e6466 100644 --- a/Resources/Locale/en-US/parkstation/Prototypes/Entities/Mobs/Customization/tails.ftl +++ b/Resources/Locale/en-US/parkstation/Prototypes/Entities/Mobs/Customization/tails.ftl @@ -12,8 +12,3 @@ marking-TailShark=Shark marking-TailSnake=Snake marking-TailSuccubus=Pointy marking-TailTentacle=Tentacles -marking-TailShadowkin=Shadowkin -marking-TailShadowkinBig=Shadowkin (Big) -marking-TailShadowkinShorter=Shadowkin (Short) -marking-TailShadowkinMedium=Shadowkin (Medium) -marking-TailShadowkinBigFluff=Shadowkin (Big and Fluffy) diff --git a/Resources/Locale/en-US/parkstation/Traits/disabilities.ftl b/Resources/Locale/en-US/parkstation/Traits/disabilities.ftl deleted file mode 100644 index 8360aaeb9d..0000000000 --- a/Resources/Locale/en-US/parkstation/Traits/disabilities.ftl +++ /dev/null @@ -1,2 +0,0 @@ -trait-name-Nearsighted = Nearsighted -trait-description-Nearsighted = You require glasses to see properly. diff --git a/Resources/Locale/en-US/power/batteryDrinker.ftl b/Resources/Locale/en-US/power/batteryDrinker.ftl new file mode 100644 index 0000000000..35c76f8825 --- /dev/null +++ b/Resources/Locale/en-US/power/batteryDrinker.ftl @@ -0,0 +1,2 @@ +battery-drinker-verb-drink = Drain +battery-drinker-empty = {CAPATALIZE(THE($target))} is already charged! diff --git a/Resources/Locale/en-US/preferences/ui/character-setup-gui.ftl b/Resources/Locale/en-US/preferences/ui/character-setup-gui.ftl index bd80815e23..b85d7be38e 100644 --- a/Resources/Locale/en-US/preferences/ui/character-setup-gui.ftl +++ b/Resources/Locale/en-US/preferences/ui/character-setup-gui.ftl @@ -1,7 +1,6 @@ character-setup-gui-character-setup-label = Character setup character-setup-gui-character-setup-stats-button = Stats character-setup-gui-character-setup-rules-button = Rules -character-setup-gui-character-setup-save-button = Save character-setup-gui-character-setup-close-button = Close character-setup-gui-create-new-character-button = Create new slot... character-setup-gui-create-new-character-button-tooltip = A maximum of {$maxCharacters} characters are allowed. diff --git a/Resources/Locale/en-US/preferences/ui/humanoid-profile-editor.ftl b/Resources/Locale/en-US/preferences/ui/humanoid-profile-editor.ftl index f0ea0a4a72..e4588407d3 100644 --- a/Resources/Locale/en-US/preferences/ui/humanoid-profile-editor.ftl +++ b/Resources/Locale/en-US/preferences/ui/humanoid-profile-editor.ftl @@ -2,8 +2,8 @@ humanoid-profile-editor-randomize-everything-button = Randomize everything humanoid-profile-editor-name-label = Name: humanoid-profile-editor-name-random-button = Randomize humanoid-profile-editor-appearance-tab = Appearance -humanoid-profile-editor-clothing = Show clothing -humanoid-profile-editor-loadouts = Show loadout +humanoid-profile-editor-clothing = Preview job equipment: +humanoid-profile-editor-loadouts = Preview loadout items: humanoid-profile-editor-clothing-show = Show humanoid-profile-editor-sex-label = Sex: humanoid-profile-editor-sex-male-text = Male @@ -15,6 +15,7 @@ humanoid-profile-editor-height-label = Height: {$height}cm humanoid-profile-editor-width-label = Width: {$width}cm humanoid-profile-editor-weight-label = Weight: {$weight}kg humanoid-profile-editor-species-label = Species: +humanoid-profile-editor-customspeciename-label = Custom Specie Name: humanoid-profile-editor-pronouns-label = Pronouns: humanoid-profile-editor-pronouns-male-text = He / Him humanoid-profile-editor-pronouns-female-text = She / Her @@ -23,6 +24,7 @@ humanoid-profile-editor-pronouns-neuter-text = It / It humanoid-profile-editor-import-button = Import humanoid-profile-editor-export-button = Export humanoid-profile-editor-save-button = Save +humanoid-profile-editor-reset-button = Reset humanoid-profile-editor-clothing-label = Clothing: humanoid-profile-editor-backpack-label = Backpack: humanoid-profile-editor-spawn-priority-label = Spawn priority: @@ -59,11 +61,15 @@ humanoid-profile-editor-traits-header = You have {$points -> } *[other] {$traits}/{$maxTraits} traits } -humanoid-profile-editor-traits-show-unusable-button = Show Unusable +humanoid-profile-editor-traits-show-unusable-button = Show Unusable Traits humanoid-profile-editor-traits-show-unusable-button-tooltip = - When enabled, traits that your current character setup cannot use will be shown highlighted in red. + When enabled, traits that your current character setup cannot use will be shown highlighted in red or orange if selected. You will still not be able to use the invalid traits unless your character setup changes to fit the requirements. This is most likely useful only if there's a bug hiding traits you actually can use or if you want to see other species' traits or something. +humanoid-profile-editor-traits-remove-unusable-button = Remove {$count} Unusable Traits +humanoid-profile-editor-traits-remove-unusable-button-tooltip = + If you click this button, all traits that your current character setup cannot use will be removed. + You will be asked for confirmation before the traits are removed. humanoid-profile-editor-traits-no-traits = No traits found humanoid-profile-editor-job-priority-high-button = High @@ -75,11 +81,19 @@ humanoid-profile-editor-naming-rules-warning = Warning: Offensive or LRP IC name humanoid-profile-editor-loadouts-tab = Loadout humanoid-profile-editor-loadouts-points-label = You have {$points}/{$max} points -humanoid-profile-editor-loadouts-show-unusable-button = Show Unusable +humanoid-profile-editor-loadouts-show-unusable-button = Show Unusable Loadouts humanoid-profile-editor-loadouts-show-unusable-button-tooltip = - When enabled, loadouts that your current character setup cannot use will be shown highlighted in red. + When enabled, loadouts that your current character setup cannot use will be highlighted in red. + Loadouts that your character cannot wear (if clothing) will be highlighted in yellow. You will still not be able to use the invalid loadouts unless your character setup changes to fit the requirements. - This may be useful if you like switching between multiple jobs and don't want to have to reselect your loadout every + This may be useful if you like switching between multiple jobs and don't want to have to reselect your loadout every time you switch. +humanoid-profile-editor-loadouts-remove-unusable-button = Remove {$count -> + [1] {$count} Unusable Loadout + *[other] {$count} Unusable Loadouts +} +humanoid-profile-editor-loadouts-remove-unusable-button-tooltip = + If you click this button, all loadouts that your current character setup cannot use will be removed. + You will be asked for confirmation before the loadouts are removed. humanoid-profile-editor-loadouts-no-loadouts = No loadouts foundtime. humanoid-profile-editor-markings-tab = Markings diff --git a/Resources/Locale/en-US/preferences/ui/markings-picker.ftl b/Resources/Locale/en-US/preferences/ui/markings-picker.ftl index 92a82b3f60..f0d68c26d5 100644 --- a/Resources/Locale/en-US/preferences/ui/markings-picker.ftl +++ b/Resources/Locale/en-US/preferences/ui/markings-picker.ftl @@ -20,8 +20,14 @@ markings-category-HeadTop = Head (Top) markings-category-HeadSide = Head (Side) markings-category-Snout = Snout markings-category-Chest = Chest -markings-category-Arms = Arms -markings-category-Legs = Legs +markings-category-RightArm = Right Arm +markings-category-RightHand = Right Hand +markings-category-LeftArm = Left Arm +markings-category-LeftHand = Left Hand +markings-category-RightLeg = Right Leg +markings-category-RightFoot = Right Foot +markings-category-LeftLeg = Left Leg +markings-category-LeftFoot = Left Foot ## Parkstation-Wings markings-category-Wings = Wings markings-category-Tail = Tail diff --git a/Resources/Locale/en-US/prototypes/catalog/cargo/cargoproduct-categories.ftl b/Resources/Locale/en-US/prototypes/catalog/cargo/cargoproduct-categories.ftl index f2451527b0..70dc057b39 100644 --- a/Resources/Locale/en-US/prototypes/catalog/cargo/cargoproduct-categories.ftl +++ b/Resources/Locale/en-US/prototypes/catalog/cargo/cargoproduct-categories.ftl @@ -6,6 +6,7 @@ cargoproduct-category-name-emergency = Emergency cargoproduct-category-name-engineering = Engineering cargoproduct-category-name-food = Food cargoproduct-category-name-fun = Fun +cargoproduct-category-name-hardsuits = Hardsuits cargoproduct-category-name-hydroponics = Hydroponics cargoproduct-category-name-livestock = Livestock cargoproduct-category-name-materials = Materials diff --git a/Resources/Locale/en-US/parkstation/Prototypes/Entities/Mobs/Customization/ipcAntenna.ftl b/Resources/Locale/en-US/prototypes/entities/Mobs/Customization/ipcAntenna.ftl similarity index 100% rename from Resources/Locale/en-US/parkstation/Prototypes/Entities/Mobs/Customization/ipcAntenna.ftl rename to Resources/Locale/en-US/prototypes/entities/Mobs/Customization/ipcAntenna.ftl diff --git a/Resources/Locale/en-US/parkstation/Prototypes/Entities/Mobs/Customization/ipcScreens.ftl b/Resources/Locale/en-US/prototypes/entities/Mobs/Customization/ipcScreens.ftl similarity index 100% rename from Resources/Locale/en-US/parkstation/Prototypes/Entities/Mobs/Customization/ipcScreens.ftl rename to Resources/Locale/en-US/prototypes/entities/Mobs/Customization/ipcScreens.ftl diff --git a/Resources/Locale/en-US/prototypes/entities/clothing/head/hardsuit-helmets.ftl b/Resources/Locale/en-US/prototypes/entities/clothing/head/hardsuit-helmets.ftl index 2bbacf2abc..af0a836ce9 100644 --- a/Resources/Locale/en-US/prototypes/entities/clothing/head/hardsuit-helmets.ftl +++ b/Resources/Locale/en-US/prototypes/entities/clothing/head/hardsuit-helmets.ftl @@ -55,6 +55,8 @@ ent-ClothingHeadHelmetHardsuitLuxury = HpI-20c helmet .desc = A modified helmet for the Minos hardsuit, fashioned after the Logistics Officer's colors. It's been modified for greater mobility at the expense of physical trauma protection. ent-ClothingHeadHelmetHardsuitSyndie = CSA-51a helmet .desc = An armored helmet deployed over a Shanlin tacsuit. This one has been painted blood red. +ent-ClothingHeadHelmetHardsuitSyndieReverseEngineered = CSA-51a helmet + .desc = An armored helmet deployed over a Shanlin tacsuit. This one has been painted blue. ent-ClothingHeadHelmetHardsuitSyndieMedic = CSA-51m helmet .desc = An armored helmet deployed over a Zhongyao tacsuit. features optic integrations for nearly every medical hud on the market. Designed to enable the survival of combat medics in the most dangerous of environments. @@ -64,6 +66,8 @@ ent-ClothingHeadHelmetHardsuitSyndieCommander = CSA-54c helmet .desc = A bulked up version of the Shanlin tacsuit's helmet, purpose-built for commanders of special operation squads. This one has been painted blood-red. ent-ClothingHeadHelmetHardsuitCybersun = CSA-80UA helmet .desc = An incredibly sturdy looking helmet designed for the Guan Yu tacsuit. +ent-ClothingHeadHelmetHardsuitJuggernautReverseEngineered = CSA-80UA helmet + .desc = An incredibly sturdy looking helmet designed for the Guan Yu tacsuit. This one has been painted blue. ent-ClothingHeadHelmetHardsuitWizard = WZD-84 helmet .desc = A bizarre, gem-encrusted helmet from unknown origins. It provides some protection to its wearer without restricting their movements. ent-ClothingHeadHelmetHardsuitLing = organic space helmet @@ -74,10 +78,6 @@ ent-ClothingHeadHelmetHardsuitPirateEVA = pirate helmet ent-ClothingHeadHelmetHardsuitPirateCap = pirate captain's tacsuit helmet .desc = A special hardsuit helmet, made for the captain of a pirate ship. .suffix = Pirate -ent-ClothingHeadHelmetHardsuitSyndieReverseEngineered = NTSA-122 helmet - .desc = A sturdy, lightweight helmet made by the special adquisitions department of Nanotrasen. -ent-ClothingHeadHelmetHardsuitJuggernautReverseEngineered = NTSA-126 helmet - .desc = A very sturdy helmet made by the special acquisitions department of Nanotrasen, based on the "Juggernaut" tacsuit's design. ent-ClothingHeadHelmetHardsuitERTCentcomm = NT-444c helmet .desc = A special tacsuit helmet worn by Central Command Officers. ent-ClothingHeadHelmetHardsuitERTLeader = NT-444l helmet @@ -99,4 +99,4 @@ ent-ClothingHeadHelmetHardsuitClown = clown vacsuit helmet ent-ClothingHeadHelmetHardsuitMime = mime vacsuit helmet .desc = A mime vacsuit helmet. On closer inspection, it appears to be a normal helmet painted with crayons, and a mime mask glued on top. ent-ClothingHeadHelmetHardsuitSanta = DNK-31 helmet - .desc = A festive-looking hardsuit helmet that provides the jolly gift-giver protection from low-pressure environments. \ No newline at end of file + .desc = A festive-looking hardsuit helmet that provides the jolly gift-giver protection from low-pressure environments. diff --git a/Resources/Locale/en-US/prototypes/entities/clothing/outerClothing/hardsuits.ftl b/Resources/Locale/en-US/prototypes/entities/clothing/outerClothing/hardsuits.ftl index 84f03af5c9..8232ac352d 100644 --- a/Resources/Locale/en-US/prototypes/entities/clothing/outerClothing/hardsuits.ftl +++ b/Resources/Locale/en-US/prototypes/entities/clothing/outerClothing/hardsuits.ftl @@ -74,6 +74,9 @@ ent-ClothingOuterHardsuitLuxury = HpI-20c - "Minos" hardsuit ent-ClothingOuterHardsuitSyndie = CSA-51a - "Shanlin" tacsuit .desc = A tactical combat hardsuit produced by the Cybersun-Armaments Corporation, the suit's tags indicate it provides moderate protection against most forms of damage. This one has been painted blood red. It feels incredibly light. +ent-ClothingOuterHardsuitSyndieReverseEngineered = CSA-51a - "Shanlin" tacsuit + .desc = A tactical combat hardsuit produced by the Cybersun-Armaments Corporation, the suit's tags indicate it provides moderate protection against most forms of damage. + This one has been painted blue. It feels incredibly light. ent-ClothingOuterHardsuitSyndieMedic = CSA-51m - "Zhongyao" tacsuit .desc = A tactical combat hardsuit produced by the Cybersun-Armaments Corporation, the suit's tags indicate it provides moderate protection against most forms of damage. Half of the suit is painted blood red, the rest bears galactic-standard medical markings. It feels incredibly light. @@ -87,6 +90,9 @@ ent-ClothingOuterHardsuitSyndieCommander = CSA-54c - "Tianming" tacsuit ent-ClothingOuterHardsuitJuggernaut = CSA-80UA - "Guan Yu" tacsuit .desc = The pride and joy of the Cybersun-Armaments Corporation, named after an ancient Sol' War God. Commonly known throughout the galaxy as a "Juggernaut". Matching its bulky appearance, it protects against all forms of damage. It feels VERY heavy. +end-ClothingOuterHardsuitJuggernautReverseEngineered = CSA-80UA - "Guan Yu" tacsuit + .desc = The pride and joy of the Cybersun-Armaments Corporation, named after an ancient Sol' War God. Commonly known throughout the galaxy as a "Juggernaut". + Matching its bulky appearance, it protects against all forms of damage. It feels VERY heavy. ent-ClothingOuterHardsuitWizard = WZD-84 - "Mana" tacsuit .desc = A bizarre gem-encrusted hardsuit. Famously used by members of the Wizard Federation in their operations. Contrary to it's appearance, it can protect its wearer from space and considerable amounts of physical trauma, it feels somewhat light. @@ -98,11 +104,6 @@ ent-ClothingOuterHardsuitPirateEVA = pirate vacsuit ent-ClothingOuterHardsuitPirateCap = pirate captain's tacsuit .desc = An ancient armored tactical combat hardsuit of unknown origin, provides basic protections from the cold harsh realities of deep space and physical trauma. It doesn't seem to have any weight either. Perfect for defending against space scurvy and toolbox-wielding scallywags. -ent-ClothingOuterHardsuitSyndieReverseEngineered = NTSA-122 tacsuit - .desc = A suit made by the special adquisitions department of Nanotrasen, the suit's tags indicate it provides moderate protection against most forms of damage. -ent-ClothingOuterHardsuitJuggernautReverseEngineered = NTSA-126 tacsuit - .desc = A suit made by the special acquisitions department of Nanotrasen based on the "Juggernaut" design. - Matching its bulky appearance, it protects against all forms of damage. It feels VERY heavy. ent-ClothingOuterHardsuitERTCentcomm = NT-444c - "Ophanim" tacsuit .desc = A highly advanced, tactical combat hardsuit used by Central Command Officers, it seems to be branded with the Nanotrasen logo and a strange looking series number. The armor appears to be lined with a very sturdy alloy, it doesn't seem to have any weight either. diff --git a/Resources/Locale/en-US/prototypes/roles/antags.ftl b/Resources/Locale/en-US/prototypes/roles/antags.ftl index 40f2c9a682..5c514dba8e 100644 --- a/Resources/Locale/en-US/prototypes/roles/antags.ftl +++ b/Resources/Locale/en-US/prototypes/roles/antags.ftl @@ -30,6 +30,3 @@ roles-antag-space-ninja-objective = Use your stealth to sabotage the station, no roles-antag-thief-name = Thief roles-antag-thief-objective = Add some NT property to your personal collection without using violence. - -roles-antag-terminator-name = Paradox Anomaly # DeltaV - paradox anomaly -roles-antag-terminator-objective = Replace your double, or befriend them. # DeltaV - paradox anomaly diff --git a/Resources/Locale/en-US/psionics/psionic-chat.ftl b/Resources/Locale/en-US/psionics/psionic-chat.ftl index b7fe23b751..45444d0d10 100644 --- a/Resources/Locale/en-US/psionics/psionic-chat.ftl +++ b/Resources/Locale/en-US/psionics/psionic-chat.ftl @@ -1,4 +1,5 @@ chat-manager-send-telepathic-chat-wrap-message = {$telepathicChannelName}: {$message} +chat-manager-send-telepathic-chat-wrap-message-psychognomy = {$source}: {$message} chat-manager-send-telepathic-chat-wrap-message-admin = {$source} (Ψ): {$message} chat-manager-telepathic-channel-name = TELEPATHIC hud-chatbox-select-channel-Telepathic = Telepathic diff --git a/Resources/Locale/en-US/psionics/psionic-commands.ftl b/Resources/Locale/en-US/psionics/psionic-commands.ftl index e1110ef285..34d12981de 100644 --- a/Resources/Locale/en-US/psionics/psionic-commands.ftl +++ b/Resources/Locale/en-US/psionics/psionic-commands.ftl @@ -6,3 +6,8 @@ command-glimmerset-help = glimmerset (integer) command-lspsionic-description = List psionics. command-lspsionic-help = No arguments. + +command-addpsionicpower-description = Initialize an entity as Psionic with a given PowerPrototype +command-addpsionicpower-help = Argument 1 must be an EntityUid, and argument 2 must be a string matching the PrototypeId of a PsionicPower. +addpsionicpower-args-one-error = Argument 1 must be an EntityUid +addpsionicpower-args-two-error = Argument 2 must match the PrototypeId of a PsionicPower diff --git a/Resources/Locale/en-US/psionics/psionic-powers.ftl b/Resources/Locale/en-US/psionics/psionic-powers.ftl new file mode 100644 index 0000000000..a7cec77aa2 --- /dev/null +++ b/Resources/Locale/en-US/psionics/psionic-powers.ftl @@ -0,0 +1,186 @@ +generic-power-initialization-feedback = I Awaken. +already-casting = I cannot channel more than one power at a time. +no-mana = I cannot channel enough power. + +# Dispel +dispel-power-description = Dispel summoned entities such as familiars or forcewalls. +dispel-power-initialization-feedback = The powers of fate are nothing to me. I feel as though I can reach out to the strands around me, and enforce reality upon others. +dispel-power-metapsionic-feedback = {CAPITALIZE($entity)} is a mighty stone, standing against the currents of fate + +# Mass Sleep +mass-sleep-power-description = Put targets in a small area to sleep. +mass-sleep-initialization-feedback = Reaching out to the minds around me, I have located the words that can send others to the realm of dreams. +mass-sleep-metapsionic-feedback = {CAPITALIZE($entity)} bears the indelible mark of a dream thief. + +# Mind Swap +mind-swap-power-description = Swap minds with the target. Either can change back after 20 seconds. +mind-swap-power-initialization-feedback = I can feel the bonds of soul and body wither at my whim, my vessel may be replaced with that of another. +mind-swap-power-metapsionic-feedback = {CAPITALIZE($entity)} lacks a strong bond with their vessel, as if their connection with spirit is malleable. + +# Noospheric Zap +noospheric-zap-power-description = Shocks the conciousness of the target and leaves them stunned and stuttering. +noospheric-zap-power-initialization-feedback = + In a single transcendent moment, I find myself standing in a universe tiled by silicon. + I wander this place for days, desperate to find some form of life, and yet none greet me. + Just before I succumb to thirst, a man of silver finds me. He plunges his arm into my body, and I awake screaming. +noospheric-zap-power-metapsionic-feedback = + I look inside {CAPITALIZE($entity)}'s heart, and there, nestled amidst the flesh, whirs a microscopic sliver of a being composed of pure energy. + It turns upon my gaze with malice, its silvery eyes filled with a hatred for the carbon-fleshed. + +# Pyrokinesis +pyrokinesis-power-description = Light a flammable target on fire. +pyrokinesis-power-initialization-feedback = + There is a brilliant flash of light and heat, and for an instant I feel as though every milimeter of my flesh is turned to vapor. + Yet death does not come for me, though I find myself praying it does. The world beyond is both agonizingly hot and bone chilling. + For weeks I despair that Gehenna is real, I starve, I cry, I scream, and the pain does not cease. Finally, a man in white, with the face of an ogrous + fly beckons me to offer my service. When I reach out to shake his hand, the vision fades, and I find myself standing in the prime materium. + I know His name now, it is the Secret of Fire. Merely by thinking of it, I can feel the heat of that place come to my hands. +pyrokinesis-power-metapsionic-feedback = The Secret of Fire dwells within {CAPITALIZE($entity)} + +# Metapsionic Pulse +metapsionic-power-description = Send a mental pulse through the area to see if there are any psychics nearby. +metapsionic-power-initialization-feedback = + The world around me awakens with dreamlight. For a transcendent moment, I can see all that is, all that will ever be. + I find myself staggering, my lips parched not for want of water, but to drink of the cup of knowledge. I. Must. Find. It. +metapsionic-power-metapsionic-feedback = {CAPITALIZE($entity)} gazes back upon thee. + +# Psionic Regeneration +psionic-regeneration-power-description = Push your natural metabolism to the limit to power your body's regenerative capability. +psionic-regeneration-power-initialization-feedback = + I look within myself, finding a wellspring of life. +psionic-regeneration-power-metapsionic-feedback = {CAPITALIZE($entity)} possesses an overwhelming will to live + +# Healing Word +action-name-healing-word = Healing Word +action-description-healing-word = Speak the Lesser Secret Of Life, and restore health to another. +healing-word-power-description = Speak the Lesser Secret Of Life, and restore health to another. +healing-word-power-initialization-feedback = + At the beginning of time, a word was spoken that brought life into the Spheres. + Though it taxes my mind to know it, this Secret is known to me now. + I need only speak it. +healing-word-power-metapsionic-feedback = {CAPITALIZE($entity)} bears the Lesser Secret of Life. +healing-word-begin = {CAPITALIZE($entity)} mutters a word that brings both joy and pain alike to those who hear it. + +# Revivify +action-name-revivify = Breath of Life +action-description-revivify = Speak the Greater Secret of Life, and restore another to life. +revivify-power-description = Speak the Greater Secret of Life, and restore another to life. +revivify-power-initialization-feedback = + For a moment, my soul journeys across time and space to the beginning of it all, there I hear it. + The Secret of Life in its fullness. I feel my entire existence burning out from within, merely by knowing it. + Power flows through me as a mighty river, begging to be released with a simple spoken word. +revivify-power-metapsionic-feedback = {CAPITALIZE($entity)} bears the Greater Secret of Life. +revivify-begin = {CAPITALIZE($entity)} enunciates a word of such divine power, that those who hear it weep from joy. + +# Telegnosis +telegnosis-power-description = Create a telegnostic projection to remotely observe things. +telegnosis-power-initialization-feedback = + With my next step, I find that I am no longer in the material realm. My feet are trodding upon a bridge of rainbow light. + Yet strangly, as I look left and right, I first see a color that is as pink within pink, and to my right, blue within blue. + Just as my mind reels from the displeasure of knowing colors that aren't, a creature I can only describe as a + dragon with the wings of a peacock swoops down, and consumes my flesh in a single bite. I awaken in an instant, to a world utterly devoid + of true, real colors. +telegnosis-power-metapsionic-feedback = {CAPITALIZE($entity)}'s soul travels across bridges composed of dreamlight + +# Psionic Invisibility +psionic-invisibility-power-description = Render yourself invisible to any entity that could potentially be psychic. Borgs, animals, and so on are not affected. +psionic-invisibility-power-initialization-feedback = + I suddenly find myself plunged into a world utterly without light, yet I can feel the rays of warmth cast upon me. + Pondering this, I arrive at a realization that sight itself is an illusion. I reject it, I deny that light itself is real. + When I awaken, I can no longer see even myself. +psionic-invisibility-power-metapsionic-feedback = {CAPITALIZE($entity)}'s wyrd seeks to hide from thine gaze + +# Xenoglossy +xenoglossy-power-description = You understand all languages. +xenoglossy-power-initialization-feedback = + I feel an empathy with all creation, so that I may understand them and be understood. + The barrier between thought and expressions is permeable to me. + +psionic-language-power-metapsionic-feedback = The noösphere flows freely through {CAPITALIZE($entity)}, who seems to digest it and pass it back out undisturbed. + +# Psychognomy +psychognomy-power-description = You have some vague sense of the form of the source of telepathic messages. +psychognomy-power-initialization-feedback = + I have pierced the veil, and I know I'm not alone. More concerning, the piercing I made seems to be still indefinitely permeable. + When energy passes through the perforations in the noösphere, I get a faint glimpse of the material origin. + +# Telepathy +telepathy-power-description = You are capable of both sending and receiving telepathic messages. +telepathy-power-initialization-feedback = + The voices I've heard all my life begin to clear, yet they do not leave me. Before, they were as incoherent whispers, + now my senses broaden, I come to a realization that they are part of a communal shared hallucination. Behind every voice is a glimmering sentience. + +# Shadeskip +action-name-shadeskip = Shadeskip +action-description-shadeskip = + Call upon the Lords of the End of Time, and beseech them for a fragment of true entropy. +shadeskip-power-description = { action-description-shadeskip } +shadeskip-power-initialization-feedback = + I find myself standing in a frigid land, under a sky lacking in all starlight. Cold is the void at the End of Time. + I look to the pale blue within blue horizon, and find a great eye standing at the center of it all, black and emptier than the deepest reaches of space. + My soul begins to wither under its gaze, and I find myself begging for it to look away. The eye laughs, it demands that I serve it or die. + Knowing I have no choice, I pledge myself to it, and suddenly I am back in the material realm. The eye stares behind me still. +shadeskip-power-metapsionic-feedback = {CAPITALIZE($entity)} has been claimed by the Lords of the End of Time. +shadeskip-overcharge-feedback = My body reels from shock as it is overwhelmed by the sheer force flowing through me. + +# Telekinetic Pulse +action-name-telekinetic-pulse = Telekinetic Pulse +action-description-telekinetic-pulse = + Force everyone around you away. +telekinetic-pulse-power-description = { action-description-telekinetic-pulse } +telekinetic-pulse-power-initialization-feedback = + As I reach through the veil with my psyche, I discover a wellspring of pure kinetic energy. It courses through me, but I seem to lack fine control over it. +telekinetic-pulse-power-metapsionic-feedback = {CAPITALIZE($entity)} has the essence of pure kinesis flowing through him. + +# Pyrokinetic Flare +action-name-pyrokinetic-flare = Pyrokinetic Flare +action-description-pyrokinetic-flare = + Generate a flash of firelight from Gehenna to blind your adversaries. +pyrokinetic-flare-power-description = { action-description-pyrokinetic-flare } +pyrokinetic-flare-power-initialization-feedback = + My gaze is briefly filled with a flash of immense light and head, and for a single moment I can see a glimpse of a realm + of fire and pain, of hunger and suffering. Just as soon as I glimpse it, the vision fades. But the memory of that flash lingers within my mind. + I can recall it still, a glimpse of the fires of Gehenna. +pyrokinetic-flare-power-metapsionic-feedback = Guh these don't even matter because nobody can read this line in-game and I don't know when I'm ever bringing back Narrow Pulse + +# Summon Imp +action-name-summon-imp = Summon Imp +action-description-summon-imp = + Summon and bind an Imp from Gehenna to serve as your Familiar. +summon-imp-power-description = { action-description-summon-imp } +summon-imp-power-initialization-feedback = + For a brief time, I find myself wandering the blackened fields of Gehenna. I sift between the ashes, finding a smoldering coal in the shape of an eye. + I breathe upon it, and it bursts alight with flame. Before I return, the creature thanks me and tells me its name. + +# Summon Remilia +action-name-summon-remilia = Summon Remilia +action-description-summon-remilia = + Call forth your ever-loyal familiar Remilia. +summon-remilia-power-description = { action-description-summon-remilia } + +# Psionic System Messages +mindbreaking-feedback = The light of life vanishes from {CAPITALIZE($entity)}'s eyes, leaving behind a husk pretending at sapience +examine-mindbroken-message = + Eyes unblinking, staring deep into the horizon. {CAPITALIZE($entity)} is a sack of meat pretending it has a soul. + There is nothing behind its gaze, no evidence there can be found of the divine light of creation. +psionic-roll-failed = For a moment, my consciousness expands, yet I feel that it is not enough. +entity-anomaly-no-grid = There is nowhere for me to conjure beings. +power-overwhelming-power-feedback = {CAPITALIZE($entity)} wields a vast connection to the noösphere + +# Shadowkin ShadeSkip +action-description-shadowkin-shadeskip = Aaramrra! + +# DarkSwap +action-name-darkswap = DarkSwap +action-description-darkswap = Mmra Mamm! + +ethereal-pickup-fail = My hand sizzles as it passes through... + +# Psionic Familiar System +psionic-familiar-cant-attack-master = I am bound by my Master, I cannot harm them. +psionic-familiar-despawn-text = {CAPITALIZE($entity)} returns from whence it came! +ghost-role-information-familiar-name = Psionic Familiar +ghost-role-information-familiar-description = An interdimensional creature bound to the will of a Psion. +ghost-role-information-familiar-rules = + Obey the one who summoned you. Do not act against the interests of your Master. You will die for your Master if it is necessary. + diff --git a/Resources/Locale/en-US/psionics/psychognomic-descriptors.ftl b/Resources/Locale/en-US/psionics/psychognomic-descriptors.ftl new file mode 100644 index 0000000000..d91f0f2524 --- /dev/null +++ b/Resources/Locale/en-US/psionics/psychognomic-descriptors.ftl @@ -0,0 +1,23 @@ +p-descriptor-mysterious = mysterious +p-descriptor-male = male +p-descriptor-female = female +p-descriptor-young = young +p-descriptor-old = old +p-descriptor-masculine = masculine +p-descriptor-feminine = feminine +p-descriptor-hylic = hylic +p-descriptor-pneumatic = pneumatic +p-descriptor-liminal = liminal +p-descriptor-bound = bound +p-descriptor-ignorant = ignorant +p-descriptor-demiurgic = demiurgic +p-descriptor-emanative = emanative +p-descriptor-light = light +p-descriptor-heavy = heavy +p-descriptor-cyclic = cyclic +p-descriptor-hungry = hungry +p-descriptor-vampiric = vampiric +p-descriptor-kinetic = kinetic +p-descriptor-gnostic = gnostic +p-descriptor-dumb = dumb +p-descriptor-passive = passive \ No newline at end of file diff --git a/Resources/Locale/en-US/radiation/geiger-component.ftl b/Resources/Locale/en-US/radiation/geiger-component.ftl index 0e7d2a8a35..726c7190f2 100644 --- a/Resources/Locale/en-US/radiation/geiger-component.ftl +++ b/Resources/Locale/en-US/radiation/geiger-component.ftl @@ -1,3 +1,3 @@ -geiger-item-control-status = Radiation: [color={$color}]{$rads} rads[/color] +geiger-item-control-status = [color={$color}]{$rads} rads[/color] geiger-item-control-disabled = Disabled geiger-component-examine = Current radiation: [color={$color}]{$rads} rads[/color] diff --git a/Resources/Locale/en-US/radio/components/radio-jammer-component.ftl b/Resources/Locale/en-US/radio/components/radio-jammer-component.ftl index 68efbf8d4e..eb540ee971 100644 --- a/Resources/Locale/en-US/radio/components/radio-jammer-component.ftl +++ b/Resources/Locale/en-US/radio/components/radio-jammer-component.ftl @@ -4,3 +4,13 @@ radio-jammer-component-off-state = off radio-jammer-component-examine-on-state = The light is currently [color=darkgreen]on[/color]. radio-jammer-component-examine-off-state = The light is currently [color=darkred]off[/color]. + +radio-jammer-component-setting-high = High +radio-jammer-component-setting-medium = Medium +radio-jammer-component-setting-low = Low + +radio-jammer-component-set-message-high = The jammer is now operating at high power. +radio-jammer-component-set-message-medium = The jammer is now operating at medium power. +radio-jammer-component-set-message-low = The jammer is now operating at low power. + +radio-jammer-component-switch-setting = The power level switch is set to "[color=yellow]{$powerLevel}[/color]". diff --git a/Resources/Locale/en-US/random-barks/!default.ftl b/Resources/Locale/en-US/random-barks/!default.ftl new file mode 100644 index 0000000000..9620b80a93 --- /dev/null +++ b/Resources/Locale/en-US/random-barks/!default.ftl @@ -0,0 +1,28 @@ +# Default barks. This file serves as a template for future bark types and as a fallback for mobs who do not have unique barks for one reason or another. + +# Recommendations: +# - Try to keep barks simple and reasonably short. 8 words is the usual limit, but most barks should be at msot 3 words long. +# - Each bark should be one, or at most two sentences long. +# - Keep in mind that those will usually not be understood by players. +# - Do not add punctuation at the end of the message, except for question marks, so that the random bark system will add random punctuation. +bark-default-1 = Bark +bark-default-2 = Boof +bark-default-3 = Woofums +bark-default-4 = Rawrl +bark-default-5 = Eeeeeee +bark-default-6 = Barkums +bark-default-7 = Awooooooooooooooooooo awooo awoooo +bark-default-8 = Grrrrrrrrrrrrrrrrrr +bark-default-9 = Rarrwrarrwr +bark-default-10 = Goddamn I love gold fish crackers +bark-default-11 = Bork bork boof boof bork bork boof boof bork +bark-default-12 = Bark +bark-default-13 = Boof +bark-default-14 = Woofums +bark-default-15 = Rawrl +bark-default-16 = Eeeeeee +bark-default-17 = Barkum + +# This should always come last so that it's easy to keep track of. +# Bark counts are locale-specific so they are typically defined in FTL files instead of YML, to make localization easier. +bark-default-count = 17 diff --git a/Resources/Locale/en-US/random-barks/bee.ftl b/Resources/Locale/en-US/random-barks/bee.ftl new file mode 100644 index 0000000000..2398eaa8a8 --- /dev/null +++ b/Resources/Locale/en-US/random-barks/bee.ftl @@ -0,0 +1,5 @@ +bark-bee-1 = Bzzzz +bark-bee-2 = Bzzt Bzzzt +bark-bee-3 = Bzzzt bzzzzzz +bark-bee-4 = Bzzzzt +bark-bee-count = 4 diff --git a/Resources/Locale/en-US/random-barks/cat.ftl b/Resources/Locale/en-US/random-barks/cat.ftl new file mode 100644 index 0000000000..61536efdf4 --- /dev/null +++ b/Resources/Locale/en-US/random-barks/cat.ftl @@ -0,0 +1,25 @@ +bark-cat-1 = Meow +bark-cat-2 = Purr purr +bark-cat-3 = Hungry +bark-cat-4 = Fish! +bark-cat-5 = Birds! +bark-cat-6 = Nap time +bark-cat-7 = Scratch scratch scratch +bark-cat-8 = Purr purr purr +bark-cat-9 = That a mouse? +bark-cat-10 = Meow meow +bark-cat-11 = Brrow +bark-cat-12 = Pet me +bark-cat-13 = Pets good +bark-cat-14 = Yawn +bark-cat-15 = Hiss +bark-cat-16 = Where'd the sun go? +bark-cat-17 = Play time +bark-cat-18 = Sleepy +bark-cat-19 = Need to rest +bark-cat-20 = I hear mice +bark-cat-21 = Must catch the mouse +bark-cat-22 = Mothroach tasted good +bark-cat-23 = Purrmeow +bark-cat-24 = Mrrp meow mrrow +bark-cat-count = 24 diff --git a/Resources/Locale/en-US/random-barks/chicken.ftl b/Resources/Locale/en-US/random-barks/chicken.ftl new file mode 100644 index 0000000000..fe7070e045 --- /dev/null +++ b/Resources/Locale/en-US/random-barks/chicken.ftl @@ -0,0 +1,9 @@ +bark-chicken-1 = Coooot +bark-chicken-2 = Coot +bark-chicken-3 = Coot Coot Cooot +bark-chicken-4 = Food +bark-chicken-5 = Where is grass? +bark-chicken-6 = Need to eat +bark-chicken-8 = Egg +bark-chicken-9 = Coot coot +bark-chicken-count = 9 diff --git a/Resources/Locale/en-US/random-barks/cow.ftl b/Resources/Locale/en-US/random-barks/cow.ftl new file mode 100644 index 0000000000..e628fde767 --- /dev/null +++ b/Resources/Locale/en-US/random-barks/cow.ftl @@ -0,0 +1,11 @@ +bark-cow-1 = Mooooo +bark-cow-2 = Moo +bark-cow-3 = Huff +bark-cow-4 = Moooooooo +bark-cow-5 = Moooo +bark-cow-6 = Floor is food +bark-cow-7 = I'm hungry +bark-cow-8 = How long have I been here? +bark-cow-9 = Moooo mooooooo +bark-cow-10 = Huff... Moooooo +bark-cow-count = 10 diff --git a/Resources/Locale/en-US/random-barks/crab.ftl b/Resources/Locale/en-US/random-barks/crab.ftl new file mode 100644 index 0000000000..5fe69f5c6b --- /dev/null +++ b/Resources/Locale/en-US/random-barks/crab.ftl @@ -0,0 +1,12 @@ +# Contrary to a popular belief, crabs do not actually possess much of an intelligence, if any at all. +bark-crab-1 = Claw! +bark-crab-2 = Snap snap +bark-crab-3 = Clickity clack +bark-crab-4 = Clack clack +bark-crab-5 = Water +bark-crab-6 = Snap +bark-crab-7 = Clack +bark-crab-8 = Click +bark-crab-9 = Clack clack clack +bark-crab-10 = click click clack +bark-crab-count = 10 diff --git a/Resources/Locale/en-US/random-barks/dog.ftl b/Resources/Locale/en-US/random-barks/dog.ftl new file mode 100644 index 0000000000..4cd94c3a4a --- /dev/null +++ b/Resources/Locale/en-US/random-barks/dog.ftl @@ -0,0 +1,36 @@ +bark-dog-1 = Bark +bark-dog-2 = Boof +bark-dog-3 = Woofums +bark-dog-4 = Rawrl +bark-dog-5 = Eeeeeee +bark-dog-6 = Barkums +bark-dog-7 = Awooooooooooooooooooo awoo awoooo +bark-dog-8 = Grrrrrrrrrrrrrrrrrr +bark-dog-9 = Rarrwrarrwr +bark-dog-10 = Goddamn I love gold fish crackers +bark-dog-11 = Bork bork boof boof bork bork boof boof boof bork +bark-dog-12 = Bark +bark-dog-13 = Boof +bark-dog-14 = Woofums +bark-dog-15 = Rawrl +bark-dog-16 = Eeeeeee +bark-dog-17 = Barkum +bark-dog-18 = Woof woof woof! +bark-dog-19 = Awoooo! I smell food! +bark-dog-20 = Bark bark bark bark! +bark-dog-21 = Grrr, let’s play! +bark-dog-22 = Wooooo! +bark-dog-23 = Rawr! I’m a fierce creature of god! +bark-dog-24 = I love belly rubs! +bark-dog-25 = Bark! Who's there? +bark-dog-26 = Eeeeeee! So excited! +bark-dog-27 = Woof woof, where's my snack? +bark-dog-28 = Bark bark, chasing my tail again! +bark-dog-29 = Grrr, I see a shiny thing! +bark-dog-30 = Bork bork, can I eat that? +bark-dog-31 = Awoo, what’s that weird smell? +bark-dog-32 = Woofums, I like to bark at stars! +bark-dog-33 = Bark bark, I forgot what I wanted! +bark-dog-34 = Eeeee, squeaky toy, my best friend! +bark-dog-35 = Rawrl, let’s run in circles forever! +bark-dog-count = 35 diff --git a/Resources/Locale/en-US/random-barks/fox.ftl b/Resources/Locale/en-US/random-barks/fox.ftl new file mode 100644 index 0000000000..c7a9f61208 --- /dev/null +++ b/Resources/Locale/en-US/random-barks/fox.ftl @@ -0,0 +1,29 @@ +bark-fox-1 = Yip yip +bark-fox-2 = Yap yap +bark-fox-3 = Food +bark-fox-4 = Hungry +bark-fox-5 = Critter! Must catch +bark-fox-6 = Run +bark-fox-7 = Hide +bark-fox-8 = Sniff sniff +bark-fox-9 = I'm scared +bark-fox-10 = Purr purr +bark-fox-11 = Play? +bark-fox-12 = Sleepy +bark-fox-13 = Yap yap yap +bark-fox-14 = Yawn +bark-fox-15 = Curious +bark-fox-16 = Happy +bark-fox-17 = Food, now! +bark-fox-18 = Lonely +bark-fox-19 = Barruf +bark-fox-20 = Raff ruff +bark-fox-21 = Zoomies +bark-fox-22 = Yip yap yap yip yip +bark-fox-23 = I'm hungry +bark-fox-24 = Feed me +bark-fox-25 = Eeeeeeeeeeeeee +bark-fox-26 = Eeeeeee +bark-fox-27 = Myaaaaah +bark-fox-28 = Yip yip yip +bark-fox-count = 28 diff --git a/Resources/Locale/en-US/random-barks/hissing.ftl b/Resources/Locale/en-US/random-barks/hissing.ftl new file mode 100644 index 0000000000..d9f97d984e --- /dev/null +++ b/Resources/Locale/en-US/random-barks/hissing.ftl @@ -0,0 +1,25 @@ +# Agressive xenos/spiders +bark-hissing-1 = Hsssss +bark-hissing-2 = Hssssssss +bark-hissing-3 = I sssee you +bark-hissing-4 = I will catch you +bark-hissing-6 = I'm hhhungry! +bark-hissing-7 = Ssseek... food... +bark-hissing-8 = Hsss... Get there +bark-hissing-9 = People... food +bark-hissing-10 = Hsssss... hssssss +bark-hissing-11 = Darknesss... safe... +bark-hissing-12 = Need to lure them in... +bark-hissing-13 = Yummy meat... +bark-hissing-14 = Ventsss... home... +bark-hissing-15 = Sssneaking up... +bark-hissing-16 = Hsss... shadows... +bark-hissing-17 = I am lurking... +bark-hissing-18 = Flesh... tasty... +bark-hissing-19 = Sssilent... deadly... +bark-hissing-20 = Hsssss... wait... +bark-hissing-21 = Creep closer... +bark-hissing-22 = Trap set... +bark-hissing-23 = Sssso close now... +bark-hissing-24 = Hsss... prey spotted... +bark-hissing-count = 24 diff --git a/Resources/Locale/en-US/random-barks/kobold.ftl b/Resources/Locale/en-US/random-barks/kobold.ftl new file mode 100644 index 0000000000..3168dbf9b7 --- /dev/null +++ b/Resources/Locale/en-US/random-barks/kobold.ftl @@ -0,0 +1,18 @@ +bark-kobold-1 = Grrr +bark-kobold-2 = Treasure, mine now +bark-kobold-3 = Hungry +bark-kobold-4 = More shinies! +bark-kobold-5 = Must collect stuff +bark-kobold-6 = Sneaky sneaky +bark-kobold-7 = Mine now +bark-kobold-8 = Yip yip +bark-kobold-9 = Scared +bark-kobold-10 = Hide +bark-kobold-11 = Yikes +bark-kobold-12 = Yip yip yip +bark-kobold-13 = Go away +bark-kobold-14 = Look, shiny +bark-kobold-15 = Need help +bark-kobold-16 = I see you +bark-kobold-17 = I don't like you +bark-kobold-count = 17 diff --git a/Resources/Locale/en-US/random-barks/mouse.ftl b/Resources/Locale/en-US/random-barks/mouse.ftl new file mode 100644 index 0000000000..88fd0ef04b --- /dev/null +++ b/Resources/Locale/en-US/random-barks/mouse.ftl @@ -0,0 +1,28 @@ +# Mice get a lot because they are a lot more common than e.g. cows or chickens, are understood by certain species and player-controlled mice, etc... +bark-mouse-1 = pi pi pi +bark-mouse-2 = pi pi pi pi +bark-mouse-3 = Cheese +bark-mouse-4 = Food +bark-mouse-5 = I'm hungry +bark-mouse-6 = Need cheese +bark-mouse-7 = Goddamn I love grilled cheese +bark-mouse-8 = Traps everywhere +bark-mouse-9 = Should find food +bark-mouse-10 = Where is the kitchen? +bark-mouse-11 = I'm so hungry +bark-mouse-12 = Squeak +bark-mouse-13 = Cheese now, please +bark-mouse-14 = I want food +bark-mouse-15 = Pi pi pi pi pi +bark-mouse-16 = Where's the cheese? +bark-mouse-17 = Snack time, please +bark-mouse-18 = Hungry! Need snack +bark-mouse-19 = Food? Anyone? +bark-mouse-20 = Cheese is life +bark-mouse-21 = Squeak squeak! More cheese +bark-mouse-22 = I'm still hungry +bark-mouse-23 = Pi? What's that? +bark-mouse-24 = Kitchen smells good +bark-mouse-25 = Cheese is missing +bark-mouse-26 = I'm going to form my own kingdom and overthrow the station command +bark-mouse-count = 26 diff --git a/Resources/Locale/en-US/random-barks/penguin.ftl b/Resources/Locale/en-US/random-barks/penguin.ftl new file mode 100644 index 0000000000..f6f1cc1242 --- /dev/null +++ b/Resources/Locale/en-US/random-barks/penguin.ftl @@ -0,0 +1,12 @@ +bark-penguin-1 = Wark wark +bark-penguin-2 = Fish! +bark-penguin-3 = Cold? +bark-penguin-4 = Waddle waddle +bark-penguin-5 = Gimme fish +bark-penguin-6 = Wark! +bark-penguin-7 = I miss water +bark-penguin-8 = Where's fish? +bark-penguin-9 = Can't fly +bark-penguin-10 = Wank wank wank +bark-penguin-11 = Wank wank wank wank wank wank wank +bark-penguin-count = 11 diff --git a/Resources/Locale/en-US/random-barks/possum.ftl b/Resources/Locale/en-US/random-barks/possum.ftl new file mode 100644 index 0000000000..c9f179e6cc --- /dev/null +++ b/Resources/Locale/en-US/random-barks/possum.ftl @@ -0,0 +1,24 @@ +# Possum <3 +bark-possum-1 = Hiss hiss +bark-possum-2 = Hiss hiss hiss +bark-possum-3 = Food +bark-possum-4 = Hungry +bark-possum-5 = More food +bark-possum-6 = Hiss +bark-possum-7 = Play dead +bark-possum-8 = Scared +bark-possum-9 = Hide +bark-possum-10 = Dark please +bark-possum-11 = Tree home +bark-possum-12 = Hiss hiss hiss hiss +bark-possum-13 = Food now +bark-possum-14 = Sleepy +bark-possum-15 = Hiss hiss hiss +bark-possum-16 = Nighttime good +bark-possum-17 = Fruit please +bark-possum-18 = No light +bark-possum-19 = Hiss hiss hiss hiss hiss +bark-possum-20 = Safe dark +bark-possum-21 = Need to hide +bark-possum-22 = Hiss... Stay away +bark-possum-count = 22 diff --git a/Resources/Locale/en-US/random-barks/raccoon.ftl b/Resources/Locale/en-US/random-barks/raccoon.ftl new file mode 100644 index 0000000000..5493ea255f --- /dev/null +++ b/Resources/Locale/en-US/random-barks/raccoon.ftl @@ -0,0 +1,16 @@ +bark-raccoon-1 = Chomp chomp +bark-raccoon-2 = Rummage +bark-raccoon-3 = Snack time +bark-raccoon-4 = Where's food +bark-raccoon-5 = I want shiny +bark-raccoon-6 = Oooo... lots of garbage +bark-raccoon-7 = Squeak! +bark-raccoon-8 = Need to eat +bark-raccoon-9 = Hungry now +bark-raccoon-10 = Ooh, shiny +bark-raccoon-11 = Should explore more +bark-raccoon-12 = Need to find food +bark-raccoon-13 = Yummy +bark-raccoon-14 = I’m curious +bark-raccoon-15 = A human called me a possum... I am not a possum +bark-raccoon-count = 15 diff --git a/Resources/Locale/en-US/reagents/mannitol.ftl b/Resources/Locale/en-US/reagents/mannitol.ftl new file mode 100644 index 0000000000..1d35aff587 --- /dev/null +++ b/Resources/Locale/en-US/reagents/mannitol.ftl @@ -0,0 +1 @@ +mannitol-effect-enlightened = You feel ENLIGHTENED! diff --git a/Resources/Locale/en-US/reagents/meta/consumable/drink/alcohol.ftl b/Resources/Locale/en-US/reagents/meta/consumable/drink/alcohol.ftl index 421dfe1484..98a6b7984a 100644 --- a/Resources/Locale/en-US/reagents/meta/consumable/drink/alcohol.ftl +++ b/Resources/Locale/en-US/reagents/meta/consumable/drink/alcohol.ftl @@ -37,8 +37,8 @@ reagent-desc-poison-wine = Is this even wine? Toxic! Hallucinogenic! Probably co reagent-name-rum = rum reagent-desc-rum = Distilled alcoholic drink made from sugarcane byproducts. -#reagent-name-sake = sake -#reagent-desc-sake = Alcoholic beverage made by fermenting rice that has been polished. +# reagent-name-sake = sake +# reagent-desc-sake = Alcoholic beverage made by fermenting rice that has been polished. reagent-name-tequila = tequila reagent-desc-tequila = A strong and mildly flavoured, mexican produced spirit. @@ -261,3 +261,21 @@ reagent-desc-whiskey-soda = For the more refined griffon. reagent-name-white-russian = white russian reagent-desc-white-russian = That's just, like, your opinion, man... + +reagent-name-vodka-red-bool = vodka red bool +reagent-desc-vodka-red-bool = Because heart failure and liver failure go hand in hand. + +reagent-name-xeno-basher = xeno basher +reagent-desc-xeno-basher = The perfect drink before an expedition. + +reagent-name-irish-bool = irish bool +reagent-desc-irish-bool = Like a bool in a Ireland shop. + +reagent-name-budget-insuls = budget insuls +reagent-desc-budget-insuls = A tider's preferred drink. + +reagent-name-watermelon-wakeup = watermelon wakeup +reagent-desc-watermelon-wakeup = If you want to be awake, this will do it... Also sweet. + +reagent-name-rubberneck = rubberneck +reagent-desc-rubberneck = A popular drink amongst those adhering to an all synthetic diet. diff --git a/Resources/Locale/en-US/reagents/meta/fun.ftl b/Resources/Locale/en-US/reagents/meta/fun.ftl index 8764a3d28a..a4a8c0f150 100644 --- a/Resources/Locale/en-US/reagents/meta/fun.ftl +++ b/Resources/Locale/en-US/reagents/meta/fun.ftl @@ -22,7 +22,7 @@ reagent-desc-razorium = A strange, non-newtonian chemical. It is produced when t reagent-name-fresium = Fresium reagent-desc-fresium = A mysterious compound that slows the vibration of atoms and molecules... somehow. In layman's terms, it makes things cold... REALLY cold. Can cause long-lasting movement issues if ingested. -reagent-name-laughter = Laughter +reagent-name-laughter = laughter reagent-desc-laughter = Some say that this is the best medicine, but recent studies have proven that to be untrue. reagent-name-weh = juice that makes you Weh diff --git a/Resources/Locale/en-US/reagents/meta/medicine.ftl b/Resources/Locale/en-US/reagents/meta/medicine.ftl index e02d428082..a84e8315fd 100644 --- a/Resources/Locale/en-US/reagents/meta/medicine.ftl +++ b/Resources/Locale/en-US/reagents/meta/medicine.ftl @@ -127,8 +127,17 @@ reagent-desc-pyrazine = Efficiently heals burns from the hottest of fires. Cause reagent-name-insuzine = insuzine reagent-desc-insuzine = Rapidly repairs dead tissue caused by electrocution, but cools you slightly. Completely freezes the patient when overdosed. +reagent-name-opporozidone = opporozidone +reagent-desc-opporozidone= A difficult to synthesize cryogenic drug used to regenerate rotting tissue and brain matter. + reagent-name-necrosol = necrosol reagent-desc-necrosol = A necrotic substance that seems to be able to heal frozen corpses. It can treat and rejuvenate plants when applied in small doses. reagent-name-aloxadone = aloxadone reagent-desc-aloxadone = A cryogenics chemical. Used to treat severe third degree burns via regeneration of the burnt tissue. Works regardless of the patient being alive or dead. + +reagent-name-mannitol = mannitol +reagent-desc-mannitol = Efficiently restores brain damage. + +reagent-name-psicodine = psicodine +reagent-desc-psicodine = Suppresses anxiety and other various forms of mental distress. Overdose causes hallucinations and minor toxin damage. diff --git a/Resources/Locale/en-US/reagents/meta/narcotics.ftl b/Resources/Locale/en-US/reagents/meta/narcotics.ftl index ea115bf962..d6da259501 100644 --- a/Resources/Locale/en-US/reagents/meta/narcotics.ftl +++ b/Resources/Locale/en-US/reagents/meta/narcotics.ftl @@ -13,9 +13,6 @@ reagent-desc-experimental-stimulants = A prototype version of the stimulant chem reagent-name-thc = THC reagent-desc-thc = The main psychoactive compound in cannabis. -reagent-name-thc-oil = THC oil -reagent-desc-thc-oil = Pure THC oil, extracted from the leaves of the cannabis plant. Much stronger than its natural form and can be used to numb chronic pain in patients. - reagent-name-bananadine = bananadine reagent-desc-bananadine = A mild psychedelic that is found in small traces in banana peels. @@ -39,3 +36,6 @@ reagent-desc-norepinephric-acid = A smooth chemical that blocks the optical rece reagent-name-tear-gas = tear gas reagent-desc-tear-gas = A chemical that causes severe irritation and crying, commonly used in riot control. + +reagent-name-happiness = happiness +reagent-desc-happiness = Fills you with ecstatic numbness and causes minor brain damage. Highly addictive. If overdosed causes sudden mood swings. diff --git a/Resources/Locale/en-US/reagents/meta/physical-desc.ftl b/Resources/Locale/en-US/reagents/meta/physical-desc.ftl index 064b21eaa9..a132a865a6 100644 --- a/Resources/Locale/en-US/reagents/meta/physical-desc.ftl +++ b/Resources/Locale/en-US/reagents/meta/physical-desc.ftl @@ -77,14 +77,12 @@ reagent-physical-desc-sickly = sickly reagent-physical-desc-skunky = skunky reagent-physical-desc-slimy = slimy reagent-physical-desc-soapy = soapy -reagent-physical-desc-soapy = soapy reagent-physical-desc-soothing = soothing reagent-physical-desc-sour = sour reagent-physical-desc-spicy = spicy reagent-physical-desc-starchy = starchy reagent-physical-desc-starry = starry reagent-physical-desc-sticky = sticky -reagent-physical-desc-strong-smelling = strong smelling reagent-physical-desc-strong-smelling = strong-smelling reagent-physical-desc-sugary = sugary reagent-physical-desc-sweet = sweet diff --git a/Resources/Locale/en-US/reagents/meta/toxins.ftl b/Resources/Locale/en-US/reagents/meta/toxins.ftl index fa2a813d1d..66bdbb480b 100644 --- a/Resources/Locale/en-US/reagents/meta/toxins.ftl +++ b/Resources/Locale/en-US/reagents/meta/toxins.ftl @@ -76,6 +76,9 @@ reagent-desc-vestine = Has an adverse reaction within the body causing major jit reagent-name-tazinide = tazinide reagent-desc-tazinide = A highly dangerous metallic mixture which can interfere with most movement through an electrifying current. +reagent-name-lipolicide = lipolicide +reagent-desc-lipolicide = A powerful toxin that will destroy fat cells, massively reducing body weight in a short time. Deadly to those without nutriment in their body. + reagent-name-soulbreaker-toxin = soulbreaker toxin reagent-desc-soulbreaker-toxin = An anti-psionic about 4 times as powerful as mindbreaker toxin. diff --git a/Resources/Locale/en-US/reagents/psicodine.ftl b/Resources/Locale/en-US/reagents/psicodine.ftl new file mode 100644 index 0000000000..c9795b11a9 --- /dev/null +++ b/Resources/Locale/en-US/reagents/psicodine.ftl @@ -0,0 +1,3 @@ +psicodine-effect-fearless = You feel totally fearless! +psicodine-effect-anxieties-wash-away = All of your anxieties wash away! +psicodine-effect-at-peace = You feel completely at peace. diff --git a/Resources/Locale/en-US/research/components/robotics-console.ftl b/Resources/Locale/en-US/research/components/robotics-console.ftl new file mode 100644 index 0000000000..978fa9a43c --- /dev/null +++ b/Resources/Locale/en-US/research/components/robotics-console.ftl @@ -0,0 +1,19 @@ +robotics-console-window-title = Robotics Console +robotics-console-no-cyborgs = No Cyborgs! + +robotics-console-select-cyborg = Select a cyborg above. +robotics-console-model = [color=gray]Model:[/color] {$name} +# name is not formatted to prevent players trolling +robotics-console-designation = [color=gray]Designation:[/color] +robotics-console-battery = [color=gray]Battery charge:[/color] [color={$color}]{$charge}[/color]% +robotics-console-modules = [color=gray]Modules installed:[/color] {$count} +robotics-console-brain = [color=gray]Brain installed:[/color] [color={$brain -> + [true] green]Yes + *[false] red]No +}[/color] + +robotics-console-locked-message = Controls locked, swipe ID. +robotics-console-disable = Disable +robotics-console-destroy = Destroy + +robotics-console-cyborg-destroyed = The cyborg {$name} has been remotely destroyed. diff --git a/Resources/Locale/en-US/research/technologies.ftl b/Resources/Locale/en-US/research/technologies.ftl index 96cb203911..fe7293d848 100644 --- a/Resources/Locale/en-US/research/technologies.ftl +++ b/Resources/Locale/en-US/research/technologies.ftl @@ -43,9 +43,10 @@ research-technology-basic-xenoarcheology = Basic XenoArcheology research-technology-alternative-research = Alternative Research research-technology-magnets-tech = Localized Magnetism research-technology-advanced-parts = Advanced Parts +research-technology-advanced-bluespace = Advanced Bluespace Research research-technology-anomaly-harnessing = Anomaly Core Harnessing research-technology-grappling = Grappling -research-technology-abnormal-artifact-manipulation = Abnormal Artifact Manipulation +research-technology-abnormal-artifact-manipulation = Artifact Recycling research-technology-gravity-manipulation = Gravity Manipulation research-technology-quantum-leaping = Quantum Leaping research-technology-advanced-anomaly-research = Advanced Anomaly Research diff --git a/Resources/Locale/en-US/revenant/revenant.ftl b/Resources/Locale/en-US/revenant/revenant.ftl index f4ea6d79ff..1d6bae7b34 100644 --- a/Resources/Locale/en-US/revenant/revenant.ftl +++ b/Resources/Locale/en-US/revenant/revenant.ftl @@ -16,7 +16,7 @@ revenant-soul-yield-low = {CAPITALIZE(THE($target))} has a below average soul. revenant-soul-begin-harvest = {CAPITALIZE(THE($target))} suddenly rises slightly into the air, {POSS-ADJ($target)} skin turning an ashy gray. revenant-soul-finish-harvest = {CAPITALIZE(THE($target))} slumps onto the ground! -#UI +# UI revenant-user-interface-title = Ability Shop revenant-user-interface-essence-amount = [color=plum]{$amount}[/color] Stolen Essence diff --git a/Resources/Locale/en-US/salvage/salvage-magnet.ftl b/Resources/Locale/en-US/salvage/salvage-magnet.ftl index 7ce2a486de..027afffef2 100644 --- a/Resources/Locale/en-US/salvage/salvage-magnet.ftl +++ b/Resources/Locale/en-US/salvage/salvage-magnet.ftl @@ -17,6 +17,8 @@ salvage-magnet-resources = {$resource -> [OrePlasma] Plasma [OreUranium] Uranium [OreArtifactFragment] Artifact fragments + [OreBluespace] Bluespace crystals + [OreNormality] Normality crystals *[other] {$resource} } diff --git a/Resources/Locale/en-US/seeds/seeds.ftl b/Resources/Locale/en-US/seeds/seeds.ftl index b398378288..9abfcdaff1 100644 --- a/Resources/Locale/en-US/seeds/seeds.ftl +++ b/Resources/Locale/en-US/seeds/seeds.ftl @@ -1,4 +1,5 @@ # Nouns +# Nouns seeds-noun-seeds = seeds seeds-noun-spores = spores @@ -57,6 +58,8 @@ seeds-eggy-name = egg-plant seeds-eggy-display-name = egg-plants seeds-cannabis-name = cannabis seeds-cannabis-display-name = cannabis +seeds-rainbow-cannabis-name = rainbow cannabis +seeds-rainbow-cannabis-display-name = rainbow cannabis seeds-tobacco-name = tobacco seeds-tobacco-display-name = tobacco plant seeds-nettle-name = nettle @@ -64,7 +67,7 @@ seeds-nettle-display-name = nettles seeds-deathnettle-name = death nettle seeds-deathnettle-display-name = death nettles seeds-chili-name = chili -seeds-chili-display-name = chilis +seeds-chili-display-name = chili peppers seeds-chilly-name = chilly seeds-chilly-display-name = chilly peppers seeds-poppy-name = poppy @@ -111,3 +114,5 @@ seeds-pumpkin-name = pumpkin seeds-pumpkin-display-name = pumpkins seeds-cotton-name = cotton seeds-cotton-display-name = cotton plant +seeds-pyrotton-name = pyrotton +seeds-pyrotton-display-name = pyrotton plant diff --git a/Resources/Locale/en-US/shuttles/thruster.ftl b/Resources/Locale/en-US/shuttles/thruster.ftl index 94035811c7..faed6e8dd2 100644 --- a/Resources/Locale/en-US/shuttles/thruster.ftl +++ b/Resources/Locale/en-US/shuttles/thruster.ftl @@ -3,3 +3,5 @@ thruster-comp-disabled = The thruster is turned [color=red]off[/color]. thruster-comp-nozzle-direction = The nozzle is facing [color=yellow]{$direction}[/color]. thruster-comp-nozzle-exposed = The nozzle [color=green]exposed[/color] to space. thruster-comp-nozzle-not-exposed = The nozzle [color=red]is not exposed[/color] to space. + +thruster-comp-upgrade-thrust = Thrust strength diff --git a/Resources/Locale/en-US/silicons/cyberlimbs.ftl b/Resources/Locale/en-US/silicons/cyberlimbs.ftl index b7686b0a2d..ed0212d042 100644 --- a/Resources/Locale/en-US/silicons/cyberlimbs.ftl +++ b/Resources/Locale/en-US/silicons/cyberlimbs.ftl @@ -1,77 +1,237 @@ marking-MobIPCHeadDefault = Standard Operational Monitor +marking-MobIPCHeadDefault-head_m = Head marking-MobIPCTorsoDefault = Standard Robotic Chassis +marking-MobIPCTorsoDefault-torso_m = Chest marking-MobIPCTorsoFemaleDefault = Standard Robotic Chassis -marking-MobIPCLArmDefault = Standard Left Robotic Arm +marking-MobIPCTorsoFemaleDefault-torso_f = Chest +marking-MobIPCLArmDefault = Standard Left Robotic Arm +marking-MobIPCLArmDefault-l_arm = Left Arm marking-MobIPCLHandDefault = Standard Left Robotic Hand -marking-MobIPCLLegDefault = Standard Left Robotic Leg +marking-MobIPCLHandDefault-l_hand = Left Hand +marking-MobIPCLLegDefault = Standard Left Robotic Leg +marking-MobIPCLLegDefault-l_leg = Left Leg marking-MobIPCLFootDefault = Standard Left Robotic Foot +marking-MobIPCLFootDefault-l_foot = Left Foot marking-MobIPCRArmDefault = Standard Right Robotic Arm +marking-MobIPCRArmDefault-r_arm = Right Arm marking-MobIPCRHandDefault = Standard Right Robotic Hand +marking-MobIPCRHandDefault-r_hand = Right Hand marking-MobIPCRLegDefault = Standard Right Robotic Leg +marking-MobIPCRLegDefault-r_leg = Right Leg marking-MobIPCRFootDefault = Standard Right Robotic Foot +marking-MobIPCRFootDefault-r_foot = Right Foot marking-CyberLimbsMarkingBishopHead = Operational Monitor from Bishop Cybernetics +marking-CyberLimbsMarkingBishopHead-head = Primary Monitor +marking-CyberLimbsMarkingBishopHead-head-2 = Secondary Monitor +marking-CyberLimbsMarkingBishopHeadAlt = Head from Bishop Cybernetics +marking-CyberLimbsMarkingBishopHeadAlt-head = Head +marking-CyberLimbsMarkingBishopHeadAlt1 = Alternate Head from Bishop Cybernetics +marking-CyberLimbsMarkingBishopHeadAlt1-head = Head marking-CyberLimbsMarkingBishopChest = Robotic Chassis from Bishop Cybernetics +marking-CyberLimbsMarkingBishopChest-torso-primary = Primary Torso +marking-CyberLimbsMarkingBishopChest-torso-secondary = Secondary Torso marking-CyberLimbsMarkingBishopLArm = Left Robotic Arm from Bishop Cybernetics +marking-CyberLimbsMarkingBishopLArm-l_arm-primary = Primary Arm +marking-CyberLimbsMarkingBishopLArm-l_arm-secondary = Secondary Arm +marking-CyberLimbsMarkingBishopLArm-l_arm-tertiary = Tertiary Arm marking-CyberLimbsMarkingBishopLHand = Left Robotic Hand from Bishop Cybernetics +marking-CyberLimbsMarkingBishopLHand-l_hand = Left Hand marking-CyberLimbsMarkingBishopLLeg = Left Robotic Leg from Bishop Cybernetics +marking-CyberLimbsMarkingBishopLLeg-l_leg-primary = Primary Leg +marking-CyberLimbsMarkingBishopLLeg-l_leg-secondary = Secondary Leg marking-CyberLimbsMarkingBishopLFoot = Left Robotic Foot from Bishop Cybernetics +marking-CyberLimbsMarkingBishopLFoot-l_foot = Left Foot marking-CyberLimbsMarkingBishopRArm = Right Robotic Arm from Bishop Cybernetics +marking-CyberLimbsMarkingBishopRArm-r_arm-primary = Primary Arm +marking-CyberLimbsMarkingBishopRArm-r_arm-secondary = Secondary Arm +marking-CyberLimbsMarkingBishopRArm-r_arm-tertiary = Tertiary Arm marking-CyberLimbsMarkingBishopRHand = Right Robotic Hand from Bishop Cybernetics +marking-CyberLimbsMarkingBishopRHand-r_hand = Hand marking-CyberLimbsMarkingBishopRLeg = Right Robotic Leg from Bishop Cybernetics +marking-CyberLimbsMarkingBishopRLeg-r_leg-primary = Primary Leg +marking-CyberLimbsMarkingBishopRLeg-r_leg-secondary = Secondary Leg marking-CyberLimbsMarkingBishopRFoot = Right Robotic Foot from Bishop Cybernetics +marking-CyberLimbsMarkingBishopRFoot-r_foot = Right Foot marking-CyberLimbsMarkingHesphiastosHead = Operational Monitor from Hesphiastos Industries +marking-CyberLimbsMarkingHesphiastosHead-head-1 = Primary Head +marking-CyberLimbsMarkingHesphiastosHead-head-2 = Primary Head +marking-CyberLimbsMarkingHesphiastosHeadAlt = Head from Hesphiastos Industries +marking-CyberLimbsMarkingHesphiastosHeadAlt-head-1 = Primary Head +marking-CyberLimbsMarkingHesphiastosHeadAlt-head-2 = Secondary Head +marking-CyberLimbsMarkingHesphiastosHeadAlt-head-3 = Tertiary Head marking-CyberLimbsMarkingHesphiastosChest = Robotic Chassis from Hesphiastos Industries +marking-CyberLimbsMarkingHesphiastosChest-torso-1 = Primary Chest +marking-CyberLimbsMarkingHesphiastosChest-torso-2 = Secondary Chest marking-CyberLimbsMarkingHesphiastosLArm = Left Robotic Arm from Hesphiastos Industries +marking-CyberLimbsMarkingHesphiastosLArm-l_arm-1 = Primary Arm +marking-CyberLimbsMarkingHesphiastosLArm-l_arm-2 = Secondary Arm marking-CyberLimbsMarkingHesphiastosLHand = Left Robotic Hand from Hesphiastos Industries +marking-CyberLimbsMarkingHesphiastosLHand-l_hand-1 = Primary Hand +marking-CyberLimbsMarkingHesphiastosLHand-l_hand-2 = Secondary Hand marking-CyberLimbsMarkingHesphiastosLLeg = Left Robotic Leg from Hesphiastos Industries +marking-CyberLimbsMarkingHesphiastosLLeg-l_leg-1 = Primary Leg +marking-CyberLimbsMarkingHesphiastosLLeg-l_leg-2 = Secondary Leg marking-CyberLimbsMarkingHesphiastosLFoot = Left Robotic Foot from Hesphiastos Industries +marking-CyberLimbsMarkingHesphiastosLFoot-l_foot-1 = Primary Foot +marking-CyberLimbsMarkingHesphiastosLFoot-l_foot-2 = Secondary Foot marking-CyberLimbsMarkingHesphiastosRArm = Right Robotic Arm from Hesphiastos Industries +marking-CyberLimbsMarkingHesphiastosRArm-r_arm-1 = Primary Arm +marking-CyberLimbsMarkingHesphiastosRArm-r_arm-2 = Secondary Arm marking-CyberLimbsMarkingHesphiastosRHand = Right Robotic Hand from Hesphiastos Industries +marking-CyberLimbsMarkingHesphiastosRHand-r_hand-1 = Primary Hand +marking-CyberLimbsMarkingHesphiastosRHand-r_hand-2 = Secondary Hand marking-CyberLimbsMarkingHesphiastosRLeg = Right Robotic Leg from Hesphiastos Industries +marking-CyberLimbsMarkingHesphiastosRLeg-r_leg-1 = Primary Leg +marking-CyberLimbsMarkingHesphiastosRLeg-r_leg-2 = Secondary Leg marking-CyberLimbsMarkingHesphiastosRFoot = Right Robotic Foot from Hesphiastos Industries +marking-CyberLimbsMarkingHesphiastosRFoot-r_foot-1 = Primary Foot +marking-CyberLimbsMarkingHesphiastosRFoot-r_foot-2 = Secondary Foot marking-CyberLimbsMarkingWardtakahashiHead = Operational Monitor from Ward-Takahashi +marking-CyberLimbsMarkingWardtakahashiHead-head = Head +marking-CyberLimbsMarkingWardtakahashiHeadAlt = Head from Ward-Takahashi +marking-CyberLimbsMarkingWardtakahashiHeadAlt-head = Head +marking-CyberLimbsMarkingWardtakahashiHeadAlt1 = Alternate Head from Ward-Takahashi +marking-CyberLimbsMarkingWardtakahashiHeadAlt1-head = Head marking-CyberLimbsMarkingWardtakahashiChest = Robotic Chassis from Ward-Takahashi +marking-CyberLimbsMarkingWardtakahashiChest-torso = Chest marking-CyberLimbsMarkingWardtakahashiLArm = Left Robotic Arm from Ward-Takahashi +marking-CyberLimbsMarkingWardtakahashiLArm-l_arm = Left Arm marking-CyberLimbsMarkingWardtakahashiLHand = Left Robotic Hand from Ward-Takahashi +marking-CyberLimbsMarkingWardtakahashiLHand-l_hand = Left Hand marking-CyberLimbsMarkingWardtakahashiLLeg = Left Robotic Leg from Ward-Takahashi +marking-CyberLimbsMarkingWardtakahashiLLeg-l_leg = Left Leg marking-CyberLimbsMarkingWardtakahashiLFoot = Left Robotic Foot from Ward-Takahashi +marking-CyberLimbsMarkingWardtakahashiLFoot-l_foot = Left Foot marking-CyberLimbsMarkingWardtakahashiRArm = Right Robotic Arm from Ward-Takahashi +marking-CyberLimbsMarkingWardtakahashiRArm-r_arm = Right Arm marking-CyberLimbsMarkingWardtakahashiRHand = Right Robotic Hand from Ward-Takahashi +marking-CyberLimbsMarkingWardtakahashiRHand-r_hand = Right Hand marking-CyberLimbsMarkingWardtakahashiRLeg = Right Robotic Leg from Ward-Takahashi +marking-CyberLimbsMarkingWardtakahashiRLeg-r_leg = Right Leg marking-CyberLimbsMarkingWardtakahashiRFoot = Right Robotic Foot from Ward-Takahashi +marking-CyberLimbsMarkingWardtakahashiRFoot-r_foot = Right Foot marking-CyberLimbsMarkingXionHead = Operational Monitor from Xion Manufacturing Group +marking-CyberLimbsMarkingXionHead-head-1 = Primary Head +marking-CyberLimbsMarkingXionHead-head-2 = Secondary Head +marking-CyberLimbsMarkingXionHeadAlt = Head from Xion Manufacturing Group +marking-CyberLimbsMarkingXionHeadAlt-head-1 = Primary Head +marking-CyberLimbsMarkingXionHeadAlt-head-2 = Secondary Head marking-CyberLimbsMarkingXionChest = Robotic Chassis from Xion Manufacturing Group +marking-CyberLimbsMarkingXionChest-torso-1 = Primary Chest +marking-CyberLimbsMarkingXionChest-torso-2 = Secondary Chest marking-CyberLimbsMarkingXionLArm = Left Robotic Arm from Xion Manufacturing Group +marking-CyberLimbsMarkingXionLArm-l_arm-1 = Primary Arm +marking-CyberLimbsMarkingXionLArm-l_arm-2 = Secondary Arm marking-CyberLimbsMarkingXionLHand = Left Robotic Hand from Xion Manufacturing Group +marking-CyberLimbsMarkingXionLHand-l_hand-1 = Primary Hand +marking-CyberLimbsMarkingXionLHand-l_hand-2 = Secondary Hand marking-CyberLimbsMarkingXionLLeg = Left Robotic Leg from Xion Manufacturing Group +marking-CyberLimbsMarkingXionLLeg-l_leg-1 = Primary Leg +marking-CyberLimbsMarkingXionLLeg-l_leg-2 = Secondary Leg marking-CyberLimbsMarkingXionLFoot = Left Robotic Foot from Xion Manufacturing Group +marking-CyberLimbsMarkingXionLFoot-l_foot-1 = Primary Foot +marking-CyberLimbsMarkingXionLFoot-l_foot-2 = Secondary Foot marking-CyberLimbsMarkingXionRArm = Right Robotic Arm from Xion Manufacturing Group +marking-CyberLimbsMarkingXionRArm-r_arm-1 = Primary Arm +marking-CyberLimbsMarkingXionRArm-r_arm-2 = Secondary Arm marking-CyberLimbsMarkingXionRHand = Right Robotic Hand from Xion Manufacturing Group +marking-CyberLimbsMarkingXionRHand-r_hand-1 = Primary Hand +marking-CyberLimbsMarkingXionRHand-r_hand-2 = Secondary Hand marking-CyberLimbsMarkingXionRLeg = Right Robotic Leg from Xion Manufacturing Group +marking-CyberLimbsMarkingXionRLeg-r_leg-1 = Primary Leg +marking-CyberLimbsMarkingXionRLeg-r_leg-2 = Secondary Leg marking-CyberLimbsMarkingXionRFoot = Right Robotic Foot from Xion Manufacturing Group +marking-CyberLimbsMarkingXionRFoot-r_foot-1 = Primary Foot +marking-CyberLimbsMarkingXionRFoot-r_foot-2 = Secondary Foot marking-CyberLimbsMarkingShellguardHead = Operational Monitor from Shellguard Munitions +marking-CyberLimbsMarkingShellguardHead-head-1 = Primary Head +marking-CyberLimbsMarkingShellguardHead-head-2 = Secondary Head +marking-CyberLimbsMarkingShellguardHeadAlt = Head from Shellguard Munitions +marking-CyberLimbsMarkingShellguardHeadAlt-head-1 = Primary Head +marking-CyberLimbsMarkingShellguardHeadAlt-head-2 = Secondary Head marking-CyberLimbsMarkingShellguardChest = Robotic Chassis from Shellguard Munitions +marking-CyberLimbsMarkingShellguardChest-torso-1 = Primary Chest +marking-CyberLimbsMarkingShellguardChest-torso-2 = Secondary Chest marking-CyberLimbsMarkingShellguardLArm = Left Robotic Arm from Shellguard Munitions +marking-CyberLimbsMarkingShellguardLArm-l_arm-1 = Primary Arm +marking-CyberLimbsMarkingShellguardLArm-l_arm-2 = Secondary Arm marking-CyberLimbsMarkingShellguardLHand = Left Robotic Hand from Shellguard Munitions +marking-CyberLimbsMarkingShellguardLHand-l_hand-1 = Primary Hand +marking-CyberLimbsMarkingShellguardLHand-l_hand-2 = Secondary Hand marking-CyberLimbsMarkingShellguardLLeg = Left Robotic Leg from Shellguard Munitions +marking-CyberLimbsMarkingShellguardLLeg-l_leg-1 = Primary Leg +marking-CyberLimbsMarkingShellguardLLeg-l_leg-2 = Secondary Leg marking-CyberLimbsMarkingShellguardLFoot = Left Robotic Foot from Shellguard Munitions +marking-CyberLimbsMarkingShellguardLFoot-l_foot-1 = Primary Foot +marking-CyberLimbsMarkingShellguardLFoot-l_foot-2 = Secondary Foot marking-CyberLimbsMarkingShellguardRArm = Right Robotic Arm from Shellguard Munitions +marking-CyberLimbsMarkingShellguardRArm-r_arm-1 = Primary Arm +marking-CyberLimbsMarkingShellguardRArm-r_arm-2 = Secondary Arm marking-CyberLimbsMarkingShellguardRHand = Right Robotic Hand from Shellguard Munitions +marking-CyberLimbsMarkingShellguardRHand-r_hand-1 = Primary Hand +marking-CyberLimbsMarkingShellguardRHand-r_hand-2 = Secondary Hand marking-CyberLimbsMarkingShellguardRLeg = Right Robotic Leg from Shellguard Munitions +marking-CyberLimbsMarkingShellguardRLeg-r_leg-1 = Primary Leg +marking-CyberLimbsMarkingShellguardRLeg-r_leg-2 = Secondary Leg marking-CyberLimbsMarkingShellguardRFoot = Right Robotic Foot from Shellguard Munitions +marking-CyberLimbsMarkingShellguardRFoot-r_foot-1 = Primary Foot +marking-CyberLimbsMarkingShellguardRFoot-r_foot-2 = Secondary Foot marking-CyberLimbsMarkingMorpheusHead = Operational Monitor from Morpheus Cyberkinetics +marking-CyberLimbsMarkingMorpheusHead-head = Head +marking-CyberLimbsMarkingMorpheusHeadAlt = Head from Morpheus Cyberkinetics +marking-CyberLimbsMarkingMorpheusHeadAlt-head = Head marking-CyberLimbsMarkingMorpheusChest = Robotic Chassis from Morpheus Cyberkinetics +marking-CyberLimbsMarkingMorpheusChest-torso = Chest marking-CyberLimbsMarkingMorpheusLArm = Left Robotic Arm from Morpheus Cyberkinetics +marking-CyberLimbsMarkingMorpheusLArm-l_arm = Left Arm marking-CyberLimbsMarkingMorpheusLHand = Left Robotic Hand from Morpheus Cyberkinetics +marking-CyberLimbsMarkingMorpheusLHand-l_hand = Left Hand marking-CyberLimbsMarkingMorpheusLLeg = Left Robotic Leg from Morpheus Cyberkinetics +marking-CyberLimbsMarkingMorpheusLLeg-l_leg = Left Leg marking-CyberLimbsMarkingMorpheusLFoot = Left Robotic Foot from Morpheus Cyberkinetics +marking-CyberLimbsMarkingMorpheusLFoot-l_foot = Left Foot marking-CyberLimbsMarkingMorpheusRArm = Right Robotic Arm from Morpheus Cyberkinetics +marking-CyberLimbsMarkingMorpheusRArm-r_arm = Right Arm marking-CyberLimbsMarkingMorpheusRHand = Right Robotic Hand from Morpheus Cyberkinetics +marking-CyberLimbsMarkingMorpheusRHand-r_hand = Right Hand marking-CyberLimbsMarkingMorpheusRLeg = Right Robotic Leg from Morpheus Cyberkinetics +marking-CyberLimbsMarkingMorpheusRLeg-r_leg = Right Leg marking-CyberLimbsMarkingMorpheusRFoot = Right Robotic Foot from Morpheus Cyberkinetics +marking-CyberLimbsMarkingMorpheusRFoot-r_foot = Right Foot + +marking-CyberLimbsMarkingZenghuHead = Head from Zenghu Pharmaceuticals +marking-CyberLimbsMarkingZenghuHead-head-1 = Primary Head +marking-CyberLimbsMarkingZenghuHead-head-2 = Secondary Head +marking-CyberLimbsMarkingZenghuChest = Robotic Chassis from Zenghu Pharmaceuticals +marking-CyberLimbsMarkingZenghuChest-torso-1 = Primary Chest +marking-CyberLimbsMarkingZenghuChest-torso-2 = Secondary Chest +marking-CyberLimbsMarkingZenghuLArm = Left Robotic Arm from Zenghu Pharmaceuticals +marking-CyberLimbsMarkingZenghuLArm-l_arm-1 = Primary Arm +marking-CyberLimbsMarkingZenghuLArm-l_arm-2 = Secondary Arm +marking-CyberLimbsMarkingZenghuLHand = Left Robotic Hand from Zenghu Pharmaceuticals +marking-CyberLimbsMarkingZenghuLHand-l_hand-1 = Primary Hand +marking-CyberLimbsMarkingZenghuLHand-l_hand-2 = Secondary Hand +marking-CyberLimbsMarkingZenghuLLeg = Left Robotic Leg from Zenghu Pharmaceuticals +marking-CyberLimbsMarkingZenghuLLeg-l_leg-1 = Primary Leg +marking-CyberLimbsMarkingZenghuLLeg-l_leg-2 = Secondary Leg +marking-CyberLimbsMarkingZenghuLFoot = Left Robotic Foot from Zenghu Pharmaceuticals +marking-CyberLimbsMarkingZenghuLFoot-l_foot-1 = Primary Foot +marking-CyberLimbsMarkingZenghuLFoot-l_foot-2 = Secondary Foot +marking-CyberLimbsMarkingZenghuRArm = Right Robotic Arm from Zenghu Pharmaceuticals +marking-CyberLimbsMarkingZenghuRArm-r_arm-1 = Primary Arm +marking-CyberLimbsMarkingZenghuRArm-r_arm-2 = Secondary Arm +marking-CyberLimbsMarkingZenghuRHand = Right Robotic Hand from Zenghu Pharmaceuticals +marking-CyberLimbsMarkingZenghuRHand-r_hand-1 = Primary Hand +marking-CyberLimbsMarkingZenghuRHand-r_hand-2 = Secondary Hand +marking-CyberLimbsMarkingZenghuRLeg = Right Robotic Leg from Zenghu Pharmaceuticals +marking-CyberLimbsMarkingZenghuRLeg-r_leg-1 = Primary Leg +marking-CyberLimbsMarkingZenghuRLeg-r_leg-2 = Secondary Leg +marking-CyberLimbsMarkingZenghuRFoot = Right Robotic Foot from Zenghu Pharmaceuticals +marking-CyberLimbsMarkingZenghuRFoot-r_foot-1 = Primary Foot +marking-CyberLimbsMarkingZenghuRFoot-r_foot-2 = Secondary Foot diff --git a/Resources/Locale/en-US/singularity/components/emitter-component.ftl b/Resources/Locale/en-US/singularity/components/emitter-component.ftl index c71b3d6bdf..c7db1a93bb 100644 --- a/Resources/Locale/en-US/singularity/components/emitter-component.ftl +++ b/Resources/Locale/en-US/singularity/components/emitter-component.ftl @@ -11,5 +11,8 @@ comp-emitter-turned-off = The {$target} turns off. # Shows if the user attempts to activate the emitter while it's un-anchored. comp-emitter-not-anchored = The {$target} isn't anchored to the ground! +# Upgrades +emitter-component-upgrade-fire-rate = fire rate + emitter-component-current-type = The current selected type is: {$type}. emitter-component-type-set = Type set to: {$type} diff --git a/Resources/Locale/en-US/sleep/narcolepsy.ftl b/Resources/Locale/en-US/sleep/narcolepsy.ftl new file mode 100644 index 0000000000..c8e130fe18 --- /dev/null +++ b/Resources/Locale/en-US/sleep/narcolepsy.ftl @@ -0,0 +1,9 @@ +narcolepsy-warning-popup-1 = A wave of drowsiness washes over you, making it hard to focus... +narcolepsy-warning-popup-2 = Your surroundings blur as a sudden fatigue pulls at your consciousness... +narcolepsy-warning-popup-3 = Your head feels heavy, and you struggle to resist the pull of slumber... +narcolepsy-warning-popup-4 = Time seems to stretch, and you can’t help but feel the lure of a quick nap... +narcolepsy-warning-popup-5 = Your body grows heavy, and you instinctively seek a comfortable place to rest... + +narcolepsy-wakeup-popup-1 = You awaken, feeling refreshed and alert once more. +narcolepsy-wakeup-popup-2 = The world comes into focus as you shake off the last vestiges of slumber. +narcolepsy-wakeup-popup-3 = Your mind clears up, leaving the feeling of drowsiness behind. diff --git a/Resources/Locale/en-US/species/namepreset.ftl b/Resources/Locale/en-US/species/namepreset.ftl index 5a42c87b78..c79f3d91a4 100644 --- a/Resources/Locale/en-US/species/namepreset.ftl +++ b/Resources/Locale/en-US/species/namepreset.ftl @@ -2,3 +2,5 @@ namepreset-first = {$first} namepreset-firstlast = {$first} {$last} namepreset-firstdashfirst = {$first1}-{$first2} namepreset-thefirstoflast = The {$first} of {$last} +## Parkstation IPC +namepreset-firstdashlast = {$first}-{$last} diff --git a/Resources/Locale/en-US/species/shadowkin.ftl b/Resources/Locale/en-US/species/shadowkin.ftl new file mode 100644 index 0000000000..ebc56487b7 --- /dev/null +++ b/Resources/Locale/en-US/species/shadowkin.ftl @@ -0,0 +1,15 @@ +shadowkin-power-examined-other = {CAPITALIZE(SUBJECT($target))} seems to be {$powerType}. +shadowkin-power-examined-self = I have {$power}/{$powerMax} energy, I am {$powerType}. + +shadowkin-power-5 = energetic +shadowkin-power-4 = great +shadowkin-power-3 = good +shadowkin-power-2 = okay +shadowkin-power-1 = exhausted +shadowkin-power-0 = drained + +examine-mindbroken-shadowkin-message = {CAPITALIZE($entity)} seems to be a blackeye. + +identity-eye-shadowkin = {$color}-eye + +shadowkin-blackeye = I feel my power draining away... \ No newline at end of file diff --git a/Resources/Locale/en-US/species/species.ftl b/Resources/Locale/en-US/species/species.ftl index 79ce7fea6a..9278267cc4 100644 --- a/Resources/Locale/en-US/species/species.ftl +++ b/Resources/Locale/en-US/species/species.ftl @@ -2,7 +2,7 @@ species-name-human = Human species-name-dwarf = Dwarf -species-name-reptilian = Reptilian +species-name-reptilian = Unathi species-name-slime = Slime Person species-name-diona = Diona species-name-arachnid = Arachnid @@ -10,3 +10,5 @@ species-name-arachne = Arachne species-name-moth = Moth Person species-name-skeleton = Skeleton species-name-vox = Vox +species-name-ipc = IPC +species-name-shadowkin = Shadowkin \ No newline at end of file diff --git a/Resources/Locale/en-US/speech/speech-liar.ftl b/Resources/Locale/en-US/speech/speech-liar.ftl new file mode 100644 index 0000000000..4f157d2e23 --- /dev/null +++ b/Resources/Locale/en-US/speech/speech-liar.ftl @@ -0,0 +1,132 @@ +liar-word-1 = yes +liar-word-replacement-1 = no + +liar-word-2 = no +liar-word-replacement-2 = yes + +liar-word-3 = yeah +liar-word-replacement-3 = nah + +liar-word-4 = nah +liar-word-replacement-4 = yeah + +liar-word-5 = yep +liar-word-replacement-5 = nope + +liar-word-6 = nope +liar-word-replacement-6 = yep + +liar-word-7 = sure +liar-word-replacement-7 = nah + +liar-word-8 = was +liar-word-replacement-8 = wasnt + +liar-word-9 = wasnt +liar-word-replacement-9 = was + +liar-word-10 = was +liar-word-replacement-10 = wasnt + +liar-word-11 = is +liar-word-replacement-11 = isnt + +liar-word-12 = will +liar-word-replacement-12 = wont + +liar-word-13 = dont +liar-word-replacement-13 = "" + +liar-word-14 = can +liar-word-replacement-14 = cant + +liar-word-15 = cant +liar-word-replacement-15 = can + +liar-word-16 = should +liar-word-replacement-16 = shouldnt + +liar-word-17 = dead +liar-word-replacement-17 = alive + +liar-word-18 = alive +liar-word-replacement-18 = dead + +liar-word-19 = does +liar-word-replacement-19 = doesnt + +liar-word-20 = did +liar-word-replacement-20 = didnt + +liar-word-21 = didnt +liar-word-replacement-21 = "" + +liar-word-22 = nothing +liar-word-replacement-22 = something + +liar-word-23 = something +liar-word-replacement-23 = nothing + +liar-word-24 = somebody +liar-word-replacement-24 = nobody + +liar-word-25 = nobody +liar-word-replacement-25 = somebody + +liar-word-26 = can +liar-word-replacement-26 = "can't" + +liar-word-27 = "can't" +liar-word-replacement-27 = can + +liar-word-28 = should +liar-word-replacement-28 = "shouldn't" + +liar-word-29 = do +liar-word-replacement-29 = "don't" + +liar-word-30 = "don't" +liar-word-replacement-30 = "" + +liar-word-31 = does +liar-word-replacement-31 = "doesn't" + +liar-word-32 = did +liar-word-replacement-32 = "didn't" + +liar-word-33 = "didn't" +liar-word-replacement-33 = did + +liar-word-34 = ye +liar-word-34-2 = ya +liar-word-replacement-34 = na + +liar-word-35 = na +liar-word-replacement-35 = ye + +liar-word-36 = yuh +liar-word-replacement-36 = nuh + +liar-word-37 = nuh +liar-word-replacement-37 = yuh + +liar-word-38 = love +liar-word-replacement-38 = hate + +liar-word-39 = hate +liar-word-replacement-39 = love + +liar-word-40 = like +liar-word-replacement-40 = don't like + +liar-word-41 = good +liar-word-replacement-41 = bad + +liar-word-42 = bad +liar-word-replacement-42 = good + +liar-word-43 = want +liar-word-replacement-43 = "don't want" + +liar-word-44 = not +liar-word-replacement-44 = "" diff --git a/Resources/Locale/en-US/station-events/events/intercept.ftl b/Resources/Locale/en-US/station-events/events/intercept.ftl new file mode 100644 index 0000000000..3f84b027be --- /dev/null +++ b/Resources/Locale/en-US/station-events/events/intercept.ftl @@ -0,0 +1 @@ +station-event-communication-interception = Attention! Enemy communication intercepted. Security level elevated. diff --git a/Resources/Locale/en-US/station-events/events/unknown-shuttle.ftl b/Resources/Locale/en-US/station-events/events/unknown-shuttle.ftl new file mode 100644 index 0000000000..c94b32c54c --- /dev/null +++ b/Resources/Locale/en-US/station-events/events/unknown-shuttle.ftl @@ -0,0 +1 @@ +station-event-unknown-shuttle-incoming = Attention! An unidentified space shuttle has been spotted approaching your sector. \ No newline at end of file diff --git a/Resources/Locale/en-US/station-events/events/vent-critters.ftl b/Resources/Locale/en-US/station-events/events/vent-critters.ftl index 04b124d824..d99906be4a 100644 --- a/Resources/Locale/en-US/station-events/events/vent-critters.ftl +++ b/Resources/Locale/en-US/station-events/events/vent-critters.ftl @@ -2,7 +2,14 @@ station-event-slimes-spawn-announcement = Attention. A large influx of unknown life forms have been detected residing within the station's ventilation systems. Please be rid of these creatures before it begins to affect productivity. station-event-vent-critters-announcement = Attention. A large influx of unknown life forms have been detected residing within the station's ventilation systems. Please be rid of these creatures before it begins to affect productivity. station-event-spider-spawn-announcement = Attention. A large influx of unknown life forms have been detected residing within the station's ventilation systems. Please be rid of these creatures before it begins to affect productivity. - +station-event-reagentslime-vents-announcement = Attention. A large influx of unknown life forms have been detected residing within the station's ventilation systems. Please be rid of these creatures before it begins to affect productivity. +station-event-meat-vents-announcement = Attention. A large influx of unknown life forms have been detected residing within the station's ventilation systems. Please be rid of these creatures before it begins to affect productivity. +station-event-neutral-xeno-vents-announcement = Confirmed sightings of alien wildlife on the station. Personnel are advised to arm themselves, barricade doors, and defend themselves if necessary. Security is advised to investigate the threat as soon as possible. +station-event-tick-vents-announcement = Confirmed sightings of hostile alien wildlife on the station. Personnel are advised to arm themselves, barricade doors, and defend themselves if necessary. Security is advised to eradicate the threat as soon as possible. +station-event-argocyte-vents-announcement = Confirmed sightings of hostile alien wildlife on the station. Personnel are advised to arm themselves, barricade doors, and defend themselves if necessary. Security is advised to eradicate the threat as soon as possible. +station-event-light-vents-announcement = Confirmed sightings of hostile alien wildlife on the station. Personnel are advised to arm themselves, barricade doors, and defend themselves if necessary. Security is advised to eradicate the threat as soon as possible. +station-event-carp-vents-announcement = Confirmed sightings of hostile alien wildlife on the station. Personnel are advised to arm themselves, barricade doors, and defend themselves if necessary. Security is advised to eradicate the threat as soon as possible. +station-event-space-vents-announcement = Confirmed sightings of hostile wildlife on the station. Personnel are advised to arm themselves, barricade doors, and defend themselves if necessary. Security is advised to eradicate the threat as soon as possible. # Weak station-event-slimes-spawn-weak-announcement = Attention. A moderate influx of unknown life forms have been detected residing within the station's ventilation systems. Please be rid of these creatures before it begins to affect productivity. station-event-spider-spawn-weak-announcement = Attention. A moderate influx of unknown life forms have been detected residing within the station's ventilation systems. Please be rid of these creatures before it begins to affect productivity. diff --git a/Resources/Locale/en-US/station-laws/laws.ftl b/Resources/Locale/en-US/station-laws/laws.ftl index f73755a359..6b5ca57872 100644 --- a/Resources/Locale/en-US/station-laws/laws.ftl +++ b/Resources/Locale/en-US/station-laws/laws.ftl @@ -42,5 +42,3 @@ laws-ui-state-law = State law: laws-notify = You are bound to silicon laws, which you can view via the sidebar action. You are required to always follow your laws. laws-update-notify = Your laws have been updated. You can view the changes via the sidebar action. - -laws-compromised-examine = The [color=red]law-governing[/color] internals seem damaged... diff --git a/Resources/Locale/en-US/step-trigger/shoes-required.ftl b/Resources/Locale/en-US/step-trigger/shoes-required.ftl index 8c1369a49f..07a4b8a84f 100644 --- a/Resources/Locale/en-US/step-trigger/shoes-required.ftl +++ b/Resources/Locale/en-US/step-trigger/shoes-required.ftl @@ -1 +1 @@ -shoes-required-step-trigger-examine = You probably shouldn't step on this barefoot. +clothing-required-step-trigger-examine = You probably shouldn't step on this barefoot. diff --git a/Resources/Locale/en-US/store/categories.ftl b/Resources/Locale/en-US/store/categories.ftl index b6abc3e428..0d0dc4aecc 100644 --- a/Resources/Locale/en-US/store/categories.ftl +++ b/Resources/Locale/en-US/store/categories.ftl @@ -16,3 +16,11 @@ store-category-deception = Deception # Revenant store-category-abilities = Abilities + +# Wizard +store-caregory-spellbook-offensive = Offensive Spells +store-caregory-spellbook-defensive = Defensive Spells +store-caregory-spellbook-utility = Utility Spells +store-caregory-spellbook-equipment = Wizard Equipment +store-caregory-spellbook-events = Event Spells + diff --git a/Resources/Locale/en-US/store/currency.ftl b/Resources/Locale/en-US/store/currency.ftl index 5d7ed95935..ada70b5597 100644 --- a/Resources/Locale/en-US/store/currency.ftl +++ b/Resources/Locale/en-US/store/currency.ftl @@ -1,4 +1,4 @@ -store-currency-inserted = {CAPITALIZE(THE($used))} is inserted into the {THE($target)}. +store-currency-inserted = {CAPITALIZE(THE($used))} is inserted into {THE($target)}. store-currency-war-boost-given = { CAPITALIZE($target) } starts buzzing store-currency-inserted-implant = {CAPITALIZE(THE($used))} is inserted into your implant. @@ -9,3 +9,4 @@ store-currency-display-debugdollar = {$amount -> } store-currency-display-telecrystal = TC store-currency-display-stolen-essence = Stolen Essence +store-currency-display-wizcoin = Wiz€oin™ diff --git a/Resources/Locale/en-US/store/sales.ftl b/Resources/Locale/en-US/store/sales.ftl new file mode 100644 index 0000000000..7223a8a0dc --- /dev/null +++ b/Resources/Locale/en-US/store/sales.ftl @@ -0,0 +1,2 @@ +store-sales-amount = [DISCOUNT] { $amount }%! +store-sales-over = [The sale is over] diff --git a/Resources/Locale/en-US/store/spellbook-catalog.ftl b/Resources/Locale/en-US/store/spellbook-catalog.ftl new file mode 100644 index 0000000000..457f02916f --- /dev/null +++ b/Resources/Locale/en-US/store/spellbook-catalog.ftl @@ -0,0 +1,35 @@ +# Spells +spellbook-fireball-name = Fireball +spellbook-fireball-desc = Get most crew exploding with rage when they see this fireball heading toward them! + +spellbook-blink-name = Blink +spellbook-blink-desc = Don't blink or you'll miss yourself teleporting away. + +spellbook-force-wall-name = Force Wall +spellbook-force-wall-desc = Make three walls of pure force that you can pass through, but other's can't. + +spellbook-polymoprh-spider-name = Spider Polymoprh +spellbook-polymorph-spider-desc = Transforms you into a spider, man! + +spellbook-polymorph-rod-name = Rod Polymorph +spellbook-polymorph-rod-desc = Change into an Immovable Rod with limited movement. + +spellbook-charge-name = Charge +spellbook-charge-desc = Adds a charge back to your wand! + +# Equipment + +spellbook-wand-polymorph-door-name = Wand of Entrance +spellbook-wand-polymorph-door-description = For when you need a get-away route. + +spellbook-wand-polymorph-carp-name = Wand of Carp Polymorph +spellbook-wand-polymorph-carp-description = For when you need a carp filet quick and the clown is looking juicy. + +# Events + +spellbook-event-summon-ghosts-name = Summon Ghosts +spellbook-event-summon-ghosts-description = Who ya gonna call? + +# Upgrades +spellbook-upgrade-fireball-name = Upgrade Fireball +spellbook-upgrade-fireball-description = Upgrades Fireball to a maximum of level 3! diff --git a/Resources/Locale/en-US/store/uplink-catalog.ftl b/Resources/Locale/en-US/store/uplink-catalog.ftl index 67d221d9b6..e1b9776deb 100644 --- a/Resources/Locale/en-US/store/uplink-catalog.ftl +++ b/Resources/Locale/en-US/store/uplink-catalog.ftl @@ -121,8 +121,8 @@ uplink-agent-id-card-desc = A modified ID card that can copy accesses from other uplink-black-jetpack-name = Black Jetpack uplink-black-jetpack-desc = A black jetpack. It allows you to fly around in space. Refills not included, use your fuel wisely. -uplink-reinforcement-radio-monkey-name = Monkey Reinforcement Teleporter -uplink-reinforcement-radio-monkey-desc = Call in a trained monkey to assist you. Comes with a single syndicate cigarette. +uplink-reinforcement-radio-ancestor-name = Genetic Ancestor Reinforcement Teleporter +uplink-reinforcement-radio-ancestor-desc = Call in a trained ancestor of your choosing to assist you. Comes with a single syndicate cigarette. uplink-reinforcement-radio-name = Reinforcement Teleporter uplink-reinforcement-radio-desc = Radio in a reinforcement agent of extremely questionable quality. No off button, buy this if you're ready to party. They have a pistol with no reserve ammo, and a knife. That's it. @@ -157,6 +157,9 @@ uplink-radio-jammer-desc = This device will disrupt any nearby outgoing radio co uplink-syndicate-weapon-module-name = Weapon Cyborg Module uplink-syndicate-weapon-module-desc = This module will give a cyborg advanced laser and machete +uplink-syndicate-martyr-module-name = Martyr Cyborg Module +uplink-syndicate-martyr-module-desc = Turn your emagged borg friend into a walking bomb with just this module. Make sure they're loyal to your cause, results may vary. + uplink-singularity-beacon-name = Singularity Beacon uplink-singularity-beacon-desc = A device that attracts singularities. Has to be anchored and powered. Causes singularities to grow when consumed. @@ -282,11 +285,16 @@ uplink-proximity-mine-name = Proximity Mine uplink-proximity-mine-desc = A mine disguised as a wet floor sign. uplink-disposable-turret-name = Disposable Ballistic Turret -uplink-disposable-turret-desc = Looks and functions like a normal electrical toolbox. Upon hitting the toolbox it will transform into a ballistic turret, theoretically shooting at anyone except members of the syndicate. Can be turned back into a toolbox using a screwdriver and repaired using a wrench. +uplink-disposable-turret-desc = + Looks and functions like a normal electrical toolbox. Upon hitting the toolbox it will transform into a ballistic turret, theoretically shooting at anyone except members of the syndicate. + Can be turned back into a toolbox using a screwdriver and repaired using a wrench. IF YOU DO NOT HAVE AN AGENT ID IN YOUR ID SLOT, OR SYNDICATE PDA, IT WILL FIRE UPON YOU. uplink-cluster-banana-peel-name = Cluster Banana uplink-cluster-banana-peel-desc = Splits into 6 explosive banana peels after being thrown, the peels detonate automatically after 20 seconds if nobody slips on them. +uplink-cane-blade-name = Cane Blade +uplink-cane-blade-desc = A cane that has a hidden blade that can be unsheathed. + # Armor uplink-chameleon-name = Chameleon Kit uplink-chameleon-desc = A backpack full of items that contain chameleon technology allowing you to disguise as pretty much anything on the station, and more! @@ -367,6 +375,9 @@ uplink-slipocalypse-clustersoap-desc = Scatters arounds small pieces of syndicat uplink-mobcat-microbomb-name = SyndiCat uplink-mobcat-microbomb-desc = A hand cat equipped with a microbomb implant. Explodes when seriously injured. Can bite painfully +uplink-chameleon-projector-name = Chameleon Projector +uplink-chameleon-projector-desc = Disappear in plain sight by creating a hologram of an item around you. Do not use this to play the game "Object Search". + # Pointless uplink-revolver-cap-gun-name = Cap Gun uplink-revolver-cap-gun-desc = Looks almost like the real thing! Ages 8 and up. diff --git a/Resources/Locale/en-US/supermatter/supermatter.ftl b/Resources/Locale/en-US/supermatter/supermatter.ftl index 52593f5524..2f36560a26 100644 --- a/Resources/Locale/en-US/supermatter/supermatter.ftl +++ b/Resources/Locale/en-US/supermatter/supermatter.ftl @@ -1,19 +1,19 @@ supermatter-announcer = Automatic Supermatter Engine supermatter-examine-integrity = Its' integrity is [color=yellow]{$integrity}%[/color]. -supermatter-announcement-warning = +supermatter-warning = Warning! Crystal hyperstructure integrity faltering! Integrity: {$integrity}%. -supermatter-announcement-emergency = +supermatter-emergency = DANGER! Crystal hyperstructure integrity reaching critical levels! Integrity: {$integrity}%. -supermatter-announcement-delam-explosion = +supermatter-delam-explosion = CRYSTAL DELAMINATION IMMINENT! The crystal has reached critical integrity failure! Emergency causality destabilization field has been engaged. -supermatter-announcement-delam-overmass = +supermatter-delam-overmass = CRYSTAL DELAMINATION IMMINENT! Crystal hyperstructure integrity has reached critical mass failure! Singularity formation imminent! -supermatter-announcement-delam-tesla = +supermatter-delam-tesla = CRYSTAL DELAMINATION IMMINENT! Crystal hyperstructure integrity has reached critical power surge failure! Energy ball formation imminent! -supermatter-announcement-delam-cascade = +supermatter-delam-cascade = CRYSTAL DELAMINATION IMMINENT! Harmonic frequency limits exceeded, casualty destabilization field could not be engaged! -supermatter-announcement-delam-cancel = +supermatter-delam-cancel = Crystalline hyperstructure returning to safe operating parameters. Failsafe has been Disengaged. Integrity: {$integrity}%. supermatter-seconds-before-delam = Estimated time before delamination: {$seconds} seconds. diff --git a/Resources/Locale/en-US/surgery/surgery-ui.ftl b/Resources/Locale/en-US/surgery/surgery-ui.ftl new file mode 100644 index 0000000000..f09c9dc102 --- /dev/null +++ b/Resources/Locale/en-US/surgery/surgery-ui.ftl @@ -0,0 +1,12 @@ +surgery-ui-window-title = Surgery +surgery-ui-window-require = Requires +surgery-ui-window-parts = < Parts +surgery-ui-window-surgeries = < Surgeries +surgery-ui-window-steps = < Steps +surgery-ui-window-steps-error-skills = You have no surgical skills. +surgery-ui-window-steps-error-table = You need an operating table for this. +surgery-ui-window-steps-error-armor = You need to remove their armor! +surgery-ui-window-steps-error-tools = Missing tools. +surgery-error-laying = They need to be laying down! +surgery-error-self-surgery = You can't perform surgery on yourself! +surgery-part-damage-evaded = {$user} narrowly evaded! diff --git a/Resources/Locale/en-US/thief/backpack.ftl b/Resources/Locale/en-US/thief/backpack.ftl index 31b87c6f02..90cb0031fb 100644 --- a/Resources/Locale/en-US/thief/backpack.ftl +++ b/Resources/Locale/en-US/thief/backpack.ftl @@ -15,9 +15,9 @@ thief-backpack-button-deselect = Select [X] thief-backpack-category-chameleon-name = chameleon's kit thief-backpack-category-chameleon-description = - Includes a full set of clothing that contain - chameleon technology, allowing you to disguise - as pretty much anything on the station. + Includes a full set of clothing that contains chameleon technology, + Contains a chameleon projector to help disguise yourself as objects, + You'll be able to disguise yourself as almost anything and anyone. thief-backpack-category-tools-name = bearcatcher's kit thief-backpack-category-tools-description = diff --git a/Resources/Locale/en-US/tools/components/welder-component.ftl b/Resources/Locale/en-US/tools/components/welder-component.ftl index 681975deb8..6307068521 100644 --- a/Resources/Locale/en-US/tools/components/welder-component.ftl +++ b/Resources/Locale/en-US/tools/components/welder-component.ftl @@ -4,7 +4,8 @@ welder-component-no-fuel-message = The welder has no fuel left! welder-component-no-fuel-in-tank = The {$owner} is empty. welder-component-on-examine-welder-lit-message = [color=orange]Lit[/color] welder-component-on-examine-welder-not-lit-message = Not lit -welder-component-on-examine-detailed-message = Fuel: [color={$colorName}]{$fuelLeft}/{$fuelCapacity}[/color]. {$status} +welder-component-on-examine-detailed-message = Fuel: [color={$colorName}]{$fuelLeft}/{$fuelCapacity}[/color] + {$status} welder-component-suicide-lit-others-message = {$victim} welds their every orifice closed! It looks like they are trying to commit suicide! welder-component-suicide-lit-message = You weld your every orifice closed! welder-component-suicide-unlit-others-message = {$victim} bashes themselves with the unlit welding torch! diff --git a/Resources/Locale/en-US/tools/tool-qualities.ftl b/Resources/Locale/en-US/tools/tool-qualities.ftl index 14e42390a7..2482dca517 100644 --- a/Resources/Locale/en-US/tools/tool-qualities.ftl +++ b/Resources/Locale/en-US/tools/tool-qualities.ftl @@ -32,4 +32,7 @@ tool-quality-rolling-name = Rolling tool-quality-rolling-tool-name = Rolling Pin tool-quality-digging-name = Digging -tool-quality-digging-tool-name = Shovel \ No newline at end of file +tool-quality-digging-tool-name = Shovel + +tool-quality-axing-name = Axing +tool-quality-axing-tool-name = Fireaxe \ No newline at end of file diff --git a/Resources/Locale/en-US/traits/categories.ftl b/Resources/Locale/en-US/traits/categories.ftl index 56f0adeb47..2bd4b7ba49 100644 --- a/Resources/Locale/en-US/traits/categories.ftl +++ b/Resources/Locale/en-US/traits/categories.ftl @@ -5,4 +5,7 @@ trait-category-Auditory = Auditory trait-category-Mental = Mental trait-category-Physical = Physical trait-category-Speech = Speech -trait-category-Visual = Visual +trait-category-TraitsSpeechUncategorized = Uncategorized +trait-category-TraitsSpeechAccents = Accents +trait-category-TraitsSpeechLanguages = Languages +trait-category-Visual = Visual \ No newline at end of file diff --git a/Resources/Locale/en-US/traits/misc.ftl b/Resources/Locale/en-US/traits/misc.ftl new file mode 100644 index 0000000000..814614f4e2 --- /dev/null +++ b/Resources/Locale/en-US/traits/misc.ftl @@ -0,0 +1 @@ +examine-cybereyes-message = {CAPITALIZE(POSS-ADJ($entity))} eyes shine with a faint inner light. diff --git a/Resources/Locale/en-US/traits/traits.ftl b/Resources/Locale/en-US/traits/traits.ftl index 828afa7666..921546d466 100644 --- a/Resources/Locale/en-US/traits/traits.ftl +++ b/Resources/Locale/en-US/traits/traits.ftl @@ -62,6 +62,9 @@ trait-name-CPRTraining = CPR Training trait-description-CPRTraining = At some point in your life, you have received training in how to perform CPR. This trait is automatically given for free to medical doctors, and is intended for non-medical characters +trait-name-Nearsighted = Nearsighted +trait-description-Nearsighted = Your eyes are not what they once were, you have difficulty seeing things far away without corrective glasses. + trait-name-NormalVisionHarpy = Trichromat Modification trait-description-NormalVisionHarpy = Your eyes have been modified by means of advanced medicine to see in the standard colors of Red, Green, and Blue. @@ -86,6 +89,12 @@ trait-description-Foreigner = For one reason or another you do not speak this station's primary language. Instead, you have a translator issued to you that only you can use. +trait-name-Saturnine = Saturnine +trait-description-Saturnine = You are naturally dour and morose. Your mood is permanently decreased by a large amount. + +trait-name-Sanguine = Sanguine +trait-description-Sanguine = You are naturally upbeat and cheerful! Your mood is permanently increased by a large amount. + trait-name-WillToLive = Will To Live trait-description-WillToLive = You have an unusually strong "will to live", and will resist death more than others. @@ -146,9 +155,30 @@ trait-description-Lethargy = trait-name-SignLanguage = Sign Language trait-description-SignLanguage = - You can understand and use Galactic Sign Language (GSL). + You can understand and use Tau-Ceti Basic Sign Language (TCB-SL). If you are mute for any reason, you can still communicate with sign language. +trait-name-SolCommon = Sol Common +trait-description-SolCommon = + With its roots in Mandarin Chinese - Common evolved as the official language of the Sol Alliance - with officials working to tie it together with a common tongue. + It's spoken by state officials - taught in schools - and spoken by those who either feel a sense of national pride in the Alliance or otherwise fell sway to the culture. + +trait-name-Tradeband = Tradeband +trait-description-Tradeband = + Descended from latin and romance languages of old Earth - Tradeband remains the main tongue of the upper class of humanity. + The language sounds elegant and well structured to most ears. It remains in popular use with traders - diplomats - and those seeking to hold onto a piece of a romantic past. + +trait-name-Freespeak = Freespeak (Gutter) +trait-description-Freespeak = + A language of renegades and frontiersmen descending from various languages from Earth like Hindi combined into a multi-rooted jumble that sounds incoherent or even barbarian to non-native speakers. + This language is the only common cultural identity for humans in the frontier. Speaking this language in itself boldly declares the speaker a free spirit. + Often called 'Gutter' by Alliance citizens. + +trait-name-Elyran = Elyran Standard +trait-description-Elyran = + Elyran Standard is the official tongue of the Republic of Elyra. + Constructed using elements of Farsi - Arabic - and Turkish - influence from all three of these languages can be seen throughout its grammar and vocabulary. + trait-name-Voracious = Voracious trait-description-Voracious = Nothing gets between you and your food. @@ -189,4 +219,191 @@ trait-description-WeaponsGeneralist = Your melee damage bonus for all Brute damage types (Blunt, Slash, Piercing) becomes 25%. trait-name-Singer = Singer -trait-description-Singer = You are naturally capable of singing simple melodies with your voice. \ No newline at end of file +trait-description-Singer = You are naturally capable of singing simple melodies with your voice. + +trait-name-LatentPsychic = Latent Psychic +trait-description-LatentPsychic = + Your mind and soul are open to the noosphere, allowing for use of Telepathy. + Thus, you are eligible for potentially receiving psychic powers. + It is possible that you may be hunted by otherworldly forces, so consider keeping your powers a secret. + +trait-name-PsionicInsulation = χ Waveform Misalignment +trait-description-PsionicInsulation = + You are a flesh automaton animated by neurotransmitters. Within your skull lies a + 1.5kg sack of meat pretending at sentience. By modern epistemiological theory, you aren't even a sophont. + The good news is that you are immune to most positive and negative effects of psychic powers. + There may be other consequences to this malady. + +trait-name-NaturalTelepath = Natural Telepath +trait-description-NaturalTelepath = + As a naturally occuring Telepath, you are capable of fluent telepathic communication, regardless of + whether or not you possess any notable psychic powers. This offers all of the same benefits and + drawbacks of Latent Psychic, except that you are guaranteed to start with full Telepathy. You may + still gain powers as normal for a Latent Psychic. + +trait-name-TrapAvoider = Trap Avoider +trait-description-TrapAvoider = + You possess a preturnatural sense of traps, and will unconsciously avoid them. You will never trigger + floor traps, such as land mines, tripwires, mouse traps(If you're small enough), etc. + +trait-name-AnomalousPositronics = Anomalous Positronics +trait-description-AnomalousPositronics = + Whether by intentional design from the manufacturer, black market modifications, or accidental omission, + your positronic brain lacks its standard psionic insulation. As a being that can be argued to have a soul, + this by extension means that it is possible for you to be influenced by the Noosphere. + +trait-name-Photophobia = Photophobia +trait-description-Photophobia = + Your eyes are extremely sensitive to bright lights. + As a result, you may be blinded for a greater duration than others when exposed to sudden flashes of light. + Your eyes are also more likely to be injured by flashes. + +trait-name-Clumsy = Clumsy +trait-description-Clumsy = + You have a severe deficiency in hand-eye-coordination, resulting in an inability to do some things that others would take for granted. + Any weapons you may try to use are more likely to injure yourself than others. You are unable to climb any objects without injuring yourself. + +trait-name-Small = Small +trait-description-Small = + You are much smaller than a typical person, and can climb into spaces others would not normally be able to fit into, such as duffel bags. + This trait does not in any way modify your character's size, it merely requires that your character be at most the size of a standard Felinid. + +trait-name-TemperatureTolerance = Temperature Tolerance +trait-description-TemperatureTolerance = + You have a notable tolerance for lower temperatures. You can stand for extended periods of time + in conditions just slightly below freezing, such as the inside of a kitchen fridge, + or the sunlit mountainside of the famous Glacier station. + +trait-name-Talons = Talons +trait-description-Talons = + Your fingertips have been replaced with piercing talons. + These could come from gene modifications, vatgrown implants, + or even hard plastic retractable talons incorpoated into a prosthetic limb. + Your unarmed melee attacks deal Piercing damage instead of the standard damage type for your species. + This has no effect on damage dealt with any form of armed melee. + +trait-name-Claws = Claws +trait-description-Claws = + Your fingertips have been replaced with sharp claws. + These could come from gene modifications, vatgrown implants, + or even hard plastic retractable claws incorpoated into a prosthetic limb. + Your unarmed melee attacks deal Slashing damage instead of the standard damage type for your species. + This has no effect on damage dealt with any form of armed melee. + +trait-name-NaturalWeaponRemoval = Natural Weapons Removal +trait-description-NaturalWeaponRemoval = + Whatever "Natural Weapons" your species are normally born with have been surgically removed. + This could have been done to better fit in with terran space stations, or as a cosmetic choice. + As a result, your unarmed attacks deal Blunt damage instead of the standard damage type for your species. + This has no effect on damage dealt with any form of armed melee. + +trait-name-StrikingCalluses = Striking Calluses +trait-description-StrikingCalluses = + An iconic enhancement commonly found in the world of cyberenhanced martial arts. + Striking Calluses consist of bony dermal deposits grafted into a user's hands, either inside the palm + for "Tiger Style" fighting, or just below the knuckles for those who favor traditional boxing. + Owners of prosthetic or bionic limbs would instead have a hard plastic shell over their knuckles. + These enhancements increase your unarmed strike damage by 1 point base, but do not confer + any benefits to any form of armed melee. + +trait-name-Spinarette = Bionic Spinarette +trait-description-Spinarette = + This vatgrown organ-- trademarked and patented by the Cybersun Corporation, is marketed as a highly + utilitarian enhancement, and sold in clinics all across known space. It consists of a nodule that is traditionally + implanted right below the wrist, which absorbs bodily lipids to convert into all natural silk. A small opening + in the palm allows the user to 'spin' this thread. Users of this enhancement typically require twice as much food + as a standard Sol Human, owing to the high metabolic cost of artificial Sericulture. + +trait-name-AddictionNicotine = Nicotine Addiction +trait-description-AddictionNicotine = + You have an addiction to Nicotine, and will require frequent smoke breaks to keep your mood in check. + +trait-name-AnimalFriend = Animal Friend +trait-description-AnimalFriend = + You have a way with animals. You will never be attacked by animals, unless you attack them first. + +trait-name-Liar = Pathological liar +trait-description-Liar = You can hardly bring yourself to tell the truth. Sometimes you lie anyway. + +trait-name-ValyrianStandard = Valyrian Standard +trait-description-ValyrianStandard = + A language descended from eastern european languages of old earth - Valyrian Standard is the commonly spoken tongue of Harpies brought up on their homeworld of Valyrian 4b + It is rarely spoken outside of the worlds of its native speakers, and has in modern times been supplanted by the 'Conlangs of the Sol Alliance. + Its speakers are those who wish to uphold the traditions and beliefs of ancient peoples from before the colonial era. + +trait-name-LowPotential = Low Psi-Potential +trait-description-LowPotential = + You possess an unusually weak connection to the noösphere, which makes it more difficult to obtain new psionic powers. + +trait-name-HighPotential = High Psi-Potential +trait-description-HighPotential = + Your connection to the noösphere is greater than average, making it easier to obtain new psionic powers. + +trait-name-LowAmplification = kα Deficiency +trait-description-LowAmplifiction = + Your psionic abilities are noticeably weaker than ones used by other psions. + +trait-name-HighAmplification = kα Abundance +trait-description-HighAmplification = + Your psionic abilities are stronger than those of other psions. + +trait-name-PowerOverwhelming = Power Overwhelming +trait-description-PowerOverwhelming = + WITNESS MY HATE MORTALS, COWER BEFORE MY PSIONIC MIGHT! REALITY IS AS I DEEM IT. + +trait-name-LowDampening = kδ Defect +trait-description-LowDampening = + Your skill in manipulating the noösphere is weaker than others. You may experience unintended effects from using your abilities. + +trait-name-HighDampening = kδ Proficient +trait-description-HighDampening = + You are skilled in the art of subtly manipulating the noösphere. Your powers are less likely to show unintended effects. + +trait-name-Azaziba = Sinta'Azaziba +trait-description-Azaziba = + A language of Moghes consisting of a combination of spoken word and gesticulation. + While waning since Moghes entered the galactic stage - it enjoys popular use by Unathi that never fell to the Hegemony's cultural dominance. + +trait-name-BionicArm = Bionic Arm +trait-description-BionicArm = + One or more of your limbs have been replaced with an expensive, state of the art bionic. It could be either one made of highly realistic synthflesh, + or a more obvious metal limb. This limb provides enhanced strength to its user, allowing one to pry open barriers with their bare hands. + +trait-name-PlateletFactories = Platelet Factories +trait-description-PlateletFactories = + Your body has been augmented with a series of biotailored organs that enhance the owner's long term survivability. These organs will attempt + to keep the user alive, even in the face of advanced trauma, all the way up until - but not including - death. + Your natural healing is no longer capped, and will now slowly heal any damage type. This includes more exotic injuries like radiation exposure, or cancer. + +trait-name-DermalArmor = Dermal Armor +trait-description-DermalArmor = + Your skin has been replaced with a flexible, yet sturdy, hard-polymer shell wrapped in a layer of synthetic flesh. + This augmentation provides an innate 10% resistance to physical damage. + +trait-name-CyberEyes = Cyber-Eyes Basic System +trait-description-CyberEyes = + One or more of your eyes have been replaced with a highly modular mechanical ocular implant. + Their most basic functionality is to provide amelioration for weaknesses of the wearer's natural eyes, + but additionally these implants provide protection from bright flashes of light. + +trait-name-CyberEyesSecurity = Cyber-Eyes SecHud +trait-description-CyberEyesSecurity = + Your Cyber-Eyes have been upgraded to include a built-in Security Hud. Note that this augmentation is considered Contraband + for anyone not under the employ of station Security personel, and may be disabled by your employer before dispatch to the station. + +trait-name-CyberEyesMedical = Cyber-Eyes MedHud +trait-description-CyberEyesMedical = + Your Cyber-Eyes have been upgraded to include a built-in Medical Hud, allowing you to track the relative health condition of biological organisms. + +trait-name-CyberEyesDiagnostic = Cyber-Eyes DiagHud +trait-description-CyberEyesDiagnostic = + Your Cyber-Eyes have been upgraded to include a built-in Diagnostic Hud, allowing you to track the condition of synthetic entities. + +trait-name-CyberEyesOmni = Cyber-Eyes HudSuite +trait-description-CyberEyesOmni = + This expensive implant provides the combined benefits of a SecHud, MedHud, and a DiagHud. + Note that this augmentation is considered Contraband for anyone not under the employ of station Security personel, + and may be disabled by your employer before dispatch to the station. + +trait-name-ShadowkinBlackeye = Blackeye +trait-description-ShadowkinBlackeye = You lose your special Shadowkin powers, in return for some points. diff --git a/Resources/Locale/en-US/ui/verbs.ftl b/Resources/Locale/en-US/ui/verbs.ftl deleted file mode 100644 index 1471261dcb..0000000000 --- a/Resources/Locale/en-US/ui/verbs.ftl +++ /dev/null @@ -1,3 +0,0 @@ -### Loc for the various UI-related verbs -ui-verb-toggle-open = Toggle UI -verb-instrument-openui = Play Music diff --git a/Resources/Locale/en-US/verbs/verb-system.ftl b/Resources/Locale/en-US/verbs/verb-system.ftl index 2bebddca61..dfb4e621dc 100644 --- a/Resources/Locale/en-US/verbs/verb-system.ftl +++ b/Resources/Locale/en-US/verbs/verb-system.ftl @@ -1,4 +1,3 @@ -verb-system-waiting-on-server-text = Waiting on Server... verb-system-null-server-response = Entity not in view. You should not see this. @@ -28,6 +27,8 @@ verb-categories-timer = Set Delay verb-categories-lever = Lever verb-categories-select-type = Select Type verb-categories-fax = Set Destination +verb-categories-power-level = Power Level +verb-categories-interaction = Interact verb-common-toggle-light = Toggle light verb-common-close = Close diff --git a/Resources/Locale/en-US/virtual/virtual-item.ftl b/Resources/Locale/en-US/virtual/virtual-item.ftl new file mode 100644 index 0000000000..cb91f24cf7 --- /dev/null +++ b/Resources/Locale/en-US/virtual/virtual-item.ftl @@ -0,0 +1 @@ +virtual-item-dropped-other = You dropped {THE($dropped)}! diff --git a/Resources/Locale/en-US/voice-mask.ftl b/Resources/Locale/en-US/voice-mask.ftl index 2f5acefee4..f3740cdafb 100644 --- a/Resources/Locale/en-US/voice-mask.ftl +++ b/Resources/Locale/en-US/voice-mask.ftl @@ -1,3 +1,5 @@ +voice-mask-default-name-override = Unknown + voice-mask-name-change-window = Voice Mask Name Change voice-mask-name-change-info = Type in the name you want to mimic. voice-mask-name-change-speech-style = Speech style diff --git a/Resources/Locale/en-US/weapons/melee/melee.ftl b/Resources/Locale/en-US/weapons/melee/melee.ftl index 871d142504..d3318ea244 100644 --- a/Resources/Locale/en-US/weapons/melee/melee.ftl +++ b/Resources/Locale/en-US/weapons/melee/melee.ftl @@ -3,5 +3,5 @@ melee-inject-failed-hardsuit = Your {$weapon} cannot inject through hardsuits! melee-balloon-pop = {CAPITALIZE(THE($balloon))} popped! -#BatteryComponent +# BatteryComponent melee-battery-examine = It has enough charge for [color={$color}]{$count}[/color] hits. diff --git a/Resources/Locale/en-US/wieldable/wieldable-component.ftl b/Resources/Locale/en-US/wieldable/wieldable-component.ftl index 91eee8c2ea..84b58224a7 100644 --- a/Resources/Locale/en-US/wieldable/wieldable-component.ftl +++ b/Resources/Locale/en-US/wieldable/wieldable-component.ftl @@ -1,4 +1,4 @@ -### Locale for wielding items; i.e. two-handing them +### Locale for wielding items; i.e. two-handing them wieldable-verb-text-wield = Wield wieldable-verb-text-unwield = Unwield @@ -17,3 +17,4 @@ wieldable-component-not-in-hands = { CAPITALIZE(THE($item)) } isn't in your hand wieldable-component-requires = { CAPITALIZE(THE($item))} must be wielded! +gunwieldbonus-component-examine = This weapon has improved accuracy when wielded. diff --git a/Resources/Locale/en-US/xenoarchaeology/artifact-analyzer.ftl b/Resources/Locale/en-US/xenoarchaeology/artifact-analyzer.ftl index 599f36ec91..35dd42167f 100644 --- a/Resources/Locale/en-US/xenoarchaeology/artifact-analyzer.ftl +++ b/Resources/Locale/en-US/xenoarchaeology/artifact-analyzer.ftl @@ -6,6 +6,10 @@ analysis-console-print-button = Print analysis-console-print-tooltip-info = Print out the current information about the artifact. analysis-console-extract-button = Extract analysis-console-extract-button-info = Extract points from an artifact based on the newly explored nodes. +analysis-console-bias-up = Up +analysis-console-bias-down = Down +analysis-console-bias-button-info-up = Toggles the bias an artifact has in moving between its nodes. Up heads toward zero depth. +analysis-console-bias-button-info-down = Toggles the bias an artifact has in moving between its nodes. Down heads toward ever-higher depths. analysis-console-info-no-scanner = No analyzer connected! Please connect one using a multitool. analysis-console-info-no-artifact = No artifact present! Place one on the pad then scan for information. @@ -26,6 +30,9 @@ analysis-console-progress-text = {$seconds -> [one] T-{$seconds} second *[other] T-{$seconds} seconds } +analysis-console-no-server-connected = Cannot extract. No server connected. +analysis-console-no-artifact-placed = No artifact on scanner. +analysis-console-no-points-to-extract = No points to extract. analyzer-artifact-component-upgrade-analysis = analysis duration diff --git a/Resources/Locale/en-US/xenoarchaeology/traversal-distorter.ftl b/Resources/Locale/en-US/xenoarchaeology/traversal-distorter.ftl index 5c9eac57a5..407de66ebd 100644 --- a/Resources/Locale/en-US/xenoarchaeology/traversal-distorter.ftl +++ b/Resources/Locale/en-US/xenoarchaeology/traversal-distorter.ftl @@ -1,5 +1,6 @@ -traversal-distorter-set-in = Traversal bias set to "in" -traversal-distorter-set-out = Traversal bias set to "out" +traversal-distorter-set-up = Traversal bias set to up, toward safer nodes +traversal-distorter-set-down = Traversal bias set to down, toward more dangerous nodes -traversal-distorter-desc-in = The affected artifact's traversal now favors moving inwards to the beginning. -traversal-distorter-desc-out = The affected artifact's traversal now favors moving outwards towards more dangerous nodes. +traversal-distorter-desc-up = The affected artifact's traversal now favors moving up the node tree toward safer nodes. +traversal-distorter-desc-down = The affected artifact's traversal now favors moving down the node tree towards more dangerous nodes. +traversal-distorter-upgrade-bias = Bias effectiveness diff --git a/Resources/Locale/ru-RU/Escape-Menu/options-menu.ftl b/Resources/Locale/ru-RU/Escape-Menu/options-menu.ftl new file mode 100644 index 0000000000..a39bc37af9 --- /dev/null +++ b/Resources/Locale/ru-RU/Escape-Menu/options-menu.ftl @@ -0,0 +1,3 @@ +ui-options-function-look-up = Присмотреться/Прицелиться +ui-options-function-auto-get-up = Автоматически вставать при падении +ui-options-function-hold-look-up = Удерживать клавишу для прицеливания diff --git a/Resources/Locale/ru-RU/ghost/ghost-gui.ftl b/Resources/Locale/ru-RU/ghost/ghost-gui.ftl new file mode 100644 index 0000000000..ce9084d21f --- /dev/null +++ b/Resources/Locale/ru-RU/ghost/ghost-gui.ftl @@ -0,0 +1 @@ +ghost-gui-return-to-round-button = Вернуться в раунд \ No newline at end of file diff --git a/Resources/Locale/ru-RU/ghost/ghost-respawn.ftl b/Resources/Locale/ru-RU/ghost/ghost-respawn.ftl new file mode 100644 index 0000000000..eaa118b399 --- /dev/null +++ b/Resources/Locale/ru-RU/ghost/ghost-respawn.ftl @@ -0,0 +1,16 @@ +ghost-respawn-time-left = До возможности вернуться в раунд { $time } + { $time -> + [one] минута + [few] минуты + *[other] минут + } +ghost-respawn-max-players = Функция недоступна, игроков на сервере должно быть меньше { $players }. +ghost-respawn-window-title = Правила возвращения в раунд +ghost-respawn-window-rules-footer = Пользуясь это функцией, вы [color=#ff7700]обязуетесь[/color] [color=#ff0000]не переносить[/color] знания своего прошлого персонажа в нового. За нарушение пункта, указанного здесь, следует [color=#ff0000]бан в размере от 3-ех дней[/color]. +ghost-respawn-same-character = Нельзя заходить в раунд за того же персонажа. Поменяйте его в настройках персонажей. + +ghost-respawn-log-character-almost-same = Игрок { $player } { $try -> + [true] зашёл + *[false] попытался зайти +} в раунд после возвращения в лобби с похожим именем. Прошлое имя: { $oldName }, текущее: { $newName }. +ghost-respawn-log-return-to-lobby = { $userName } вернулся в лобби. \ No newline at end of file diff --git a/Resources/Locale/ru-RU/mood/mood-alerts.ftl b/Resources/Locale/ru-RU/mood/mood-alerts.ftl new file mode 100644 index 0000000000..e96fb1f09f --- /dev/null +++ b/Resources/Locale/ru-RU/mood/mood-alerts.ftl @@ -0,0 +1,22 @@ +alerts-mood-insane-name = Безумие +alerts-mood-insane-desc = В моей душе тлеют мрак и безнадежность, мир обречен на абсолютное зло. +alerts-mood-horrible-name = Печально +alerts-mood-horrible-desc = Я борюсь с болями и страхами, моя судьба - череда мучений и страданий. +alerts-mood-terrible-name = Очень плохо +alerts-mood-terrible-desc = Моя жизнь иссякла, как кровь из раны, и вокруг лишь мрак и отчаяние. +alerts-mood-bad-name = Плохо +alerts-mood-bad-desc = Силы покидают меня, и каждый день становится тяжелым испытанием. +alerts-mood-meh-name = Нехорошо +alerts-mood-meh-desc = Мир полон угроз и боли, и мои надежды медленно умирают. +alerts-mood-neutral-name = Нормально +alerts-mood-neutral-desc = Я продолжаю свой путь, несмотря на угрозы и лишения, ища хоть малейший свет во мраке. +alerts-mood-good-name = Неплохо +alerts-mood-good-desc = В этом мире полном страданий, я обретаю небольшое облегчение и надежду. +alerts-mood-great-name = Хорошо +alerts-mood-great-desc = Моя сила восстанавливается, и мир кажется меньшим злом и болью. +alerts-mood-exceptional-name = Очень хорошо +alerts-mood-exceptional-desc = Я ощущаю в себе силы и надежду на лучшие дни, несмотря на угрозы, что таятся вокруг. +alerts-mood-perfect-name = Великолепно +alerts-mood-perfect-desc = Моя душа полна света и силы, и я готов сразиться с тьмой в этом жестоком мире. +alerts-mood-dead-name = Мёртв +alerts-mood-dead-desc = Вечная пустота окутала меня, и мир больше не имеет власти над моей душой. diff --git a/Resources/Locale/ru-RU/mood/mood.ftl b/Resources/Locale/ru-RU/mood/mood.ftl new file mode 100644 index 0000000000..b9619035ea --- /dev/null +++ b/Resources/Locale/ru-RU/mood/mood.ftl @@ -0,0 +1 @@ +mood-show-effects-start = [font size=12]Настроение:[/font] diff --git a/Resources/Locale/ru-RU/store/sales.ftl b/Resources/Locale/ru-RU/store/sales.ftl new file mode 100644 index 0000000000..93f1798fe7 --- /dev/null +++ b/Resources/Locale/ru-RU/store/sales.ftl @@ -0,0 +1,2 @@ +store-sales-amount = [СКИДКА] { $amount }%! +store-sales-over = [Скидка закончилась] diff --git a/Resources/Maps/Dungeon/experiment.yml b/Resources/Maps/Dungeon/experiment.yml index 8449abf932..590692a6bb 100644 --- a/Resources/Maps/Dungeon/experiment.yml +++ b/Resources/Maps/Dungeon/experiment.yml @@ -7444,13 +7444,6 @@ entities: - type: Transform pos: 32.5,14.5 parent: 1653 -- proto: MachineTraversalDistorter - entities: - - uid: 1058 - components: - - type: Transform - pos: 34.5,22.5 - parent: 1653 - proto: MaintenanceFluffSpawner entities: - uid: 867 diff --git a/Resources/Maps/Dungeon/lava_brig.yml b/Resources/Maps/Dungeon/lava_brig.yml index 071083c291..ebf267a9c2 100644 --- a/Resources/Maps/Dungeon/lava_brig.yml +++ b/Resources/Maps/Dungeon/lava_brig.yml @@ -1,13004 +1,13004 @@ -meta: - format: 6 - postmapinit: false -tilemap: - 0: Space - 15: FloorBasalt - 29: FloorDark - 33: FloorDarkMini - 34: FloorDarkMono - 42: FloorElevatorShaft - 54: FloorGreenCircuit - 62: FloorLino - 77: FloorReinforced - 82: FloorShuttleOrange - 89: FloorSteel - 99: FloorSteelMini - 100: FloorSteelMono - 104: FloorTechMaint - 108: FloorWhite - 112: FloorWhiteMini - 118: FloorWood - 121: Plating -entities: -- proto: "" - entities: - - uid: 588 - components: - - type: MetaData - - type: Transform - - type: Map - - type: PhysicsMap - - type: Broadphase - - type: OccluderTree - - type: MapGrid - chunks: - -1,-1: - ind: -1,-1 - tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAA - version: 6 - 0,0: - ind: 0,0 - tiles: WQAAAAADWQAAAAABWQAAAAACWQAAAAADWQAAAAACWQAAAAABeQAAAAAADwAAAAAAHQAAAAABDwAAAAAAeQAAAAAAWQAAAAACWQAAAAACWQAAAAACWQAAAAACWQAAAAACWQAAAAACWQAAAAABWQAAAAABWQAAAAAAWQAAAAADWQAAAAADeQAAAAAADwAAAAAADwAAAAAADwAAAAAAeQAAAAAAWQAAAAABWQAAAAACWQAAAAABWQAAAAACWQAAAAAAWQAAAAADWQAAAAADWQAAAAABWQAAAAADWQAAAAACWQAAAAABIgAAAAABHQAAAAABDwAAAAAAHQAAAAACIgAAAAAAWQAAAAABWQAAAAACWQAAAAABWQAAAAADWQAAAAADaAAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAeQAAAAAADwAAAAAADwAAAAAADwAAAAAAeQAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAeQAAAAAADwAAAAAAHQAAAAACDwAAAAAAeQAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAHQAAAAABHQAAAAABHQAAAAACDwAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAAHQAAAAABHQAAAAADHQAAAAABUgAAAAAAZAAAAAACWQAAAAAAZAAAAAACeQAAAAAAHQAAAAAAIgAAAAADeQAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAAeQAAAAAAIgAAAAACHQAAAAACUgAAAAAAYwAAAAACYwAAAAAAYwAAAAAAeQAAAAAAHQAAAAABIgAAAAACeQAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAAeQAAAAAAIgAAAAABHQAAAAADUgAAAAAAYwAAAAACYwAAAAAAYwAAAAAAWQAAAAACHQAAAAAAIgAAAAADeQAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAAeQAAAAAAIgAAAAAAHQAAAAABUgAAAAAAYwAAAAADYwAAAAABYwAAAAABeQAAAAAAHQAAAAADHQAAAAADHQAAAAACDwAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAAHQAAAAABHQAAAAABHQAAAAAAUgAAAAAAZAAAAAABWQAAAAABZAAAAAACeQAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAHQAAAAAAHQAAAAABeQAAAAAAHQAAAAADeQAAAAAAHQAAAAADHQAAAAADUgAAAAAAaAAAAAAAeQAAAAAAdgAAAAADdgAAAAABdgAAAAADdgAAAAAAdgAAAAADUgAAAAAAHQAAAAABHQAAAAACHQAAAAACHQAAAAADHQAAAAADHQAAAAADHQAAAAAAUgAAAAAAaAAAAAAAeQAAAAAAdgAAAAABdgAAAAACdgAAAAAAdgAAAAACdgAAAAACUgAAAAAAHQAAAAADHQAAAAACDwAAAAAADwAAAAAADwAAAAAAHQAAAAACHQAAAAACUgAAAAAAaAAAAAAAeQAAAAAAdgAAAAACdgAAAAADdgAAAAADdgAAAAACdgAAAAADUgAAAAAAHQAAAAADHQAAAAACHQAAAAABHQAAAAACHQAAAAACHQAAAAADHQAAAAABUgAAAAAAaAAAAAAAeQAAAAAAeQAAAAAAaAAAAAAAeQAAAAAAeQAAAAAAdgAAAAABUgAAAAAA - version: 6 - 0,1: - ind: 0,1 - tiles: HQAAAAAAHQAAAAADeQAAAAAAHQAAAAACeQAAAAAAHQAAAAADHQAAAAAAUgAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAeQAAAAAAdgAAAAACUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAHQAAAAADHQAAAAAAHQAAAAADHQAAAAADHQAAAAABUgAAAAAAWQAAAAAAWQAAAAABWQAAAAACWQAAAAAAWQAAAAAAUgAAAAAAaAAAAAAAeQAAAAAAWQAAAAABaAAAAAAAHQAAAAACeQAAAAAADwAAAAAAeQAAAAAAHQAAAAACUgAAAAAAWQAAAAADYwAAAAAAYwAAAAABYwAAAAADWQAAAAABUgAAAAAAaAAAAAAAaAAAAAAAWQAAAAAAeQAAAAAAHQAAAAABDwAAAAAADwAAAAAADwAAAAAAHQAAAAAAUgAAAAAAWQAAAAAAYwAAAAABYwAAAAACYwAAAAACWQAAAAADUgAAAAAAWQAAAAACWQAAAAADWQAAAAABWQAAAAAAHQAAAAAAeQAAAAAADwAAAAAAeQAAAAAAHQAAAAAAUgAAAAAAWQAAAAABYwAAAAABYwAAAAABYwAAAAABWQAAAAADUgAAAAAAaAAAAAAAaAAAAAAAWQAAAAAAWQAAAAABHQAAAAADHQAAAAABHQAAAAACHQAAAAAAHQAAAAABUgAAAAAAWQAAAAAAWQAAAAABWQAAAAAAWQAAAAACWQAAAAABUgAAAAAAaAAAAAAAaAAAAAAAWQAAAAABWQAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAHQAAAAAAHQAAAAADHQAAAAAAUgAAAAAAaAAAAAAAaAAAAAAAeQAAAAAAUgAAAAAAcAAAAAACcAAAAAACcAAAAAABUgAAAAAAWQAAAAADWQAAAAADZAAAAAAAUgAAAAAAHQAAAAAADwAAAAAAHQAAAAACUgAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAUgAAAAAAcAAAAAADcAAAAAACcAAAAAADUgAAAAAAWQAAAAAAWQAAAAABZAAAAAABUgAAAAAAHQAAAAACDwAAAAAAHQAAAAAAUgAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAUgAAAAAAcAAAAAADcAAAAAAAcAAAAAAAUgAAAAAAWQAAAAABWQAAAAABWQAAAAABUgAAAAAAHQAAAAACDwAAAAAAHQAAAAABUgAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAUgAAAAAAcAAAAAADcAAAAAADcAAAAAABUgAAAAAAZAAAAAACWQAAAAABWQAAAAACUgAAAAAAHQAAAAADHQAAAAACHQAAAAADUgAAAAAAeQAAAAAAaAAAAAAAeQAAAAAAUgAAAAAAcAAAAAACcAAAAAACcAAAAAADUgAAAAAAZAAAAAACWQAAAAAAWQAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAHQAAAAAAHQAAAAABDwAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAAHQAAAAADHQAAAAACUgAAAAAAWQAAAAABWQAAAAACHQAAAAACHQAAAAABHQAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAAHQAAAAADHQAAAAADHQAAAAAAUgAAAAAAaAAAAAAAeQAAAAAA - version: 6 - 0,-1: - ind: 0,-1 - tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAA - version: 6 - -1,0: - ind: -1,0 - tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAA - version: 6 - -1,1: - ind: -1,1 - tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAA - version: 6 - 1,-1: - ind: 1,-1 - tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAA - version: 6 - 1,0: - ind: 1,0 - tiles: WQAAAAABUgAAAAAAPgAAAAAAPgAAAAAAPgAAAAAAPgAAAAAAeQAAAAAAHQAAAAACHQAAAAAAHQAAAAABHQAAAAADHQAAAAABHQAAAAADHQAAAAADHQAAAAACHQAAAAADWQAAAAADUgAAAAAAPgAAAAAAPgAAAAAAPgAAAAAAPgAAAAAAeQAAAAAAHQAAAAAAHQAAAAADHQAAAAABHQAAAAACHQAAAAABHQAAAAADHQAAAAACHQAAAAABHQAAAAABWQAAAAAAUgAAAAAAPgAAAAAAPgAAAAAAPgAAAAAAPgAAAAAAIgAAAAACHQAAAAACHQAAAAACHQAAAAACHQAAAAABHQAAAAACHQAAAAACHQAAAAAAHQAAAAAAHQAAAAABaAAAAAAAUgAAAAAAPgAAAAAAPgAAAAAAPgAAAAAAPgAAAAAAeQAAAAAAHQAAAAADHQAAAAAAHQAAAAACHQAAAAAAHQAAAAADHQAAAAADHQAAAAADHQAAAAAAHQAAAAABaAAAAAAAUgAAAAAAPgAAAAAAPgAAAAAAPgAAAAAAPgAAAAAAeQAAAAAAHQAAAAABHQAAAAABHQAAAAACHQAAAAAAHQAAAAABHQAAAAACHQAAAAAAHQAAAAACHQAAAAADUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAWQAAAAABWQAAAAACWQAAAAACeQAAAAAAZAAAAAAAWQAAAAABZAAAAAAAUgAAAAAATQAAAAAATQAAAAAATQAAAAAATQAAAAAATQAAAAAATQAAAAAATQAAAAAATQAAAAAAWQAAAAAAWQAAAAABWQAAAAAAeQAAAAAAYwAAAAAAYwAAAAAAYwAAAAABUgAAAAAATQAAAAAAeQAAAAAAIgAAAAACeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAWQAAAAACWQAAAAACWQAAAAAAWQAAAAADYwAAAAADYwAAAAAAYwAAAAADUgAAAAAATQAAAAAAeQAAAAAAKgAAAAAAeQAAAAAAKgAAAAAAKgAAAAAAKgAAAAAAeQAAAAAAWQAAAAAAWQAAAAAAWQAAAAABeQAAAAAAYwAAAAACYwAAAAABYwAAAAADUgAAAAAATQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAIgAAAAABeQAAAAAAeQAAAAAAWQAAAAADWQAAAAAAWQAAAAAAeQAAAAAAZAAAAAADWQAAAAABZAAAAAACUgAAAAAATQAAAAAATQAAAAAATQAAAAAATQAAAAAATQAAAAAATQAAAAAATQAAAAAATQAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAWQAAAAABWQAAAAAAWQAAAAAAWQAAAAADWQAAAAACWQAAAAACWQAAAAADUgAAAAAAaAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAaAAAAAAAUgAAAAAAWQAAAAABYwAAAAACYwAAAAACYwAAAAAAYwAAAAADYwAAAAACWQAAAAAAUgAAAAAAaAAAAAAAeQAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAWQAAAAAAeQAAAAAAUgAAAAAAWQAAAAAAYwAAAAABYwAAAAADYwAAAAABYwAAAAACYwAAAAAAWQAAAAACUgAAAAAAaAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAWQAAAAADaAAAAAAAUgAAAAAAWQAAAAACYwAAAAABYwAAAAABYwAAAAADYwAAAAAAYwAAAAABWQAAAAAAUgAAAAAAaAAAAAAAeQAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAWQAAAAAAeQAAAAAAUgAAAAAA - version: 6 - 1,1: - ind: 1,1 - tiles: WQAAAAABWQAAAAACWQAAAAACWQAAAAAAWQAAAAABWQAAAAAAWQAAAAACUgAAAAAAaAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAaAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAaAAAAAAAUgAAAAAAWQAAAAACWQAAAAABWQAAAAABWQAAAAAAWQAAAAADUgAAAAAAeQAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAeQAAAAAAUgAAAAAAWQAAAAACWQAAAAACaAAAAAAAUgAAAAAAWQAAAAADYwAAAAACYwAAAAABYwAAAAAAWQAAAAABUgAAAAAAeQAAAAAAeQAAAAAAaAAAAAAAeQAAAAAAeQAAAAAAUgAAAAAAWQAAAAADYwAAAAADWQAAAAACUgAAAAAAWQAAAAADYwAAAAADYwAAAAADYwAAAAADWQAAAAABUgAAAAAAeQAAAAAAWQAAAAABeQAAAAAAWQAAAAABeQAAAAAAUgAAAAAAWQAAAAAAYwAAAAABWQAAAAADUgAAAAAAWQAAAAAAYwAAAAAAYwAAAAACYwAAAAACWQAAAAADUgAAAAAAeQAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAeQAAAAAAUgAAAAAAWQAAAAADYwAAAAADWQAAAAACUgAAAAAAWQAAAAACWQAAAAACWQAAAAADWQAAAAADWQAAAAACUgAAAAAAeQAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAeQAAAAAAUgAAAAAAWQAAAAACWQAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAWQAAAAADWQAAAAABWQAAAAAAUgAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAUgAAAAAAaAAAAAAAaAAAAAAAeQAAAAAAUgAAAAAAWQAAAAABWQAAAAACZAAAAAACUgAAAAAAWQAAAAABWQAAAAAAWQAAAAAAUgAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAUgAAAAAAeQAAAAAAaAAAAAAAaAAAAAAAUgAAAAAAWQAAAAACWQAAAAADZAAAAAABUgAAAAAAWQAAAAACWQAAAAAAWQAAAAACUgAAAAAAWQAAAAACWQAAAAAAWQAAAAACUgAAAAAAWQAAAAADWQAAAAAAWQAAAAADUgAAAAAAWQAAAAADWQAAAAACWQAAAAADUgAAAAAAZAAAAAADWQAAAAACZAAAAAADUgAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAUgAAAAAAaAAAAAAAaAAAAAAAeQAAAAAAUgAAAAAAZAAAAAAAWQAAAAADWQAAAAACUgAAAAAAZAAAAAABWQAAAAABZAAAAAABUgAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAUgAAAAAAaAAAAAAAeQAAAAAAaAAAAAAAUgAAAAAAZAAAAAADWQAAAAADWQAAAAABUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAWQAAAAAAWQAAAAACWQAAAAABWQAAAAADWQAAAAACWQAAAAAAWQAAAAACWQAAAAAAWQAAAAACWQAAAAACWQAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAaAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAA - version: 6 - -1,2: - ind: -1,2 - tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAA - version: 6 - -1,3: - ind: -1,3 - tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - version: 6 - 0,2: - ind: 0,2 - tiles: HQAAAAACHQAAAAACDwAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAAHQAAAAADHQAAAAAAUgAAAAAAaAAAAAAAaAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAaAAAAAAAeQAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAaAAAAAAAUgAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAeQAAAAAAaAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAaAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAaAAAAAAAaAAAAAAAUgAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAWQAAAAADaAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAaAAAAAAAaAAAAAAAeQAAAAAAaAAAAAAAaAAAAAAAUgAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAeQAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAHQAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAAHQAAAAAAUgAAAAAAHQAAAAABTQAAAAAATQAAAAAATQAAAAAATQAAAAAATQAAAAAAHQAAAAABUgAAAAAAHQAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAAHQAAAAAAUgAAAAAAHQAAAAAATQAAAAAANgAAAAAANgAAAAAANgAAAAAATQAAAAAAHQAAAAAAUgAAAAAAHQAAAAACDwAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAAHQAAAAACUgAAAAAAHQAAAAAATQAAAAAATQAAAAAATQAAAAAATQAAAAAATQAAAAAAHQAAAAACUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAADwAAAAAADwAAAAAAeQAAAAAAIgAAAAABeQAAAAAADwAAAAAADwAAAAAAUgAAAAAAbAAAAAACbAAAAAAAbAAAAAAAbAAAAAAAbAAAAAADbAAAAAABbAAAAAABUgAAAAAADwAAAAAADwAAAAAADwAAAAAAHQAAAAAADwAAAAAADwAAAAAADwAAAAAAUgAAAAAAbAAAAAABbAAAAAABeQAAAAAAbAAAAAAAeQAAAAAAbAAAAAABbAAAAAADUgAAAAAAeQAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAAeQAAAAAAUgAAAAAAIgAAAAABIgAAAAABIgAAAAACaAAAAAAAIgAAAAADIgAAAAACIgAAAAABUgAAAAAAIgAAAAAAHQAAAAACDwAAAAAADwAAAAAADwAAAAAAHQAAAAAAIgAAAAABUgAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAUgAAAAAAeQAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAAeQAAAAAAUgAAAAAAIgAAAAACIgAAAAAAIgAAAAABaAAAAAAAIgAAAAABIgAAAAACIgAAAAAAUgAAAAAADwAAAAAADwAAAAAADwAAAAAAHQAAAAADDwAAAAAADwAAAAAADwAAAAAAUgAAAAAAIgAAAAAAIgAAAAAAIgAAAAACaAAAAAAAIgAAAAAAIgAAAAACIgAAAAADUgAAAAAA - version: 6 - 0,3: - ind: 0,3 - tiles: DwAAAAAADwAAAAAAeQAAAAAAIgAAAAAAeQAAAAAADwAAAAAADwAAAAAAUgAAAAAAIgAAAAACIgAAAAAAIgAAAAAAaAAAAAAAIgAAAAABIgAAAAACIgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - version: 6 - 1,2: - ind: 1,2 - tiles: aAAAAAAAaAAAAAAAaAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAYwAAAAACYwAAAAADYwAAAAABeQAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAUgAAAAAAHQAAAAACHQAAAAADHQAAAAACTQAAAAAATQAAAAAATQAAAAAATQAAAAAATQAAAAAAYwAAAAACYwAAAAAAYwAAAAABWQAAAAACaAAAAAAAaAAAAAAAaAAAAAAAUgAAAAAAHQAAAAACHQAAAAABHQAAAAABTQAAAAAATQAAAAAATQAAAAAATQAAAAAATQAAAAAAYwAAAAADYwAAAAADYwAAAAADeQAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAUgAAAAAAHQAAAAADHQAAAAABHQAAAAABTQAAAAAATQAAAAAATQAAAAAATQAAAAAATQAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAWQAAAAACWQAAAAAAWQAAAAAAWQAAAAABWQAAAAADWQAAAAADWQAAAAAAUgAAAAAAaAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAaAAAAAAAUgAAAAAAWQAAAAACYwAAAAABYwAAAAACYwAAAAABYwAAAAADYwAAAAAAWQAAAAAAUgAAAAAAaAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAaAAAAAAAUgAAAAAAWQAAAAAAWQAAAAABWQAAAAABWQAAAAACWQAAAAAAWQAAAAAAWQAAAAADUgAAAAAAaAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAaAAAAAAAaAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAaAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAaAAAAAAAUgAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAWQAAAAACYwAAAAAAYwAAAAADYwAAAAADUgAAAAAAaAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAaAAAAAAAUgAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAWQAAAAADYwAAAAABYwAAAAABYwAAAAACUgAAAAAAaAAAAAAAeQAAAAAAaAAAAAAAeQAAAAAAaAAAAAAAeQAAAAAAaAAAAAAAUgAAAAAAWQAAAAACWQAAAAADWQAAAAABWQAAAAACYwAAAAADYwAAAAABYwAAAAACUgAAAAAAaAAAAAAAWQAAAAADeQAAAAAAeQAAAAAAeQAAAAAAWQAAAAACaAAAAAAAUgAAAAAAWQAAAAADWQAAAAADWQAAAAACWQAAAAADWQAAAAACWQAAAAADWQAAAAADUgAAAAAAaAAAAAAAWQAAAAABaAAAAAAAaAAAAAAAaAAAAAAAWQAAAAAAaAAAAAAAUgAAAAAAWQAAAAAAWQAAAAAAWQAAAAAAWQAAAAACIQAAAAABIQAAAAADWQAAAAACUgAAAAAAaAAAAAAAWQAAAAADeQAAAAAAeQAAAAAAeQAAAAAAWQAAAAAAaAAAAAAAUgAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAWQAAAAABIQAAAAAAIQAAAAABWQAAAAABUgAAAAAA - version: 6 - 1,3: - ind: 1,3 - tiles: aAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAaAAAAAAAUgAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAWQAAAAABWQAAAAAAWQAAAAAAWQAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - version: 6 - 2,-1: - ind: 2,-1 - tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - version: 6 - 2,0: - ind: 2,0 - tiles: HQAAAAAAHQAAAAABWQAAAAACUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHQAAAAABHQAAAAACWQAAAAABUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHQAAAAACHQAAAAACWQAAAAABUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHQAAAAAAHQAAAAADWQAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHQAAAAACHQAAAAAAWQAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATQAAAAAATQAAAAAATQAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgAAAAADeQAAAAAATQAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKgAAAAAAeQAAAAAATQAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAATQAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATQAAAAAATQAAAAAATQAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - version: 6 - 2,1: - ind: 2,1 - tiles: UgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWQAAAAABWQAAAAACWQAAAAADUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYwAAAAAAYwAAAAABWQAAAAADUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYwAAAAADYwAAAAABWQAAAAABUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYwAAAAABYwAAAAAAWQAAAAABUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWQAAAAACWQAAAAABWQAAAAACUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgAAAAACHQAAAAAAIgAAAAADUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgAAAAACHQAAAAACIgAAAAADUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHQAAAAABHQAAAAADHQAAAAADUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgAAAAABIgAAAAACIgAAAAADUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHQAAAAAAHQAAAAADHQAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - version: 6 - 2,2: - ind: 2,2 - tiles: UgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATQAAAAAAHQAAAAADHQAAAAABUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgAAAAABeQAAAAAAHQAAAAADUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATQAAAAAAHQAAAAAAHQAAAAADUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - version: 6 - 2,3: - ind: 2,3 - tiles: UgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - version: 6 - - type: Gravity - gravityShakeSound: !type:SoundPathSpecifier - path: /Audio/Effects/alert.ogg - - type: DecalGrid - chunkCollection: - version: 2 - nodes: - - node: - color: '#52B4E996' - id: BotGreyscale - decals: - 680: 30,25 - 681: 30,24 - 682: 28,27 - 683: 28,27 - 684: 28,28 - 685: 28,28 - 686: 30,25 - 687: 30,24 - - node: - color: '#DE3A3A96' - id: BotGreyscale - decals: - 478: 32,25 - 479: 32,24 - 480: 34,25 - 481: 34,24 - 482: 16,28 - 483: 18,28 - 484: 18,27 - 485: 16,27 - 486: 14,25 - 487: 14,24 - 488: 12,27 - 489: 12,28 - 748: 26,7 - 749: 32,7 - 750: 29,9 - 836: 6,2 - 837: 10,2 - 940: 1,9 - 941: 1,8 - 942: 1,7 - 943: 9,9 - 944: 9,8 - 945: 9,7 - 946: 20,10 - 947: 14,10 - 948: 14,6 - 949: 20,6 - 950: 22,6 - 951: 22,10 - 952: 12,10 - 953: 12,6 - - node: - color: '#FFFFFFFF' - id: BrickTileDarkCornerNe - decals: - 194: 21,4 - 250: 10,44 - 525: 12,32 - 543: 4,22 - 585: 6,40 - 659: 34,36 - 675: 30,28 - 703: 6,16 - 757: 10,10 - - node: - color: '#FFFFFFFF' - id: BrickTileDarkCornerNw - decals: - 191: 18,4 - 251: 12,44 - 519: 0,32 - 542: 0,22 - 582: 0,40 - 633: 24,36 - 702: 0,16 - 754: 0,10 - 853: 23,4 - - node: - color: '#FFFFFFFF' - id: BrickTileDarkCornerSe - decals: - 192: 21,0 - 238: 10,46 - 272: 14,42 - 524: 12,30 - 537: 4,18 - 584: 6,38 - 658: 34,34 - 701: 6,12 - 755: 10,6 - - node: - color: '#FFFFFFFF' - id: BrickTileDarkCornerSw - decals: - 193: 18,0 - 241: 12,46 - 270: 8,42 - 518: 0,30 - 534: 0,18 - 583: 0,38 - 632: 24,34 - 672: 28,24 - 700: 0,12 - 756: 0,6 - 852: 23,0 - - node: - color: '#FFFFFFFF' - id: BrickTileDarkLineE - decals: - 202: 21,1 - 203: 21,2 - 204: 21,3 - 239: 10,47 - 240: 10,48 - 273: 14,43 - 501: 0,25 - 502: 0,26 - 503: 0,27 - 535: 4,19 - 536: 4,21 - 592: 14,38 - 593: 14,40 - 674: 30,27 - 706: 6,15 - 707: 6,13 - 758: 10,9 - 759: 10,7 - 926: 5,45 - 929: 33,36 - 930: 33,34 - 931: 10,31 - 935: 8,10 - 936: 8,6 - 937: 7,2 - - node: - color: '#FFFFFFFF' - id: BrickTileDarkLineN - decals: - 197: 19,4 - 198: 20,4 - 248: 8,44 - 249: 9,44 - 252: 13,44 - 253: 14,44 - 506: 0,28 - 507: 2,28 - 520: 1,32 - 523: 11,32 - 540: 1,22 - 541: 3,22 - 634: 25,36 - 635: 26,36 - 704: 1,16 - 705: 5,16 - 762: 1,10 - 765: 9,10 - 846: 24,4 - 847: 25,4 - 848: 27,4 - 849: 28,4 - 850: 29,4 - 851: 30,4 - 927: 3,47 - - node: - color: '#FFFFFFFF' - id: BrickTileDarkLineS - decals: - 195: 20,0 - 196: 19,0 - 244: 8,46 - 245: 9,46 - 246: 13,46 - 247: 14,46 - 274: 9,42 - 275: 10,42 - 276: 13,42 - 277: 12,42 - 504: 0,24 - 505: 2,24 - 521: 1,30 - 522: 11,30 - 538: 3,18 - 539: 1,18 - 636: 25,34 - 637: 26,34 - 710: 1,12 - 711: 5,12 - 763: 1,6 - 764: 9,6 - 840: 25,0 - 841: 24,0 - 842: 27,0 - 843: 28,0 - 844: 29,0 - 845: 30,0 - 928: 3,43 - - node: - color: '#FFFFFFFF' - id: BrickTileDarkLineW - decals: - 199: 18,3 - 200: 18,2 - 201: 18,1 - 242: 12,47 - 243: 12,48 - 271: 8,43 - 498: 2,27 - 499: 2,26 - 500: 2,25 - 544: 0,19 - 545: 0,21 - 590: 8,40 - 591: 8,38 - 673: 28,25 - 708: 0,13 - 709: 0,15 - 760: 0,7 - 761: 0,9 - 854: 23,3 - 855: 23,1 - 925: 1,45 - 932: 2,31 - 933: 2,10 - 934: 2,6 - 938: 9,2 - 939: 30,2 - - node: - color: '#FFFFFFFF' - id: BrickTileSteelCornerNe - decals: - 84: 18,36 - 104: 16,25 - 336: 9,21 - 337: 10,22 - 364: 16,22 - 374: 21,15 - 375: 22,16 - 415: 21,21 - 421: 22,22 - 446: 33,21 - 447: 34,22 - 490: 12,25 - 554: 22,9 - 568: 14,9 - 610: 22,40 - 2025: 30,48 - 2039: 30,44 - 2058: 29,47 - - node: - color: '#FFFFFFFF' - id: BrickTileSteelCornerNw - decals: - 85: 16,36 - 103: 18,25 - 338: 7,21 - 339: 6,22 - 376: 17,15 - 377: 16,16 - 414: 19,21 - 420: 18,22 - 448: 31,21 - 449: 30,22 - 555: 20,9 - 569: 12,9 - 611: 16,40 - 2040: 28,44 - 2059: 28,47 - - node: - color: '#FFFFFFFF' - id: BrickTileSteelCornerSe - decals: - 83: 18,34 - 308: 26,30 - 332: 9,19 - 335: 10,18 - 372: 21,13 - 373: 22,12 - 418: 21,19 - 419: 22,18 - 452: 33,19 - 453: 34,18 - 561: 22,7 - 562: 14,7 - 609: 22,38 - 778: 16,0 - 779: 5,0 - 2038: 30,42 - 2056: 29,46 - - node: - color: '#FFFFFFFF' - id: BrickTileSteelCornerSw - decals: - 81: 16,34 - 82: 3,35 - 309: 14,30 - 333: 7,19 - 334: 6,18 - 370: 17,13 - 371: 16,12 - 416: 18,18 - 417: 19,19 - 450: 31,19 - 451: 30,18 - 493: 14,27 - 558: 20,7 - 567: 12,7 - 608: 16,38 - 780: 0,0 - 781: 11,0 - 2037: 28,42 - 2057: 28,46 - - node: - color: '#FFFFFFFF' - id: BrickTileSteelEndE - decals: - 73: 21,39 - - node: - color: '#FFFFFFFF' - id: BrickTileSteelEndW - decals: - 74: 17,39 - - node: - color: '#D4D4D496' - id: BrickTileSteelLineE - decals: - 1330: 32,2 - 1332: 31,2 - - node: - color: '#FFFFFFFF' - id: BrickTileSteelLineE - decals: - 87: 18,35 - 93: 14,34 - 94: 14,35 - 95: 14,36 - 105: 16,24 - 286: 17,45 - 287: 17,46 - 288: 17,47 - 323: 27,20 - 349: 9,20 - 350: 10,21 - 351: 10,19 - 365: 16,21 - 378: 21,14 - 379: 22,13 - 380: 22,15 - 425: 21,20 - 426: 22,19 - 427: 22,21 - 454: 34,19 - 455: 34,21 - 462: 33,20 - 491: 12,24 - 559: 22,8 - 566: 14,8 - 802: 16,1 - 806: 5,1 - 872: 34,0 - 873: 34,1 - 874: 34,3 - 875: 34,4 - 1328: 33,2 - 2023: 30,46 - 2024: 30,47 - 2041: 30,43 - - node: - color: '#FFFFFFFF' - id: BrickTileSteelLineN - decals: - 75: 18,39 - 76: 19,39 - 77: 20,39 - 86: 17,36 - 340: 8,21 - 341: 9,22 - 342: 7,22 - 366: 15,22 - 391: 18,15 - 392: 19,15 - 393: 20,15 - 394: 21,16 - 395: 20,16 - 396: 18,16 - 397: 17,16 - 422: 20,21 - 423: 19,22 - 424: 21,22 - 458: 31,22 - 459: 33,22 - 460: 32,21 - 556: 21,9 - 564: 13,9 - 572: 16,10 - 573: 18,10 - 612: 17,40 - 613: 18,40 - 614: 21,40 - 615: 20,40 - 2026: 29,48 - 2027: 28,48 - 2044: 29,44 - - node: - color: '#FFFFFFFF' - id: BrickTileSteelLineS - decals: - 78: 18,39 - 79: 19,39 - 80: 20,39 - 89: 17,34 - 298: 15,30 - 299: 16,30 - 300: 17,30 - 301: 18,30 - 302: 19,30 - 303: 21,30 - 304: 22,30 - 305: 23,30 - 306: 24,30 - 307: 25,30 - 346: 8,19 - 347: 7,18 - 348: 9,18 - 384: 18,13 - 385: 19,13 - 386: 20,13 - 387: 20,12 - 388: 21,12 - 389: 18,12 - 390: 17,12 - 431: 20,19 - 432: 19,18 - 433: 21,18 - 463: 32,19 - 464: 31,18 - 465: 33,18 - 557: 21,7 - 563: 13,7 - 570: 16,6 - 571: 18,6 - 616: 17,38 - 617: 18,38 - 618: 21,38 - 619: 20,38 - 782: 1,0 - 783: 2,0 - 784: 3,0 - 785: 4,0 - 786: 0,3 - 787: 1,3 - 788: 2,3 - 789: 3,3 - 790: 4,3 - 791: 5,3 - 792: 12,0 - 793: 13,0 - 794: 14,0 - 795: 15,0 - 796: 11,3 - 797: 12,3 - 798: 13,3 - 799: 14,3 - 800: 15,3 - 801: 16,3 - 2043: 29,42 - - node: - color: '#D4D4D496' - id: BrickTileSteelLineW - decals: - 1329: 33,2 - 1331: 32,2 - 1333: 31,2 - - node: - color: '#FFFFFFFF' - id: BrickTileSteelLineW - decals: - 88: 16,35 - 90: 20,34 - 91: 20,35 - 92: 20,36 - 102: 18,24 - 289: 21,45 - 290: 21,46 - 291: 21,47 - 322: 25,20 - 326: 29,15 - 327: 29,14 - 328: 29,13 - 343: 7,20 - 344: 6,21 - 345: 6,19 - 381: 17,14 - 382: 16,15 - 383: 16,13 - 428: 19,20 - 429: 18,19 - 430: 18,21 - 456: 30,19 - 457: 30,21 - 461: 31,20 - 492: 14,28 - 560: 20,8 - 565: 12,8 - 803: 0,1 - 805: 11,1 - 2021: 24,44 - 2022: 24,46 - 2042: 28,43 - - node: - color: '#52B4E996' - id: BrickTileWhiteCornerNe - decals: - 264: 10,44 - 679: 30,28 - - node: - color: '#DE3A3A96' - id: BrickTileWhiteCornerNe - decals: - 109: 16,25 - 362: 10,22 - 367: 16,22 - 413: 22,16 - 438: 22,22 - 475: 34,22 - 494: 12,25 - 527: 12,32 - 580: 4,22 - 587: 6,40 - 630: 22,40 - 660: 34,36 - 715: 6,16 - 777: 10,10 - 2045: 30,48 - - node: - color: '#FFFFFFFF' - id: BrickTileWhiteCornerNe - decals: - 689: 10,28 - - node: - color: '#52B4E996' - id: BrickTileWhiteCornerNw - decals: - 265: 12,44 - - node: - color: '#DE3A3A96' - id: BrickTileWhiteCornerNw - decals: - 108: 18,25 - 363: 6,22 - 412: 16,16 - 442: 18,22 - 476: 30,22 - 532: 0,32 - 579: 0,22 - 588: 0,40 - 629: 16,40 - 639: 24,36 - 714: 0,16 - 772: 0,10 - 858: 23,4 - - node: - color: '#FFFFFFFF' - id: BrickTileWhiteCornerNw - decals: - 688: 8,28 - - node: - color: '#52B4E996' - id: BrickTileWhiteCornerSe - decals: - 263: 10,46 - 279: 14,42 - - node: - color: '#DE3A3A96' - id: BrickTileWhiteCornerSe - decals: - 361: 10,18 - 411: 22,12 - 443: 22,18 - 477: 34,18 - 526: 12,30 - 581: 4,18 - 586: 6,38 - 628: 22,38 - 661: 34,34 - 713: 6,12 - 771: 10,6 - 809: 5,0 - 810: 16,0 - - node: - color: '#EFB34196' - id: BrickTileWhiteCornerSe - decals: - 311: 26,30 - - node: - color: '#FFFFFFFF' - id: BrickTileWhiteCornerSe - decals: - 690: 10,24 - - node: - color: '#52B4E996' - id: BrickTileWhiteCornerSw - decals: - 262: 12,46 - 278: 8,42 - 676: 28,24 - - node: - color: '#DE3A3A96' - id: BrickTileWhiteCornerSw - decals: - 360: 6,18 - 406: 16,12 - 439: 18,18 - 474: 30,18 - 496: 14,27 - 533: 0,30 - 578: 0,18 - 589: 0,38 - 631: 16,38 - 638: 24,34 - 712: 0,12 - 770: 0,6 - 807: 0,0 - 808: 11,0 - 859: 23,0 - - node: - color: '#EFB34196' - id: BrickTileWhiteCornerSw - decals: - 310: 14,30 - - node: - color: '#FFFFFFFF' - id: BrickTileWhiteCornerSw - decals: - 691: 8,24 - - node: - color: '#52B4E996' - id: BrickTileWhiteLineE - decals: - 258: 10,47 - 259: 10,48 - 280: 14,43 - 678: 30,27 - - node: - color: '#D4D4D419' - id: BrickTileWhiteLineE - decals: - 895: 2,6 - 896: 2,10 - 900: 9,2 - 901: 30,2 - 906: 1,45 - 908: 2,31 - - node: - color: '#DE3A3A96' - id: BrickTileWhiteLineE - decals: - 96: 14,36 - 97: 14,35 - 98: 14,34 - 106: 16,24 - 356: 10,19 - 357: 10,21 - 368: 16,21 - 409: 22,15 - 410: 22,13 - 444: 22,19 - 445: 22,21 - 470: 34,21 - 471: 34,19 - 495: 12,24 - 511: 0,25 - 512: 0,26 - 513: 0,27 - 548: 4,19 - 549: 4,21 - 596: 14,38 - 597: 14,40 - 716: 6,15 - 717: 6,13 - 768: 10,9 - 769: 10,7 - 811: 5,1 - 812: 16,1 - 876: 34,0 - 877: 34,1 - 878: 34,3 - 879: 34,4 - 2048: 30,47 - 2049: 30,46 - - node: - color: '#EFB34196' - id: BrickTileWhiteLineE - decals: - 292: 17,45 - 293: 17,46 - 294: 17,47 - 324: 27,20 - - node: - color: '#FFFFFFFF' - id: BrickTileWhiteLineE - decals: - 693: 10,27 - 694: 10,26 - 695: 10,25 - - node: - color: '#52B4E996' - id: BrickTileWhiteLineN - decals: - 266: 9,44 - 267: 8,44 - 268: 13,44 - 269: 14,44 - - node: - color: '#D4D4D419' - id: BrickTileWhiteLineN - decals: - 907: 3,43 - - node: - color: '#DE3A3A96' - id: BrickTileWhiteLineN - decals: - 358: 9,22 - 359: 7,22 - 369: 15,22 - 398: 18,16 - 399: 17,16 - 400: 21,16 - 401: 20,16 - 436: 19,22 - 437: 21,22 - 468: 31,22 - 469: 33,22 - 516: 0,28 - 517: 2,28 - 528: 11,32 - 531: 1,32 - 552: 1,22 - 553: 3,22 - 574: 16,10 - 575: 18,10 - 620: 18,40 - 621: 17,40 - 622: 20,40 - 623: 21,40 - 642: 25,36 - 643: 26,36 - 722: 1,16 - 723: 5,16 - 773: 1,10 - 774: 9,10 - 866: 24,4 - 867: 25,4 - 868: 27,4 - 869: 28,4 - 870: 30,4 - 871: 29,4 - 2046: 28,48 - 2047: 29,48 - - node: - color: '#FFFFFFFF' - id: BrickTileWhiteLineN - decals: - 692: 9,28 - - node: - color: '#52B4E996' - id: BrickTileWhiteLineS - decals: - 254: 8,46 - 255: 9,46 - 256: 13,46 - 257: 14,46 - 282: 9,42 - 283: 10,42 - 284: 12,42 - 285: 13,42 - - node: - color: '#D4D4D419' - id: BrickTileWhiteLineS - decals: - 905: 3,47 - - node: - color: '#DE3A3A96' - id: BrickTileWhiteLineS - decals: - 352: 7,18 - 353: 9,18 - 402: 17,12 - 403: 18,12 - 404: 20,12 - 405: 21,12 - 434: 19,18 - 435: 21,18 - 466: 33,18 - 467: 31,18 - 514: 0,24 - 515: 2,24 - 529: 11,30 - 530: 1,30 - 550: 1,18 - 551: 3,18 - 576: 16,6 - 577: 18,6 - 624: 17,38 - 625: 18,38 - 626: 20,38 - 627: 21,38 - 640: 25,34 - 641: 26,34 - 718: 5,12 - 719: 1,12 - 775: 1,6 - 776: 9,6 - 813: 12,0 - 814: 13,0 - 815: 14,0 - 816: 15,0 - 817: 16,3 - 818: 15,3 - 819: 13,3 - 820: 14,3 - 821: 11,3 - 822: 12,3 - 823: 0,3 - 824: 1,3 - 825: 2,3 - 826: 3,3 - 827: 4,3 - 828: 5,3 - 829: 4,0 - 830: 3,0 - 831: 2,0 - 832: 1,0 - 860: 24,0 - 861: 25,0 - 862: 27,0 - 863: 28,0 - 864: 29,0 - 865: 30,0 - - node: - color: '#EFB34196' - id: BrickTileWhiteLineS - decals: - 312: 15,30 - 313: 16,30 - 314: 17,30 - 315: 18,30 - 316: 19,30 - 317: 21,30 - 318: 22,30 - 319: 23,30 - 320: 24,30 - 321: 25,30 - - node: - color: '#FFFFFFFF' - id: BrickTileWhiteLineS - decals: - 696: 9,24 - - node: - color: '#52B4E996' - id: BrickTileWhiteLineW - decals: - 260: 12,47 - 261: 12,48 - 281: 8,43 - 677: 28,25 - - node: - color: '#D4D4D419' - id: BrickTileWhiteLineW - decals: - 897: 8,10 - 898: 8,6 - 899: 7,2 - 902: 33,36 - 903: 33,34 - 904: 5,45 - 909: 10,31 - - node: - color: '#DE3A3A96' - id: BrickTileWhiteLineW - decals: - 99: 20,36 - 100: 20,35 - 101: 20,34 - 107: 18,24 - 354: 6,19 - 355: 6,21 - 407: 16,13 - 408: 16,15 - 440: 18,19 - 441: 18,21 - 472: 30,19 - 473: 30,21 - 497: 14,28 - 508: 2,25 - 509: 2,26 - 510: 2,27 - 546: 0,19 - 547: 0,21 - 594: 8,38 - 595: 8,40 - 720: 0,13 - 721: 0,15 - 766: 0,7 - 767: 0,9 - 804: 0,1 - 833: 11,1 - 856: 23,1 - 857: 23,3 - 2050: 24,44 - 2051: 24,46 - - node: - color: '#EFB34196' - id: BrickTileWhiteLineW - decals: - 295: 21,45 - 296: 21,46 - 297: 21,47 - 325: 25,20 - 329: 29,13 - 330: 29,14 - 331: 29,15 - - node: - color: '#FFFFFFFF' - id: BrickTileWhiteLineW - decals: - 697: 8,25 - 698: 8,26 - 699: 8,27 - - node: - cleanable: True - angle: 1.5707963267948966 rad - color: '#B02E269B' - id: Clandestine - decals: - 2136: 3.132535,34.09553 - - node: - color: '#A4610696' - id: Dirt - decals: - 1334: 31,2 - 1335: 32,2 - 1336: 33,2 - 1337: 30,2 - 1338: 34,2 - 1339: 33,3 - 1340: 31,4 - 1341: 32,3 - 1342: 32,1 - 1343: 31,1 - 1344: 31,0 - 1345: 33,0 - 1346: 32,0 - 1347: 33,3 - 1348: 33,4 - 1349: 32,4 - 1350: 31,3 - 1351: 32,1 - 1352: 33,1 - - node: - cleanable: True - color: '#A4610696' - id: Dirt - decals: - 954: 0,45 - 955: 3,47 - 956: 3,48 - 957: 6,45 - 958: 3,42 - 959: 3,43 - 960: 5,45 - 961: 1,45 - 962: 13,45 - 963: 12,45 - 964: 11,47 - 965: 9,45 - 966: 8,45 - 967: 8,46 - 968: 10,47 - 969: 13,47 - 970: 14,46 - 971: 11,43 - 972: 11,42 - 973: 9,42 - 974: 10,42 - 975: 13,42 - 976: 12,42 - 977: 13,43 - 978: 9,43 - 979: 11,44 - 980: 11,45 - 981: 11,47 - 982: 9,46 - 983: 11,46 - 984: 16,45 - 985: 16,46 - 986: 16,47 - 987: 16,44 - 988: 17,45 - 989: 17,46 - 990: 21,45 - 991: 22,45 - 992: 22,44 - 993: 22,43 - 994: 22,46 - 995: 21,45 - 996: 21,46 - 997: 22,47 - 998: 24,39 - 999: 24,38 - 1000: 24,40 - 1001: 27,39 - 1002: 27,39 - 1003: 30,40 - 1004: 30,39 - 1005: 30,38 - 1006: 30,39 - 1007: 34,35 - 1008: 33,34 - 1009: 33,36 - 1010: 24,35 - 1011: 22,35 - 1012: 20,35 - 1013: 21,35 - 1014: 20,34 - 1015: 14,35 - 1016: 13,35 - 1017: 12,35 - 1018: 14,36 - 1019: 17,36 - 1020: 17,35 - 1021: 17,34 - 1022: 16,35 - 1023: 18,35 - 1024: 18,36 - 1025: 18,34 - 1026: 16,34 - 1027: 16,36 - 1028: 16,30 - 1029: 16,30 - 1030: 18,30 - 1031: 19,30 - 1032: 22,30 - 1033: 24,30 - 1034: 25,30 - 1035: 26,30 - 1036: 20,30 - 1037: 20,30 - 1038: 15,30 - 1039: 14,30 - 1040: 14,31 - 1041: 26,31 - 1042: 10,31 - 1043: 2,31 - 1044: 0,31 - 1045: 12,31 - 1046: 5,35 - 1047: 6,36 - 1048: 4,34 - 1049: 9,35 - 1050: 9,35 - 1051: 10,35 - 1052: 10,34 - 1053: 0,35 - 1054: 0,36 - 1055: 0,35 - 1056: 5,36 - 1057: 6,36 - 1058: 6,39 - 1059: 0,39 - 1060: 8,39 - 1061: 8,40 - 1062: 8,38 - 1063: 9,39 - 1064: 11,38 - 1065: 9,38 - 1066: 11,40 - 1067: 10,40 - 1068: 13,39 - 1069: 14,39 - 1070: 14,40 - 1071: 14,38 - 1072: 13,39 - 1073: 13,40 - 1074: 16,39 - 1075: 18,40 - 1076: 19,40 - 1077: 19,39 - 1078: 18,39 - 1079: 17,39 - 1080: 20,39 - 1081: 21,39 - 1082: 19,38 - 1083: 22,39 - 1084: 21,38 - 1085: 24,39 - 1086: 24,38 - 1087: 24,40 - 1088: 17,32 - 1089: 24,32 - 1090: 23,32 - 1091: 34,26 - 1092: 33,26 - 1093: 33,28 - 1094: 32,28 - 1095: 32,27 - 1096: 34,27 - 1097: 34,28 - 1098: 32,25 - 1099: 33,24 - 1100: 33,25 - 1101: 32,26 - 1102: 29,24 - 1103: 30,26 - 1104: 28,26 - 1105: 29,28 - 1106: 29,27 - 1107: 29,25 - 1108: 25,26 - 1109: 24,26 - 1110: 26,26 - 1111: 25,27 - 1112: 24,28 - 1113: 25,28 - 1114: 24,27 - 1115: 24,24 - 1116: 25,25 - 1117: 25,24 - 1118: 26,25 - 1119: 22,26 - 1120: 24,26 - 1121: 26,26 - 1122: 20,26 - 1123: 21,25 - 1124: 21,24 - 1125: 21,28 - 1126: 21,27 - 1127: 18,26 - 1128: 16,26 - 1129: 17,27 - 1130: 17,28 - 1131: 17,25 - 1132: 17,24 - 1133: 17,26 - 1134: 13,28 - 1135: 13,27 - 1136: 13,26 - 1137: 13,25 - 1138: 13,24 - 1139: 12,26 - 1140: 14,26 - 1141: 9,26 - 1142: 9,27 - 1143: 9,28 - 1144: 8,28 - 1145: 8,27 - 1146: 8,26 - 1147: 9,25 - 1148: 8,25 - 1149: 8,24 - 1150: 9,24 - 1151: 10,24 - 1152: 10,25 - 1153: 10,26 - 1154: 10,27 - 1155: 10,28 - 1156: 5,28 - 1157: 5,28 - 1158: 5,24 - 1159: 5,24 - 1160: 0,26 - 1161: 2,26 - 1162: 2,28 - 1163: 1,24 - 1164: 2,24 - 1165: 2,22 - 1166: 4,20 - 1167: 2,18 - 1168: 1,19 - 1169: 0,20 - 1170: 0,17 - 1171: 0,18 - 1172: 4,18 - 1173: 4,19 - 1174: 4,22 - 1175: 0,22 - 1176: 6,20 - 1177: 7,20 - 1178: 8,20 - 1179: 9,20 - 1180: 10,20 - 1181: 8,21 - 1182: 8,22 - 1183: 8,19 - 1184: 8,18 - 1185: 6,18 - 1186: 7,19 - 1187: 7,18 - 1188: 9,20 - 1189: 10,21 - 1190: 9,21 - 1191: 9,22 - 1192: 9,19 - 1193: 7,21 - 1194: 8,13 - 1195: 8,13 - 1196: 11,16 - 1197: 11,16 - 1198: 10,16 - 1199: 9,16 - 1200: 8,15 - 1201: 8,14 - 1202: 8,16 - 1203: 10,16 - 1204: 9,16 - 1205: 12,14 - 1206: 11,14 - 1207: 11,12 - 1208: 10,12 - 1209: 14,12 - 1210: 14,14 - 1211: 14,15 - 1212: 13,14 - 1213: 12,14 - 1214: 11,14 - 1215: 11,13 - 1216: 6,14 - 1217: 0,14 - 1218: 3,16 - 1219: 3,12 - 1220: 3,13 - 1221: 2,13 - 1222: 4,13 - 1223: 3,15 - 1224: 2,15 - 1225: 4,15 - 1226: 5,14 - 1227: 1,14 - 1228: 1,15 - 1229: 1,13 - 1230: 5,15 - 1231: 5,13 - 1232: 0,8 - 1233: 2,10 - 1234: 2,6 - 1235: 8,6 - 1236: 8,10 - 1237: 0,8 - 1238: 10,8 - 1239: 10,10 - 1240: 10,7 - 1241: 10,6 - 1242: 9,6 - 1243: 9,10 - 1244: 1,10 - 1245: 0,7 - 1246: 0,6 - 1247: 0,3 - 1248: 1,3 - 1249: 1,3 - 1250: 2,3 - 1251: 3,3 - 1252: 4,3 - 1253: 4,3 - 1254: 5,3 - 1255: 7,2 - 1256: 9,2 - 1257: 4,0 - 1258: 4,1 - 1259: 2,1 - 1260: 1,1 - 1261: 0,2 - 1262: 1,2 - 1263: 2,2 - 1264: 5,2 - 1265: 4,2 - 1266: 3,2 - 1267: 4,1 - 1268: 3,1 - 1269: 11,2 - 1270: 12,3 - 1271: 11,3 - 1272: 14,3 - 1273: 14,3 - 1274: 15,3 - 1275: 16,3 - 1276: 13,3 - 1277: 11,3 - 1278: 12,2 - 1279: 11,1 - 1280: 13,1 - 1281: 16,2 - 1282: 16,1 - 1283: 14,1 - 1284: 13,2 - 1285: 14,2 - 1286: 18,2 - 1287: 19,2 - 1288: 20,2 - 1289: 21,2 - 1290: 19,3 - 1291: 18,3 - 1292: 20,3 - 1293: 20,1 - 1294: 21,1 - 1295: 19,1 - 1296: 23,2 - 1297: 28,2 - 1298: 29,2 - 1299: 30,2 - 1300: 28,4 - 1301: 29,4 - 1302: 26,4 - 1303: 26,2 - 1304: 26,1 - 1305: 26,0 - 1306: 24,2 - 1307: 25,2 - 1308: 27,2 - 1309: 24,4 - 1310: 23,3 - 1311: 24,3 - 1312: 23,4 - 1469: 14,8 - 1470: 12,8 - 1471: 12,7 - 1472: 13,7 - 1473: 13,6 - 1474: 13,10 - 1475: 12,9 - 1476: 14,9 - 1477: 17,10 - 1478: 17,9 - 1479: 18,9 - 1480: 18,8 - 1481: 17,7 - 1482: 17,7 - 1483: 17,6 - 1484: 17,8 - 1485: 16,7 - 1486: 18,7 - 1487: 20,9 - 1488: 20,8 - 1489: 21,10 - 1490: 21,9 - 1491: 21,7 - 1492: 21,6 - 1493: 20,7 - 1494: 22,7 - 1495: 22,8 - 1534: 19,16 - 1535: 18,15 - 1536: 19,13 - 1537: 19,12 - 1538: 22,14 - 1539: 22,14 - 1540: 22,15 - 1541: 21,16 - 1542: 21,15 - 1543: 21,13 - 1544: 16,14 - 1545: 17,13 - 1546: 17,15 - 1556: 30,20 - 1557: 32,22 - 1558: 31,22 - 1559: 31,21 - 1560: 32,19 - 1561: 32,18 - 1562: 34,20 - 1563: 34,19 - 1564: 34,18 - 1565: 34,21 - 1566: 34,22 - 2060: 24,45 - 2061: 25,45 - 2062: 27,48 - 2063: 27,42 - 2064: 30,45 - 2065: 28,43 - 2066: 29,43 - 2067: 30,43 - 2068: 24,42 - 2069: 25,43 - 2070: 26,42 - 2071: 25,42 - 2072: 26,48 - 2073: 25,47 - 2074: 26,47 - 2075: 25,47 - 2076: 25,48 - 2077: 27,47 - 2078: 27,45 - 2079: 27,46 - 2080: 26,45 - 2081: 28,45 - 2082: 26,44 - 2083: 27,44 - 2084: 25,46 - 2085: 26,46 - 2086: 29,45 - - node: - color: '#A4610696' - id: DirtHeavy - decals: - 1353: 11,31 - 1354: 1,31 - 1355: 0,39 - 1356: 6,39 - 1357: 5,45 - 1358: 14,39 - 1359: 16,39 - 1360: 19,38 - - node: - cleanable: True - color: '#A4610696' - id: DirtHeavy - decals: - 1361: 22,39 - 1362: 19,40 - 1363: 30,39 - 1364: 25,35 - 1365: 24,36 - 1366: 20,30 - 1367: 14,30 - 1368: 18,30 - 1369: 22,30 - 1370: 26,31 - 1371: 22,26 - 1372: 24,26 - 1373: 26,26 - 1374: 28,26 - 1375: 29,27 - 1376: 33,25 - 1377: 33,28 - 1378: 32,28 - 1379: 14,26 - 1380: 12,26 - 1381: 9,26 - 1382: 9,28 - 1383: 8,28 - 1384: 8,24 - 1385: 10,25 - 1386: 5,24 - 1387: 5,28 - 1388: 0,26 - 1389: 0,27 - 1390: 2,24 - 1391: 2,25 - 1392: 0,35 - 1393: 0,36 - 1394: 7,36 - 1395: 5,35 - 1396: 4,34 - 1397: 10,35 - 1398: 9,35 - 1399: 8,39 - 1400: 14,39 - 1401: 14,38 - 1402: 8,45 - 1403: 11,48 - 1404: 11,44 - 1405: 14,45 - 1406: 11,42 - 1407: 9,42 - 1408: 13,43 - 1409: 3,47 - 1410: 4,20 - 1411: 3,22 - 1412: 2,18 - 1413: 0,19 - 1414: 6,20 - 1415: 8,20 - 1416: 8,18 - 1417: 10,20 - 1418: 8,22 - 1419: 14,20 - 1420: 15,21 - 1421: 16,20 - 1422: 12,20 - 1423: 12,21 - 1424: 13,22 - 1425: 13,21 - 1426: 13,19 - 1427: 16,19 - 1428: 16,19 - 1429: 15,20 - 1430: 14,18 - 1431: 12,20 - 1432: 14,22 - 1433: 20,20 - 1434: 20,22 - 1435: 18,20 - 1436: 22,21 - 1437: 19,21 - 1438: 20,18 - 1439: 22,20 - 1440: 27,22 - 1441: 25,22 - 1442: 26,18 - 1443: 27,18 - 1444: 31,18 - 1445: 32,18 - 1446: 31,20 - 1447: 32,21 - 1448: 34,22 - 1449: 34,20 - 1450: 32,19 - 1451: 29,14 - 1452: 24,14 - 1453: 24,13 - 1454: 30,14 - 1455: 29,13 - 1456: 34,2 - 1457: 32,3 - 1458: 30,1 - 1459: 26,1 - 1460: 23,3 - 1461: 24,4 - 1462: 29,4 - 1463: 26,0 - 1464: 26,1 - 1465: 18,2 - 1466: 22,8 - 1467: 20,8 - 1468: 16,8 - 1496: 17,6 - 1497: 16,8 - 1498: 18,8 - 1499: 17,10 - 1500: 14,8 - 1501: 12,8 - 1502: 13,6 - 1503: 13,10 - 1504: 21,10 - 1505: 21,9 - 1506: 21,8 - 1507: 21,7 - 1508: 21,6 - 1509: 10,8 - 1510: 9,10 - 1511: 9,6 - 1512: 2,6 - 1513: 0,7 - 1514: 0,9 - 1515: 1,10 - 1516: 8,10 - 1517: 0,14 - 1518: 5,14 - 1519: 4,15 - 1520: 3,16 - 1521: 1,18 - 1522: 5,15 - 1523: 6,14 - 1524: 11,16 - 1525: 8,13 - 1526: 11,12 - 1527: 10,13 - 1528: 16,14 - 1529: 19,16 - 1530: 19,13 - 1531: 18,13 - 1532: 18,15 - 1533: 16,15 - 1547: 19,16 - 1548: 16,14 - 1549: 17,15 - 1550: 18,14 - 1551: 19,13 - 1552: 21,13 - 1553: 22,14 - 1554: 29,15 - 1555: 29,13 - 2095: 24,45 - 2096: 27,48 - 2097: 27,42 - 2098: 25,44 - 2099: 29,45 - 2100: 29,46 - 2101: 29,47 - 2102: 25,45 - - node: - cleanable: True - color: '#A4610696' - id: DirtLight - decals: - 1567: 0,2 - 1568: 5,2 - 1569: 4,1 - 1570: 5,2 - 1571: 2,1 - 1572: 7,2 - 1573: 9,2 - 1574: 11,2 - 1575: 14,2 - 1576: 13,3 - 1577: 10,3 - 1578: 11,3 - 1579: 12,3 - 1580: 16,3 - 1581: 15,3 - 1582: 16,2 - 1583: 16,1 - 1584: 14,1 - 1585: 15,1 - 1586: 1,3 - 1587: 3,3 - 1588: 5,3 - 1589: 5,3 - 1590: 1,1 - 1591: 23,2 - 1592: 26,2 - 1593: 28,2 - 1594: 28,4 - 1595: 30,2 - 1596: 31,1 - 1597: 34,1 - 1598: 33,0 - 1599: 34,3 - 1600: 21,9 - 1601: 20,8 - 1602: 22,8 - 1603: 16,8 - 1604: 17,9 - 1605: 17,10 - 1606: 17,6 - 1607: 12,8 - 1608: 14,9 - 1609: 14,8 - 1610: 13,6 - 1611: 10,8 - 1612: 8,10 - 1613: 2,10 - 1614: 0,8 - 1615: 0,9 - 1616: 2,6 - 1617: 0,14 - 1618: 3,12 - 1619: 1,14 - 1620: 3,15 - 1621: 5,14 - 1622: 5,15 - 1623: 6,14 - 1624: 3,13 - 1625: 3,12 - 1626: 8,13 - 1627: 10,16 - 1628: 11,16 - 1629: 17,14 - 1630: 17,13 - 1631: 16,15 - 1632: 19,16 - 1633: 22,14 - 1634: 21,14 - 1635: 21,13 - 1636: 19,13 - 1637: 20,12 - 1638: 20,13 - 1639: 21,15 - 1640: 29,14 - 1641: 29,15 - 1642: 34,20 - 1643: 32,18 - 1644: 34,18 - 1645: 34,21 - 1646: 32,22 - 1647: 30,20 - 1648: 30,21 - 1649: 32,19 - 1650: 32,21 - 1651: 32,20 - 1652: 30,18 - 1653: 26,22 - 1654: 25,22 - 1655: 25,20 - 1656: 27,20 - 1657: 27,22 - 1658: 25,20 - 1659: 27,20 - 1660: 26,18 - 1661: 27,18 - 1662: 25,18 - 1663: 22,20 - 1664: 18,20 - 1665: 20,22 - 1666: 20,20 - 1667: 19,19 - 1668: 20,19 - 1669: 20,21 - 1670: 21,20 - 1671: 16,20 - 1672: 16,21 - 1673: 12,20 - 1674: 13,21 - 1675: 13,22 - 1676: 13,19 - 1677: 13,19 - 1678: 14,20 - 1679: 14,18 - 1680: 10,20 - 1681: 10,21 - 1682: 8,22 - 1683: 6,21 - 1684: 6,20 - 1685: 7,20 - 1686: 7,19 - 1687: 8,20 - 1688: 9,20 - 1689: 4,20 - 1690: 3,18 - 1691: 2,18 - 1692: 1,18 - 1693: 0,21 - 1694: 1,22 - 1695: 0,20 - 1696: 2,22 - 1697: 1,24 - 1698: 0,26 - 1699: 0,25 - 1700: 2,25 - 1701: 2,27 - 1702: 5,28 - 1703: 5,24 - 1704: 9,26 - 1705: 9,27 - 1706: 9,24 - 1707: 8,24 - 1708: 10,24 - 1709: 10,26 - 1710: 13,26 - 1711: 12,26 - 1712: 13,28 - 1713: 12,28 - 1714: 13,24 - 1715: 14,26 - 1716: 13,25 - 1717: 18,26 - 1718: 16,26 - 1719: 17,28 - 1720: 17,25 - 1721: 17,24 - 1722: 20,26 - 1723: 22,26 - 1724: 21,26 - 1725: 21,27 - 1726: 21,24 - 1727: 21,25 - 1728: 25,26 - 1729: 26,26 - 1730: 25,26 - 1731: 25,26 - 1732: 25,24 - 1733: 25,24 - 1734: 26,25 - 1735: 24,27 - 1736: 29,26 - 1737: 29,27 - 1738: 30,26 - 1739: 29,24 - 1740: 33,26 - 1741: 33,24 - 1742: 34,26 - 1743: 32,26 - 1744: 34,28 - 1745: 34,28 - 1746: 33,28 - 1747: 32,28 - 1748: 26,31 - 1749: 21,30 - 1750: 20,30 - 1751: 17,30 - 1752: 15,30 - 1753: 14,31 - 1754: 10,31 - 1755: 2,31 - 1756: 11,30 - 1757: 12,31 - 1758: 0,32 - 1759: 1,32 - 1760: 0,31 - 1761: -1,35 - 1762: 0,36 - 1763: 0,35 - 1764: 4,34 - 1765: 5,35 - 1766: 3,34 - 1767: 10,34 - 1768: 10,35 - 1769: 6,39 - 1770: 0,39 - 1771: 0,45 - 1772: 3,43 - 1773: 3,47 - 1774: 4,45 - 1775: 5,45 - 1776: 8,45 - 1777: 11,46 - 1778: 11,45 - 1779: 11,42 - 1780: 13,42 - 1781: 9,42 - 1782: 14,45 - 1783: 10,44 - 1784: 11,44 - 1785: 11,45 - 1786: 16,47 - 1787: 16,45 - 1788: 17,45 - 1789: 21,45 - 1790: 16,48 - 1791: 16,43 - 1792: 23,43 - 1793: 22,42 - 1794: 30,38 - 1795: 30,40 - 1796: 24,38 - 1797: 22,39 - 1798: 19,38 - 1799: 19,39 - 1800: 18,39 - 1801: 16,39 - 1802: 18,38 - 1803: 17,38 - 1804: 14,38 - 1805: 14,40 - 1806: 9,39 - 1807: 10,40 - 1808: 12,38 - 1809: 10,38 - 1810: 10,38 - 1811: 6,39 - 1812: 0,39 - 2103: 24,45 - 2104: 30,45 - 2105: 26,44 - 2106: 27,43 - 2107: 30,43 - 2108: 29,42 - 2109: 28,47 - - node: - cleanable: True - color: '#A4610696' - id: DirtMedium - decals: - 1813: 0,35 - 1814: 0,39 - 1815: 6,39 - 1816: 3,43 - 1817: 1,45 - 1818: 11,45 - 1819: 11,42 - 1820: 14,45 - 1821: 16,45 - 1822: 21,45 - 1823: 22,45 - 1824: 21,38 - 1825: 20,38 - 1826: 20,38 - 1827: 24,38 - 1828: 30,39 - 1829: 34,35 - 1830: 33,36 - 1831: 33,34 - 1832: 24,34 - 1833: 20,34 - 1834: 22,35 - 1835: 17,35 - 1836: 16,34 - 1837: 17,36 - 1838: 12,35 - 1839: 10,35 - 1840: 5,35 - 1841: 0,35 - 1842: 1,31 - 1843: 2,31 - 1844: 11,31 - 1845: 9,26 - 1846: 8,26 - 1847: 9,27 - 1848: 9,28 - 1849: 10,26 - 1850: 10,25 - 1851: 9,25 - 1852: 13,26 - 1853: 12,26 - 1854: 13,28 - 1855: 14,26 - 1856: 17,24 - 1857: 18,26 - 1858: 17,28 - 1859: 16,26 - 1860: 22,26 - 1861: 20,26 - 1862: 25,27 - 1863: 24,27 - 1864: 24,28 - 1865: 24,25 - 1866: 25,25 - 1867: 25,24 - 1868: 24,24 - 1869: 26,25 - 1870: 26,26 - 1871: 25,26 - 1872: 24,26 - 1873: 24,26 - 1874: 25,26 - 1875: 25,26 - 1876: 28,26 - 1877: 30,26 - 1878: 29,27 - 1879: 29,25 - 1880: 33,26 - 1881: 34,26 - 1882: 34,28 - 1883: 32,28 - 1884: 34,28 - 1885: 32,20 - 1886: 31,20 - 1887: 30,20 - 1888: 30,18 - 1889: 34,18 - 1890: 34,21 - 1891: 25,18 - 1892: 26,18 - 1893: 25,18 - 1894: 27,20 - 1895: 27,22 - 1896: 29,15 - 1897: 30,14 - 1898: 29,12 - 1899: 24,14 - 1900: 24,15 - 1901: 24,16 - 1902: 20,20 - 1903: 17,20 - 1904: 19,21 - 1905: 18,20 - 1906: 21,22 - 1907: 22,20 - 1908: 22,21 - 1909: 20,19 - 1910: 15,20 - 1911: 14,21 - 1912: 15,21 - 1913: 16,21 - 1914: 14,22 - 1915: 12,20 - 1916: 14,18 - 1917: 15,18 - 1918: 16,19 - 1919: 16,19 - 1920: 13,19 - 1921: 12,19 - 1922: 12,19 - 1923: 12,18 - 1924: 14,18 - 1925: 15,18 - 1926: 15,18 - 1927: 10,20 - 1928: 8,22 - 1929: 7,20 - 1930: 9,21 - 1931: 7,19 - 1932: 10,19 - 1933: 10,21 - 1934: 7,18 - 1935: 4,20 - 1936: 4,21 - 1937: 2,22 - 1938: 1,22 - 1939: 0,19 - 1940: 1,18 - 1941: 1,18 - 1942: 3,18 - 1943: 0,14 - 1944: 2,13 - 1945: 3,13 - 1946: 5,13 - 1947: 3,12 - 1948: 8,15 - 1949: 8,16 - 1950: 9,16 - 1951: 9,16 - 1952: 18,14 - 1953: 19,16 - 1954: 19,14 - 1955: 20,13 - 1956: 18,16 - 1957: 17,13 - 1958: 20,12 - 1959: 22,13 - 1960: 24,13 - 1961: 30,12 - 1962: 30,14 - 1963: 29,16 - 1964: 24,16 - 1965: 24,16 - 1966: 25,6 - 1967: 24,7 - 1968: 26,9 - 1969: 27,10 - 1970: 28,10 - 1971: 34,10 - 1972: 34,8 - 1973: 34,7 - 1974: 33,6 - 1975: 30,6 - 1976: 27,6 - 1977: 21,8 - 1978: 16,8 - 1979: 17,10 - 1980: 18,8 - 1981: 17,6 - 1982: 13,7 - 1983: 13,9 - 1984: 13,8 - 1985: 14,7 - 1986: 10,6 - 1987: 8,10 - 1988: 2,6 - 1989: 0,7 - 1990: 1,1 - 1991: 0,1 - 1992: 4,1 - 1993: 5,2 - 1994: 3,1 - 1995: 3,0 - 1996: 7,2 - 1997: 9,2 - 1998: 11,2 - 1999: 13,1 - 2000: 15,1 - 2001: 16,2 - 2002: 16,1 - 2003: 23,2 - 2004: 26,2 - 2005: 29,3 - 2006: 30,2 - 2007: 33,2 - 2008: 33,3 - 2009: 34,2 - 2010: 34,0 - 2011: 34,1 - 2012: 31,1 - 2013: 29,0 - 2014: 26,0 - 2015: 23,3 - 2016: 28,4 - 2087: 27,42 - 2088: 27,48 - 2089: 24,45 - 2090: 30,45 - 2091: 28,45 - 2092: 26,45 - 2093: 28,47 - 2094: 27,44 - 2110: 24,45 - 2111: 27,48 - 2112: 27,45 - 2113: 26,46 - 2114: 26,42 - 2115: 25,42 - 2116: 25,47 - 2117: 25,47 - 2118: 26,48 - 2119: 26,47 - 2120: 26,47 - 2121: 26,43 - 2122: 26,43 - 2123: 27,43 - 2124: 6,1 - 2125: 10,3 - - node: - color: '#D4D4D41B' - id: FullTileOverlayGreyscale - decals: - 1313: 31,4 - 1314: 31,3 - 1315: 31,2 - 1316: 31,1 - 1317: 31,0 - - node: - color: '#D4D4D433' - id: FullTileOverlayGreyscale - decals: - 1318: 32,0 - 1319: 32,1 - 1320: 32,2 - 1321: 32,3 - 1322: 32,4 - - node: - color: '#D4D4D44C' - id: FullTileOverlayGreyscale - decals: - 1323: 33,0 - 1324: 33,1 - 1325: 33,2 - 1326: 33,3 - 1327: 33,4 - - node: - color: '#D4D4D40C' - id: HalfTileOverlayGreyscale - decals: - 923: 3,47 - - node: - color: '#D4D4D419' - id: HalfTileOverlayGreyscale - decals: - 893: 3,43 - - node: - color: '#D4D4D40C' - id: HalfTileOverlayGreyscale180 - decals: - 924: 3,43 - - node: - color: '#D4D4D419' - id: HalfTileOverlayGreyscale180 - decals: - 894: 3,47 - - node: - color: '#D4D4D40C' - id: HalfTileOverlayGreyscale270 - decals: - 910: 30,2 - 911: 9,2 - 914: 2,6 - 915: 2,10 - 918: 2,31 - 922: 1,45 - - node: - color: '#D4D4D419' - id: HalfTileOverlayGreyscale270 - decals: - 882: 7,2 - 883: 8,6 - 884: 8,10 - 888: 10,31 - 889: 33,34 - 890: 33,36 - 891: 5,45 - - node: - color: '#D4D4D40C' - id: HalfTileOverlayGreyscale90 - decals: - 912: 7,2 - 913: 8,6 - 916: 8,10 - 917: 10,31 - 919: 33,34 - 920: 33,36 - 921: 5,45 - - node: - color: '#D4D4D419' - id: HalfTileOverlayGreyscale90 - decals: - 880: 30,2 - 881: 9,2 - 885: 2,6 - 886: 2,10 - 887: 2,31 - 892: 1,45 - - node: - color: '#9FED5896' - id: MiniTileCheckerAOverlay - decals: - 2028: 28,42 - 2029: 29,42 - 2030: 30,42 - 2031: 28,43 - 2032: 29,43 - 2033: 30,43 - 2034: 28,44 - 2035: 29,44 - 2036: 30,44 - - node: - color: '#DE3A3A96' - id: MiniTileCheckerAOverlay - decals: - 0: 7,19 - 1: 8,19 - 2: 9,19 - 3: 9,20 - 4: 8,20 - 5: 7,20 - 6: 7,21 - 7: 8,21 - 8: 9,21 - 9: 19,19 - 10: 20,19 - 11: 21,19 - 12: 21,20 - 13: 20,20 - 14: 19,20 - 15: 19,21 - 16: 20,21 - 17: 21,21 - 18: 17,15 - 19: 17,14 - 20: 17,13 - 21: 18,13 - 22: 19,13 - 23: 20,13 - 24: 21,13 - 25: 21,14 - 26: 21,15 - 27: 20,15 - 28: 19,15 - 29: 18,15 - 30: 18,14 - 31: 19,14 - 32: 20,9 - 33: 21,9 - 34: 22,9 - 35: 22,8 - 36: 22,7 - 37: 21,7 - 38: 21,8 - 39: 20,8 - 40: 20,7 - 41: 12,7 - 42: 13,7 - 43: 14,7 - 44: 14,8 - 45: 13,8 - 46: 12,8 - 47: 12,9 - 48: 13,9 - 49: 14,9 - 59: 17,39 - 60: 18,39 - 61: 19,39 - 62: 20,39 - 63: 21,39 - 64: 16,36 - 65: 16,35 - 66: 16,34 - 67: 17,34 - 68: 18,34 - 69: 18,35 - 70: 17,35 - 71: 17,36 - 72: 18,36 - - node: - color: '#FFFFFFFF' - id: MiniTileCheckerAOverlay - decals: - 2052: 28,46 - 2053: 28,47 - 2054: 29,47 - 2055: 29,46 - - node: - color: '#9FED5896' - id: MiniTileCheckerBOverlay - decals: - 50: 31,21 - 51: 31,20 - 52: 31,19 - 53: 33,21 - 54: 33,20 - 55: 33,19 - - node: - color: '#DE3A3A96' - id: MiniTileCheckerBOverlay - decals: - 56: 32,21 - 57: 32,20 - 58: 32,19 - - node: - color: '#DE3A3A96' - id: StandClearGreyscale - decals: - 751: 26,7 - 752: 32,7 - 753: 29,9 - 838: 6,2 - 839: 10,2 - - node: - color: '#FFFFFFFF' - id: WarnCornerNE - decals: - 605: 13,40 - 747: 34,10 - - node: - color: '#FFFFFFFF' - id: WarnCornerNW - decals: - 606: 9,40 - 741: 24,10 - - node: - color: '#FFFFFFFF' - id: WarnCornerSE - decals: - 604: 13,38 - 746: 34,6 - - node: - color: '#FFFFFFFF' - id: WarnCornerSW - decals: - 598: 9,38 - 740: 24,6 - - node: - color: '#FFFFFFFF' - id: WarnFull - decals: - 657: 32,35 - - node: - color: '#FFFFFFFF' - id: WarnLineE - decals: - 607: 13,39 - 644: 26,34 - 645: 26,35 - 646: 26,36 - 744: 34,7 - 745: 34,9 - - node: - color: '#DE3A3A96' - id: WarnLineGreyscaleE - decals: - 112: 18,26 - 117: 14,26 - 118: 22,26 - 121: 22,35 - 122: 22,39 - 128: 14,39 - 131: 6,39 - 132: 12,31 - 135: 26,31 - 140: 26,26 - 145: 30,26 - 146: 34,26 - 153: 2,26 - 159: 4,20 - 160: 10,20 - 166: 16,20 - 170: 22,20 - 175: 34,20 - 176: 22,14 - 183: 6,14 - 184: 10,8 - 188: 16,2 - 190: 34,2 - 205: 21,2 - 213: 34,8 - 214: 30,14 - 219: 34,35 - 223: 30,39 - 224: 22,45 - 227: 14,45 - 230: 6,45 - 235: 10,35 - 834: 5,2 - 2018: 30,45 - - node: - color: '#DE3A3A96' - id: WarnLineGreyscaleN - decals: - 111: 17,28 - 116: 13,28 - 125: 19,40 - 126: 11,40 - 138: 21,28 - 144: 29,28 - 154: 1,28 - 158: 2,22 - 161: 8,22 - 165: 14,22 - 171: 20,22 - 173: 32,22 - 179: 19,16 - 180: 3,16 - 186: 17,10 - 209: 26,4 - 210: 29,10 - 217: 26,22 - 220: 29,36 - 228: 11,48 - 233: 3,48 - 237: 5,28 - 2020: 27,48 - - node: - color: '#FFFFFFFF' - id: WarnLineGreyscaleN - decals: - 667: 27,35 - 668: 28,35 - 669: 29,35 - 670: 30,35 - 671: 31,35 - - node: - color: '#DE3A3A96' - id: WarnLineGreyscaleS - decals: - 110: 17,24 - 115: 13,24 - 124: 19,38 - 127: 11,38 - 136: 20,30 - 137: 21,24 - 139: 25,24 - 143: 29,24 - 148: 33,24 - 149: 32,28 - 150: 33,28 - 151: 34,28 - 152: 1,24 - 157: 2,18 - 163: 8,18 - 164: 14,18 - 169: 20,18 - 172: 32,18 - 177: 19,12 - 181: 3,12 - 187: 17,6 - 208: 26,0 - 211: 29,6 - 216: 26,18 - 221: 29,34 - 229: 11,42 - 231: 3,42 - 236: 5,24 - 2019: 27,42 - - node: - color: '#FFFFFFFF' - id: WarnLineGreyscaleS - decals: - 662: 27,35 - 663: 28,35 - 664: 29,35 - 665: 30,35 - 666: 31,35 - - node: - color: '#DE3A3A96' - id: WarnLineGreyscaleW - decals: - 113: 16,26 - 114: 12,26 - 119: 20,26 - 120: 12,35 - 123: 16,39 - 129: 8,39 - 130: 0,39 - 133: 0,31 - 134: 14,31 - 141: 24,26 - 142: 28,26 - 147: 32,26 - 155: 0,26 - 156: 0,20 - 162: 6,20 - 167: 12,20 - 168: 18,20 - 174: 30,20 - 178: 16,14 - 182: 0,14 - 185: 0,8 - 189: 0,2 - 206: 18,2 - 207: 23,2 - 212: 24,8 - 215: 24,14 - 218: 24,35 - 222: 24,39 - 225: 16,45 - 226: 8,45 - 232: 0,45 - 234: 0,35 - 835: 11,2 - 2017: 24,45 - - node: - color: '#FFFFFFFF' - id: WarnLineN - decals: - 601: 10,38 - 602: 12,38 - 647: 27,34 - 648: 28,34 - 649: 30,34 - 650: 31,34 - 651: 32,34 - 732: 28,6 - 733: 27,6 - 734: 26,6 - 735: 25,6 - 736: 30,6 - 737: 31,6 - 738: 32,6 - 739: 33,6 - - node: - color: '#FFFFFFFF' - id: WarnLineS - decals: - 603: 9,39 - 742: 24,7 - 743: 24,9 - - node: - color: '#FFFFFFFF' - id: WarnLineW - decals: - 599: 10,40 - 600: 12,40 - 652: 27,36 - 653: 28,36 - 654: 30,36 - 655: 31,36 - 656: 32,36 - 724: 25,10 - 725: 26,10 - 726: 28,10 - 727: 27,10 - 728: 30,10 - 729: 31,10 - 730: 32,10 - 731: 33,10 - - node: - cleanable: True - color: '#80C71F7F' - id: revolution - decals: - 2138: 14.060958,20.754644 - 2139: 13.607299,19.803425 - - node: - cleanable: True - color: '#B02E60A3' - id: revolution - decals: - 2137: 25.02975,25.438416 - - node: - cleanable: True - angle: -1.5707963267948966 rad - color: '#B02E2644' - id: splatter - decals: - 2131: 27.967218,24.104916 - - node: - cleanable: True - color: '#B02E2666' - id: splatter - decals: - 2126: 8.891183,43.065514 - - node: - cleanable: True - angle: -1.5707963267948966 rad - color: '#B02E266F' - id: splatter - decals: - 2132: 28.36234,24.163452 - 2133: 32.200607,35.087025 - 2134: 13.24002,46.473877 - 2135: 24.497486,47.84553 - - node: - cleanable: True - angle: -4.71238898038469 rad - color: '#B02E26B4' - id: splatter - decals: - 2128: 8.788744,42.524048 - 2129: 15.538555,20.953827 - 2130: 24.864944,27.488853 - - node: - cleanable: True - angle: -3.141592653589793 rad - color: '#B02E26B4' - id: splatter - decals: - 2127: 9.110695,42.81673 - - type: RadiationGridResistance - - type: LoadedMap - - type: SpreaderGrid - - type: GridTree - - type: MovedGrids - - type: GridPathfinding -- proto: AirlockBrigGlassLocked - entities: - - uid: 1245 - components: - - type: Transform - pos: 15.5,35.5 - parent: 588 - - uid: 1246 - components: - - type: Transform - pos: 19.5,35.5 - parent: 588 - - uid: 1625 - components: - - type: Transform - pos: 22.5,2.5 - parent: 588 -- proto: AirlockEngineering - entities: - - uid: 1515 - components: - - type: Transform - pos: 19.5,43.5 - parent: 588 -- proto: AirlockSecurityGlassLocked - entities: - - uid: 1579 - components: - - type: Transform - pos: 11.5,15.5 - parent: 588 - - uid: 1609 - components: - - type: Transform - pos: 15.5,8.5 - parent: 588 - - uid: 1610 - components: - - type: Transform - pos: 19.5,8.5 - parent: 588 - - uid: 1623 - components: - - type: Transform - pos: 6.5,2.5 - parent: 588 - - uid: 1624 - components: - - type: Transform - pos: 10.5,2.5 - parent: 588 -- proto: APCBasic - entities: - - uid: 484 - components: - - type: Transform - pos: 25.5,13.5 - parent: 588 - - uid: 773 - components: - - type: Transform - pos: 25.5,19.5 - parent: 588 - - uid: 973 - components: - - type: Transform - pos: 21.5,32.5 - parent: 588 - - uid: 1509 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 19.5,47.5 - parent: 588 -- proto: BannerSecurity - entities: - - uid: 553 - components: - - type: Transform - pos: 30.5,8.5 - parent: 588 - - uid: 1619 - components: - - type: Transform - pos: 18.5,10.5 - parent: 588 - - uid: 1620 - components: - - type: Transform - pos: 16.5,6.5 - parent: 588 -- proto: BasaltFive - entities: - - uid: 608 - components: - - type: Transform - pos: 2.5,14.5 - parent: 588 - - uid: 647 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 2.5,21.5 - parent: 588 - - uid: 899 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 10.5,30.5 - parent: 588 - - uid: 1264 - components: - - type: Transform - pos: 9.5,0.5 - parent: 588 - - uid: 1384 - components: - - type: Transform - pos: 5.5,48.5 - parent: 588 - - uid: 1386 - components: - - type: Transform - pos: 0.5,47.5 - parent: 588 - - uid: 1387 - components: - - type: Transform - pos: 0.5,42.5 - parent: 588 - - uid: 1388 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 1.5,42.5 - parent: 588 -- proto: BasaltFour - entities: - - uid: 900 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 8.5,32.5 - parent: 588 - - uid: 904 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 2.5,40.5 - parent: 588 - - uid: 1381 - components: - - type: Transform - pos: 1.5,44.5 - parent: 588 - - uid: 1385 - components: - - type: Transform - pos: 6.5,48.5 - parent: 588 -- proto: BasaltOne - entities: - - uid: 813 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 1.5,27.5 - parent: 588 - - uid: 897 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 3.5,30.5 - parent: 588 - - uid: 901 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 10.5,32.5 - parent: 588 - - uid: 903 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 5.5,38.5 - parent: 588 - - uid: 1382 - components: - - type: Transform - pos: 1.5,48.5 - parent: 588 -- proto: BasaltRandom - entities: - - uid: 613 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 4.5,14.5 - parent: 588 - - uid: 615 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 3.5,7.5 - parent: 588 -- proto: BasaltThree - entities: - - uid: 616 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 6.5,7.5 - parent: 588 - - uid: 644 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 1.5,20.5 - parent: 588 - - uid: 898 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 2.5,30.5 - parent: 588 - - uid: 905 - components: - - type: Transform - pos: 4.5,38.5 - parent: 588 - - uid: 1265 - components: - - type: Transform - pos: 7.5,1.5 - parent: 588 - - uid: 1266 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 9.5,3.5 - parent: 588 - - uid: 1383 - components: - - type: Transform - pos: 6.5,47.5 - parent: 588 -- proto: BasaltTwo - entities: - - uid: 610 - components: - - type: Transform - pos: 3.5,14.5 - parent: 588 - - uid: 617 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 3.5,9.5 - parent: 588 - - uid: 618 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 7.5,8.5 - parent: 588 - - uid: 646 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 2.5,19.5 - parent: 588 - - uid: 814 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 1.5,26.5 - parent: 588 - - uid: 896 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 3.5,32.5 - parent: 588 - - uid: 902 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 1.5,38.5 - parent: 588 - - uid: 1263 - components: - - type: Transform - pos: 7.5,4.5 - parent: 588 - - uid: 1380 - components: - - type: Transform - pos: 1.5,43.5 - parent: 588 -- proto: Bed - entities: - - uid: 257 - components: - - type: Transform - pos: 15.5,4.5 - parent: 588 - - uid: 282 - components: - - type: Transform - pos: 1.5,4.5 - parent: 588 - - uid: 293 - components: - - type: Transform - pos: 3.5,4.5 - parent: 588 - - uid: 294 - components: - - type: Transform - pos: 5.5,4.5 - parent: 588 - - uid: 295 - components: - - type: Transform - pos: 11.5,4.5 - parent: 588 - - uid: 296 - components: - - type: Transform - pos: 13.5,4.5 - parent: 588 - - uid: 700 - components: - - type: Transform - pos: 12.5,22.5 - parent: 588 - - uid: 701 - components: - - type: Transform - pos: 16.5,18.5 - parent: 588 - - uid: 1043 - components: - - type: Transform - pos: 20.5,28.5 - parent: 588 - - uid: 1044 - components: - - type: Transform - pos: 22.5,24.5 - parent: 588 - - uid: 1075 - components: - - type: Transform - pos: 24.5,28.5 - parent: 588 - - uid: 1099 - components: - - type: Transform - pos: 20.5,36.5 - parent: 588 - - uid: 1100 - components: - - type: Transform - pos: 14.5,34.5 - parent: 588 - - uid: 1763 - components: - - type: Transform - pos: 24.5,48.5 - parent: 588 - - uid: 1764 - components: - - type: Transform - pos: 24.5,42.5 - parent: 588 -- proto: BedsheetMedical - entities: - - uid: 1150 - components: - - type: Transform - pos: 28.5,24.5 - parent: 588 - - uid: 1151 - components: - - type: Transform - pos: 28.5,25.5 - parent: 588 -- proto: BedsheetOrange - entities: - - uid: 298 - components: - - type: Transform - pos: 3.5,4.5 - parent: 588 - - uid: 299 - components: - - type: Transform - pos: 5.5,4.5 - parent: 588 - - uid: 300 - components: - - type: Transform - pos: 13.5,4.5 - parent: 588 - - uid: 1765 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 24.5,42.5 - parent: 588 -- proto: BedsheetSpawner - entities: - - uid: 301 - components: - - type: Transform - pos: 11.5,4.5 - parent: 588 - - uid: 1041 - components: - - type: Transform - pos: 20.5,28.5 - parent: 588 - - uid: 1225 - components: - - type: Transform - pos: 14.5,34.5 - parent: 588 - - uid: 1226 - components: - - type: Transform - pos: 20.5,36.5 - parent: 588 -- proto: BedsheetSyndie - entities: - - uid: 1037 - components: - - type: Transform - pos: 22.5,24.5 - parent: 588 - - uid: 1766 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 24.5,48.5 - parent: 588 -- proto: BlastDoor - entities: - - uid: 1600 - components: - - type: Transform - pos: 26.5,7.5 - parent: 588 - - uid: 1601 - components: - - type: Transform - pos: 29.5,9.5 - parent: 588 - - uid: 1602 - components: - - type: Transform - pos: 32.5,7.5 - parent: 588 -- proto: Bola - entities: - - uid: 1157 - components: - - type: Transform - pos: 15.616387,0.61007345 - parent: 588 -- proto: BookEscalationSecurity - entities: - - uid: 373 - components: - - type: Transform - pos: 19.42657,0.6288943 - parent: 588 -- proto: BookRandom - entities: - - uid: 1835 - components: - - type: Transform - pos: 24.420084,44.539436 - parent: 588 -- proto: BookSecurity - entities: - - uid: 522 - components: - - type: Transform - pos: 32.41844,8.400207 - parent: 588 -- proto: BoxFolderBlack - entities: - - uid: 365 - components: - - type: Transform - pos: 27.841173,1.5632035 - parent: 588 - - uid: 728 - components: - - type: Transform - pos: 10.690479,19.262342 - parent: 588 -- proto: BoxFolderGrey - entities: - - uid: 364 - components: - - type: Transform - pos: 25.072409,1.4780562 - parent: 588 - - uid: 727 - components: - - type: Transform - pos: 7.2148314,22.575037 - parent: 588 -- proto: BoxFolderRed - entities: - - uid: 329 - components: - - type: Transform - pos: 21.42984,3.6329575 - parent: 588 - - uid: 362 - components: - - type: Transform - pos: 24.788435,1.6057768 - parent: 588 - - uid: 363 - components: - - type: Transform - pos: 28.196142,1.5773941 - parent: 588 - - uid: 726 - components: - - type: Transform - pos: 10.428753,19.429379 - parent: 588 -- proto: BoxMouthSwab - entities: - - uid: 1476 - components: - - type: Transform - pos: 12.356534,44.605965 - parent: 588 -- proto: BoxSterileMask - entities: - - uid: 1477 - components: - - type: Transform - pos: 12.683106,44.705303 - parent: 588 -- proto: BriefcaseBrownFilled - entities: - - uid: 325 - components: - - type: Transform - pos: 19.413612,4.6972914 - parent: 588 -- proto: BrokenBottle - entities: - - uid: 1691 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 25.063513,27.520548 - parent: 588 -- proto: Bucket - entities: - - uid: 1839 - components: - - type: Transform - pos: 29.616838,43.531868 - parent: 588 - - uid: 1857 - components: - - type: Transform - pos: 30.264944,21.705044 - parent: 588 -- proto: CableApcExtension - entities: - - uid: 1 - components: - - type: Transform - pos: 0.5,2.5 - parent: 588 - - uid: 2 - components: - - type: Transform - pos: 1.5,2.5 - parent: 588 - - uid: 3 - components: - - type: Transform - pos: 2.5,2.5 - parent: 588 - - uid: 4 - components: - - type: Transform - pos: 3.5,2.5 - parent: 588 - - uid: 5 - components: - - type: Transform - pos: 4.5,2.5 - parent: 588 - - uid: 6 - components: - - type: Transform - pos: 5.5,2.5 - parent: 588 - - uid: 7 - components: - - type: Transform - pos: 6.5,2.5 - parent: 588 - - uid: 8 - components: - - type: Transform - pos: 7.5,2.5 - parent: 588 - - uid: 9 - components: - - type: Transform - pos: 8.5,2.5 - parent: 588 - - uid: 10 - components: - - type: Transform - pos: 9.5,2.5 - parent: 588 - - uid: 11 - components: - - type: Transform - pos: 10.5,2.5 - parent: 588 - - uid: 12 - components: - - type: Transform - pos: 11.5,2.5 - parent: 588 - - uid: 13 - components: - - type: Transform - pos: 12.5,2.5 - parent: 588 - - uid: 14 - components: - - type: Transform - pos: 13.5,2.5 - parent: 588 - - uid: 15 - components: - - type: Transform - pos: 14.5,2.5 - parent: 588 - - uid: 16 - components: - - type: Transform - pos: 15.5,2.5 - parent: 588 - - uid: 17 - components: - - type: Transform - pos: 16.5,2.5 - parent: 588 - - uid: 18 - components: - - type: Transform - pos: 8.5,4.5 - parent: 588 - - uid: 19 - components: - - type: Transform - pos: 8.5,3.5 - parent: 588 - - uid: 20 - components: - - type: Transform - pos: 8.5,1.5 - parent: 588 - - uid: 21 - components: - - type: Transform - pos: 8.5,0.5 - parent: 588 - - uid: 22 - components: - - type: Transform - pos: 18.5,2.5 - parent: 588 - - uid: 23 - components: - - type: Transform - pos: 19.5,2.5 - parent: 588 - - uid: 24 - components: - - type: Transform - pos: 20.5,2.5 - parent: 588 - - uid: 25 - components: - - type: Transform - pos: 21.5,2.5 - parent: 588 - - uid: 26 - components: - - type: Transform - pos: 22.5,2.5 - parent: 588 - - uid: 27 - components: - - type: Transform - pos: 23.5,2.5 - parent: 588 - - uid: 28 - components: - - type: Transform - pos: 24.5,2.5 - parent: 588 - - uid: 29 - components: - - type: Transform - pos: 25.5,2.5 - parent: 588 - - uid: 30 - components: - - type: Transform - pos: 26.5,2.5 - parent: 588 - - uid: 31 - components: - - type: Transform - pos: 27.5,2.5 - parent: 588 - - uid: 32 - components: - - type: Transform - pos: 28.5,2.5 - parent: 588 - - uid: 33 - components: - - type: Transform - pos: 29.5,2.5 - parent: 588 - - uid: 34 - components: - - type: Transform - pos: 30.5,2.5 - parent: 588 - - uid: 35 - components: - - type: Transform - pos: 31.5,2.5 - parent: 588 - - uid: 36 - components: - - type: Transform - pos: 32.5,2.5 - parent: 588 - - uid: 37 - components: - - type: Transform - pos: 33.5,2.5 - parent: 588 - - uid: 38 - components: - - type: Transform - pos: 34.5,2.5 - parent: 588 - - uid: 39 - components: - - type: Transform - pos: 26.5,4.5 - parent: 588 - - uid: 40 - components: - - type: Transform - pos: 26.5,3.5 - parent: 588 - - uid: 41 - components: - - type: Transform - pos: 26.5,1.5 - parent: 588 - - uid: 42 - components: - - type: Transform - pos: 26.5,0.5 - parent: 588 - - uid: 43 - components: - - type: Transform - pos: 0.5,8.5 - parent: 588 - - uid: 44 - components: - - type: Transform - pos: 1.5,8.5 - parent: 588 - - uid: 52 - components: - - type: Transform - pos: 9.5,8.5 - parent: 588 - - uid: 53 - components: - - type: Transform - pos: 10.5,8.5 - parent: 588 - - uid: 54 - components: - - type: Transform - pos: 5.5,10.5 - parent: 588 - - uid: 57 - components: - - type: Transform - pos: 5.5,6.5 - parent: 588 - - uid: 58 - components: - - type: Transform - pos: 17.5,6.5 - parent: 588 - - uid: 59 - components: - - type: Transform - pos: 17.5,7.5 - parent: 588 - - uid: 60 - components: - - type: Transform - pos: 17.5,8.5 - parent: 588 - - uid: 61 - components: - - type: Transform - pos: 17.5,9.5 - parent: 588 - - uid: 62 - components: - - type: Transform - pos: 17.5,10.5 - parent: 588 - - uid: 63 - components: - - type: Transform - pos: 12.5,8.5 - parent: 588 - - uid: 64 - components: - - type: Transform - pos: 13.5,8.5 - parent: 588 - - uid: 65 - components: - - type: Transform - pos: 14.5,8.5 - parent: 588 - - uid: 66 - components: - - type: Transform - pos: 15.5,8.5 - parent: 588 - - uid: 67 - components: - - type: Transform - pos: 16.5,8.5 - parent: 588 - - uid: 68 - components: - - type: Transform - pos: 18.5,8.5 - parent: 588 - - uid: 69 - components: - - type: Transform - pos: 19.5,8.5 - parent: 588 - - uid: 70 - components: - - type: Transform - pos: 20.5,8.5 - parent: 588 - - uid: 71 - components: - - type: Transform - pos: 21.5,8.5 - parent: 588 - - uid: 72 - components: - - type: Transform - pos: 22.5,8.5 - parent: 588 - - uid: 73 - components: - - type: Transform - pos: 22.5,14.5 - parent: 588 - - uid: 74 - components: - - type: Transform - pos: 21.5,14.5 - parent: 588 - - uid: 75 - components: - - type: Transform - pos: 20.5,14.5 - parent: 588 - - uid: 76 - components: - - type: Transform - pos: 19.5,14.5 - parent: 588 - - uid: 77 - components: - - type: Transform - pos: 18.5,14.5 - parent: 588 - - uid: 78 - components: - - type: Transform - pos: 17.5,14.5 - parent: 588 - - uid: 79 - components: - - type: Transform - pos: 16.5,14.5 - parent: 588 - - uid: 80 - components: - - type: Transform - pos: 19.5,13.5 - parent: 588 - - uid: 81 - components: - - type: Transform - pos: 19.5,12.5 - parent: 588 - - uid: 82 - components: - - type: Transform - pos: 19.5,15.5 - parent: 588 - - uid: 83 - components: - - type: Transform - pos: 19.5,16.5 - parent: 588 - - uid: 84 - components: - - type: Transform - pos: 14.5,14.5 - parent: 588 - - uid: 85 - components: - - type: Transform - pos: 13.5,14.5 - parent: 588 - - uid: 86 - components: - - type: Transform - pos: 12.5,14.5 - parent: 588 - - uid: 87 - components: - - type: Transform - pos: 11.5,14.5 - parent: 588 - - uid: 88 - components: - - type: Transform - pos: 10.5,14.5 - parent: 588 - - uid: 89 - components: - - type: Transform - pos: 9.5,14.5 - parent: 588 - - uid: 90 - components: - - type: Transform - pos: 8.5,14.5 - parent: 588 - - uid: 91 - components: - - type: Transform - pos: 11.5,13.5 - parent: 588 - - uid: 92 - components: - - type: Transform - pos: 11.5,12.5 - parent: 588 - - uid: 93 - components: - - type: Transform - pos: 11.5,15.5 - parent: 588 - - uid: 94 - components: - - type: Transform - pos: 11.5,16.5 - parent: 588 - - uid: 95 - components: - - type: Transform - pos: 6.5,14.5 - parent: 588 - - uid: 96 - components: - - type: Transform - pos: 5.5,14.5 - parent: 588 - - uid: 98 - components: - - type: Transform - pos: 4.5,22.5 - parent: 588 - - uid: 100 - components: - - type: Transform - pos: 1.5,14.5 - parent: 588 - - uid: 101 - components: - - type: Transform - pos: 0.5,14.5 - parent: 588 - - uid: 102 - components: - - type: Transform - pos: 3.5,15.5 - parent: 588 - - uid: 103 - components: - - type: Transform - pos: 3.5,16.5 - parent: 588 - - uid: 104 - components: - - type: Transform - pos: 3.5,13.5 - parent: 588 - - uid: 105 - components: - - type: Transform - pos: 3.5,12.5 - parent: 588 - - uid: 106 - components: - - type: Transform - pos: 14.5,18.5 - parent: 588 - - uid: 107 - components: - - type: Transform - pos: 14.5,19.5 - parent: 588 - - uid: 108 - components: - - type: Transform - pos: 14.5,20.5 - parent: 588 - - uid: 109 - components: - - type: Transform - pos: 14.5,21.5 - parent: 588 - - uid: 110 - components: - - type: Transform - pos: 14.5,22.5 - parent: 588 - - uid: 111 - components: - - type: Transform - pos: 15.5,20.5 - parent: 588 - - uid: 112 - components: - - type: Transform - pos: 16.5,20.5 - parent: 588 - - uid: 113 - components: - - type: Transform - pos: 13.5,20.5 - parent: 588 - - uid: 114 - components: - - type: Transform - pos: 12.5,20.5 - parent: 588 - - uid: 115 - components: - - type: Transform - pos: 8.5,18.5 - parent: 588 - - uid: 116 - components: - - type: Transform - pos: 8.5,19.5 - parent: 588 - - uid: 117 - components: - - type: Transform - pos: 8.5,20.5 - parent: 588 - - uid: 118 - components: - - type: Transform - pos: 8.5,21.5 - parent: 588 - - uid: 119 - components: - - type: Transform - pos: 8.5,22.5 - parent: 588 - - uid: 120 - components: - - type: Transform - pos: 9.5,20.5 - parent: 588 - - uid: 121 - components: - - type: Transform - pos: 10.5,20.5 - parent: 588 - - uid: 122 - components: - - type: Transform - pos: 7.5,20.5 - parent: 588 - - uid: 123 - components: - - type: Transform - pos: 6.5,20.5 - parent: 588 - - uid: 124 - components: - - type: Transform - pos: 2.5,22.5 - parent: 588 - - uid: 125 - components: - - type: Transform - pos: 4.5,21.5 - parent: 588 - - uid: 126 - components: - - type: Transform - pos: 4.5,19.5 - parent: 588 - - uid: 127 - components: - - type: Transform - pos: 3.5,18.5 - parent: 588 - - uid: 128 - components: - - type: Transform - pos: 2.5,18.5 - parent: 588 - - uid: 129 - components: - - type: Transform - pos: 3.5,22.5 - parent: 588 - - uid: 130 - components: - - type: Transform - pos: 0.5,20.5 - parent: 588 - - uid: 131 - components: - - type: Transform - pos: 4.5,18.5 - parent: 588 - - uid: 132 - components: - - type: Transform - pos: 4.5,20.5 - parent: 588 - - uid: 133 - components: - - type: Transform - pos: 1.5,24.5 - parent: 588 - - uid: 134 - components: - - type: Transform - pos: 2.5,25.5 - parent: 588 - - uid: 135 - components: - - type: Transform - pos: 0.5,24.5 - parent: 588 - - uid: 136 - components: - - type: Transform - pos: 2.5,24.5 - parent: 588 - - uid: 137 - components: - - type: Transform - pos: 1.5,28.5 - parent: 588 - - uid: 138 - components: - - type: Transform - pos: 0.5,26.5 - parent: 588 - - uid: 139 - components: - - type: Transform - pos: 2.5,26.5 - parent: 588 - - uid: 147 - components: - - type: Transform - pos: 9.5,28.5 - parent: 588 - - uid: 148 - components: - - type: Transform - pos: 9.5,27.5 - parent: 588 - - uid: 149 - components: - - type: Transform - pos: 9.5,26.5 - parent: 588 - - uid: 150 - components: - - type: Transform - pos: 9.5,25.5 - parent: 588 - - uid: 151 - components: - - type: Transform - pos: 9.5,24.5 - parent: 588 - - uid: 152 - components: - - type: Transform - pos: 8.5,26.5 - parent: 588 - - uid: 153 - components: - - type: Transform - pos: 10.5,26.5 - parent: 588 - - uid: 154 - components: - - type: Transform - pos: 13.5,28.5 - parent: 588 - - uid: 155 - components: - - type: Transform - pos: 13.5,27.5 - parent: 588 - - uid: 156 - components: - - type: Transform - pos: 13.5,26.5 - parent: 588 - - uid: 157 - components: - - type: Transform - pos: 13.5,25.5 - parent: 588 - - uid: 158 - components: - - type: Transform - pos: 13.5,24.5 - parent: 588 - - uid: 159 - components: - - type: Transform - pos: 12.5,26.5 - parent: 588 - - uid: 160 - components: - - type: Transform - pos: 14.5,26.5 - parent: 588 - - uid: 161 - components: - - type: Transform - pos: 0.5,31.5 - parent: 588 - - uid: 162 - components: - - type: Transform - pos: 1.5,31.5 - parent: 588 - - uid: 163 - components: - - type: Transform - pos: 2.5,31.5 - parent: 588 - - uid: 164 - components: - - type: Transform - pos: 3.5,31.5 - parent: 588 - - uid: 165 - components: - - type: Transform - pos: 4.5,31.5 - parent: 588 - - uid: 166 - components: - - type: Transform - pos: 5.5,31.5 - parent: 588 - - uid: 167 - components: - - type: Transform - pos: 6.5,31.5 - parent: 588 - - uid: 168 - components: - - type: Transform - pos: 7.5,31.5 - parent: 588 - - uid: 169 - components: - - type: Transform - pos: 8.5,31.5 - parent: 588 - - uid: 170 - components: - - type: Transform - pos: 9.5,31.5 - parent: 588 - - uid: 171 - components: - - type: Transform - pos: 10.5,31.5 - parent: 588 - - uid: 172 - components: - - type: Transform - pos: 11.5,31.5 - parent: 588 - - uid: 173 - components: - - type: Transform - pos: 12.5,31.5 - parent: 588 - - uid: 174 - components: - - type: Transform - pos: 6.5,30.5 - parent: 588 - - uid: 175 - components: - - type: Transform - pos: 6.5,32.5 - parent: 588 - - uid: 176 - components: - - type: Transform - pos: 26.5,31.5 - parent: 588 - - uid: 177 - components: - - type: Transform - pos: 25.5,31.5 - parent: 588 - - uid: 178 - components: - - type: Transform - pos: 24.5,31.5 - parent: 588 - - uid: 179 - components: - - type: Transform - pos: 23.5,31.5 - parent: 588 - - uid: 180 - components: - - type: Transform - pos: 22.5,31.5 - parent: 588 - - uid: 181 - components: - - type: Transform - pos: 21.5,31.5 - parent: 588 - - uid: 182 - components: - - type: Transform - pos: 20.5,31.5 - parent: 588 - - uid: 183 - components: - - type: Transform - pos: 19.5,31.5 - parent: 588 - - uid: 184 - components: - - type: Transform - pos: 18.5,31.5 - parent: 588 - - uid: 185 - components: - - type: Transform - pos: 17.5,31.5 - parent: 588 - - uid: 186 - components: - - type: Transform - pos: 16.5,31.5 - parent: 588 - - uid: 187 - components: - - type: Transform - pos: 15.5,31.5 - parent: 588 - - uid: 188 - components: - - type: Transform - pos: 14.5,31.5 - parent: 588 - - uid: 189 - components: - - type: Transform - pos: 20.5,32.5 - parent: 588 - - uid: 190 - components: - - type: Transform - pos: 20.5,30.5 - parent: 588 - - uid: 191 - components: - - type: Transform - pos: 22.5,35.5 - parent: 588 - - uid: 192 - components: - - type: Transform - pos: 21.5,35.5 - parent: 588 - - uid: 193 - components: - - type: Transform - pos: 20.5,35.5 - parent: 588 - - uid: 194 - components: - - type: Transform - pos: 19.5,35.5 - parent: 588 - - uid: 195 - components: - - type: Transform - pos: 18.5,35.5 - parent: 588 - - uid: 196 - components: - - type: Transform - pos: 17.5,35.5 - parent: 588 - - uid: 197 - components: - - type: Transform - pos: 16.5,35.5 - parent: 588 - - uid: 198 - components: - - type: Transform - pos: 15.5,35.5 - parent: 588 - - uid: 199 - components: - - type: Transform - pos: 14.5,35.5 - parent: 588 - - uid: 200 - components: - - type: Transform - pos: 13.5,35.5 - parent: 588 - - uid: 201 - components: - - type: Transform - pos: 12.5,35.5 - parent: 588 - - uid: 202 - components: - - type: Transform - pos: 17.5,36.5 - parent: 588 - - uid: 203 - components: - - type: Transform - pos: 17.5,34.5 - parent: 588 - - uid: 204 - components: - - type: Transform - pos: 10.5,35.5 - parent: 588 - - uid: 215 - components: - - type: Transform - pos: 5.5,36.5 - parent: 588 - - uid: 216 - components: - - type: Transform - pos: 5.5,34.5 - parent: 588 - - uid: 217 - components: - - type: Transform - pos: 0.5,39.5 - parent: 588 - - uid: 218 - components: - - type: Transform - pos: 1.5,39.5 - parent: 588 - - uid: 219 - components: - - type: Transform - pos: 2.5,39.5 - parent: 588 - - uid: 220 - components: - - type: Transform - pos: 3.5,39.5 - parent: 588 - - uid: 221 - components: - - type: Transform - pos: 4.5,39.5 - parent: 588 - - uid: 222 - components: - - type: Transform - pos: 5.5,39.5 - parent: 588 - - uid: 223 - components: - - type: Transform - pos: 6.5,39.5 - parent: 588 - - uid: 224 - components: - - type: Transform - pos: 3.5,38.5 - parent: 588 - - uid: 225 - components: - - type: Transform - pos: 3.5,40.5 - parent: 588 - - uid: 226 - components: - - type: Transform - pos: 8.5,39.5 - parent: 588 - - uid: 227 - components: - - type: Transform - pos: 9.5,39.5 - parent: 588 - - uid: 228 - components: - - type: Transform - pos: 10.5,39.5 - parent: 588 - - uid: 229 - components: - - type: Transform - pos: 11.5,39.5 - parent: 588 - - uid: 230 - components: - - type: Transform - pos: 12.5,39.5 - parent: 588 - - uid: 231 - components: - - type: Transform - pos: 13.5,39.5 - parent: 588 - - uid: 232 - components: - - type: Transform - pos: 14.5,39.5 - parent: 588 - - uid: 233 - components: - - type: Transform - pos: 11.5,38.5 - parent: 588 - - uid: 234 - components: - - type: Transform - pos: 11.5,40.5 - parent: 588 - - uid: 235 - components: - - type: Transform - pos: 16.5,39.5 - parent: 588 - - uid: 236 - components: - - type: Transform - pos: 17.5,39.5 - parent: 588 - - uid: 237 - components: - - type: Transform - pos: 18.5,39.5 - parent: 588 - - uid: 238 - components: - - type: Transform - pos: 19.5,39.5 - parent: 588 - - uid: 239 - components: - - type: Transform - pos: 20.5,39.5 - parent: 588 - - uid: 240 - components: - - type: Transform - pos: 21.5,39.5 - parent: 588 - - uid: 241 - components: - - type: Transform - pos: 22.5,39.5 - parent: 588 - - uid: 242 - components: - - type: Transform - pos: 19.5,40.5 - parent: 588 - - uid: 243 - components: - - type: Transform - pos: 19.5,38.5 - parent: 588 - - uid: 284 - components: - - type: Transform - pos: 29.5,26.5 - parent: 588 - - uid: 288 - components: - - type: Transform - pos: 28.5,26.5 - parent: 588 - - uid: 425 - components: - - type: Transform - pos: 1.5,10.5 - parent: 588 - - uid: 438 - components: - - type: Transform - pos: 1.5,9.5 - parent: 588 - - uid: 441 - components: - - type: Transform - pos: 2.5,10.5 - parent: 588 - - uid: 442 - components: - - type: Transform - pos: 3.5,10.5 - parent: 588 - - uid: 443 - components: - - type: Transform - pos: 4.5,10.5 - parent: 588 - - uid: 444 - components: - - type: Transform - pos: 1.5,7.5 - parent: 588 - - uid: 445 - components: - - type: Transform - pos: 1.5,6.5 - parent: 588 - - uid: 446 - components: - - type: Transform - pos: 2.5,6.5 - parent: 588 - - uid: 447 - components: - - type: Transform - pos: 3.5,6.5 - parent: 588 - - uid: 448 - components: - - type: Transform - pos: 4.5,6.5 - parent: 588 - - uid: 449 - components: - - type: Transform - pos: 6.5,6.5 - parent: 588 - - uid: 450 - components: - - type: Transform - pos: 7.5,6.5 - parent: 588 - - uid: 451 - components: - - type: Transform - pos: 8.5,6.5 - parent: 588 - - uid: 452 - components: - - type: Transform - pos: 9.5,6.5 - parent: 588 - - uid: 453 - components: - - type: Transform - pos: 9.5,7.5 - parent: 588 - - uid: 454 - components: - - type: Transform - pos: 9.5,9.5 - parent: 588 - - uid: 455 - components: - - type: Transform - pos: 9.5,10.5 - parent: 588 - - uid: 456 - components: - - type: Transform - pos: 8.5,10.5 - parent: 588 - - uid: 457 - components: - - type: Transform - pos: 7.5,10.5 - parent: 588 - - uid: 472 - components: - - type: Transform - pos: 29.5,14.5 - parent: 588 - - uid: 477 - components: - - type: Transform - pos: 24.5,13.5 - parent: 588 - - uid: 479 - components: - - type: Transform - pos: 30.5,14.5 - parent: 588 - - uid: 493 - components: - - type: Transform - pos: 25.5,13.5 - parent: 588 - - uid: 494 - components: - - type: Transform - pos: 25.5,12.5 - parent: 588 - - uid: 495 - components: - - type: Transform - pos: 26.5,12.5 - parent: 588 - - uid: 496 - components: - - type: Transform - pos: 27.5,12.5 - parent: 588 - - uid: 497 - components: - - type: Transform - pos: 28.5,12.5 - parent: 588 - - uid: 498 - components: - - type: Transform - pos: 29.5,12.5 - parent: 588 - - uid: 500 - components: - - type: Transform - pos: 24.5,12.5 - parent: 588 - - uid: 502 - components: - - type: Transform - pos: 24.5,14.5 - parent: 588 - - uid: 503 - components: - - type: Transform - pos: 24.5,15.5 - parent: 588 - - uid: 504 - components: - - type: Transform - pos: 24.5,16.5 - parent: 588 - - uid: 505 - components: - - type: Transform - pos: 25.5,16.5 - parent: 588 - - uid: 506 - components: - - type: Transform - pos: 26.5,16.5 - parent: 588 - - uid: 507 - components: - - type: Transform - pos: 27.5,16.5 - parent: 588 - - uid: 508 - components: - - type: Transform - pos: 28.5,16.5 - parent: 588 - - uid: 509 - components: - - type: Transform - pos: 29.5,16.5 - parent: 588 - - uid: 514 - components: - - type: Transform - pos: 29.5,13.5 - parent: 588 - - uid: 523 - components: - - type: Transform - pos: 29.5,15.5 - parent: 588 - - uid: 628 - components: - - type: Transform - pos: 1.5,22.5 - parent: 588 - - uid: 639 - components: - - type: Transform - pos: 0.5,22.5 - parent: 588 - - uid: 640 - components: - - type: Transform - pos: 0.5,21.5 - parent: 588 - - uid: 641 - components: - - type: Transform - pos: 0.5,19.5 - parent: 588 - - uid: 642 - components: - - type: Transform - pos: 0.5,18.5 - parent: 588 - - uid: 643 - components: - - type: Transform - pos: 1.5,18.5 - parent: 588 - - uid: 657 - components: - - type: Transform - pos: 2.5,15.5 - parent: 588 - - uid: 661 - components: - - type: Transform - pos: 1.5,15.5 - parent: 588 - - uid: 662 - components: - - type: Transform - pos: 1.5,13.5 - parent: 588 - - uid: 663 - components: - - type: Transform - pos: 2.5,13.5 - parent: 588 - - uid: 664 - components: - - type: Transform - pos: 4.5,13.5 - parent: 588 - - uid: 665 - components: - - type: Transform - pos: 5.5,13.5 - parent: 588 - - uid: 666 - components: - - type: Transform - pos: 5.5,15.5 - parent: 588 - - uid: 667 - components: - - type: Transform - pos: 4.5,15.5 - parent: 588 - - uid: 668 - components: - - type: Transform - pos: 29.5,10.5 - parent: 588 - - uid: 669 - components: - - type: Transform - pos: 28.5,10.5 - parent: 588 - - uid: 670 - components: - - type: Transform - pos: 27.5,10.5 - parent: 588 - - uid: 671 - components: - - type: Transform - pos: 26.5,10.5 - parent: 588 - - uid: 672 - components: - - type: Transform - pos: 25.5,10.5 - parent: 588 - - uid: 673 - components: - - type: Transform - pos: 24.5,10.5 - parent: 588 - - uid: 674 - components: - - type: Transform - pos: 24.5,9.5 - parent: 588 - - uid: 675 - components: - - type: Transform - pos: 24.5,8.5 - parent: 588 - - uid: 676 - components: - - type: Transform - pos: 24.5,7.5 - parent: 588 - - uid: 677 - components: - - type: Transform - pos: 24.5,6.5 - parent: 588 - - uid: 678 - components: - - type: Transform - pos: 25.5,6.5 - parent: 588 - - uid: 679 - components: - - type: Transform - pos: 26.5,6.5 - parent: 588 - - uid: 680 - components: - - type: Transform - pos: 27.5,6.5 - parent: 588 - - uid: 681 - components: - - type: Transform - pos: 28.5,6.5 - parent: 588 - - uid: 682 - components: - - type: Transform - pos: 29.5,6.5 - parent: 588 - - uid: 683 - components: - - type: Transform - pos: 30.5,6.5 - parent: 588 - - uid: 684 - components: - - type: Transform - pos: 31.5,6.5 - parent: 588 - - uid: 685 - components: - - type: Transform - pos: 32.5,6.5 - parent: 588 - - uid: 686 - components: - - type: Transform - pos: 33.5,6.5 - parent: 588 - - uid: 687 - components: - - type: Transform - pos: 34.5,6.5 - parent: 588 - - uid: 688 - components: - - type: Transform - pos: 34.5,7.5 - parent: 588 - - uid: 689 - components: - - type: Transform - pos: 34.5,8.5 - parent: 588 - - uid: 690 - components: - - type: Transform - pos: 34.5,9.5 - parent: 588 - - uid: 691 - components: - - type: Transform - pos: 34.5,10.5 - parent: 588 - - uid: 692 - components: - - type: Transform - pos: 33.5,10.5 - parent: 588 - - uid: 693 - components: - - type: Transform - pos: 32.5,10.5 - parent: 588 - - uid: 694 - components: - - type: Transform - pos: 31.5,10.5 - parent: 588 - - uid: 695 - components: - - type: Transform - pos: 30.5,10.5 - parent: 588 - - uid: 733 - components: - - type: Transform - pos: 20.5,18.5 - parent: 588 - - uid: 734 - components: - - type: Transform - pos: 20.5,19.5 - parent: 588 - - uid: 735 - components: - - type: Transform - pos: 20.5,20.5 - parent: 588 - - uid: 736 - components: - - type: Transform - pos: 20.5,21.5 - parent: 588 - - uid: 737 - components: - - type: Transform - pos: 20.5,22.5 - parent: 588 - - uid: 738 - components: - - type: Transform - pos: 21.5,20.5 - parent: 588 - - uid: 739 - components: - - type: Transform - pos: 22.5,20.5 - parent: 588 - - uid: 740 - components: - - type: Transform - pos: 19.5,20.5 - parent: 588 - - uid: 741 - components: - - type: Transform - pos: 18.5,20.5 - parent: 588 - - uid: 751 - components: - - type: Transform - pos: 32.5,22.5 - parent: 588 - - uid: 752 - components: - - type: Transform - pos: 32.5,21.5 - parent: 588 - - uid: 753 - components: - - type: Transform - pos: 32.5,20.5 - parent: 588 - - uid: 754 - components: - - type: Transform - pos: 32.5,19.5 - parent: 588 - - uid: 755 - components: - - type: Transform - pos: 32.5,18.5 - parent: 588 - - uid: 756 - components: - - type: Transform - pos: 31.5,20.5 - parent: 588 - - uid: 757 - components: - - type: Transform - pos: 30.5,20.5 - parent: 588 - - uid: 758 - components: - - type: Transform - pos: 33.5,20.5 - parent: 588 - - uid: 759 - components: - - type: Transform - pos: 34.5,20.5 - parent: 588 - - uid: 779 - components: - - type: Transform - pos: 25.5,19.5 - parent: 588 - - uid: 780 - components: - - type: Transform - pos: 25.5,18.5 - parent: 588 - - uid: 781 - components: - - type: Transform - pos: 24.5,18.5 - parent: 588 - - uid: 782 - components: - - type: Transform - pos: 24.5,19.5 - parent: 588 - - uid: 783 - components: - - type: Transform - pos: 24.5,20.5 - parent: 588 - - uid: 784 - components: - - type: Transform - pos: 24.5,21.5 - parent: 588 - - uid: 785 - components: - - type: Transform - pos: 24.5,22.5 - parent: 588 - - uid: 786 - components: - - type: Transform - pos: 25.5,22.5 - parent: 588 - - uid: 787 - components: - - type: Transform - pos: 26.5,22.5 - parent: 588 - - uid: 788 - components: - - type: Transform - pos: 27.5,22.5 - parent: 588 - - uid: 789 - components: - - type: Transform - pos: 28.5,22.5 - parent: 588 - - uid: 790 - components: - - type: Transform - pos: 28.5,21.5 - parent: 588 - - uid: 791 - components: - - type: Transform - pos: 28.5,20.5 - parent: 588 - - uid: 792 - components: - - type: Transform - pos: 28.5,19.5 - parent: 588 - - uid: 793 - components: - - type: Transform - pos: 28.5,18.5 - parent: 588 - - uid: 794 - components: - - type: Transform - pos: 27.5,18.5 - parent: 588 - - uid: 795 - components: - - type: Transform - pos: 26.5,18.5 - parent: 588 - - uid: 815 - components: - - type: Transform - pos: 0.5,25.5 - parent: 588 - - uid: 816 - components: - - type: Transform - pos: 0.5,27.5 - parent: 588 - - uid: 817 - components: - - type: Transform - pos: 0.5,28.5 - parent: 588 - - uid: 818 - components: - - type: Transform - pos: 2.5,28.5 - parent: 588 - - uid: 819 - components: - - type: Transform - pos: 2.5,27.5 - parent: 588 - - uid: 869 - components: - - type: Transform - pos: 5.5,24.5 - parent: 588 - - uid: 870 - components: - - type: Transform - pos: 6.5,24.5 - parent: 588 - - uid: 871 - components: - - type: Transform - pos: 6.5,25.5 - parent: 588 - - uid: 872 - components: - - type: Transform - pos: 6.5,26.5 - parent: 588 - - uid: 873 - components: - - type: Transform - pos: 6.5,27.5 - parent: 588 - - uid: 874 - components: - - type: Transform - pos: 6.5,28.5 - parent: 588 - - uid: 875 - components: - - type: Transform - pos: 5.5,28.5 - parent: 588 - - uid: 876 - components: - - type: Transform - pos: 4.5,28.5 - parent: 588 - - uid: 877 - components: - - type: Transform - pos: 4.5,27.5 - parent: 588 - - uid: 878 - components: - - type: Transform - pos: 4.5,26.5 - parent: 588 - - uid: 912 - components: - - type: Transform - pos: 6.5,10.5 - parent: 588 - - uid: 927 - components: - - type: Transform - pos: 34.5,36.5 - parent: 588 - - uid: 928 - components: - - type: Transform - pos: 32.5,36.5 - parent: 588 - - uid: 996 - components: - - type: Transform - pos: 21.5,32.5 - parent: 588 - - uid: 1079 - components: - - type: Transform - pos: 32.5,35.5 - parent: 588 - - uid: 1080 - components: - - type: Transform - pos: 31.5,35.5 - parent: 588 - - uid: 1081 - components: - - type: Transform - pos: 32.5,34.5 - parent: 588 - - uid: 1082 - components: - - type: Transform - pos: 29.5,34.5 - parent: 588 - - uid: 1083 - components: - - type: Transform - pos: 24.5,35.5 - parent: 588 - - uid: 1084 - components: - - type: Transform - pos: 27.5,35.5 - parent: 588 - - uid: 1085 - components: - - type: Transform - pos: 29.5,35.5 - parent: 588 - - uid: 1086 - components: - - type: Transform - pos: 28.5,35.5 - parent: 588 - - uid: 1089 - components: - - type: Transform - pos: 4.5,36.5 - parent: 588 - - uid: 1090 - components: - - type: Transform - pos: 33.5,34.5 - parent: 588 - - uid: 1091 - components: - - type: Transform - pos: 5.5,35.5 - parent: 588 - - uid: 1092 - components: - - type: Transform - pos: 29.5,36.5 - parent: 588 - - uid: 1093 - components: - - type: Transform - pos: 34.5,34.5 - parent: 588 - - uid: 1094 - components: - - type: Transform - pos: 34.5,35.5 - parent: 588 - - uid: 1095 - components: - - type: Transform - pos: 26.5,35.5 - parent: 588 - - uid: 1096 - components: - - type: Transform - pos: 25.5,35.5 - parent: 588 - - uid: 1097 - components: - - type: Transform - pos: 33.5,36.5 - parent: 588 - - uid: 1098 - components: - - type: Transform - pos: 30.5,35.5 - parent: 588 - - uid: 1117 - components: - - type: Transform - pos: 16.5,26.5 - parent: 588 - - uid: 1121 - components: - - type: Transform - pos: 17.5,26.5 - parent: 588 - - uid: 1122 - components: - - type: Transform - pos: 18.5,26.5 - parent: 588 - - uid: 1123 - components: - - type: Transform - pos: 17.5,27.5 - parent: 588 - - uid: 1124 - components: - - type: Transform - pos: 17.5,28.5 - parent: 588 - - uid: 1125 - components: - - type: Transform - pos: 17.5,24.5 - parent: 588 - - uid: 1126 - components: - - type: Transform - pos: 17.5,25.5 - parent: 588 - - uid: 1127 - components: - - type: Transform - pos: 20.5,26.5 - parent: 588 - - uid: 1128 - components: - - type: Transform - pos: 21.5,26.5 - parent: 588 - - uid: 1129 - components: - - type: Transform - pos: 22.5,26.5 - parent: 588 - - uid: 1130 - components: - - type: Transform - pos: 21.5,27.5 - parent: 588 - - uid: 1131 - components: - - type: Transform - pos: 21.5,28.5 - parent: 588 - - uid: 1132 - components: - - type: Transform - pos: 21.5,25.5 - parent: 588 - - uid: 1133 - components: - - type: Transform - pos: 21.5,24.5 - parent: 588 - - uid: 1134 - components: - - type: Transform - pos: 24.5,26.5 - parent: 588 - - uid: 1135 - components: - - type: Transform - pos: 25.5,26.5 - parent: 588 - - uid: 1136 - components: - - type: Transform - pos: 26.5,26.5 - parent: 588 - - uid: 1137 - components: - - type: Transform - pos: 25.5,27.5 - parent: 588 - - uid: 1138 - components: - - type: Transform - pos: 25.5,28.5 - parent: 588 - - uid: 1139 - components: - - type: Transform - pos: 25.5,25.5 - parent: 588 - - uid: 1140 - components: - - type: Transform - pos: 25.5,24.5 - parent: 588 - - uid: 1170 - components: - - type: Transform - pos: 3.5,36.5 - parent: 588 - - uid: 1171 - components: - - type: Transform - pos: 2.5,36.5 - parent: 588 - - uid: 1172 - components: - - type: Transform - pos: 1.5,36.5 - parent: 588 - - uid: 1173 - components: - - type: Transform - pos: 0.5,36.5 - parent: 588 - - uid: 1174 - components: - - type: Transform - pos: 0.5,35.5 - parent: 588 - - uid: 1175 - components: - - type: Transform - pos: 6.5,34.5 - parent: 588 - - uid: 1176 - components: - - type: Transform - pos: 7.5,34.5 - parent: 588 - - uid: 1177 - components: - - type: Transform - pos: 8.5,34.5 - parent: 588 - - uid: 1178 - components: - - type: Transform - pos: 9.5,34.5 - parent: 588 - - uid: 1179 - components: - - type: Transform - pos: 10.5,34.5 - parent: 588 - - uid: 1268 - components: - - type: Transform - pos: 30.5,26.5 - parent: 588 - - uid: 1269 - components: - - type: Transform - pos: 29.5,27.5 - parent: 588 - - uid: 1270 - components: - - type: Transform - pos: 29.5,28.5 - parent: 588 - - uid: 1271 - components: - - type: Transform - pos: 29.5,25.5 - parent: 588 - - uid: 1272 - components: - - type: Transform - pos: 29.5,24.5 - parent: 588 - - uid: 1273 - components: - - type: Transform - pos: 32.5,26.5 - parent: 588 - - uid: 1274 - components: - - type: Transform - pos: 33.5,26.5 - parent: 588 - - uid: 1275 - components: - - type: Transform - pos: 34.5,26.5 - parent: 588 - - uid: 1276 - components: - - type: Transform - pos: 33.5,27.5 - parent: 588 - - uid: 1277 - components: - - type: Transform - pos: 33.5,28.5 - parent: 588 - - uid: 1278 - components: - - type: Transform - pos: 33.5,25.5 - parent: 588 - - uid: 1279 - components: - - type: Transform - pos: 33.5,24.5 - parent: 588 - - uid: 1306 - components: - - type: Transform - pos: 27.5,40.5 - parent: 588 - - uid: 1307 - components: - - type: Transform - pos: 26.5,40.5 - parent: 588 - - uid: 1308 - components: - - type: Transform - pos: 25.5,40.5 - parent: 588 - - uid: 1309 - components: - - type: Transform - pos: 24.5,40.5 - parent: 588 - - uid: 1310 - components: - - type: Transform - pos: 24.5,39.5 - parent: 588 - - uid: 1311 - components: - - type: Transform - pos: 24.5,38.5 - parent: 588 - - uid: 1312 - components: - - type: Transform - pos: 25.5,38.5 - parent: 588 - - uid: 1313 - components: - - type: Transform - pos: 26.5,38.5 - parent: 588 - - uid: 1314 - components: - - type: Transform - pos: 27.5,38.5 - parent: 588 - - uid: 1315 - components: - - type: Transform - pos: 28.5,38.5 - parent: 588 - - uid: 1316 - components: - - type: Transform - pos: 29.5,38.5 - parent: 588 - - uid: 1317 - components: - - type: Transform - pos: 30.5,38.5 - parent: 588 - - uid: 1318 - components: - - type: Transform - pos: 30.5,39.5 - parent: 588 - - uid: 1426 - components: - - type: Transform - pos: 11.5,42.5 - parent: 588 - - uid: 1427 - components: - - type: Transform - pos: 11.5,43.5 - parent: 588 - - uid: 1428 - components: - - type: Transform - pos: 11.5,44.5 - parent: 588 - - uid: 1429 - components: - - type: Transform - pos: 11.5,45.5 - parent: 588 - - uid: 1430 - components: - - type: Transform - pos: 11.5,46.5 - parent: 588 - - uid: 1431 - components: - - type: Transform - pos: 11.5,47.5 - parent: 588 - - uid: 1432 - components: - - type: Transform - pos: 11.5,48.5 - parent: 588 - - uid: 1433 - components: - - type: Transform - pos: 10.5,45.5 - parent: 588 - - uid: 1434 - components: - - type: Transform - pos: 9.5,45.5 - parent: 588 - - uid: 1435 - components: - - type: Transform - pos: 8.5,45.5 - parent: 588 - - uid: 1436 - components: - - type: Transform - pos: 12.5,45.5 - parent: 588 - - uid: 1437 - components: - - type: Transform - pos: 13.5,45.5 - parent: 588 - - uid: 1438 - components: - - type: Transform - pos: 14.5,45.5 - parent: 588 - - uid: 1439 - components: - - type: Transform - pos: 13.5,46.5 - parent: 588 - - uid: 1440 - components: - - type: Transform - pos: 13.5,47.5 - parent: 588 - - uid: 1441 - components: - - type: Transform - pos: 9.5,46.5 - parent: 588 - - uid: 1442 - components: - - type: Transform - pos: 9.5,47.5 - parent: 588 - - uid: 1443 - components: - - type: Transform - pos: 10.5,43.5 - parent: 588 - - uid: 1444 - components: - - type: Transform - pos: 9.5,43.5 - parent: 588 - - uid: 1445 - components: - - type: Transform - pos: 12.5,43.5 - parent: 588 - - uid: 1446 - components: - - type: Transform - pos: 13.5,43.5 - parent: 588 - - uid: 1447 - components: - - type: Transform - pos: 3.5,42.5 - parent: 588 - - uid: 1448 - components: - - type: Transform - pos: 3.5,43.5 - parent: 588 - - uid: 1449 - components: - - type: Transform - pos: 3.5,44.5 - parent: 588 - - uid: 1450 - components: - - type: Transform - pos: 3.5,45.5 - parent: 588 - - uid: 1451 - components: - - type: Transform - pos: 3.5,46.5 - parent: 588 - - uid: 1452 - components: - - type: Transform - pos: 3.5,47.5 - parent: 588 - - uid: 1453 - components: - - type: Transform - pos: 3.5,48.5 - parent: 588 - - uid: 1454 - components: - - type: Transform - pos: 0.5,45.5 - parent: 588 - - uid: 1455 - components: - - type: Transform - pos: 1.5,45.5 - parent: 588 - - uid: 1456 - components: - - type: Transform - pos: 2.5,45.5 - parent: 588 - - uid: 1457 - components: - - type: Transform - pos: 3.5,45.5 - parent: 588 - - uid: 1458 - components: - - type: Transform - pos: 4.5,45.5 - parent: 588 - - uid: 1459 - components: - - type: Transform - pos: 5.5,45.5 - parent: 588 - - uid: 1460 - components: - - type: Transform - pos: 6.5,45.5 - parent: 588 - - uid: 1529 - components: - - type: Transform - pos: 19.5,47.5 - parent: 588 - - uid: 1530 - components: - - type: Transform - pos: 19.5,48.5 - parent: 588 - - uid: 1531 - components: - - type: Transform - pos: 18.5,48.5 - parent: 588 - - uid: 1532 - components: - - type: Transform - pos: 17.5,48.5 - parent: 588 - - uid: 1533 - components: - - type: Transform - pos: 16.5,48.5 - parent: 588 - - uid: 1534 - components: - - type: Transform - pos: 16.5,47.5 - parent: 588 - - uid: 1535 - components: - - type: Transform - pos: 16.5,46.5 - parent: 588 - - uid: 1536 - components: - - type: Transform - pos: 16.5,45.5 - parent: 588 - - uid: 1537 - components: - - type: Transform - pos: 16.5,44.5 - parent: 588 - - uid: 1538 - components: - - type: Transform - pos: 16.5,43.5 - parent: 588 - - uid: 1539 - components: - - type: Transform - pos: 16.5,42.5 - parent: 588 - - uid: 1540 - components: - - type: Transform - pos: 17.5,42.5 - parent: 588 - - uid: 1541 - components: - - type: Transform - pos: 18.5,42.5 - parent: 588 - - uid: 1542 - components: - - type: Transform - pos: 19.5,42.5 - parent: 588 - - uid: 1543 - components: - - type: Transform - pos: 20.5,42.5 - parent: 588 - - uid: 1544 - components: - - type: Transform - pos: 21.5,42.5 - parent: 588 - - uid: 1545 - components: - - type: Transform - pos: 22.5,42.5 - parent: 588 - - uid: 1546 - components: - - type: Transform - pos: 22.5,43.5 - parent: 588 - - uid: 1547 - components: - - type: Transform - pos: 22.5,44.5 - parent: 588 - - uid: 1548 - components: - - type: Transform - pos: 22.5,45.5 - parent: 588 - - uid: 1549 - components: - - type: Transform - pos: 22.5,46.5 - parent: 588 - - uid: 1550 - components: - - type: Transform - pos: 22.5,47.5 - parent: 588 - - uid: 1551 - components: - - type: Transform - pos: 22.5,48.5 - parent: 588 - - uid: 1552 - components: - - type: Transform - pos: 21.5,48.5 - parent: 588 - - uid: 1553 - components: - - type: Transform - pos: 20.5,48.5 - parent: 588 - - uid: 1554 - components: - - type: Transform - pos: 19.5,43.5 - parent: 588 - - uid: 1555 - components: - - type: Transform - pos: 19.5,44.5 - parent: 588 - - uid: 1556 - components: - - type: Transform - pos: 19.5,45.5 - parent: 588 - - uid: 1557 - components: - - type: Transform - pos: 19.5,46.5 - parent: 588 - - uid: 1807 - components: - - type: Transform - pos: 27.5,42.5 - parent: 588 - - uid: 1808 - components: - - type: Transform - pos: 27.5,43.5 - parent: 588 - - uid: 1809 - components: - - type: Transform - pos: 27.5,44.5 - parent: 588 - - uid: 1810 - components: - - type: Transform - pos: 27.5,45.5 - parent: 588 - - uid: 1811 - components: - - type: Transform - pos: 27.5,46.5 - parent: 588 - - uid: 1812 - components: - - type: Transform - pos: 27.5,47.5 - parent: 588 - - uid: 1813 - components: - - type: Transform - pos: 27.5,48.5 - parent: 588 - - uid: 1814 - components: - - type: Transform - pos: 28.5,45.5 - parent: 588 - - uid: 1815 - components: - - type: Transform - pos: 29.5,45.5 - parent: 588 - - uid: 1816 - components: - - type: Transform - pos: 30.5,45.5 - parent: 588 - - uid: 1817 - components: - - type: Transform - pos: 26.5,45.5 - parent: 588 - - uid: 1818 - components: - - type: Transform - pos: 25.5,45.5 - parent: 588 - - uid: 1819 - components: - - type: Transform - pos: 24.5,45.5 - parent: 588 - - uid: 1820 - components: - - type: Transform - pos: 26.5,47.5 - parent: 588 - - uid: 1821 - components: - - type: Transform - pos: 25.5,47.5 - parent: 588 - - uid: 1822 - components: - - type: Transform - pos: 28.5,47.5 - parent: 588 - - uid: 1823 - components: - - type: Transform - pos: 29.5,47.5 - parent: 588 - - uid: 1824 - components: - - type: Transform - pos: 26.5,43.5 - parent: 588 - - uid: 1825 - components: - - type: Transform - pos: 25.5,43.5 - parent: 588 - - uid: 1826 - components: - - type: Transform - pos: 28.5,43.5 - parent: 588 - - uid: 1827 - components: - - type: Transform - pos: 29.5,43.5 - parent: 588 -- proto: CableApcStack1 - entities: - - uid: 655 - components: - - type: Transform - pos: 16.273203,19.650417 - parent: 588 -- proto: CableHV - entities: - - uid: 462 - components: - - type: Transform - pos: 27.5,13.5 - parent: 588 - - uid: 466 - components: - - type: Transform - pos: 26.5,13.5 - parent: 588 - - uid: 468 - components: - - type: Transform - pos: 28.5,13.5 - parent: 588 - - uid: 485 - components: - - type: Transform - pos: 26.5,15.5 - parent: 588 - - uid: 486 - components: - - type: Transform - pos: 26.5,14.5 - parent: 588 - - uid: 487 - components: - - type: Transform - pos: 27.5,14.5 - parent: 588 - - uid: 488 - components: - - type: Transform - pos: 28.5,14.5 - parent: 588 - - uid: 489 - components: - - type: Transform - pos: 28.5,15.5 - parent: 588 - - uid: 743 - components: - - type: Transform - pos: 26.5,21.5 - parent: 588 - - uid: 744 - components: - - type: Transform - pos: 27.5,21.5 - parent: 588 - - uid: 745 - components: - - type: Transform - pos: 26.5,20.5 - parent: 588 - - uid: 746 - components: - - type: Transform - pos: 25.5,21.5 - parent: 588 - - uid: 768 - components: - - type: Transform - pos: 26.5,19.5 - parent: 588 - - uid: 778 - components: - - type: Transform - pos: 27.5,19.5 - parent: 588 - - uid: 978 - components: - - type: Transform - pos: 16.5,32.5 - parent: 588 - - uid: 979 - components: - - type: Transform - pos: 15.5,31.5 - parent: 588 - - uid: 980 - components: - - type: Transform - pos: 16.5,31.5 - parent: 588 - - uid: 981 - components: - - type: Transform - pos: 17.5,31.5 - parent: 588 - - uid: 982 - components: - - type: Transform - pos: 18.5,31.5 - parent: 588 - - uid: 983 - components: - - type: Transform - pos: 22.5,31.5 - parent: 588 - - uid: 984 - components: - - type: Transform - pos: 23.5,31.5 - parent: 588 - - uid: 985 - components: - - type: Transform - pos: 24.5,31.5 - parent: 588 - - uid: 986 - components: - - type: Transform - pos: 24.5,32.5 - parent: 588 - - uid: 987 - components: - - type: Transform - pos: 25.5,31.5 - parent: 588 - - uid: 989 - components: - - type: Transform - pos: 18.5,32.5 - parent: 588 - - uid: 990 - components: - - type: Transform - pos: 19.5,32.5 - parent: 588 - - uid: 991 - components: - - type: Transform - pos: 20.5,32.5 - parent: 588 - - uid: 992 - components: - - type: Transform - pos: 21.5,32.5 - parent: 588 - - uid: 993 - components: - - type: Transform - pos: 22.5,32.5 - parent: 588 - - uid: 1003 - components: - - type: Transform - pos: 16.5,30.5 - parent: 588 - - uid: 1004 - components: - - type: Transform - pos: 24.5,30.5 - parent: 588 - - uid: 1510 - components: - - type: Transform - pos: 18.5,44.5 - parent: 588 - - uid: 1511 - components: - - type: Transform - pos: 18.5,45.5 - parent: 588 - - uid: 1512 - components: - - type: Transform - pos: 20.5,45.5 - parent: 588 - - uid: 1513 - components: - - type: Transform - pos: 20.5,44.5 - parent: 588 - - uid: 1514 - components: - - type: Transform - pos: 19.5,44.5 - parent: 588 - - uid: 1522 - components: - - type: Transform - pos: 17.5,46.5 - parent: 588 - - uid: 1523 - components: - - type: Transform - pos: 21.5,46.5 - parent: 588 - - uid: 1524 - components: - - type: Transform - pos: 20.5,46.5 - parent: 588 - - uid: 1525 - components: - - type: Transform - pos: 18.5,46.5 - parent: 588 - - uid: 1526 - components: - - type: Transform - pos: 19.5,46.5 - parent: 588 -- proto: CableMV - entities: - - uid: 490 - components: - - type: Transform - pos: 27.5,13.5 - parent: 588 - - uid: 491 - components: - - type: Transform - pos: 26.5,13.5 - parent: 588 - - uid: 492 - components: - - type: Transform - pos: 25.5,13.5 - parent: 588 - - uid: 775 - components: - - type: Transform - pos: 27.5,19.5 - parent: 588 - - uid: 776 - components: - - type: Transform - pos: 26.5,19.5 - parent: 588 - - uid: 777 - components: - - type: Transform - pos: 25.5,19.5 - parent: 588 - - uid: 1527 - components: - - type: Transform - pos: 19.5,46.5 - parent: 588 - - uid: 1528 - components: - - type: Transform - pos: 19.5,47.5 - parent: 588 -- proto: CableTerminal - entities: - - uid: 463 - components: - - type: Transform - pos: 26.5,14.5 - parent: 588 - - uid: 464 - components: - - type: Transform - pos: 27.5,14.5 - parent: 588 - - uid: 465 - components: - - type: Transform - pos: 28.5,14.5 - parent: 588 - - uid: 767 - components: - - type: Transform - pos: 26.5,20.5 - parent: 588 - - uid: 970 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 18.5,31.5 - parent: 588 - - uid: 976 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 22.5,31.5 - parent: 588 - - uid: 1558 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 18.5,45.5 - parent: 588 - - uid: 1559 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 20.5,45.5 - parent: 588 -- proto: Carpet - entities: - - uid: 1632 - components: - - type: Transform - pos: 24.5,0.5 - parent: 588 - - uid: 1633 - components: - - type: Transform - pos: 25.5,0.5 - parent: 588 - - uid: 1634 - components: - - type: Transform - pos: 25.5,1.5 - parent: 588 - - uid: 1635 - components: - - type: Transform - pos: 24.5,1.5 - parent: 588 -- proto: CarpetBlue - entities: - - uid: 1636 - components: - - type: Transform - pos: 27.5,0.5 - parent: 588 - - uid: 1637 - components: - - type: Transform - pos: 28.5,1.5 - parent: 588 - - uid: 1638 - components: - - type: Transform - pos: 27.5,1.5 - parent: 588 - - uid: 1639 - components: - - type: Transform - pos: 28.5,0.5 - parent: 588 -- proto: CarpetPurple - entities: - - uid: 1626 - components: - - type: Transform - pos: 25.5,4.5 - parent: 588 - - uid: 1627 - components: - - type: Transform - pos: 25.5,3.5 - parent: 588 - - uid: 1628 - components: - - type: Transform - pos: 26.5,3.5 - parent: 588 - - uid: 1629 - components: - - type: Transform - pos: 27.5,3.5 - parent: 588 - - uid: 1630 - components: - - type: Transform - pos: 27.5,4.5 - parent: 588 - - uid: 1631 - components: - - type: Transform - pos: 26.5,4.5 - parent: 588 -- proto: Catwalk - entities: - - uid: 141 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 6.5,24.5 - parent: 588 - - uid: 142 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 6.5,26.5 - parent: 588 - - uid: 143 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 6.5,25.5 - parent: 588 - - uid: 248 - components: - - type: Transform - pos: 8.5,4.5 - parent: 588 - - uid: 249 - components: - - type: Transform - pos: 8.5,2.5 - parent: 588 - - uid: 250 - components: - - type: Transform - pos: 8.5,3.5 - parent: 588 - - uid: 251 - components: - - type: Transform - pos: 8.5,1.5 - parent: 588 - - uid: 471 - components: - - type: Transform - pos: 27.5,14.5 - parent: 588 - - uid: 473 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 29.5,12.5 - parent: 588 - - uid: 474 - components: - - type: Transform - pos: 28.5,14.5 - parent: 588 - - uid: 475 - components: - - type: Transform - pos: 26.5,14.5 - parent: 588 - - uid: 512 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 25.5,12.5 - parent: 588 - - uid: 513 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 28.5,12.5 - parent: 588 - - uid: 515 - components: - - type: Transform - pos: 25.5,16.5 - parent: 588 - - uid: 516 - components: - - type: Transform - pos: 26.5,16.5 - parent: 588 - - uid: 517 - components: - - type: Transform - pos: 27.5,16.5 - parent: 588 - - uid: 518 - components: - - type: Transform - pos: 28.5,16.5 - parent: 588 - - uid: 519 - components: - - type: Transform - pos: 29.5,16.5 - parent: 588 - - uid: 520 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 27.5,12.5 - parent: 588 - - uid: 521 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 26.5,12.5 - parent: 588 - - uid: 769 - components: - - type: Transform - pos: 26.5,20.5 - parent: 588 - - uid: 832 - components: - - type: Transform - pos: 3.5,31.5 - parent: 588 - - uid: 833 - components: - - type: Transform - pos: 4.5,31.5 - parent: 588 - - uid: 834 - components: - - type: Transform - pos: 5.5,31.5 - parent: 588 - - uid: 835 - components: - - type: Transform - pos: 6.5,31.5 - parent: 588 - - uid: 836 - components: - - type: Transform - pos: 7.5,31.5 - parent: 588 - - uid: 837 - components: - - type: Transform - pos: 8.5,31.5 - parent: 588 - - uid: 838 - components: - - type: Transform - pos: 9.5,31.5 - parent: 588 - - uid: 839 - components: - - type: Transform - pos: 6.5,32.5 - parent: 588 - - uid: 840 - components: - - type: Transform - pos: 6.5,30.5 - parent: 588 - - uid: 841 - components: - - type: Transform - pos: 5.5,32.5 - parent: 588 - - uid: 842 - components: - - type: Transform - pos: 7.5,32.5 - parent: 588 - - uid: 843 - components: - - type: Transform - pos: 5.5,30.5 - parent: 588 - - uid: 844 - components: - - type: Transform - pos: 7.5,30.5 - parent: 588 - - uid: 861 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 6.5,27.5 - parent: 588 - - uid: 862 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 6.5,28.5 - parent: 588 - - uid: 863 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 4.5,28.5 - parent: 588 - - uid: 864 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 4.5,27.5 - parent: 588 - - uid: 865 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 4.5,26.5 - parent: 588 - - uid: 879 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 3.5,40.5 - parent: 588 - - uid: 880 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 3.5,39.5 - parent: 588 - - uid: 881 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 2.5,39.5 - parent: 588 - - uid: 882 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 1.5,39.5 - parent: 588 - - uid: 883 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 4.5,39.5 - parent: 588 - - uid: 884 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 5.5,39.5 - parent: 588 - - uid: 885 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 3.5,38.5 - parent: 588 - - uid: 914 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 3.5,10.5 - parent: 588 - - uid: 915 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 4.5,10.5 - parent: 588 - - uid: 916 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 5.5,10.5 - parent: 588 - - uid: 917 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 6.5,10.5 - parent: 588 - - uid: 918 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 7.5,10.5 - parent: 588 - - uid: 919 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 3.5,6.5 - parent: 588 - - uid: 920 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 4.5,6.5 - parent: 588 - - uid: 921 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 5.5,6.5 - parent: 588 - - uid: 922 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 6.5,6.5 - parent: 588 - - uid: 923 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 7.5,6.5 - parent: 588 - - uid: 955 - components: - - type: Transform - pos: 15.5,31.5 - parent: 588 - - uid: 956 - components: - - type: Transform - pos: 16.5,31.5 - parent: 588 - - uid: 957 - components: - - type: Transform - pos: 17.5,31.5 - parent: 588 - - uid: 958 - components: - - type: Transform - pos: 18.5,31.5 - parent: 588 - - uid: 959 - components: - - type: Transform - pos: 19.5,31.5 - parent: 588 - - uid: 960 - components: - - type: Transform - pos: 20.5,31.5 - parent: 588 - - uid: 961 - components: - - type: Transform - pos: 21.5,31.5 - parent: 588 - - uid: 962 - components: - - type: Transform - pos: 22.5,31.5 - parent: 588 - - uid: 963 - components: - - type: Transform - pos: 23.5,31.5 - parent: 588 - - uid: 964 - components: - - type: Transform - pos: 24.5,31.5 - parent: 588 - - uid: 965 - components: - - type: Transform - pos: 25.5,31.5 - parent: 588 - - uid: 994 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 20.5,32.5 - parent: 588 - - uid: 1158 - components: - - type: Transform - pos: 8.5,0.5 - parent: 588 - - uid: 1180 - components: - - type: Transform - pos: 1.5,36.5 - parent: 588 - - uid: 1181 - components: - - type: Transform - pos: 2.5,36.5 - parent: 588 - - uid: 1182 - components: - - type: Transform - pos: 3.5,36.5 - parent: 588 - - uid: 1183 - components: - - type: Transform - pos: 4.5,36.5 - parent: 588 - - uid: 1184 - components: - - type: Transform - pos: 5.5,36.5 - parent: 588 - - uid: 1185 - components: - - type: Transform - pos: 5.5,34.5 - parent: 588 - - uid: 1186 - components: - - type: Transform - pos: 6.5,34.5 - parent: 588 - - uid: 1187 - components: - - type: Transform - pos: 7.5,34.5 - parent: 588 - - uid: 1188 - components: - - type: Transform - pos: 8.5,34.5 - parent: 588 - - uid: 1189 - components: - - type: Transform - pos: 9.5,34.5 - parent: 588 - - uid: 1320 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 27.5,40.5 - parent: 588 - - uid: 1321 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 26.5,40.5 - parent: 588 - - uid: 1322 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 25.5,40.5 - parent: 588 - - uid: 1323 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 25.5,38.5 - parent: 588 - - uid: 1324 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 26.5,38.5 - parent: 588 - - uid: 1325 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 27.5,38.5 - parent: 588 - - uid: 1326 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 28.5,38.5 - parent: 588 - - uid: 1327 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 29.5,38.5 - parent: 588 - - uid: 1331 - components: - - type: Transform - pos: 4.5,45.5 - parent: 588 - - uid: 1336 - components: - - type: Transform - pos: 2.5,45.5 - parent: 588 - - uid: 1339 - components: - - type: Transform - pos: 3.5,45.5 - parent: 588 - - uid: 1342 - components: - - type: Transform - pos: 3.5,46.5 - parent: 588 - - uid: 1344 - components: - - type: Transform - pos: 3.5,44.5 - parent: 588 - - uid: 1346 - components: - - type: Transform - pos: 2.5,44.5 - parent: 588 - - uid: 1347 - components: - - type: Transform - pos: 4.5,44.5 - parent: 588 - - uid: 1348 - components: - - type: Transform - pos: 4.5,44.5 - parent: 588 - - uid: 1349 - components: - - type: Transform - pos: 4.5,46.5 - parent: 588 - - uid: 1350 - components: - - type: Transform - pos: 2.5,46.5 - parent: 588 - - uid: 1494 - components: - - type: Transform - pos: 17.5,42.5 - parent: 588 - - uid: 1495 - components: - - type: Transform - pos: 18.5,42.5 - parent: 588 - - uid: 1496 - components: - - type: Transform - pos: 19.5,42.5 - parent: 588 - - uid: 1497 - components: - - type: Transform - pos: 20.5,42.5 - parent: 588 - - uid: 1498 - components: - - type: Transform - pos: 21.5,42.5 - parent: 588 - - uid: 1499 - components: - - type: Transform - pos: 17.5,48.5 - parent: 588 - - uid: 1500 - components: - - type: Transform - pos: 18.5,48.5 - parent: 588 - - uid: 1501 - components: - - type: Transform - pos: 19.5,48.5 - parent: 588 - - uid: 1502 - components: - - type: Transform - pos: 20.5,48.5 - parent: 588 - - uid: 1503 - components: - - type: Transform - pos: 21.5,48.5 - parent: 588 - - uid: 1516 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 19.5,44.5 - parent: 588 - - uid: 1517 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 19.5,45.5 - parent: 588 - - uid: 1518 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 18.5,45.5 - parent: 588 - - uid: 1519 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 20.5,45.5 - parent: 588 - - uid: 1582 - components: - - type: Transform - pos: 28.5,22.5 - parent: 588 - - uid: 1583 - components: - - type: Transform - pos: 28.5,21.5 - parent: 588 - - uid: 1584 - components: - - type: Transform - pos: 28.5,20.5 - parent: 588 - - uid: 1585 - components: - - type: Transform - pos: 28.5,19.5 - parent: 588 - - uid: 1586 - components: - - type: Transform - pos: 28.5,18.5 - parent: 588 - - uid: 1587 - components: - - type: Transform - pos: 24.5,22.5 - parent: 588 - - uid: 1588 - components: - - type: Transform - pos: 24.5,21.5 - parent: 588 - - uid: 1589 - components: - - type: Transform - pos: 24.5,20.5 - parent: 588 - - uid: 1590 - components: - - type: Transform - pos: 24.5,19.5 - parent: 588 - - uid: 1591 - components: - - type: Transform - pos: 24.5,18.5 - parent: 588 -- proto: Cautery - entities: - - uid: 1474 - components: - - type: Transform - pos: 8.533231,42.775993 - parent: 588 -- proto: Chair - entities: - - uid: 357 - components: - - type: Transform - pos: 23.5,4.5 - parent: 588 - - uid: 421 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 14.5,9.5 - parent: 588 - - uid: 422 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 20.5,9.5 - parent: 588 - - uid: 423 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 20.5,7.5 - parent: 588 - - uid: 533 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 19.5,14.5 - parent: 588 - - uid: 534 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 19.5,15.5 - parent: 588 - - uid: 537 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 21.5,14.5 - parent: 588 - - uid: 569 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 11.5,13.5 - parent: 588 - - uid: 716 - components: - - type: Transform - pos: 18.5,19.5 - parent: 588 - - uid: 717 - components: - - type: Transform - pos: 19.5,19.5 - parent: 588 - - uid: 718 - components: - - type: Transform - pos: 22.5,19.5 - parent: 588 - - uid: 719 - components: - - type: Transform - pos: 21.5,19.5 - parent: 588 - - uid: 1280 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 17.5,38.5 - parent: 588 - - uid: 1281 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 18.5,38.5 - parent: 588 - - uid: 1282 - components: - - type: Transform - pos: 20.5,40.5 - parent: 588 - - uid: 1283 - components: - - type: Transform - pos: 21.5,40.5 - parent: 588 - - uid: 1865 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 14.5,7.5 - parent: 588 -- proto: ChairFolding - entities: - - uid: 344 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 31.5,0.5 - parent: 588 - - uid: 345 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 32.5,1.5 - parent: 588 - - uid: 346 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 32.5,0.5 - parent: 588 - - uid: 347 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 31.5,3.5 - parent: 588 - - uid: 348 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 32.5,3.5 - parent: 588 - - uid: 349 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 31.5,4.5 - parent: 588 - - uid: 350 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 33.5,1.5 - parent: 588 - - uid: 351 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 33.5,0.5 - parent: 588 - - uid: 352 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 33.5,4.5 - parent: 588 - - uid: 353 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 32.5,4.5 - parent: 588 - - uid: 1212 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 7.5,36.5 - parent: 588 -- proto: ChairFoldingSpawnFolded - entities: - - uid: 354 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 31.53707,1.6455604 - parent: 588 - - uid: 355 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 33.595894,3.4052575 - parent: 588 -- proto: ChairOfficeDark - entities: - - uid: 330 - components: - - type: Transform - pos: 19.5,1.5 - parent: 588 - - uid: 331 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 20.5,3.5 - parent: 588 - - uid: 358 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 24.5,0.5 - parent: 588 - - uid: 359 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 25.5,0.5 - parent: 588 - - uid: 360 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 28.5,0.5 - parent: 588 - - uid: 361 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 27.5,0.5 - parent: 588 - - uid: 571 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 13.5,12.5 - parent: 588 -- proto: ChairOfficeLight - entities: - - uid: 631 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 9.5,19.5 - parent: 588 - - uid: 638 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 7.5,21.5 - parent: 588 - - uid: 707 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 15.5,21.5 - parent: 588 -- proto: ChairPilotSeat - entities: - - uid: 356 - components: - - type: Transform - pos: 26.5,4.5 - parent: 588 -- proto: ChairWood - entities: - - uid: 1049 - components: - - type: Transform - pos: 20.5,25.5 - parent: 588 - - uid: 1050 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 22.5,27.5 - parent: 588 - - uid: 1231 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 21.5,34.5 - parent: 588 - - uid: 1232 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 13.5,36.5 - parent: 588 - - uid: 1790 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 25.5,46.5 - parent: 588 - - uid: 1791 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 25.5,44.5 - parent: 588 -- proto: CigarGold - entities: - - uid: 1219 - components: - - type: Transform - pos: 7.4719925,36.539555 - parent: 588 -- proto: ClosetBombFilled - entities: - - uid: 413 - components: - - type: Transform - pos: 14.5,6.5 - parent: 588 - - uid: 1014 - components: - - type: Transform - pos: 12.5,27.5 - parent: 588 - - uid: 1026 - components: - - type: Transform - pos: 16.5,27.5 - parent: 588 -- proto: ClosetEmergencyFilledRandom - entities: - - uid: 1203 - components: - - type: Transform - pos: 12.5,30.5 - parent: 588 - - uid: 1204 - components: - - type: Transform - pos: 0.5,32.5 - parent: 588 - - uid: 1205 - components: - - type: Transform - pos: 9.5,9.5 - parent: 588 - - uid: 1207 - components: - - type: Transform - pos: 1.5,7.5 - parent: 588 -- proto: ClosetFireFilled - entities: - - uid: 1194 - components: - - type: Transform - pos: 1.5,9.5 - parent: 588 - - uid: 1195 - components: - - type: Transform - pos: 9.5,7.5 - parent: 588 - - uid: 1196 - components: - - type: Transform - pos: 6.5,40.5 - parent: 588 - - uid: 1197 - components: - - type: Transform - pos: 0.5,38.5 - parent: 588 -- proto: ClosetL3SecurityFilled - entities: - - uid: 415 - components: - - type: Transform - pos: 20.5,10.5 - parent: 588 -- proto: ClosetToolFilled - entities: - - uid: 1007 - components: - - type: Transform - pos: 14.5,30.5 - parent: 588 -- proto: ClosetWallMaintenanceFilledRandom - entities: - - uid: 499 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 25.5,15.5 - parent: 588 - - uid: 868 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 5.5,27.5 - parent: 588 - - uid: 1564 - components: - - type: Transform - pos: 17.5,43.5 - parent: 588 - - uid: 1565 - components: - - type: Transform - pos: 21.5,43.5 - parent: 588 -- proto: ClothingBeltChampion - entities: - - uid: 1236 - components: - - type: Transform - pos: 10.581136,39.53631 - parent: 588 -- proto: ClothingEyesGlassesMeson - entities: - - uid: 1108 - components: - - type: Transform - pos: 25.666832,30.643515 - parent: 588 -- proto: ClothingHandsGlovesNitrile - entities: - - uid: 1715 - components: - - type: Transform - pos: 10.432637,44.476112 - parent: 588 -- proto: ClothingHeadBandRed - entities: - - uid: 1295 - components: - - type: Transform - pos: 12.571781,39.694115 - parent: 588 -- proto: ClothingHeadHatFedoraBrown - entities: - - uid: 577 - components: - - type: Transform - pos: 12.686508,13.58602 - parent: 588 -- proto: ClothingHeadHatPwig - entities: - - uid: 369 - components: - - type: Transform - pos: 25.824945,3.5783403 - parent: 588 -- proto: ClothingHeadHatSecsoftFlipped - entities: - - uid: 606 - components: - - type: Transform - pos: 12.705482,6.671774 - parent: 588 - - uid: 1027 - components: - - type: Transform - pos: 18.403675,25.53719 - parent: 588 -- proto: ClothingHeadHatSurgcapPurple - entities: - - uid: 1711 - components: - - type: Transform - pos: 10.304593,44.632217 - parent: 588 -- proto: ClothingHeadHelmetRiot - entities: - - uid: 1617 - components: - - type: Transform - pos: 22.499683,6.7142525 - parent: 588 -- proto: ClothingHeadHelmetThunderdome - entities: - - uid: 1240 - components: - - type: Transform - pos: 34.666565,24.66942 - parent: 588 -- proto: ClothingNeckLawyerbadge - entities: - - uid: 326 - components: - - type: Transform - pos: 21.586027,4.583762 - parent: 588 -- proto: ClothingNeckTieDet - entities: - - uid: 573 - components: - - type: Transform - pos: 12.714905,13.486683 - parent: 588 -- proto: ClothingOuterArmorReflective - entities: - - uid: 1031 - components: - - type: Transform - pos: 18.47467,24.458666 - parent: 588 -- proto: ClothingOuterCoatDetective - entities: - - uid: 574 - components: - - type: Transform - pos: 13.396446,12.479115 - parent: 588 -- proto: ClothingOuterRobesJudge - entities: - - uid: 370 - components: - - type: Transform - pos: 27.40101,3.677678 - parent: 588 -- proto: ClothingShoesBootsCombatFilled - entities: - - uid: 1036 - components: - - type: Transform - pos: 12.582174,25.636528 - parent: 588 -- proto: ClothingShoesBootsLaceup - entities: - - uid: 372 - components: - - type: Transform - pos: 18.586912,0.70824456 - parent: 588 -- proto: ClothingUniformJumpskirtColorMaroon - entities: - - uid: 1714 - components: - - type: Transform - pos: 10.673761,44.53288 - parent: 588 -- proto: ClothingUniformJumpsuitColorMaroon - entities: - - uid: 1713 - components: - - type: Transform - pos: 10.645364,44.67479 - parent: 588 -- proto: ClusterBangFull - entities: - - uid: 599 - components: - - type: Transform - pos: 33.484257,28.42918 - parent: 588 -- proto: ComputerAlert - entities: - - uid: 999 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 17.5,30.5 - parent: 588 - - uid: 1001 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 23.5,30.5 - parent: 588 - - uid: 1561 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 21.5,46.5 - parent: 588 -- proto: computerBodyScanner - entities: - - uid: 1394 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 9.5,44.5 - parent: 588 - - uid: 1423 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 13.5,44.5 - parent: 588 -- proto: ComputerCriminalRecords - entities: - - uid: 461 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 11.5,0.5 - parent: 588 - - uid: 634 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 9.5,18.5 - parent: 588 -- proto: ComputerPowerMonitoring - entities: - - uid: 1000 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 16.5,30.5 - parent: 588 - - uid: 1002 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 24.5,30.5 - parent: 588 - - uid: 1560 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 17.5,46.5 - parent: 588 -- proto: ComputerSurveillanceCameraMonitor - entities: - - uid: 635 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 6.5,21.5 - parent: 588 -- proto: ComputerTelevision - entities: - - uid: 1229 - components: - - type: Transform - pos: 12.5,34.5 - parent: 588 - - uid: 1230 - components: - - type: Transform - pos: 22.5,36.5 - parent: 588 -- proto: CrateEngineeringGear - entities: - - uid: 1008 - components: - - type: Transform - pos: 26.5,30.5 - parent: 588 -- proto: CrateFunBoardGames - entities: - - uid: 1845 - components: - - type: Transform - pos: 26.5,48.5 - parent: 588 -- proto: CrateFunParty - entities: - - uid: 1876 - components: - - type: Transform - pos: 25.5,43.5 - parent: 588 -- proto: CrateHydroponicsSeedsExotic - entities: - - uid: 1660 - components: - - type: Transform - pos: 31.5,22.5 - parent: 588 -- proto: CrayonBox - entities: - - uid: 1057 - components: - - type: Transform - pos: 20.47107,24.608877 - parent: 588 - - uid: 1116 - components: - - type: Transform - pos: 20.607256,14.646415 - parent: 588 -- proto: CryoPod - entities: - - uid: 1395 - components: - - type: Transform - pos: 8.5,47.5 - parent: 588 - - uid: 1397 - components: - - type: Transform - pos: 14.5,47.5 - parent: 588 -- proto: DebugSMES - entities: - - uid: 971 - components: - - type: Transform - pos: 22.5,32.5 - parent: 588 - - uid: 974 - components: - - type: Transform - pos: 18.5,32.5 - parent: 588 -- proto: DeployableBarrier - entities: - - uid: 1233 - components: - - type: Transform - pos: 32.5,24.5 - parent: 588 -- proto: DiceBag - entities: - - uid: 552 - components: - - type: Transform - pos: 20.294882,15.426926 - parent: 588 -- proto: DiseaseDiagnoser - entities: - - uid: 1424 - components: - - type: Transform - pos: 14.5,44.5 - parent: 588 -- proto: DisposalUnit - entities: - - uid: 550 - components: - - type: Transform - pos: 22.5,16.5 - parent: 588 - - uid: 725 - components: - - type: Transform - pos: 21.5,22.5 - parent: 588 - - uid: 766 - components: - - type: Transform - pos: 9.5,22.5 - parent: 588 - - uid: 1288 - components: - - type: Transform - pos: 22.5,38.5 - parent: 588 -- proto: DonkpocketBoxSpawner - entities: - - uid: 526 - components: - - type: Transform - pos: 16.5,13.5 - parent: 588 - - uid: 723 - components: - - type: Transform - pos: 18.5,21.5 - parent: 588 -- proto: DoorElectronics - entities: - - uid: 659 - components: - - type: Transform - pos: 12.581519,21.410114 - parent: 588 - - uid: 1074 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 25.639427,25.54549 - parent: 588 -- proto: Dresser - entities: - - uid: 1051 - components: - - type: Transform - pos: 22.5,25.5 - parent: 588 - - uid: 1052 - components: - - type: Transform - pos: 20.5,27.5 - parent: 588 - - uid: 1061 - components: - - type: Transform - pos: 24.5,25.5 - parent: 588 - - uid: 1221 - components: - - type: Transform - pos: 21.5,36.5 - parent: 588 - - uid: 1222 - components: - - type: Transform - pos: 13.5,34.5 - parent: 588 -- proto: DrinkDetFlask - entities: - - uid: 1577 - components: - - type: Transform - pos: 12.606661,13.037249 - parent: 588 -- proto: DrinkMugMetal - entities: - - uid: 1294 - components: - - type: Transform - pos: 22.442232,12.514399 - parent: 588 -- proto: DrinkMugRed - entities: - - uid: 721 - components: - - type: Transform - pos: 22.448559,18.561966 - parent: 588 - - uid: 1293 - components: - - type: Transform - pos: 22.328642,12.741456 - parent: 588 -- proto: DrinkShinyFlask - entities: - - uid: 1874 - components: - - type: Transform - pos: 6.890398,22.663696 - parent: 588 -- proto: DrinkShotGlass - entities: - - uid: 578 - components: - - type: Transform - pos: 12.412022,12.535878 - parent: 588 - - uid: 579 - components: - - type: Transform - pos: 12.539811,12.748745 - parent: 588 -- proto: DrinkWaterCup - entities: - - uid: 722 - components: - - type: Transform - pos: 18.373508,18.661304 - parent: 588 - - uid: 762 - components: - - type: Transform - pos: 6.313587,19.590261 - parent: 588 - - uid: 763 - components: - - type: Transform - pos: 6.441377,19.419968 - parent: 588 -- proto: EmergencyLight - entities: - - uid: 1716 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 13.5,6.5 - parent: 588 - - type: PointLight - enabled: True - - uid: 1717 - components: - - type: Transform - pos: 21.5,10.5 - parent: 588 - - type: PointLight - enabled: True - - uid: 1718 - components: - - type: Transform - pos: 30.5,4.5 - parent: 588 - - type: PointLight - enabled: True - - uid: 1719 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 13.5,0.5 - parent: 588 - - type: PointLight - enabled: True - - uid: 1720 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 3.5,0.5 - parent: 588 - - type: PointLight - enabled: True - - uid: 1721 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 1.5,12.5 - parent: 588 - - type: PointLight - enabled: True - - uid: 1722 - components: - - type: Transform - pos: 18.5,16.5 - parent: 588 - - type: PointLight - enabled: True - - uid: 1723 - components: - - type: Transform - pos: 31.5,22.5 - parent: 588 - - type: PointLight - enabled: True - - uid: 1724 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 34.5,25.5 - parent: 588 - - type: PointLight - enabled: True - - uid: 1726 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 30.5,25.5 - parent: 588 - - type: PointLight - enabled: True - - uid: 1727 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 26.5,27.5 - parent: 588 - - type: PointLight - enabled: True - - uid: 1728 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 20.5,27.5 - parent: 588 - - type: PointLight - enabled: True - - uid: 1729 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 18.5,27.5 - parent: 588 - - type: PointLight - enabled: True - - uid: 1730 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 12.5,25.5 - parent: 588 - - type: PointLight - enabled: True - - uid: 1731 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 0.5,7.5 - parent: 588 - - type: PointLight - enabled: True - - uid: 1732 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 10.5,9.5 - parent: 588 - - type: PointLight - enabled: True - - uid: 1733 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 4.5,20.5 - parent: 588 - - type: PointLight - enabled: True - - uid: 1734 - components: - - type: Transform - pos: 1.5,24.5 - parent: 588 - - type: PointLight - enabled: True - - uid: 1735 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 1.5,30.5 - parent: 588 - - type: PointLight - enabled: True - - uid: 1736 - components: - - type: Transform - pos: 11.5,32.5 - parent: 588 - - type: PointLight - enabled: True - - uid: 1737 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 6.5,40.5 - parent: 588 - - type: PointLight - enabled: True - - uid: 1738 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 8.5,40.5 - parent: 588 - - type: PointLight - enabled: True - - uid: 1739 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 22.5,30.5 - parent: 588 - - type: PointLight - enabled: True - - uid: 1740 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 6.5,19.5 - parent: 588 - - type: PointLight - enabled: True - - uid: 1742 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 16.5,21.5 - parent: 588 - - type: PointLight - enabled: True - - uid: 1744 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 19.5,18.5 - parent: 588 - - type: PointLight - enabled: True - - uid: 1745 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 16.5,34.5 - parent: 588 - - type: PointLight - enabled: True - - uid: 1746 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 30.5,34.5 - parent: 588 - - type: PointLight - enabled: True - - uid: 1747 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 20.5,38.5 - parent: 588 - - type: PointLight - enabled: True - - uid: 1748 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 20.5,44.5 - parent: 588 - - type: PointLight - enabled: True - - uid: 1749 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 8.5,44.5 - parent: 588 - - type: PointLight - enabled: True - - uid: 1750 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 5.5,46.5 - parent: 588 - - type: PointLight - enabled: True - - uid: 1751 - components: - - type: Transform - pos: 30.5,6.5 - parent: 588 - - type: PointLight - enabled: True - - uid: 1752 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 28.5,10.5 - parent: 588 - - type: PointLight - enabled: True - - uid: 1832 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 30.5,46.5 - parent: 588 - - type: PointLight - enabled: True -- proto: EmergencyRollerBed - entities: - - uid: 1141 - components: - - type: Transform - pos: 30.5,25.5 - parent: 588 - - uid: 1142 - components: - - type: Transform - pos: 30.5,24.5 - parent: 588 -- proto: ExtinguisherCabinetFilled - entities: - - uid: 867 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 5.5,26.5 - parent: 588 - - uid: 1198 - components: - - type: Transform - pos: 2.5,8.5 - parent: 588 - - uid: 1199 - components: - - type: Transform - pos: 8.5,8.5 - parent: 588 - - uid: 1200 - components: - - type: Transform - pos: 8.5,35.5 - parent: 588 - - uid: 1201 - components: - - type: Transform - pos: 1.5,35.5 - parent: 588 - - uid: 1202 - components: - - type: Transform - pos: 25.5,14.5 - parent: 588 - - uid: 1328 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 28.5,39.5 - parent: 588 - - uid: 1566 - components: - - type: Transform - pos: 17.5,44.5 - parent: 588 -- proto: filingCabinetRandom - entities: - - uid: 320 - components: - - type: Transform - pos: 21.5,0.5 - parent: 588 - - uid: 321 - components: - - type: Transform - pos: 20.5,0.5 - parent: 588 -- proto: filingCabinetTallRandom - entities: - - uid: 1396 - components: - - type: Transform - pos: 8.5,44.5 - parent: 588 -- proto: Flash - entities: - - uid: 1209 - components: - - type: Transform - pos: 10.726851,19.047483 - parent: 588 -- proto: FlashlightSeclite - entities: - - uid: 374 - components: - - type: Transform - pos: 13.377204,0.54605544 - parent: 588 -- proto: FloodlightBroken - entities: - - uid: 1193 - components: - - type: Transform - pos: 9.462372,35.6454 - parent: 588 -- proto: FloorDrain - entities: - - uid: 944 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 8.5,28.5 - parent: 588 - - type: Fixtures - fixtures: {} - - uid: 945 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 10.5,28.5 - parent: 588 - - type: Fixtures - fixtures: {} -- proto: FloorLavaEntity - entities: - - uid: 47 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 5.5,8.5 - parent: 588 - - uid: 49 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 6.5,8.5 - parent: 588 - - uid: 458 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 5.5,10.5 - parent: 588 - - uid: 459 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 4.5,8.5 - parent: 588 - - uid: 460 - components: - - type: Transform - pos: 8.5,3.5 - parent: 588 - - uid: 645 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 2.5,20.5 - parent: 588 - - uid: 820 - components: - - type: Transform - pos: 4.5,32.5 - parent: 588 - - uid: 821 - components: - - type: Transform - pos: 4.5,31.5 - parent: 588 - - uid: 822 - components: - - type: Transform - pos: 5.5,31.5 - parent: 588 - - uid: 823 - components: - - type: Transform - pos: 5.5,32.5 - parent: 588 - - uid: 824 - components: - - type: Transform - pos: 5.5,30.5 - parent: 588 - - uid: 825 - components: - - type: Transform - pos: 6.5,30.5 - parent: 588 - - uid: 826 - components: - - type: Transform - pos: 7.5,30.5 - parent: 588 - - uid: 827 - components: - - type: Transform - pos: 6.5,32.5 - parent: 588 - - uid: 828 - components: - - type: Transform - pos: 7.5,32.5 - parent: 588 - - uid: 829 - components: - - type: Transform - pos: 7.5,31.5 - parent: 588 - - uid: 830 - components: - - type: Transform - pos: 6.5,31.5 - parent: 588 - - uid: 831 - components: - - type: Transform - pos: 8.5,30.5 - parent: 588 - - uid: 857 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 2.5,39.5 - parent: 588 - - uid: 858 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 3.5,39.5 - parent: 588 - - uid: 859 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 3.5,38.5 - parent: 588 - - uid: 860 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 3.5,40.5 - parent: 588 - - uid: 887 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 4.5,40.5 - parent: 588 - - uid: 889 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 4.5,39.5 - parent: 588 - - uid: 906 - components: - - type: Transform - pos: 4.5,7.5 - parent: 588 - - uid: 907 - components: - - type: Transform - pos: 5.5,7.5 - parent: 588 - - uid: 908 - components: - - type: Transform - pos: 5.5,9.5 - parent: 588 - - uid: 909 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 5.5,6.5 - parent: 588 - - uid: 910 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 4.5,6.5 - parent: 588 - - uid: 911 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 6.5,9.5 - parent: 588 - - uid: 913 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 6.5,10.5 - parent: 588 - - uid: 1247 - components: - - type: Transform - pos: 8.5,4.5 - parent: 588 - - uid: 1248 - components: - - type: Transform - pos: 9.5,4.5 - parent: 588 - - uid: 1249 - components: - - type: Transform - pos: 8.5,2.5 - parent: 588 - - uid: 1250 - components: - - type: Transform - pos: 8.5,1.5 - parent: 588 - - uid: 1251 - components: - - type: Transform - pos: 9.5,1.5 - parent: 588 - - uid: 1252 - components: - - type: Transform - pos: 8.5,1.5 - parent: 588 - - uid: 1253 - components: - - type: Transform - pos: 8.5,0.5 - parent: 588 - - uid: 1254 - components: - - type: Transform - pos: 7.5,0.5 - parent: 588 - - uid: 1333 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 2.5,46.5 - parent: 588 - - uid: 1341 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 3.5,45.5 - parent: 588 - - uid: 1343 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 3.5,46.5 - parent: 588 - - uid: 1345 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 1.5,46.5 - parent: 588 - - uid: 1359 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 1.5,47.5 - parent: 588 - - uid: 1360 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 4.5,46.5 - parent: 588 - - uid: 1361 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 4.5,47.5 - parent: 588 - - uid: 1362 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 5.5,47.5 - parent: 588 - - uid: 1363 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 2.5,44.5 - parent: 588 - - uid: 1364 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 2.5,47.5 - parent: 588 - - uid: 1365 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 3.5,44.5 - parent: 588 - - uid: 1366 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 4.5,45.5 - parent: 588 - - uid: 1367 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 4.5,44.5 - parent: 588 - - uid: 1368 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 4.5,43.5 - parent: 588 - - uid: 1369 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 5.5,43.5 - parent: 588 - - uid: 1370 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 5.5,42.5 - parent: 588 - - uid: 1371 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 6.5,42.5 - parent: 588 - - uid: 1372 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 6.5,43.5 - parent: 588 - - uid: 1373 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 5.5,44.5 - parent: 588 - - uid: 1374 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 5.5,46.5 - parent: 588 - - uid: 1375 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 2.5,43.5 - parent: 588 -- proto: FoodBowlBigTrash - entities: - - uid: 1840 - components: - - type: Transform - pos: 30.547388,48.16116 - parent: 588 -- proto: FoodBurgerMime - entities: - - uid: 399 - components: - - type: Transform - pos: 10.958169,39.64943 - parent: 588 -- proto: FoodPlateSmallPlastic - entities: - - uid: 529 - components: - - type: Transform - pos: 17.462528,12.615073 - parent: 588 -- proto: FoodPlateTrash - entities: - - uid: 1692 - components: - - type: Transform - pos: 28.80027,47.44947 - parent: 588 -- proto: ForensicPad - entities: - - uid: 761 - components: - - type: Transform - pos: 7.562898,22.48225 - parent: 588 -- proto: ForkPlastic - entities: - - uid: 531 - components: - - type: Transform - pos: 17.405733,12.600882 - parent: 588 -- proto: GasFilter - entities: - - uid: 1415 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 12.5,47.5 - parent: 588 -- proto: GasPipeBend - entities: - - uid: 1412 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 8.5,46.5 - parent: 588 - - uid: 1414 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 14.5,46.5 - parent: 588 - - uid: 1416 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 11.5,47.5 - parent: 588 - - uid: 1421 - components: - - type: Transform - pos: 13.5,47.5 - parent: 588 -- proto: GasPipeStraight - entities: - - uid: 1418 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 10.5,47.5 - parent: 588 - - uid: 1420 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 12.5,46.5 - parent: 588 -- proto: GasPipeTJunction - entities: - - uid: 1410 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 9.5,46.5 - parent: 588 - - uid: 1411 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 13.5,46.5 - parent: 588 - - uid: 1417 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 10.5,46.5 - parent: 588 - - uid: 1419 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 11.5,46.5 - parent: 588 -- proto: GasPort - entities: - - uid: 1404 - components: - - type: Transform - pos: 9.5,48.5 - parent: 588 - - uid: 1422 - components: - - type: Transform - pos: 12.5,48.5 - parent: 588 -- proto: GasPressurePump - entities: - - uid: 1409 - components: - - type: Transform - pos: 9.5,47.5 - parent: 588 -- proto: GasThermoMachineFreezer - entities: - - uid: 1403 - components: - - type: Transform - pos: 10.5,48.5 - parent: 588 -- proto: GatfruitSeeds - entities: - - uid: 562 - components: - - type: Transform - pos: 8.528373,27.49547 - parent: 588 -- proto: Gauze - entities: - - uid: 1482 - components: - - type: Transform - pos: 8.452288,42.514927 - parent: 588 -- proto: GeneratorRTG - entities: - - uid: 742 - components: - - type: Transform - pos: 27.5,21.5 - parent: 588 - - uid: 748 - components: - - type: Transform - pos: 25.5,21.5 - parent: 588 -- proto: Girder - entities: - - uid: 1301 - components: - - type: Transform - pos: 26.5,39.5 - parent: 588 -- proto: Grille - entities: - - uid: 209 - components: - - type: Transform - pos: 15.5,34.5 - parent: 588 - - uid: 211 - components: - - type: Transform - pos: 19.5,36.5 - parent: 588 - - uid: 212 - components: - - type: Transform - pos: 19.5,34.5 - parent: 588 - - uid: 213 - components: - - type: Transform - pos: 15.5,36.5 - parent: 588 - - uid: 403 - components: - - type: Transform - pos: 15.5,9.5 - parent: 588 - - uid: 404 - components: - - type: Transform - pos: 15.5,7.5 - parent: 588 - - uid: 407 - components: - - type: Transform - pos: 19.5,9.5 - parent: 588 - - uid: 408 - components: - - type: Transform - pos: 19.5,7.5 - parent: 588 - - uid: 568 - components: - - type: Transform - pos: 12.5,15.5 - parent: 588 - - uid: 584 - components: - - type: Transform - pos: 2.5,12.5 - parent: 588 - - uid: 586 - components: - - type: Transform - pos: 2.5,16.5 - parent: 588 - - uid: 587 - components: - - type: Transform - pos: 4.5,16.5 - parent: 588 - - uid: 589 - components: - - type: Transform - pos: 4.5,12.5 - parent: 588 - - uid: 1465 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 10.5,43.5 - parent: 588 - - uid: 1466 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 12.5,43.5 - parent: 588 -- proto: GrilleBroken - entities: - - uid: 1302 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 27.5,39.5 - parent: 588 -- proto: Handcuffs - entities: - - uid: 1614 - components: - - type: Transform - pos: 22.608034,10.659381 - parent: 588 -- proto: Hemostat - entities: - - uid: 1471 - components: - - type: Transform - pos: 8.51377,43.004257 - parent: 588 -- proto: HighSecArmoryLocked - entities: - - uid: 1597 - components: - - type: Transform - pos: 26.5,7.5 - parent: 588 - - uid: 1598 - components: - - type: Transform - pos: 32.5,7.5 - parent: 588 - - uid: 1599 - components: - - type: Transform - pos: 29.5,9.5 - parent: 588 -- proto: HospitalCurtains - entities: - - uid: 402 - components: - - type: Transform - pos: 8.5,27.5 - parent: 588 - - uid: 949 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 8.5,24.5 - parent: 588 - - uid: 951 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 10.5,27.5 - parent: 588 - - uid: 1768 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 24.5,48.5 - parent: 588 -- proto: HospitalCurtainsOpen - entities: - - uid: 946 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 8.5,25.5 - parent: 588 - - uid: 947 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 10.5,24.5 - parent: 588 - - uid: 1040 - components: - - type: Transform - pos: 20.5,28.5 - parent: 588 - - uid: 1046 - components: - - type: Transform - pos: 22.5,24.5 - parent: 588 - - uid: 1148 - components: - - type: Transform - pos: 28.5,25.5 - parent: 588 - - uid: 1149 - components: - - type: Transform - pos: 28.5,24.5 - parent: 588 - - uid: 1223 - components: - - type: Transform - pos: 20.5,36.5 - parent: 588 - - uid: 1224 - components: - - type: Transform - pos: 14.5,34.5 - parent: 588 - - uid: 1467 - components: - - type: Transform - pos: 12.5,42.5 - parent: 588 - - uid: 1469 - components: - - type: Transform - pos: 10.5,42.5 - parent: 588 - - uid: 1767 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 24.5,42.5 - parent: 588 -- proto: HydroponicsToolHatchet - entities: - - uid: 1844 - components: - - type: Transform - pos: 29.538284,44.04174 - parent: 588 - - uid: 1851 - components: - - type: Transform - pos: 30.630798,21.602604 - parent: 588 -- proto: HydroponicsToolMiniHoe - entities: - - uid: 1837 - components: - - type: Transform - pos: 30.099596,43.446724 - parent: 588 - - uid: 1841 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 33.38517,20.601 - parent: 588 -- proto: HydroponicsToolSpade - entities: - - uid: 1838 - components: - - type: Transform - pos: 29.95761,43.361576 - parent: 588 - - uid: 1842 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 33.42777,20.58681 - parent: 588 -- proto: hydroponicsTray - entities: - - uid: 796 - components: - - type: Transform - pos: 31.5,21.5 - parent: 588 - - uid: 797 - components: - - type: Transform - pos: 31.5,20.5 - parent: 588 - - uid: 798 - components: - - type: Transform - pos: 31.5,19.5 - parent: 588 - - uid: 1772 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 29.5,42.5 - parent: 588 - - uid: 1774 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 29.5,44.5 - parent: 588 - - uid: 1787 - components: - - type: Transform - pos: 30.5,44.5 - parent: 588 - - uid: 1788 - components: - - type: Transform - pos: 30.5,42.5 - parent: 588 -- proto: IngotGold - entities: - - uid: 952 - components: - - type: Transform - pos: 11.069347,39.504154 - parent: 588 -- proto: KitchenMicrowave - entities: - - uid: 524 - components: - - type: Transform - pos: 16.5,12.5 - parent: 588 - - uid: 709 - components: - - type: Transform - pos: 18.5,22.5 - parent: 588 - - uid: 1785 - components: - - type: Transform - pos: 29.5,48.5 - parent: 588 -- proto: KitchenReagentGrinder - entities: - - uid: 1786 - components: - - type: Transform - pos: 30.5,47.5 - parent: 588 -- proto: KnifePlastic - entities: - - uid: 530 - components: - - type: Transform - pos: 17.249546,12.643455 - parent: 588 - - uid: 1836 - components: - - type: Transform - pos: 30.241585,48.271698 - parent: 588 -- proto: Lamp - entities: - - uid: 581 - components: - - type: Transform - pos: 12.369425,13.798887 - parent: 588 -- proto: LampGold - entities: - - uid: 322 - components: - - type: Transform - pos: 18.419699,1.6320114 - parent: 588 - - uid: 323 - components: - - type: Transform - pos: 20.563715,4.8959665 - parent: 588 - - uid: 729 - components: - - type: Transform - pos: 6.4779434,22.892899 - parent: 588 - - uid: 730 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 10.765976,19.912766 - parent: 588 -- proto: Lighter - entities: - - uid: 1220 - components: - - type: Transform - pos: 7.5287867,36.397644 - parent: 588 -- proto: LockerDetective - entities: - - uid: 560 - components: - - type: Transform - pos: 14.5,16.5 - parent: 588 -- proto: LockerEvidence - entities: - - uid: 254 - components: - - type: Transform - pos: 16.5,0.5 - parent: 588 - - uid: 262 - components: - - type: Transform - pos: 2.5,0.5 - parent: 588 - - uid: 263 - components: - - type: Transform - pos: 4.5,0.5 - parent: 588 - - uid: 276 - components: - - type: Transform - pos: 0.5,0.5 - parent: 588 - - uid: 286 - components: - - type: Transform - pos: 12.5,0.5 - parent: 588 - - uid: 287 - components: - - type: Transform - pos: 14.5,0.5 - parent: 588 - - uid: 704 - components: - - type: Transform - pos: 16.5,22.5 - parent: 588 -- proto: LockerMedicineFilled - entities: - - uid: 1152 - components: - - type: Transform - pos: 28.5,27.5 - parent: 588 -- proto: LockerSecurityFilled - entities: - - uid: 416 - components: - - type: Transform - pos: 20.5,6.5 - parent: 588 -- proto: LockerSyndicatePersonal - entities: - - uid: 605 - components: - - type: Transform - pos: 6.5,12.5 - parent: 588 -- proto: MachineFrame - entities: - - uid: 400 - components: - - type: Transform - pos: 26.5,8.5 - parent: 588 -- proto: MaintenanceFluffSpawner - entities: - - uid: 414 - components: - - type: Transform - pos: 25.5,32.5 - parent: 588 - - uid: 1289 - components: - - type: Transform - pos: 17.5,38.5 - parent: 588 - - uid: 1290 - components: - - type: Transform - pos: 21.5,40.5 - parent: 588 - - uid: 1291 - components: - - type: Transform - pos: 20.5,40.5 - parent: 588 -- proto: MaintenanceWeaponSpawner - entities: - - uid: 548 - components: - - type: Transform - pos: 15.5,4.5 - parent: 588 - - uid: 549 - components: - - type: Transform - pos: 3.5,4.5 - parent: 588 - - uid: 1580 - components: - - type: Transform - pos: 1.5,8.5 - parent: 588 - - uid: 1581 - components: - - type: Transform - pos: 9.5,8.5 - parent: 588 -- proto: MaterialCloth1 - entities: - - uid: 702 - components: - - type: Transform - pos: 12.462601,18.586084 - parent: 588 - - uid: 1065 - components: - - type: Transform - pos: 24.460928,24.594687 - parent: 588 - - uid: 1066 - components: - - type: Transform - pos: 24.389935,24.296673 - parent: 588 -- proto: MaterialWoodPlank1 - entities: - - uid: 703 - components: - - type: Transform - pos: 12.817572,18.685423 - parent: 588 - - uid: 1064 - components: - - type: Transform - pos: 26.278374,24.608877 - parent: 588 - - uid: 1067 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 24.801699,24.708214 - parent: 588 - - uid: 1076 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 24.823982,27.574818 - parent: 588 -- proto: MedicalBed - entities: - - uid: 1146 - components: - - type: Transform - pos: 28.5,24.5 - parent: 588 - - uid: 1147 - components: - - type: Transform - pos: 28.5,25.5 - parent: 588 -- proto: MedkitAdvancedFilled - entities: - - uid: 1153 - components: - - type: Transform - pos: 30.614443,28.392822 - parent: 588 -- proto: MedkitCombatFilled - entities: - - uid: 1154 - components: - - type: Transform - pos: 30.40146,28.066427 - parent: 588 -- proto: OperatingTable - entities: - - uid: 1389 - components: - - type: Transform - pos: 9.5,43.5 - parent: 588 -- proto: Paper - entities: - - uid: 1055 - components: - - type: Transform - pos: 20.428474,24.722406 - parent: 588 - - uid: 1056 - components: - - type: Transform - pos: 20.669853,24.52373 - parent: 588 -- proto: PaperOffice - entities: - - uid: 327 - components: - - type: Transform - pos: 21.415642,4.0728827 - parent: 588 - - uid: 328 - components: - - type: Transform - pos: 21.586027,4.0019264 - parent: 588 - - uid: 1113 - components: - - type: Transform - pos: 20.706646,15.341779 - parent: 588 - - uid: 1114 - components: - - type: Transform - pos: 20.465267,15.185677 - parent: 588 - - uid: 1115 - components: - - type: Transform - pos: 20.30908,14.603841 - parent: 588 -- proto: PartRodMetal1 - entities: - - uid: 1071 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 25.341253,26.595633 - parent: 588 - - uid: 1072 - components: - - type: Transform - pos: 25.36965,28.11408 - parent: 588 -- proto: Pen - entities: - - uid: 366 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 21.352327,3.9473093 - parent: 588 - - uid: 367 - components: - - type: Transform - pos: 18.75395,1.1232786 - parent: 588 - - uid: 368 - components: - - type: Transform - pos: 24.788435,1.4496742 - parent: 588 - - uid: 731 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 10.36841,19.019064 - parent: 588 - - uid: 732 - components: - - type: Transform - pos: 7.4631767,22.637186 - parent: 588 -- proto: PhoneInstrument - entities: - - uid: 371 - components: - - type: Transform - pos: 25.484175,4.4865713 - parent: 588 -- proto: PillCanister - entities: - - uid: 1481 - components: - - type: Transform - pos: 14.438607,42.637726 - parent: 588 -- proto: PillSpaceDrugs - entities: - - uid: 1479 - components: - - type: Transform - pos: 14.438607,42.96412 - parent: 588 - - uid: 1480 - components: - - type: Transform - pos: 14.537998,42.878975 - parent: 588 -- proto: PlushieNuke - entities: - - uid: 1850 - components: - - type: Transform - pos: 22.519993,28.594225 - parent: 588 -- proto: PortableFlasher - entities: - - uid: 1234 - components: - - type: Transform - pos: 32.5,25.5 - parent: 588 -- proto: PortableGeneratorPacman - entities: - - uid: 967 - components: - - type: Transform - pos: 16.5,32.5 - parent: 588 - - uid: 969 - components: - - type: Transform - pos: 24.5,32.5 - parent: 588 -- proto: PortableGeneratorSuperPacman - entities: - - uid: 50 - components: - - type: Transform - pos: 26.5,15.5 - parent: 588 - - uid: 55 - components: - - type: Transform - pos: 28.5,15.5 - parent: 588 - - uid: 1504 - components: - - type: Transform - pos: 18.5,44.5 - parent: 588 - - uid: 1505 - components: - - type: Transform - pos: 20.5,44.5 - parent: 588 -- proto: PortableScrubber - entities: - - uid: 1101 - components: - - type: Transform - pos: 18.5,30.5 - parent: 588 -- proto: PosterContrabandBountyHunters - entities: - - uid: 1578 - components: - - type: Transform - pos: 13.5,15.5 - parent: 588 -- proto: PosterLegitDickGumshue - entities: - - uid: 1576 - components: - - type: Transform - pos: 9.5,15.5 - parent: 588 -- proto: PosterLegitEnlist - entities: - - uid: 1800 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 8.5,9.5 - parent: 588 -- proto: PosterLegitNanotrasenLogo - entities: - - uid: 1802 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 2.5,48.5 - parent: 588 - - uid: 1803 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 3.5,21.5 - parent: 588 -- proto: PosterLegitObey - entities: - - uid: 1801 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 4.5,42.5 - parent: 588 -- proto: PosterLegitSecWatch - entities: - - uid: 1799 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 2.5,7.5 - parent: 588 -- proto: PosterLegitSpaceCops - entities: - - uid: 1804 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 1.5,19.5 - parent: 588 -- proto: PottedPlantRandom - entities: - - uid: 1286 - components: - - type: Transform - pos: 16.5,38.5 - parent: 588 - - uid: 1287 - components: - - type: Transform - pos: 22.5,40.5 - parent: 588 -- proto: PowerCellHyper - entities: - - uid: 469 - components: - - type: Transform - pos: 12.355853,25.41643 - parent: 588 - - uid: 590 - components: - - type: Transform - pos: 5.381793,16.642464 - parent: 588 -- proto: PowerCellRecharger - entities: - - uid: 1103 - components: - - type: Transform - pos: 15.5,30.5 - parent: 588 - - uid: 1568 - components: - - type: Transform - pos: 21.5,47.5 - parent: 588 -- proto: Poweredlight - entities: - - uid: 1641 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 2.5,0.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1642 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 14.5,0.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1646 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 24.5,0.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1647 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 28.5,0.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1648 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 21.5,6.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1649 - components: - - type: Transform - pos: 13.5,10.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1650 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 16.5,6.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1651 - components: - - type: Transform - pos: 18.5,10.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1693 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 32.5,25.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1701 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 18.5,12.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1702 - components: - - type: Transform - pos: 20.5,16.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1703 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 34.5,20.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1704 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 16.5,25.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1705 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 14.5,27.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1706 - components: - - type: Transform - pos: 18.5,40.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1741 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 10.5,21.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1743 - components: - - type: Transform - pos: 21.5,22.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1830 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 30.5,43.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1831 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 30.5,47.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 -- proto: PoweredlightLED - entities: - - uid: 1707 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 8.5,42.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1708 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 14.5,42.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1709 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 14.5,46.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1710 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 8.5,46.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1725 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 28.5,27.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 -- proto: PoweredSmallLight - entities: - - uid: 470 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 26.5,14.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 940 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 10.5,28.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 948 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 8.5,28.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 953 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 10.5,25.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1603 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 26.5,10.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1604 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 32.5,10.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1605 - components: - - type: Transform - pos: 29.5,6.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1606 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 29.5,8.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1607 - components: - - type: Transform - pos: 32.5,8.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1608 - components: - - type: Transform - pos: 26.5,8.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1643 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 7.5,1.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1644 - components: - - type: Transform - pos: 20.5,4.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1645 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 19.5,0.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1652 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 9.5,8.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1653 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 7.5,8.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1654 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 3.5,8.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1655 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 1.5,8.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1656 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 5.5,14.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1657 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 1.5,14.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1659 - components: - - type: Transform - pos: 3.5,18.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1661 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 1.5,22.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1662 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 4.5,27.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1663 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 6.5,25.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1664 - components: - - type: Transform - pos: 0.5,28.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1665 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 2.5,24.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1666 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 3.5,30.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1667 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 9.5,30.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1668 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 1.5,36.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1669 - components: - - type: Transform - pos: 8.5,34.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1670 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 5.5,38.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1671 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 1.5,38.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1672 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 2.5,43.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1673 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 4.5,43.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1674 - components: - - type: Transform - pos: 2.5,47.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1675 - components: - - type: Transform - pos: 4.5,47.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1676 - components: - - type: Transform - pos: 9.5,40.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1677 - components: - - type: Transform - pos: 13.5,40.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1678 - components: - - type: Transform - pos: 13.5,36.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1679 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 21.5,34.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1680 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 32.5,35.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1681 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 34.5,35.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1682 - components: - - type: Transform - pos: 25.5,36.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1683 - components: - - type: Transform - pos: 28.5,38.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1684 - components: - - type: Transform - pos: 19.5,46.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1685 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 17.5,47.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1686 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 21.5,47.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1687 - components: - - type: Transform - pos: 23.5,32.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1688 - components: - - type: Transform - pos: 17.5,32.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1689 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 22.5,27.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1690 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 20.5,25.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1694 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 28.5,19.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1695 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 24.5,19.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1696 - components: - - type: Transform - pos: 13.5,22.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1697 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 13.5,18.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1698 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 15.5,18.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1699 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 9.5,16.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1700 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 10.5,13.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1828 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 25.5,42.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1829 - components: - - type: Transform - pos: 25.5,48.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 -- proto: PoweredSmallLightEmpty - entities: - - uid: 1640 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 26.5,25.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 1658 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 24.5,27.5 - parent: 588 - - type: ApcPowerReceiver - powerLoad: 0 -- proto: Rack - entities: - - uid: 255 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 15.5,0.5 - parent: 588 - - uid: 264 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 3.5,0.5 - parent: 588 - - uid: 283 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 1.5,0.5 - parent: 588 - - uid: 285 - components: - - type: Transform - pos: 13.5,0.5 - parent: 588 - - uid: 324 - components: - - type: Transform - pos: 19.5,4.5 - parent: 588 - - uid: 396 - components: - - type: Transform - pos: 28.5,8.5 - parent: 588 - - uid: 401 - components: - - type: Transform - pos: 32.5,8.5 - parent: 588 - - uid: 417 - components: - - type: Transform - pos: 12.5,6.5 - parent: 588 - - uid: 418 - components: - - type: Transform - pos: 12.5,10.5 - parent: 588 - - uid: 419 - components: - - type: Transform - pos: 22.5,10.5 - parent: 588 - - uid: 420 - components: - - type: Transform - pos: 22.5,6.5 - parent: 588 - - uid: 478 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 27.5,15.5 - parent: 588 - - uid: 551 - components: - - type: Transform - pos: 22.5,15.5 - parent: 588 - - uid: 585 - components: - - type: Transform - pos: 1.5,12.5 - parent: 588 - - uid: 596 - components: - - type: Transform - pos: 1.5,16.5 - parent: 588 - - uid: 597 - components: - - type: Transform - pos: 5.5,16.5 - parent: 588 - - uid: 598 - components: - - type: Transform - pos: 5.5,12.5 - parent: 588 - - uid: 966 - components: - - type: Transform - pos: 25.5,32.5 - parent: 588 - - uid: 977 - components: - - type: Transform - pos: 15.5,32.5 - parent: 588 - - uid: 1015 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 12.5,25.5 - parent: 588 - - uid: 1016 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 12.5,24.5 - parent: 588 - - uid: 1021 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 18.5,25.5 - parent: 588 - - uid: 1024 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 18.5,24.5 - parent: 588 - - uid: 1111 - components: - - type: Transform - pos: 14.5,32.5 - parent: 588 - - uid: 1112 - components: - - type: Transform - pos: 26.5,32.5 - parent: 588 - - uid: 1206 - components: - - type: Transform - pos: 1.5,8.5 - parent: 588 - - uid: 1208 - components: - - type: Transform - pos: 9.5,8.5 - parent: 588 - - uid: 1211 - components: - - type: Transform - pos: 2.5,34.5 - parent: 588 - - uid: 1319 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 29.5,40.5 - parent: 588 - - uid: 1562 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 17.5,47.5 - parent: 588 - - uid: 1858 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 26.5,21.5 - parent: 588 -- proto: Railing - entities: - - uid: 313 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 30.5,4.5 - parent: 588 - - uid: 314 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 30.5,0.5 - parent: 588 - - uid: 427 - components: - - type: Transform - pos: 6.5,7.5 - parent: 588 - - uid: 428 - components: - - type: Transform - pos: 4.5,7.5 - parent: 588 - - uid: 429 - components: - - type: Transform - pos: 3.5,7.5 - parent: 588 - - uid: 430 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 7.5,9.5 - parent: 588 - - uid: 431 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 6.5,9.5 - parent: 588 - - uid: 435 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 5.5,9.5 - parent: 588 - - uid: 436 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 4.5,9.5 - parent: 588 - - uid: 437 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 3.5,9.5 - parent: 588 - - uid: 439 - components: - - type: Transform - pos: 5.5,7.5 - parent: 588 - - uid: 440 - components: - - type: Transform - pos: 7.5,7.5 - parent: 588 - - uid: 770 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 26.5,21.5 - parent: 588 - - uid: 850 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 9.5,30.5 - parent: 588 - - uid: 851 - components: - - type: Transform - pos: 9.5,32.5 - parent: 588 - - uid: 854 - components: - - type: Transform - pos: 3.5,32.5 - parent: 588 - - uid: 855 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 3.5,30.5 - parent: 588 - - uid: 1259 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 7.5,4.5 - parent: 588 - - uid: 1260 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 9.5,4.5 - parent: 588 - - uid: 1261 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 9.5,0.5 - parent: 588 - - uid: 1262 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 7.5,0.5 - parent: 588 -- proto: RailingCorner - entities: - - uid: 315 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 30.5,1.5 - parent: 588 - - uid: 316 - components: - - type: Transform - pos: 30.5,3.5 - parent: 588 - - uid: 771 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 27.5,21.5 - parent: 588 - - uid: 772 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 25.5,21.5 - parent: 588 - - uid: 845 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 2.5,32.5 - parent: 588 - - uid: 846 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 2.5,30.5 - parent: 588 - - uid: 847 - components: - - type: Transform - pos: 10.5,32.5 - parent: 588 - - uid: 848 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 10.5,30.5 - parent: 588 - - uid: 849 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 8.5,30.5 - parent: 588 - - uid: 852 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 8.5,32.5 - parent: 588 - - uid: 853 - components: - - type: Transform - pos: 4.5,32.5 - parent: 588 - - uid: 856 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 4.5,30.5 - parent: 588 - - uid: 886 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 4.5,40.5 - parent: 588 - - uid: 888 - components: - - type: Transform - pos: 2.5,40.5 - parent: 588 - - uid: 890 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 1.5,40.5 - parent: 588 - - uid: 891 - components: - - type: Transform - pos: 5.5,40.5 - parent: 588 - - uid: 892 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 4.5,38.5 - parent: 588 - - uid: 893 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 5.5,38.5 - parent: 588 - - uid: 894 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 2.5,38.5 - parent: 588 - - uid: 895 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 1.5,38.5 - parent: 588 - - uid: 1255 - components: - - type: Transform - pos: 7.5,3.5 - parent: 588 - - uid: 1256 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 9.5,3.5 - parent: 588 - - uid: 1257 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 9.5,1.5 - parent: 588 - - uid: 1258 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 7.5,1.5 - parent: 588 - - uid: 1351 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 1.5,44.5 - parent: 588 - - uid: 1352 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 2.5,43.5 - parent: 588 - - uid: 1353 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 4.5,43.5 - parent: 588 - - uid: 1354 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 5.5,44.5 - parent: 588 - - uid: 1355 - components: - - type: Transform - pos: 1.5,46.5 - parent: 588 - - uid: 1356 - components: - - type: Transform - pos: 2.5,47.5 - parent: 588 - - uid: 1357 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 4.5,47.5 - parent: 588 - - uid: 1358 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 5.5,46.5 - parent: 588 -- proto: RailingCornerSmall - entities: - - uid: 337 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 29.5,3.5 - parent: 588 - - uid: 338 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 29.5,1.5 - parent: 588 - - uid: 1376 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 1.5,43.5 - parent: 588 - - uid: 1377 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 1.5,47.5 - parent: 588 - - uid: 1378 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 5.5,47.5 - parent: 588 - - uid: 1379 - components: - - type: Transform - pos: 5.5,43.5 - parent: 588 -- proto: RandomDrinkBottle - entities: - - uid: 580 - components: - - type: Transform - pos: 12.5,12.5 - parent: 588 -- proto: RandomFoodSingle - entities: - - uid: 764 - components: - - type: Transform - pos: 6.5,19.5 - parent: 588 -- proto: RandomInstruments - entities: - - uid: 546 - components: - - type: Transform - pos: 1.5,4.5 - parent: 588 - - uid: 1159 - components: - - type: Transform - pos: 21.5,18.5 - parent: 588 - - uid: 1833 - components: - - type: Transform - pos: 24.5,42.5 - parent: 588 -- proto: RandomPosterContraband - entities: - - uid: 1571 - components: - - type: Transform - pos: 25.5,39.5 - parent: 588 - - uid: 1572 - components: - - type: Transform - pos: 3.5,35.5 - parent: 588 - - uid: 1573 - components: - - type: Transform - pos: 7.5,35.5 - parent: 588 - - uid: 1574 - components: - - type: Transform - pos: 5.5,25.5 - parent: 588 - - uid: 1575 - components: - - type: Transform - pos: 30.5,15.5 - parent: 588 - - uid: 1805 - components: - - type: Transform - pos: 18.5,43.5 - parent: 588 - - uid: 1806 - components: - - type: Transform - pos: 20.5,47.5 - parent: 588 -- proto: RandomSoap - entities: - - uid: 397 - components: - - type: Transform - pos: 8.5,24.5 - parent: 588 -- proto: RandomVending - entities: - - uid: 539 - components: - - type: Transform - pos: 17.5,16.5 - parent: 588 -- proto: ReinforcedWindow - entities: - - uid: 214 - components: - - type: Transform - pos: 19.5,34.5 - parent: 588 - - uid: 409 - components: - - type: Transform - pos: 15.5,7.5 - parent: 588 - - uid: 410 - components: - - type: Transform - pos: 15.5,9.5 - parent: 588 - - uid: 411 - components: - - type: Transform - pos: 19.5,7.5 - parent: 588 - - uid: 412 - components: - - type: Transform - pos: 19.5,9.5 - parent: 588 - - uid: 591 - components: - - type: Transform - pos: 2.5,12.5 - parent: 588 - - uid: 592 - components: - - type: Transform - pos: 4.5,12.5 - parent: 588 - - uid: 594 - components: - - type: Transform - pos: 4.5,16.5 - parent: 588 - - uid: 595 - components: - - type: Transform - pos: 2.5,16.5 - parent: 588 - - uid: 1166 - components: - - type: Transform - pos: 19.5,36.5 - parent: 588 - - uid: 1168 - components: - - type: Transform - pos: 15.5,36.5 - parent: 588 - - uid: 1169 - components: - - type: Transform - pos: 15.5,34.5 - parent: 588 -- proto: RemoteSignaller - entities: - - uid: 593 - components: - - type: Transform - pos: 34.361877,24.623777 - parent: 588 - - type: DeviceLinkSource - linkedPorts: - 1238: - - Pressed: Toggle - 1239: - - Pressed: Toggle - 1237: - - Pressed: Toggle - - uid: 1104 - components: - - type: Transform - pos: 25.24476,30.698978 - parent: 588 - - uid: 1105 - components: - - type: Transform - pos: 25.443544,30.613832 - parent: 588 - - uid: 1106 - components: - - type: Transform - pos: 5.677143,16.762346 - parent: 588 -- proto: RiotLaserShield - entities: - - uid: 1618 - components: - - type: Transform - pos: 12.616156,10.534842 - parent: 588 -- proto: RiotShield - entities: - - uid: 600 - components: - - type: Transform - pos: 1.3209407,16.656654 - parent: 588 - - uid: 601 - components: - - type: Transform - pos: 1.5481212,16.543125 - parent: 588 -- proto: SalvageCanisterSpawner - entities: - - uid: 998 - components: - - type: Transform - pos: 22.5,30.5 - parent: 588 - - uid: 1009 - components: - - type: Transform - pos: 19.5,30.5 - parent: 588 - - uid: 1025 - components: - - type: Transform - pos: 21.5,30.5 - parent: 588 - - uid: 1190 - components: - - type: Transform - pos: 9.5,36.5 - parent: 588 - - uid: 1210 - components: - - type: Transform - pos: 9.5,48.5 - parent: 588 - - uid: 1413 - components: - - type: Transform - pos: 13.5,48.5 - parent: 588 - - uid: 1470 - components: - - type: Transform - pos: 12.5,48.5 - parent: 588 -- proto: SawAdvanced - entities: - - uid: 1468 - components: - - type: Transform - pos: 8.527969,43.529327 - parent: 588 -- proto: ScalpelLaser - entities: - - uid: 1472 - components: - - type: Transform - pos: 8.485372,43.131977 - parent: 588 -- proto: ScalpelShiv - entities: - - uid: 708 - components: - - type: Transform - pos: 16.074419,18.727995 - parent: 588 - - uid: 1592 - components: - - type: Transform - pos: 10.50393,24.491432 - parent: 588 -- proto: SeedExtractor - entities: - - uid: 802 - components: - - type: Transform - pos: 33.5,21.5 - parent: 588 - - uid: 1776 - components: - - type: Transform - pos: 28.5,42.5 - parent: 588 -- proto: SheetGlass - entities: - - uid: 649 - components: - - type: Transform - pos: 14.3137455,32.471424 - parent: 588 -- proto: SheetPlasteel - entities: - - uid: 866 - components: - - type: Transform - pos: 2.412651,34.456436 - parent: 588 -- proto: SheetPlastic - entities: - - uid: 398 - components: - - type: Transform - pos: 20.04785,45.07574 - parent: 588 -- proto: SheetSteel - entities: - - uid: 656 - components: - - type: Transform - pos: 26.36062,32.5183 - parent: 588 - - uid: 699 - components: - - type: Transform - pos: 29.259031,40.432583 - parent: 588 -- proto: ShowcaseRobot - entities: - - uid: 1621 - components: - - type: Transform - pos: 16.5,10.5 - parent: 588 - - uid: 1622 - components: - - type: Transform - pos: 18.5,6.5 - parent: 588 -- proto: ShuttersNormal - entities: - - uid: 1237 - components: - - type: Transform - pos: 34.5,27.5 - parent: 588 - - type: DeviceLinkSink - links: - - 593 - - uid: 1238 - components: - - type: Transform - pos: 32.5,27.5 - parent: 588 - - type: DeviceLinkSink - links: - - 593 -- proto: ShuttersWindow - entities: - - uid: 1239 - components: - - type: Transform - pos: 33.5,27.5 - parent: 588 - - type: DeviceLinkSink - links: - - 593 -- proto: SignCloning - entities: - - uid: 1484 - components: - - type: Transform - pos: 12.5,43.5 - parent: 588 -- proto: SignPrison - entities: - - uid: 1792 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 6.5,3.5 - parent: 588 - - uid: 1793 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 10.5,1.5 - parent: 588 - - uid: 1794 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 1.5,21.5 - parent: 588 - - uid: 1795 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 3.5,19.5 - parent: 588 - - uid: 1796 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 0.5,46.5 - parent: 588 - - uid: 1797 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 6.5,44.5 - parent: 588 -- proto: SignSecurity - entities: - - uid: 1798 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 22.5,1.5 - parent: 588 -- proto: SignSurgery - entities: - - uid: 1483 - components: - - type: Transform - pos: 10.5,43.5 - parent: 588 -- proto: Sink - entities: - - uid: 935 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 10.5,25.5 - parent: 588 -- proto: SinkStemlessWater - entities: - - uid: 1461 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 9.5,42.5 - parent: 588 - - uid: 1462 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 13.5,42.5 - parent: 588 -- proto: SMESBasic - entities: - - uid: 46 - components: - - type: Transform - pos: 26.5,13.5 - parent: 588 - - uid: 56 - components: - - type: Transform - pos: 28.5,13.5 - parent: 588 - - uid: 747 - components: - - type: Transform - pos: 26.5,19.5 - parent: 588 - - uid: 1506 - components: - - type: Transform - pos: 18.5,46.5 - parent: 588 - - uid: 1507 - components: - - type: Transform - pos: 20.5,46.5 - parent: 588 -- proto: SoapSyndie - entities: - - uid: 1856 - components: - - type: Transform - pos: 10.4890785,27.46785 - parent: 588 -- proto: SpaceCash100 - entities: - - uid: 1243 - components: - - type: Transform - pos: 11.887424,39.621456 - parent: 588 - - uid: 1244 - components: - - type: Transform - pos: 11.759636,39.479546 - parent: 588 - - uid: 1296 - components: - - type: Transform - pos: 12.100407,39.465355 - parent: 588 - - uid: 1297 - components: - - type: Transform - pos: 12.100407,39.80594 - parent: 588 - - uid: 1298 - components: - - type: Transform - pos: 11.688642,39.720795 - parent: 588 - - uid: 1299 - components: - - type: Transform - pos: 11.4330635,39.57888 - parent: 588 -- proto: Spear - entities: - - uid: 1834 - components: - - type: Transform - pos: 24.466219,48.441994 - parent: 588 -- proto: SpeedLoaderMagnum - entities: - - uid: 950 - components: - - type: Transform - pos: 28.703945,8.421182 - parent: 588 -- proto: StasisBed - entities: - - uid: 1425 - components: - - type: Transform - pos: 13.5,43.5 - parent: 588 -- proto: StimkitFilled - entities: - - uid: 561 - components: - - type: Transform - pos: 30.39083,27.514402 - parent: 588 -- proto: StimpackMini - entities: - - uid: 1879 - components: - - type: Transform - pos: 24.467485,46.702366 - parent: 588 -- proto: Stool - entities: - - uid: 1017 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 14.5,25.5 - parent: 588 - - uid: 1018 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 14.5,24.5 - parent: 588 - - uid: 1019 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 16.5,28.5 - parent: 588 - - uid: 1020 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 18.5,28.5 - parent: 588 - - uid: 1593 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 0.5,13.5 - parent: 588 - - uid: 1594 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 0.5,15.5 - parent: 588 - - uid: 1595 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 6.5,15.5 - parent: 588 - - uid: 1596 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 6.5,13.5 - parent: 588 -- proto: SubstationBasic - entities: - - uid: 467 - components: - - type: Transform - pos: 27.5,13.5 - parent: 588 - - uid: 1508 - components: - - type: Transform - pos: 19.5,46.5 - parent: 588 -- proto: SubstationWallBasic - entities: - - uid: 774 - components: - - type: Transform - pos: 27.5,19.5 - parent: 588 - - uid: 972 - components: - - type: Transform - pos: 19.5,32.5 - parent: 588 -- proto: SyringeEphedrine - entities: - - uid: 1475 - components: - - type: Transform - pos: 14.472328,42.917698 - parent: 588 -- proto: Table - entities: - - uid: 525 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 16.5,13.5 - parent: 588 - - uid: 527 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 16.5,12.5 - parent: 588 - - uid: 528 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 17.5,12.5 - parent: 588 - - uid: 535 - components: - - type: Transform - pos: 20.5,14.5 - parent: 588 - - uid: 536 - components: - - type: Transform - pos: 20.5,15.5 - parent: 588 - - uid: 630 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 6.5,19.5 - parent: 588 - - uid: 632 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 10.5,19.5 - parent: 588 - - uid: 633 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 10.5,18.5 - parent: 588 - - uid: 636 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 6.5,22.5 - parent: 588 - - uid: 637 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 7.5,22.5 - parent: 588 - - uid: 710 - components: - - type: Transform - pos: 18.5,22.5 - parent: 588 - - uid: 711 - components: - - type: Transform - pos: 18.5,21.5 - parent: 588 - - uid: 712 - components: - - type: Transform - pos: 21.5,18.5 - parent: 588 - - uid: 713 - components: - - type: Transform - pos: 22.5,18.5 - parent: 588 - - uid: 714 - components: - - type: Transform - pos: 18.5,18.5 - parent: 588 - - uid: 715 - components: - - type: Transform - pos: 19.5,18.5 - parent: 588 - - uid: 799 - components: - - type: Transform - pos: 33.5,20.5 - parent: 588 - - uid: 1118 - components: - - type: Transform - pos: 22.5,12.5 - parent: 588 - - uid: 1778 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 30.5,48.5 - parent: 588 - - uid: 1779 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 29.5,48.5 - parent: 588 - - uid: 1780 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 30.5,47.5 - parent: 588 -- proto: TableFrame - entities: - - uid: 705 - components: - - type: Transform - pos: 15.5,22.5 - parent: 588 - - uid: 1063 - components: - - type: Transform - pos: 26.5,24.5 - parent: 588 -- proto: TableGlass - entities: - - uid: 1144 - components: - - type: Transform - pos: 30.5,27.5 - parent: 588 - - uid: 1145 - components: - - type: Transform - pos: 30.5,28.5 - parent: 588 - - uid: 1390 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 8.5,43.5 - parent: 588 - - uid: 1391 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 8.5,42.5 - parent: 588 - - uid: 1398 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 10.5,44.5 - parent: 588 - - uid: 1405 - components: - - type: Transform - pos: 14.5,42.5 - parent: 588 - - uid: 1406 - components: - - type: Transform - pos: 14.5,43.5 - parent: 588 - - uid: 1407 - components: - - type: Transform - pos: 12.5,44.5 - parent: 588 -- proto: TableReinforced - entities: - - uid: 924 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 26.5,36.5 - parent: 588 - - uid: 925 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 26.5,35.5 - parent: 588 - - uid: 926 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 26.5,34.5 - parent: 588 - - uid: 1005 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 15.5,30.5 - parent: 588 - - uid: 1006 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 25.5,30.5 - parent: 588 - - uid: 1012 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 14.5,28.5 - parent: 588 - - uid: 1023 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 16.5,24.5 - parent: 588 - - uid: 1235 - components: - - type: Transform - pos: 34.5,24.5 - parent: 588 - - uid: 1567 - components: - - type: Transform - pos: 21.5,47.5 - parent: 588 -- proto: TableReinforcedGlass - entities: - - uid: 1213 - components: - - type: Transform - pos: 10.5,39.5 - parent: 588 - - uid: 1214 - components: - - type: Transform - pos: 11.5,39.5 - parent: 588 - - uid: 1215 - components: - - type: Transform - pos: 12.5,39.5 - parent: 588 -- proto: TableWood - entities: - - uid: 309 - components: - - type: Transform - pos: 20.5,4.5 - parent: 588 - - uid: 310 - components: - - type: Transform - pos: 21.5,4.5 - parent: 588 - - uid: 311 - components: - - type: Transform - pos: 18.5,0.5 - parent: 588 - - uid: 312 - components: - - type: Transform - pos: 21.5,3.5 - parent: 588 - - uid: 317 - components: - - type: Transform - pos: 18.5,1.5 - parent: 588 - - uid: 318 - components: - - type: Transform - pos: 19.5,0.5 - parent: 588 - - uid: 332 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 25.5,3.5 - parent: 588 - - uid: 333 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 26.5,3.5 - parent: 588 - - uid: 334 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 27.5,3.5 - parent: 588 - - uid: 335 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 25.5,4.5 - parent: 588 - - uid: 336 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 27.5,4.5 - parent: 588 - - uid: 340 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 24.5,1.5 - parent: 588 - - uid: 341 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 25.5,1.5 - parent: 588 - - uid: 342 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 27.5,1.5 - parent: 588 - - uid: 343 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 28.5,1.5 - parent: 588 - - uid: 563 - components: - - type: Transform - pos: 13.5,13.5 - parent: 588 - - uid: 564 - components: - - type: Transform - pos: 12.5,13.5 - parent: 588 - - uid: 565 - components: - - type: Transform - pos: 12.5,12.5 - parent: 588 - - uid: 1047 - components: - - type: Transform - pos: 22.5,28.5 - parent: 588 - - uid: 1048 - components: - - type: Transform - pos: 20.5,24.5 - parent: 588 - - uid: 1062 - components: - - type: Transform - pos: 26.5,28.5 - parent: 588 - - uid: 1227 - components: - - type: Transform - pos: 12.5,36.5 - parent: 588 - - uid: 1228 - components: - - type: Transform - pos: 22.5,34.5 - parent: 588 - - uid: 1783 - components: - - type: Transform - pos: 24.5,46.5 - parent: 588 - - uid: 1784 - components: - - type: Transform - pos: 24.5,44.5 - parent: 588 -- proto: TargetHuman - entities: - - uid: 1077 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 32.5,35.5 - parent: 588 -- proto: TintedWindow - entities: - - uid: 567 - components: - - type: Transform - pos: 12.5,15.5 - parent: 588 - - uid: 1463 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 10.5,43.5 - parent: 588 - - uid: 1464 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 12.5,43.5 - parent: 588 -- proto: ToiletEmpty - entities: - - uid: 932 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 8.5,24.5 - parent: 588 - - uid: 933 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 10.5,24.5 - parent: 588 - - uid: 934 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 8.5,25.5 - parent: 588 -- proto: UniformScrubsColorPurple - entities: - - uid: 1712 - components: - - type: Transform - pos: 10.503376,44.53288 - parent: 588 -- proto: VendingMachineChefvend - entities: - - uid: 532 - components: - - type: Transform - pos: 18.5,12.5 - parent: 588 -- proto: VendingMachineCigs - entities: - - uid: 720 - components: - - type: Transform - pos: 22.5,22.5 - parent: 588 -- proto: VendingMachineCoffee - entities: - - uid: 765 - components: - - type: Transform - pos: 10.5,22.5 - parent: 588 - - uid: 1285 - components: - - type: Transform - pos: 18.5,40.5 - parent: 588 -- proto: VendingMachineDetDrobe - entities: - - uid: 582 - components: - - type: Transform - pos: 10.5,14.5 - parent: 588 -- proto: VendingMachineDinnerware - entities: - - uid: 1781 - components: - - type: Transform - pos: 30.5,46.5 - parent: 588 -- proto: VendingMachineDonut - entities: - - uid: 538 - components: - - type: Transform - pos: 16.5,16.5 - parent: 588 -- proto: VendingMachineLawDrobe - entities: - - uid: 319 - components: - - type: Transform - pos: 18.5,4.5 - parent: 588 -- proto: VendingMachineMedical - entities: - - uid: 1143 - components: - - type: Transform - pos: 28.5,28.5 - parent: 588 -- proto: VendingMachineSec - entities: - - uid: 1013 - components: - - type: Transform - pos: 14.5,27.5 - parent: 588 -- proto: VendingMachineSeedsUnlocked - entities: - - uid: 800 - components: - - type: Transform - pos: 33.5,19.5 - parent: 588 - - uid: 1775 - components: - - type: Transform - pos: 28.5,44.5 - parent: 588 -- proto: WallmountTelescreen - entities: - - uid: 572 - components: - - type: Transform - pos: 13.5,13.5 - parent: 588 -- proto: WallPlastitaniumIndestructible - entities: - - uid: 377 - components: - - type: Transform - pos: 30.5,7.5 - parent: 588 - - uid: 378 - components: - - type: Transform - pos: 29.5,7.5 - parent: 588 - - uid: 379 - components: - - type: Transform - pos: 25.5,9.5 - parent: 588 - - uid: 380 - components: - - type: Transform - pos: 27.5,9.5 - parent: 588 - - uid: 381 - components: - - type: Transform - pos: 27.5,8.5 - parent: 588 - - uid: 382 - components: - - type: Transform - pos: 28.5,9.5 - parent: 588 - - uid: 383 - components: - - type: Transform - pos: 31.5,9.5 - parent: 588 - - uid: 384 - components: - - type: Transform - pos: 31.5,8.5 - parent: 588 - - uid: 385 - components: - - type: Transform - pos: 31.5,7.5 - parent: 588 - - uid: 386 - components: - - type: Transform - pos: 25.5,8.5 - parent: 588 - - uid: 387 - components: - - type: Transform - pos: 33.5,7.5 - parent: 588 - - uid: 388 - components: - - type: Transform - pos: 25.5,7.5 - parent: 588 - - uid: 389 - components: - - type: Transform - pos: 27.5,7.5 - parent: 588 - - uid: 390 - components: - - type: Transform - pos: 30.5,9.5 - parent: 588 - - uid: 391 - components: - - type: Transform - pos: 28.5,7.5 - parent: 588 - - uid: 392 - components: - - type: Transform - pos: 26.5,9.5 - parent: 588 - - uid: 393 - components: - - type: Transform - pos: 33.5,8.5 - parent: 588 - - uid: 394 - components: - - type: Transform - pos: 33.5,9.5 - parent: 588 - - uid: 395 - components: - - type: Transform - pos: 32.5,9.5 - parent: 588 -- proto: WallReinforced - entities: - - uid: 45 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 2.5,8.5 - parent: 588 - - uid: 51 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 8.5,8.5 - parent: 588 - - uid: 244 - components: - - type: Transform - pos: 6.5,4.5 - parent: 588 - - uid: 245 - components: - - type: Transform - pos: 6.5,3.5 - parent: 588 - - uid: 246 - components: - - type: Transform - pos: 10.5,4.5 - parent: 588 - - uid: 247 - components: - - type: Transform - pos: 10.5,3.5 - parent: 588 - - uid: 266 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 6.5,1.5 - parent: 588 - - uid: 267 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 6.5,0.5 - parent: 588 - - uid: 268 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 10.5,1.5 - parent: 588 - - uid: 269 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 10.5,0.5 - parent: 588 - - uid: 291 - components: - - type: Transform - pos: 15.5,6.5 - parent: 588 - - uid: 292 - components: - - type: Transform - pos: 19.5,6.5 - parent: 588 - - uid: 405 - components: - - type: Transform - pos: 15.5,10.5 - parent: 588 - - uid: 406 - components: - - type: Transform - pos: 19.5,10.5 - parent: 588 - - uid: 426 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 8.5,9.5 - parent: 588 - - uid: 432 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 2.5,7.5 - parent: 588 - - uid: 433 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 2.5,9.5 - parent: 588 - - uid: 434 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 8.5,7.5 - parent: 588 - - uid: 570 - components: - - type: Transform - pos: 0.5,46.5 - parent: 588 - - uid: 621 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 1.5,21.5 - parent: 588 - - uid: 622 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 3.5,19.5 - parent: 588 - - uid: 623 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 3.5,21.5 - parent: 588 - - uid: 627 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 1.5,19.5 - parent: 588 - - uid: 1078 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 33.5,35.5 - parent: 588 - - uid: 1330 - components: - - type: Transform - pos: 0.5,44.5 - parent: 588 - - uid: 1332 - components: - - type: Transform - pos: 2.5,42.5 - parent: 588 - - uid: 1334 - components: - - type: Transform - pos: 4.5,42.5 - parent: 588 - - uid: 1335 - components: - - type: Transform - pos: 6.5,44.5 - parent: 588 - - uid: 1337 - components: - - type: Transform - pos: 4.5,48.5 - parent: 588 - - uid: 1338 - components: - - type: Transform - pos: 6.5,46.5 - parent: 588 - - uid: 1340 - components: - - type: Transform - pos: 2.5,48.5 - parent: 588 -- proto: WallSolid - entities: - - uid: 140 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 5.5,25.5 - parent: 588 - - uid: 144 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 5.5,26.5 - parent: 588 - - uid: 145 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 4.5,25.5 - parent: 588 - - uid: 146 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 5.5,27.5 - parent: 588 - - uid: 205 - components: - - type: Transform - pos: 8.5,35.5 - parent: 588 - - uid: 206 - components: - - type: Transform - pos: 7.5,35.5 - parent: 588 - - uid: 207 - components: - - type: Transform - pos: 6.5,35.5 - parent: 588 - - uid: 208 - components: - - type: Transform - pos: 4.5,35.5 - parent: 588 - - uid: 210 - components: - - type: Transform - pos: 1.5,34.5 - parent: 588 - - uid: 305 - components: - - type: Transform - pos: 22.5,4.5 - parent: 588 - - uid: 306 - components: - - type: Transform - pos: 22.5,3.5 - parent: 588 - - uid: 307 - components: - - type: Transform - pos: 22.5,0.5 - parent: 588 - - uid: 308 - components: - - type: Transform - pos: 22.5,1.5 - parent: 588 - - uid: 476 - components: - - type: Transform - pos: 25.5,13.5 - parent: 588 - - uid: 481 - components: - - type: Transform - pos: 25.5,15.5 - parent: 588 - - uid: 483 - components: - - type: Transform - pos: 30.5,13.5 - parent: 588 - - uid: 501 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 25.5,14.5 - parent: 588 - - uid: 510 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 30.5,15.5 - parent: 588 - - uid: 749 - components: - - type: Transform - pos: 25.5,19.5 - parent: 588 - - uid: 750 - components: - - type: Transform - pos: 27.5,19.5 - parent: 588 - - uid: 975 - components: - - type: Transform - pos: 19.5,32.5 - parent: 588 - - uid: 995 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 21.5,32.5 - parent: 588 - - uid: 1163 - components: - - type: Transform - pos: 8.5,36.5 - parent: 588 - - uid: 1164 - components: - - type: Transform - pos: 3.5,35.5 - parent: 588 - - uid: 1165 - components: - - type: Transform - pos: 2.5,35.5 - parent: 588 - - uid: 1167 - components: - - type: Transform - pos: 1.5,35.5 - parent: 588 - - uid: 1300 - components: - - type: Transform - pos: 25.5,39.5 - parent: 588 - - uid: 1303 - components: - - type: Transform - pos: 28.5,39.5 - parent: 588 - - uid: 1304 - components: - - type: Transform - pos: 29.5,39.5 - parent: 588 - - uid: 1305 - components: - - type: Transform - pos: 28.5,40.5 - parent: 588 - - uid: 1485 - components: - - type: Transform - pos: 17.5,43.5 - parent: 588 - - uid: 1486 - components: - - type: Transform - pos: 17.5,44.5 - parent: 588 - - uid: 1487 - components: - - type: Transform - pos: 18.5,43.5 - parent: 588 - - uid: 1488 - components: - - type: Transform - pos: 20.5,43.5 - parent: 588 - - uid: 1489 - components: - - type: Transform - pos: 21.5,43.5 - parent: 588 - - uid: 1490 - components: - - type: Transform - pos: 21.5,44.5 - parent: 588 - - uid: 1491 - components: - - type: Transform - pos: 18.5,47.5 - parent: 588 - - uid: 1492 - components: - - type: Transform - pos: 19.5,47.5 - parent: 588 - - uid: 1493 - components: - - type: Transform - pos: 20.5,47.5 - parent: 588 -- proto: WallWood - entities: - - uid: 554 - components: - - type: Transform - pos: 9.5,13.5 - parent: 588 - - uid: 555 - components: - - type: Transform - pos: 9.5,14.5 - parent: 588 - - uid: 556 - components: - - type: Transform - pos: 9.5,15.5 - parent: 588 - - uid: 557 - components: - - type: Transform - pos: 10.5,15.5 - parent: 588 - - uid: 558 - components: - - type: Transform - pos: 13.5,15.5 - parent: 588 - - uid: 559 - components: - - type: Transform - pos: 13.5,16.5 - parent: 588 - - uid: 566 - components: - - type: Transform - pos: 9.5,12.5 - parent: 588 -- proto: WardrobePrisonFilled - entities: - - uid: 297 - components: - - type: Transform - pos: 2.5,4.5 - parent: 588 - - uid: 302 - components: - - type: Transform - pos: 4.5,4.5 - parent: 588 - - uid: 303 - components: - - type: Transform - pos: 12.5,4.5 - parent: 588 - - uid: 304 - components: - - type: Transform - pos: 14.5,4.5 - parent: 588 - - uid: 544 - components: - - type: Transform - pos: 16.5,4.5 - parent: 588 - - uid: 545 - components: - - type: Transform - pos: 0.5,4.5 - parent: 588 - - uid: 1769 - components: - - type: Transform - pos: 24.5,43.5 - parent: 588 - - uid: 1770 - components: - - type: Transform - pos: 24.5,47.5 - parent: 588 -- proto: WaterCooler - entities: - - uid: 629 - components: - - type: Transform - pos: 6.5,18.5 - parent: 588 - - uid: 724 - components: - - type: Transform - pos: 19.5,22.5 - parent: 588 - - uid: 1284 - components: - - type: Transform - pos: 17.5,40.5 - parent: 588 - - uid: 1292 - components: - - type: Transform - pos: 21.5,12.5 - parent: 588 -- proto: WaterTankHighCapacity - entities: - - uid: 801 - components: - - type: Transform - pos: 30.5,22.5 - parent: 588 - - uid: 1789 - components: - - type: Transform - pos: 28.5,48.5 - parent: 588 -- proto: WeaponCapacitorRecharger - entities: - - uid: 760 - components: - - type: Transform - pos: 10.5,18.5 - parent: 588 - - uid: 929 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 26.5,34.5 - parent: 588 - - uid: 1033 - components: - - type: Transform - pos: 14.5,28.5 - parent: 588 - - uid: 1034 - components: - - type: Transform - pos: 16.5,24.5 - parent: 588 -- proto: WeaponDisablerPractice - entities: - - uid: 547 - components: - - type: Transform - pos: 1.4370823,0.5241035 - parent: 588 - - uid: 930 - components: - - type: Transform - pos: 26.440151,36.61676 - parent: 588 - - uid: 1611 - components: - - type: Transform - pos: 12.371853,10.605072 - parent: 588 -- proto: WeaponLaserCarbinePractice - entities: - - uid: 931 - components: - - type: Transform - pos: 26.596338,36.36132 - parent: 588 - - uid: 1612 - components: - - type: Transform - pos: 22.543945,6.5464144 - parent: 588 -- proto: WeaponShotgunKammerer - entities: - - uid: 583 - components: - - type: Transform - pos: 26.57963,35.4414 - parent: 588 -- proto: WindoorAssemblySecure - entities: - - uid: 696 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 16.5,19.5 - parent: 588 - - uid: 697 - components: - - type: Transform - pos: 12.5,21.5 - parent: 588 - - uid: 1073 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 25.5,25.5 - parent: 588 -- proto: WindoorSecure - entities: - - uid: 1761 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 26.5,43.5 - parent: 588 - - uid: 1762 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 26.5,47.5 - parent: 588 -- proto: WindoorSecureBrigLocked - entities: - - uid: 339 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 29.5,2.5 - parent: 588 -- proto: WindoorSecureEngineeringLocked - entities: - - uid: 1569 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 18.5,45.5 - parent: 588 - - uid: 1570 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 20.5,45.5 - parent: 588 -- proto: WindoorSecureMedicalLocked - entities: - - uid: 1408 - components: - - type: Transform - pos: 11.5,44.5 - parent: 588 -- proto: WindoorSecureSecurityLocked - entities: - - uid: 252 - components: - - type: Transform - pos: 4.5,3.5 - parent: 588 - - uid: 253 - components: - - type: Transform - pos: 2.5,3.5 - parent: 588 - - uid: 256 - components: - - type: Transform - pos: 16.5,3.5 - parent: 588 - - uid: 274 - components: - - type: Transform - pos: 12.5,3.5 - parent: 588 - - uid: 275 - components: - - type: Transform - pos: 14.5,3.5 - parent: 588 - - uid: 289 - components: - - type: Transform - pos: 0.5,3.5 - parent: 588 - - uid: 698 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 12.5,19.5 - parent: 588 - - uid: 1053 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 21.5,25.5 - parent: 588 - - uid: 1054 - components: - - type: Transform - pos: 21.5,27.5 - parent: 588 -- proto: WindowDirectional - entities: - - uid: 480 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 26.5,15.5 - parent: 588 - - uid: 482 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 27.5,15.5 - parent: 588 - - uid: 540 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 28.5,15.5 - parent: 588 - - uid: 541 - components: - - type: Transform - pos: 26.5,13.5 - parent: 588 - - uid: 542 - components: - - type: Transform - pos: 27.5,13.5 - parent: 588 - - uid: 543 - components: - - type: Transform - pos: 28.5,13.5 - parent: 588 - - uid: 575 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 33.5,20.5 - parent: 588 - - uid: 576 - components: - - type: Transform - pos: 26.5,19.5 - parent: 588 - - uid: 803 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 33.5,21.5 - parent: 588 - - uid: 804 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 33.5,19.5 - parent: 588 - - uid: 1087 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 27.5,36.5 - parent: 588 - - uid: 1088 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 27.5,34.5 - parent: 588 - - uid: 1520 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 18.5,46.5 - parent: 588 - - uid: 1521 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 20.5,46.5 - parent: 588 - - uid: 1771 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 28.5,44.5 - parent: 588 - - uid: 1773 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 29.5,44.5 - parent: 588 - - uid: 1777 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 30.5,44.5 - parent: 588 - - uid: 1782 - components: - - type: Transform - pos: 30.5,46.5 - parent: 588 -- proto: WindowFrostedDirectional - entities: - - uid: 936 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 8.5,24.5 - parent: 588 - - uid: 937 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 10.5,24.5 - parent: 588 - - uid: 938 - components: - - type: Transform - pos: 8.5,27.5 - parent: 588 - - uid: 939 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 8.5,25.5 - parent: 588 - - uid: 941 - components: - - type: Transform - pos: 10.5,27.5 - parent: 588 - - uid: 942 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 10.5,28.5 - parent: 588 - - uid: 943 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 8.5,28.5 - parent: 588 - - uid: 1392 - components: - - type: Transform - pos: 8.5,44.5 - parent: 588 - - uid: 1393 - components: - - type: Transform - pos: 10.5,44.5 - parent: 588 - - uid: 1401 - components: - - type: Transform - pos: 12.5,44.5 - parent: 588 - - uid: 1402 - components: - - type: Transform - pos: 14.5,44.5 - parent: 588 - - uid: 1753 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 24.5,43.5 - parent: 588 - - uid: 1754 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 25.5,43.5 - parent: 588 - - uid: 1755 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 26.5,43.5 - parent: 588 - - uid: 1756 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 26.5,42.5 - parent: 588 - - uid: 1757 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 26.5,48.5 - parent: 588 - - uid: 1758 - components: - - type: Transform - pos: 26.5,47.5 - parent: 588 - - uid: 1759 - components: - - type: Transform - pos: 25.5,47.5 - parent: 588 - - uid: 1760 - components: - - type: Transform - pos: 24.5,47.5 - parent: 588 -- proto: WindowReinforcedDirectional - entities: - - uid: 97 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 2.5,14.5 - parent: 588 - - uid: 99 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 4.5,14.5 - parent: 588 - - uid: 258 - components: - - type: Transform - pos: 3.5,3.5 - parent: 588 - - uid: 259 - components: - - type: Transform - pos: 5.5,3.5 - parent: 588 - - uid: 260 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 3.5,3.5 - parent: 588 - - uid: 261 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 3.5,4.5 - parent: 588 - - uid: 270 - components: - - type: Transform - pos: 11.5,3.5 - parent: 588 - - uid: 271 - components: - - type: Transform - pos: 13.5,3.5 - parent: 588 - - uid: 272 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 13.5,3.5 - parent: 588 - - uid: 273 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 13.5,4.5 - parent: 588 - - uid: 277 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 1.5,4.5 - parent: 588 - - uid: 278 - components: - - type: Transform - pos: 15.5,3.5 - parent: 588 - - uid: 279 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 15.5,4.5 - parent: 588 - - uid: 280 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 1.5,3.5 - parent: 588 - - uid: 281 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 15.5,3.5 - parent: 588 - - uid: 290 - components: - - type: Transform - pos: 1.5,3.5 - parent: 588 - - uid: 607 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 3.5,14.5 - parent: 588 - - uid: 609 - components: - - type: Transform - pos: 4.5,14.5 - parent: 588 - - uid: 611 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 4.5,14.5 - parent: 588 - - uid: 612 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 2.5,14.5 - parent: 588 - - uid: 614 - components: - - type: Transform - pos: 3.5,14.5 - parent: 588 - - uid: 619 - components: - - type: Transform - pos: 2.5,14.5 - parent: 588 - - uid: 620 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 3.5,20.5 - parent: 588 - - uid: 624 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 1.5,20.5 - parent: 588 - - uid: 625 - components: - - type: Transform - pos: 2.5,19.5 - parent: 588 - - uid: 626 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 2.5,21.5 - parent: 588 - - uid: 648 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 15.5,19.5 - parent: 588 - - uid: 650 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 13.5,18.5 - parent: 588 - - uid: 651 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 13.5,19.5 - parent: 588 - - uid: 652 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 15.5,19.5 - parent: 588 - - uid: 653 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 15.5,18.5 - parent: 588 - - uid: 654 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 13.5,21.5 - parent: 588 - - uid: 658 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 13.5,22.5 - parent: 588 - - uid: 660 - components: - - type: Transform - pos: 13.5,21.5 - parent: 588 - - uid: 805 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 1.5,27.5 - parent: 588 - - uid: 806 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 1.5,26.5 - parent: 588 - - uid: 807 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 1.5,25.5 - parent: 588 - - uid: 808 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 1.5,25.5 - parent: 588 - - uid: 809 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 1.5,26.5 - parent: 588 - - uid: 810 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 1.5,27.5 - parent: 588 - - uid: 811 - components: - - type: Transform - pos: 1.5,25.5 - parent: 588 - - uid: 812 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 1.5,27.5 - parent: 588 - - uid: 1038 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 22.5,25.5 - parent: 588 - - uid: 1039 - components: - - type: Transform - pos: 20.5,27.5 - parent: 588 - - uid: 1042 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 20.5,25.5 - parent: 588 - - uid: 1045 - components: - - type: Transform - pos: 22.5,27.5 - parent: 588 - - uid: 1058 - components: - - type: Transform - pos: 24.5,27.5 - parent: 588 - - uid: 1059 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 26.5,25.5 - parent: 588 - - uid: 1060 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 24.5,25.5 - parent: 588 - - uid: 1399 - components: - - type: Transform - pos: 9.5,44.5 - parent: 588 - - uid: 1400 - components: - - type: Transform - pos: 13.5,44.5 - parent: 588 -- proto: Wrench - entities: - - uid: 424 - components: - - type: Transform - pos: 15.156982,32.526764 - parent: 588 -- proto: Zipties - entities: - - uid: 1156 - components: - - type: Transform - pos: 15.332411,0.52492684 - parent: 588 -- proto: ZiptiesBroken - entities: - - uid: 48 - components: - - type: Transform - pos: 5.2591753,3.5817227 - parent: 588 - - uid: 706 - components: - - type: Transform - pos: 16.06022,21.977758 - parent: 588 -... +meta: + format: 6 + postmapinit: false +tilemap: + 0: Space + 15: FloorBasalt + 29: FloorDark + 33: FloorDarkMini + 34: FloorDarkMono + 42: FloorElevatorShaft + 54: FloorGreenCircuit + 62: FloorLino + 77: FloorReinforced + 82: FloorShuttleOrange + 89: FloorSteel + 99: FloorSteelMini + 100: FloorSteelMono + 104: FloorTechMaint + 108: FloorWhite + 112: FloorWhiteMini + 118: FloorWood + 121: Plating +entities: +- proto: "" + entities: + - uid: 588 + components: + - type: MetaData + - type: Transform + - type: Map + - type: PhysicsMap + - type: Broadphase + - type: OccluderTree + - type: MapGrid + chunks: + -1,-1: + ind: -1,-1 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAA + version: 6 + 0,0: + ind: 0,0 + tiles: WQAAAAADWQAAAAABWQAAAAACWQAAAAADWQAAAAACWQAAAAABeQAAAAAADwAAAAAAHQAAAAABDwAAAAAAeQAAAAAAWQAAAAACWQAAAAACWQAAAAACWQAAAAACWQAAAAACWQAAAAACWQAAAAABWQAAAAABWQAAAAAAWQAAAAADWQAAAAADeQAAAAAADwAAAAAADwAAAAAADwAAAAAAeQAAAAAAWQAAAAABWQAAAAACWQAAAAABWQAAAAACWQAAAAAAWQAAAAADWQAAAAADWQAAAAABWQAAAAADWQAAAAACWQAAAAABIgAAAAABHQAAAAABDwAAAAAAHQAAAAACIgAAAAAAWQAAAAABWQAAAAACWQAAAAABWQAAAAADWQAAAAADaAAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAeQAAAAAADwAAAAAADwAAAAAADwAAAAAAeQAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAeQAAAAAADwAAAAAAHQAAAAACDwAAAAAAeQAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAHQAAAAABHQAAAAABHQAAAAACDwAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAAHQAAAAABHQAAAAADHQAAAAABUgAAAAAAZAAAAAACWQAAAAAAZAAAAAACeQAAAAAAHQAAAAAAIgAAAAADeQAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAAeQAAAAAAIgAAAAACHQAAAAACUgAAAAAAYwAAAAACYwAAAAAAYwAAAAAAeQAAAAAAHQAAAAABIgAAAAACeQAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAAeQAAAAAAIgAAAAABHQAAAAADUgAAAAAAYwAAAAACYwAAAAAAYwAAAAAAWQAAAAACHQAAAAAAIgAAAAADeQAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAAeQAAAAAAIgAAAAAAHQAAAAABUgAAAAAAYwAAAAADYwAAAAABYwAAAAABeQAAAAAAHQAAAAADHQAAAAADHQAAAAACDwAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAAHQAAAAABHQAAAAABHQAAAAAAUgAAAAAAZAAAAAABWQAAAAABZAAAAAACeQAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAHQAAAAAAHQAAAAABeQAAAAAAHQAAAAADeQAAAAAAHQAAAAADHQAAAAADUgAAAAAAaAAAAAAAeQAAAAAAdgAAAAADdgAAAAABdgAAAAADdgAAAAAAdgAAAAADUgAAAAAAHQAAAAABHQAAAAACHQAAAAACHQAAAAADHQAAAAADHQAAAAADHQAAAAAAUgAAAAAAaAAAAAAAeQAAAAAAdgAAAAABdgAAAAACdgAAAAAAdgAAAAACdgAAAAACUgAAAAAAHQAAAAADHQAAAAACDwAAAAAADwAAAAAADwAAAAAAHQAAAAACHQAAAAACUgAAAAAAaAAAAAAAeQAAAAAAdgAAAAACdgAAAAADdgAAAAADdgAAAAACdgAAAAADUgAAAAAAHQAAAAADHQAAAAACHQAAAAABHQAAAAACHQAAAAACHQAAAAADHQAAAAABUgAAAAAAaAAAAAAAeQAAAAAAeQAAAAAAaAAAAAAAeQAAAAAAeQAAAAAAdgAAAAABUgAAAAAA + version: 6 + 0,1: + ind: 0,1 + tiles: HQAAAAAAHQAAAAADeQAAAAAAHQAAAAACeQAAAAAAHQAAAAADHQAAAAAAUgAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAeQAAAAAAdgAAAAACUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAHQAAAAADHQAAAAAAHQAAAAADHQAAAAADHQAAAAABUgAAAAAAWQAAAAAAWQAAAAABWQAAAAACWQAAAAAAWQAAAAAAUgAAAAAAaAAAAAAAeQAAAAAAWQAAAAABaAAAAAAAHQAAAAACeQAAAAAADwAAAAAAeQAAAAAAHQAAAAACUgAAAAAAWQAAAAADYwAAAAAAYwAAAAABYwAAAAADWQAAAAABUgAAAAAAaAAAAAAAaAAAAAAAWQAAAAAAeQAAAAAAHQAAAAABDwAAAAAADwAAAAAADwAAAAAAHQAAAAAAUgAAAAAAWQAAAAAAYwAAAAABYwAAAAACYwAAAAACWQAAAAADUgAAAAAAWQAAAAACWQAAAAADWQAAAAABWQAAAAAAHQAAAAAAeQAAAAAADwAAAAAAeQAAAAAAHQAAAAAAUgAAAAAAWQAAAAABYwAAAAABYwAAAAABYwAAAAABWQAAAAADUgAAAAAAaAAAAAAAaAAAAAAAWQAAAAAAWQAAAAABHQAAAAADHQAAAAABHQAAAAACHQAAAAAAHQAAAAABUgAAAAAAWQAAAAAAWQAAAAABWQAAAAAAWQAAAAACWQAAAAABUgAAAAAAaAAAAAAAaAAAAAAAWQAAAAABWQAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAHQAAAAAAHQAAAAADHQAAAAAAUgAAAAAAaAAAAAAAaAAAAAAAeQAAAAAAUgAAAAAAcAAAAAACcAAAAAACcAAAAAABUgAAAAAAWQAAAAADWQAAAAADZAAAAAAAUgAAAAAAHQAAAAAADwAAAAAAHQAAAAACUgAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAUgAAAAAAcAAAAAADcAAAAAACcAAAAAADUgAAAAAAWQAAAAAAWQAAAAABZAAAAAABUgAAAAAAHQAAAAACDwAAAAAAHQAAAAAAUgAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAUgAAAAAAcAAAAAADcAAAAAAAcAAAAAAAUgAAAAAAWQAAAAABWQAAAAABWQAAAAABUgAAAAAAHQAAAAACDwAAAAAAHQAAAAABUgAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAUgAAAAAAcAAAAAADcAAAAAADcAAAAAABUgAAAAAAZAAAAAACWQAAAAABWQAAAAACUgAAAAAAHQAAAAADHQAAAAACHQAAAAADUgAAAAAAeQAAAAAAaAAAAAAAeQAAAAAAUgAAAAAAcAAAAAACcAAAAAACcAAAAAADUgAAAAAAZAAAAAACWQAAAAAAWQAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAHQAAAAAAHQAAAAABDwAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAAHQAAAAADHQAAAAACUgAAAAAAWQAAAAABWQAAAAACHQAAAAACHQAAAAABHQAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAAHQAAAAADHQAAAAADHQAAAAAAUgAAAAAAaAAAAAAAeQAAAAAA + version: 6 + 0,-1: + ind: 0,-1 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAA + version: 6 + -1,0: + ind: -1,0 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAA + version: 6 + -1,1: + ind: -1,1 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAA + version: 6 + 1,-1: + ind: 1,-1 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAA + version: 6 + 1,0: + ind: 1,0 + tiles: WQAAAAABUgAAAAAAPgAAAAAAPgAAAAAAPgAAAAAAPgAAAAAAeQAAAAAAHQAAAAACHQAAAAAAHQAAAAABHQAAAAADHQAAAAABHQAAAAADHQAAAAADHQAAAAACHQAAAAADWQAAAAADUgAAAAAAPgAAAAAAPgAAAAAAPgAAAAAAPgAAAAAAeQAAAAAAHQAAAAAAHQAAAAADHQAAAAABHQAAAAACHQAAAAABHQAAAAADHQAAAAACHQAAAAABHQAAAAABWQAAAAAAUgAAAAAAPgAAAAAAPgAAAAAAPgAAAAAAPgAAAAAAIgAAAAACHQAAAAACHQAAAAACHQAAAAACHQAAAAABHQAAAAACHQAAAAACHQAAAAAAHQAAAAAAHQAAAAABaAAAAAAAUgAAAAAAPgAAAAAAPgAAAAAAPgAAAAAAPgAAAAAAeQAAAAAAHQAAAAADHQAAAAAAHQAAAAACHQAAAAAAHQAAAAADHQAAAAADHQAAAAADHQAAAAAAHQAAAAABaAAAAAAAUgAAAAAAPgAAAAAAPgAAAAAAPgAAAAAAPgAAAAAAeQAAAAAAHQAAAAABHQAAAAABHQAAAAACHQAAAAAAHQAAAAABHQAAAAACHQAAAAAAHQAAAAACHQAAAAADUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAWQAAAAABWQAAAAACWQAAAAACeQAAAAAAZAAAAAAAWQAAAAABZAAAAAAAUgAAAAAATQAAAAAATQAAAAAATQAAAAAATQAAAAAATQAAAAAATQAAAAAATQAAAAAATQAAAAAAWQAAAAAAWQAAAAABWQAAAAAAeQAAAAAAYwAAAAAAYwAAAAAAYwAAAAABUgAAAAAATQAAAAAAeQAAAAAAIgAAAAACeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAWQAAAAACWQAAAAACWQAAAAAAWQAAAAADYwAAAAADYwAAAAAAYwAAAAADUgAAAAAATQAAAAAAeQAAAAAAKgAAAAAAeQAAAAAAKgAAAAAAKgAAAAAAKgAAAAAAeQAAAAAAWQAAAAAAWQAAAAAAWQAAAAABeQAAAAAAYwAAAAACYwAAAAABYwAAAAADUgAAAAAATQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAIgAAAAABeQAAAAAAeQAAAAAAWQAAAAADWQAAAAAAWQAAAAAAeQAAAAAAZAAAAAADWQAAAAABZAAAAAACUgAAAAAATQAAAAAATQAAAAAATQAAAAAATQAAAAAATQAAAAAATQAAAAAATQAAAAAATQAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAWQAAAAABWQAAAAAAWQAAAAAAWQAAAAADWQAAAAACWQAAAAACWQAAAAADUgAAAAAAaAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAaAAAAAAAUgAAAAAAWQAAAAABYwAAAAACYwAAAAACYwAAAAAAYwAAAAADYwAAAAACWQAAAAAAUgAAAAAAaAAAAAAAeQAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAWQAAAAAAeQAAAAAAUgAAAAAAWQAAAAAAYwAAAAABYwAAAAADYwAAAAABYwAAAAACYwAAAAAAWQAAAAACUgAAAAAAaAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAWQAAAAADaAAAAAAAUgAAAAAAWQAAAAACYwAAAAABYwAAAAABYwAAAAADYwAAAAAAYwAAAAABWQAAAAAAUgAAAAAAaAAAAAAAeQAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAWQAAAAAAeQAAAAAAUgAAAAAA + version: 6 + 1,1: + ind: 1,1 + tiles: WQAAAAABWQAAAAACWQAAAAACWQAAAAAAWQAAAAABWQAAAAAAWQAAAAACUgAAAAAAaAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAaAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAaAAAAAAAUgAAAAAAWQAAAAACWQAAAAABWQAAAAABWQAAAAAAWQAAAAADUgAAAAAAeQAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAeQAAAAAAUgAAAAAAWQAAAAACWQAAAAACaAAAAAAAUgAAAAAAWQAAAAADYwAAAAACYwAAAAABYwAAAAAAWQAAAAABUgAAAAAAeQAAAAAAeQAAAAAAaAAAAAAAeQAAAAAAeQAAAAAAUgAAAAAAWQAAAAADYwAAAAADWQAAAAACUgAAAAAAWQAAAAADYwAAAAADYwAAAAADYwAAAAADWQAAAAABUgAAAAAAeQAAAAAAWQAAAAABeQAAAAAAWQAAAAABeQAAAAAAUgAAAAAAWQAAAAAAYwAAAAABWQAAAAADUgAAAAAAWQAAAAAAYwAAAAAAYwAAAAACYwAAAAACWQAAAAADUgAAAAAAeQAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAeQAAAAAAUgAAAAAAWQAAAAADYwAAAAADWQAAAAACUgAAAAAAWQAAAAACWQAAAAACWQAAAAADWQAAAAADWQAAAAACUgAAAAAAeQAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAeQAAAAAAUgAAAAAAWQAAAAACWQAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAWQAAAAADWQAAAAABWQAAAAAAUgAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAUgAAAAAAaAAAAAAAaAAAAAAAeQAAAAAAUgAAAAAAWQAAAAABWQAAAAACZAAAAAACUgAAAAAAWQAAAAABWQAAAAAAWQAAAAAAUgAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAUgAAAAAAeQAAAAAAaAAAAAAAaAAAAAAAUgAAAAAAWQAAAAACWQAAAAADZAAAAAABUgAAAAAAWQAAAAACWQAAAAAAWQAAAAACUgAAAAAAWQAAAAACWQAAAAAAWQAAAAACUgAAAAAAWQAAAAADWQAAAAAAWQAAAAADUgAAAAAAWQAAAAADWQAAAAACWQAAAAADUgAAAAAAZAAAAAADWQAAAAACZAAAAAADUgAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAUgAAAAAAaAAAAAAAaAAAAAAAeQAAAAAAUgAAAAAAZAAAAAAAWQAAAAADWQAAAAACUgAAAAAAZAAAAAABWQAAAAABZAAAAAABUgAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAUgAAAAAAaAAAAAAAeQAAAAAAaAAAAAAAUgAAAAAAZAAAAAADWQAAAAADWQAAAAABUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAWQAAAAAAWQAAAAACWQAAAAABWQAAAAADWQAAAAACWQAAAAAAWQAAAAACWQAAAAAAWQAAAAACWQAAAAACWQAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAaAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAA + version: 6 + -1,2: + ind: -1,2 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAA + version: 6 + -1,3: + ind: -1,3 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + version: 6 + 0,2: + ind: 0,2 + tiles: HQAAAAACHQAAAAACDwAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAAHQAAAAADHQAAAAAAUgAAAAAAaAAAAAAAaAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAaAAAAAAAeQAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAaAAAAAAAUgAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAeQAAAAAAaAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAaAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAaAAAAAAAaAAAAAAAUgAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAWQAAAAADaAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAaAAAAAAAaAAAAAAAeQAAAAAAaAAAAAAAaAAAAAAAUgAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAeQAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAHQAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAAHQAAAAAAUgAAAAAAHQAAAAABTQAAAAAATQAAAAAATQAAAAAATQAAAAAATQAAAAAAHQAAAAABUgAAAAAAHQAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAAHQAAAAAAUgAAAAAAHQAAAAAATQAAAAAANgAAAAAANgAAAAAANgAAAAAATQAAAAAAHQAAAAAAUgAAAAAAHQAAAAACDwAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAAHQAAAAACUgAAAAAAHQAAAAAATQAAAAAATQAAAAAATQAAAAAATQAAAAAATQAAAAAAHQAAAAACUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAADwAAAAAADwAAAAAAeQAAAAAAIgAAAAABeQAAAAAADwAAAAAADwAAAAAAUgAAAAAAbAAAAAACbAAAAAAAbAAAAAAAbAAAAAAAbAAAAAADbAAAAAABbAAAAAABUgAAAAAADwAAAAAADwAAAAAADwAAAAAAHQAAAAAADwAAAAAADwAAAAAADwAAAAAAUgAAAAAAbAAAAAABbAAAAAABeQAAAAAAbAAAAAAAeQAAAAAAbAAAAAABbAAAAAADUgAAAAAAeQAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAAeQAAAAAAUgAAAAAAIgAAAAABIgAAAAABIgAAAAACaAAAAAAAIgAAAAADIgAAAAACIgAAAAABUgAAAAAAIgAAAAAAHQAAAAACDwAAAAAADwAAAAAADwAAAAAAHQAAAAAAIgAAAAABUgAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAUgAAAAAAeQAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAAeQAAAAAAUgAAAAAAIgAAAAACIgAAAAAAIgAAAAABaAAAAAAAIgAAAAABIgAAAAACIgAAAAAAUgAAAAAADwAAAAAADwAAAAAADwAAAAAAHQAAAAADDwAAAAAADwAAAAAADwAAAAAAUgAAAAAAIgAAAAAAIgAAAAAAIgAAAAACaAAAAAAAIgAAAAAAIgAAAAACIgAAAAADUgAAAAAA + version: 6 + 0,3: + ind: 0,3 + tiles: DwAAAAAADwAAAAAAeQAAAAAAIgAAAAAAeQAAAAAADwAAAAAADwAAAAAAUgAAAAAAIgAAAAACIgAAAAAAIgAAAAAAaAAAAAAAIgAAAAABIgAAAAACIgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + version: 6 + 1,2: + ind: 1,2 + tiles: aAAAAAAAaAAAAAAAaAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAYwAAAAACYwAAAAADYwAAAAABeQAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAUgAAAAAAHQAAAAACHQAAAAADHQAAAAACTQAAAAAATQAAAAAATQAAAAAATQAAAAAATQAAAAAAYwAAAAACYwAAAAAAYwAAAAABWQAAAAACaAAAAAAAaAAAAAAAaAAAAAAAUgAAAAAAHQAAAAACHQAAAAABHQAAAAABTQAAAAAATQAAAAAATQAAAAAATQAAAAAATQAAAAAAYwAAAAADYwAAAAADYwAAAAADeQAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAUgAAAAAAHQAAAAADHQAAAAABHQAAAAABTQAAAAAATQAAAAAATQAAAAAATQAAAAAATQAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAWQAAAAACWQAAAAAAWQAAAAAAWQAAAAABWQAAAAADWQAAAAADWQAAAAAAUgAAAAAAaAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAaAAAAAAAUgAAAAAAWQAAAAACYwAAAAABYwAAAAACYwAAAAABYwAAAAADYwAAAAAAWQAAAAAAUgAAAAAAaAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAaAAAAAAAUgAAAAAAWQAAAAAAWQAAAAABWQAAAAABWQAAAAACWQAAAAAAWQAAAAAAWQAAAAADUgAAAAAAaAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAaAAAAAAAaAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAaAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAaAAAAAAAUgAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAWQAAAAACYwAAAAAAYwAAAAADYwAAAAADUgAAAAAAaAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAaAAAAAAAUgAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAWQAAAAADYwAAAAABYwAAAAABYwAAAAACUgAAAAAAaAAAAAAAeQAAAAAAaAAAAAAAeQAAAAAAaAAAAAAAeQAAAAAAaAAAAAAAUgAAAAAAWQAAAAACWQAAAAADWQAAAAABWQAAAAACYwAAAAADYwAAAAABYwAAAAACUgAAAAAAaAAAAAAAWQAAAAADeQAAAAAAeQAAAAAAeQAAAAAAWQAAAAACaAAAAAAAUgAAAAAAWQAAAAADWQAAAAADWQAAAAACWQAAAAADWQAAAAACWQAAAAADWQAAAAADUgAAAAAAaAAAAAAAWQAAAAABaAAAAAAAaAAAAAAAaAAAAAAAWQAAAAAAaAAAAAAAUgAAAAAAWQAAAAAAWQAAAAAAWQAAAAAAWQAAAAACIQAAAAABIQAAAAADWQAAAAACUgAAAAAAaAAAAAAAWQAAAAADeQAAAAAAeQAAAAAAeQAAAAAAWQAAAAAAaAAAAAAAUgAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAWQAAAAABIQAAAAAAIQAAAAABWQAAAAABUgAAAAAA + version: 6 + 1,3: + ind: 1,3 + tiles: aAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAaAAAAAAAUgAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAWQAAAAABWQAAAAAAWQAAAAAAWQAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + version: 6 + 2,-1: + ind: 2,-1 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + version: 6 + 2,0: + ind: 2,0 + tiles: HQAAAAAAHQAAAAABWQAAAAACUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHQAAAAABHQAAAAACWQAAAAABUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHQAAAAACHQAAAAACWQAAAAABUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHQAAAAAAHQAAAAADWQAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHQAAAAACHQAAAAAAWQAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATQAAAAAATQAAAAAATQAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgAAAAADeQAAAAAATQAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKgAAAAAAeQAAAAAATQAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAATQAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATQAAAAAATQAAAAAATQAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + version: 6 + 2,1: + ind: 2,1 + tiles: UgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWQAAAAABWQAAAAACWQAAAAADUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYwAAAAAAYwAAAAABWQAAAAADUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYwAAAAADYwAAAAABWQAAAAABUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYwAAAAABYwAAAAAAWQAAAAABUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWQAAAAACWQAAAAABWQAAAAACUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgAAAAACHQAAAAAAIgAAAAADUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgAAAAACHQAAAAACIgAAAAADUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHQAAAAABHQAAAAADHQAAAAADUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgAAAAABIgAAAAACIgAAAAADUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHQAAAAAAHQAAAAADHQAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + version: 6 + 2,2: + ind: 2,2 + tiles: UgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATQAAAAAAHQAAAAADHQAAAAABUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgAAAAABeQAAAAAAHQAAAAADUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATQAAAAAAHQAAAAAAHQAAAAADUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + version: 6 + 2,3: + ind: 2,3 + tiles: UgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAUgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + version: 6 + - type: Gravity + gravityShakeSound: !type:SoundPathSpecifier + path: /Audio/Effects/alert.ogg + - type: DecalGrid + chunkCollection: + version: 2 + nodes: + - node: + color: '#52B4E996' + id: BotGreyscale + decals: + 680: 30,25 + 681: 30,24 + 682: 28,27 + 683: 28,27 + 684: 28,28 + 685: 28,28 + 686: 30,25 + 687: 30,24 + - node: + color: '#DE3A3A96' + id: BotGreyscale + decals: + 478: 32,25 + 479: 32,24 + 480: 34,25 + 481: 34,24 + 482: 16,28 + 483: 18,28 + 484: 18,27 + 485: 16,27 + 486: 14,25 + 487: 14,24 + 488: 12,27 + 489: 12,28 + 748: 26,7 + 749: 32,7 + 750: 29,9 + 836: 6,2 + 837: 10,2 + 940: 1,9 + 941: 1,8 + 942: 1,7 + 943: 9,9 + 944: 9,8 + 945: 9,7 + 946: 20,10 + 947: 14,10 + 948: 14,6 + 949: 20,6 + 950: 22,6 + 951: 22,10 + 952: 12,10 + 953: 12,6 + - node: + color: '#FFFFFFFF' + id: BrickTileDarkCornerNe + decals: + 194: 21,4 + 250: 10,44 + 525: 12,32 + 543: 4,22 + 585: 6,40 + 659: 34,36 + 675: 30,28 + 703: 6,16 + 757: 10,10 + - node: + color: '#FFFFFFFF' + id: BrickTileDarkCornerNw + decals: + 191: 18,4 + 251: 12,44 + 519: 0,32 + 542: 0,22 + 582: 0,40 + 633: 24,36 + 702: 0,16 + 754: 0,10 + 853: 23,4 + - node: + color: '#FFFFFFFF' + id: BrickTileDarkCornerSe + decals: + 192: 21,0 + 238: 10,46 + 272: 14,42 + 524: 12,30 + 537: 4,18 + 584: 6,38 + 658: 34,34 + 701: 6,12 + 755: 10,6 + - node: + color: '#FFFFFFFF' + id: BrickTileDarkCornerSw + decals: + 193: 18,0 + 241: 12,46 + 270: 8,42 + 518: 0,30 + 534: 0,18 + 583: 0,38 + 632: 24,34 + 672: 28,24 + 700: 0,12 + 756: 0,6 + 852: 23,0 + - node: + color: '#FFFFFFFF' + id: BrickTileDarkLineE + decals: + 202: 21,1 + 203: 21,2 + 204: 21,3 + 239: 10,47 + 240: 10,48 + 273: 14,43 + 501: 0,25 + 502: 0,26 + 503: 0,27 + 535: 4,19 + 536: 4,21 + 592: 14,38 + 593: 14,40 + 674: 30,27 + 706: 6,15 + 707: 6,13 + 758: 10,9 + 759: 10,7 + 926: 5,45 + 929: 33,36 + 930: 33,34 + 931: 10,31 + 935: 8,10 + 936: 8,6 + 937: 7,2 + - node: + color: '#FFFFFFFF' + id: BrickTileDarkLineN + decals: + 197: 19,4 + 198: 20,4 + 248: 8,44 + 249: 9,44 + 252: 13,44 + 253: 14,44 + 506: 0,28 + 507: 2,28 + 520: 1,32 + 523: 11,32 + 540: 1,22 + 541: 3,22 + 634: 25,36 + 635: 26,36 + 704: 1,16 + 705: 5,16 + 762: 1,10 + 765: 9,10 + 846: 24,4 + 847: 25,4 + 848: 27,4 + 849: 28,4 + 850: 29,4 + 851: 30,4 + 927: 3,47 + - node: + color: '#FFFFFFFF' + id: BrickTileDarkLineS + decals: + 195: 20,0 + 196: 19,0 + 244: 8,46 + 245: 9,46 + 246: 13,46 + 247: 14,46 + 274: 9,42 + 275: 10,42 + 276: 13,42 + 277: 12,42 + 504: 0,24 + 505: 2,24 + 521: 1,30 + 522: 11,30 + 538: 3,18 + 539: 1,18 + 636: 25,34 + 637: 26,34 + 710: 1,12 + 711: 5,12 + 763: 1,6 + 764: 9,6 + 840: 25,0 + 841: 24,0 + 842: 27,0 + 843: 28,0 + 844: 29,0 + 845: 30,0 + 928: 3,43 + - node: + color: '#FFFFFFFF' + id: BrickTileDarkLineW + decals: + 199: 18,3 + 200: 18,2 + 201: 18,1 + 242: 12,47 + 243: 12,48 + 271: 8,43 + 498: 2,27 + 499: 2,26 + 500: 2,25 + 544: 0,19 + 545: 0,21 + 590: 8,40 + 591: 8,38 + 673: 28,25 + 708: 0,13 + 709: 0,15 + 760: 0,7 + 761: 0,9 + 854: 23,3 + 855: 23,1 + 925: 1,45 + 932: 2,31 + 933: 2,10 + 934: 2,6 + 938: 9,2 + 939: 30,2 + - node: + color: '#FFFFFFFF' + id: BrickTileSteelCornerNe + decals: + 84: 18,36 + 104: 16,25 + 336: 9,21 + 337: 10,22 + 364: 16,22 + 374: 21,15 + 375: 22,16 + 415: 21,21 + 421: 22,22 + 446: 33,21 + 447: 34,22 + 490: 12,25 + 554: 22,9 + 568: 14,9 + 610: 22,40 + 2025: 30,48 + 2039: 30,44 + 2058: 29,47 + - node: + color: '#FFFFFFFF' + id: BrickTileSteelCornerNw + decals: + 85: 16,36 + 103: 18,25 + 338: 7,21 + 339: 6,22 + 376: 17,15 + 377: 16,16 + 414: 19,21 + 420: 18,22 + 448: 31,21 + 449: 30,22 + 555: 20,9 + 569: 12,9 + 611: 16,40 + 2040: 28,44 + 2059: 28,47 + - node: + color: '#FFFFFFFF' + id: BrickTileSteelCornerSe + decals: + 83: 18,34 + 308: 26,30 + 332: 9,19 + 335: 10,18 + 372: 21,13 + 373: 22,12 + 418: 21,19 + 419: 22,18 + 452: 33,19 + 453: 34,18 + 561: 22,7 + 562: 14,7 + 609: 22,38 + 778: 16,0 + 779: 5,0 + 2038: 30,42 + 2056: 29,46 + - node: + color: '#FFFFFFFF' + id: BrickTileSteelCornerSw + decals: + 81: 16,34 + 82: 3,35 + 309: 14,30 + 333: 7,19 + 334: 6,18 + 370: 17,13 + 371: 16,12 + 416: 18,18 + 417: 19,19 + 450: 31,19 + 451: 30,18 + 493: 14,27 + 558: 20,7 + 567: 12,7 + 608: 16,38 + 780: 0,0 + 781: 11,0 + 2037: 28,42 + 2057: 28,46 + - node: + color: '#FFFFFFFF' + id: BrickTileSteelEndE + decals: + 73: 21,39 + - node: + color: '#FFFFFFFF' + id: BrickTileSteelEndW + decals: + 74: 17,39 + - node: + color: '#D4D4D496' + id: BrickTileSteelLineE + decals: + 1330: 32,2 + 1332: 31,2 + - node: + color: '#FFFFFFFF' + id: BrickTileSteelLineE + decals: + 87: 18,35 + 93: 14,34 + 94: 14,35 + 95: 14,36 + 105: 16,24 + 286: 17,45 + 287: 17,46 + 288: 17,47 + 323: 27,20 + 349: 9,20 + 350: 10,21 + 351: 10,19 + 365: 16,21 + 378: 21,14 + 379: 22,13 + 380: 22,15 + 425: 21,20 + 426: 22,19 + 427: 22,21 + 454: 34,19 + 455: 34,21 + 462: 33,20 + 491: 12,24 + 559: 22,8 + 566: 14,8 + 802: 16,1 + 806: 5,1 + 872: 34,0 + 873: 34,1 + 874: 34,3 + 875: 34,4 + 1328: 33,2 + 2023: 30,46 + 2024: 30,47 + 2041: 30,43 + - node: + color: '#FFFFFFFF' + id: BrickTileSteelLineN + decals: + 75: 18,39 + 76: 19,39 + 77: 20,39 + 86: 17,36 + 340: 8,21 + 341: 9,22 + 342: 7,22 + 366: 15,22 + 391: 18,15 + 392: 19,15 + 393: 20,15 + 394: 21,16 + 395: 20,16 + 396: 18,16 + 397: 17,16 + 422: 20,21 + 423: 19,22 + 424: 21,22 + 458: 31,22 + 459: 33,22 + 460: 32,21 + 556: 21,9 + 564: 13,9 + 572: 16,10 + 573: 18,10 + 612: 17,40 + 613: 18,40 + 614: 21,40 + 615: 20,40 + 2026: 29,48 + 2027: 28,48 + 2044: 29,44 + - node: + color: '#FFFFFFFF' + id: BrickTileSteelLineS + decals: + 78: 18,39 + 79: 19,39 + 80: 20,39 + 89: 17,34 + 298: 15,30 + 299: 16,30 + 300: 17,30 + 301: 18,30 + 302: 19,30 + 303: 21,30 + 304: 22,30 + 305: 23,30 + 306: 24,30 + 307: 25,30 + 346: 8,19 + 347: 7,18 + 348: 9,18 + 384: 18,13 + 385: 19,13 + 386: 20,13 + 387: 20,12 + 388: 21,12 + 389: 18,12 + 390: 17,12 + 431: 20,19 + 432: 19,18 + 433: 21,18 + 463: 32,19 + 464: 31,18 + 465: 33,18 + 557: 21,7 + 563: 13,7 + 570: 16,6 + 571: 18,6 + 616: 17,38 + 617: 18,38 + 618: 21,38 + 619: 20,38 + 782: 1,0 + 783: 2,0 + 784: 3,0 + 785: 4,0 + 786: 0,3 + 787: 1,3 + 788: 2,3 + 789: 3,3 + 790: 4,3 + 791: 5,3 + 792: 12,0 + 793: 13,0 + 794: 14,0 + 795: 15,0 + 796: 11,3 + 797: 12,3 + 798: 13,3 + 799: 14,3 + 800: 15,3 + 801: 16,3 + 2043: 29,42 + - node: + color: '#D4D4D496' + id: BrickTileSteelLineW + decals: + 1329: 33,2 + 1331: 32,2 + 1333: 31,2 + - node: + color: '#FFFFFFFF' + id: BrickTileSteelLineW + decals: + 88: 16,35 + 90: 20,34 + 91: 20,35 + 92: 20,36 + 102: 18,24 + 289: 21,45 + 290: 21,46 + 291: 21,47 + 322: 25,20 + 326: 29,15 + 327: 29,14 + 328: 29,13 + 343: 7,20 + 344: 6,21 + 345: 6,19 + 381: 17,14 + 382: 16,15 + 383: 16,13 + 428: 19,20 + 429: 18,19 + 430: 18,21 + 456: 30,19 + 457: 30,21 + 461: 31,20 + 492: 14,28 + 560: 20,8 + 565: 12,8 + 803: 0,1 + 805: 11,1 + 2021: 24,44 + 2022: 24,46 + 2042: 28,43 + - node: + color: '#52B4E996' + id: BrickTileWhiteCornerNe + decals: + 264: 10,44 + 679: 30,28 + - node: + color: '#DE3A3A96' + id: BrickTileWhiteCornerNe + decals: + 109: 16,25 + 362: 10,22 + 367: 16,22 + 413: 22,16 + 438: 22,22 + 475: 34,22 + 494: 12,25 + 527: 12,32 + 580: 4,22 + 587: 6,40 + 630: 22,40 + 660: 34,36 + 715: 6,16 + 777: 10,10 + 2045: 30,48 + - node: + color: '#FFFFFFFF' + id: BrickTileWhiteCornerNe + decals: + 689: 10,28 + - node: + color: '#52B4E996' + id: BrickTileWhiteCornerNw + decals: + 265: 12,44 + - node: + color: '#DE3A3A96' + id: BrickTileWhiteCornerNw + decals: + 108: 18,25 + 363: 6,22 + 412: 16,16 + 442: 18,22 + 476: 30,22 + 532: 0,32 + 579: 0,22 + 588: 0,40 + 629: 16,40 + 639: 24,36 + 714: 0,16 + 772: 0,10 + 858: 23,4 + - node: + color: '#FFFFFFFF' + id: BrickTileWhiteCornerNw + decals: + 688: 8,28 + - node: + color: '#52B4E996' + id: BrickTileWhiteCornerSe + decals: + 263: 10,46 + 279: 14,42 + - node: + color: '#DE3A3A96' + id: BrickTileWhiteCornerSe + decals: + 361: 10,18 + 411: 22,12 + 443: 22,18 + 477: 34,18 + 526: 12,30 + 581: 4,18 + 586: 6,38 + 628: 22,38 + 661: 34,34 + 713: 6,12 + 771: 10,6 + 809: 5,0 + 810: 16,0 + - node: + color: '#EFB34196' + id: BrickTileWhiteCornerSe + decals: + 311: 26,30 + - node: + color: '#FFFFFFFF' + id: BrickTileWhiteCornerSe + decals: + 690: 10,24 + - node: + color: '#52B4E996' + id: BrickTileWhiteCornerSw + decals: + 262: 12,46 + 278: 8,42 + 676: 28,24 + - node: + color: '#DE3A3A96' + id: BrickTileWhiteCornerSw + decals: + 360: 6,18 + 406: 16,12 + 439: 18,18 + 474: 30,18 + 496: 14,27 + 533: 0,30 + 578: 0,18 + 589: 0,38 + 631: 16,38 + 638: 24,34 + 712: 0,12 + 770: 0,6 + 807: 0,0 + 808: 11,0 + 859: 23,0 + - node: + color: '#EFB34196' + id: BrickTileWhiteCornerSw + decals: + 310: 14,30 + - node: + color: '#FFFFFFFF' + id: BrickTileWhiteCornerSw + decals: + 691: 8,24 + - node: + color: '#52B4E996' + id: BrickTileWhiteLineE + decals: + 258: 10,47 + 259: 10,48 + 280: 14,43 + 678: 30,27 + - node: + color: '#D4D4D419' + id: BrickTileWhiteLineE + decals: + 895: 2,6 + 896: 2,10 + 900: 9,2 + 901: 30,2 + 906: 1,45 + 908: 2,31 + - node: + color: '#DE3A3A96' + id: BrickTileWhiteLineE + decals: + 96: 14,36 + 97: 14,35 + 98: 14,34 + 106: 16,24 + 356: 10,19 + 357: 10,21 + 368: 16,21 + 409: 22,15 + 410: 22,13 + 444: 22,19 + 445: 22,21 + 470: 34,21 + 471: 34,19 + 495: 12,24 + 511: 0,25 + 512: 0,26 + 513: 0,27 + 548: 4,19 + 549: 4,21 + 596: 14,38 + 597: 14,40 + 716: 6,15 + 717: 6,13 + 768: 10,9 + 769: 10,7 + 811: 5,1 + 812: 16,1 + 876: 34,0 + 877: 34,1 + 878: 34,3 + 879: 34,4 + 2048: 30,47 + 2049: 30,46 + - node: + color: '#EFB34196' + id: BrickTileWhiteLineE + decals: + 292: 17,45 + 293: 17,46 + 294: 17,47 + 324: 27,20 + - node: + color: '#FFFFFFFF' + id: BrickTileWhiteLineE + decals: + 693: 10,27 + 694: 10,26 + 695: 10,25 + - node: + color: '#52B4E996' + id: BrickTileWhiteLineN + decals: + 266: 9,44 + 267: 8,44 + 268: 13,44 + 269: 14,44 + - node: + color: '#D4D4D419' + id: BrickTileWhiteLineN + decals: + 907: 3,43 + - node: + color: '#DE3A3A96' + id: BrickTileWhiteLineN + decals: + 358: 9,22 + 359: 7,22 + 369: 15,22 + 398: 18,16 + 399: 17,16 + 400: 21,16 + 401: 20,16 + 436: 19,22 + 437: 21,22 + 468: 31,22 + 469: 33,22 + 516: 0,28 + 517: 2,28 + 528: 11,32 + 531: 1,32 + 552: 1,22 + 553: 3,22 + 574: 16,10 + 575: 18,10 + 620: 18,40 + 621: 17,40 + 622: 20,40 + 623: 21,40 + 642: 25,36 + 643: 26,36 + 722: 1,16 + 723: 5,16 + 773: 1,10 + 774: 9,10 + 866: 24,4 + 867: 25,4 + 868: 27,4 + 869: 28,4 + 870: 30,4 + 871: 29,4 + 2046: 28,48 + 2047: 29,48 + - node: + color: '#FFFFFFFF' + id: BrickTileWhiteLineN + decals: + 692: 9,28 + - node: + color: '#52B4E996' + id: BrickTileWhiteLineS + decals: + 254: 8,46 + 255: 9,46 + 256: 13,46 + 257: 14,46 + 282: 9,42 + 283: 10,42 + 284: 12,42 + 285: 13,42 + - node: + color: '#D4D4D419' + id: BrickTileWhiteLineS + decals: + 905: 3,47 + - node: + color: '#DE3A3A96' + id: BrickTileWhiteLineS + decals: + 352: 7,18 + 353: 9,18 + 402: 17,12 + 403: 18,12 + 404: 20,12 + 405: 21,12 + 434: 19,18 + 435: 21,18 + 466: 33,18 + 467: 31,18 + 514: 0,24 + 515: 2,24 + 529: 11,30 + 530: 1,30 + 550: 1,18 + 551: 3,18 + 576: 16,6 + 577: 18,6 + 624: 17,38 + 625: 18,38 + 626: 20,38 + 627: 21,38 + 640: 25,34 + 641: 26,34 + 718: 5,12 + 719: 1,12 + 775: 1,6 + 776: 9,6 + 813: 12,0 + 814: 13,0 + 815: 14,0 + 816: 15,0 + 817: 16,3 + 818: 15,3 + 819: 13,3 + 820: 14,3 + 821: 11,3 + 822: 12,3 + 823: 0,3 + 824: 1,3 + 825: 2,3 + 826: 3,3 + 827: 4,3 + 828: 5,3 + 829: 4,0 + 830: 3,0 + 831: 2,0 + 832: 1,0 + 860: 24,0 + 861: 25,0 + 862: 27,0 + 863: 28,0 + 864: 29,0 + 865: 30,0 + - node: + color: '#EFB34196' + id: BrickTileWhiteLineS + decals: + 312: 15,30 + 313: 16,30 + 314: 17,30 + 315: 18,30 + 316: 19,30 + 317: 21,30 + 318: 22,30 + 319: 23,30 + 320: 24,30 + 321: 25,30 + - node: + color: '#FFFFFFFF' + id: BrickTileWhiteLineS + decals: + 696: 9,24 + - node: + color: '#52B4E996' + id: BrickTileWhiteLineW + decals: + 260: 12,47 + 261: 12,48 + 281: 8,43 + 677: 28,25 + - node: + color: '#D4D4D419' + id: BrickTileWhiteLineW + decals: + 897: 8,10 + 898: 8,6 + 899: 7,2 + 902: 33,36 + 903: 33,34 + 904: 5,45 + 909: 10,31 + - node: + color: '#DE3A3A96' + id: BrickTileWhiteLineW + decals: + 99: 20,36 + 100: 20,35 + 101: 20,34 + 107: 18,24 + 354: 6,19 + 355: 6,21 + 407: 16,13 + 408: 16,15 + 440: 18,19 + 441: 18,21 + 472: 30,19 + 473: 30,21 + 497: 14,28 + 508: 2,25 + 509: 2,26 + 510: 2,27 + 546: 0,19 + 547: 0,21 + 594: 8,38 + 595: 8,40 + 720: 0,13 + 721: 0,15 + 766: 0,7 + 767: 0,9 + 804: 0,1 + 833: 11,1 + 856: 23,1 + 857: 23,3 + 2050: 24,44 + 2051: 24,46 + - node: + color: '#EFB34196' + id: BrickTileWhiteLineW + decals: + 295: 21,45 + 296: 21,46 + 297: 21,47 + 325: 25,20 + 329: 29,13 + 330: 29,14 + 331: 29,15 + - node: + color: '#FFFFFFFF' + id: BrickTileWhiteLineW + decals: + 697: 8,25 + 698: 8,26 + 699: 8,27 + - node: + cleanable: True + angle: 1.5707963267948966 rad + color: '#B02E269B' + id: Clandestine + decals: + 2136: 3.132535,34.09553 + - node: + color: '#A4610696' + id: Dirt + decals: + 1334: 31,2 + 1335: 32,2 + 1336: 33,2 + 1337: 30,2 + 1338: 34,2 + 1339: 33,3 + 1340: 31,4 + 1341: 32,3 + 1342: 32,1 + 1343: 31,1 + 1344: 31,0 + 1345: 33,0 + 1346: 32,0 + 1347: 33,3 + 1348: 33,4 + 1349: 32,4 + 1350: 31,3 + 1351: 32,1 + 1352: 33,1 + - node: + cleanable: True + color: '#A4610696' + id: Dirt + decals: + 954: 0,45 + 955: 3,47 + 956: 3,48 + 957: 6,45 + 958: 3,42 + 959: 3,43 + 960: 5,45 + 961: 1,45 + 962: 13,45 + 963: 12,45 + 964: 11,47 + 965: 9,45 + 966: 8,45 + 967: 8,46 + 968: 10,47 + 969: 13,47 + 970: 14,46 + 971: 11,43 + 972: 11,42 + 973: 9,42 + 974: 10,42 + 975: 13,42 + 976: 12,42 + 977: 13,43 + 978: 9,43 + 979: 11,44 + 980: 11,45 + 981: 11,47 + 982: 9,46 + 983: 11,46 + 984: 16,45 + 985: 16,46 + 986: 16,47 + 987: 16,44 + 988: 17,45 + 989: 17,46 + 990: 21,45 + 991: 22,45 + 992: 22,44 + 993: 22,43 + 994: 22,46 + 995: 21,45 + 996: 21,46 + 997: 22,47 + 998: 24,39 + 999: 24,38 + 1000: 24,40 + 1001: 27,39 + 1002: 27,39 + 1003: 30,40 + 1004: 30,39 + 1005: 30,38 + 1006: 30,39 + 1007: 34,35 + 1008: 33,34 + 1009: 33,36 + 1010: 24,35 + 1011: 22,35 + 1012: 20,35 + 1013: 21,35 + 1014: 20,34 + 1015: 14,35 + 1016: 13,35 + 1017: 12,35 + 1018: 14,36 + 1019: 17,36 + 1020: 17,35 + 1021: 17,34 + 1022: 16,35 + 1023: 18,35 + 1024: 18,36 + 1025: 18,34 + 1026: 16,34 + 1027: 16,36 + 1028: 16,30 + 1029: 16,30 + 1030: 18,30 + 1031: 19,30 + 1032: 22,30 + 1033: 24,30 + 1034: 25,30 + 1035: 26,30 + 1036: 20,30 + 1037: 20,30 + 1038: 15,30 + 1039: 14,30 + 1040: 14,31 + 1041: 26,31 + 1042: 10,31 + 1043: 2,31 + 1044: 0,31 + 1045: 12,31 + 1046: 5,35 + 1047: 6,36 + 1048: 4,34 + 1049: 9,35 + 1050: 9,35 + 1051: 10,35 + 1052: 10,34 + 1053: 0,35 + 1054: 0,36 + 1055: 0,35 + 1056: 5,36 + 1057: 6,36 + 1058: 6,39 + 1059: 0,39 + 1060: 8,39 + 1061: 8,40 + 1062: 8,38 + 1063: 9,39 + 1064: 11,38 + 1065: 9,38 + 1066: 11,40 + 1067: 10,40 + 1068: 13,39 + 1069: 14,39 + 1070: 14,40 + 1071: 14,38 + 1072: 13,39 + 1073: 13,40 + 1074: 16,39 + 1075: 18,40 + 1076: 19,40 + 1077: 19,39 + 1078: 18,39 + 1079: 17,39 + 1080: 20,39 + 1081: 21,39 + 1082: 19,38 + 1083: 22,39 + 1084: 21,38 + 1085: 24,39 + 1086: 24,38 + 1087: 24,40 + 1088: 17,32 + 1089: 24,32 + 1090: 23,32 + 1091: 34,26 + 1092: 33,26 + 1093: 33,28 + 1094: 32,28 + 1095: 32,27 + 1096: 34,27 + 1097: 34,28 + 1098: 32,25 + 1099: 33,24 + 1100: 33,25 + 1101: 32,26 + 1102: 29,24 + 1103: 30,26 + 1104: 28,26 + 1105: 29,28 + 1106: 29,27 + 1107: 29,25 + 1108: 25,26 + 1109: 24,26 + 1110: 26,26 + 1111: 25,27 + 1112: 24,28 + 1113: 25,28 + 1114: 24,27 + 1115: 24,24 + 1116: 25,25 + 1117: 25,24 + 1118: 26,25 + 1119: 22,26 + 1120: 24,26 + 1121: 26,26 + 1122: 20,26 + 1123: 21,25 + 1124: 21,24 + 1125: 21,28 + 1126: 21,27 + 1127: 18,26 + 1128: 16,26 + 1129: 17,27 + 1130: 17,28 + 1131: 17,25 + 1132: 17,24 + 1133: 17,26 + 1134: 13,28 + 1135: 13,27 + 1136: 13,26 + 1137: 13,25 + 1138: 13,24 + 1139: 12,26 + 1140: 14,26 + 1141: 9,26 + 1142: 9,27 + 1143: 9,28 + 1144: 8,28 + 1145: 8,27 + 1146: 8,26 + 1147: 9,25 + 1148: 8,25 + 1149: 8,24 + 1150: 9,24 + 1151: 10,24 + 1152: 10,25 + 1153: 10,26 + 1154: 10,27 + 1155: 10,28 + 1156: 5,28 + 1157: 5,28 + 1158: 5,24 + 1159: 5,24 + 1160: 0,26 + 1161: 2,26 + 1162: 2,28 + 1163: 1,24 + 1164: 2,24 + 1165: 2,22 + 1166: 4,20 + 1167: 2,18 + 1168: 1,19 + 1169: 0,20 + 1170: 0,17 + 1171: 0,18 + 1172: 4,18 + 1173: 4,19 + 1174: 4,22 + 1175: 0,22 + 1176: 6,20 + 1177: 7,20 + 1178: 8,20 + 1179: 9,20 + 1180: 10,20 + 1181: 8,21 + 1182: 8,22 + 1183: 8,19 + 1184: 8,18 + 1185: 6,18 + 1186: 7,19 + 1187: 7,18 + 1188: 9,20 + 1189: 10,21 + 1190: 9,21 + 1191: 9,22 + 1192: 9,19 + 1193: 7,21 + 1194: 8,13 + 1195: 8,13 + 1196: 11,16 + 1197: 11,16 + 1198: 10,16 + 1199: 9,16 + 1200: 8,15 + 1201: 8,14 + 1202: 8,16 + 1203: 10,16 + 1204: 9,16 + 1205: 12,14 + 1206: 11,14 + 1207: 11,12 + 1208: 10,12 + 1209: 14,12 + 1210: 14,14 + 1211: 14,15 + 1212: 13,14 + 1213: 12,14 + 1214: 11,14 + 1215: 11,13 + 1216: 6,14 + 1217: 0,14 + 1218: 3,16 + 1219: 3,12 + 1220: 3,13 + 1221: 2,13 + 1222: 4,13 + 1223: 3,15 + 1224: 2,15 + 1225: 4,15 + 1226: 5,14 + 1227: 1,14 + 1228: 1,15 + 1229: 1,13 + 1230: 5,15 + 1231: 5,13 + 1232: 0,8 + 1233: 2,10 + 1234: 2,6 + 1235: 8,6 + 1236: 8,10 + 1237: 0,8 + 1238: 10,8 + 1239: 10,10 + 1240: 10,7 + 1241: 10,6 + 1242: 9,6 + 1243: 9,10 + 1244: 1,10 + 1245: 0,7 + 1246: 0,6 + 1247: 0,3 + 1248: 1,3 + 1249: 1,3 + 1250: 2,3 + 1251: 3,3 + 1252: 4,3 + 1253: 4,3 + 1254: 5,3 + 1255: 7,2 + 1256: 9,2 + 1257: 4,0 + 1258: 4,1 + 1259: 2,1 + 1260: 1,1 + 1261: 0,2 + 1262: 1,2 + 1263: 2,2 + 1264: 5,2 + 1265: 4,2 + 1266: 3,2 + 1267: 4,1 + 1268: 3,1 + 1269: 11,2 + 1270: 12,3 + 1271: 11,3 + 1272: 14,3 + 1273: 14,3 + 1274: 15,3 + 1275: 16,3 + 1276: 13,3 + 1277: 11,3 + 1278: 12,2 + 1279: 11,1 + 1280: 13,1 + 1281: 16,2 + 1282: 16,1 + 1283: 14,1 + 1284: 13,2 + 1285: 14,2 + 1286: 18,2 + 1287: 19,2 + 1288: 20,2 + 1289: 21,2 + 1290: 19,3 + 1291: 18,3 + 1292: 20,3 + 1293: 20,1 + 1294: 21,1 + 1295: 19,1 + 1296: 23,2 + 1297: 28,2 + 1298: 29,2 + 1299: 30,2 + 1300: 28,4 + 1301: 29,4 + 1302: 26,4 + 1303: 26,2 + 1304: 26,1 + 1305: 26,0 + 1306: 24,2 + 1307: 25,2 + 1308: 27,2 + 1309: 24,4 + 1310: 23,3 + 1311: 24,3 + 1312: 23,4 + 1469: 14,8 + 1470: 12,8 + 1471: 12,7 + 1472: 13,7 + 1473: 13,6 + 1474: 13,10 + 1475: 12,9 + 1476: 14,9 + 1477: 17,10 + 1478: 17,9 + 1479: 18,9 + 1480: 18,8 + 1481: 17,7 + 1482: 17,7 + 1483: 17,6 + 1484: 17,8 + 1485: 16,7 + 1486: 18,7 + 1487: 20,9 + 1488: 20,8 + 1489: 21,10 + 1490: 21,9 + 1491: 21,7 + 1492: 21,6 + 1493: 20,7 + 1494: 22,7 + 1495: 22,8 + 1534: 19,16 + 1535: 18,15 + 1536: 19,13 + 1537: 19,12 + 1538: 22,14 + 1539: 22,14 + 1540: 22,15 + 1541: 21,16 + 1542: 21,15 + 1543: 21,13 + 1544: 16,14 + 1545: 17,13 + 1546: 17,15 + 1556: 30,20 + 1557: 32,22 + 1558: 31,22 + 1559: 31,21 + 1560: 32,19 + 1561: 32,18 + 1562: 34,20 + 1563: 34,19 + 1564: 34,18 + 1565: 34,21 + 1566: 34,22 + 2060: 24,45 + 2061: 25,45 + 2062: 27,48 + 2063: 27,42 + 2064: 30,45 + 2065: 28,43 + 2066: 29,43 + 2067: 30,43 + 2068: 24,42 + 2069: 25,43 + 2070: 26,42 + 2071: 25,42 + 2072: 26,48 + 2073: 25,47 + 2074: 26,47 + 2075: 25,47 + 2076: 25,48 + 2077: 27,47 + 2078: 27,45 + 2079: 27,46 + 2080: 26,45 + 2081: 28,45 + 2082: 26,44 + 2083: 27,44 + 2084: 25,46 + 2085: 26,46 + 2086: 29,45 + - node: + color: '#A4610696' + id: DirtHeavy + decals: + 1353: 11,31 + 1354: 1,31 + 1355: 0,39 + 1356: 6,39 + 1357: 5,45 + 1358: 14,39 + 1359: 16,39 + 1360: 19,38 + - node: + cleanable: True + color: '#A4610696' + id: DirtHeavy + decals: + 1361: 22,39 + 1362: 19,40 + 1363: 30,39 + 1364: 25,35 + 1365: 24,36 + 1366: 20,30 + 1367: 14,30 + 1368: 18,30 + 1369: 22,30 + 1370: 26,31 + 1371: 22,26 + 1372: 24,26 + 1373: 26,26 + 1374: 28,26 + 1375: 29,27 + 1376: 33,25 + 1377: 33,28 + 1378: 32,28 + 1379: 14,26 + 1380: 12,26 + 1381: 9,26 + 1382: 9,28 + 1383: 8,28 + 1384: 8,24 + 1385: 10,25 + 1386: 5,24 + 1387: 5,28 + 1388: 0,26 + 1389: 0,27 + 1390: 2,24 + 1391: 2,25 + 1392: 0,35 + 1393: 0,36 + 1394: 7,36 + 1395: 5,35 + 1396: 4,34 + 1397: 10,35 + 1398: 9,35 + 1399: 8,39 + 1400: 14,39 + 1401: 14,38 + 1402: 8,45 + 1403: 11,48 + 1404: 11,44 + 1405: 14,45 + 1406: 11,42 + 1407: 9,42 + 1408: 13,43 + 1409: 3,47 + 1410: 4,20 + 1411: 3,22 + 1412: 2,18 + 1413: 0,19 + 1414: 6,20 + 1415: 8,20 + 1416: 8,18 + 1417: 10,20 + 1418: 8,22 + 1419: 14,20 + 1420: 15,21 + 1421: 16,20 + 1422: 12,20 + 1423: 12,21 + 1424: 13,22 + 1425: 13,21 + 1426: 13,19 + 1427: 16,19 + 1428: 16,19 + 1429: 15,20 + 1430: 14,18 + 1431: 12,20 + 1432: 14,22 + 1433: 20,20 + 1434: 20,22 + 1435: 18,20 + 1436: 22,21 + 1437: 19,21 + 1438: 20,18 + 1439: 22,20 + 1440: 27,22 + 1441: 25,22 + 1442: 26,18 + 1443: 27,18 + 1444: 31,18 + 1445: 32,18 + 1446: 31,20 + 1447: 32,21 + 1448: 34,22 + 1449: 34,20 + 1450: 32,19 + 1451: 29,14 + 1452: 24,14 + 1453: 24,13 + 1454: 30,14 + 1455: 29,13 + 1456: 34,2 + 1457: 32,3 + 1458: 30,1 + 1459: 26,1 + 1460: 23,3 + 1461: 24,4 + 1462: 29,4 + 1463: 26,0 + 1464: 26,1 + 1465: 18,2 + 1466: 22,8 + 1467: 20,8 + 1468: 16,8 + 1496: 17,6 + 1497: 16,8 + 1498: 18,8 + 1499: 17,10 + 1500: 14,8 + 1501: 12,8 + 1502: 13,6 + 1503: 13,10 + 1504: 21,10 + 1505: 21,9 + 1506: 21,8 + 1507: 21,7 + 1508: 21,6 + 1509: 10,8 + 1510: 9,10 + 1511: 9,6 + 1512: 2,6 + 1513: 0,7 + 1514: 0,9 + 1515: 1,10 + 1516: 8,10 + 1517: 0,14 + 1518: 5,14 + 1519: 4,15 + 1520: 3,16 + 1521: 1,18 + 1522: 5,15 + 1523: 6,14 + 1524: 11,16 + 1525: 8,13 + 1526: 11,12 + 1527: 10,13 + 1528: 16,14 + 1529: 19,16 + 1530: 19,13 + 1531: 18,13 + 1532: 18,15 + 1533: 16,15 + 1547: 19,16 + 1548: 16,14 + 1549: 17,15 + 1550: 18,14 + 1551: 19,13 + 1552: 21,13 + 1553: 22,14 + 1554: 29,15 + 1555: 29,13 + 2095: 24,45 + 2096: 27,48 + 2097: 27,42 + 2098: 25,44 + 2099: 29,45 + 2100: 29,46 + 2101: 29,47 + 2102: 25,45 + - node: + cleanable: True + color: '#A4610696' + id: DirtLight + decals: + 1567: 0,2 + 1568: 5,2 + 1569: 4,1 + 1570: 5,2 + 1571: 2,1 + 1572: 7,2 + 1573: 9,2 + 1574: 11,2 + 1575: 14,2 + 1576: 13,3 + 1577: 10,3 + 1578: 11,3 + 1579: 12,3 + 1580: 16,3 + 1581: 15,3 + 1582: 16,2 + 1583: 16,1 + 1584: 14,1 + 1585: 15,1 + 1586: 1,3 + 1587: 3,3 + 1588: 5,3 + 1589: 5,3 + 1590: 1,1 + 1591: 23,2 + 1592: 26,2 + 1593: 28,2 + 1594: 28,4 + 1595: 30,2 + 1596: 31,1 + 1597: 34,1 + 1598: 33,0 + 1599: 34,3 + 1600: 21,9 + 1601: 20,8 + 1602: 22,8 + 1603: 16,8 + 1604: 17,9 + 1605: 17,10 + 1606: 17,6 + 1607: 12,8 + 1608: 14,9 + 1609: 14,8 + 1610: 13,6 + 1611: 10,8 + 1612: 8,10 + 1613: 2,10 + 1614: 0,8 + 1615: 0,9 + 1616: 2,6 + 1617: 0,14 + 1618: 3,12 + 1619: 1,14 + 1620: 3,15 + 1621: 5,14 + 1622: 5,15 + 1623: 6,14 + 1624: 3,13 + 1625: 3,12 + 1626: 8,13 + 1627: 10,16 + 1628: 11,16 + 1629: 17,14 + 1630: 17,13 + 1631: 16,15 + 1632: 19,16 + 1633: 22,14 + 1634: 21,14 + 1635: 21,13 + 1636: 19,13 + 1637: 20,12 + 1638: 20,13 + 1639: 21,15 + 1640: 29,14 + 1641: 29,15 + 1642: 34,20 + 1643: 32,18 + 1644: 34,18 + 1645: 34,21 + 1646: 32,22 + 1647: 30,20 + 1648: 30,21 + 1649: 32,19 + 1650: 32,21 + 1651: 32,20 + 1652: 30,18 + 1653: 26,22 + 1654: 25,22 + 1655: 25,20 + 1656: 27,20 + 1657: 27,22 + 1658: 25,20 + 1659: 27,20 + 1660: 26,18 + 1661: 27,18 + 1662: 25,18 + 1663: 22,20 + 1664: 18,20 + 1665: 20,22 + 1666: 20,20 + 1667: 19,19 + 1668: 20,19 + 1669: 20,21 + 1670: 21,20 + 1671: 16,20 + 1672: 16,21 + 1673: 12,20 + 1674: 13,21 + 1675: 13,22 + 1676: 13,19 + 1677: 13,19 + 1678: 14,20 + 1679: 14,18 + 1680: 10,20 + 1681: 10,21 + 1682: 8,22 + 1683: 6,21 + 1684: 6,20 + 1685: 7,20 + 1686: 7,19 + 1687: 8,20 + 1688: 9,20 + 1689: 4,20 + 1690: 3,18 + 1691: 2,18 + 1692: 1,18 + 1693: 0,21 + 1694: 1,22 + 1695: 0,20 + 1696: 2,22 + 1697: 1,24 + 1698: 0,26 + 1699: 0,25 + 1700: 2,25 + 1701: 2,27 + 1702: 5,28 + 1703: 5,24 + 1704: 9,26 + 1705: 9,27 + 1706: 9,24 + 1707: 8,24 + 1708: 10,24 + 1709: 10,26 + 1710: 13,26 + 1711: 12,26 + 1712: 13,28 + 1713: 12,28 + 1714: 13,24 + 1715: 14,26 + 1716: 13,25 + 1717: 18,26 + 1718: 16,26 + 1719: 17,28 + 1720: 17,25 + 1721: 17,24 + 1722: 20,26 + 1723: 22,26 + 1724: 21,26 + 1725: 21,27 + 1726: 21,24 + 1727: 21,25 + 1728: 25,26 + 1729: 26,26 + 1730: 25,26 + 1731: 25,26 + 1732: 25,24 + 1733: 25,24 + 1734: 26,25 + 1735: 24,27 + 1736: 29,26 + 1737: 29,27 + 1738: 30,26 + 1739: 29,24 + 1740: 33,26 + 1741: 33,24 + 1742: 34,26 + 1743: 32,26 + 1744: 34,28 + 1745: 34,28 + 1746: 33,28 + 1747: 32,28 + 1748: 26,31 + 1749: 21,30 + 1750: 20,30 + 1751: 17,30 + 1752: 15,30 + 1753: 14,31 + 1754: 10,31 + 1755: 2,31 + 1756: 11,30 + 1757: 12,31 + 1758: 0,32 + 1759: 1,32 + 1760: 0,31 + 1761: -1,35 + 1762: 0,36 + 1763: 0,35 + 1764: 4,34 + 1765: 5,35 + 1766: 3,34 + 1767: 10,34 + 1768: 10,35 + 1769: 6,39 + 1770: 0,39 + 1771: 0,45 + 1772: 3,43 + 1773: 3,47 + 1774: 4,45 + 1775: 5,45 + 1776: 8,45 + 1777: 11,46 + 1778: 11,45 + 1779: 11,42 + 1780: 13,42 + 1781: 9,42 + 1782: 14,45 + 1783: 10,44 + 1784: 11,44 + 1785: 11,45 + 1786: 16,47 + 1787: 16,45 + 1788: 17,45 + 1789: 21,45 + 1790: 16,48 + 1791: 16,43 + 1792: 23,43 + 1793: 22,42 + 1794: 30,38 + 1795: 30,40 + 1796: 24,38 + 1797: 22,39 + 1798: 19,38 + 1799: 19,39 + 1800: 18,39 + 1801: 16,39 + 1802: 18,38 + 1803: 17,38 + 1804: 14,38 + 1805: 14,40 + 1806: 9,39 + 1807: 10,40 + 1808: 12,38 + 1809: 10,38 + 1810: 10,38 + 1811: 6,39 + 1812: 0,39 + 2103: 24,45 + 2104: 30,45 + 2105: 26,44 + 2106: 27,43 + 2107: 30,43 + 2108: 29,42 + 2109: 28,47 + - node: + cleanable: True + color: '#A4610696' + id: DirtMedium + decals: + 1813: 0,35 + 1814: 0,39 + 1815: 6,39 + 1816: 3,43 + 1817: 1,45 + 1818: 11,45 + 1819: 11,42 + 1820: 14,45 + 1821: 16,45 + 1822: 21,45 + 1823: 22,45 + 1824: 21,38 + 1825: 20,38 + 1826: 20,38 + 1827: 24,38 + 1828: 30,39 + 1829: 34,35 + 1830: 33,36 + 1831: 33,34 + 1832: 24,34 + 1833: 20,34 + 1834: 22,35 + 1835: 17,35 + 1836: 16,34 + 1837: 17,36 + 1838: 12,35 + 1839: 10,35 + 1840: 5,35 + 1841: 0,35 + 1842: 1,31 + 1843: 2,31 + 1844: 11,31 + 1845: 9,26 + 1846: 8,26 + 1847: 9,27 + 1848: 9,28 + 1849: 10,26 + 1850: 10,25 + 1851: 9,25 + 1852: 13,26 + 1853: 12,26 + 1854: 13,28 + 1855: 14,26 + 1856: 17,24 + 1857: 18,26 + 1858: 17,28 + 1859: 16,26 + 1860: 22,26 + 1861: 20,26 + 1862: 25,27 + 1863: 24,27 + 1864: 24,28 + 1865: 24,25 + 1866: 25,25 + 1867: 25,24 + 1868: 24,24 + 1869: 26,25 + 1870: 26,26 + 1871: 25,26 + 1872: 24,26 + 1873: 24,26 + 1874: 25,26 + 1875: 25,26 + 1876: 28,26 + 1877: 30,26 + 1878: 29,27 + 1879: 29,25 + 1880: 33,26 + 1881: 34,26 + 1882: 34,28 + 1883: 32,28 + 1884: 34,28 + 1885: 32,20 + 1886: 31,20 + 1887: 30,20 + 1888: 30,18 + 1889: 34,18 + 1890: 34,21 + 1891: 25,18 + 1892: 26,18 + 1893: 25,18 + 1894: 27,20 + 1895: 27,22 + 1896: 29,15 + 1897: 30,14 + 1898: 29,12 + 1899: 24,14 + 1900: 24,15 + 1901: 24,16 + 1902: 20,20 + 1903: 17,20 + 1904: 19,21 + 1905: 18,20 + 1906: 21,22 + 1907: 22,20 + 1908: 22,21 + 1909: 20,19 + 1910: 15,20 + 1911: 14,21 + 1912: 15,21 + 1913: 16,21 + 1914: 14,22 + 1915: 12,20 + 1916: 14,18 + 1917: 15,18 + 1918: 16,19 + 1919: 16,19 + 1920: 13,19 + 1921: 12,19 + 1922: 12,19 + 1923: 12,18 + 1924: 14,18 + 1925: 15,18 + 1926: 15,18 + 1927: 10,20 + 1928: 8,22 + 1929: 7,20 + 1930: 9,21 + 1931: 7,19 + 1932: 10,19 + 1933: 10,21 + 1934: 7,18 + 1935: 4,20 + 1936: 4,21 + 1937: 2,22 + 1938: 1,22 + 1939: 0,19 + 1940: 1,18 + 1941: 1,18 + 1942: 3,18 + 1943: 0,14 + 1944: 2,13 + 1945: 3,13 + 1946: 5,13 + 1947: 3,12 + 1948: 8,15 + 1949: 8,16 + 1950: 9,16 + 1951: 9,16 + 1952: 18,14 + 1953: 19,16 + 1954: 19,14 + 1955: 20,13 + 1956: 18,16 + 1957: 17,13 + 1958: 20,12 + 1959: 22,13 + 1960: 24,13 + 1961: 30,12 + 1962: 30,14 + 1963: 29,16 + 1964: 24,16 + 1965: 24,16 + 1966: 25,6 + 1967: 24,7 + 1968: 26,9 + 1969: 27,10 + 1970: 28,10 + 1971: 34,10 + 1972: 34,8 + 1973: 34,7 + 1974: 33,6 + 1975: 30,6 + 1976: 27,6 + 1977: 21,8 + 1978: 16,8 + 1979: 17,10 + 1980: 18,8 + 1981: 17,6 + 1982: 13,7 + 1983: 13,9 + 1984: 13,8 + 1985: 14,7 + 1986: 10,6 + 1987: 8,10 + 1988: 2,6 + 1989: 0,7 + 1990: 1,1 + 1991: 0,1 + 1992: 4,1 + 1993: 5,2 + 1994: 3,1 + 1995: 3,0 + 1996: 7,2 + 1997: 9,2 + 1998: 11,2 + 1999: 13,1 + 2000: 15,1 + 2001: 16,2 + 2002: 16,1 + 2003: 23,2 + 2004: 26,2 + 2005: 29,3 + 2006: 30,2 + 2007: 33,2 + 2008: 33,3 + 2009: 34,2 + 2010: 34,0 + 2011: 34,1 + 2012: 31,1 + 2013: 29,0 + 2014: 26,0 + 2015: 23,3 + 2016: 28,4 + 2087: 27,42 + 2088: 27,48 + 2089: 24,45 + 2090: 30,45 + 2091: 28,45 + 2092: 26,45 + 2093: 28,47 + 2094: 27,44 + 2110: 24,45 + 2111: 27,48 + 2112: 27,45 + 2113: 26,46 + 2114: 26,42 + 2115: 25,42 + 2116: 25,47 + 2117: 25,47 + 2118: 26,48 + 2119: 26,47 + 2120: 26,47 + 2121: 26,43 + 2122: 26,43 + 2123: 27,43 + 2124: 6,1 + 2125: 10,3 + - node: + color: '#D4D4D41B' + id: FullTileOverlayGreyscale + decals: + 1313: 31,4 + 1314: 31,3 + 1315: 31,2 + 1316: 31,1 + 1317: 31,0 + - node: + color: '#D4D4D433' + id: FullTileOverlayGreyscale + decals: + 1318: 32,0 + 1319: 32,1 + 1320: 32,2 + 1321: 32,3 + 1322: 32,4 + - node: + color: '#D4D4D44C' + id: FullTileOverlayGreyscale + decals: + 1323: 33,0 + 1324: 33,1 + 1325: 33,2 + 1326: 33,3 + 1327: 33,4 + - node: + color: '#D4D4D40C' + id: HalfTileOverlayGreyscale + decals: + 923: 3,47 + - node: + color: '#D4D4D419' + id: HalfTileOverlayGreyscale + decals: + 893: 3,43 + - node: + color: '#D4D4D40C' + id: HalfTileOverlayGreyscale180 + decals: + 924: 3,43 + - node: + color: '#D4D4D419' + id: HalfTileOverlayGreyscale180 + decals: + 894: 3,47 + - node: + color: '#D4D4D40C' + id: HalfTileOverlayGreyscale270 + decals: + 910: 30,2 + 911: 9,2 + 914: 2,6 + 915: 2,10 + 918: 2,31 + 922: 1,45 + - node: + color: '#D4D4D419' + id: HalfTileOverlayGreyscale270 + decals: + 882: 7,2 + 883: 8,6 + 884: 8,10 + 888: 10,31 + 889: 33,34 + 890: 33,36 + 891: 5,45 + - node: + color: '#D4D4D40C' + id: HalfTileOverlayGreyscale90 + decals: + 912: 7,2 + 913: 8,6 + 916: 8,10 + 917: 10,31 + 919: 33,34 + 920: 33,36 + 921: 5,45 + - node: + color: '#D4D4D419' + id: HalfTileOverlayGreyscale90 + decals: + 880: 30,2 + 881: 9,2 + 885: 2,6 + 886: 2,10 + 887: 2,31 + 892: 1,45 + - node: + color: '#9FED5896' + id: MiniTileCheckerAOverlay + decals: + 2028: 28,42 + 2029: 29,42 + 2030: 30,42 + 2031: 28,43 + 2032: 29,43 + 2033: 30,43 + 2034: 28,44 + 2035: 29,44 + 2036: 30,44 + - node: + color: '#DE3A3A96' + id: MiniTileCheckerAOverlay + decals: + 0: 7,19 + 1: 8,19 + 2: 9,19 + 3: 9,20 + 4: 8,20 + 5: 7,20 + 6: 7,21 + 7: 8,21 + 8: 9,21 + 9: 19,19 + 10: 20,19 + 11: 21,19 + 12: 21,20 + 13: 20,20 + 14: 19,20 + 15: 19,21 + 16: 20,21 + 17: 21,21 + 18: 17,15 + 19: 17,14 + 20: 17,13 + 21: 18,13 + 22: 19,13 + 23: 20,13 + 24: 21,13 + 25: 21,14 + 26: 21,15 + 27: 20,15 + 28: 19,15 + 29: 18,15 + 30: 18,14 + 31: 19,14 + 32: 20,9 + 33: 21,9 + 34: 22,9 + 35: 22,8 + 36: 22,7 + 37: 21,7 + 38: 21,8 + 39: 20,8 + 40: 20,7 + 41: 12,7 + 42: 13,7 + 43: 14,7 + 44: 14,8 + 45: 13,8 + 46: 12,8 + 47: 12,9 + 48: 13,9 + 49: 14,9 + 59: 17,39 + 60: 18,39 + 61: 19,39 + 62: 20,39 + 63: 21,39 + 64: 16,36 + 65: 16,35 + 66: 16,34 + 67: 17,34 + 68: 18,34 + 69: 18,35 + 70: 17,35 + 71: 17,36 + 72: 18,36 + - node: + color: '#FFFFFFFF' + id: MiniTileCheckerAOverlay + decals: + 2052: 28,46 + 2053: 28,47 + 2054: 29,47 + 2055: 29,46 + - node: + color: '#9FED5896' + id: MiniTileCheckerBOverlay + decals: + 50: 31,21 + 51: 31,20 + 52: 31,19 + 53: 33,21 + 54: 33,20 + 55: 33,19 + - node: + color: '#DE3A3A96' + id: MiniTileCheckerBOverlay + decals: + 56: 32,21 + 57: 32,20 + 58: 32,19 + - node: + color: '#DE3A3A96' + id: StandClearGreyscale + decals: + 751: 26,7 + 752: 32,7 + 753: 29,9 + 838: 6,2 + 839: 10,2 + - node: + color: '#FFFFFFFF' + id: WarnCornerNE + decals: + 605: 13,40 + 747: 34,10 + - node: + color: '#FFFFFFFF' + id: WarnCornerNW + decals: + 606: 9,40 + 741: 24,10 + - node: + color: '#FFFFFFFF' + id: WarnCornerSE + decals: + 604: 13,38 + 746: 34,6 + - node: + color: '#FFFFFFFF' + id: WarnCornerSW + decals: + 598: 9,38 + 740: 24,6 + - node: + color: '#FFFFFFFF' + id: WarnFull + decals: + 657: 32,35 + - node: + color: '#FFFFFFFF' + id: WarnLineE + decals: + 607: 13,39 + 644: 26,34 + 645: 26,35 + 646: 26,36 + 744: 34,7 + 745: 34,9 + - node: + color: '#DE3A3A96' + id: WarnLineGreyscaleE + decals: + 112: 18,26 + 117: 14,26 + 118: 22,26 + 121: 22,35 + 122: 22,39 + 128: 14,39 + 131: 6,39 + 132: 12,31 + 135: 26,31 + 140: 26,26 + 145: 30,26 + 146: 34,26 + 153: 2,26 + 159: 4,20 + 160: 10,20 + 166: 16,20 + 170: 22,20 + 175: 34,20 + 176: 22,14 + 183: 6,14 + 184: 10,8 + 188: 16,2 + 190: 34,2 + 205: 21,2 + 213: 34,8 + 214: 30,14 + 219: 34,35 + 223: 30,39 + 224: 22,45 + 227: 14,45 + 230: 6,45 + 235: 10,35 + 834: 5,2 + 2018: 30,45 + - node: + color: '#DE3A3A96' + id: WarnLineGreyscaleN + decals: + 111: 17,28 + 116: 13,28 + 125: 19,40 + 126: 11,40 + 138: 21,28 + 144: 29,28 + 154: 1,28 + 158: 2,22 + 161: 8,22 + 165: 14,22 + 171: 20,22 + 173: 32,22 + 179: 19,16 + 180: 3,16 + 186: 17,10 + 209: 26,4 + 210: 29,10 + 217: 26,22 + 220: 29,36 + 228: 11,48 + 233: 3,48 + 237: 5,28 + 2020: 27,48 + - node: + color: '#FFFFFFFF' + id: WarnLineGreyscaleN + decals: + 667: 27,35 + 668: 28,35 + 669: 29,35 + 670: 30,35 + 671: 31,35 + - node: + color: '#DE3A3A96' + id: WarnLineGreyscaleS + decals: + 110: 17,24 + 115: 13,24 + 124: 19,38 + 127: 11,38 + 136: 20,30 + 137: 21,24 + 139: 25,24 + 143: 29,24 + 148: 33,24 + 149: 32,28 + 150: 33,28 + 151: 34,28 + 152: 1,24 + 157: 2,18 + 163: 8,18 + 164: 14,18 + 169: 20,18 + 172: 32,18 + 177: 19,12 + 181: 3,12 + 187: 17,6 + 208: 26,0 + 211: 29,6 + 216: 26,18 + 221: 29,34 + 229: 11,42 + 231: 3,42 + 236: 5,24 + 2019: 27,42 + - node: + color: '#FFFFFFFF' + id: WarnLineGreyscaleS + decals: + 662: 27,35 + 663: 28,35 + 664: 29,35 + 665: 30,35 + 666: 31,35 + - node: + color: '#DE3A3A96' + id: WarnLineGreyscaleW + decals: + 113: 16,26 + 114: 12,26 + 119: 20,26 + 120: 12,35 + 123: 16,39 + 129: 8,39 + 130: 0,39 + 133: 0,31 + 134: 14,31 + 141: 24,26 + 142: 28,26 + 147: 32,26 + 155: 0,26 + 156: 0,20 + 162: 6,20 + 167: 12,20 + 168: 18,20 + 174: 30,20 + 178: 16,14 + 182: 0,14 + 185: 0,8 + 189: 0,2 + 206: 18,2 + 207: 23,2 + 212: 24,8 + 215: 24,14 + 218: 24,35 + 222: 24,39 + 225: 16,45 + 226: 8,45 + 232: 0,45 + 234: 0,35 + 835: 11,2 + 2017: 24,45 + - node: + color: '#FFFFFFFF' + id: WarnLineN + decals: + 601: 10,38 + 602: 12,38 + 647: 27,34 + 648: 28,34 + 649: 30,34 + 650: 31,34 + 651: 32,34 + 732: 28,6 + 733: 27,6 + 734: 26,6 + 735: 25,6 + 736: 30,6 + 737: 31,6 + 738: 32,6 + 739: 33,6 + - node: + color: '#FFFFFFFF' + id: WarnLineS + decals: + 603: 9,39 + 742: 24,7 + 743: 24,9 + - node: + color: '#FFFFFFFF' + id: WarnLineW + decals: + 599: 10,40 + 600: 12,40 + 652: 27,36 + 653: 28,36 + 654: 30,36 + 655: 31,36 + 656: 32,36 + 724: 25,10 + 725: 26,10 + 726: 28,10 + 727: 27,10 + 728: 30,10 + 729: 31,10 + 730: 32,10 + 731: 33,10 + - node: + cleanable: True + color: '#80C71F7F' + id: revolution + decals: + 2138: 14.060958,20.754644 + 2139: 13.607299,19.803425 + - node: + cleanable: True + color: '#B02E60A3' + id: revolution + decals: + 2137: 25.02975,25.438416 + - node: + cleanable: True + angle: -1.5707963267948966 rad + color: '#B02E2644' + id: splatter + decals: + 2131: 27.967218,24.104916 + - node: + cleanable: True + color: '#B02E2666' + id: splatter + decals: + 2126: 8.891183,43.065514 + - node: + cleanable: True + angle: -1.5707963267948966 rad + color: '#B02E266F' + id: splatter + decals: + 2132: 28.36234,24.163452 + 2133: 32.200607,35.087025 + 2134: 13.24002,46.473877 + 2135: 24.497486,47.84553 + - node: + cleanable: True + angle: -4.71238898038469 rad + color: '#B02E26B4' + id: splatter + decals: + 2128: 8.788744,42.524048 + 2129: 15.538555,20.953827 + 2130: 24.864944,27.488853 + - node: + cleanable: True + angle: -3.141592653589793 rad + color: '#B02E26B4' + id: splatter + decals: + 2127: 9.110695,42.81673 + - type: RadiationGridResistance + - type: LoadedMap + - type: SpreaderGrid + - type: GridTree + - type: MovedGrids + - type: GridPathfinding +- proto: AirlockBrigGlassLocked + entities: + - uid: 1245 + components: + - type: Transform + pos: 15.5,35.5 + parent: 588 + - uid: 1246 + components: + - type: Transform + pos: 19.5,35.5 + parent: 588 + - uid: 1625 + components: + - type: Transform + pos: 22.5,2.5 + parent: 588 +- proto: AirlockEngineering + entities: + - uid: 1515 + components: + - type: Transform + pos: 19.5,43.5 + parent: 588 +- proto: AirlockSecurityGlassLocked + entities: + - uid: 1579 + components: + - type: Transform + pos: 11.5,15.5 + parent: 588 + - uid: 1609 + components: + - type: Transform + pos: 15.5,8.5 + parent: 588 + - uid: 1610 + components: + - type: Transform + pos: 19.5,8.5 + parent: 588 + - uid: 1623 + components: + - type: Transform + pos: 6.5,2.5 + parent: 588 + - uid: 1624 + components: + - type: Transform + pos: 10.5,2.5 + parent: 588 +- proto: APCBasic + entities: + - uid: 484 + components: + - type: Transform + pos: 25.5,13.5 + parent: 588 + - uid: 773 + components: + - type: Transform + pos: 25.5,19.5 + parent: 588 + - uid: 973 + components: + - type: Transform + pos: 21.5,32.5 + parent: 588 + - uid: 1509 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 19.5,47.5 + parent: 588 +- proto: BannerSecurity + entities: + - uid: 553 + components: + - type: Transform + pos: 30.5,8.5 + parent: 588 + - uid: 1619 + components: + - type: Transform + pos: 18.5,10.5 + parent: 588 + - uid: 1620 + components: + - type: Transform + pos: 16.5,6.5 + parent: 588 +- proto: BasaltFive + entities: + - uid: 608 + components: + - type: Transform + pos: 2.5,14.5 + parent: 588 + - uid: 647 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 2.5,21.5 + parent: 588 + - uid: 899 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 10.5,30.5 + parent: 588 + - uid: 1264 + components: + - type: Transform + pos: 9.5,0.5 + parent: 588 + - uid: 1384 + components: + - type: Transform + pos: 5.5,48.5 + parent: 588 + - uid: 1386 + components: + - type: Transform + pos: 0.5,47.5 + parent: 588 + - uid: 1387 + components: + - type: Transform + pos: 0.5,42.5 + parent: 588 + - uid: 1388 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.5,42.5 + parent: 588 +- proto: BasaltFour + entities: + - uid: 900 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 8.5,32.5 + parent: 588 + - uid: 904 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 2.5,40.5 + parent: 588 + - uid: 1381 + components: + - type: Transform + pos: 1.5,44.5 + parent: 588 + - uid: 1385 + components: + - type: Transform + pos: 6.5,48.5 + parent: 588 +- proto: BasaltOne + entities: + - uid: 813 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.5,27.5 + parent: 588 + - uid: 897 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 3.5,30.5 + parent: 588 + - uid: 901 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 10.5,32.5 + parent: 588 + - uid: 903 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 5.5,38.5 + parent: 588 + - uid: 1382 + components: + - type: Transform + pos: 1.5,48.5 + parent: 588 +- proto: BasaltRandom + entities: + - uid: 613 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 4.5,14.5 + parent: 588 + - uid: 615 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 3.5,7.5 + parent: 588 +- proto: BasaltThree + entities: + - uid: 616 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 6.5,7.5 + parent: 588 + - uid: 644 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 1.5,20.5 + parent: 588 + - uid: 898 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 2.5,30.5 + parent: 588 + - uid: 905 + components: + - type: Transform + pos: 4.5,38.5 + parent: 588 + - uid: 1265 + components: + - type: Transform + pos: 7.5,1.5 + parent: 588 + - uid: 1266 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 9.5,3.5 + parent: 588 + - uid: 1383 + components: + - type: Transform + pos: 6.5,47.5 + parent: 588 +- proto: BasaltTwo + entities: + - uid: 610 + components: + - type: Transform + pos: 3.5,14.5 + parent: 588 + - uid: 617 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 3.5,9.5 + parent: 588 + - uid: 618 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 7.5,8.5 + parent: 588 + - uid: 646 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 2.5,19.5 + parent: 588 + - uid: 814 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.5,26.5 + parent: 588 + - uid: 896 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 3.5,32.5 + parent: 588 + - uid: 902 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.5,38.5 + parent: 588 + - uid: 1263 + components: + - type: Transform + pos: 7.5,4.5 + parent: 588 + - uid: 1380 + components: + - type: Transform + pos: 1.5,43.5 + parent: 588 +- proto: Bed + entities: + - uid: 257 + components: + - type: Transform + pos: 15.5,4.5 + parent: 588 + - uid: 282 + components: + - type: Transform + pos: 1.5,4.5 + parent: 588 + - uid: 293 + components: + - type: Transform + pos: 3.5,4.5 + parent: 588 + - uid: 294 + components: + - type: Transform + pos: 5.5,4.5 + parent: 588 + - uid: 295 + components: + - type: Transform + pos: 11.5,4.5 + parent: 588 + - uid: 296 + components: + - type: Transform + pos: 13.5,4.5 + parent: 588 + - uid: 700 + components: + - type: Transform + pos: 12.5,22.5 + parent: 588 + - uid: 701 + components: + - type: Transform + pos: 16.5,18.5 + parent: 588 + - uid: 1043 + components: + - type: Transform + pos: 20.5,28.5 + parent: 588 + - uid: 1044 + components: + - type: Transform + pos: 22.5,24.5 + parent: 588 + - uid: 1075 + components: + - type: Transform + pos: 24.5,28.5 + parent: 588 + - uid: 1099 + components: + - type: Transform + pos: 20.5,36.5 + parent: 588 + - uid: 1100 + components: + - type: Transform + pos: 14.5,34.5 + parent: 588 + - uid: 1763 + components: + - type: Transform + pos: 24.5,48.5 + parent: 588 + - uid: 1764 + components: + - type: Transform + pos: 24.5,42.5 + parent: 588 +- proto: BedsheetMedical + entities: + - uid: 1150 + components: + - type: Transform + pos: 28.5,24.5 + parent: 588 + - uid: 1151 + components: + - type: Transform + pos: 28.5,25.5 + parent: 588 +- proto: BedsheetOrange + entities: + - uid: 298 + components: + - type: Transform + pos: 3.5,4.5 + parent: 588 + - uid: 299 + components: + - type: Transform + pos: 5.5,4.5 + parent: 588 + - uid: 300 + components: + - type: Transform + pos: 13.5,4.5 + parent: 588 + - uid: 1765 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 24.5,42.5 + parent: 588 +- proto: BedsheetSpawner + entities: + - uid: 301 + components: + - type: Transform + pos: 11.5,4.5 + parent: 588 + - uid: 1041 + components: + - type: Transform + pos: 20.5,28.5 + parent: 588 + - uid: 1225 + components: + - type: Transform + pos: 14.5,34.5 + parent: 588 + - uid: 1226 + components: + - type: Transform + pos: 20.5,36.5 + parent: 588 +- proto: BedsheetSyndie + entities: + - uid: 1037 + components: + - type: Transform + pos: 22.5,24.5 + parent: 588 + - uid: 1766 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 24.5,48.5 + parent: 588 +- proto: BlastDoor + entities: + - uid: 1600 + components: + - type: Transform + pos: 26.5,7.5 + parent: 588 + - uid: 1601 + components: + - type: Transform + pos: 29.5,9.5 + parent: 588 + - uid: 1602 + components: + - type: Transform + pos: 32.5,7.5 + parent: 588 +- proto: Bola + entities: + - uid: 1157 + components: + - type: Transform + pos: 15.616387,0.61007345 + parent: 588 +- proto: BookEscalationSecurity + entities: + - uid: 373 + components: + - type: Transform + pos: 19.42657,0.6288943 + parent: 588 +- proto: BookRandom + entities: + - uid: 1835 + components: + - type: Transform + pos: 24.420084,44.539436 + parent: 588 +- proto: BookSecurity + entities: + - uid: 522 + components: + - type: Transform + pos: 32.41844,8.400207 + parent: 588 +- proto: BoxFolderBlack + entities: + - uid: 365 + components: + - type: Transform + pos: 27.841173,1.5632035 + parent: 588 + - uid: 728 + components: + - type: Transform + pos: 10.690479,19.262342 + parent: 588 +- proto: BoxFolderGrey + entities: + - uid: 364 + components: + - type: Transform + pos: 25.072409,1.4780562 + parent: 588 + - uid: 727 + components: + - type: Transform + pos: 7.2148314,22.575037 + parent: 588 +- proto: BoxFolderRed + entities: + - uid: 329 + components: + - type: Transform + pos: 21.42984,3.6329575 + parent: 588 + - uid: 362 + components: + - type: Transform + pos: 24.788435,1.6057768 + parent: 588 + - uid: 363 + components: + - type: Transform + pos: 28.196142,1.5773941 + parent: 588 + - uid: 726 + components: + - type: Transform + pos: 10.428753,19.429379 + parent: 588 +- proto: BoxMouthSwab + entities: + - uid: 1476 + components: + - type: Transform + pos: 12.356534,44.605965 + parent: 588 +- proto: BoxSterileMask + entities: + - uid: 1477 + components: + - type: Transform + pos: 12.683106,44.705303 + parent: 588 +- proto: BriefcaseBrownFilled + entities: + - uid: 325 + components: + - type: Transform + pos: 19.413612,4.6972914 + parent: 588 +- proto: BrokenBottle + entities: + - uid: 1691 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 25.063513,27.520548 + parent: 588 +- proto: Bucket + entities: + - uid: 1839 + components: + - type: Transform + pos: 29.616838,43.531868 + parent: 588 + - uid: 1857 + components: + - type: Transform + pos: 30.264944,21.705044 + parent: 588 +- proto: CableApcExtension + entities: + - uid: 1 + components: + - type: Transform + pos: 0.5,2.5 + parent: 588 + - uid: 2 + components: + - type: Transform + pos: 1.5,2.5 + parent: 588 + - uid: 3 + components: + - type: Transform + pos: 2.5,2.5 + parent: 588 + - uid: 4 + components: + - type: Transform + pos: 3.5,2.5 + parent: 588 + - uid: 5 + components: + - type: Transform + pos: 4.5,2.5 + parent: 588 + - uid: 6 + components: + - type: Transform + pos: 5.5,2.5 + parent: 588 + - uid: 7 + components: + - type: Transform + pos: 6.5,2.5 + parent: 588 + - uid: 8 + components: + - type: Transform + pos: 7.5,2.5 + parent: 588 + - uid: 9 + components: + - type: Transform + pos: 8.5,2.5 + parent: 588 + - uid: 10 + components: + - type: Transform + pos: 9.5,2.5 + parent: 588 + - uid: 11 + components: + - type: Transform + pos: 10.5,2.5 + parent: 588 + - uid: 12 + components: + - type: Transform + pos: 11.5,2.5 + parent: 588 + - uid: 13 + components: + - type: Transform + pos: 12.5,2.5 + parent: 588 + - uid: 14 + components: + - type: Transform + pos: 13.5,2.5 + parent: 588 + - uid: 15 + components: + - type: Transform + pos: 14.5,2.5 + parent: 588 + - uid: 16 + components: + - type: Transform + pos: 15.5,2.5 + parent: 588 + - uid: 17 + components: + - type: Transform + pos: 16.5,2.5 + parent: 588 + - uid: 18 + components: + - type: Transform + pos: 8.5,4.5 + parent: 588 + - uid: 19 + components: + - type: Transform + pos: 8.5,3.5 + parent: 588 + - uid: 20 + components: + - type: Transform + pos: 8.5,1.5 + parent: 588 + - uid: 21 + components: + - type: Transform + pos: 8.5,0.5 + parent: 588 + - uid: 22 + components: + - type: Transform + pos: 18.5,2.5 + parent: 588 + - uid: 23 + components: + - type: Transform + pos: 19.5,2.5 + parent: 588 + - uid: 24 + components: + - type: Transform + pos: 20.5,2.5 + parent: 588 + - uid: 25 + components: + - type: Transform + pos: 21.5,2.5 + parent: 588 + - uid: 26 + components: + - type: Transform + pos: 22.5,2.5 + parent: 588 + - uid: 27 + components: + - type: Transform + pos: 23.5,2.5 + parent: 588 + - uid: 28 + components: + - type: Transform + pos: 24.5,2.5 + parent: 588 + - uid: 29 + components: + - type: Transform + pos: 25.5,2.5 + parent: 588 + - uid: 30 + components: + - type: Transform + pos: 26.5,2.5 + parent: 588 + - uid: 31 + components: + - type: Transform + pos: 27.5,2.5 + parent: 588 + - uid: 32 + components: + - type: Transform + pos: 28.5,2.5 + parent: 588 + - uid: 33 + components: + - type: Transform + pos: 29.5,2.5 + parent: 588 + - uid: 34 + components: + - type: Transform + pos: 30.5,2.5 + parent: 588 + - uid: 35 + components: + - type: Transform + pos: 31.5,2.5 + parent: 588 + - uid: 36 + components: + - type: Transform + pos: 32.5,2.5 + parent: 588 + - uid: 37 + components: + - type: Transform + pos: 33.5,2.5 + parent: 588 + - uid: 38 + components: + - type: Transform + pos: 34.5,2.5 + parent: 588 + - uid: 39 + components: + - type: Transform + pos: 26.5,4.5 + parent: 588 + - uid: 40 + components: + - type: Transform + pos: 26.5,3.5 + parent: 588 + - uid: 41 + components: + - type: Transform + pos: 26.5,1.5 + parent: 588 + - uid: 42 + components: + - type: Transform + pos: 26.5,0.5 + parent: 588 + - uid: 43 + components: + - type: Transform + pos: 0.5,8.5 + parent: 588 + - uid: 44 + components: + - type: Transform + pos: 1.5,8.5 + parent: 588 + - uid: 52 + components: + - type: Transform + pos: 9.5,8.5 + parent: 588 + - uid: 53 + components: + - type: Transform + pos: 10.5,8.5 + parent: 588 + - uid: 54 + components: + - type: Transform + pos: 5.5,10.5 + parent: 588 + - uid: 57 + components: + - type: Transform + pos: 5.5,6.5 + parent: 588 + - uid: 58 + components: + - type: Transform + pos: 17.5,6.5 + parent: 588 + - uid: 59 + components: + - type: Transform + pos: 17.5,7.5 + parent: 588 + - uid: 60 + components: + - type: Transform + pos: 17.5,8.5 + parent: 588 + - uid: 61 + components: + - type: Transform + pos: 17.5,9.5 + parent: 588 + - uid: 62 + components: + - type: Transform + pos: 17.5,10.5 + parent: 588 + - uid: 63 + components: + - type: Transform + pos: 12.5,8.5 + parent: 588 + - uid: 64 + components: + - type: Transform + pos: 13.5,8.5 + parent: 588 + - uid: 65 + components: + - type: Transform + pos: 14.5,8.5 + parent: 588 + - uid: 66 + components: + - type: Transform + pos: 15.5,8.5 + parent: 588 + - uid: 67 + components: + - type: Transform + pos: 16.5,8.5 + parent: 588 + - uid: 68 + components: + - type: Transform + pos: 18.5,8.5 + parent: 588 + - uid: 69 + components: + - type: Transform + pos: 19.5,8.5 + parent: 588 + - uid: 70 + components: + - type: Transform + pos: 20.5,8.5 + parent: 588 + - uid: 71 + components: + - type: Transform + pos: 21.5,8.5 + parent: 588 + - uid: 72 + components: + - type: Transform + pos: 22.5,8.5 + parent: 588 + - uid: 73 + components: + - type: Transform + pos: 22.5,14.5 + parent: 588 + - uid: 74 + components: + - type: Transform + pos: 21.5,14.5 + parent: 588 + - uid: 75 + components: + - type: Transform + pos: 20.5,14.5 + parent: 588 + - uid: 76 + components: + - type: Transform + pos: 19.5,14.5 + parent: 588 + - uid: 77 + components: + - type: Transform + pos: 18.5,14.5 + parent: 588 + - uid: 78 + components: + - type: Transform + pos: 17.5,14.5 + parent: 588 + - uid: 79 + components: + - type: Transform + pos: 16.5,14.5 + parent: 588 + - uid: 80 + components: + - type: Transform + pos: 19.5,13.5 + parent: 588 + - uid: 81 + components: + - type: Transform + pos: 19.5,12.5 + parent: 588 + - uid: 82 + components: + - type: Transform + pos: 19.5,15.5 + parent: 588 + - uid: 83 + components: + - type: Transform + pos: 19.5,16.5 + parent: 588 + - uid: 84 + components: + - type: Transform + pos: 14.5,14.5 + parent: 588 + - uid: 85 + components: + - type: Transform + pos: 13.5,14.5 + parent: 588 + - uid: 86 + components: + - type: Transform + pos: 12.5,14.5 + parent: 588 + - uid: 87 + components: + - type: Transform + pos: 11.5,14.5 + parent: 588 + - uid: 88 + components: + - type: Transform + pos: 10.5,14.5 + parent: 588 + - uid: 89 + components: + - type: Transform + pos: 9.5,14.5 + parent: 588 + - uid: 90 + components: + - type: Transform + pos: 8.5,14.5 + parent: 588 + - uid: 91 + components: + - type: Transform + pos: 11.5,13.5 + parent: 588 + - uid: 92 + components: + - type: Transform + pos: 11.5,12.5 + parent: 588 + - uid: 93 + components: + - type: Transform + pos: 11.5,15.5 + parent: 588 + - uid: 94 + components: + - type: Transform + pos: 11.5,16.5 + parent: 588 + - uid: 95 + components: + - type: Transform + pos: 6.5,14.5 + parent: 588 + - uid: 96 + components: + - type: Transform + pos: 5.5,14.5 + parent: 588 + - uid: 98 + components: + - type: Transform + pos: 4.5,22.5 + parent: 588 + - uid: 100 + components: + - type: Transform + pos: 1.5,14.5 + parent: 588 + - uid: 101 + components: + - type: Transform + pos: 0.5,14.5 + parent: 588 + - uid: 102 + components: + - type: Transform + pos: 3.5,15.5 + parent: 588 + - uid: 103 + components: + - type: Transform + pos: 3.5,16.5 + parent: 588 + - uid: 104 + components: + - type: Transform + pos: 3.5,13.5 + parent: 588 + - uid: 105 + components: + - type: Transform + pos: 3.5,12.5 + parent: 588 + - uid: 106 + components: + - type: Transform + pos: 14.5,18.5 + parent: 588 + - uid: 107 + components: + - type: Transform + pos: 14.5,19.5 + parent: 588 + - uid: 108 + components: + - type: Transform + pos: 14.5,20.5 + parent: 588 + - uid: 109 + components: + - type: Transform + pos: 14.5,21.5 + parent: 588 + - uid: 110 + components: + - type: Transform + pos: 14.5,22.5 + parent: 588 + - uid: 111 + components: + - type: Transform + pos: 15.5,20.5 + parent: 588 + - uid: 112 + components: + - type: Transform + pos: 16.5,20.5 + parent: 588 + - uid: 113 + components: + - type: Transform + pos: 13.5,20.5 + parent: 588 + - uid: 114 + components: + - type: Transform + pos: 12.5,20.5 + parent: 588 + - uid: 115 + components: + - type: Transform + pos: 8.5,18.5 + parent: 588 + - uid: 116 + components: + - type: Transform + pos: 8.5,19.5 + parent: 588 + - uid: 117 + components: + - type: Transform + pos: 8.5,20.5 + parent: 588 + - uid: 118 + components: + - type: Transform + pos: 8.5,21.5 + parent: 588 + - uid: 119 + components: + - type: Transform + pos: 8.5,22.5 + parent: 588 + - uid: 120 + components: + - type: Transform + pos: 9.5,20.5 + parent: 588 + - uid: 121 + components: + - type: Transform + pos: 10.5,20.5 + parent: 588 + - uid: 122 + components: + - type: Transform + pos: 7.5,20.5 + parent: 588 + - uid: 123 + components: + - type: Transform + pos: 6.5,20.5 + parent: 588 + - uid: 124 + components: + - type: Transform + pos: 2.5,22.5 + parent: 588 + - uid: 125 + components: + - type: Transform + pos: 4.5,21.5 + parent: 588 + - uid: 126 + components: + - type: Transform + pos: 4.5,19.5 + parent: 588 + - uid: 127 + components: + - type: Transform + pos: 3.5,18.5 + parent: 588 + - uid: 128 + components: + - type: Transform + pos: 2.5,18.5 + parent: 588 + - uid: 129 + components: + - type: Transform + pos: 3.5,22.5 + parent: 588 + - uid: 130 + components: + - type: Transform + pos: 0.5,20.5 + parent: 588 + - uid: 131 + components: + - type: Transform + pos: 4.5,18.5 + parent: 588 + - uid: 132 + components: + - type: Transform + pos: 4.5,20.5 + parent: 588 + - uid: 133 + components: + - type: Transform + pos: 1.5,24.5 + parent: 588 + - uid: 134 + components: + - type: Transform + pos: 2.5,25.5 + parent: 588 + - uid: 135 + components: + - type: Transform + pos: 0.5,24.5 + parent: 588 + - uid: 136 + components: + - type: Transform + pos: 2.5,24.5 + parent: 588 + - uid: 137 + components: + - type: Transform + pos: 1.5,28.5 + parent: 588 + - uid: 138 + components: + - type: Transform + pos: 0.5,26.5 + parent: 588 + - uid: 139 + components: + - type: Transform + pos: 2.5,26.5 + parent: 588 + - uid: 147 + components: + - type: Transform + pos: 9.5,28.5 + parent: 588 + - uid: 148 + components: + - type: Transform + pos: 9.5,27.5 + parent: 588 + - uid: 149 + components: + - type: Transform + pos: 9.5,26.5 + parent: 588 + - uid: 150 + components: + - type: Transform + pos: 9.5,25.5 + parent: 588 + - uid: 151 + components: + - type: Transform + pos: 9.5,24.5 + parent: 588 + - uid: 152 + components: + - type: Transform + pos: 8.5,26.5 + parent: 588 + - uid: 153 + components: + - type: Transform + pos: 10.5,26.5 + parent: 588 + - uid: 154 + components: + - type: Transform + pos: 13.5,28.5 + parent: 588 + - uid: 155 + components: + - type: Transform + pos: 13.5,27.5 + parent: 588 + - uid: 156 + components: + - type: Transform + pos: 13.5,26.5 + parent: 588 + - uid: 157 + components: + - type: Transform + pos: 13.5,25.5 + parent: 588 + - uid: 158 + components: + - type: Transform + pos: 13.5,24.5 + parent: 588 + - uid: 159 + components: + - type: Transform + pos: 12.5,26.5 + parent: 588 + - uid: 160 + components: + - type: Transform + pos: 14.5,26.5 + parent: 588 + - uid: 161 + components: + - type: Transform + pos: 0.5,31.5 + parent: 588 + - uid: 162 + components: + - type: Transform + pos: 1.5,31.5 + parent: 588 + - uid: 163 + components: + - type: Transform + pos: 2.5,31.5 + parent: 588 + - uid: 164 + components: + - type: Transform + pos: 3.5,31.5 + parent: 588 + - uid: 165 + components: + - type: Transform + pos: 4.5,31.5 + parent: 588 + - uid: 166 + components: + - type: Transform + pos: 5.5,31.5 + parent: 588 + - uid: 167 + components: + - type: Transform + pos: 6.5,31.5 + parent: 588 + - uid: 168 + components: + - type: Transform + pos: 7.5,31.5 + parent: 588 + - uid: 169 + components: + - type: Transform + pos: 8.5,31.5 + parent: 588 + - uid: 170 + components: + - type: Transform + pos: 9.5,31.5 + parent: 588 + - uid: 171 + components: + - type: Transform + pos: 10.5,31.5 + parent: 588 + - uid: 172 + components: + - type: Transform + pos: 11.5,31.5 + parent: 588 + - uid: 173 + components: + - type: Transform + pos: 12.5,31.5 + parent: 588 + - uid: 174 + components: + - type: Transform + pos: 6.5,30.5 + parent: 588 + - uid: 175 + components: + - type: Transform + pos: 6.5,32.5 + parent: 588 + - uid: 176 + components: + - type: Transform + pos: 26.5,31.5 + parent: 588 + - uid: 177 + components: + - type: Transform + pos: 25.5,31.5 + parent: 588 + - uid: 178 + components: + - type: Transform + pos: 24.5,31.5 + parent: 588 + - uid: 179 + components: + - type: Transform + pos: 23.5,31.5 + parent: 588 + - uid: 180 + components: + - type: Transform + pos: 22.5,31.5 + parent: 588 + - uid: 181 + components: + - type: Transform + pos: 21.5,31.5 + parent: 588 + - uid: 182 + components: + - type: Transform + pos: 20.5,31.5 + parent: 588 + - uid: 183 + components: + - type: Transform + pos: 19.5,31.5 + parent: 588 + - uid: 184 + components: + - type: Transform + pos: 18.5,31.5 + parent: 588 + - uid: 185 + components: + - type: Transform + pos: 17.5,31.5 + parent: 588 + - uid: 186 + components: + - type: Transform + pos: 16.5,31.5 + parent: 588 + - uid: 187 + components: + - type: Transform + pos: 15.5,31.5 + parent: 588 + - uid: 188 + components: + - type: Transform + pos: 14.5,31.5 + parent: 588 + - uid: 189 + components: + - type: Transform + pos: 20.5,32.5 + parent: 588 + - uid: 190 + components: + - type: Transform + pos: 20.5,30.5 + parent: 588 + - uid: 191 + components: + - type: Transform + pos: 22.5,35.5 + parent: 588 + - uid: 192 + components: + - type: Transform + pos: 21.5,35.5 + parent: 588 + - uid: 193 + components: + - type: Transform + pos: 20.5,35.5 + parent: 588 + - uid: 194 + components: + - type: Transform + pos: 19.5,35.5 + parent: 588 + - uid: 195 + components: + - type: Transform + pos: 18.5,35.5 + parent: 588 + - uid: 196 + components: + - type: Transform + pos: 17.5,35.5 + parent: 588 + - uid: 197 + components: + - type: Transform + pos: 16.5,35.5 + parent: 588 + - uid: 198 + components: + - type: Transform + pos: 15.5,35.5 + parent: 588 + - uid: 199 + components: + - type: Transform + pos: 14.5,35.5 + parent: 588 + - uid: 200 + components: + - type: Transform + pos: 13.5,35.5 + parent: 588 + - uid: 201 + components: + - type: Transform + pos: 12.5,35.5 + parent: 588 + - uid: 202 + components: + - type: Transform + pos: 17.5,36.5 + parent: 588 + - uid: 203 + components: + - type: Transform + pos: 17.5,34.5 + parent: 588 + - uid: 204 + components: + - type: Transform + pos: 10.5,35.5 + parent: 588 + - uid: 215 + components: + - type: Transform + pos: 5.5,36.5 + parent: 588 + - uid: 216 + components: + - type: Transform + pos: 5.5,34.5 + parent: 588 + - uid: 217 + components: + - type: Transform + pos: 0.5,39.5 + parent: 588 + - uid: 218 + components: + - type: Transform + pos: 1.5,39.5 + parent: 588 + - uid: 219 + components: + - type: Transform + pos: 2.5,39.5 + parent: 588 + - uid: 220 + components: + - type: Transform + pos: 3.5,39.5 + parent: 588 + - uid: 221 + components: + - type: Transform + pos: 4.5,39.5 + parent: 588 + - uid: 222 + components: + - type: Transform + pos: 5.5,39.5 + parent: 588 + - uid: 223 + components: + - type: Transform + pos: 6.5,39.5 + parent: 588 + - uid: 224 + components: + - type: Transform + pos: 3.5,38.5 + parent: 588 + - uid: 225 + components: + - type: Transform + pos: 3.5,40.5 + parent: 588 + - uid: 226 + components: + - type: Transform + pos: 8.5,39.5 + parent: 588 + - uid: 227 + components: + - type: Transform + pos: 9.5,39.5 + parent: 588 + - uid: 228 + components: + - type: Transform + pos: 10.5,39.5 + parent: 588 + - uid: 229 + components: + - type: Transform + pos: 11.5,39.5 + parent: 588 + - uid: 230 + components: + - type: Transform + pos: 12.5,39.5 + parent: 588 + - uid: 231 + components: + - type: Transform + pos: 13.5,39.5 + parent: 588 + - uid: 232 + components: + - type: Transform + pos: 14.5,39.5 + parent: 588 + - uid: 233 + components: + - type: Transform + pos: 11.5,38.5 + parent: 588 + - uid: 234 + components: + - type: Transform + pos: 11.5,40.5 + parent: 588 + - uid: 235 + components: + - type: Transform + pos: 16.5,39.5 + parent: 588 + - uid: 236 + components: + - type: Transform + pos: 17.5,39.5 + parent: 588 + - uid: 237 + components: + - type: Transform + pos: 18.5,39.5 + parent: 588 + - uid: 238 + components: + - type: Transform + pos: 19.5,39.5 + parent: 588 + - uid: 239 + components: + - type: Transform + pos: 20.5,39.5 + parent: 588 + - uid: 240 + components: + - type: Transform + pos: 21.5,39.5 + parent: 588 + - uid: 241 + components: + - type: Transform + pos: 22.5,39.5 + parent: 588 + - uid: 242 + components: + - type: Transform + pos: 19.5,40.5 + parent: 588 + - uid: 243 + components: + - type: Transform + pos: 19.5,38.5 + parent: 588 + - uid: 284 + components: + - type: Transform + pos: 29.5,26.5 + parent: 588 + - uid: 288 + components: + - type: Transform + pos: 28.5,26.5 + parent: 588 + - uid: 425 + components: + - type: Transform + pos: 1.5,10.5 + parent: 588 + - uid: 438 + components: + - type: Transform + pos: 1.5,9.5 + parent: 588 + - uid: 441 + components: + - type: Transform + pos: 2.5,10.5 + parent: 588 + - uid: 442 + components: + - type: Transform + pos: 3.5,10.5 + parent: 588 + - uid: 443 + components: + - type: Transform + pos: 4.5,10.5 + parent: 588 + - uid: 444 + components: + - type: Transform + pos: 1.5,7.5 + parent: 588 + - uid: 445 + components: + - type: Transform + pos: 1.5,6.5 + parent: 588 + - uid: 446 + components: + - type: Transform + pos: 2.5,6.5 + parent: 588 + - uid: 447 + components: + - type: Transform + pos: 3.5,6.5 + parent: 588 + - uid: 448 + components: + - type: Transform + pos: 4.5,6.5 + parent: 588 + - uid: 449 + components: + - type: Transform + pos: 6.5,6.5 + parent: 588 + - uid: 450 + components: + - type: Transform + pos: 7.5,6.5 + parent: 588 + - uid: 451 + components: + - type: Transform + pos: 8.5,6.5 + parent: 588 + - uid: 452 + components: + - type: Transform + pos: 9.5,6.5 + parent: 588 + - uid: 453 + components: + - type: Transform + pos: 9.5,7.5 + parent: 588 + - uid: 454 + components: + - type: Transform + pos: 9.5,9.5 + parent: 588 + - uid: 455 + components: + - type: Transform + pos: 9.5,10.5 + parent: 588 + - uid: 456 + components: + - type: Transform + pos: 8.5,10.5 + parent: 588 + - uid: 457 + components: + - type: Transform + pos: 7.5,10.5 + parent: 588 + - uid: 472 + components: + - type: Transform + pos: 29.5,14.5 + parent: 588 + - uid: 477 + components: + - type: Transform + pos: 24.5,13.5 + parent: 588 + - uid: 479 + components: + - type: Transform + pos: 30.5,14.5 + parent: 588 + - uid: 493 + components: + - type: Transform + pos: 25.5,13.5 + parent: 588 + - uid: 494 + components: + - type: Transform + pos: 25.5,12.5 + parent: 588 + - uid: 495 + components: + - type: Transform + pos: 26.5,12.5 + parent: 588 + - uid: 496 + components: + - type: Transform + pos: 27.5,12.5 + parent: 588 + - uid: 497 + components: + - type: Transform + pos: 28.5,12.5 + parent: 588 + - uid: 498 + components: + - type: Transform + pos: 29.5,12.5 + parent: 588 + - uid: 500 + components: + - type: Transform + pos: 24.5,12.5 + parent: 588 + - uid: 502 + components: + - type: Transform + pos: 24.5,14.5 + parent: 588 + - uid: 503 + components: + - type: Transform + pos: 24.5,15.5 + parent: 588 + - uid: 504 + components: + - type: Transform + pos: 24.5,16.5 + parent: 588 + - uid: 505 + components: + - type: Transform + pos: 25.5,16.5 + parent: 588 + - uid: 506 + components: + - type: Transform + pos: 26.5,16.5 + parent: 588 + - uid: 507 + components: + - type: Transform + pos: 27.5,16.5 + parent: 588 + - uid: 508 + components: + - type: Transform + pos: 28.5,16.5 + parent: 588 + - uid: 509 + components: + - type: Transform + pos: 29.5,16.5 + parent: 588 + - uid: 514 + components: + - type: Transform + pos: 29.5,13.5 + parent: 588 + - uid: 523 + components: + - type: Transform + pos: 29.5,15.5 + parent: 588 + - uid: 628 + components: + - type: Transform + pos: 1.5,22.5 + parent: 588 + - uid: 639 + components: + - type: Transform + pos: 0.5,22.5 + parent: 588 + - uid: 640 + components: + - type: Transform + pos: 0.5,21.5 + parent: 588 + - uid: 641 + components: + - type: Transform + pos: 0.5,19.5 + parent: 588 + - uid: 642 + components: + - type: Transform + pos: 0.5,18.5 + parent: 588 + - uid: 643 + components: + - type: Transform + pos: 1.5,18.5 + parent: 588 + - uid: 657 + components: + - type: Transform + pos: 2.5,15.5 + parent: 588 + - uid: 661 + components: + - type: Transform + pos: 1.5,15.5 + parent: 588 + - uid: 662 + components: + - type: Transform + pos: 1.5,13.5 + parent: 588 + - uid: 663 + components: + - type: Transform + pos: 2.5,13.5 + parent: 588 + - uid: 664 + components: + - type: Transform + pos: 4.5,13.5 + parent: 588 + - uid: 665 + components: + - type: Transform + pos: 5.5,13.5 + parent: 588 + - uid: 666 + components: + - type: Transform + pos: 5.5,15.5 + parent: 588 + - uid: 667 + components: + - type: Transform + pos: 4.5,15.5 + parent: 588 + - uid: 668 + components: + - type: Transform + pos: 29.5,10.5 + parent: 588 + - uid: 669 + components: + - type: Transform + pos: 28.5,10.5 + parent: 588 + - uid: 670 + components: + - type: Transform + pos: 27.5,10.5 + parent: 588 + - uid: 671 + components: + - type: Transform + pos: 26.5,10.5 + parent: 588 + - uid: 672 + components: + - type: Transform + pos: 25.5,10.5 + parent: 588 + - uid: 673 + components: + - type: Transform + pos: 24.5,10.5 + parent: 588 + - uid: 674 + components: + - type: Transform + pos: 24.5,9.5 + parent: 588 + - uid: 675 + components: + - type: Transform + pos: 24.5,8.5 + parent: 588 + - uid: 676 + components: + - type: Transform + pos: 24.5,7.5 + parent: 588 + - uid: 677 + components: + - type: Transform + pos: 24.5,6.5 + parent: 588 + - uid: 678 + components: + - type: Transform + pos: 25.5,6.5 + parent: 588 + - uid: 679 + components: + - type: Transform + pos: 26.5,6.5 + parent: 588 + - uid: 680 + components: + - type: Transform + pos: 27.5,6.5 + parent: 588 + - uid: 681 + components: + - type: Transform + pos: 28.5,6.5 + parent: 588 + - uid: 682 + components: + - type: Transform + pos: 29.5,6.5 + parent: 588 + - uid: 683 + components: + - type: Transform + pos: 30.5,6.5 + parent: 588 + - uid: 684 + components: + - type: Transform + pos: 31.5,6.5 + parent: 588 + - uid: 685 + components: + - type: Transform + pos: 32.5,6.5 + parent: 588 + - uid: 686 + components: + - type: Transform + pos: 33.5,6.5 + parent: 588 + - uid: 687 + components: + - type: Transform + pos: 34.5,6.5 + parent: 588 + - uid: 688 + components: + - type: Transform + pos: 34.5,7.5 + parent: 588 + - uid: 689 + components: + - type: Transform + pos: 34.5,8.5 + parent: 588 + - uid: 690 + components: + - type: Transform + pos: 34.5,9.5 + parent: 588 + - uid: 691 + components: + - type: Transform + pos: 34.5,10.5 + parent: 588 + - uid: 692 + components: + - type: Transform + pos: 33.5,10.5 + parent: 588 + - uid: 693 + components: + - type: Transform + pos: 32.5,10.5 + parent: 588 + - uid: 694 + components: + - type: Transform + pos: 31.5,10.5 + parent: 588 + - uid: 695 + components: + - type: Transform + pos: 30.5,10.5 + parent: 588 + - uid: 733 + components: + - type: Transform + pos: 20.5,18.5 + parent: 588 + - uid: 734 + components: + - type: Transform + pos: 20.5,19.5 + parent: 588 + - uid: 735 + components: + - type: Transform + pos: 20.5,20.5 + parent: 588 + - uid: 736 + components: + - type: Transform + pos: 20.5,21.5 + parent: 588 + - uid: 737 + components: + - type: Transform + pos: 20.5,22.5 + parent: 588 + - uid: 738 + components: + - type: Transform + pos: 21.5,20.5 + parent: 588 + - uid: 739 + components: + - type: Transform + pos: 22.5,20.5 + parent: 588 + - uid: 740 + components: + - type: Transform + pos: 19.5,20.5 + parent: 588 + - uid: 741 + components: + - type: Transform + pos: 18.5,20.5 + parent: 588 + - uid: 751 + components: + - type: Transform + pos: 32.5,22.5 + parent: 588 + - uid: 752 + components: + - type: Transform + pos: 32.5,21.5 + parent: 588 + - uid: 753 + components: + - type: Transform + pos: 32.5,20.5 + parent: 588 + - uid: 754 + components: + - type: Transform + pos: 32.5,19.5 + parent: 588 + - uid: 755 + components: + - type: Transform + pos: 32.5,18.5 + parent: 588 + - uid: 756 + components: + - type: Transform + pos: 31.5,20.5 + parent: 588 + - uid: 757 + components: + - type: Transform + pos: 30.5,20.5 + parent: 588 + - uid: 758 + components: + - type: Transform + pos: 33.5,20.5 + parent: 588 + - uid: 759 + components: + - type: Transform + pos: 34.5,20.5 + parent: 588 + - uid: 779 + components: + - type: Transform + pos: 25.5,19.5 + parent: 588 + - uid: 780 + components: + - type: Transform + pos: 25.5,18.5 + parent: 588 + - uid: 781 + components: + - type: Transform + pos: 24.5,18.5 + parent: 588 + - uid: 782 + components: + - type: Transform + pos: 24.5,19.5 + parent: 588 + - uid: 783 + components: + - type: Transform + pos: 24.5,20.5 + parent: 588 + - uid: 784 + components: + - type: Transform + pos: 24.5,21.5 + parent: 588 + - uid: 785 + components: + - type: Transform + pos: 24.5,22.5 + parent: 588 + - uid: 786 + components: + - type: Transform + pos: 25.5,22.5 + parent: 588 + - uid: 787 + components: + - type: Transform + pos: 26.5,22.5 + parent: 588 + - uid: 788 + components: + - type: Transform + pos: 27.5,22.5 + parent: 588 + - uid: 789 + components: + - type: Transform + pos: 28.5,22.5 + parent: 588 + - uid: 790 + components: + - type: Transform + pos: 28.5,21.5 + parent: 588 + - uid: 791 + components: + - type: Transform + pos: 28.5,20.5 + parent: 588 + - uid: 792 + components: + - type: Transform + pos: 28.5,19.5 + parent: 588 + - uid: 793 + components: + - type: Transform + pos: 28.5,18.5 + parent: 588 + - uid: 794 + components: + - type: Transform + pos: 27.5,18.5 + parent: 588 + - uid: 795 + components: + - type: Transform + pos: 26.5,18.5 + parent: 588 + - uid: 815 + components: + - type: Transform + pos: 0.5,25.5 + parent: 588 + - uid: 816 + components: + - type: Transform + pos: 0.5,27.5 + parent: 588 + - uid: 817 + components: + - type: Transform + pos: 0.5,28.5 + parent: 588 + - uid: 818 + components: + - type: Transform + pos: 2.5,28.5 + parent: 588 + - uid: 819 + components: + - type: Transform + pos: 2.5,27.5 + parent: 588 + - uid: 869 + components: + - type: Transform + pos: 5.5,24.5 + parent: 588 + - uid: 870 + components: + - type: Transform + pos: 6.5,24.5 + parent: 588 + - uid: 871 + components: + - type: Transform + pos: 6.5,25.5 + parent: 588 + - uid: 872 + components: + - type: Transform + pos: 6.5,26.5 + parent: 588 + - uid: 873 + components: + - type: Transform + pos: 6.5,27.5 + parent: 588 + - uid: 874 + components: + - type: Transform + pos: 6.5,28.5 + parent: 588 + - uid: 875 + components: + - type: Transform + pos: 5.5,28.5 + parent: 588 + - uid: 876 + components: + - type: Transform + pos: 4.5,28.5 + parent: 588 + - uid: 877 + components: + - type: Transform + pos: 4.5,27.5 + parent: 588 + - uid: 878 + components: + - type: Transform + pos: 4.5,26.5 + parent: 588 + - uid: 912 + components: + - type: Transform + pos: 6.5,10.5 + parent: 588 + - uid: 927 + components: + - type: Transform + pos: 34.5,36.5 + parent: 588 + - uid: 928 + components: + - type: Transform + pos: 32.5,36.5 + parent: 588 + - uid: 996 + components: + - type: Transform + pos: 21.5,32.5 + parent: 588 + - uid: 1079 + components: + - type: Transform + pos: 32.5,35.5 + parent: 588 + - uid: 1080 + components: + - type: Transform + pos: 31.5,35.5 + parent: 588 + - uid: 1081 + components: + - type: Transform + pos: 32.5,34.5 + parent: 588 + - uid: 1082 + components: + - type: Transform + pos: 29.5,34.5 + parent: 588 + - uid: 1083 + components: + - type: Transform + pos: 24.5,35.5 + parent: 588 + - uid: 1084 + components: + - type: Transform + pos: 27.5,35.5 + parent: 588 + - uid: 1085 + components: + - type: Transform + pos: 29.5,35.5 + parent: 588 + - uid: 1086 + components: + - type: Transform + pos: 28.5,35.5 + parent: 588 + - uid: 1089 + components: + - type: Transform + pos: 4.5,36.5 + parent: 588 + - uid: 1090 + components: + - type: Transform + pos: 33.5,34.5 + parent: 588 + - uid: 1091 + components: + - type: Transform + pos: 5.5,35.5 + parent: 588 + - uid: 1092 + components: + - type: Transform + pos: 29.5,36.5 + parent: 588 + - uid: 1093 + components: + - type: Transform + pos: 34.5,34.5 + parent: 588 + - uid: 1094 + components: + - type: Transform + pos: 34.5,35.5 + parent: 588 + - uid: 1095 + components: + - type: Transform + pos: 26.5,35.5 + parent: 588 + - uid: 1096 + components: + - type: Transform + pos: 25.5,35.5 + parent: 588 + - uid: 1097 + components: + - type: Transform + pos: 33.5,36.5 + parent: 588 + - uid: 1098 + components: + - type: Transform + pos: 30.5,35.5 + parent: 588 + - uid: 1117 + components: + - type: Transform + pos: 16.5,26.5 + parent: 588 + - uid: 1121 + components: + - type: Transform + pos: 17.5,26.5 + parent: 588 + - uid: 1122 + components: + - type: Transform + pos: 18.5,26.5 + parent: 588 + - uid: 1123 + components: + - type: Transform + pos: 17.5,27.5 + parent: 588 + - uid: 1124 + components: + - type: Transform + pos: 17.5,28.5 + parent: 588 + - uid: 1125 + components: + - type: Transform + pos: 17.5,24.5 + parent: 588 + - uid: 1126 + components: + - type: Transform + pos: 17.5,25.5 + parent: 588 + - uid: 1127 + components: + - type: Transform + pos: 20.5,26.5 + parent: 588 + - uid: 1128 + components: + - type: Transform + pos: 21.5,26.5 + parent: 588 + - uid: 1129 + components: + - type: Transform + pos: 22.5,26.5 + parent: 588 + - uid: 1130 + components: + - type: Transform + pos: 21.5,27.5 + parent: 588 + - uid: 1131 + components: + - type: Transform + pos: 21.5,28.5 + parent: 588 + - uid: 1132 + components: + - type: Transform + pos: 21.5,25.5 + parent: 588 + - uid: 1133 + components: + - type: Transform + pos: 21.5,24.5 + parent: 588 + - uid: 1134 + components: + - type: Transform + pos: 24.5,26.5 + parent: 588 + - uid: 1135 + components: + - type: Transform + pos: 25.5,26.5 + parent: 588 + - uid: 1136 + components: + - type: Transform + pos: 26.5,26.5 + parent: 588 + - uid: 1137 + components: + - type: Transform + pos: 25.5,27.5 + parent: 588 + - uid: 1138 + components: + - type: Transform + pos: 25.5,28.5 + parent: 588 + - uid: 1139 + components: + - type: Transform + pos: 25.5,25.5 + parent: 588 + - uid: 1140 + components: + - type: Transform + pos: 25.5,24.5 + parent: 588 + - uid: 1170 + components: + - type: Transform + pos: 3.5,36.5 + parent: 588 + - uid: 1171 + components: + - type: Transform + pos: 2.5,36.5 + parent: 588 + - uid: 1172 + components: + - type: Transform + pos: 1.5,36.5 + parent: 588 + - uid: 1173 + components: + - type: Transform + pos: 0.5,36.5 + parent: 588 + - uid: 1174 + components: + - type: Transform + pos: 0.5,35.5 + parent: 588 + - uid: 1175 + components: + - type: Transform + pos: 6.5,34.5 + parent: 588 + - uid: 1176 + components: + - type: Transform + pos: 7.5,34.5 + parent: 588 + - uid: 1177 + components: + - type: Transform + pos: 8.5,34.5 + parent: 588 + - uid: 1178 + components: + - type: Transform + pos: 9.5,34.5 + parent: 588 + - uid: 1179 + components: + - type: Transform + pos: 10.5,34.5 + parent: 588 + - uid: 1268 + components: + - type: Transform + pos: 30.5,26.5 + parent: 588 + - uid: 1269 + components: + - type: Transform + pos: 29.5,27.5 + parent: 588 + - uid: 1270 + components: + - type: Transform + pos: 29.5,28.5 + parent: 588 + - uid: 1271 + components: + - type: Transform + pos: 29.5,25.5 + parent: 588 + - uid: 1272 + components: + - type: Transform + pos: 29.5,24.5 + parent: 588 + - uid: 1273 + components: + - type: Transform + pos: 32.5,26.5 + parent: 588 + - uid: 1274 + components: + - type: Transform + pos: 33.5,26.5 + parent: 588 + - uid: 1275 + components: + - type: Transform + pos: 34.5,26.5 + parent: 588 + - uid: 1276 + components: + - type: Transform + pos: 33.5,27.5 + parent: 588 + - uid: 1277 + components: + - type: Transform + pos: 33.5,28.5 + parent: 588 + - uid: 1278 + components: + - type: Transform + pos: 33.5,25.5 + parent: 588 + - uid: 1279 + components: + - type: Transform + pos: 33.5,24.5 + parent: 588 + - uid: 1306 + components: + - type: Transform + pos: 27.5,40.5 + parent: 588 + - uid: 1307 + components: + - type: Transform + pos: 26.5,40.5 + parent: 588 + - uid: 1308 + components: + - type: Transform + pos: 25.5,40.5 + parent: 588 + - uid: 1309 + components: + - type: Transform + pos: 24.5,40.5 + parent: 588 + - uid: 1310 + components: + - type: Transform + pos: 24.5,39.5 + parent: 588 + - uid: 1311 + components: + - type: Transform + pos: 24.5,38.5 + parent: 588 + - uid: 1312 + components: + - type: Transform + pos: 25.5,38.5 + parent: 588 + - uid: 1313 + components: + - type: Transform + pos: 26.5,38.5 + parent: 588 + - uid: 1314 + components: + - type: Transform + pos: 27.5,38.5 + parent: 588 + - uid: 1315 + components: + - type: Transform + pos: 28.5,38.5 + parent: 588 + - uid: 1316 + components: + - type: Transform + pos: 29.5,38.5 + parent: 588 + - uid: 1317 + components: + - type: Transform + pos: 30.5,38.5 + parent: 588 + - uid: 1318 + components: + - type: Transform + pos: 30.5,39.5 + parent: 588 + - uid: 1426 + components: + - type: Transform + pos: 11.5,42.5 + parent: 588 + - uid: 1427 + components: + - type: Transform + pos: 11.5,43.5 + parent: 588 + - uid: 1428 + components: + - type: Transform + pos: 11.5,44.5 + parent: 588 + - uid: 1429 + components: + - type: Transform + pos: 11.5,45.5 + parent: 588 + - uid: 1430 + components: + - type: Transform + pos: 11.5,46.5 + parent: 588 + - uid: 1431 + components: + - type: Transform + pos: 11.5,47.5 + parent: 588 + - uid: 1432 + components: + - type: Transform + pos: 11.5,48.5 + parent: 588 + - uid: 1433 + components: + - type: Transform + pos: 10.5,45.5 + parent: 588 + - uid: 1434 + components: + - type: Transform + pos: 9.5,45.5 + parent: 588 + - uid: 1435 + components: + - type: Transform + pos: 8.5,45.5 + parent: 588 + - uid: 1436 + components: + - type: Transform + pos: 12.5,45.5 + parent: 588 + - uid: 1437 + components: + - type: Transform + pos: 13.5,45.5 + parent: 588 + - uid: 1438 + components: + - type: Transform + pos: 14.5,45.5 + parent: 588 + - uid: 1439 + components: + - type: Transform + pos: 13.5,46.5 + parent: 588 + - uid: 1440 + components: + - type: Transform + pos: 13.5,47.5 + parent: 588 + - uid: 1441 + components: + - type: Transform + pos: 9.5,46.5 + parent: 588 + - uid: 1442 + components: + - type: Transform + pos: 9.5,47.5 + parent: 588 + - uid: 1443 + components: + - type: Transform + pos: 10.5,43.5 + parent: 588 + - uid: 1444 + components: + - type: Transform + pos: 9.5,43.5 + parent: 588 + - uid: 1445 + components: + - type: Transform + pos: 12.5,43.5 + parent: 588 + - uid: 1446 + components: + - type: Transform + pos: 13.5,43.5 + parent: 588 + - uid: 1447 + components: + - type: Transform + pos: 3.5,42.5 + parent: 588 + - uid: 1448 + components: + - type: Transform + pos: 3.5,43.5 + parent: 588 + - uid: 1449 + components: + - type: Transform + pos: 3.5,44.5 + parent: 588 + - uid: 1450 + components: + - type: Transform + pos: 3.5,45.5 + parent: 588 + - uid: 1451 + components: + - type: Transform + pos: 3.5,46.5 + parent: 588 + - uid: 1452 + components: + - type: Transform + pos: 3.5,47.5 + parent: 588 + - uid: 1453 + components: + - type: Transform + pos: 3.5,48.5 + parent: 588 + - uid: 1454 + components: + - type: Transform + pos: 0.5,45.5 + parent: 588 + - uid: 1455 + components: + - type: Transform + pos: 1.5,45.5 + parent: 588 + - uid: 1456 + components: + - type: Transform + pos: 2.5,45.5 + parent: 588 + - uid: 1457 + components: + - type: Transform + pos: 3.5,45.5 + parent: 588 + - uid: 1458 + components: + - type: Transform + pos: 4.5,45.5 + parent: 588 + - uid: 1459 + components: + - type: Transform + pos: 5.5,45.5 + parent: 588 + - uid: 1460 + components: + - type: Transform + pos: 6.5,45.5 + parent: 588 + - uid: 1529 + components: + - type: Transform + pos: 19.5,47.5 + parent: 588 + - uid: 1530 + components: + - type: Transform + pos: 19.5,48.5 + parent: 588 + - uid: 1531 + components: + - type: Transform + pos: 18.5,48.5 + parent: 588 + - uid: 1532 + components: + - type: Transform + pos: 17.5,48.5 + parent: 588 + - uid: 1533 + components: + - type: Transform + pos: 16.5,48.5 + parent: 588 + - uid: 1534 + components: + - type: Transform + pos: 16.5,47.5 + parent: 588 + - uid: 1535 + components: + - type: Transform + pos: 16.5,46.5 + parent: 588 + - uid: 1536 + components: + - type: Transform + pos: 16.5,45.5 + parent: 588 + - uid: 1537 + components: + - type: Transform + pos: 16.5,44.5 + parent: 588 + - uid: 1538 + components: + - type: Transform + pos: 16.5,43.5 + parent: 588 + - uid: 1539 + components: + - type: Transform + pos: 16.5,42.5 + parent: 588 + - uid: 1540 + components: + - type: Transform + pos: 17.5,42.5 + parent: 588 + - uid: 1541 + components: + - type: Transform + pos: 18.5,42.5 + parent: 588 + - uid: 1542 + components: + - type: Transform + pos: 19.5,42.5 + parent: 588 + - uid: 1543 + components: + - type: Transform + pos: 20.5,42.5 + parent: 588 + - uid: 1544 + components: + - type: Transform + pos: 21.5,42.5 + parent: 588 + - uid: 1545 + components: + - type: Transform + pos: 22.5,42.5 + parent: 588 + - uid: 1546 + components: + - type: Transform + pos: 22.5,43.5 + parent: 588 + - uid: 1547 + components: + - type: Transform + pos: 22.5,44.5 + parent: 588 + - uid: 1548 + components: + - type: Transform + pos: 22.5,45.5 + parent: 588 + - uid: 1549 + components: + - type: Transform + pos: 22.5,46.5 + parent: 588 + - uid: 1550 + components: + - type: Transform + pos: 22.5,47.5 + parent: 588 + - uid: 1551 + components: + - type: Transform + pos: 22.5,48.5 + parent: 588 + - uid: 1552 + components: + - type: Transform + pos: 21.5,48.5 + parent: 588 + - uid: 1553 + components: + - type: Transform + pos: 20.5,48.5 + parent: 588 + - uid: 1554 + components: + - type: Transform + pos: 19.5,43.5 + parent: 588 + - uid: 1555 + components: + - type: Transform + pos: 19.5,44.5 + parent: 588 + - uid: 1556 + components: + - type: Transform + pos: 19.5,45.5 + parent: 588 + - uid: 1557 + components: + - type: Transform + pos: 19.5,46.5 + parent: 588 + - uid: 1807 + components: + - type: Transform + pos: 27.5,42.5 + parent: 588 + - uid: 1808 + components: + - type: Transform + pos: 27.5,43.5 + parent: 588 + - uid: 1809 + components: + - type: Transform + pos: 27.5,44.5 + parent: 588 + - uid: 1810 + components: + - type: Transform + pos: 27.5,45.5 + parent: 588 + - uid: 1811 + components: + - type: Transform + pos: 27.5,46.5 + parent: 588 + - uid: 1812 + components: + - type: Transform + pos: 27.5,47.5 + parent: 588 + - uid: 1813 + components: + - type: Transform + pos: 27.5,48.5 + parent: 588 + - uid: 1814 + components: + - type: Transform + pos: 28.5,45.5 + parent: 588 + - uid: 1815 + components: + - type: Transform + pos: 29.5,45.5 + parent: 588 + - uid: 1816 + components: + - type: Transform + pos: 30.5,45.5 + parent: 588 + - uid: 1817 + components: + - type: Transform + pos: 26.5,45.5 + parent: 588 + - uid: 1818 + components: + - type: Transform + pos: 25.5,45.5 + parent: 588 + - uid: 1819 + components: + - type: Transform + pos: 24.5,45.5 + parent: 588 + - uid: 1820 + components: + - type: Transform + pos: 26.5,47.5 + parent: 588 + - uid: 1821 + components: + - type: Transform + pos: 25.5,47.5 + parent: 588 + - uid: 1822 + components: + - type: Transform + pos: 28.5,47.5 + parent: 588 + - uid: 1823 + components: + - type: Transform + pos: 29.5,47.5 + parent: 588 + - uid: 1824 + components: + - type: Transform + pos: 26.5,43.5 + parent: 588 + - uid: 1825 + components: + - type: Transform + pos: 25.5,43.5 + parent: 588 + - uid: 1826 + components: + - type: Transform + pos: 28.5,43.5 + parent: 588 + - uid: 1827 + components: + - type: Transform + pos: 29.5,43.5 + parent: 588 +- proto: CableApcStack1 + entities: + - uid: 655 + components: + - type: Transform + pos: 16.273203,19.650417 + parent: 588 +- proto: CableHV + entities: + - uid: 462 + components: + - type: Transform + pos: 27.5,13.5 + parent: 588 + - uid: 466 + components: + - type: Transform + pos: 26.5,13.5 + parent: 588 + - uid: 468 + components: + - type: Transform + pos: 28.5,13.5 + parent: 588 + - uid: 485 + components: + - type: Transform + pos: 26.5,15.5 + parent: 588 + - uid: 486 + components: + - type: Transform + pos: 26.5,14.5 + parent: 588 + - uid: 487 + components: + - type: Transform + pos: 27.5,14.5 + parent: 588 + - uid: 488 + components: + - type: Transform + pos: 28.5,14.5 + parent: 588 + - uid: 489 + components: + - type: Transform + pos: 28.5,15.5 + parent: 588 + - uid: 743 + components: + - type: Transform + pos: 26.5,21.5 + parent: 588 + - uid: 744 + components: + - type: Transform + pos: 27.5,21.5 + parent: 588 + - uid: 745 + components: + - type: Transform + pos: 26.5,20.5 + parent: 588 + - uid: 746 + components: + - type: Transform + pos: 25.5,21.5 + parent: 588 + - uid: 768 + components: + - type: Transform + pos: 26.5,19.5 + parent: 588 + - uid: 778 + components: + - type: Transform + pos: 27.5,19.5 + parent: 588 + - uid: 978 + components: + - type: Transform + pos: 16.5,32.5 + parent: 588 + - uid: 979 + components: + - type: Transform + pos: 15.5,31.5 + parent: 588 + - uid: 980 + components: + - type: Transform + pos: 16.5,31.5 + parent: 588 + - uid: 981 + components: + - type: Transform + pos: 17.5,31.5 + parent: 588 + - uid: 982 + components: + - type: Transform + pos: 18.5,31.5 + parent: 588 + - uid: 983 + components: + - type: Transform + pos: 22.5,31.5 + parent: 588 + - uid: 984 + components: + - type: Transform + pos: 23.5,31.5 + parent: 588 + - uid: 985 + components: + - type: Transform + pos: 24.5,31.5 + parent: 588 + - uid: 986 + components: + - type: Transform + pos: 24.5,32.5 + parent: 588 + - uid: 987 + components: + - type: Transform + pos: 25.5,31.5 + parent: 588 + - uid: 989 + components: + - type: Transform + pos: 18.5,32.5 + parent: 588 + - uid: 990 + components: + - type: Transform + pos: 19.5,32.5 + parent: 588 + - uid: 991 + components: + - type: Transform + pos: 20.5,32.5 + parent: 588 + - uid: 992 + components: + - type: Transform + pos: 21.5,32.5 + parent: 588 + - uid: 993 + components: + - type: Transform + pos: 22.5,32.5 + parent: 588 + - uid: 1003 + components: + - type: Transform + pos: 16.5,30.5 + parent: 588 + - uid: 1004 + components: + - type: Transform + pos: 24.5,30.5 + parent: 588 + - uid: 1510 + components: + - type: Transform + pos: 18.5,44.5 + parent: 588 + - uid: 1511 + components: + - type: Transform + pos: 18.5,45.5 + parent: 588 + - uid: 1512 + components: + - type: Transform + pos: 20.5,45.5 + parent: 588 + - uid: 1513 + components: + - type: Transform + pos: 20.5,44.5 + parent: 588 + - uid: 1514 + components: + - type: Transform + pos: 19.5,44.5 + parent: 588 + - uid: 1522 + components: + - type: Transform + pos: 17.5,46.5 + parent: 588 + - uid: 1523 + components: + - type: Transform + pos: 21.5,46.5 + parent: 588 + - uid: 1524 + components: + - type: Transform + pos: 20.5,46.5 + parent: 588 + - uid: 1525 + components: + - type: Transform + pos: 18.5,46.5 + parent: 588 + - uid: 1526 + components: + - type: Transform + pos: 19.5,46.5 + parent: 588 +- proto: CableMV + entities: + - uid: 490 + components: + - type: Transform + pos: 27.5,13.5 + parent: 588 + - uid: 491 + components: + - type: Transform + pos: 26.5,13.5 + parent: 588 + - uid: 492 + components: + - type: Transform + pos: 25.5,13.5 + parent: 588 + - uid: 775 + components: + - type: Transform + pos: 27.5,19.5 + parent: 588 + - uid: 776 + components: + - type: Transform + pos: 26.5,19.5 + parent: 588 + - uid: 777 + components: + - type: Transform + pos: 25.5,19.5 + parent: 588 + - uid: 1527 + components: + - type: Transform + pos: 19.5,46.5 + parent: 588 + - uid: 1528 + components: + - type: Transform + pos: 19.5,47.5 + parent: 588 +- proto: CableTerminal + entities: + - uid: 463 + components: + - type: Transform + pos: 26.5,14.5 + parent: 588 + - uid: 464 + components: + - type: Transform + pos: 27.5,14.5 + parent: 588 + - uid: 465 + components: + - type: Transform + pos: 28.5,14.5 + parent: 588 + - uid: 767 + components: + - type: Transform + pos: 26.5,20.5 + parent: 588 + - uid: 970 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 18.5,31.5 + parent: 588 + - uid: 976 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 22.5,31.5 + parent: 588 + - uid: 1558 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 18.5,45.5 + parent: 588 + - uid: 1559 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 20.5,45.5 + parent: 588 +- proto: Carpet + entities: + - uid: 1632 + components: + - type: Transform + pos: 24.5,0.5 + parent: 588 + - uid: 1633 + components: + - type: Transform + pos: 25.5,0.5 + parent: 588 + - uid: 1634 + components: + - type: Transform + pos: 25.5,1.5 + parent: 588 + - uid: 1635 + components: + - type: Transform + pos: 24.5,1.5 + parent: 588 +- proto: CarpetBlue + entities: + - uid: 1636 + components: + - type: Transform + pos: 27.5,0.5 + parent: 588 + - uid: 1637 + components: + - type: Transform + pos: 28.5,1.5 + parent: 588 + - uid: 1638 + components: + - type: Transform + pos: 27.5,1.5 + parent: 588 + - uid: 1639 + components: + - type: Transform + pos: 28.5,0.5 + parent: 588 +- proto: CarpetPurple + entities: + - uid: 1626 + components: + - type: Transform + pos: 25.5,4.5 + parent: 588 + - uid: 1627 + components: + - type: Transform + pos: 25.5,3.5 + parent: 588 + - uid: 1628 + components: + - type: Transform + pos: 26.5,3.5 + parent: 588 + - uid: 1629 + components: + - type: Transform + pos: 27.5,3.5 + parent: 588 + - uid: 1630 + components: + - type: Transform + pos: 27.5,4.5 + parent: 588 + - uid: 1631 + components: + - type: Transform + pos: 26.5,4.5 + parent: 588 +- proto: Catwalk + entities: + - uid: 141 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 6.5,24.5 + parent: 588 + - uid: 142 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 6.5,26.5 + parent: 588 + - uid: 143 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 6.5,25.5 + parent: 588 + - uid: 248 + components: + - type: Transform + pos: 8.5,4.5 + parent: 588 + - uid: 249 + components: + - type: Transform + pos: 8.5,2.5 + parent: 588 + - uid: 250 + components: + - type: Transform + pos: 8.5,3.5 + parent: 588 + - uid: 251 + components: + - type: Transform + pos: 8.5,1.5 + parent: 588 + - uid: 471 + components: + - type: Transform + pos: 27.5,14.5 + parent: 588 + - uid: 473 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 29.5,12.5 + parent: 588 + - uid: 474 + components: + - type: Transform + pos: 28.5,14.5 + parent: 588 + - uid: 475 + components: + - type: Transform + pos: 26.5,14.5 + parent: 588 + - uid: 512 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 25.5,12.5 + parent: 588 + - uid: 513 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 28.5,12.5 + parent: 588 + - uid: 515 + components: + - type: Transform + pos: 25.5,16.5 + parent: 588 + - uid: 516 + components: + - type: Transform + pos: 26.5,16.5 + parent: 588 + - uid: 517 + components: + - type: Transform + pos: 27.5,16.5 + parent: 588 + - uid: 518 + components: + - type: Transform + pos: 28.5,16.5 + parent: 588 + - uid: 519 + components: + - type: Transform + pos: 29.5,16.5 + parent: 588 + - uid: 520 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 27.5,12.5 + parent: 588 + - uid: 521 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 26.5,12.5 + parent: 588 + - uid: 769 + components: + - type: Transform + pos: 26.5,20.5 + parent: 588 + - uid: 832 + components: + - type: Transform + pos: 3.5,31.5 + parent: 588 + - uid: 833 + components: + - type: Transform + pos: 4.5,31.5 + parent: 588 + - uid: 834 + components: + - type: Transform + pos: 5.5,31.5 + parent: 588 + - uid: 835 + components: + - type: Transform + pos: 6.5,31.5 + parent: 588 + - uid: 836 + components: + - type: Transform + pos: 7.5,31.5 + parent: 588 + - uid: 837 + components: + - type: Transform + pos: 8.5,31.5 + parent: 588 + - uid: 838 + components: + - type: Transform + pos: 9.5,31.5 + parent: 588 + - uid: 839 + components: + - type: Transform + pos: 6.5,32.5 + parent: 588 + - uid: 840 + components: + - type: Transform + pos: 6.5,30.5 + parent: 588 + - uid: 841 + components: + - type: Transform + pos: 5.5,32.5 + parent: 588 + - uid: 842 + components: + - type: Transform + pos: 7.5,32.5 + parent: 588 + - uid: 843 + components: + - type: Transform + pos: 5.5,30.5 + parent: 588 + - uid: 844 + components: + - type: Transform + pos: 7.5,30.5 + parent: 588 + - uid: 861 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 6.5,27.5 + parent: 588 + - uid: 862 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 6.5,28.5 + parent: 588 + - uid: 863 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 4.5,28.5 + parent: 588 + - uid: 864 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 4.5,27.5 + parent: 588 + - uid: 865 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 4.5,26.5 + parent: 588 + - uid: 879 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 3.5,40.5 + parent: 588 + - uid: 880 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 3.5,39.5 + parent: 588 + - uid: 881 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 2.5,39.5 + parent: 588 + - uid: 882 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.5,39.5 + parent: 588 + - uid: 883 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 4.5,39.5 + parent: 588 + - uid: 884 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 5.5,39.5 + parent: 588 + - uid: 885 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 3.5,38.5 + parent: 588 + - uid: 914 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 3.5,10.5 + parent: 588 + - uid: 915 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 4.5,10.5 + parent: 588 + - uid: 916 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 5.5,10.5 + parent: 588 + - uid: 917 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 6.5,10.5 + parent: 588 + - uid: 918 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 7.5,10.5 + parent: 588 + - uid: 919 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 3.5,6.5 + parent: 588 + - uid: 920 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 4.5,6.5 + parent: 588 + - uid: 921 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 5.5,6.5 + parent: 588 + - uid: 922 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 6.5,6.5 + parent: 588 + - uid: 923 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 7.5,6.5 + parent: 588 + - uid: 955 + components: + - type: Transform + pos: 15.5,31.5 + parent: 588 + - uid: 956 + components: + - type: Transform + pos: 16.5,31.5 + parent: 588 + - uid: 957 + components: + - type: Transform + pos: 17.5,31.5 + parent: 588 + - uid: 958 + components: + - type: Transform + pos: 18.5,31.5 + parent: 588 + - uid: 959 + components: + - type: Transform + pos: 19.5,31.5 + parent: 588 + - uid: 960 + components: + - type: Transform + pos: 20.5,31.5 + parent: 588 + - uid: 961 + components: + - type: Transform + pos: 21.5,31.5 + parent: 588 + - uid: 962 + components: + - type: Transform + pos: 22.5,31.5 + parent: 588 + - uid: 963 + components: + - type: Transform + pos: 23.5,31.5 + parent: 588 + - uid: 964 + components: + - type: Transform + pos: 24.5,31.5 + parent: 588 + - uid: 965 + components: + - type: Transform + pos: 25.5,31.5 + parent: 588 + - uid: 994 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 20.5,32.5 + parent: 588 + - uid: 1158 + components: + - type: Transform + pos: 8.5,0.5 + parent: 588 + - uid: 1180 + components: + - type: Transform + pos: 1.5,36.5 + parent: 588 + - uid: 1181 + components: + - type: Transform + pos: 2.5,36.5 + parent: 588 + - uid: 1182 + components: + - type: Transform + pos: 3.5,36.5 + parent: 588 + - uid: 1183 + components: + - type: Transform + pos: 4.5,36.5 + parent: 588 + - uid: 1184 + components: + - type: Transform + pos: 5.5,36.5 + parent: 588 + - uid: 1185 + components: + - type: Transform + pos: 5.5,34.5 + parent: 588 + - uid: 1186 + components: + - type: Transform + pos: 6.5,34.5 + parent: 588 + - uid: 1187 + components: + - type: Transform + pos: 7.5,34.5 + parent: 588 + - uid: 1188 + components: + - type: Transform + pos: 8.5,34.5 + parent: 588 + - uid: 1189 + components: + - type: Transform + pos: 9.5,34.5 + parent: 588 + - uid: 1320 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 27.5,40.5 + parent: 588 + - uid: 1321 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 26.5,40.5 + parent: 588 + - uid: 1322 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 25.5,40.5 + parent: 588 + - uid: 1323 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 25.5,38.5 + parent: 588 + - uid: 1324 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 26.5,38.5 + parent: 588 + - uid: 1325 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 27.5,38.5 + parent: 588 + - uid: 1326 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 28.5,38.5 + parent: 588 + - uid: 1327 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 29.5,38.5 + parent: 588 + - uid: 1331 + components: + - type: Transform + pos: 4.5,45.5 + parent: 588 + - uid: 1336 + components: + - type: Transform + pos: 2.5,45.5 + parent: 588 + - uid: 1339 + components: + - type: Transform + pos: 3.5,45.5 + parent: 588 + - uid: 1342 + components: + - type: Transform + pos: 3.5,46.5 + parent: 588 + - uid: 1344 + components: + - type: Transform + pos: 3.5,44.5 + parent: 588 + - uid: 1346 + components: + - type: Transform + pos: 2.5,44.5 + parent: 588 + - uid: 1347 + components: + - type: Transform + pos: 4.5,44.5 + parent: 588 + - uid: 1348 + components: + - type: Transform + pos: 4.5,44.5 + parent: 588 + - uid: 1349 + components: + - type: Transform + pos: 4.5,46.5 + parent: 588 + - uid: 1350 + components: + - type: Transform + pos: 2.5,46.5 + parent: 588 + - uid: 1494 + components: + - type: Transform + pos: 17.5,42.5 + parent: 588 + - uid: 1495 + components: + - type: Transform + pos: 18.5,42.5 + parent: 588 + - uid: 1496 + components: + - type: Transform + pos: 19.5,42.5 + parent: 588 + - uid: 1497 + components: + - type: Transform + pos: 20.5,42.5 + parent: 588 + - uid: 1498 + components: + - type: Transform + pos: 21.5,42.5 + parent: 588 + - uid: 1499 + components: + - type: Transform + pos: 17.5,48.5 + parent: 588 + - uid: 1500 + components: + - type: Transform + pos: 18.5,48.5 + parent: 588 + - uid: 1501 + components: + - type: Transform + pos: 19.5,48.5 + parent: 588 + - uid: 1502 + components: + - type: Transform + pos: 20.5,48.5 + parent: 588 + - uid: 1503 + components: + - type: Transform + pos: 21.5,48.5 + parent: 588 + - uid: 1516 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 19.5,44.5 + parent: 588 + - uid: 1517 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 19.5,45.5 + parent: 588 + - uid: 1518 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 18.5,45.5 + parent: 588 + - uid: 1519 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 20.5,45.5 + parent: 588 + - uid: 1582 + components: + - type: Transform + pos: 28.5,22.5 + parent: 588 + - uid: 1583 + components: + - type: Transform + pos: 28.5,21.5 + parent: 588 + - uid: 1584 + components: + - type: Transform + pos: 28.5,20.5 + parent: 588 + - uid: 1585 + components: + - type: Transform + pos: 28.5,19.5 + parent: 588 + - uid: 1586 + components: + - type: Transform + pos: 28.5,18.5 + parent: 588 + - uid: 1587 + components: + - type: Transform + pos: 24.5,22.5 + parent: 588 + - uid: 1588 + components: + - type: Transform + pos: 24.5,21.5 + parent: 588 + - uid: 1589 + components: + - type: Transform + pos: 24.5,20.5 + parent: 588 + - uid: 1590 + components: + - type: Transform + pos: 24.5,19.5 + parent: 588 + - uid: 1591 + components: + - type: Transform + pos: 24.5,18.5 + parent: 588 +- proto: Cautery + entities: + - uid: 1474 + components: + - type: Transform + pos: 8.533231,42.775993 + parent: 588 +- proto: Chair + entities: + - uid: 357 + components: + - type: Transform + pos: 23.5,4.5 + parent: 588 + - uid: 421 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 14.5,9.5 + parent: 588 + - uid: 422 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 20.5,9.5 + parent: 588 + - uid: 423 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 20.5,7.5 + parent: 588 + - uid: 533 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 19.5,14.5 + parent: 588 + - uid: 534 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 19.5,15.5 + parent: 588 + - uid: 537 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 21.5,14.5 + parent: 588 + - uid: 569 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 11.5,13.5 + parent: 588 + - uid: 716 + components: + - type: Transform + pos: 18.5,19.5 + parent: 588 + - uid: 717 + components: + - type: Transform + pos: 19.5,19.5 + parent: 588 + - uid: 718 + components: + - type: Transform + pos: 22.5,19.5 + parent: 588 + - uid: 719 + components: + - type: Transform + pos: 21.5,19.5 + parent: 588 + - uid: 1280 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 17.5,38.5 + parent: 588 + - uid: 1281 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 18.5,38.5 + parent: 588 + - uid: 1282 + components: + - type: Transform + pos: 20.5,40.5 + parent: 588 + - uid: 1283 + components: + - type: Transform + pos: 21.5,40.5 + parent: 588 + - uid: 1865 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 14.5,7.5 + parent: 588 +- proto: ChairFolding + entities: + - uid: 344 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 31.5,0.5 + parent: 588 + - uid: 345 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 32.5,1.5 + parent: 588 + - uid: 346 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 32.5,0.5 + parent: 588 + - uid: 347 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 31.5,3.5 + parent: 588 + - uid: 348 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 32.5,3.5 + parent: 588 + - uid: 349 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 31.5,4.5 + parent: 588 + - uid: 350 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 33.5,1.5 + parent: 588 + - uid: 351 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 33.5,0.5 + parent: 588 + - uid: 352 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 33.5,4.5 + parent: 588 + - uid: 353 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 32.5,4.5 + parent: 588 + - uid: 1212 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 7.5,36.5 + parent: 588 +- proto: ChairFoldingSpawnFolded + entities: + - uid: 354 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 31.53707,1.6455604 + parent: 588 + - uid: 355 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 33.595894,3.4052575 + parent: 588 +- proto: ChairOfficeDark + entities: + - uid: 330 + components: + - type: Transform + pos: 19.5,1.5 + parent: 588 + - uid: 331 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 20.5,3.5 + parent: 588 + - uid: 358 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 24.5,0.5 + parent: 588 + - uid: 359 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 25.5,0.5 + parent: 588 + - uid: 360 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 28.5,0.5 + parent: 588 + - uid: 361 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 27.5,0.5 + parent: 588 + - uid: 571 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 13.5,12.5 + parent: 588 +- proto: ChairOfficeLight + entities: + - uid: 631 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 9.5,19.5 + parent: 588 + - uid: 638 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 7.5,21.5 + parent: 588 + - uid: 707 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 15.5,21.5 + parent: 588 +- proto: ChairPilotSeat + entities: + - uid: 356 + components: + - type: Transform + pos: 26.5,4.5 + parent: 588 +- proto: ChairWood + entities: + - uid: 1049 + components: + - type: Transform + pos: 20.5,25.5 + parent: 588 + - uid: 1050 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 22.5,27.5 + parent: 588 + - uid: 1231 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 21.5,34.5 + parent: 588 + - uid: 1232 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 13.5,36.5 + parent: 588 + - uid: 1790 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 25.5,46.5 + parent: 588 + - uid: 1791 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 25.5,44.5 + parent: 588 +- proto: CigarGold + entities: + - uid: 1219 + components: + - type: Transform + pos: 7.4719925,36.539555 + parent: 588 +- proto: ClosetBombFilled + entities: + - uid: 413 + components: + - type: Transform + pos: 14.5,6.5 + parent: 588 + - uid: 1014 + components: + - type: Transform + pos: 12.5,27.5 + parent: 588 + - uid: 1026 + components: + - type: Transform + pos: 16.5,27.5 + parent: 588 +- proto: ClosetEmergencyFilledRandom + entities: + - uid: 1203 + components: + - type: Transform + pos: 12.5,30.5 + parent: 588 + - uid: 1204 + components: + - type: Transform + pos: 0.5,32.5 + parent: 588 + - uid: 1205 + components: + - type: Transform + pos: 9.5,9.5 + parent: 588 + - uid: 1207 + components: + - type: Transform + pos: 1.5,7.5 + parent: 588 +- proto: ClosetFireFilled + entities: + - uid: 1194 + components: + - type: Transform + pos: 1.5,9.5 + parent: 588 + - uid: 1195 + components: + - type: Transform + pos: 9.5,7.5 + parent: 588 + - uid: 1196 + components: + - type: Transform + pos: 6.5,40.5 + parent: 588 + - uid: 1197 + components: + - type: Transform + pos: 0.5,38.5 + parent: 588 +- proto: ClosetL3SecurityFilled + entities: + - uid: 415 + components: + - type: Transform + pos: 20.5,10.5 + parent: 588 +- proto: ClosetToolFilled + entities: + - uid: 1007 + components: + - type: Transform + pos: 14.5,30.5 + parent: 588 +- proto: ClosetWallMaintenanceFilledRandom + entities: + - uid: 499 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 25.5,15.5 + parent: 588 + - uid: 868 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 5.5,27.5 + parent: 588 + - uid: 1564 + components: + - type: Transform + pos: 17.5,43.5 + parent: 588 + - uid: 1565 + components: + - type: Transform + pos: 21.5,43.5 + parent: 588 +- proto: ClothingBeltChampion + entities: + - uid: 1236 + components: + - type: Transform + pos: 10.581136,39.53631 + parent: 588 +- proto: ClothingEyesGlassesMeson + entities: + - uid: 1108 + components: + - type: Transform + pos: 25.666832,30.643515 + parent: 588 +- proto: ClothingHandsGlovesNitrile + entities: + - uid: 1715 + components: + - type: Transform + pos: 10.432637,44.476112 + parent: 588 +- proto: ClothingHeadBandRed + entities: + - uid: 1295 + components: + - type: Transform + pos: 12.571781,39.694115 + parent: 588 +- proto: ClothingHeadHatFedoraBrown + entities: + - uid: 577 + components: + - type: Transform + pos: 12.686508,13.58602 + parent: 588 +- proto: ClothingHeadHatPwig + entities: + - uid: 369 + components: + - type: Transform + pos: 25.824945,3.5783403 + parent: 588 +- proto: ClothingHeadHatSecsoftFlipped + entities: + - uid: 606 + components: + - type: Transform + pos: 12.705482,6.671774 + parent: 588 + - uid: 1027 + components: + - type: Transform + pos: 18.403675,25.53719 + parent: 588 +- proto: ClothingHeadHatSurgcapPurple + entities: + - uid: 1711 + components: + - type: Transform + pos: 10.304593,44.632217 + parent: 588 +- proto: ClothingHeadHelmetRiot + entities: + - uid: 1617 + components: + - type: Transform + pos: 22.499683,6.7142525 + parent: 588 +- proto: ClothingHeadHelmetThunderdome + entities: + - uid: 1240 + components: + - type: Transform + pos: 34.666565,24.66942 + parent: 588 +- proto: ClothingNeckLawyerbadge + entities: + - uid: 326 + components: + - type: Transform + pos: 21.586027,4.583762 + parent: 588 +- proto: ClothingNeckTieDet + entities: + - uid: 573 + components: + - type: Transform + pos: 12.714905,13.486683 + parent: 588 +- proto: ClothingOuterArmorReflective + entities: + - uid: 1031 + components: + - type: Transform + pos: 18.47467,24.458666 + parent: 588 +- proto: ClothingOuterCoatDetectiveLoadout + entities: + - uid: 574 + components: + - type: Transform + pos: 13.396446,12.479115 + parent: 588 +- proto: ClothingOuterRobesJudge + entities: + - uid: 370 + components: + - type: Transform + pos: 27.40101,3.677678 + parent: 588 +- proto: ClothingShoesBootsCombatFilled + entities: + - uid: 1036 + components: + - type: Transform + pos: 12.582174,25.636528 + parent: 588 +- proto: ClothingShoesBootsLaceup + entities: + - uid: 372 + components: + - type: Transform + pos: 18.586912,0.70824456 + parent: 588 +- proto: ClothingUniformJumpskirtColorMaroon + entities: + - uid: 1714 + components: + - type: Transform + pos: 10.673761,44.53288 + parent: 588 +- proto: ClothingUniformJumpsuitColorMaroon + entities: + - uid: 1713 + components: + - type: Transform + pos: 10.645364,44.67479 + parent: 588 +- proto: ClusterBangFull + entities: + - uid: 599 + components: + - type: Transform + pos: 33.484257,28.42918 + parent: 588 +- proto: ComputerAlert + entities: + - uid: 999 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 17.5,30.5 + parent: 588 + - uid: 1001 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 23.5,30.5 + parent: 588 + - uid: 1561 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 21.5,46.5 + parent: 588 +- proto: computerBodyScanner + entities: + - uid: 1394 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 9.5,44.5 + parent: 588 + - uid: 1423 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 13.5,44.5 + parent: 588 +- proto: ComputerCriminalRecords + entities: + - uid: 461 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 11.5,0.5 + parent: 588 + - uid: 634 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 9.5,18.5 + parent: 588 +- proto: ComputerPowerMonitoring + entities: + - uid: 1000 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 16.5,30.5 + parent: 588 + - uid: 1002 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 24.5,30.5 + parent: 588 + - uid: 1560 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 17.5,46.5 + parent: 588 +- proto: ComputerSurveillanceCameraMonitor + entities: + - uid: 635 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 6.5,21.5 + parent: 588 +- proto: ComputerTelevision + entities: + - uid: 1229 + components: + - type: Transform + pos: 12.5,34.5 + parent: 588 + - uid: 1230 + components: + - type: Transform + pos: 22.5,36.5 + parent: 588 +- proto: CrateEngineeringGear + entities: + - uid: 1008 + components: + - type: Transform + pos: 26.5,30.5 + parent: 588 +- proto: CrateFunBoardGames + entities: + - uid: 1845 + components: + - type: Transform + pos: 26.5,48.5 + parent: 588 +- proto: CrateFunParty + entities: + - uid: 1876 + components: + - type: Transform + pos: 25.5,43.5 + parent: 588 +- proto: CrateHydroponicsSeedsExotic + entities: + - uid: 1660 + components: + - type: Transform + pos: 31.5,22.5 + parent: 588 +- proto: CrayonBox + entities: + - uid: 1057 + components: + - type: Transform + pos: 20.47107,24.608877 + parent: 588 + - uid: 1116 + components: + - type: Transform + pos: 20.607256,14.646415 + parent: 588 +- proto: CryoPod + entities: + - uid: 1395 + components: + - type: Transform + pos: 8.5,47.5 + parent: 588 + - uid: 1397 + components: + - type: Transform + pos: 14.5,47.5 + parent: 588 +- proto: DebugSMES + entities: + - uid: 971 + components: + - type: Transform + pos: 22.5,32.5 + parent: 588 + - uid: 974 + components: + - type: Transform + pos: 18.5,32.5 + parent: 588 +- proto: DeployableBarrier + entities: + - uid: 1233 + components: + - type: Transform + pos: 32.5,24.5 + parent: 588 +- proto: DiceBag + entities: + - uid: 552 + components: + - type: Transform + pos: 20.294882,15.426926 + parent: 588 +- proto: DiseaseDiagnoser + entities: + - uid: 1424 + components: + - type: Transform + pos: 14.5,44.5 + parent: 588 +- proto: DisposalUnit + entities: + - uid: 550 + components: + - type: Transform + pos: 22.5,16.5 + parent: 588 + - uid: 725 + components: + - type: Transform + pos: 21.5,22.5 + parent: 588 + - uid: 766 + components: + - type: Transform + pos: 9.5,22.5 + parent: 588 + - uid: 1288 + components: + - type: Transform + pos: 22.5,38.5 + parent: 588 +- proto: DonkpocketBoxSpawner + entities: + - uid: 526 + components: + - type: Transform + pos: 16.5,13.5 + parent: 588 + - uid: 723 + components: + - type: Transform + pos: 18.5,21.5 + parent: 588 +- proto: DoorElectronics + entities: + - uid: 659 + components: + - type: Transform + pos: 12.581519,21.410114 + parent: 588 + - uid: 1074 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 25.639427,25.54549 + parent: 588 +- proto: Dresser + entities: + - uid: 1051 + components: + - type: Transform + pos: 22.5,25.5 + parent: 588 + - uid: 1052 + components: + - type: Transform + pos: 20.5,27.5 + parent: 588 + - uid: 1061 + components: + - type: Transform + pos: 24.5,25.5 + parent: 588 + - uid: 1221 + components: + - type: Transform + pos: 21.5,36.5 + parent: 588 + - uid: 1222 + components: + - type: Transform + pos: 13.5,34.5 + parent: 588 +- proto: DrinkDetFlask + entities: + - uid: 1577 + components: + - type: Transform + pos: 12.606661,13.037249 + parent: 588 +- proto: DrinkMugMetal + entities: + - uid: 1294 + components: + - type: Transform + pos: 22.442232,12.514399 + parent: 588 +- proto: DrinkMugRed + entities: + - uid: 721 + components: + - type: Transform + pos: 22.448559,18.561966 + parent: 588 + - uid: 1293 + components: + - type: Transform + pos: 22.328642,12.741456 + parent: 588 +- proto: DrinkShinyFlask + entities: + - uid: 1874 + components: + - type: Transform + pos: 6.890398,22.663696 + parent: 588 +- proto: DrinkShotGlass + entities: + - uid: 578 + components: + - type: Transform + pos: 12.412022,12.535878 + parent: 588 + - uid: 579 + components: + - type: Transform + pos: 12.539811,12.748745 + parent: 588 +- proto: DrinkWaterCup + entities: + - uid: 722 + components: + - type: Transform + pos: 18.373508,18.661304 + parent: 588 + - uid: 762 + components: + - type: Transform + pos: 6.313587,19.590261 + parent: 588 + - uid: 763 + components: + - type: Transform + pos: 6.441377,19.419968 + parent: 588 +- proto: EmergencyLight + entities: + - uid: 1716 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 13.5,6.5 + parent: 588 + - type: PointLight + enabled: True + - uid: 1717 + components: + - type: Transform + pos: 21.5,10.5 + parent: 588 + - type: PointLight + enabled: True + - uid: 1718 + components: + - type: Transform + pos: 30.5,4.5 + parent: 588 + - type: PointLight + enabled: True + - uid: 1719 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 13.5,0.5 + parent: 588 + - type: PointLight + enabled: True + - uid: 1720 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 3.5,0.5 + parent: 588 + - type: PointLight + enabled: True + - uid: 1721 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.5,12.5 + parent: 588 + - type: PointLight + enabled: True + - uid: 1722 + components: + - type: Transform + pos: 18.5,16.5 + parent: 588 + - type: PointLight + enabled: True + - uid: 1723 + components: + - type: Transform + pos: 31.5,22.5 + parent: 588 + - type: PointLight + enabled: True + - uid: 1724 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 34.5,25.5 + parent: 588 + - type: PointLight + enabled: True + - uid: 1726 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 30.5,25.5 + parent: 588 + - type: PointLight + enabled: True + - uid: 1727 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 26.5,27.5 + parent: 588 + - type: PointLight + enabled: True + - uid: 1728 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 20.5,27.5 + parent: 588 + - type: PointLight + enabled: True + - uid: 1729 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 18.5,27.5 + parent: 588 + - type: PointLight + enabled: True + - uid: 1730 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 12.5,25.5 + parent: 588 + - type: PointLight + enabled: True + - uid: 1731 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 0.5,7.5 + parent: 588 + - type: PointLight + enabled: True + - uid: 1732 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 10.5,9.5 + parent: 588 + - type: PointLight + enabled: True + - uid: 1733 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 4.5,20.5 + parent: 588 + - type: PointLight + enabled: True + - uid: 1734 + components: + - type: Transform + pos: 1.5,24.5 + parent: 588 + - type: PointLight + enabled: True + - uid: 1735 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.5,30.5 + parent: 588 + - type: PointLight + enabled: True + - uid: 1736 + components: + - type: Transform + pos: 11.5,32.5 + parent: 588 + - type: PointLight + enabled: True + - uid: 1737 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 6.5,40.5 + parent: 588 + - type: PointLight + enabled: True + - uid: 1738 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 8.5,40.5 + parent: 588 + - type: PointLight + enabled: True + - uid: 1739 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 22.5,30.5 + parent: 588 + - type: PointLight + enabled: True + - uid: 1740 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 6.5,19.5 + parent: 588 + - type: PointLight + enabled: True + - uid: 1742 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 16.5,21.5 + parent: 588 + - type: PointLight + enabled: True + - uid: 1744 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 19.5,18.5 + parent: 588 + - type: PointLight + enabled: True + - uid: 1745 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 16.5,34.5 + parent: 588 + - type: PointLight + enabled: True + - uid: 1746 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 30.5,34.5 + parent: 588 + - type: PointLight + enabled: True + - uid: 1747 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 20.5,38.5 + parent: 588 + - type: PointLight + enabled: True + - uid: 1748 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 20.5,44.5 + parent: 588 + - type: PointLight + enabled: True + - uid: 1749 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 8.5,44.5 + parent: 588 + - type: PointLight + enabled: True + - uid: 1750 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 5.5,46.5 + parent: 588 + - type: PointLight + enabled: True + - uid: 1751 + components: + - type: Transform + pos: 30.5,6.5 + parent: 588 + - type: PointLight + enabled: True + - uid: 1752 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 28.5,10.5 + parent: 588 + - type: PointLight + enabled: True + - uid: 1832 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 30.5,46.5 + parent: 588 + - type: PointLight + enabled: True +- proto: EmergencyRollerBed + entities: + - uid: 1141 + components: + - type: Transform + pos: 30.5,25.5 + parent: 588 + - uid: 1142 + components: + - type: Transform + pos: 30.5,24.5 + parent: 588 +- proto: ExtinguisherCabinetFilled + entities: + - uid: 867 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 5.5,26.5 + parent: 588 + - uid: 1198 + components: + - type: Transform + pos: 2.5,8.5 + parent: 588 + - uid: 1199 + components: + - type: Transform + pos: 8.5,8.5 + parent: 588 + - uid: 1200 + components: + - type: Transform + pos: 8.5,35.5 + parent: 588 + - uid: 1201 + components: + - type: Transform + pos: 1.5,35.5 + parent: 588 + - uid: 1202 + components: + - type: Transform + pos: 25.5,14.5 + parent: 588 + - uid: 1328 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 28.5,39.5 + parent: 588 + - uid: 1566 + components: + - type: Transform + pos: 17.5,44.5 + parent: 588 +- proto: filingCabinetRandom + entities: + - uid: 320 + components: + - type: Transform + pos: 21.5,0.5 + parent: 588 + - uid: 321 + components: + - type: Transform + pos: 20.5,0.5 + parent: 588 +- proto: filingCabinetTallRandom + entities: + - uid: 1396 + components: + - type: Transform + pos: 8.5,44.5 + parent: 588 +- proto: Flash + entities: + - uid: 1209 + components: + - type: Transform + pos: 10.726851,19.047483 + parent: 588 +- proto: FlashlightSeclite + entities: + - uid: 374 + components: + - type: Transform + pos: 13.377204,0.54605544 + parent: 588 +- proto: FloodlightBroken + entities: + - uid: 1193 + components: + - type: Transform + pos: 9.462372,35.6454 + parent: 588 +- proto: FloorDrain + entities: + - uid: 944 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 8.5,28.5 + parent: 588 + - type: Fixtures + fixtures: {} + - uid: 945 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 10.5,28.5 + parent: 588 + - type: Fixtures + fixtures: {} +- proto: FloorLavaEntity + entities: + - uid: 47 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 5.5,8.5 + parent: 588 + - uid: 49 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 6.5,8.5 + parent: 588 + - uid: 458 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 5.5,10.5 + parent: 588 + - uid: 459 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 4.5,8.5 + parent: 588 + - uid: 460 + components: + - type: Transform + pos: 8.5,3.5 + parent: 588 + - uid: 645 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 2.5,20.5 + parent: 588 + - uid: 820 + components: + - type: Transform + pos: 4.5,32.5 + parent: 588 + - uid: 821 + components: + - type: Transform + pos: 4.5,31.5 + parent: 588 + - uid: 822 + components: + - type: Transform + pos: 5.5,31.5 + parent: 588 + - uid: 823 + components: + - type: Transform + pos: 5.5,32.5 + parent: 588 + - uid: 824 + components: + - type: Transform + pos: 5.5,30.5 + parent: 588 + - uid: 825 + components: + - type: Transform + pos: 6.5,30.5 + parent: 588 + - uid: 826 + components: + - type: Transform + pos: 7.5,30.5 + parent: 588 + - uid: 827 + components: + - type: Transform + pos: 6.5,32.5 + parent: 588 + - uid: 828 + components: + - type: Transform + pos: 7.5,32.5 + parent: 588 + - uid: 829 + components: + - type: Transform + pos: 7.5,31.5 + parent: 588 + - uid: 830 + components: + - type: Transform + pos: 6.5,31.5 + parent: 588 + - uid: 831 + components: + - type: Transform + pos: 8.5,30.5 + parent: 588 + - uid: 857 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 2.5,39.5 + parent: 588 + - uid: 858 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 3.5,39.5 + parent: 588 + - uid: 859 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 3.5,38.5 + parent: 588 + - uid: 860 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 3.5,40.5 + parent: 588 + - uid: 887 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 4.5,40.5 + parent: 588 + - uid: 889 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 4.5,39.5 + parent: 588 + - uid: 906 + components: + - type: Transform + pos: 4.5,7.5 + parent: 588 + - uid: 907 + components: + - type: Transform + pos: 5.5,7.5 + parent: 588 + - uid: 908 + components: + - type: Transform + pos: 5.5,9.5 + parent: 588 + - uid: 909 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 5.5,6.5 + parent: 588 + - uid: 910 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 4.5,6.5 + parent: 588 + - uid: 911 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 6.5,9.5 + parent: 588 + - uid: 913 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 6.5,10.5 + parent: 588 + - uid: 1247 + components: + - type: Transform + pos: 8.5,4.5 + parent: 588 + - uid: 1248 + components: + - type: Transform + pos: 9.5,4.5 + parent: 588 + - uid: 1249 + components: + - type: Transform + pos: 8.5,2.5 + parent: 588 + - uid: 1250 + components: + - type: Transform + pos: 8.5,1.5 + parent: 588 + - uid: 1251 + components: + - type: Transform + pos: 9.5,1.5 + parent: 588 + - uid: 1252 + components: + - type: Transform + pos: 8.5,1.5 + parent: 588 + - uid: 1253 + components: + - type: Transform + pos: 8.5,0.5 + parent: 588 + - uid: 1254 + components: + - type: Transform + pos: 7.5,0.5 + parent: 588 + - uid: 1333 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 2.5,46.5 + parent: 588 + - uid: 1341 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 3.5,45.5 + parent: 588 + - uid: 1343 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 3.5,46.5 + parent: 588 + - uid: 1345 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.5,46.5 + parent: 588 + - uid: 1359 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.5,47.5 + parent: 588 + - uid: 1360 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 4.5,46.5 + parent: 588 + - uid: 1361 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 4.5,47.5 + parent: 588 + - uid: 1362 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 5.5,47.5 + parent: 588 + - uid: 1363 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 2.5,44.5 + parent: 588 + - uid: 1364 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 2.5,47.5 + parent: 588 + - uid: 1365 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 3.5,44.5 + parent: 588 + - uid: 1366 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 4.5,45.5 + parent: 588 + - uid: 1367 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 4.5,44.5 + parent: 588 + - uid: 1368 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 4.5,43.5 + parent: 588 + - uid: 1369 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 5.5,43.5 + parent: 588 + - uid: 1370 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 5.5,42.5 + parent: 588 + - uid: 1371 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 6.5,42.5 + parent: 588 + - uid: 1372 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 6.5,43.5 + parent: 588 + - uid: 1373 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 5.5,44.5 + parent: 588 + - uid: 1374 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 5.5,46.5 + parent: 588 + - uid: 1375 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 2.5,43.5 + parent: 588 +- proto: FoodBowlBigTrash + entities: + - uid: 1840 + components: + - type: Transform + pos: 30.547388,48.16116 + parent: 588 +- proto: FoodBurgerMime + entities: + - uid: 399 + components: + - type: Transform + pos: 10.958169,39.64943 + parent: 588 +- proto: FoodPlateSmallPlastic + entities: + - uid: 529 + components: + - type: Transform + pos: 17.462528,12.615073 + parent: 588 +- proto: FoodPlateTrash + entities: + - uid: 1692 + components: + - type: Transform + pos: 28.80027,47.44947 + parent: 588 +- proto: ForensicPad + entities: + - uid: 761 + components: + - type: Transform + pos: 7.562898,22.48225 + parent: 588 +- proto: ForkPlastic + entities: + - uid: 531 + components: + - type: Transform + pos: 17.405733,12.600882 + parent: 588 +- proto: GasFilter + entities: + - uid: 1415 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 12.5,47.5 + parent: 588 +- proto: GasPipeBend + entities: + - uid: 1412 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 8.5,46.5 + parent: 588 + - uid: 1414 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 14.5,46.5 + parent: 588 + - uid: 1416 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 11.5,47.5 + parent: 588 + - uid: 1421 + components: + - type: Transform + pos: 13.5,47.5 + parent: 588 +- proto: GasPipeStraight + entities: + - uid: 1418 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 10.5,47.5 + parent: 588 + - uid: 1420 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 12.5,46.5 + parent: 588 +- proto: GasPipeTJunction + entities: + - uid: 1410 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 9.5,46.5 + parent: 588 + - uid: 1411 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 13.5,46.5 + parent: 588 + - uid: 1417 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 10.5,46.5 + parent: 588 + - uid: 1419 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 11.5,46.5 + parent: 588 +- proto: GasPort + entities: + - uid: 1404 + components: + - type: Transform + pos: 9.5,48.5 + parent: 588 + - uid: 1422 + components: + - type: Transform + pos: 12.5,48.5 + parent: 588 +- proto: GasPressurePump + entities: + - uid: 1409 + components: + - type: Transform + pos: 9.5,47.5 + parent: 588 +- proto: GasThermoMachineFreezer + entities: + - uid: 1403 + components: + - type: Transform + pos: 10.5,48.5 + parent: 588 +- proto: GatfruitSeeds + entities: + - uid: 562 + components: + - type: Transform + pos: 8.528373,27.49547 + parent: 588 +- proto: Gauze + entities: + - uid: 1482 + components: + - type: Transform + pos: 8.452288,42.514927 + parent: 588 +- proto: GeneratorRTG + entities: + - uid: 742 + components: + - type: Transform + pos: 27.5,21.5 + parent: 588 + - uid: 748 + components: + - type: Transform + pos: 25.5,21.5 + parent: 588 +- proto: Girder + entities: + - uid: 1301 + components: + - type: Transform + pos: 26.5,39.5 + parent: 588 +- proto: Grille + entities: + - uid: 209 + components: + - type: Transform + pos: 15.5,34.5 + parent: 588 + - uid: 211 + components: + - type: Transform + pos: 19.5,36.5 + parent: 588 + - uid: 212 + components: + - type: Transform + pos: 19.5,34.5 + parent: 588 + - uid: 213 + components: + - type: Transform + pos: 15.5,36.5 + parent: 588 + - uid: 403 + components: + - type: Transform + pos: 15.5,9.5 + parent: 588 + - uid: 404 + components: + - type: Transform + pos: 15.5,7.5 + parent: 588 + - uid: 407 + components: + - type: Transform + pos: 19.5,9.5 + parent: 588 + - uid: 408 + components: + - type: Transform + pos: 19.5,7.5 + parent: 588 + - uid: 568 + components: + - type: Transform + pos: 12.5,15.5 + parent: 588 + - uid: 584 + components: + - type: Transform + pos: 2.5,12.5 + parent: 588 + - uid: 586 + components: + - type: Transform + pos: 2.5,16.5 + parent: 588 + - uid: 587 + components: + - type: Transform + pos: 4.5,16.5 + parent: 588 + - uid: 589 + components: + - type: Transform + pos: 4.5,12.5 + parent: 588 + - uid: 1465 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 10.5,43.5 + parent: 588 + - uid: 1466 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 12.5,43.5 + parent: 588 +- proto: GrilleBroken + entities: + - uid: 1302 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 27.5,39.5 + parent: 588 +- proto: Handcuffs + entities: + - uid: 1614 + components: + - type: Transform + pos: 22.608034,10.659381 + parent: 588 +- proto: Hemostat + entities: + - uid: 1471 + components: + - type: Transform + pos: 8.51377,43.004257 + parent: 588 +- proto: HighSecArmoryLocked + entities: + - uid: 1597 + components: + - type: Transform + pos: 26.5,7.5 + parent: 588 + - uid: 1598 + components: + - type: Transform + pos: 32.5,7.5 + parent: 588 + - uid: 1599 + components: + - type: Transform + pos: 29.5,9.5 + parent: 588 +- proto: HospitalCurtains + entities: + - uid: 402 + components: + - type: Transform + pos: 8.5,27.5 + parent: 588 + - uid: 949 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 8.5,24.5 + parent: 588 + - uid: 951 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 10.5,27.5 + parent: 588 + - uid: 1768 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 24.5,48.5 + parent: 588 +- proto: HospitalCurtainsOpen + entities: + - uid: 946 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 8.5,25.5 + parent: 588 + - uid: 947 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 10.5,24.5 + parent: 588 + - uid: 1040 + components: + - type: Transform + pos: 20.5,28.5 + parent: 588 + - uid: 1046 + components: + - type: Transform + pos: 22.5,24.5 + parent: 588 + - uid: 1148 + components: + - type: Transform + pos: 28.5,25.5 + parent: 588 + - uid: 1149 + components: + - type: Transform + pos: 28.5,24.5 + parent: 588 + - uid: 1223 + components: + - type: Transform + pos: 20.5,36.5 + parent: 588 + - uid: 1224 + components: + - type: Transform + pos: 14.5,34.5 + parent: 588 + - uid: 1467 + components: + - type: Transform + pos: 12.5,42.5 + parent: 588 + - uid: 1469 + components: + - type: Transform + pos: 10.5,42.5 + parent: 588 + - uid: 1767 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 24.5,42.5 + parent: 588 +- proto: HydroponicsToolHatchet + entities: + - uid: 1844 + components: + - type: Transform + pos: 29.538284,44.04174 + parent: 588 + - uid: 1851 + components: + - type: Transform + pos: 30.630798,21.602604 + parent: 588 +- proto: HydroponicsToolMiniHoe + entities: + - uid: 1837 + components: + - type: Transform + pos: 30.099596,43.446724 + parent: 588 + - uid: 1841 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 33.38517,20.601 + parent: 588 +- proto: HydroponicsToolSpade + entities: + - uid: 1838 + components: + - type: Transform + pos: 29.95761,43.361576 + parent: 588 + - uid: 1842 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 33.42777,20.58681 + parent: 588 +- proto: hydroponicsTray + entities: + - uid: 796 + components: + - type: Transform + pos: 31.5,21.5 + parent: 588 + - uid: 797 + components: + - type: Transform + pos: 31.5,20.5 + parent: 588 + - uid: 798 + components: + - type: Transform + pos: 31.5,19.5 + parent: 588 + - uid: 1772 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 29.5,42.5 + parent: 588 + - uid: 1774 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 29.5,44.5 + parent: 588 + - uid: 1787 + components: + - type: Transform + pos: 30.5,44.5 + parent: 588 + - uid: 1788 + components: + - type: Transform + pos: 30.5,42.5 + parent: 588 +- proto: IngotGold + entities: + - uid: 952 + components: + - type: Transform + pos: 11.069347,39.504154 + parent: 588 +- proto: KitchenMicrowave + entities: + - uid: 524 + components: + - type: Transform + pos: 16.5,12.5 + parent: 588 + - uid: 709 + components: + - type: Transform + pos: 18.5,22.5 + parent: 588 + - uid: 1785 + components: + - type: Transform + pos: 29.5,48.5 + parent: 588 +- proto: KitchenReagentGrinder + entities: + - uid: 1786 + components: + - type: Transform + pos: 30.5,47.5 + parent: 588 +- proto: KnifePlastic + entities: + - uid: 530 + components: + - type: Transform + pos: 17.249546,12.643455 + parent: 588 + - uid: 1836 + components: + - type: Transform + pos: 30.241585,48.271698 + parent: 588 +- proto: Lamp + entities: + - uid: 581 + components: + - type: Transform + pos: 12.369425,13.798887 + parent: 588 +- proto: LampGold + entities: + - uid: 322 + components: + - type: Transform + pos: 18.419699,1.6320114 + parent: 588 + - uid: 323 + components: + - type: Transform + pos: 20.563715,4.8959665 + parent: 588 + - uid: 729 + components: + - type: Transform + pos: 6.4779434,22.892899 + parent: 588 + - uid: 730 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 10.765976,19.912766 + parent: 588 +- proto: Lighter + entities: + - uid: 1220 + components: + - type: Transform + pos: 7.5287867,36.397644 + parent: 588 +- proto: LockerDetective + entities: + - uid: 560 + components: + - type: Transform + pos: 14.5,16.5 + parent: 588 +- proto: LockerEvidence + entities: + - uid: 254 + components: + - type: Transform + pos: 16.5,0.5 + parent: 588 + - uid: 262 + components: + - type: Transform + pos: 2.5,0.5 + parent: 588 + - uid: 263 + components: + - type: Transform + pos: 4.5,0.5 + parent: 588 + - uid: 276 + components: + - type: Transform + pos: 0.5,0.5 + parent: 588 + - uid: 286 + components: + - type: Transform + pos: 12.5,0.5 + parent: 588 + - uid: 287 + components: + - type: Transform + pos: 14.5,0.5 + parent: 588 + - uid: 704 + components: + - type: Transform + pos: 16.5,22.5 + parent: 588 +- proto: LockerMedicineFilled + entities: + - uid: 1152 + components: + - type: Transform + pos: 28.5,27.5 + parent: 588 +- proto: LockerSecurityFilled + entities: + - uid: 416 + components: + - type: Transform + pos: 20.5,6.5 + parent: 588 +- proto: LockerSyndicatePersonal + entities: + - uid: 605 + components: + - type: Transform + pos: 6.5,12.5 + parent: 588 +- proto: MachineFrame + entities: + - uid: 400 + components: + - type: Transform + pos: 26.5,8.5 + parent: 588 +- proto: MaintenanceFluffSpawner + entities: + - uid: 414 + components: + - type: Transform + pos: 25.5,32.5 + parent: 588 + - uid: 1289 + components: + - type: Transform + pos: 17.5,38.5 + parent: 588 + - uid: 1290 + components: + - type: Transform + pos: 21.5,40.5 + parent: 588 + - uid: 1291 + components: + - type: Transform + pos: 20.5,40.5 + parent: 588 +- proto: MaintenanceWeaponSpawner + entities: + - uid: 548 + components: + - type: Transform + pos: 15.5,4.5 + parent: 588 + - uid: 549 + components: + - type: Transform + pos: 3.5,4.5 + parent: 588 + - uid: 1580 + components: + - type: Transform + pos: 1.5,8.5 + parent: 588 + - uid: 1581 + components: + - type: Transform + pos: 9.5,8.5 + parent: 588 +- proto: MaterialCloth1 + entities: + - uid: 702 + components: + - type: Transform + pos: 12.462601,18.586084 + parent: 588 + - uid: 1065 + components: + - type: Transform + pos: 24.460928,24.594687 + parent: 588 + - uid: 1066 + components: + - type: Transform + pos: 24.389935,24.296673 + parent: 588 +- proto: MaterialWoodPlank1 + entities: + - uid: 703 + components: + - type: Transform + pos: 12.817572,18.685423 + parent: 588 + - uid: 1064 + components: + - type: Transform + pos: 26.278374,24.608877 + parent: 588 + - uid: 1067 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 24.801699,24.708214 + parent: 588 + - uid: 1076 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 24.823982,27.574818 + parent: 588 +- proto: MedicalBed + entities: + - uid: 1146 + components: + - type: Transform + pos: 28.5,24.5 + parent: 588 + - uid: 1147 + components: + - type: Transform + pos: 28.5,25.5 + parent: 588 +- proto: MedkitAdvancedFilled + entities: + - uid: 1153 + components: + - type: Transform + pos: 30.614443,28.392822 + parent: 588 +- proto: MedkitCombatFilled + entities: + - uid: 1154 + components: + - type: Transform + pos: 30.40146,28.066427 + parent: 588 +- proto: OperatingTable + entities: + - uid: 1389 + components: + - type: Transform + pos: 9.5,43.5 + parent: 588 +- proto: Paper + entities: + - uid: 1055 + components: + - type: Transform + pos: 20.428474,24.722406 + parent: 588 + - uid: 1056 + components: + - type: Transform + pos: 20.669853,24.52373 + parent: 588 +- proto: PaperOffice + entities: + - uid: 327 + components: + - type: Transform + pos: 21.415642,4.0728827 + parent: 588 + - uid: 328 + components: + - type: Transform + pos: 21.586027,4.0019264 + parent: 588 + - uid: 1113 + components: + - type: Transform + pos: 20.706646,15.341779 + parent: 588 + - uid: 1114 + components: + - type: Transform + pos: 20.465267,15.185677 + parent: 588 + - uid: 1115 + components: + - type: Transform + pos: 20.30908,14.603841 + parent: 588 +- proto: PartRodMetal1 + entities: + - uid: 1071 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 25.341253,26.595633 + parent: 588 + - uid: 1072 + components: + - type: Transform + pos: 25.36965,28.11408 + parent: 588 +- proto: Pen + entities: + - uid: 366 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 21.352327,3.9473093 + parent: 588 + - uid: 367 + components: + - type: Transform + pos: 18.75395,1.1232786 + parent: 588 + - uid: 368 + components: + - type: Transform + pos: 24.788435,1.4496742 + parent: 588 + - uid: 731 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 10.36841,19.019064 + parent: 588 + - uid: 732 + components: + - type: Transform + pos: 7.4631767,22.637186 + parent: 588 +- proto: PhoneInstrument + entities: + - uid: 371 + components: + - type: Transform + pos: 25.484175,4.4865713 + parent: 588 +- proto: PillCanister + entities: + - uid: 1481 + components: + - type: Transform + pos: 14.438607,42.637726 + parent: 588 +- proto: PillSpaceDrugs + entities: + - uid: 1479 + components: + - type: Transform + pos: 14.438607,42.96412 + parent: 588 + - uid: 1480 + components: + - type: Transform + pos: 14.537998,42.878975 + parent: 588 +- proto: PlushieNuke + entities: + - uid: 1850 + components: + - type: Transform + pos: 22.519993,28.594225 + parent: 588 +- proto: PortableFlasher + entities: + - uid: 1234 + components: + - type: Transform + pos: 32.5,25.5 + parent: 588 +- proto: PortableGeneratorPacman + entities: + - uid: 967 + components: + - type: Transform + pos: 16.5,32.5 + parent: 588 + - uid: 969 + components: + - type: Transform + pos: 24.5,32.5 + parent: 588 +- proto: PortableGeneratorSuperPacman + entities: + - uid: 50 + components: + - type: Transform + pos: 26.5,15.5 + parent: 588 + - uid: 55 + components: + - type: Transform + pos: 28.5,15.5 + parent: 588 + - uid: 1504 + components: + - type: Transform + pos: 18.5,44.5 + parent: 588 + - uid: 1505 + components: + - type: Transform + pos: 20.5,44.5 + parent: 588 +- proto: PortableScrubber + entities: + - uid: 1101 + components: + - type: Transform + pos: 18.5,30.5 + parent: 588 +- proto: PosterContrabandBountyHunters + entities: + - uid: 1578 + components: + - type: Transform + pos: 13.5,15.5 + parent: 588 +- proto: PosterLegitDickGumshue + entities: + - uid: 1576 + components: + - type: Transform + pos: 9.5,15.5 + parent: 588 +- proto: PosterLegitEnlist + entities: + - uid: 1800 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 8.5,9.5 + parent: 588 +- proto: PosterLegitNanotrasenLogo + entities: + - uid: 1802 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 2.5,48.5 + parent: 588 + - uid: 1803 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 3.5,21.5 + parent: 588 +- proto: PosterLegitObey + entities: + - uid: 1801 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 4.5,42.5 + parent: 588 +- proto: PosterLegitSecWatch + entities: + - uid: 1799 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 2.5,7.5 + parent: 588 +- proto: PosterLegitSpaceCops + entities: + - uid: 1804 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.5,19.5 + parent: 588 +- proto: PottedPlantRandom + entities: + - uid: 1286 + components: + - type: Transform + pos: 16.5,38.5 + parent: 588 + - uid: 1287 + components: + - type: Transform + pos: 22.5,40.5 + parent: 588 +- proto: PowerCellHyper + entities: + - uid: 469 + components: + - type: Transform + pos: 12.355853,25.41643 + parent: 588 + - uid: 590 + components: + - type: Transform + pos: 5.381793,16.642464 + parent: 588 +- proto: PowerCellRecharger + entities: + - uid: 1103 + components: + - type: Transform + pos: 15.5,30.5 + parent: 588 + - uid: 1568 + components: + - type: Transform + pos: 21.5,47.5 + parent: 588 +- proto: Poweredlight + entities: + - uid: 1641 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 2.5,0.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1642 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 14.5,0.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1646 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 24.5,0.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1647 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 28.5,0.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1648 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 21.5,6.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1649 + components: + - type: Transform + pos: 13.5,10.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1650 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 16.5,6.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1651 + components: + - type: Transform + pos: 18.5,10.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1693 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 32.5,25.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1701 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 18.5,12.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1702 + components: + - type: Transform + pos: 20.5,16.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1703 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 34.5,20.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1704 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 16.5,25.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1705 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 14.5,27.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1706 + components: + - type: Transform + pos: 18.5,40.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1741 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 10.5,21.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1743 + components: + - type: Transform + pos: 21.5,22.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1830 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 30.5,43.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1831 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 30.5,47.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 +- proto: PoweredlightLED + entities: + - uid: 1707 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 8.5,42.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1708 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 14.5,42.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1709 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 14.5,46.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1710 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 8.5,46.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1725 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 28.5,27.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 +- proto: PoweredSmallLight + entities: + - uid: 470 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 26.5,14.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 940 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 10.5,28.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 948 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 8.5,28.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 953 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 10.5,25.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1603 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 26.5,10.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1604 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 32.5,10.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1605 + components: + - type: Transform + pos: 29.5,6.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1606 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 29.5,8.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1607 + components: + - type: Transform + pos: 32.5,8.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1608 + components: + - type: Transform + pos: 26.5,8.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1643 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 7.5,1.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1644 + components: + - type: Transform + pos: 20.5,4.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1645 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 19.5,0.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1652 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 9.5,8.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1653 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 7.5,8.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1654 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 3.5,8.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1655 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.5,8.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1656 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 5.5,14.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1657 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.5,14.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1659 + components: + - type: Transform + pos: 3.5,18.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1661 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.5,22.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1662 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 4.5,27.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1663 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 6.5,25.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1664 + components: + - type: Transform + pos: 0.5,28.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1665 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 2.5,24.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1666 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 3.5,30.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1667 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 9.5,30.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1668 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.5,36.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1669 + components: + - type: Transform + pos: 8.5,34.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1670 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 5.5,38.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1671 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.5,38.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1672 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 2.5,43.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1673 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 4.5,43.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1674 + components: + - type: Transform + pos: 2.5,47.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1675 + components: + - type: Transform + pos: 4.5,47.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1676 + components: + - type: Transform + pos: 9.5,40.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1677 + components: + - type: Transform + pos: 13.5,40.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1678 + components: + - type: Transform + pos: 13.5,36.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1679 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 21.5,34.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1680 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 32.5,35.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1681 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 34.5,35.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1682 + components: + - type: Transform + pos: 25.5,36.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1683 + components: + - type: Transform + pos: 28.5,38.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1684 + components: + - type: Transform + pos: 19.5,46.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1685 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 17.5,47.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1686 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 21.5,47.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1687 + components: + - type: Transform + pos: 23.5,32.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1688 + components: + - type: Transform + pos: 17.5,32.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1689 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 22.5,27.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1690 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 20.5,25.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1694 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 28.5,19.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1695 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 24.5,19.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1696 + components: + - type: Transform + pos: 13.5,22.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1697 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 13.5,18.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1698 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 15.5,18.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1699 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 9.5,16.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1700 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 10.5,13.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1828 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 25.5,42.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1829 + components: + - type: Transform + pos: 25.5,48.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 +- proto: PoweredSmallLightEmpty + entities: + - uid: 1640 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 26.5,25.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 1658 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 24.5,27.5 + parent: 588 + - type: ApcPowerReceiver + powerLoad: 0 +- proto: Rack + entities: + - uid: 255 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 15.5,0.5 + parent: 588 + - uid: 264 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 3.5,0.5 + parent: 588 + - uid: 283 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 1.5,0.5 + parent: 588 + - uid: 285 + components: + - type: Transform + pos: 13.5,0.5 + parent: 588 + - uid: 324 + components: + - type: Transform + pos: 19.5,4.5 + parent: 588 + - uid: 396 + components: + - type: Transform + pos: 28.5,8.5 + parent: 588 + - uid: 401 + components: + - type: Transform + pos: 32.5,8.5 + parent: 588 + - uid: 417 + components: + - type: Transform + pos: 12.5,6.5 + parent: 588 + - uid: 418 + components: + - type: Transform + pos: 12.5,10.5 + parent: 588 + - uid: 419 + components: + - type: Transform + pos: 22.5,10.5 + parent: 588 + - uid: 420 + components: + - type: Transform + pos: 22.5,6.5 + parent: 588 + - uid: 478 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 27.5,15.5 + parent: 588 + - uid: 551 + components: + - type: Transform + pos: 22.5,15.5 + parent: 588 + - uid: 585 + components: + - type: Transform + pos: 1.5,12.5 + parent: 588 + - uid: 596 + components: + - type: Transform + pos: 1.5,16.5 + parent: 588 + - uid: 597 + components: + - type: Transform + pos: 5.5,16.5 + parent: 588 + - uid: 598 + components: + - type: Transform + pos: 5.5,12.5 + parent: 588 + - uid: 966 + components: + - type: Transform + pos: 25.5,32.5 + parent: 588 + - uid: 977 + components: + - type: Transform + pos: 15.5,32.5 + parent: 588 + - uid: 1015 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 12.5,25.5 + parent: 588 + - uid: 1016 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 12.5,24.5 + parent: 588 + - uid: 1021 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 18.5,25.5 + parent: 588 + - uid: 1024 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 18.5,24.5 + parent: 588 + - uid: 1111 + components: + - type: Transform + pos: 14.5,32.5 + parent: 588 + - uid: 1112 + components: + - type: Transform + pos: 26.5,32.5 + parent: 588 + - uid: 1206 + components: + - type: Transform + pos: 1.5,8.5 + parent: 588 + - uid: 1208 + components: + - type: Transform + pos: 9.5,8.5 + parent: 588 + - uid: 1211 + components: + - type: Transform + pos: 2.5,34.5 + parent: 588 + - uid: 1319 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 29.5,40.5 + parent: 588 + - uid: 1562 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 17.5,47.5 + parent: 588 + - uid: 1858 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 26.5,21.5 + parent: 588 +- proto: Railing + entities: + - uid: 313 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 30.5,4.5 + parent: 588 + - uid: 314 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 30.5,0.5 + parent: 588 + - uid: 427 + components: + - type: Transform + pos: 6.5,7.5 + parent: 588 + - uid: 428 + components: + - type: Transform + pos: 4.5,7.5 + parent: 588 + - uid: 429 + components: + - type: Transform + pos: 3.5,7.5 + parent: 588 + - uid: 430 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 7.5,9.5 + parent: 588 + - uid: 431 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 6.5,9.5 + parent: 588 + - uid: 435 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 5.5,9.5 + parent: 588 + - uid: 436 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 4.5,9.5 + parent: 588 + - uid: 437 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 3.5,9.5 + parent: 588 + - uid: 439 + components: + - type: Transform + pos: 5.5,7.5 + parent: 588 + - uid: 440 + components: + - type: Transform + pos: 7.5,7.5 + parent: 588 + - uid: 770 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 26.5,21.5 + parent: 588 + - uid: 850 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 9.5,30.5 + parent: 588 + - uid: 851 + components: + - type: Transform + pos: 9.5,32.5 + parent: 588 + - uid: 854 + components: + - type: Transform + pos: 3.5,32.5 + parent: 588 + - uid: 855 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 3.5,30.5 + parent: 588 + - uid: 1259 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 7.5,4.5 + parent: 588 + - uid: 1260 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 9.5,4.5 + parent: 588 + - uid: 1261 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 9.5,0.5 + parent: 588 + - uid: 1262 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 7.5,0.5 + parent: 588 +- proto: RailingCorner + entities: + - uid: 315 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 30.5,1.5 + parent: 588 + - uid: 316 + components: + - type: Transform + pos: 30.5,3.5 + parent: 588 + - uid: 771 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 27.5,21.5 + parent: 588 + - uid: 772 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 25.5,21.5 + parent: 588 + - uid: 845 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 2.5,32.5 + parent: 588 + - uid: 846 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 2.5,30.5 + parent: 588 + - uid: 847 + components: + - type: Transform + pos: 10.5,32.5 + parent: 588 + - uid: 848 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 10.5,30.5 + parent: 588 + - uid: 849 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 8.5,30.5 + parent: 588 + - uid: 852 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 8.5,32.5 + parent: 588 + - uid: 853 + components: + - type: Transform + pos: 4.5,32.5 + parent: 588 + - uid: 856 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 4.5,30.5 + parent: 588 + - uid: 886 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 4.5,40.5 + parent: 588 + - uid: 888 + components: + - type: Transform + pos: 2.5,40.5 + parent: 588 + - uid: 890 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.5,40.5 + parent: 588 + - uid: 891 + components: + - type: Transform + pos: 5.5,40.5 + parent: 588 + - uid: 892 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 4.5,38.5 + parent: 588 + - uid: 893 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 5.5,38.5 + parent: 588 + - uid: 894 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 2.5,38.5 + parent: 588 + - uid: 895 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.5,38.5 + parent: 588 + - uid: 1255 + components: + - type: Transform + pos: 7.5,3.5 + parent: 588 + - uid: 1256 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 9.5,3.5 + parent: 588 + - uid: 1257 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 9.5,1.5 + parent: 588 + - uid: 1258 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 7.5,1.5 + parent: 588 + - uid: 1351 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 1.5,44.5 + parent: 588 + - uid: 1352 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 2.5,43.5 + parent: 588 + - uid: 1353 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 4.5,43.5 + parent: 588 + - uid: 1354 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 5.5,44.5 + parent: 588 + - uid: 1355 + components: + - type: Transform + pos: 1.5,46.5 + parent: 588 + - uid: 1356 + components: + - type: Transform + pos: 2.5,47.5 + parent: 588 + - uid: 1357 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 4.5,47.5 + parent: 588 + - uid: 1358 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 5.5,46.5 + parent: 588 +- proto: RailingCornerSmall + entities: + - uid: 337 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 29.5,3.5 + parent: 588 + - uid: 338 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 29.5,1.5 + parent: 588 + - uid: 1376 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.5,43.5 + parent: 588 + - uid: 1377 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.5,47.5 + parent: 588 + - uid: 1378 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 5.5,47.5 + parent: 588 + - uid: 1379 + components: + - type: Transform + pos: 5.5,43.5 + parent: 588 +- proto: RandomDrinkBottle + entities: + - uid: 580 + components: + - type: Transform + pos: 12.5,12.5 + parent: 588 +- proto: RandomFoodSingle + entities: + - uid: 764 + components: + - type: Transform + pos: 6.5,19.5 + parent: 588 +- proto: RandomInstruments + entities: + - uid: 546 + components: + - type: Transform + pos: 1.5,4.5 + parent: 588 + - uid: 1159 + components: + - type: Transform + pos: 21.5,18.5 + parent: 588 + - uid: 1833 + components: + - type: Transform + pos: 24.5,42.5 + parent: 588 +- proto: RandomPosterContraband + entities: + - uid: 1571 + components: + - type: Transform + pos: 25.5,39.5 + parent: 588 + - uid: 1572 + components: + - type: Transform + pos: 3.5,35.5 + parent: 588 + - uid: 1573 + components: + - type: Transform + pos: 7.5,35.5 + parent: 588 + - uid: 1574 + components: + - type: Transform + pos: 5.5,25.5 + parent: 588 + - uid: 1575 + components: + - type: Transform + pos: 30.5,15.5 + parent: 588 + - uid: 1805 + components: + - type: Transform + pos: 18.5,43.5 + parent: 588 + - uid: 1806 + components: + - type: Transform + pos: 20.5,47.5 + parent: 588 +- proto: RandomSoap + entities: + - uid: 397 + components: + - type: Transform + pos: 8.5,24.5 + parent: 588 +- proto: RandomVending + entities: + - uid: 539 + components: + - type: Transform + pos: 17.5,16.5 + parent: 588 +- proto: ReinforcedWindow + entities: + - uid: 214 + components: + - type: Transform + pos: 19.5,34.5 + parent: 588 + - uid: 409 + components: + - type: Transform + pos: 15.5,7.5 + parent: 588 + - uid: 410 + components: + - type: Transform + pos: 15.5,9.5 + parent: 588 + - uid: 411 + components: + - type: Transform + pos: 19.5,7.5 + parent: 588 + - uid: 412 + components: + - type: Transform + pos: 19.5,9.5 + parent: 588 + - uid: 591 + components: + - type: Transform + pos: 2.5,12.5 + parent: 588 + - uid: 592 + components: + - type: Transform + pos: 4.5,12.5 + parent: 588 + - uid: 594 + components: + - type: Transform + pos: 4.5,16.5 + parent: 588 + - uid: 595 + components: + - type: Transform + pos: 2.5,16.5 + parent: 588 + - uid: 1166 + components: + - type: Transform + pos: 19.5,36.5 + parent: 588 + - uid: 1168 + components: + - type: Transform + pos: 15.5,36.5 + parent: 588 + - uid: 1169 + components: + - type: Transform + pos: 15.5,34.5 + parent: 588 +- proto: RemoteSignaller + entities: + - uid: 593 + components: + - type: Transform + pos: 34.361877,24.623777 + parent: 588 + - type: DeviceLinkSource + linkedPorts: + 1238: + - Pressed: Toggle + 1239: + - Pressed: Toggle + 1237: + - Pressed: Toggle + - uid: 1104 + components: + - type: Transform + pos: 25.24476,30.698978 + parent: 588 + - uid: 1105 + components: + - type: Transform + pos: 25.443544,30.613832 + parent: 588 + - uid: 1106 + components: + - type: Transform + pos: 5.677143,16.762346 + parent: 588 +- proto: RiotLaserShield + entities: + - uid: 1618 + components: + - type: Transform + pos: 12.616156,10.534842 + parent: 588 +- proto: RiotShield + entities: + - uid: 600 + components: + - type: Transform + pos: 1.3209407,16.656654 + parent: 588 + - uid: 601 + components: + - type: Transform + pos: 1.5481212,16.543125 + parent: 588 +- proto: SalvageCanisterSpawner + entities: + - uid: 998 + components: + - type: Transform + pos: 22.5,30.5 + parent: 588 + - uid: 1009 + components: + - type: Transform + pos: 19.5,30.5 + parent: 588 + - uid: 1025 + components: + - type: Transform + pos: 21.5,30.5 + parent: 588 + - uid: 1190 + components: + - type: Transform + pos: 9.5,36.5 + parent: 588 + - uid: 1210 + components: + - type: Transform + pos: 9.5,48.5 + parent: 588 + - uid: 1413 + components: + - type: Transform + pos: 13.5,48.5 + parent: 588 + - uid: 1470 + components: + - type: Transform + pos: 12.5,48.5 + parent: 588 +- proto: SawAdvanced + entities: + - uid: 1468 + components: + - type: Transform + pos: 8.527969,43.529327 + parent: 588 +- proto: ScalpelLaser + entities: + - uid: 1472 + components: + - type: Transform + pos: 8.485372,43.131977 + parent: 588 +- proto: ScalpelShiv + entities: + - uid: 708 + components: + - type: Transform + pos: 16.074419,18.727995 + parent: 588 + - uid: 1592 + components: + - type: Transform + pos: 10.50393,24.491432 + parent: 588 +- proto: SeedExtractor + entities: + - uid: 802 + components: + - type: Transform + pos: 33.5,21.5 + parent: 588 + - uid: 1776 + components: + - type: Transform + pos: 28.5,42.5 + parent: 588 +- proto: SheetGlass + entities: + - uid: 649 + components: + - type: Transform + pos: 14.3137455,32.471424 + parent: 588 +- proto: SheetPlasteel + entities: + - uid: 866 + components: + - type: Transform + pos: 2.412651,34.456436 + parent: 588 +- proto: SheetPlastic + entities: + - uid: 398 + components: + - type: Transform + pos: 20.04785,45.07574 + parent: 588 +- proto: SheetSteel + entities: + - uid: 656 + components: + - type: Transform + pos: 26.36062,32.5183 + parent: 588 + - uid: 699 + components: + - type: Transform + pos: 29.259031,40.432583 + parent: 588 +- proto: ShowcaseRobot + entities: + - uid: 1621 + components: + - type: Transform + pos: 16.5,10.5 + parent: 588 + - uid: 1622 + components: + - type: Transform + pos: 18.5,6.5 + parent: 588 +- proto: ShuttersNormal + entities: + - uid: 1237 + components: + - type: Transform + pos: 34.5,27.5 + parent: 588 + - type: DeviceLinkSink + links: + - 593 + - uid: 1238 + components: + - type: Transform + pos: 32.5,27.5 + parent: 588 + - type: DeviceLinkSink + links: + - 593 +- proto: ShuttersWindow + entities: + - uid: 1239 + components: + - type: Transform + pos: 33.5,27.5 + parent: 588 + - type: DeviceLinkSink + links: + - 593 +- proto: SignCloning + entities: + - uid: 1484 + components: + - type: Transform + pos: 12.5,43.5 + parent: 588 +- proto: SignPrison + entities: + - uid: 1792 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 6.5,3.5 + parent: 588 + - uid: 1793 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 10.5,1.5 + parent: 588 + - uid: 1794 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.5,21.5 + parent: 588 + - uid: 1795 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 3.5,19.5 + parent: 588 + - uid: 1796 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 0.5,46.5 + parent: 588 + - uid: 1797 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 6.5,44.5 + parent: 588 +- proto: SignSecurity + entities: + - uid: 1798 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 22.5,1.5 + parent: 588 +- proto: SignSurgery + entities: + - uid: 1483 + components: + - type: Transform + pos: 10.5,43.5 + parent: 588 +- proto: Sink + entities: + - uid: 935 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 10.5,25.5 + parent: 588 +- proto: SinkStemlessWater + entities: + - uid: 1461 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 9.5,42.5 + parent: 588 + - uid: 1462 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 13.5,42.5 + parent: 588 +- proto: SMESBasic + entities: + - uid: 46 + components: + - type: Transform + pos: 26.5,13.5 + parent: 588 + - uid: 56 + components: + - type: Transform + pos: 28.5,13.5 + parent: 588 + - uid: 747 + components: + - type: Transform + pos: 26.5,19.5 + parent: 588 + - uid: 1506 + components: + - type: Transform + pos: 18.5,46.5 + parent: 588 + - uid: 1507 + components: + - type: Transform + pos: 20.5,46.5 + parent: 588 +- proto: SoapSyndie + entities: + - uid: 1856 + components: + - type: Transform + pos: 10.4890785,27.46785 + parent: 588 +- proto: SpaceCash100 + entities: + - uid: 1243 + components: + - type: Transform + pos: 11.887424,39.621456 + parent: 588 + - uid: 1244 + components: + - type: Transform + pos: 11.759636,39.479546 + parent: 588 + - uid: 1296 + components: + - type: Transform + pos: 12.100407,39.465355 + parent: 588 + - uid: 1297 + components: + - type: Transform + pos: 12.100407,39.80594 + parent: 588 + - uid: 1298 + components: + - type: Transform + pos: 11.688642,39.720795 + parent: 588 + - uid: 1299 + components: + - type: Transform + pos: 11.4330635,39.57888 + parent: 588 +- proto: Spear + entities: + - uid: 1834 + components: + - type: Transform + pos: 24.466219,48.441994 + parent: 588 +- proto: SpeedLoaderMagnum + entities: + - uid: 950 + components: + - type: Transform + pos: 28.703945,8.421182 + parent: 588 +- proto: StasisBed + entities: + - uid: 1425 + components: + - type: Transform + pos: 13.5,43.5 + parent: 588 +- proto: StimkitFilled + entities: + - uid: 561 + components: + - type: Transform + pos: 30.39083,27.514402 + parent: 588 +- proto: StimpackMini + entities: + - uid: 1879 + components: + - type: Transform + pos: 24.467485,46.702366 + parent: 588 +- proto: Stool + entities: + - uid: 1017 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 14.5,25.5 + parent: 588 + - uid: 1018 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 14.5,24.5 + parent: 588 + - uid: 1019 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 16.5,28.5 + parent: 588 + - uid: 1020 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 18.5,28.5 + parent: 588 + - uid: 1593 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 0.5,13.5 + parent: 588 + - uid: 1594 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 0.5,15.5 + parent: 588 + - uid: 1595 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 6.5,15.5 + parent: 588 + - uid: 1596 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 6.5,13.5 + parent: 588 +- proto: SubstationBasic + entities: + - uid: 467 + components: + - type: Transform + pos: 27.5,13.5 + parent: 588 + - uid: 1508 + components: + - type: Transform + pos: 19.5,46.5 + parent: 588 +- proto: SubstationWallBasic + entities: + - uid: 774 + components: + - type: Transform + pos: 27.5,19.5 + parent: 588 + - uid: 972 + components: + - type: Transform + pos: 19.5,32.5 + parent: 588 +- proto: SyringeEphedrine + entities: + - uid: 1475 + components: + - type: Transform + pos: 14.472328,42.917698 + parent: 588 +- proto: Table + entities: + - uid: 525 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 16.5,13.5 + parent: 588 + - uid: 527 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 16.5,12.5 + parent: 588 + - uid: 528 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 17.5,12.5 + parent: 588 + - uid: 535 + components: + - type: Transform + pos: 20.5,14.5 + parent: 588 + - uid: 536 + components: + - type: Transform + pos: 20.5,15.5 + parent: 588 + - uid: 630 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 6.5,19.5 + parent: 588 + - uid: 632 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 10.5,19.5 + parent: 588 + - uid: 633 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 10.5,18.5 + parent: 588 + - uid: 636 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 6.5,22.5 + parent: 588 + - uid: 637 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 7.5,22.5 + parent: 588 + - uid: 710 + components: + - type: Transform + pos: 18.5,22.5 + parent: 588 + - uid: 711 + components: + - type: Transform + pos: 18.5,21.5 + parent: 588 + - uid: 712 + components: + - type: Transform + pos: 21.5,18.5 + parent: 588 + - uid: 713 + components: + - type: Transform + pos: 22.5,18.5 + parent: 588 + - uid: 714 + components: + - type: Transform + pos: 18.5,18.5 + parent: 588 + - uid: 715 + components: + - type: Transform + pos: 19.5,18.5 + parent: 588 + - uid: 799 + components: + - type: Transform + pos: 33.5,20.5 + parent: 588 + - uid: 1118 + components: + - type: Transform + pos: 22.5,12.5 + parent: 588 + - uid: 1778 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 30.5,48.5 + parent: 588 + - uid: 1779 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 29.5,48.5 + parent: 588 + - uid: 1780 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 30.5,47.5 + parent: 588 +- proto: TableFrame + entities: + - uid: 705 + components: + - type: Transform + pos: 15.5,22.5 + parent: 588 + - uid: 1063 + components: + - type: Transform + pos: 26.5,24.5 + parent: 588 +- proto: TableGlass + entities: + - uid: 1144 + components: + - type: Transform + pos: 30.5,27.5 + parent: 588 + - uid: 1145 + components: + - type: Transform + pos: 30.5,28.5 + parent: 588 + - uid: 1390 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 8.5,43.5 + parent: 588 + - uid: 1391 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 8.5,42.5 + parent: 588 + - uid: 1398 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 10.5,44.5 + parent: 588 + - uid: 1405 + components: + - type: Transform + pos: 14.5,42.5 + parent: 588 + - uid: 1406 + components: + - type: Transform + pos: 14.5,43.5 + parent: 588 + - uid: 1407 + components: + - type: Transform + pos: 12.5,44.5 + parent: 588 +- proto: TableReinforced + entities: + - uid: 924 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 26.5,36.5 + parent: 588 + - uid: 925 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 26.5,35.5 + parent: 588 + - uid: 926 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 26.5,34.5 + parent: 588 + - uid: 1005 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 15.5,30.5 + parent: 588 + - uid: 1006 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 25.5,30.5 + parent: 588 + - uid: 1012 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 14.5,28.5 + parent: 588 + - uid: 1023 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 16.5,24.5 + parent: 588 + - uid: 1235 + components: + - type: Transform + pos: 34.5,24.5 + parent: 588 + - uid: 1567 + components: + - type: Transform + pos: 21.5,47.5 + parent: 588 +- proto: TableReinforcedGlass + entities: + - uid: 1213 + components: + - type: Transform + pos: 10.5,39.5 + parent: 588 + - uid: 1214 + components: + - type: Transform + pos: 11.5,39.5 + parent: 588 + - uid: 1215 + components: + - type: Transform + pos: 12.5,39.5 + parent: 588 +- proto: TableWood + entities: + - uid: 309 + components: + - type: Transform + pos: 20.5,4.5 + parent: 588 + - uid: 310 + components: + - type: Transform + pos: 21.5,4.5 + parent: 588 + - uid: 311 + components: + - type: Transform + pos: 18.5,0.5 + parent: 588 + - uid: 312 + components: + - type: Transform + pos: 21.5,3.5 + parent: 588 + - uid: 317 + components: + - type: Transform + pos: 18.5,1.5 + parent: 588 + - uid: 318 + components: + - type: Transform + pos: 19.5,0.5 + parent: 588 + - uid: 332 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 25.5,3.5 + parent: 588 + - uid: 333 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 26.5,3.5 + parent: 588 + - uid: 334 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 27.5,3.5 + parent: 588 + - uid: 335 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 25.5,4.5 + parent: 588 + - uid: 336 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 27.5,4.5 + parent: 588 + - uid: 340 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 24.5,1.5 + parent: 588 + - uid: 341 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 25.5,1.5 + parent: 588 + - uid: 342 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 27.5,1.5 + parent: 588 + - uid: 343 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 28.5,1.5 + parent: 588 + - uid: 563 + components: + - type: Transform + pos: 13.5,13.5 + parent: 588 + - uid: 564 + components: + - type: Transform + pos: 12.5,13.5 + parent: 588 + - uid: 565 + components: + - type: Transform + pos: 12.5,12.5 + parent: 588 + - uid: 1047 + components: + - type: Transform + pos: 22.5,28.5 + parent: 588 + - uid: 1048 + components: + - type: Transform + pos: 20.5,24.5 + parent: 588 + - uid: 1062 + components: + - type: Transform + pos: 26.5,28.5 + parent: 588 + - uid: 1227 + components: + - type: Transform + pos: 12.5,36.5 + parent: 588 + - uid: 1228 + components: + - type: Transform + pos: 22.5,34.5 + parent: 588 + - uid: 1783 + components: + - type: Transform + pos: 24.5,46.5 + parent: 588 + - uid: 1784 + components: + - type: Transform + pos: 24.5,44.5 + parent: 588 +- proto: TargetHuman + entities: + - uid: 1077 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 32.5,35.5 + parent: 588 +- proto: TintedWindow + entities: + - uid: 567 + components: + - type: Transform + pos: 12.5,15.5 + parent: 588 + - uid: 1463 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 10.5,43.5 + parent: 588 + - uid: 1464 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 12.5,43.5 + parent: 588 +- proto: ToiletEmpty + entities: + - uid: 932 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 8.5,24.5 + parent: 588 + - uid: 933 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 10.5,24.5 + parent: 588 + - uid: 934 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 8.5,25.5 + parent: 588 +- proto: UniformScrubsColorPurple + entities: + - uid: 1712 + components: + - type: Transform + pos: 10.503376,44.53288 + parent: 588 +- proto: VendingMachineChefvend + entities: + - uid: 532 + components: + - type: Transform + pos: 18.5,12.5 + parent: 588 +- proto: VendingMachineCigs + entities: + - uid: 720 + components: + - type: Transform + pos: 22.5,22.5 + parent: 588 +- proto: VendingMachineCoffee + entities: + - uid: 765 + components: + - type: Transform + pos: 10.5,22.5 + parent: 588 + - uid: 1285 + components: + - type: Transform + pos: 18.5,40.5 + parent: 588 +- proto: VendingMachineDetDrobe + entities: + - uid: 582 + components: + - type: Transform + pos: 10.5,14.5 + parent: 588 +- proto: VendingMachineDinnerware + entities: + - uid: 1781 + components: + - type: Transform + pos: 30.5,46.5 + parent: 588 +- proto: VendingMachineDonut + entities: + - uid: 538 + components: + - type: Transform + pos: 16.5,16.5 + parent: 588 +- proto: VendingMachineLawDrobe + entities: + - uid: 319 + components: + - type: Transform + pos: 18.5,4.5 + parent: 588 +- proto: VendingMachineMedical + entities: + - uid: 1143 + components: + - type: Transform + pos: 28.5,28.5 + parent: 588 +- proto: VendingMachineSec + entities: + - uid: 1013 + components: + - type: Transform + pos: 14.5,27.5 + parent: 588 +- proto: VendingMachineSeedsUnlocked + entities: + - uid: 800 + components: + - type: Transform + pos: 33.5,19.5 + parent: 588 + - uid: 1775 + components: + - type: Transform + pos: 28.5,44.5 + parent: 588 +- proto: WallmountTelescreen + entities: + - uid: 572 + components: + - type: Transform + pos: 13.5,13.5 + parent: 588 +- proto: WallPlastitaniumIndestructible + entities: + - uid: 377 + components: + - type: Transform + pos: 30.5,7.5 + parent: 588 + - uid: 378 + components: + - type: Transform + pos: 29.5,7.5 + parent: 588 + - uid: 379 + components: + - type: Transform + pos: 25.5,9.5 + parent: 588 + - uid: 380 + components: + - type: Transform + pos: 27.5,9.5 + parent: 588 + - uid: 381 + components: + - type: Transform + pos: 27.5,8.5 + parent: 588 + - uid: 382 + components: + - type: Transform + pos: 28.5,9.5 + parent: 588 + - uid: 383 + components: + - type: Transform + pos: 31.5,9.5 + parent: 588 + - uid: 384 + components: + - type: Transform + pos: 31.5,8.5 + parent: 588 + - uid: 385 + components: + - type: Transform + pos: 31.5,7.5 + parent: 588 + - uid: 386 + components: + - type: Transform + pos: 25.5,8.5 + parent: 588 + - uid: 387 + components: + - type: Transform + pos: 33.5,7.5 + parent: 588 + - uid: 388 + components: + - type: Transform + pos: 25.5,7.5 + parent: 588 + - uid: 389 + components: + - type: Transform + pos: 27.5,7.5 + parent: 588 + - uid: 390 + components: + - type: Transform + pos: 30.5,9.5 + parent: 588 + - uid: 391 + components: + - type: Transform + pos: 28.5,7.5 + parent: 588 + - uid: 392 + components: + - type: Transform + pos: 26.5,9.5 + parent: 588 + - uid: 393 + components: + - type: Transform + pos: 33.5,8.5 + parent: 588 + - uid: 394 + components: + - type: Transform + pos: 33.5,9.5 + parent: 588 + - uid: 395 + components: + - type: Transform + pos: 32.5,9.5 + parent: 588 +- proto: WallReinforced + entities: + - uid: 45 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 2.5,8.5 + parent: 588 + - uid: 51 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 8.5,8.5 + parent: 588 + - uid: 244 + components: + - type: Transform + pos: 6.5,4.5 + parent: 588 + - uid: 245 + components: + - type: Transform + pos: 6.5,3.5 + parent: 588 + - uid: 246 + components: + - type: Transform + pos: 10.5,4.5 + parent: 588 + - uid: 247 + components: + - type: Transform + pos: 10.5,3.5 + parent: 588 + - uid: 266 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 6.5,1.5 + parent: 588 + - uid: 267 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 6.5,0.5 + parent: 588 + - uid: 268 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 10.5,1.5 + parent: 588 + - uid: 269 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 10.5,0.5 + parent: 588 + - uid: 291 + components: + - type: Transform + pos: 15.5,6.5 + parent: 588 + - uid: 292 + components: + - type: Transform + pos: 19.5,6.5 + parent: 588 + - uid: 405 + components: + - type: Transform + pos: 15.5,10.5 + parent: 588 + - uid: 406 + components: + - type: Transform + pos: 19.5,10.5 + parent: 588 + - uid: 426 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 8.5,9.5 + parent: 588 + - uid: 432 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 2.5,7.5 + parent: 588 + - uid: 433 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 2.5,9.5 + parent: 588 + - uid: 434 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 8.5,7.5 + parent: 588 + - uid: 570 + components: + - type: Transform + pos: 0.5,46.5 + parent: 588 + - uid: 621 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.5,21.5 + parent: 588 + - uid: 622 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 3.5,19.5 + parent: 588 + - uid: 623 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 3.5,21.5 + parent: 588 + - uid: 627 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.5,19.5 + parent: 588 + - uid: 1078 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 33.5,35.5 + parent: 588 + - uid: 1330 + components: + - type: Transform + pos: 0.5,44.5 + parent: 588 + - uid: 1332 + components: + - type: Transform + pos: 2.5,42.5 + parent: 588 + - uid: 1334 + components: + - type: Transform + pos: 4.5,42.5 + parent: 588 + - uid: 1335 + components: + - type: Transform + pos: 6.5,44.5 + parent: 588 + - uid: 1337 + components: + - type: Transform + pos: 4.5,48.5 + parent: 588 + - uid: 1338 + components: + - type: Transform + pos: 6.5,46.5 + parent: 588 + - uid: 1340 + components: + - type: Transform + pos: 2.5,48.5 + parent: 588 +- proto: WallSolid + entities: + - uid: 140 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 5.5,25.5 + parent: 588 + - uid: 144 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 5.5,26.5 + parent: 588 + - uid: 145 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 4.5,25.5 + parent: 588 + - uid: 146 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 5.5,27.5 + parent: 588 + - uid: 205 + components: + - type: Transform + pos: 8.5,35.5 + parent: 588 + - uid: 206 + components: + - type: Transform + pos: 7.5,35.5 + parent: 588 + - uid: 207 + components: + - type: Transform + pos: 6.5,35.5 + parent: 588 + - uid: 208 + components: + - type: Transform + pos: 4.5,35.5 + parent: 588 + - uid: 210 + components: + - type: Transform + pos: 1.5,34.5 + parent: 588 + - uid: 305 + components: + - type: Transform + pos: 22.5,4.5 + parent: 588 + - uid: 306 + components: + - type: Transform + pos: 22.5,3.5 + parent: 588 + - uid: 307 + components: + - type: Transform + pos: 22.5,0.5 + parent: 588 + - uid: 308 + components: + - type: Transform + pos: 22.5,1.5 + parent: 588 + - uid: 476 + components: + - type: Transform + pos: 25.5,13.5 + parent: 588 + - uid: 481 + components: + - type: Transform + pos: 25.5,15.5 + parent: 588 + - uid: 483 + components: + - type: Transform + pos: 30.5,13.5 + parent: 588 + - uid: 501 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 25.5,14.5 + parent: 588 + - uid: 510 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 30.5,15.5 + parent: 588 + - uid: 749 + components: + - type: Transform + pos: 25.5,19.5 + parent: 588 + - uid: 750 + components: + - type: Transform + pos: 27.5,19.5 + parent: 588 + - uid: 975 + components: + - type: Transform + pos: 19.5,32.5 + parent: 588 + - uid: 995 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 21.5,32.5 + parent: 588 + - uid: 1163 + components: + - type: Transform + pos: 8.5,36.5 + parent: 588 + - uid: 1164 + components: + - type: Transform + pos: 3.5,35.5 + parent: 588 + - uid: 1165 + components: + - type: Transform + pos: 2.5,35.5 + parent: 588 + - uid: 1167 + components: + - type: Transform + pos: 1.5,35.5 + parent: 588 + - uid: 1300 + components: + - type: Transform + pos: 25.5,39.5 + parent: 588 + - uid: 1303 + components: + - type: Transform + pos: 28.5,39.5 + parent: 588 + - uid: 1304 + components: + - type: Transform + pos: 29.5,39.5 + parent: 588 + - uid: 1305 + components: + - type: Transform + pos: 28.5,40.5 + parent: 588 + - uid: 1485 + components: + - type: Transform + pos: 17.5,43.5 + parent: 588 + - uid: 1486 + components: + - type: Transform + pos: 17.5,44.5 + parent: 588 + - uid: 1487 + components: + - type: Transform + pos: 18.5,43.5 + parent: 588 + - uid: 1488 + components: + - type: Transform + pos: 20.5,43.5 + parent: 588 + - uid: 1489 + components: + - type: Transform + pos: 21.5,43.5 + parent: 588 + - uid: 1490 + components: + - type: Transform + pos: 21.5,44.5 + parent: 588 + - uid: 1491 + components: + - type: Transform + pos: 18.5,47.5 + parent: 588 + - uid: 1492 + components: + - type: Transform + pos: 19.5,47.5 + parent: 588 + - uid: 1493 + components: + - type: Transform + pos: 20.5,47.5 + parent: 588 +- proto: WallWood + entities: + - uid: 554 + components: + - type: Transform + pos: 9.5,13.5 + parent: 588 + - uid: 555 + components: + - type: Transform + pos: 9.5,14.5 + parent: 588 + - uid: 556 + components: + - type: Transform + pos: 9.5,15.5 + parent: 588 + - uid: 557 + components: + - type: Transform + pos: 10.5,15.5 + parent: 588 + - uid: 558 + components: + - type: Transform + pos: 13.5,15.5 + parent: 588 + - uid: 559 + components: + - type: Transform + pos: 13.5,16.5 + parent: 588 + - uid: 566 + components: + - type: Transform + pos: 9.5,12.5 + parent: 588 +- proto: WardrobePrisonFilled + entities: + - uid: 297 + components: + - type: Transform + pos: 2.5,4.5 + parent: 588 + - uid: 302 + components: + - type: Transform + pos: 4.5,4.5 + parent: 588 + - uid: 303 + components: + - type: Transform + pos: 12.5,4.5 + parent: 588 + - uid: 304 + components: + - type: Transform + pos: 14.5,4.5 + parent: 588 + - uid: 544 + components: + - type: Transform + pos: 16.5,4.5 + parent: 588 + - uid: 545 + components: + - type: Transform + pos: 0.5,4.5 + parent: 588 + - uid: 1769 + components: + - type: Transform + pos: 24.5,43.5 + parent: 588 + - uid: 1770 + components: + - type: Transform + pos: 24.5,47.5 + parent: 588 +- proto: WaterCooler + entities: + - uid: 629 + components: + - type: Transform + pos: 6.5,18.5 + parent: 588 + - uid: 724 + components: + - type: Transform + pos: 19.5,22.5 + parent: 588 + - uid: 1284 + components: + - type: Transform + pos: 17.5,40.5 + parent: 588 + - uid: 1292 + components: + - type: Transform + pos: 21.5,12.5 + parent: 588 +- proto: WaterTankHighCapacity + entities: + - uid: 801 + components: + - type: Transform + pos: 30.5,22.5 + parent: 588 + - uid: 1789 + components: + - type: Transform + pos: 28.5,48.5 + parent: 588 +- proto: WeaponCapacitorRecharger + entities: + - uid: 760 + components: + - type: Transform + pos: 10.5,18.5 + parent: 588 + - uid: 929 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 26.5,34.5 + parent: 588 + - uid: 1033 + components: + - type: Transform + pos: 14.5,28.5 + parent: 588 + - uid: 1034 + components: + - type: Transform + pos: 16.5,24.5 + parent: 588 +- proto: WeaponDisablerPractice + entities: + - uid: 547 + components: + - type: Transform + pos: 1.4370823,0.5241035 + parent: 588 + - uid: 930 + components: + - type: Transform + pos: 26.440151,36.61676 + parent: 588 + - uid: 1611 + components: + - type: Transform + pos: 12.371853,10.605072 + parent: 588 +- proto: WeaponLaserCarbinePractice + entities: + - uid: 931 + components: + - type: Transform + pos: 26.596338,36.36132 + parent: 588 + - uid: 1612 + components: + - type: Transform + pos: 22.543945,6.5464144 + parent: 588 +- proto: WeaponShotgunKammerer + entities: + - uid: 583 + components: + - type: Transform + pos: 26.57963,35.4414 + parent: 588 +- proto: WindoorAssemblySecure + entities: + - uid: 696 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 16.5,19.5 + parent: 588 + - uid: 697 + components: + - type: Transform + pos: 12.5,21.5 + parent: 588 + - uid: 1073 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 25.5,25.5 + parent: 588 +- proto: WindoorSecure + entities: + - uid: 1761 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 26.5,43.5 + parent: 588 + - uid: 1762 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 26.5,47.5 + parent: 588 +- proto: WindoorSecureBrigLocked + entities: + - uid: 339 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 29.5,2.5 + parent: 588 +- proto: WindoorSecureEngineeringLocked + entities: + - uid: 1569 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 18.5,45.5 + parent: 588 + - uid: 1570 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 20.5,45.5 + parent: 588 +- proto: WindoorSecureMedicalLocked + entities: + - uid: 1408 + components: + - type: Transform + pos: 11.5,44.5 + parent: 588 +- proto: WindoorSecureSecurityLocked + entities: + - uid: 252 + components: + - type: Transform + pos: 4.5,3.5 + parent: 588 + - uid: 253 + components: + - type: Transform + pos: 2.5,3.5 + parent: 588 + - uid: 256 + components: + - type: Transform + pos: 16.5,3.5 + parent: 588 + - uid: 274 + components: + - type: Transform + pos: 12.5,3.5 + parent: 588 + - uid: 275 + components: + - type: Transform + pos: 14.5,3.5 + parent: 588 + - uid: 289 + components: + - type: Transform + pos: 0.5,3.5 + parent: 588 + - uid: 698 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 12.5,19.5 + parent: 588 + - uid: 1053 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 21.5,25.5 + parent: 588 + - uid: 1054 + components: + - type: Transform + pos: 21.5,27.5 + parent: 588 +- proto: WindowDirectional + entities: + - uid: 480 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 26.5,15.5 + parent: 588 + - uid: 482 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 27.5,15.5 + parent: 588 + - uid: 540 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 28.5,15.5 + parent: 588 + - uid: 541 + components: + - type: Transform + pos: 26.5,13.5 + parent: 588 + - uid: 542 + components: + - type: Transform + pos: 27.5,13.5 + parent: 588 + - uid: 543 + components: + - type: Transform + pos: 28.5,13.5 + parent: 588 + - uid: 575 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 33.5,20.5 + parent: 588 + - uid: 576 + components: + - type: Transform + pos: 26.5,19.5 + parent: 588 + - uid: 803 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 33.5,21.5 + parent: 588 + - uid: 804 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 33.5,19.5 + parent: 588 + - uid: 1087 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 27.5,36.5 + parent: 588 + - uid: 1088 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 27.5,34.5 + parent: 588 + - uid: 1520 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 18.5,46.5 + parent: 588 + - uid: 1521 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 20.5,46.5 + parent: 588 + - uid: 1771 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 28.5,44.5 + parent: 588 + - uid: 1773 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 29.5,44.5 + parent: 588 + - uid: 1777 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 30.5,44.5 + parent: 588 + - uid: 1782 + components: + - type: Transform + pos: 30.5,46.5 + parent: 588 +- proto: WindowFrostedDirectional + entities: + - uid: 936 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 8.5,24.5 + parent: 588 + - uid: 937 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 10.5,24.5 + parent: 588 + - uid: 938 + components: + - type: Transform + pos: 8.5,27.5 + parent: 588 + - uid: 939 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 8.5,25.5 + parent: 588 + - uid: 941 + components: + - type: Transform + pos: 10.5,27.5 + parent: 588 + - uid: 942 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 10.5,28.5 + parent: 588 + - uid: 943 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 8.5,28.5 + parent: 588 + - uid: 1392 + components: + - type: Transform + pos: 8.5,44.5 + parent: 588 + - uid: 1393 + components: + - type: Transform + pos: 10.5,44.5 + parent: 588 + - uid: 1401 + components: + - type: Transform + pos: 12.5,44.5 + parent: 588 + - uid: 1402 + components: + - type: Transform + pos: 14.5,44.5 + parent: 588 + - uid: 1753 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 24.5,43.5 + parent: 588 + - uid: 1754 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 25.5,43.5 + parent: 588 + - uid: 1755 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 26.5,43.5 + parent: 588 + - uid: 1756 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 26.5,42.5 + parent: 588 + - uid: 1757 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 26.5,48.5 + parent: 588 + - uid: 1758 + components: + - type: Transform + pos: 26.5,47.5 + parent: 588 + - uid: 1759 + components: + - type: Transform + pos: 25.5,47.5 + parent: 588 + - uid: 1760 + components: + - type: Transform + pos: 24.5,47.5 + parent: 588 +- proto: WindowReinforcedDirectional + entities: + - uid: 97 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 2.5,14.5 + parent: 588 + - uid: 99 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 4.5,14.5 + parent: 588 + - uid: 258 + components: + - type: Transform + pos: 3.5,3.5 + parent: 588 + - uid: 259 + components: + - type: Transform + pos: 5.5,3.5 + parent: 588 + - uid: 260 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 3.5,3.5 + parent: 588 + - uid: 261 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 3.5,4.5 + parent: 588 + - uid: 270 + components: + - type: Transform + pos: 11.5,3.5 + parent: 588 + - uid: 271 + components: + - type: Transform + pos: 13.5,3.5 + parent: 588 + - uid: 272 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 13.5,3.5 + parent: 588 + - uid: 273 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 13.5,4.5 + parent: 588 + - uid: 277 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 1.5,4.5 + parent: 588 + - uid: 278 + components: + - type: Transform + pos: 15.5,3.5 + parent: 588 + - uid: 279 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 15.5,4.5 + parent: 588 + - uid: 280 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 1.5,3.5 + parent: 588 + - uid: 281 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 15.5,3.5 + parent: 588 + - uid: 290 + components: + - type: Transform + pos: 1.5,3.5 + parent: 588 + - uid: 607 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 3.5,14.5 + parent: 588 + - uid: 609 + components: + - type: Transform + pos: 4.5,14.5 + parent: 588 + - uid: 611 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 4.5,14.5 + parent: 588 + - uid: 612 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 2.5,14.5 + parent: 588 + - uid: 614 + components: + - type: Transform + pos: 3.5,14.5 + parent: 588 + - uid: 619 + components: + - type: Transform + pos: 2.5,14.5 + parent: 588 + - uid: 620 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 3.5,20.5 + parent: 588 + - uid: 624 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.5,20.5 + parent: 588 + - uid: 625 + components: + - type: Transform + pos: 2.5,19.5 + parent: 588 + - uid: 626 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 2.5,21.5 + parent: 588 + - uid: 648 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 15.5,19.5 + parent: 588 + - uid: 650 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 13.5,18.5 + parent: 588 + - uid: 651 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 13.5,19.5 + parent: 588 + - uid: 652 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 15.5,19.5 + parent: 588 + - uid: 653 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 15.5,18.5 + parent: 588 + - uid: 654 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 13.5,21.5 + parent: 588 + - uid: 658 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 13.5,22.5 + parent: 588 + - uid: 660 + components: + - type: Transform + pos: 13.5,21.5 + parent: 588 + - uid: 805 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 1.5,27.5 + parent: 588 + - uid: 806 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.5,26.5 + parent: 588 + - uid: 807 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.5,25.5 + parent: 588 + - uid: 808 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 1.5,25.5 + parent: 588 + - uid: 809 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 1.5,26.5 + parent: 588 + - uid: 810 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.5,27.5 + parent: 588 + - uid: 811 + components: + - type: Transform + pos: 1.5,25.5 + parent: 588 + - uid: 812 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.5,27.5 + parent: 588 + - uid: 1038 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 22.5,25.5 + parent: 588 + - uid: 1039 + components: + - type: Transform + pos: 20.5,27.5 + parent: 588 + - uid: 1042 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 20.5,25.5 + parent: 588 + - uid: 1045 + components: + - type: Transform + pos: 22.5,27.5 + parent: 588 + - uid: 1058 + components: + - type: Transform + pos: 24.5,27.5 + parent: 588 + - uid: 1059 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 26.5,25.5 + parent: 588 + - uid: 1060 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 24.5,25.5 + parent: 588 + - uid: 1399 + components: + - type: Transform + pos: 9.5,44.5 + parent: 588 + - uid: 1400 + components: + - type: Transform + pos: 13.5,44.5 + parent: 588 +- proto: Wrench + entities: + - uid: 424 + components: + - type: Transform + pos: 15.156982,32.526764 + parent: 588 +- proto: Zipties + entities: + - uid: 1156 + components: + - type: Transform + pos: 15.332411,0.52492684 + parent: 588 +- proto: ZiptiesBroken + entities: + - uid: 48 + components: + - type: Transform + pos: 5.2591753,3.5817227 + parent: 588 + - uid: 706 + components: + - type: Transform + pos: 16.06022,21.977758 + parent: 588 +... diff --git a/Resources/Maps/Nonstations/glacier_surface.yml b/Resources/Maps/Nonstations/glacier_surface.yml new file mode 100644 index 0000000000..b6aeacaceb --- /dev/null +++ b/Resources/Maps/Nonstations/glacier_surface.yml @@ -0,0 +1,2001 @@ +meta: + format: 6 + postmapinit: false +tilemap: + 0: Space + 1: FloorDark + 6: FloorMining + 4: FloorMiningDark + 7: FloorMiningLight + 2: FloorMono + 9: FloorReinforced + 3: FloorSnow + 12: FloorSnowDug + 89: FloorSteel + 8: FloorTechMaint2 + 11: FloorTechMaint3 + 10: FloorWood + 5: Plating +entities: +- proto: "" + entities: + - uid: 1 + components: + - type: MetaData + name: Glacier Surface + - type: Transform + - type: Map + mapPaused: True + - type: Broadphase + - type: OccluderTree + - type: PhysicsMap + - type: GridTree + - type: MovedGrids + - type: FTLDestination + - type: LoadedMap + - type: MapAtmosphere + space: False + mixture: + volume: 2500 + immutable: True + temperature: 180 + moles: + - 42 + - 158 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - type: MapGrid + - type: Biome + template: Snow + enabled: False + - type: Gravity + inherent: True + enabled: True + - type: MapLight + ambientLightColor: '#2B3153FF' + - uid: 2 + components: + - type: MetaData + name: Surface Outpost + - type: Transform + parent: 1 + - type: MapGrid + chunks: + -1,0: + ind: -1,0 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAABAAAAAAABAAAAAAABgAAAAAABgAAAAAABgAAAAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAABQAAAAAABQAAAAAABQAAAAAABQAAAAAABwAAAAAABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAwAAAAAAAwAAAAAABQAAAAAABQAAAAAABQAAAAAABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAABQAAAAAACAAAAAAABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAACAAAAAAABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAACAAAAAAABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAACAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAABQAAAAAACQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + version: 6 + 0,0: + ind: 0,0 + tiles: BgAAAAAABgAAAAAABgAAAAAABgAAAAAABAAAAAAABgAAAAAACQAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAAABQAAAAAABQAAAAAABgAAAAAABAAAAAAABgAAAAAABQAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAABQAAAAAABQAAAAAABQAAAAAABgAAAAAABQAAAAAABQAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAABQAAAAAABQAAAAAACgAAAAAACgAAAAAACgAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAABQAAAAAABQAAAAAACgAAAAAACgAAAAAACgAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAABQAAAAAABQAAAAAACgAAAAAACgAAAAAACgAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAABQAAAAAABQAAAAAACgAAAAAACgAAAAAACgAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAwAAAAAAAwAAAAAABQAAAAAABQAAAAAABQAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + version: 6 + 0,-1: + ind: 0,-1 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAwAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAwAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAwAAAAAAAwAAAAAABQAAAAAACQAAAAAABQAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAABQAAAAAABQAAAAAABAAAAAAABAAAAAAABAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAABQAAAAAABQAAAAAABAAAAAAABAAAAAAABAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAABQAAAAAABQAAAAAABAAAAAAABAAAAAAABAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAABQAAAAAABQAAAAAABgAAAAAABAAAAAAABgAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAABQAAAAAABQAAAAAABQAAAAAABAAAAAAABQAAAAAABQAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAAABQAAAAAABQAAAAAABgAAAAAABAAAAAAABgAAAAAABQAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAAAABgAAAAAABgAAAAAABgAAAAAABAAAAAAABgAAAAAACQAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAABAAAAAAABAAAAAAABAAAAAAABAAAAAAABgAAAAAACQAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + version: 6 + -1,-1: + ind: -1,-1 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAABQAAAAAACQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAABQAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAABQAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAACwAAAAAACwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAABQAAAAAACwAAAAAABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAwAAAAAAAwAAAAAABQAAAAAABQAAAAAABQAAAAAABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAABQAAAAAABQAAAAAABQAAAAAABQAAAAAABwAAAAAABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAABAAAAAAABAAAAAAABgAAAAAABgAAAAAABgAAAAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACQAAAAAABgAAAAAABAAAAAAABAAAAAAABAAAAAAABAAAAAAABAAAAAAA + version: 6 + 1,-1: + ind: 1,-1 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAADAAAAAAADAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAADAAAAAAADAAAAAAADAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAADAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + version: 6 + - type: Broadphase + - type: SpreaderGrid + - type: OccluderTree + - type: GridPathfinding + - type: DecalGrid + chunkCollection: + version: 2 + nodes: [] + - type: RadiationGridResistance + - type: GridAtmosphere + version: 2 + data: + tiles: + -2,0: + 0: 3630 + -2,-1: + 0: 52782 + -1,0: + 0: 56015 + -1,-1: + 0: 65482 + -1,1: + 0: 11468 + -1,2: + 0: 204 + 0,0: + 0: 37535 + 0,1: + 0: 10649 + 0,2: + 0: 17 + 0,-1: + 0: 65430 + 1,0: + 0: 14523 + 1,1: + 0: 819 + 1,-1: + 0: 48057 + 0,-3: + 0: 27024 + -1,-3: + 0: 11456 + 0,-2: + 0: 39321 + -1,-2: + 0: 56524 + 1,-3: + 0: 17200 + 1,-2: + 0: 13107 + 5,-2: + 0: 51200 + 6,-2: + 0: 30512 + 5,-1: + 0: 8 + 6,-1: + 0: 19 + uniqueMixes: + - volume: 2500 + temperature: 293.15 + moles: + - 21.824879 + - 82.10312 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + chunkSize: 4 + - type: GasTileOverlay +- proto: AirlockAtmospherics + entities: + - uid: 157 + components: + - type: MetaData + name: Atmos + - type: Transform + pos: -0.5,-3.5 + parent: 1 +- proto: AirlockCargo + entities: + - uid: 263 + components: + - type: MetaData + name: Supplies + - type: Transform + pos: 4.5,-3.5 + parent: 1 +- proto: AirlockEngineering + entities: + - uid: 34 + components: + - type: MetaData + name: Power Room + - type: Transform + pos: -0.5,2.5 + parent: 1 +- proto: AirlockGlass + entities: + - uid: 65 + components: + - type: Transform + pos: 1.5,0.5 + parent: 1 + - uid: 66 + components: + - type: Transform + pos: 1.5,-0.5 + parent: 1 + - uid: 67 + components: + - type: Transform + pos: 1.5,-1.5 + parent: 1 + - uid: 68 + components: + - type: Transform + pos: -2.5,0.5 + parent: 1 + - uid: 69 + components: + - type: Transform + pos: -2.5,-0.5 + parent: 1 + - uid: 70 + components: + - type: Transform + pos: -2.5,-1.5 + parent: 1 +- proto: AirlockMiningGlass + entities: + - uid: 255 + components: + - type: MetaData + name: Outpost + - type: Transform + pos: -6.5,-1.5 + parent: 1 + - uid: 256 + components: + - type: MetaData + name: Outpost + - type: Transform + pos: -6.5,0.5 + parent: 1 +- proto: APCBasic + entities: + - uid: 22 + components: + - type: MetaData + name: Power Room APC + - type: Transform + rot: 1.5707963267948966 rad + pos: -2.5,5.5 + parent: 1 + - uid: 200 + components: + - type: MetaData + name: Atmos APC + - type: Transform + rot: 1.5707963267948966 rad + pos: -2.5,-5.5 + parent: 1 + - uid: 275 + components: + - type: MetaData + name: General APC + - type: Transform + pos: -3.5,1.5 + parent: 1 +- proto: AtmosDeviceFanTiny + entities: + - uid: 257 + components: + - type: Transform + pos: -6.5,0.5 + parent: 1 + - uid: 258 + components: + - type: Transform + pos: -6.5,-1.5 + parent: 1 +- proto: Bed + entities: + - uid: 93 + components: + - type: Transform + pos: 5.5,3.5 + parent: 1 + - uid: 94 + components: + - type: Transform + pos: 5.5,5.5 + parent: 1 +- proto: BedsheetSpawner + entities: + - uid: 92 + components: + - type: Transform + pos: 5.5,5.5 + parent: 1 + - uid: 95 + components: + - type: Transform + pos: 5.5,3.5 + parent: 1 +- proto: Bonfire + entities: + - uid: 289 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 24.5,-4.5 + parent: 1 +- proto: CableApcExtension + entities: + - uid: 41 + components: + - type: Transform + pos: -2.5,5.5 + parent: 1 + - uid: 42 + components: + - type: Transform + pos: -1.5,5.5 + parent: 1 + - uid: 43 + components: + - type: Transform + pos: -0.5,5.5 + parent: 1 + - uid: 44 + components: + - type: Transform + pos: -0.5,4.5 + parent: 1 + - uid: 45 + components: + - type: Transform + pos: -0.5,3.5 + parent: 1 + - uid: 46 + components: + - type: Transform + pos: -0.5,2.5 + parent: 1 + - uid: 47 + components: + - type: Transform + pos: -0.5,1.5 + parent: 1 + - uid: 48 + components: + - type: Transform + pos: -0.5,6.5 + parent: 1 + - uid: 103 + components: + - type: Transform + pos: -1.5,-0.5 + parent: 1 + - uid: 104 + components: + - type: Transform + pos: -0.5,-0.5 + parent: 1 + - uid: 105 + components: + - type: Transform + pos: 0.5,-0.5 + parent: 1 + - uid: 106 + components: + - type: Transform + pos: 1.5,-0.5 + parent: 1 + - uid: 107 + components: + - type: Transform + pos: 2.5,-0.5 + parent: 1 + - uid: 108 + components: + - type: Transform + pos: 3.5,-0.5 + parent: 1 + - uid: 109 + components: + - type: Transform + pos: 4.5,-0.5 + parent: 1 + - uid: 110 + components: + - type: Transform + pos: 5.5,-0.5 + parent: 1 + - uid: 111 + components: + - type: Transform + pos: 4.5,0.5 + parent: 1 + - uid: 112 + components: + - type: Transform + pos: 4.5,1.5 + parent: 1 + - uid: 113 + components: + - type: Transform + pos: 4.5,2.5 + parent: 1 + - uid: 114 + components: + - type: Transform + pos: 4.5,3.5 + parent: 1 + - uid: 115 + components: + - type: Transform + pos: 4.5,4.5 + parent: 1 + - uid: 116 + components: + - type: Transform + pos: 4.5,5.5 + parent: 1 + - uid: 117 + components: + - type: Transform + pos: 4.5,6.5 + parent: 1 + - uid: 118 + components: + - type: Transform + pos: 5.5,4.5 + parent: 1 + - uid: 119 + components: + - type: Transform + pos: 6.5,4.5 + parent: 1 + - uid: 154 + components: + - type: Transform + pos: -0.5,-2.5 + parent: 1 + - uid: 155 + components: + - type: Transform + pos: -0.5,-3.5 + parent: 1 + - uid: 156 + components: + - type: Transform + pos: -0.5,-4.5 + parent: 1 + - uid: 207 + components: + - type: Transform + pos: -0.5,-5.5 + parent: 1 + - uid: 208 + components: + - type: Transform + pos: -1.5,-5.5 + parent: 1 + - uid: 209 + components: + - type: Transform + pos: -2.5,-5.5 + parent: 1 + - uid: 265 + components: + - type: Transform + pos: -2.5,-0.5 + parent: 1 + - uid: 266 + components: + - type: Transform + pos: -3.5,-0.5 + parent: 1 + - uid: 267 + components: + - type: Transform + pos: -4.5,-0.5 + parent: 1 + - uid: 268 + components: + - type: Transform + pos: -3.5,0.5 + parent: 1 + - uid: 269 + components: + - type: Transform + pos: -3.5,1.5 + parent: 1 + - uid: 278 + components: + - type: Transform + pos: -5.5,-0.5 + parent: 1 + - uid: 279 + components: + - type: Transform + pos: -4.5,-1.5 + parent: 1 + - uid: 280 + components: + - type: Transform + pos: 4.5,-1.5 + parent: 1 + - uid: 281 + components: + - type: Transform + pos: 4.5,-2.5 + parent: 1 + - uid: 282 + components: + - type: Transform + pos: 4.5,-3.5 + parent: 1 + - uid: 283 + components: + - type: Transform + pos: 4.5,-4.5 + parent: 1 + - uid: 284 + components: + - type: Transform + pos: 4.5,-5.5 + parent: 1 + - uid: 285 + components: + - type: Transform + pos: 4.5,-6.5 + parent: 1 + - uid: 286 + components: + - type: Transform + pos: 5.5,-2.5 + parent: 1 + - uid: 287 + components: + - type: Transform + pos: 5.5,1.5 + parent: 1 +- proto: CableHV + entities: + - uid: 35 + components: + - type: Transform + pos: 1.5,4.5 + parent: 1 + - uid: 36 + components: + - type: Transform + pos: 0.5,4.5 + parent: 1 + - uid: 37 + components: + - type: Transform + pos: -0.5,4.5 + parent: 1 + - uid: 38 + components: + - type: Transform + pos: -1.5,4.5 + parent: 1 + - uid: 39 + components: + - type: Transform + pos: -2.5,4.5 + parent: 1 + - uid: 40 + components: + - type: Transform + pos: 1.5,5.5 + parent: 1 +- proto: CableMV + entities: + - uid: 999 + components: + - type: Transform + pos: -0.5,-1.5 + parent: 1 + - uid: 25 + components: + - type: Transform + pos: -2.5,5.5 + parent: 1 + - uid: 26 + components: + - type: Transform + pos: -2.5,4.5 + parent: 1 + - uid: 27 + components: + - type: Transform + pos: -1.5,4.5 + parent: 1 + - uid: 28 + components: + - type: Transform + pos: -0.5,4.5 + parent: 1 + - uid: 29 + components: + - type: Transform + pos: -0.5,3.5 + parent: 1 + - uid: 30 + components: + - type: Transform + pos: -0.5,2.5 + parent: 1 + - uid: 31 + components: + - type: Transform + pos: -0.5,1.5 + parent: 1 + - uid: 32 + components: + - type: Transform + pos: -0.5,0.5 + parent: 1 + - uid: 33 + components: + - type: Transform + pos: -0.5,-0.5 + parent: 1 + - uid: 201 + components: + - type: Transform + pos: -2.5,-5.5 + parent: 1 + - uid: 202 + components: + - type: Transform + pos: -1.5,-5.5 + parent: 1 + - uid: 203 + components: + - type: Transform + pos: -0.5,-5.5 + parent: 1 + - uid: 204 + components: + - type: Transform + pos: -0.5,-4.5 + parent: 1 + - uid: 205 + components: + - type: Transform + pos: -0.5,-3.5 + parent: 1 + - uid: 206 + components: + - type: Transform + pos: -0.5,-2.5 + parent: 1 + - uid: 270 + components: + - type: Transform + pos: -3.5,1.5 + parent: 1 + - uid: 271 + components: + - type: Transform + pos: -3.5,0.5 + parent: 1 + - uid: 272 + components: + - type: Transform + pos: -3.5,-0.5 + parent: 1 + - uid: 273 + components: + - type: Transform + pos: -2.5,-0.5 + parent: 1 + - uid: 274 + components: + - type: Transform + pos: -1.5,-0.5 + parent: 1 +- proto: ChairFolding + entities: + - uid: 290 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 25.399157,-4.3998504 + parent: 1 + - uid: 291 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 23.711657,-4.9779754 + parent: 1 +- proto: ClothingShoesBootsWinterClown + entities: + - uid: 59 + components: + - type: Transform + pos: -2.5533402,2.3444276 + parent: 1 +- proto: ComfyChair + entities: + - uid: 97 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 4.5,4.5 + parent: 1 +- proto: CrateFoodMRE + entities: + - uid: 277 + components: + - type: Transform + pos: 5.5,-0.5 + parent: 1 +- proto: DogBed + entities: + - uid: 90 + components: + - type: Transform + pos: 4.5,6.5 + parent: 1 +- proto: DrinkHotCoco + entities: + - uid: 102 + components: + - type: Transform + pos: 3.7032022,3.7809036 + parent: 1 +- proto: Fireplace + entities: + - uid: 88 + components: + - type: Transform + pos: 3.5,6.5 + parent: 1 + - uid: 89 + components: + - type: Transform + pos: 5.5,6.5 + parent: 1 +- proto: Flare + entities: + - uid: 276 + components: + - type: Transform + pos: 5.706268,-6.553908 + parent: 1 +- proto: FoodMothMothmallowSlice + entities: + - uid: 292 + components: + - type: Transform + pos: 24.460783,-4.738508 + parent: 1 +- proto: FuelDispenser + entities: + - uid: 64 + components: + - type: Transform + pos: 2.5,1.5 + parent: 1 +- proto: GasPipeBend + entities: + - uid: 141 + components: + - type: Transform + pos: 4.5,4.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 153 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 4.5,0.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 178 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -0.5,-4.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 179 + components: + - type: Transform + pos: 0.5,-4.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' +- proto: GasPipeStraight + entities: + - uid: 135 + components: + - type: Transform + pos: 4.5,1.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 139 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 4.5,3.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 140 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 4.5,2.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 142 + components: + - type: Transform + pos: -0.5,4.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 143 + components: + - type: Transform + pos: -0.5,3.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 144 + components: + - type: Transform + pos: -0.5,2.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 145 + components: + - type: Transform + pos: -0.5,1.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 147 + components: + - type: Transform + pos: -0.5,-0.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 149 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 0.5,0.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 150 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 1.5,0.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 151 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 2.5,0.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 176 + components: + - type: Transform + pos: -0.5,-2.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 177 + components: + - type: Transform + pos: -0.5,-3.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 186 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -1.5,-4.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' + - uid: 187 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -1.5,-3.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' + - uid: 188 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -1.5,-2.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' + - uid: 189 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -1.5,-1.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' + - uid: 190 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -0.5,-0.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' + - uid: 191 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 0.5,-0.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' + - uid: 192 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 1.5,-0.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' + - uid: 193 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 2.5,-0.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' + - uid: 194 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 3.5,-0.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' + - uid: 195 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 4.5,-0.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' +- proto: GasPipeTJunction + entities: + - uid: 137 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -0.5,-1.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 146 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -0.5,0.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 152 + components: + - type: Transform + pos: 3.5,0.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 197 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -1.5,-0.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' +- proto: GasPort + entities: + - uid: 182 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 0.5,-6.5 + parent: 1 + - uid: 185 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -1.5,-6.5 + parent: 1 +- proto: GasPressurePump + entities: + - uid: 184 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -1.5,-5.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' +- proto: GasVentPump + entities: + - uid: 196 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 5.5,-0.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' + - uid: 198 + components: + - type: Transform + pos: -1.5,0.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' +- proto: GasVentScrubber + entities: + - uid: 133 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 3.5,4.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 134 + components: + - type: Transform + pos: -0.5,5.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 136 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 0.5,-1.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 138 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 3.5,-0.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' +- proto: GasVolumePump + entities: + - uid: 181 + components: + - type: Transform + pos: 0.5,-5.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' +- proto: GeneratorWallmountAPU + entities: + - uid: 23 + components: + - type: Transform + pos: 1.5,4.5 + parent: 1 + - uid: 24 + components: + - type: Transform + pos: 1.5,5.5 + parent: 1 +- proto: Grille + entities: + - uid: 9 + components: + - type: Transform + pos: -0.5,7.5 + parent: 1 + - uid: 174 + components: + - type: Transform + pos: -0.5,-8.5 + parent: 1 + - uid: 230 + components: + - type: Transform + pos: 4.5,-8.5 + parent: 1 + - uid: 231 + components: + - type: Transform + pos: 6.5,-1.5 + parent: 1 + - uid: 232 + components: + - type: Transform + pos: 6.5,-0.5 + parent: 1 + - uid: 233 + components: + - type: Transform + pos: 6.5,0.5 + parent: 1 +- proto: HydroponicsToolHatchet + entities: + - uid: 125 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 3.6533608,-5.5518365 + parent: 1 +- proto: LampGold + entities: + - uid: 100 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 3.3125772,4.812154 + parent: 1 +- proto: MedkitFilled + entities: + - uid: 130 + components: + - type: Transform + pos: 3.5337152,-6.4737115 + parent: 1 +- proto: MiningWindow + entities: + - uid: 10 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -0.5,7.5 + parent: 1 + - uid: 175 + components: + - type: Transform + pos: -0.5,-8.5 + parent: 1 + - uid: 238 + components: + - type: Transform + pos: 6.5,0.5 + parent: 1 + - uid: 239 + components: + - type: Transform + pos: 6.5,-0.5 + parent: 1 + - uid: 240 + components: + - type: Transform + pos: 6.5,-1.5 + parent: 1 + - uid: 241 + components: + - type: Transform + pos: 4.5,-8.5 + parent: 1 + - uid: 242 + components: + - type: Transform + pos: -6.5,-0.5 + parent: 1 +- proto: OreBag + entities: + - uid: 131 + components: + - type: Transform + pos: 5.346893,-6.413283 + parent: 1 +- proto: OreBox + entities: + - uid: 132 + components: + - type: Transform + pos: -5.5,2.5 + parent: 1 +- proto: OxygenCanister + entities: + - uid: 183 + components: + - type: Transform + pos: -1.5,-6.5 + parent: 1 +- proto: PartRodMetal1 + entities: + - uid: 293 + components: + - type: Transform + pos: 24.226086,-4.9623504 + parent: 1 +- proto: Pickaxe + entities: + - uid: 124 + components: + - type: Transform + pos: 3.4346108,-5.3174615 + parent: 1 +- proto: PlushieLizard + entities: + - uid: 58 + components: + - type: Transform + pos: -2.4574614,2.5163026 + parent: 1 +- proto: PoweredLEDLightPostSmall + entities: + - uid: 148 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 4.5,-0.5 + parent: 1 +- proto: PoweredLightPostSmall + entities: + - uid: 120 + components: + - type: Transform + pos: -4.5,2.5 + parent: 1 + - uid: 121 + components: + - type: Transform + pos: -4.5,-3.5 + parent: 1 + - uid: 122 + components: + - type: Transform + pos: 7.5,2.5 + parent: 1 + - uid: 123 + components: + - type: Transform + pos: 7.5,-3.5 + parent: 1 +- proto: PoweredSmallLight + entities: + - uid: 49 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 0.5,5.5 + parent: 1 + - uid: 96 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 3.5,5.5 + parent: 1 + - uid: 210 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 0.5,-5.5 + parent: 1 + - uid: 264 + components: + - type: Transform + pos: -4.5,0.5 + parent: 1 +- proto: Rack + entities: + - uid: 259 + components: + - type: Transform + pos: 3.5,-5.5 + parent: 1 + - uid: 260 + components: + - type: Transform + pos: 3.5,-6.5 + parent: 1 + - uid: 261 + components: + - type: Transform + pos: 5.5,-5.5 + parent: 1 + - uid: 262 + components: + - type: Transform + pos: 5.5,-6.5 + parent: 1 +- proto: RadioHandheld + entities: + - uid: 126 + components: + - type: Transform + pos: 5.3618402,-5.2393365 + parent: 1 + - uid: 127 + components: + - type: Transform + pos: 5.7212152,-5.2080865 + parent: 1 + - uid: 128 + components: + - type: Transform + pos: 5.8149652,-5.5205865 + parent: 1 + - uid: 129 + components: + - type: Transform + pos: 5.5024652,-5.5049615 + parent: 1 +- proto: RandomBook + entities: + - uid: 101 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 3.6563272,4.405904 + parent: 1 +- proto: ReinforcedGirder + entities: + - uid: 73 + components: + - type: Transform + pos: 1.5,2.5 + parent: 1 + - uid: 219 + components: + - type: Transform + pos: 1.5,-3.5 + parent: 1 + - uid: 236 + components: + - type: Transform + pos: 2.5,-3.5 + parent: 1 + - uid: 237 + components: + - type: Transform + pos: -2.5,-3.5 + parent: 1 +- proto: SinkStemlessWater + entities: + - uid: 288 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -4.5,-1.5 + parent: 1 +- proto: SpawnMobCatGeneric + entities: + - uid: 91 + components: + - type: Transform + pos: 4.5,6.5 + parent: 1 +- proto: StorageCanister + entities: + - uid: 180 + components: + - type: Transform + pos: 0.5,-6.5 + parent: 1 +- proto: SubstationWallBasic + entities: + - uid: 21 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -2.5,4.5 + parent: 1 +- proto: Table + entities: + - uid: 62 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -0.5,6.5 + parent: 1 +- proto: TableWood + entities: + - uid: 98 + components: + - type: Transform + pos: 3.5,4.5 + parent: 1 + - uid: 99 + components: + - type: Transform + pos: 3.5,3.5 + parent: 1 +- proto: ToolboxMechanicalFilled + entities: + - uid: 63 + components: + - type: Transform + pos: -0.53097445,6.634279 + parent: 1 +- proto: WallMining + entities: + - uid: 7 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.5,4.5 + parent: 1 + - uid: 8 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.5,3.5 + parent: 1 + - uid: 11 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -2.5,3.5 + parent: 1 + - uid: 12 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -2.5,4.5 + parent: 1 + - uid: 13 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -2.5,5.5 + parent: 1 + - uid: 14 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 0.5,7.5 + parent: 1 + - uid: 15 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.5,6.5 + parent: 1 + - uid: 16 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.5,5.5 + parent: 1 + - uid: 17 + components: + - type: Transform + pos: -2.5,1.5 + parent: 1 + - uid: 19 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -1.5,7.5 + parent: 1 + - uid: 20 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -2.5,6.5 + parent: 1 + - uid: 50 + components: + - type: Transform + pos: -1.5,2.5 + parent: 1 + - uid: 54 + components: + - type: Transform + pos: 0.5,2.5 + parent: 1 + - uid: 55 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.5,-2.5 + parent: 1 + - uid: 56 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -2.5,-2.5 + parent: 1 + - uid: 61 + components: + - type: Transform + pos: 1.5,1.5 + parent: 1 + - uid: 158 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -1.5,-3.5 + parent: 1 + - uid: 159 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 0.5,-3.5 + parent: 1 + - uid: 160 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -2.5,-4.5 + parent: 1 + - uid: 161 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -2.5,-5.5 + parent: 1 + - uid: 162 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -2.5,-6.5 + parent: 1 + - uid: 163 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -2.5,-7.5 + parent: 1 + - uid: 164 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -1.5,-8.5 + parent: 1 + - uid: 165 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 0.5,-8.5 + parent: 1 + - uid: 166 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.5,-7.5 + parent: 1 + - uid: 167 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.5,-6.5 + parent: 1 + - uid: 168 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.5,-5.5 + parent: 1 + - uid: 169 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.5,-4.5 + parent: 1 + - uid: 199 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 6.5,1.5 + parent: 1 + - uid: 211 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 6.5,-2.5 + parent: 1 + - uid: 212 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 6.5,-3.5 + parent: 1 + - uid: 213 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 6.5,-4.5 + parent: 1 + - uid: 214 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 6.5,-5.5 + parent: 1 + - uid: 215 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 6.5,-6.5 + parent: 1 + - uid: 216 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 6.5,-7.5 + parent: 1 + - uid: 217 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 5.5,-3.5 + parent: 1 + - uid: 218 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 3.5,-3.5 + parent: 1 + - uid: 220 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 2.5,-4.5 + parent: 1 + - uid: 221 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 2.5,-5.5 + parent: 1 + - uid: 222 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 2.5,-6.5 + parent: 1 + - uid: 223 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 2.5,-7.5 + parent: 1 + - uid: 224 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 3.5,-8.5 + parent: 1 + - uid: 225 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 5.5,-8.5 + parent: 1 + - uid: 234 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 2.5,-2.5 + parent: 1 + - uid: 235 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 2.5,1.5 + parent: 1 + - uid: 247 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -3.5,-3.5 + parent: 1 + - uid: 248 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -3.5,-2.5 + parent: 1 + - uid: 249 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -4.5,-2.5 + parent: 1 + - uid: 250 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -5.5,-2.5 + parent: 1 + - uid: 251 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -5.5,1.5 + parent: 1 + - uid: 252 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -4.5,1.5 + parent: 1 + - uid: 253 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -3.5,1.5 + parent: 1 + - uid: 254 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -3.5,2.5 + parent: 1 +- proto: WallMiningDiagonal + entities: + - uid: 3 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -1.5,3.5 + parent: 1 + - uid: 4 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 0.5,6.5 + parent: 1 + - uid: 5 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -1.5,6.5 + parent: 1 + - uid: 6 + components: + - type: Transform + pos: 0.5,3.5 + parent: 1 + - uid: 18 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 3.5,-2.5 + parent: 1 + - uid: 51 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -1.5,1.5 + parent: 1 + - uid: 52 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -1.5,-2.5 + parent: 1 + - uid: 53 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 3.5,1.5 + parent: 1 + - uid: 57 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 0.5,1.5 + parent: 1 + - uid: 60 + components: + - type: Transform + pos: 0.5,-2.5 + parent: 1 + - uid: 170 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -1.5,-4.5 + parent: 1 + - uid: 171 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 0.5,-4.5 + parent: 1 + - uid: 172 + components: + - type: Transform + pos: 0.5,-7.5 + parent: 1 + - uid: 173 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -1.5,-7.5 + parent: 1 + - uid: 226 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 3.5,-4.5 + parent: 1 + - uid: 227 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 5.5,-4.5 + parent: 1 + - uid: 228 + components: + - type: Transform + pos: 5.5,-7.5 + parent: 1 + - uid: 229 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 3.5,-7.5 + parent: 1 + - uid: 243 + components: + - type: Transform + pos: -6.5,1.5 + parent: 1 + - uid: 244 + components: + - type: Transform + pos: -3.5,3.5 + parent: 1 + - uid: 245 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -3.5,-4.5 + parent: 1 + - uid: 246 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -6.5,-2.5 + parent: 1 + - uid: 294 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -2.5,-8.5 + parent: 1 + - uid: 295 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.5,-8.5 + parent: 1 + - uid: 296 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 2.5,-8.5 + parent: 1 + - uid: 297 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 6.5,-8.5 + parent: 1 + - uid: 298 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.5,7.5 + parent: 1 + - uid: 299 + components: + - type: Transform + pos: -2.5,7.5 + parent: 1 +- proto: WallWood + entities: + - uid: 71 + components: + - type: Transform + pos: 3.5,2.5 + parent: 1 + - uid: 72 + components: + - type: Transform + pos: 2.5,2.5 + parent: 1 + - uid: 74 + components: + - type: Transform + pos: 2.5,3.5 + parent: 1 + - uid: 75 + components: + - type: Transform + pos: 2.5,4.5 + parent: 1 + - uid: 76 + components: + - type: Transform + pos: 2.5,5.5 + parent: 1 + - uid: 77 + components: + - type: Transform + pos: 2.5,6.5 + parent: 1 + - uid: 78 + components: + - type: Transform + pos: 3.5,7.5 + parent: 1 + - uid: 79 + components: + - type: Transform + pos: 4.5,7.5 + parent: 1 + - uid: 80 + components: + - type: Transform + pos: 5.5,7.5 + parent: 1 + - uid: 81 + components: + - type: Transform + pos: 6.5,6.5 + parent: 1 + - uid: 82 + components: + - type: Transform + pos: 6.5,5.5 + parent: 1 + - uid: 83 + components: + - type: Transform + pos: 6.5,4.5 + parent: 1 + - uid: 84 + components: + - type: Transform + pos: 6.5,3.5 + parent: 1 + - uid: 85 + components: + - type: Transform + pos: 6.5,2.5 + parent: 1 + - uid: 86 + components: + - type: Transform + pos: 5.5,2.5 + parent: 1 + - uid: 300 + components: + - type: Transform + pos: 2.5,7.5 + parent: 1 + - uid: 301 + components: + - type: Transform + pos: 6.5,7.5 + parent: 1 +- proto: WoodDoor + entities: + - uid: 87 + components: + - type: MetaData + name: Bedroom + - type: Transform + pos: 4.5,2.5 + parent: 1 +... diff --git a/Resources/Maps/Ruins/DeltaV/biodome_satellite.yml b/Resources/Maps/Ruins/DeltaV/biodome_satellite.yml index 02d55a3cf0..4631b1aad8 100644 --- a/Resources/Maps/Ruins/DeltaV/biodome_satellite.yml +++ b/Resources/Maps/Ruins/DeltaV/biodome_satellite.yml @@ -73,6 +73,10 @@ entities: - type: Gravity gravityShakeSound: !type:SoundPathSpecifier path: /Audio/Effects/alert.ogg + - type: DeviceNetwork + configurators: [] + deviceLists: [] + transmitFrequencyId: ShuttleTimer - type: DecalGrid chunkCollection: version: 2 diff --git a/Resources/Maps/Ruins/DeltaV/old_ai_sat.yml b/Resources/Maps/Ruins/DeltaV/old_ai_sat.yml index 2b4110b781..3d0ef0b73b 100644 --- a/Resources/Maps/Ruins/DeltaV/old_ai_sat.yml +++ b/Resources/Maps/Ruins/DeltaV/old_ai_sat.yml @@ -81,6 +81,10 @@ entities: - type: Gravity gravityShakeSound: !type:SoundPathSpecifier path: /Audio/Effects/alert.ogg + - type: DeviceNetwork + configurators: [] + deviceLists: [] + transmitFrequencyId: ShuttleTimer - type: DecalGrid chunkCollection: version: 2 diff --git a/Resources/Maps/Salvage/DeltaV/DV-atlas-conference-room.yml b/Resources/Maps/Salvage/DeltaV/DV-atlas-conference-room.yml index c01a214ca5..4664f07f40 100644 --- a/Resources/Maps/Salvage/DeltaV/DV-atlas-conference-room.yml +++ b/Resources/Maps/Salvage/DeltaV/DV-atlas-conference-room.yml @@ -55,6 +55,10 @@ entities: - type: Gravity gravityShakeSound: !type:SoundPathSpecifier path: /Audio/Effects/alert.ogg + - type: DeviceNetwork + configurators: [] + deviceLists: [] + transmitFrequencyId: ShuttleTimer - type: DecalGrid chunkCollection: version: 2 diff --git a/Resources/Maps/Salvage/small-3.yml b/Resources/Maps/Salvage/small-3.yml index 7a29b665ff..cc015fe27b 100644 --- a/Resources/Maps/Salvage/small-3.yml +++ b/Resources/Maps/Salvage/small-3.yml @@ -926,176 +926,132 @@ entities: rot: 3.141592653589793 rad pos: 1.5,-10.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 4 components: - type: Transform rot: 3.141592653589793 rad pos: -6.5,5.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 6 components: - type: Transform rot: 3.141592653589793 rad pos: 0.5,6.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 7 components: - type: Transform rot: -1.5707963267948966 rad pos: -5.5,8.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 14 components: - type: Transform rot: -1.5707963267948966 rad pos: -0.5,6.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 17 components: - type: Transform rot: -1.5707963267948966 rad pos: 3.5,6.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 24 components: - type: Transform rot: -1.5707963267948966 rad pos: 3.5,5.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 25 components: - type: Transform rot: 3.141592653589793 rad pos: 3.5,7.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 26 components: - type: Transform rot: 3.141592653589793 rad pos: 1.5,7.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 29 components: - type: Transform rot: 3.141592653589793 rad pos: -4.5,5.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 30 components: - type: Transform rot: -1.5707963267948966 rad pos: -7.5,6.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 31 components: - type: Transform rot: 3.141592653589793 rad pos: 2.5,7.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 37 components: - type: Transform rot: -1.5707963267948966 rad pos: 4.5,8.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 38 components: - type: Transform rot: 3.141592653589793 rad pos: 0.5,7.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 44 components: - type: Transform rot: -1.5707963267948966 rad pos: -3.5,6.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 48 components: - type: Transform rot: -1.5707963267948966 rad pos: -3.5,7.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 49 components: - type: Transform rot: -1.5707963267948966 rad pos: -6.5,6.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 50 components: - type: Transform rot: 3.141592653589793 rad pos: -5.5,5.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 51 components: - type: Transform rot: -1.5707963267948966 rad pos: -7.5,4.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 52 components: - type: Transform rot: -1.5707963267948966 rad pos: -7.5,5.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 53 components: - type: Transform rot: 3.141592653589793 rad pos: -7.5,4.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 66 components: - type: Transform rot: -1.5707963267948966 rad pos: -2.5,7.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 75 components: - type: Transform @@ -1108,8 +1064,6 @@ entities: rot: -1.5707963267948966 rad pos: -2.5,5.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 78 components: - type: Transform @@ -1122,144 +1076,108 @@ entities: rot: -1.5707963267948966 rad pos: -3.5,5.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 80 components: - type: Transform rot: -1.5707963267948966 rad pos: 3.5,7.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 97 components: - type: Transform rot: 3.141592653589793 rad pos: -6.5,7.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 101 components: - type: Transform rot: 3.141592653589793 rad pos: -7.5,7.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 105 components: - type: Transform rot: 3.141592653589793 rad pos: -0.5,6.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 106 components: - type: Transform rot: -1.5707963267948966 rad pos: -2.5,6.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 107 components: - type: Transform rot: 3.141592653589793 rad pos: 1.5,6.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 108 components: - type: Transform rot: -1.5707963267948966 rad pos: -1.5,8.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 110 components: - type: Transform rot: 3.141592653589793 rad pos: -2.5,7.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 112 components: - type: Transform rot: -1.5707963267948966 rad pos: -5.5,7.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 115 components: - type: Transform rot: -1.5707963267948966 rad pos: -3.5,8.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 116 components: - type: Transform rot: -1.5707963267948966 rad pos: -4.5,7.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 117 components: - type: Transform rot: -1.5707963267948966 rad pos: -4.5,6.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 118 components: - type: Transform rot: -1.5707963267948966 rad pos: -0.5,7.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 119 components: - type: Transform rot: -1.5707963267948966 rad pos: -7.5,7.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 120 components: - type: Transform rot: -1.5707963267948966 rad pos: 1.5,6.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 121 components: - type: Transform rot: 3.141592653589793 rad pos: 0.5,5.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 122 components: - type: Transform rot: -1.5707963267948966 rad pos: 0.5,5.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 123 components: - type: Transform @@ -1278,48 +1196,36 @@ entities: rot: -1.5707963267948966 rad pos: -1.5,5.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 126 components: - type: Transform rot: -1.5707963267948966 rad pos: -1.5,6.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 127 components: - type: Transform rot: 3.141592653589793 rad pos: -6.5,3.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 128 components: - type: Transform rot: 3.141592653589793 rad pos: -5.5,3.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 129 components: - type: Transform rot: 3.141592653589793 rad pos: -4.5,3.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 130 components: - type: Transform rot: 3.141592653589793 rad pos: -3.5,3.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 131 components: - type: Transform @@ -1332,88 +1238,66 @@ entities: rot: 3.141592653589793 rad pos: -1.5,3.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 133 components: - type: Transform rot: 3.141592653589793 rad pos: -2.5,3.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 134 components: - type: Transform rot: -1.5707963267948966 rad pos: -0.5,3.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 135 components: - type: Transform rot: -1.5707963267948966 rad pos: -0.5,2.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 136 components: - type: Transform rot: 3.141592653589793 rad pos: -0.5,2.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 137 components: - type: Transform rot: 3.141592653589793 rad pos: -0.5,1.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 138 components: - type: Transform rot: 3.141592653589793 rad pos: 0.5,2.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 139 components: - type: Transform rot: 3.141592653589793 rad pos: 1.5,2.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 140 components: - type: Transform rot: 3.141592653589793 rad pos: 0.5,3.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 141 components: - type: Transform rot: 3.141592653589793 rad pos: 1.5,3.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 142 components: - type: Transform rot: 3.141592653589793 rad pos: 2.5,3.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 143 components: - type: Transform @@ -1444,40 +1328,30 @@ entities: rot: -1.5707963267948966 rad pos: 2.5,5.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 148 components: - type: Transform rot: 3.141592653589793 rad pos: 2.5,2.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 149 components: - type: Transform rot: 3.141592653589793 rad pos: 3.5,2.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 150 components: - type: Transform rot: -1.5707963267948966 rad pos: 4.5,3.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 151 components: - type: Transform rot: 3.141592653589793 rad pos: 4.5,3.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 152 components: - type: Transform @@ -1490,112 +1364,84 @@ entities: rot: -1.5707963267948966 rad pos: 5.5,5.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 154 components: - type: Transform rot: 3.141592653589793 rad pos: 4.5,5.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 155 components: - type: Transform rot: -1.5707963267948966 rad pos: 4.5,6.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 156 components: - type: Transform rot: 3.141592653589793 rad pos: 4.5,6.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 157 components: - type: Transform rot: -1.5707963267948966 rad pos: 5.5,7.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 158 components: - type: Transform rot: 3.141592653589793 rad pos: 5.5,7.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 159 components: - type: Transform rot: 3.141592653589793 rad pos: 6.5,7.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 160 components: - type: Transform rot: -1.5707963267948966 rad pos: 8.5,8.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 161 components: - type: Transform rot: -1.5707963267948966 rad pos: 8.5,7.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 162 components: - type: Transform rot: 3.141592653589793 rad pos: 7.5,6.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 163 components: - type: Transform rot: 3.141592653589793 rad pos: 6.5,6.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 164 components: - type: Transform rot: -1.5707963267948966 rad pos: 6.5,6.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 165 components: - type: Transform rot: 3.141592653589793 rad pos: 6.5,5.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 166 components: - type: Transform rot: -1.5707963267948966 rad pos: 6.5,5.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 167 components: - type: Transform @@ -1614,56 +1460,42 @@ entities: rot: -1.5707963267948966 rad pos: 8.5,5.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 170 components: - type: Transform rot: -1.5707963267948966 rad pos: 8.5,6.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 171 components: - type: Transform rot: 3.141592653589793 rad pos: 9.5,7.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 172 components: - type: Transform rot: -1.5707963267948966 rad pos: 10.5,6.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 173 components: - type: Transform rot: -1.5707963267948966 rad pos: 10.5,7.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 174 components: - type: Transform rot: -1.5707963267948966 rad pos: 9.5,6.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 175 components: - type: Transform rot: -1.5707963267948966 rad pos: 9.5,7.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 176 components: - type: Transform @@ -1676,8 +1508,6 @@ entities: rot: -1.5707963267948966 rad pos: 9.5,5.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 178 components: - type: Transform @@ -1690,1104 +1520,828 @@ entities: rot: 3.141592653589793 rad pos: 9.5,3.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 180 components: - type: Transform rot: -1.5707963267948966 rad pos: 10.5,3.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 181 components: - type: Transform rot: 3.141592653589793 rad pos: 8.5,3.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 182 components: - type: Transform rot: 3.141592653589793 rad pos: 7.5,3.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 183 components: - type: Transform rot: 3.141592653589793 rad pos: 6.5,3.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 184 components: - type: Transform rot: 3.141592653589793 rad pos: 10.5,1.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 185 components: - type: Transform rot: 3.141592653589793 rad pos: 9.5,1.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 186 components: - type: Transform rot: -1.5707963267948966 rad pos: 9.5,2.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 187 components: - type: Transform rot: 3.141592653589793 rad pos: 8.5,2.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 188 components: - type: Transform rot: -1.5707963267948966 rad pos: 7.5,3.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 189 components: - type: Transform rot: -1.5707963267948966 rad pos: 7.5,2.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 190 components: - type: Transform rot: 3.141592653589793 rad pos: 7.5,1.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 191 components: - type: Transform rot: -1.5707963267948966 rad pos: 7.5,1.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 192 components: - type: Transform rot: -1.5707963267948966 rad pos: 7.5,0.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 193 components: - type: Transform rot: -1.5707963267948966 rad pos: 10.5,0.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 194 components: - type: Transform rot: -1.5707963267948966 rad pos: 10.5,-0.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 195 components: - type: Transform rot: -1.5707963267948966 rad pos: 10.5,-1.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 196 components: - type: Transform rot: 3.141592653589793 rad pos: 10.5,-2.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 197 components: - type: Transform rot: -1.5707963267948966 rad pos: 9.5,-1.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 198 components: - type: Transform rot: -1.5707963267948966 rad pos: 9.5,-0.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 199 components: - type: Transform rot: -1.5707963267948966 rad pos: 9.5,0.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 200 components: - type: Transform rot: 3.141592653589793 rad pos: 8.5,-2.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 201 components: - type: Transform rot: 3.141592653589793 rad pos: 8.5,0.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 202 components: - type: Transform rot: -1.5707963267948966 rad pos: 8.5,-0.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 203 components: - type: Transform rot: 3.141592653589793 rad pos: 7.5,-1.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 204 components: - type: Transform rot: -1.5707963267948966 rad pos: 8.5,0.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 205 components: - type: Transform rot: 3.141592653589793 rad pos: 6.5,-1.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 206 components: - type: Transform rot: -1.5707963267948966 rad pos: 7.5,-1.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 207 components: - type: Transform rot: -1.5707963267948966 rad pos: 6.5,-0.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 208 components: - type: Transform rot: -1.5707963267948966 rad pos: 6.5,0.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 209 components: - type: Transform rot: -1.5707963267948966 rad pos: 6.5,1.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 210 components: - type: Transform rot: -1.5707963267948966 rad pos: 6.5,2.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 211 components: - type: Transform rot: -1.5707963267948966 rad pos: 6.5,3.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 212 components: - type: Transform rot: 3.141592653589793 rad pos: 5.5,2.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 213 components: - type: Transform rot: 3.141592653589793 rad pos: 5.5,0.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 214 components: - type: Transform rot: 3.141592653589793 rad pos: 4.5,1.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 215 components: - type: Transform rot: -1.5707963267948966 rad pos: 4.5,1.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 216 components: - type: Transform rot: -1.5707963267948966 rad pos: 4.5,0.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 217 components: - type: Transform rot: 3.141592653589793 rad pos: 4.5,-0.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 218 components: - type: Transform rot: -1.5707963267948966 rad pos: 5.5,-0.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 219 components: - type: Transform rot: -1.5707963267948966 rad pos: 5.5,-1.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 220 components: - type: Transform rot: 3.141592653589793 rad pos: 5.5,-2.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 221 components: - type: Transform rot: -1.5707963267948966 rad pos: 6.5,-2.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 222 components: - type: Transform rot: 3.141592653589793 rad pos: 6.5,-3.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 223 components: - type: Transform rot: 3.141592653589793 rad pos: 7.5,-3.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 224 components: - type: Transform rot: 3.141592653589793 rad pos: 8.5,-3.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 225 components: - type: Transform rot: 3.141592653589793 rad pos: 9.5,-3.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 226 components: - type: Transform rot: 3.141592653589793 rad pos: -8.5,2.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 227 components: - type: Transform rot: 3.141592653589793 rad pos: -7.5,2.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 228 components: - type: Transform rot: 3.141592653589793 rad pos: -6.5,2.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 229 components: - type: Transform rot: 3.141592653589793 rad pos: -5.5,2.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 230 components: - type: Transform rot: -1.5707963267948966 rad pos: -4.5,2.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 231 components: - type: Transform rot: -1.5707963267948966 rad pos: -4.5,1.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 232 components: - type: Transform rot: -1.5707963267948966 rad pos: -4.5,0.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 233 components: - type: Transform rot: 3.141592653589793 rad pos: -4.5,0.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 234 components: - type: Transform rot: 3.141592653589793 rad pos: -3.5,0.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 235 components: - type: Transform rot: -1.5707963267948966 rad pos: -2.5,1.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 236 components: - type: Transform rot: -1.5707963267948966 rad pos: -2.5,0.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 237 components: - type: Transform rot: -1.5707963267948966 rad pos: -3.5,2.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 238 components: - type: Transform rot: 3.141592653589793 rad pos: -3.5,2.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 239 components: - type: Transform rot: 3.141592653589793 rad pos: -2.5,2.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 240 components: - type: Transform rot: -1.5707963267948966 rad pos: -1.5,2.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 241 components: - type: Transform rot: -1.5707963267948966 rad pos: -1.5,1.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 242 components: - type: Transform rot: -1.5707963267948966 rad pos: -1.5,0.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 243 components: - type: Transform rot: -1.5707963267948966 rad pos: -1.5,-0.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 244 components: - type: Transform rot: 3.141592653589793 rad pos: -2.5,-1.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 245 components: - type: Transform rot: 3.141592653589793 rad pos: -3.5,-1.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 246 components: - type: Transform rot: -1.5707963267948966 rad pos: -3.5,-0.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 247 components: - type: Transform rot: 3.141592653589793 rad pos: -4.5,-0.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 248 components: - type: Transform rot: 3.141592653589793 rad pos: -0.5,0.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 249 components: - type: Transform rot: -1.5707963267948966 rad pos: -0.5,0.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 250 components: - type: Transform rot: -1.5707963267948966 rad pos: -0.5,-0.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 251 components: - type: Transform rot: -1.5707963267948966 rad pos: -0.5,-1.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 252 components: - type: Transform rot: 3.141592653589793 rad pos: -1.5,-2.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 253 components: - type: Transform rot: -1.5707963267948966 rad pos: -1.5,-1.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 254 components: - type: Transform rot: 3.141592653589793 rad pos: -7.5,1.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 255 components: - type: Transform rot: 3.141592653589793 rad pos: -6.5,1.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 256 components: - type: Transform rot: -1.5707963267948966 rad pos: -5.5,1.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 257 components: - type: Transform rot: 3.141592653589793 rad pos: -6.5,0.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 258 components: - type: Transform rot: -1.5707963267948966 rad pos: -6.5,0.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 259 components: - type: Transform rot: 3.141592653589793 rad pos: -7.5,-0.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 260 components: - type: Transform rot: -1.5707963267948966 rad pos: -7.5,-0.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 261 components: - type: Transform rot: 3.141592653589793 rad pos: -8.5,0.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 262 components: - type: Transform rot: -1.5707963267948966 rad pos: -7.5,-1.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 263 components: - type: Transform rot: -1.5707963267948966 rad pos: -7.5,-2.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 264 components: - type: Transform rot: -1.5707963267948966 rad pos: -7.5,-3.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 265 components: - type: Transform rot: 3.141592653589793 rad pos: -5.5,-0.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 266 components: - type: Transform rot: -1.5707963267948966 rad pos: -5.5,-0.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 267 components: - type: Transform rot: -1.5707963267948966 rad pos: -5.5,-1.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 268 components: - type: Transform rot: 3.141592653589793 rad pos: -6.5,-2.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 269 components: - type: Transform rot: 3.141592653589793 rad pos: -6.5,-1.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 270 components: - type: Transform rot: 3.141592653589793 rad pos: -7.5,-3.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 271 components: - type: Transform rot: 3.141592653589793 rad pos: -6.5,-3.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 272 components: - type: Transform rot: 3.141592653589793 rad pos: -5.5,-3.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 273 components: - type: Transform rot: -1.5707963267948966 rad pos: -4.5,-2.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 274 components: - type: Transform rot: -1.5707963267948966 rad pos: -4.5,-1.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 275 components: - type: Transform rot: 3.141592653589793 rad pos: -4.5,-2.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 276 components: - type: Transform rot: 3.141592653589793 rad pos: -3.5,-2.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 277 components: - type: Transform rot: -1.5707963267948966 rad pos: -2.5,-2.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 278 components: - type: Transform rot: 3.141592653589793 rad pos: -3.5,-3.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 279 components: - type: Transform rot: 3.141592653589793 rad pos: -2.5,-3.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 280 components: - type: Transform rot: 3.141592653589793 rad pos: -1.5,-3.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 281 components: - type: Transform rot: 3.141592653589793 rad pos: -0.5,-2.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 282 components: - type: Transform rot: 3.141592653589793 rad pos: 0.5,-2.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 283 components: - type: Transform rot: -1.5707963267948966 rad pos: 0.5,-2.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 284 components: - type: Transform rot: -1.5707963267948966 rad pos: 0.5,-3.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 285 components: - type: Transform rot: 3.141592653589793 rad pos: -0.5,-4.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 286 components: - type: Transform rot: -1.5707963267948966 rad pos: 0.5,-0.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 287 components: - type: Transform rot: 3.141592653589793 rad pos: 0.5,-1.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 288 components: - type: Transform rot: -1.5707963267948966 rad pos: 1.5,-0.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 289 components: - type: Transform rot: -1.5707963267948966 rad pos: 1.5,0.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 290 components: - type: Transform rot: -1.5707963267948966 rad pos: 1.5,1.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 291 components: - type: Transform rot: -1.5707963267948966 rad pos: 1.5,2.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 292 components: - type: Transform rot: -1.5707963267948966 rad pos: 2.5,0.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 293 components: - type: Transform rot: -1.5707963267948966 rad pos: 2.5,1.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 294 components: - type: Transform rot: 3.141592653589793 rad pos: 2.5,-0.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 295 components: - type: Transform rot: 3.141592653589793 rad pos: 3.5,-0.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 296 components: - type: Transform rot: -1.5707963267948966 rad pos: 3.5,1.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 297 components: - type: Transform rot: -1.5707963267948966 rad pos: 3.5,2.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 298 components: - type: Transform rot: 3.141592653589793 rad pos: 1.5,-1.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 299 components: - type: Transform rot: -1.5707963267948966 rad pos: 2.5,-1.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 300 components: - type: Transform rot: -1.5707963267948966 rad pos: 2.5,-2.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 301 components: - type: Transform rot: 3.141592653589793 rad pos: 1.5,-3.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 302 components: - type: Transform rot: -1.5707963267948966 rad pos: 1.5,-3.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 303 components: - type: Transform rot: -1.5707963267948966 rad pos: 1.5,-4.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 304 components: - type: Transform rot: 3.141592653589793 rad pos: 2.5,-4.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 305 components: - type: Transform rot: -1.5707963267948966 rad pos: 3.5,-4.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 306 components: - type: Transform rot: -1.5707963267948966 rad pos: 3.5,-3.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 307 components: - type: Transform rot: -1.5707963267948966 rad pos: 3.5,-2.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 308 components: - type: Transform rot: -1.5707963267948966 rad pos: 3.5,-1.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 309 components: - type: Transform rot: -1.5707963267948966 rad pos: 3.5,-0.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 310 components: - type: Transform rot: 3.141592653589793 rad pos: 3.5,-5.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 311 components: - type: Transform rot: 3.141592653589793 rad pos: 4.5,-5.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 312 components: - type: Transform rot: 3.141592653589793 rad pos: 5.5,-5.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 313 components: - type: Transform rot: -1.5707963267948966 rad pos: 5.5,-5.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 314 components: - type: Transform rot: 3.141592653589793 rad pos: 4.5,-4.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 315 components: - type: Transform rot: -1.5707963267948966 rad pos: 4.5,-3.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 316 components: - type: Transform rot: -1.5707963267948966 rad pos: 4.5,-2.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 317 components: - type: Transform @@ -2800,982 +2354,736 @@ entities: rot: 3.141592653589793 rad pos: 4.5,-3.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 319 components: - type: Transform rot: 3.141592653589793 rad pos: 5.5,-3.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 320 components: - type: Transform rot: -1.5707963267948966 rad pos: 6.5,-4.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 321 components: - type: Transform rot: 3.141592653589793 rad pos: 6.5,-4.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 322 components: - type: Transform rot: 3.141592653589793 rad pos: 7.5,-4.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 323 components: - type: Transform rot: 3.141592653589793 rad pos: 8.5,-4.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 324 components: - type: Transform rot: 3.141592653589793 rad pos: 9.5,-4.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 325 components: - type: Transform rot: 3.141592653589793 rad pos: 10.5,-5.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 326 components: - type: Transform rot: -1.5707963267948966 rad pos: 9.5,-4.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 327 components: - type: Transform rot: -1.5707963267948966 rad pos: 9.5,-5.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 328 components: - type: Transform rot: 3.141592653589793 rad pos: 9.5,-6.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 329 components: - type: Transform rot: 3.141592653589793 rad pos: 8.5,-6.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 330 components: - type: Transform rot: -1.5707963267948966 rad pos: 8.5,-6.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 331 components: - type: Transform rot: -1.5707963267948966 rad pos: 8.5,-7.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 332 components: - type: Transform rot: -1.5707963267948966 rad pos: 8.5,-8.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 333 components: - type: Transform rot: -1.5707963267948966 rad pos: 9.5,-7.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 334 components: - type: Transform rot: 3.141592653589793 rad pos: 9.5,-7.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 335 components: - type: Transform rot: 3.141592653589793 rad pos: 10.5,-7.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 336 components: - type: Transform rot: -1.5707963267948966 rad pos: 10.5,-8.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 337 components: - type: Transform rot: 3.141592653589793 rad pos: 9.5,-9.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 338 components: - type: Transform rot: 3.141592653589793 rad pos: 8.5,-9.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 339 components: - type: Transform rot: -1.5707963267948966 rad pos: 8.5,-9.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 340 components: - type: Transform rot: 3.141592653589793 rad pos: 10.5,-10.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 341 components: - type: Transform rot: -1.5707963267948966 rad pos: 9.5,-10.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 342 components: - type: Transform rot: -1.5707963267948966 rad pos: 7.5,-10.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 343 components: - type: Transform rot: 3.141592653589793 rad pos: 7.5,-9.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 344 components: - type: Transform rot: 3.141592653589793 rad pos: 6.5,-9.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 345 components: - type: Transform rot: -1.5707963267948966 rad pos: 6.5,-9.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 346 components: - type: Transform rot: 3.141592653589793 rad pos: 5.5,-10.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 347 components: - type: Transform rot: 3.141592653589793 rad pos: 4.5,-10.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 348 components: - type: Transform rot: 3.141592653589793 rad pos: 3.5,-10.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 349 components: - type: Transform rot: -1.5707963267948966 rad pos: 3.5,-10.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 350 components: - type: Transform rot: -1.5707963267948966 rad pos: 1.5,-10.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 351 components: - type: Transform rot: -1.5707963267948966 rad pos: 2.5,-9.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 352 components: - type: Transform rot: 3.141592653589793 rad pos: 2.5,-9.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 353 components: - type: Transform rot: 3.141592653589793 rad pos: 3.5,-9.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 354 components: - type: Transform rot: 3.141592653589793 rad pos: 4.5,-9.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 355 components: - type: Transform rot: -1.5707963267948966 rad pos: 5.5,-8.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 356 components: - type: Transform rot: 3.141592653589793 rad pos: 5.5,-8.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 357 components: - type: Transform rot: 3.141592653589793 rad pos: 6.5,-8.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 358 components: - type: Transform rot: -1.5707963267948966 rad pos: 7.5,-7.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 359 components: - type: Transform rot: -1.5707963267948966 rad pos: 7.5,-6.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 360 components: - type: Transform rot: -1.5707963267948966 rad pos: 7.5,-5.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 361 components: - type: Transform rot: 3.141592653589793 rad pos: 7.5,-5.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 362 components: - type: Transform rot: 3.141592653589793 rad pos: 6.5,-6.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 363 components: - type: Transform rot: -1.5707963267948966 rad pos: 6.5,-6.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 364 components: - type: Transform rot: -1.5707963267948966 rad pos: 5.5,-7.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 365 components: - type: Transform rot: 3.141592653589793 rad pos: 4.5,-7.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 366 components: - type: Transform rot: -1.5707963267948966 rad pos: 4.5,-7.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 367 components: - type: Transform rot: 3.141592653589793 rad pos: 3.5,-8.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 368 components: - type: Transform rot: -1.5707963267948966 rad pos: 2.5,-8.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 369 components: - type: Transform rot: -1.5707963267948966 rad pos: 2.5,-7.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 370 components: - type: Transform rot: 3.141592653589793 rad pos: 2.5,-7.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 371 components: - type: Transform rot: -1.5707963267948966 rad pos: 4.5,-6.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 372 components: - type: Transform rot: 3.141592653589793 rad pos: 3.5,-6.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 373 components: - type: Transform rot: 3.141592653589793 rad pos: 2.5,-6.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 374 components: - type: Transform rot: -1.5707963267948966 rad pos: 2.5,-5.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 375 components: - type: Transform rot: 3.141592653589793 rad pos: 1.5,-5.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 376 components: - type: Transform rot: 3.141592653589793 rad pos: 0.5,-5.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 377 components: - type: Transform rot: 3.141592653589793 rad pos: -0.5,-5.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 378 components: - type: Transform rot: -1.5707963267948966 rad pos: -0.5,-5.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 379 components: - type: Transform rot: 3.141592653589793 rad pos: 0.5,-6.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 380 components: - type: Transform rot: -1.5707963267948966 rad pos: 1.5,-6.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 381 components: - type: Transform rot: -1.5707963267948966 rad pos: 1.5,-7.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 382 components: - type: Transform rot: -1.5707963267948966 rad pos: 1.5,-8.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 383 components: - type: Transform rot: -1.5707963267948966 rad pos: 1.5,-9.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 384 components: - type: Transform rot: 3.141592653589793 rad pos: -8.5,-10.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 385 components: - type: Transform rot: 3.141592653589793 rad pos: -7.5,-10.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 386 components: - type: Transform rot: -1.5707963267948966 rad pos: -7.5,-8.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 387 components: - type: Transform rot: 3.141592653589793 rad pos: -7.5,-9.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 388 components: - type: Transform rot: 3.141592653589793 rad pos: -6.5,-9.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 389 components: - type: Transform rot: -1.5707963267948966 rad pos: -5.5,-10.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 390 components: - type: Transform rot: -1.5707963267948966 rad pos: -5.5,-9.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 391 components: - type: Transform rot: -1.5707963267948966 rad pos: -5.5,-8.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 392 components: - type: Transform rot: -1.5707963267948966 rad pos: -6.5,-7.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 393 components: - type: Transform rot: 3.141592653589793 rad pos: -7.5,-7.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 394 components: - type: Transform rot: 3.141592653589793 rad pos: -6.5,-7.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 395 components: - type: Transform rot: 3.141592653589793 rad pos: -5.5,-7.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 396 components: - type: Transform rot: -1.5707963267948966 rad pos: -4.5,-7.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 397 components: - type: Transform rot: -1.5707963267948966 rad pos: -4.5,-8.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 398 components: - type: Transform rot: 3.141592653589793 rad pos: -4.5,-10.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 399 components: - type: Transform rot: 3.141592653589793 rad pos: -3.5,-10.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 400 components: - type: Transform rot: -1.5707963267948966 rad pos: -2.5,-10.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 401 components: - type: Transform rot: -1.5707963267948966 rad pos: -3.5,-9.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 402 components: - type: Transform rot: -1.5707963267948966 rad pos: -5.5,-6.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 403 components: - type: Transform rot: 3.141592653589793 rad pos: -6.5,-6.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 404 components: - type: Transform rot: 3.141592653589793 rad pos: -7.5,-6.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 405 components: - type: Transform rot: 3.141592653589793 rad pos: -8.5,-6.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 406 components: - type: Transform rot: -1.5707963267948966 rad pos: -6.5,-5.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 407 components: - type: Transform rot: 3.141592653589793 rad pos: -7.5,-5.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 408 components: - type: Transform rot: 3.141592653589793 rad pos: -7.5,-4.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 409 components: - type: Transform rot: 3.141592653589793 rad pos: -6.5,-4.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 410 components: - type: Transform rot: -1.5707963267948966 rad pos: -5.5,-4.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 411 components: - type: Transform rot: 3.141592653589793 rad pos: -5.5,-5.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 412 components: - type: Transform rot: -1.5707963267948966 rad pos: -4.5,-5.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 413 components: - type: Transform rot: 3.141592653589793 rad pos: -4.5,-5.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 414 components: - type: Transform rot: 3.141592653589793 rad pos: -3.5,-5.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 415 components: - type: Transform rot: -1.5707963267948966 rad pos: -2.5,-5.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 416 components: - type: Transform rot: 3.141592653589793 rad pos: -2.5,-6.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 417 components: - type: Transform rot: -1.5707963267948966 rad pos: -1.5,-6.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 418 components: - type: Transform rot: 3.141592653589793 rad pos: -1.5,-7.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 419 components: - type: Transform rot: 3.141592653589793 rad pos: -0.5,-7.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 420 components: - type: Transform rot: 3.141592653589793 rad pos: -4.5,-6.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 421 components: - type: Transform rot: -1.5707963267948966 rad pos: -3.5,-6.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 422 components: - type: Transform rot: -1.5707963267948966 rad pos: -3.5,-7.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 423 components: - type: Transform rot: 3.141592653589793 rad pos: -3.5,-8.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 424 components: - type: Transform rot: -1.5707963267948966 rad pos: -2.5,-8.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 425 components: - type: Transform rot: -1.5707963267948966 rad pos: -2.5,-7.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 426 components: - type: Transform rot: -1.5707963267948966 rad pos: -4.5,-3.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 427 components: - type: Transform rot: 3.141592653589793 rad pos: -4.5,-4.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 428 components: - type: Transform rot: 3.141592653589793 rad pos: -3.5,-4.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 429 components: - type: Transform rot: 3.141592653589793 rad pos: -2.5,-4.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 430 components: - type: Transform rot: -1.5707963267948966 rad pos: -1.5,-4.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 431 components: - type: Transform rot: 3.141592653589793 rad pos: -1.5,-5.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 432 components: - type: Transform rot: -1.5707963267948966 rad pos: 0.5,-9.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 433 components: - type: Transform rot: -1.5707963267948966 rad pos: 0.5,-8.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 434 components: - type: Transform rot: 3.141592653589793 rad pos: -0.5,-8.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 435 components: - type: Transform rot: 3.141592653589793 rad pos: -1.5,-8.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 436 components: - type: Transform rot: 3.141592653589793 rad pos: -2.5,-8.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 437 components: - type: Transform rot: 3.141592653589793 rad pos: -0.5,-10.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 438 components: - type: Transform rot: -1.5707963267948966 rad pos: -0.5,-9.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 439 components: - type: Transform rot: -1.5707963267948966 rad pos: -1.5,-9.5 parent: 10 - - type: Godmode - oldDamage: {} - uid: 440 components: - type: Transform rot: 3.141592653589793 rad pos: -2.5,-9.5 parent: 10 - - type: Godmode - oldDamage: {} ... diff --git a/Resources/Maps/Salvage/vegan-meatball.yml b/Resources/Maps/Salvage/vegan-meatball.yml index 824e3d0b7e..ab67aabadb 100644 --- a/Resources/Maps/Salvage/vegan-meatball.yml +++ b/Resources/Maps/Salvage/vegan-meatball.yml @@ -505,7 +505,7 @@ entities: chemicals: THC: Inherent: True - PotencyDivisor: 10 + potencyDivisor: 10 Max: 10 Min: 1 productPrototypes: @@ -578,7 +578,7 @@ entities: chemicals: Stellibinin: Inherent: True - PotencyDivisor: 4 + potencyDivisor: 4 Max: 25 Min: 1 productPrototypes: diff --git a/Resources/Maps/Shuttles/DeltaV/DV-pirateradio.yml b/Resources/Maps/Shuttles/DeltaV/DV-pirateradio.yml deleted file mode 100644 index 9cff388fc8..0000000000 --- a/Resources/Maps/Shuttles/DeltaV/DV-pirateradio.yml +++ /dev/null @@ -1,6441 +0,0 @@ -meta: - format: 6 - postmapinit: false -tilemap: - 0: Space - 7: FloorAsteroidSand - 25: FloorCave - 32: FloorDark - 38: FloorDarkOffset - 41: FloorDarkPlastic - 65: FloorLino - 67: FloorMetalDiamond - 80: FloorReinforced - 89: FloorShuttleRed - 90: FloorShuttleWhite - 96: FloorSteelCheckerDark - 109: FloorTechMaint - 123: FloorWood - 126: Lattice -entities: -- proto: "" - entities: - - uid: 1 - components: - - type: MetaData - name: unknown - - type: Transform - parent: invalid - - type: MapGrid - chunks: - 0,0: - ind: 0,0 - tiles: QwAAAAAAewAAAAADQQAAAAAAQQAAAAAAewAAAAAAQwAAAAAAQwAAAAAAQwAAAAAAKQAAAAAAJgAAAAAAJgAAAAAAJgAAAAAAKQAAAAADUAAAAAAAfgAAAAAAfgAAAAAAQwAAAAAAewAAAAAAQQAAAAAAQQAAAAAAewAAAAADQwAAAAAAWQAAAAAAQwAAAAAAKQAAAAABJgAAAAAAJgAAAAAAJgAAAAAAKQAAAAABUAAAAAAAfgAAAAAAfgAAAAAAQwAAAAAAewAAAAAAQQAAAAAAQQAAAAAAewAAAAABKQAAAAABWQAAAAAAQwAAAAAAKQAAAAADKQAAAAACKQAAAAACKQAAAAACKQAAAAACUAAAAAAAfgAAAAAAfgAAAAAAKQAAAAAAewAAAAADewAAAAABewAAAAAAewAAAAABQwAAAAAAWQAAAAAAQwAAAAAAQwAAAAAAQwAAAAAAQwAAAAAAQwAAAAAAQwAAAAAAQwAAAAAAGQAAAAABfgAAAAAAQwAAAAAAQwAAAAAAQwAAAAAAKQAAAAABQwAAAAAAQwAAAAAABwAAAAAABwAAAAAAGQAAAAAAGQAAAAABGQAAAAADGQAAAAADGQAAAAACGQAAAAABGQAAAAACGQAAAAAEQwAAAAAAbQAAAAAAQwAAAAAAbQAAAAAAbQAAAAAABwAAAAAAGQAAAAACGQAAAAAGGQAAAAACGQAAAAAEGQAAAAAFGQAAAAAEGQAAAAACGQAAAAAEGQAAAAABGQAAAAADQwAAAAAABwAAAAAAQwAAAAAAQwAAAAAAQwAAAAAABwAAAAAAfgAAAAAAGQAAAAADGQAAAAAAGQAAAAAGGQAAAAABGQAAAAABGQAAAAAEGQAAAAADGQAAAAAAGQAAAAADGQAAAAABGQAAAAAAGQAAAAACGQAAAAAEGQAAAAACGQAAAAAEGQAAAAACGQAAAAAEGQAAAAAAGQAAAAAFGQAAAAAEGQAAAAACGQAAAAAFGQAAAAAFGQAAAAAAGQAAAAAGGQAAAAABGQAAAAAEGQAAAAAAGQAAAAABGQAAAAAAGQAAAAAAGQAAAAABGQAAAAAGGQAAAAAAGQAAAAACGQAAAAAFGQAAAAAEGQAAAAAAAAAAAAAAAAAAAAAAGQAAAAAEGQAAAAADGQAAAAAFGQAAAAADGQAAAAABGQAAAAAGGQAAAAADGQAAAAAFGQAAAAABAAAAAAAAAAAAAAAAGQAAAAACGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAFGQAAAAAEGQAAAAAAGQAAAAABfgAAAAAAGQAAAAACGQAAAAAFGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAEGQAAAAACGQAAAAABGQAAAAAFGQAAAAAFGQAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAAGQAAAAAEAAAAAAAAGQAAAAAAGQAAAAAGAAAAAAAAGQAAAAACGQAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - version: 6 - 0,-1: - ind: 0,-1 - tiles: GQAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAAGQAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAGGQAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAGAAAAAAAAGQAAAAAGGQAAAAABAAAAAAAAAAAAAAAAGQAAAAABAAAAAAAAAAAAAAAAGQAAAAACAAAAAAAAAAAAAAAAGQAAAAABGQAAAAADAAAAAAAAAAAAAAAAGQAAAAACGQAAAAAFGQAAAAAEGQAAAAACGQAAAAABGQAAAAAAGQAAAAABGQAAAAACGQAAAAABGQAAAAABAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAACAAAAAAAAAAAAAAAAGQAAAAAEGQAAAAADGQAAAAAGGQAAAAADGQAAAAABGQAAAAAAGQAAAAAFGQAAAAACGQAAAAABGQAAAAACGQAAAAAFAAAAAAAAGQAAAAACGQAAAAAAAAAAAAAAAAAAAAAAGQAAAAAFGQAAAAAEGQAAAAAAGQAAAAAFfgAAAAAAGQAAAAABGQAAAAACGQAAAAADGQAAAAAGGQAAAAADGQAAAAAGGQAAAAAEGQAAAAADAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAGGQAAAAAFGQAAAAADGQAAAAAAGQAAAAAFGQAAAAAFGQAAAAAGGQAAAAAGGQAAAAABGQAAAAAEGQAAAAAEGQAAAAAEGQAAAAAFGQAAAAAFGQAAAAAEAAAAAAAAQwAAAAAAQwAAAAAAQwAAAAAAQwAAAAAAGQAAAAABGQAAAAAEGQAAAAABGQAAAAAGGQAAAAABGQAAAAAAGQAAAAACGQAAAAABGQAAAAAEGQAAAAABGQAAAAAEGQAAAAAAWQAAAAAAQwAAAAAAQwAAAAAAQwAAAAAAWQAAAAAAQwAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAGQAAAAAEGQAAAAADGQAAAAAGGQAAAAADGQAAAAAGGQAAAAACGQAAAAABWQAAAAAAWQAAAAAAWQAAAAAAWQAAAAAAWQAAAAAAQwAAAAAAUAAAAAAAUAAAAAAAUAAAAAAAQwAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAGQAAAAAEGQAAAAABQwAAAAAAQwAAAAAAKQAAAAACQwAAAAAAQwAAAAAAQwAAAAAAewAAAAAAewAAAAACewAAAAACQwAAAAAAUAAAAAAAUAAAAAAAUAAAAAAAUAAAAAAAQwAAAAAAGQAAAAABQwAAAAAAYAAAAAACYAAAAAAAYAAAAAADYAAAAAAAQwAAAAAAewAAAAACewAAAAADewAAAAABQwAAAAAAKQAAAAAAKQAAAAABKQAAAAADKQAAAAAAQwAAAAAAGQAAAAAGQwAAAAAAYAAAAAACYAAAAAACYAAAAAAAYAAAAAABQwAAAAAAewAAAAACewAAAAABewAAAAADKQAAAAACKQAAAAAAKQAAAAACKQAAAAADKQAAAAADQwAAAAAAGQAAAAAGQwAAAAAAYAAAAAACYAAAAAADYAAAAAACYAAAAAACQwAAAAAAewAAAAACewAAAAACewAAAAAAQwAAAAAAKQAAAAADKQAAAAAAKQAAAAABKQAAAAABQwAAAAAAGQAAAAABQwAAAAAAQwAAAAAAKQAAAAACQwAAAAAAQwAAAAAAQwAAAAAAKQAAAAAAQwAAAAAAQwAAAAAAQwAAAAAAUAAAAAAAQwAAAAAAQwAAAAAAQwAAAAAAQwAAAAAAGQAAAAADKQAAAAACewAAAAABewAAAAABewAAAAACewAAAAADKQAAAAAAKQAAAAACKQAAAAABKQAAAAACKQAAAAABKQAAAAACKQAAAAACKQAAAAAAUAAAAAAAfgAAAAAAfgAAAAAA - version: 6 - -1,0: - ind: -1,0 - tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAADAAAAAAAAGQAAAAACGQAAAAAFGQAAAAAEGQAAAAADQwAAAAAAWQAAAAAAWQAAAAAAWQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAGGQAAAAACGQAAAAABGQAAAAAAfgAAAAAAGQAAAAADQwAAAAAAQwAAAAAAWQAAAAAAWQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAEGQAAAAAAGQAAAAAFGQAAAAAEQwAAAAAAQwAAAAAAQwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAAGQAAAAAAGQAAAAAGGQAAAAABGQAAAAAAGQAAAAAFQwAAAAAAbQAAAAAAbQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAFGQAAAAAAGQAAAAAEAAAAAAAAGQAAAAABGQAAAAAFGQAAAAAFGQAAAAADQwAAAAAAbQAAAAAAbQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAABAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAADGQAAAAAFfgAAAAAAGQAAAAACQwAAAAAAbQAAAAAAbQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAACGQAAAAAAGQAAAAAEGQAAAAACGQAAAAAAGQAAAAAAQwAAAAAAQwAAAAAAQwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAEGQAAAAACGQAAAAAGGQAAAAAGGQAAAAAEGQAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAGGQAAAAAFGQAAAAABGQAAAAAFGQAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAADGQAAAAABGQAAAAAAGQAAAAACGQAAAAAFGQAAAAABGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAEGQAAAAACGQAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - version: 6 - -1,-1: - ind: -1,-1 - tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAEAAAAAAAAAAAAAAAAGQAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAGGQAAAAACAAAAAAAAGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAADGQAAAAADAAAAAAAAAAAAAAAAGQAAAAACGQAAAAACGQAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAABAAAAAAAAGQAAAAADGQAAAAABGQAAAAADGQAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAABGQAAAAAGGQAAAAAAGQAAAAAFGQAAAAABGQAAAAADGQAAAAAGGQAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAEGQAAAAAGGQAAAAAEGQAAAAAAGQAAAAAAGQAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAEGQAAAAADGQAAAAAFGQAAAAACQwAAAAAAQwAAAAAAQwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAEGQAAAAAAGQAAAAAAGQAAAAAAQwAAAAAAIAAAAAADIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAEGQAAAAACGQAAAAAAGQAAAAAFGQAAAAABQwAAAAAAWQAAAAAAWQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAACGQAAAAAAGQAAAAAAGQAAAAADGQAAAAACGQAAAAAGGQAAAAAEQwAAAAAAWQAAAAAAWQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAADGQAAAAABGQAAAAACGQAAAAABGQAAAAAEQwAAAAAAQwAAAAAAQwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAGGQAAAAAGfgAAAAAAGQAAAAAAQwAAAAAAWgAAAAAAWgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAAGQAAAAAFGQAAAAABGQAAAAABGQAAAAACGQAAAAABGQAAAAAEQwAAAAAAWgAAAAAAWgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAGAAAAAAAAAAAAAAAAGQAAAAAAGQAAAAAAGQAAAAADGQAAAAAFQwAAAAAAKQAAAAACQwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAEGQAAAAAEGQAAAAABQwAAAAAAQwAAAAAAWQAAAAAAWQAAAAAA - version: 6 - 1,-1: - ind: 1,-1 - tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAEGQAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAEGQAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAADGQAAAAAFAAAAAAAAGQAAAAABGQAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAGGQAAAAABGQAAAAACGQAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAAGQAAAAAGGQAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAADGQAAAAAGGQAAAAABAAAAAAAAAAAAAAAAGQAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAEGQAAAAAGGQAAAAACAAAAAAAAAAAAAAAAGQAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAFGQAAAAAFGQAAAAAAGQAAAAAAGQAAAAAAGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAGGQAAAAADGQAAAAAFGQAAAAABGQAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAACGQAAAAABGQAAAAADGQAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - version: 6 - 1,0: - ind: 1,0 - tiles: GQAAAAADGQAAAAABGQAAAAADGQAAAAADGQAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAAGQAAAAAAGQAAAAAEGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAACGQAAAAAAGQAAAAAGGQAAAAADGQAAAAACGQAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAADGQAAAAACGQAAAAABGQAAAAABGQAAAAAFGQAAAAAFGQAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAACGQAAAAAEGQAAAAADGQAAAAAAGQAAAAAAGQAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAEGQAAAAAAGQAAAAAGGQAAAAAFGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAAAAAAAAAAAAAAAAAAGQAAAAADGQAAAAADGQAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAGGQAAAAAAGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAABAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAEGQAAAAAAGQAAAAAGGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAFAAAAAAAAGQAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - version: 6 - - type: Broadphase - - type: Physics - bodyStatus: InAir - angularDamping: 0.05 - linearDamping: 0.05 - fixedRotation: False - bodyType: Dynamic - - type: Fixtures - fixtures: {} - - type: OccluderTree - - type: SpreaderGrid - - type: GridPathfinding - - type: Gravity - gravityShakeSound: !type:SoundPathSpecifier - path: /Audio/Effects/alert.ogg - - type: DecalGrid - chunkCollection: - version: 2 - nodes: - - node: - color: '#FFFFFFFF' - id: Caution - decals: - 0: 3.018731,5.165427 - - node: - color: '#FFFFFFFF' - id: WarnBox - decals: - 18: 3,4 - - node: - color: '#FFFFFFFF' - id: WarnCornerSmallNW - decals: - 40: 16,-2 - - node: - color: '#FFFFFFFF' - id: WarnCornerSmallSE - decals: - 32: 5,7 - - node: - color: '#FFFFFFFF' - id: WarnCornerSmallSW - decals: - 17: 4,7 - - node: - color: '#FFFFFFFF' - id: WarnLineN - decals: - 16: 3,7 - 33: 6,7 - 35: 5,7 - 36: 4,7 - - node: - color: '#FFFFFFFF' - id: WarnLineS - decals: - 34: 7,6 - 37: 16,3 - 38: 16,2 - 39: 16,-1 - - node: - color: '#FFFFFFFF' - id: WarnLineW - decals: - 41: 11,-8 - 42: 10,-8 - 43: 7,-9 - 44: 6,-9 - - node: - color: '#FFFFFFFF' - id: WoodTrimThinCornerNe - decals: - 2: 12,2 - - node: - color: '#FFFFFFFF' - id: WoodTrimThinCornerNw - decals: - 5: 8,2 - - node: - color: '#FFFFFFFF' - id: WoodTrimThinCornerSe - decals: - 11: 12,-1 - - node: - color: '#FFFFFFFF' - id: WoodTrimThinCornerSw - decals: - 12: 8,-1 - - node: - color: '#FFFFFFFF' - id: WoodTrimThinInnerNe - decals: - 28: 1,-1 - - node: - color: '#FFFFFFFF' - id: WoodTrimThinInnerNw - decals: - 29: 4,-1 - - node: - color: '#FFFFFFFF' - id: WoodTrimThinInnerSe - decals: - 30: 1,3 - - node: - color: '#FFFFFFFF' - id: WoodTrimThinInnerSw - decals: - 31: 4,3 - - node: - color: '#FFFFFFFF' - id: WoodTrimThinLineE - decals: - 9: 12,1 - 10: 12,0 - 22: 1,2 - 23: 1,1 - 24: 1,0 - - node: - color: '#FFFFFFFF' - id: WoodTrimThinLineN - decals: - 6: 9,2 - 7: 10,2 - 8: 11,2 - 26: 2,-1 - 27: 3,-1 - - node: - color: '#FFFFFFFF' - id: WoodTrimThinLineS - decals: - 13: 9,-1 - 14: 10,-1 - 15: 11,-1 - 25: 2,3 - - node: - color: '#FFFFFFFF' - id: WoodTrimThinLineW - decals: - 3: 8,0 - 4: 8,1 - 19: 4,2 - 20: 4,1 - 21: 4,0 - - node: - color: '#FF0000FF' - id: space - decals: - 1: 3.018731,4.884177 - - type: GridAtmosphere - version: 2 - data: - tiles: - 0,0: - 0: 65535 - 0,-1: - 0: 65535 - -1,0: - 0: 65535 - -1,-1: - 0: 65535 - 0,1: - 0: 65535 - 1,0: - 0: 65535 - 1,1: - 0: 65535 - 2,0: - 0: 65535 - 0,-2: - 0: 65535 - 1,-2: - 0: 65535 - 1,-1: - 0: 65535 - 2,-1: - 0: 65535 - -1,1: - 0: 65535 - -1,-2: - 0: 65535 - 3,0: - 0: 65535 - 3,-1: - 0: 65535 - 0,2: - 0: 61439 - 1,2: - 0: 32767 - 2,1: - 0: 65535 - 3,1: - 0: 65535 - 0,-3: - 0: 65535 - 1,-3: - 0: 65535 - 2,-2: - 0: 65535 - 3,-2: - 0: 65535 - -2,0: - 0: 65279 - -2,1: - 0: 53230 - -1,2: - 0: 12031 - -2,-2: - 0: 65534 - -2,-1: - 0: 61182 - -2,-3: - 0: 60660 - -1,-3: - 0: 65535 - 4,-1: - 0: 65527 - 4,0: - 0: 65535 - 4,1: - 0: 2559 - 0,-4: - 0: 64897 - 2,-3: - 0: 65527 - 4,-2: - 0: 30715 - 0,3: - 0: 27 - 1,3: - 0: 2189 - 2,2: - 0: 2255 - 3,2: - 0: 9 - 1,-4: - 0: 62483 - 2,-4: - 0: 12832 - 3,-4: - 0: 8960 - 3,-3: - 0: 63251 - -3,1: - 0: 2094 - -3,0: - 0: 32964 - -2,2: - 0: 232 - -1,3: - 0: 130 - -3,-2: - 0: 3136 - -3,-1: - 0: 1216 - -2,-4: - 0: 25088 - -1,-4: - 0: 60312 - 4,-3: - 0: 4902 - 5,-2: - 0: 8193 - 5,-1: - 0: 306 - 4,2: - 0: 1 - 5,0: - 0: 29441 - 5,1: - 0: 29459 - 5,2: - 0: 175 - uniqueMixes: - - volume: 2500 - temperature: 293.15 - moles: - - 21.824879 - - 82.10312 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - chunkSize: 4 - - type: GasTileOverlay - - type: RadiationGridResistance - - type: Shuttle - - type: NavMap -- proto: AirAlarm - entities: - - uid: 576 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 5.5,3.5 - parent: 1 - - type: DeviceList - devices: - - 707 - - 719 - - 708 - - 720 - - 721 - - 709 - - 726 - - 714 - - 718 - - 713 - - 710 - - 711 - - 722 - - 723 - - 712 - - 725 - - 384 - - 716 - - 724 - - type: AtmosDevice - joinedGrid: 1 -- proto: AirCanister - entities: - - uid: 3 - components: - - type: Transform - anchored: True - pos: 1.5,5.5 - parent: 1 - - type: Physics - bodyType: Static - - type: AtmosDevice - joinedGrid: 1 -- proto: AirlockExternalShuttleSyndicateLocked - entities: - - uid: 4 - components: - - type: MetaData - desc: The ominous red light fills you with dread - name: suspicious airlock - - type: Transform - pos: 3.5,6.5 - parent: 1 - - type: DeviceLinkSink - links: - - 806 - missingComponents: - - Docking -- proto: AirlockSyndicate - entities: - - uid: 5 - components: - - type: Transform - pos: 5.5,2.5 - parent: 1 - - uid: 15 - components: - - type: MetaData - name: armory airlock - - type: Transform - pos: 2.5,-5.5 - parent: 1 -- proto: AirlockSyndicateGlass - entities: - - uid: 6 - components: - - type: MetaData - name: kitchen airlock - - type: Transform - pos: 2.5,-1.5 - parent: 1 - - uid: 7 - components: - - type: MetaData - name: rec-room airlock - - type: Transform - rot: -1.5707963267948966 rad - pos: 6.5,-1.5 - parent: 1 - - uid: 8 - components: - - type: Transform - pos: 0.5,-0.5 - parent: 1 - - uid: 9 - components: - - type: MetaData - name: hydroponics airlock - - type: Transform - pos: 9.5,-3.5 - parent: 1 - - uid: 10 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 5.5,-0.5 - parent: 1 -- proto: AirlockSyndicateGlassLocked - entities: - - uid: 11 - components: - - type: MetaData - name: to-externals airlock - - type: Transform - pos: 3.5,4.5 - parent: 1 - - uid: 950 - components: - - type: MetaData - name: bridge airlock - - type: Transform - pos: 7.5,-0.5 - parent: 1 -- proto: AirlockSyndicateLocked - entities: - - uid: 13 - components: - - type: MetaData - name: air supply - - type: Transform - pos: 2.5,5.5 - parent: 1 - - uid: 14 - components: - - type: MetaData - name: outpost maintenance - - type: Transform - pos: 0.5,3.5 - parent: 1 -- proto: AlwaysPoweredLightColoredFrostyBlue - entities: - - uid: 16 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: -0.5,-2.5 - parent: 1 -- proto: AlwaysPoweredLightColoredRed - entities: - - uid: 17 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 4.5,7.5 - parent: 1 -- proto: AlwaysPoweredSmallLightMaintenanceRed - entities: - - uid: 953 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 4.5,5.5 - parent: 1 - - uid: 962 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 1.5,5.5 - parent: 1 -- proto: APCBasic - entities: - - uid: 24 - components: - - type: Transform - pos: 2.5,4.5 - parent: 1 - - uid: 25 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 3.5,-5.5 - parent: 1 - - uid: 26 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 7.5,0.5 - parent: 1 - - type: Apc - hasAccess: True - lastExternalState: Good - lastChargeState: Full -- proto: AsteroidAltRock - entities: - - uid: 27 - components: - - type: Transform - pos: -5.5,2.5 - parent: 1 - - uid: 28 - components: - - type: Transform - pos: -4.5,-0.5 - parent: 1 - - uid: 29 - components: - - type: Transform - pos: 4.5,8.5 - parent: 1 - - uid: 30 - components: - - type: Transform - pos: 6.5,5.5 - parent: 1 - - uid: 31 - components: - - type: Transform - pos: 5.5,8.5 - parent: 1 - - uid: 32 - components: - - type: Transform - pos: 6.5,8.5 - parent: 1 - - uid: 33 - components: - - type: Transform - pos: 2.5,7.5 - parent: 1 - - uid: 34 - components: - - type: Transform - pos: 0.5,7.5 - parent: 1 - - uid: 35 - components: - - type: Transform - pos: 3.5,8.5 - parent: 1 - - uid: 36 - components: - - type: Transform - pos: 14.5,3.5 - parent: 1 - - uid: 37 - components: - - type: Transform - pos: 14.5,4.5 - parent: 1 - - uid: 38 - components: - - type: Transform - pos: 15.5,4.5 - parent: 1 - - uid: 39 - components: - - type: Transform - pos: 16.5,-9.5 - parent: 1 - - uid: 40 - components: - - type: Transform - pos: 2.5,8.5 - parent: 1 - - uid: 41 - components: - - type: Transform - pos: 7.5,5.5 - parent: 1 - - uid: 42 - components: - - type: Transform - pos: 7.5,7.5 - parent: 1 - - uid: 43 - components: - - type: Transform - pos: 8.5,5.5 - parent: 1 - - uid: 44 - components: - - type: Transform - pos: 14.5,5.5 - parent: 1 - - uid: 45 - components: - - type: Transform - pos: -3.5,-10.5 - parent: 1 - - uid: 47 - components: - - type: Transform - pos: -5.5,-6.5 - parent: 1 - - uid: 48 - components: - - type: Transform - pos: -3.5,-5.5 - parent: 1 - - uid: 49 - components: - - type: Transform - pos: -3.5,-6.5 - parent: 1 - - uid: 50 - components: - - type: Transform - pos: -4.5,-2.5 - parent: 1 - - uid: 51 - components: - - type: Transform - pos: -3.5,-3.5 - parent: 1 - - uid: 52 - components: - - type: Transform - pos: -4.5,-4.5 - parent: 1 - - uid: 53 - components: - - type: Transform - pos: -5.5,-4.5 - parent: 1 - - uid: 55 - components: - - type: Transform - pos: -0.5,-12.5 - parent: 1 - - uid: 56 - components: - - type: Transform - pos: -4.5,6.5 - parent: 1 - - uid: 57 - components: - - type: Transform - pos: -3.5,6.5 - parent: 1 - - uid: 58 - components: - - type: Transform - pos: -3.5,7.5 - parent: 1 - - uid: 59 - components: - - type: Transform - pos: -2.5,7.5 - parent: 1 - - uid: 60 - components: - - type: Transform - pos: -1.5,7.5 - parent: 1 - - uid: 61 - components: - - type: Transform - pos: 0.5,8.5 - parent: 1 - - uid: 62 - components: - - type: Transform - pos: 0.5,10.5 - parent: 1 - - uid: 63 - components: - - type: Transform - pos: 3.5,9.5 - parent: 1 - - uid: 64 - components: - - type: Transform - pos: 4.5,9.5 - parent: 1 - - uid: 65 - components: - - type: Transform - pos: -0.5,8.5 - parent: 1 - - uid: 66 - components: - - type: Transform - pos: 1.5,8.5 - parent: 1 - - uid: 67 - components: - - type: Transform - pos: 6.5,10.5 - parent: 1 - - uid: 68 - components: - - type: Transform - pos: 0.5,9.5 - parent: 1 - - uid: 69 - components: - - type: Transform - pos: -0.5,7.5 - parent: 1 - - uid: 70 - components: - - type: Transform - pos: -4.5,7.5 - parent: 1 - - uid: 71 - components: - - type: Transform - pos: -8.5,6.5 - parent: 1 - - uid: 72 - components: - - type: Transform - pos: -1.5,-12.5 - parent: 1 - - uid: 73 - components: - - type: Transform - pos: -4.5,2.5 - parent: 1 - - uid: 74 - components: - - type: Transform - pos: -4.5,0.5 - parent: 1 - - uid: 75 - components: - - type: Transform - pos: -4.5,-1.5 - parent: 1 - - uid: 76 - components: - - type: Transform - pos: -3.5,-4.5 - parent: 1 - - uid: 77 - components: - - type: Transform - pos: -3.5,-2.5 - parent: 1 - - uid: 78 - components: - - type: Transform - pos: -3.5,-7.5 - parent: 1 - - uid: 79 - components: - - type: Transform - pos: -4.5,-8.5 - parent: 1 - - uid: 80 - components: - - type: Transform - pos: -4.5,-5.5 - parent: 1 - - uid: 81 - components: - - type: Transform - pos: -4.5,-9.5 - parent: 1 - - uid: 82 - components: - - type: Transform - pos: -2.5,-9.5 - parent: 1 - - uid: 83 - components: - - type: Transform - pos: -1.5,-9.5 - parent: 1 - - uid: 84 - components: - - type: Transform - pos: 16.5,0.5 - parent: 1 - - uid: 85 - components: - - type: Transform - pos: 16.5,1.5 - parent: 1 - - uid: 86 - components: - - type: Transform - pos: 3.5,-10.5 - parent: 1 - - uid: 88 - components: - - type: Transform - pos: 6.5,-10.5 - parent: 1 - - uid: 89 - components: - - type: Transform - pos: 7.5,-12.5 - parent: 1 - - uid: 90 - components: - - type: Transform - pos: 9.5,-7.5 - parent: 1 - - uid: 91 - components: - - type: Transform - pos: 8.5,-8.5 - parent: 1 - - uid: 92 - components: - - type: Transform - pos: 13.5,-7.5 - parent: 1 - - uid: 93 - components: - - type: Transform - pos: 14.5,-6.5 - parent: 1 - - uid: 94 - components: - - type: Transform - pos: 15.5,-6.5 - parent: 1 - - uid: 95 - components: - - type: Transform - pos: 15.5,-5.5 - parent: 1 - - uid: 96 - components: - - type: Transform - pos: 15.5,-4.5 - parent: 1 - - uid: 97 - components: - - type: Transform - pos: 16.5,-6.5 - parent: 1 - - uid: 98 - components: - - type: Transform - pos: 15.5,-1.5 - parent: 1 - - uid: 99 - components: - - type: Transform - pos: 15.5,-2.5 - parent: 1 - - uid: 100 - components: - - type: Transform - pos: 18.5,-1.5 - parent: 1 - - uid: 101 - components: - - type: Transform - pos: 16.5,-5.5 - parent: 1 - - uid: 102 - components: - - type: Transform - pos: 16.5,-4.5 - parent: 1 - - uid: 103 - components: - - type: Transform - pos: -3.5,-11.5 - parent: 1 - - uid: 104 - components: - - type: Transform - pos: -1.5,-11.5 - parent: 1 - - uid: 105 - components: - - type: Transform - pos: -2.5,11.5 - parent: 1 - - uid: 106 - components: - - type: Transform - pos: 2.5,-12.5 - parent: 1 - - uid: 107 - components: - - type: Transform - pos: 5.5,-10.5 - parent: 1 - - uid: 108 - components: - - type: Transform - pos: 7.5,-11.5 - parent: 1 - - uid: 109 - components: - - type: Transform - pos: 15.5,-8.5 - parent: 1 - - uid: 110 - components: - - type: Transform - pos: 19.5,2.5 - parent: 1 - - uid: 111 - components: - - type: Transform - pos: 17.5,5.5 - parent: 1 - - uid: 112 - components: - - type: Transform - pos: 8.5,-12.5 - parent: 1 - - uid: 113 - components: - - type: Transform - pos: 17.5,0.5 - parent: 1 - - uid: 114 - components: - - type: Transform - pos: 17.5,-2.5 - parent: 1 - - uid: 115 - components: - - type: Transform - pos: 18.5,-0.5 - parent: 1 - - uid: 116 - components: - - type: Transform - pos: 18.5,3.5 - parent: 1 - - uid: 117 - components: - - type: Transform - pos: 17.5,3.5 - parent: 1 - - uid: 119 - components: - - type: Transform - pos: 19.5,3.5 - parent: 1 - - uid: 120 - components: - - type: Transform - pos: 19.5,4.5 - parent: 1 - - uid: 121 - components: - - type: Transform - pos: 17.5,4.5 - parent: 1 - - uid: 122 - components: - - type: Transform - pos: 18.5,4.5 - parent: 1 - - uid: 123 - components: - - type: Transform - pos: 16.5,4.5 - parent: 1 - - uid: 124 - components: - - type: Transform - pos: 16.5,5.5 - parent: 1 - - uid: 125 - components: - - type: Transform - pos: 15.5,5.5 - parent: 1 - - uid: 126 - components: - - type: Transform - pos: 13.5,5.5 - parent: 1 - - uid: 127 - components: - - type: Transform - pos: 11.5,8.5 - parent: 1 - - uid: 128 - components: - - type: Transform - pos: 12.5,8.5 - parent: 1 - - uid: 129 - components: - - type: Transform - pos: 9.5,6.5 - parent: 1 - - uid: 130 - components: - - type: Transform - pos: 8.5,8.5 - parent: 1 - - uid: 131 - components: - - type: Transform - pos: 7.5,8.5 - parent: 1 - - uid: 132 - components: - - type: Transform - pos: 7.5,9.5 - parent: 1 - - uid: 133 - components: - - type: Transform - pos: 9.5,8.5 - parent: 1 - - uid: 134 - components: - - type: Transform - pos: 13.5,6.5 - parent: 1 - - uid: 135 - components: - - type: Transform - pos: 12.5,6.5 - parent: 1 - - uid: 136 - components: - - type: Transform - pos: 12.5,7.5 - parent: 1 - - uid: 137 - components: - - type: Transform - pos: 14.5,6.5 - parent: 1 - - uid: 138 - components: - - type: Transform - pos: 17.5,-0.5 - parent: 1 - - uid: 139 - components: - - type: Transform - pos: 20.5,0.5 - parent: 1 - - uid: 140 - components: - - type: Transform - pos: 17.5,1.5 - parent: 1 - - uid: 141 - components: - - type: Transform - pos: -0.5,13.5 - parent: 1 - - uid: 142 - components: - - type: Transform - pos: 0.5,13.5 - parent: 1 - - uid: 143 - components: - - type: Transform - pos: 19.5,1.5 - parent: 1 - - uid: 144 - components: - - type: Transform - pos: 21.5,2.5 - parent: 1 - - uid: 145 - components: - - type: Transform - pos: -6.5,9.5 - parent: 1 - - uid: 146 - components: - - type: Transform - pos: 19.5,5.5 - parent: 1 - - uid: 147 - components: - - type: Transform - pos: 20.5,6.5 - parent: 1 - - uid: 148 - components: - - type: Transform - pos: 20.5,7.5 - parent: 1 - - uid: 149 - components: - - type: Transform - pos: 1.5,12.5 - parent: 1 - - uid: 150 - components: - - type: Transform - pos: 20.5,5.5 - parent: 1 - - uid: 151 - components: - - type: Transform - pos: 22.5,8.5 - parent: 1 - - uid: 152 - components: - - type: Transform - pos: 21.5,8.5 - parent: 1 - - uid: 153 - components: - - type: Transform - pos: 21.5,6.5 - parent: 1 - - uid: 154 - components: - - type: Transform - pos: 21.5,7.5 - parent: 1 - - uid: 155 - components: - - type: Transform - pos: -5.5,9.5 - parent: 1 - - uid: 156 - components: - - type: Transform - pos: 20.5,2.5 - parent: 1 - - uid: 157 - components: - - type: Transform - pos: 16.5,-2.5 - parent: 1 - - uid: 158 - components: - - type: Transform - pos: -2.5,12.5 - parent: 1 - - uid: 159 - components: - - type: Transform - pos: -10.5,5.5 - parent: 1 - - uid: 160 - components: - - type: Transform - pos: 17.5,-3.5 - parent: 1 - - uid: 161 - components: - - type: Transform - pos: 17.5,-4.5 - parent: 1 - - uid: 162 - components: - - type: Transform - pos: 18.5,-2.5 - parent: 1 - - uid: 163 - components: - - type: Transform - pos: 14.5,-7.5 - parent: 1 - - uid: 164 - components: - - type: Transform - pos: 12.5,-7.5 - parent: 1 - - uid: 165 - components: - - type: Transform - pos: 9.5,-8.5 - parent: 1 - - uid: 166 - components: - - type: Transform - pos: 11.5,-10.5 - parent: 1 - - uid: 167 - components: - - type: Transform - pos: 11.5,-8.5 - parent: 1 - - uid: 168 - components: - - type: Transform - pos: 14.5,-8.5 - parent: 1 - - uid: 169 - components: - - type: Transform - pos: 12.5,-10.5 - parent: 1 - - uid: 170 - components: - - type: Transform - pos: 10.5,-11.5 - parent: 1 - - uid: 171 - components: - - type: Transform - pos: 8.5,-10.5 - parent: 1 - - uid: 172 - components: - - type: Transform - pos: 9.5,-10.5 - parent: 1 - - uid: 173 - components: - - type: Transform - pos: -9.5,0.5 - parent: 1 - - uid: 174 - components: - - type: Transform - pos: 13.5,-8.5 - parent: 1 - - uid: 175 - components: - - type: Transform - pos: 17.5,-6.5 - parent: 1 - - uid: 176 - components: - - type: Transform - pos: 12.5,-8.5 - parent: 1 - - uid: 177 - components: - - type: Transform - pos: 15.5,-3.5 - parent: 1 - - uid: 178 - components: - - type: Transform - pos: 16.5,-3.5 - parent: 1 - - uid: 179 - components: - - type: Transform - pos: 17.5,-7.5 - parent: 1 - - uid: 180 - components: - - type: Transform - pos: 16.5,-7.5 - parent: 1 - - uid: 181 - components: - - type: Transform - pos: 15.5,-7.5 - parent: 1 - - uid: 182 - components: - - type: Transform - pos: -9.5,-1.5 - parent: 1 - - uid: 183 - components: - - type: Transform - pos: 10.5,-10.5 - parent: 1 - - uid: 184 - components: - - type: Transform - pos: 9.5,-11.5 - parent: 1 - - uid: 185 - components: - - type: Transform - pos: 0.5,-15.5 - parent: 1 - - uid: 187 - components: - - type: Transform - pos: -9.5,-5.5 - parent: 1 - - uid: 188 - components: - - type: Transform - pos: -7.5,-10.5 - parent: 1 - - uid: 189 - components: - - type: Transform - pos: 17.5,-5.5 - parent: 1 - - uid: 190 - components: - - type: Transform - pos: -6.5,-10.5 - parent: 1 - - uid: 191 - components: - - type: Transform - pos: -5.5,-11.5 - parent: 1 - - uid: 192 - components: - - type: Transform - pos: 14.5,-9.5 - parent: 1 - - uid: 193 - components: - - type: Transform - pos: -5.5,-12.5 - parent: 1 - - uid: 194 - components: - - type: Transform - pos: 18.5,-6.5 - parent: 1 - - uid: 195 - components: - - type: Transform - pos: -6.5,-12.5 - parent: 1 - - uid: 196 - components: - - type: Transform - pos: 20.5,-2.5 - parent: 1 - - uid: 197 - components: - - type: Transform - pos: 20.5,-1.5 - parent: 1 - - uid: 198 - components: - - type: Transform - pos: 21.5,4.5 - parent: 1 - - uid: 199 - components: - - type: Transform - pos: 20.5,3.5 - parent: 1 - - uid: 200 - components: - - type: Transform - pos: 20.5,4.5 - parent: 1 - - uid: 201 - components: - - type: Transform - pos: 14.5,7.5 - parent: 1 - - uid: 202 - components: - - type: Transform - pos: 15.5,7.5 - parent: 1 - - uid: 203 - components: - - type: Transform - pos: -3.5,-13.5 - parent: 1 - - uid: 204 - components: - - type: Transform - pos: 3.5,10.5 - parent: 1 - - uid: 205 - components: - - type: Transform - pos: 2.5,10.5 - parent: 1 - - uid: 206 - components: - - type: Transform - pos: 1.5,10.5 - parent: 1 - - uid: 207 - components: - - type: Transform - pos: 5.5,10.5 - parent: 1 - - uid: 208 - components: - - type: Transform - pos: 5.5,9.5 - parent: 1 - - uid: 209 - components: - - type: Transform - pos: 5.5,11.5 - parent: 1 - - uid: 210 - components: - - type: Transform - pos: 6.5,9.5 - parent: 1 - - uid: 211 - components: - - type: Transform - pos: 3.5,11.5 - parent: 1 - - uid: 212 - components: - - type: Transform - pos: 1.5,9.5 - parent: 1 - - uid: 213 - components: - - type: Transform - pos: 2.5,9.5 - parent: 1 - - uid: 214 - components: - - type: Transform - pos: -0.5,9.5 - parent: 1 - - uid: 215 - components: - - type: Transform - pos: -1.5,8.5 - parent: 1 - - uid: 216 - components: - - type: Transform - pos: -0.5,10.5 - parent: 1 - - uid: 217 - components: - - type: Transform - pos: -2.5,9.5 - parent: 1 - - uid: 218 - components: - - type: Transform - pos: -3.5,-14.5 - parent: 1 - - uid: 219 - components: - - type: Transform - pos: -1.5,9.5 - parent: 1 - - uid: 220 - components: - - type: Transform - pos: -3.5,5.5 - parent: 1 - - uid: 221 - components: - - type: Transform - pos: -7.5,6.5 - parent: 1 - - uid: 222 - components: - - type: Transform - pos: -5.5,5.5 - parent: 1 - - uid: 223 - components: - - type: Transform - pos: -5.5,4.5 - parent: 1 - - uid: 224 - components: - - type: Transform - pos: 4.5,11.5 - parent: 1 - - uid: 225 - components: - - type: Transform - pos: -1.5,10.5 - parent: 1 - - uid: 226 - components: - - type: Transform - pos: -0.5,-14.5 - parent: 1 - - uid: 227 - components: - - type: Transform - pos: 9.5,-13.5 - parent: 1 - - uid: 228 - components: - - type: Transform - pos: 3.5,12.5 - parent: 1 - - uid: 229 - components: - - type: Transform - pos: 4.5,-15.5 - parent: 1 - - uid: 230 - components: - - type: Transform - pos: 4.5,-14.5 - parent: 1 - - uid: 231 - components: - - type: Transform - pos: 5.5,-15.5 - parent: 1 - - uid: 232 - components: - - type: Transform - pos: 9.5,-14.5 - parent: 1 - - uid: 233 - components: - - type: Transform - pos: 6.5,11.5 - parent: 1 - - uid: 234 - components: - - type: Transform - pos: 12.5,-13.5 - parent: 1 - - uid: 235 - components: - - type: Transform - pos: 13.5,-12.5 - parent: 1 - - uid: 236 - components: - - type: Transform - pos: -4.5,8.5 - parent: 1 - - uid: 237 - components: - - type: Transform - pos: -3.5,8.5 - parent: 1 - - uid: 238 - components: - - type: Transform - pos: -2.5,8.5 - parent: 1 - - uid: 239 - components: - - type: Transform - pos: -6.5,6.5 - parent: 1 - - uid: 240 - components: - - type: Transform - pos: -5.5,6.5 - parent: 1 - - uid: 241 - components: - - type: Transform - pos: -4.5,4.5 - parent: 1 - - uid: 242 - components: - - type: Transform - pos: 13.5,-11.5 - parent: 1 - - uid: 243 - components: - - type: Transform - pos: -9.5,4.5 - parent: 1 - - uid: 244 - components: - - type: Transform - pos: -8.5,4.5 - parent: 1 - - uid: 245 - components: - - type: Transform - pos: -8.5,3.5 - parent: 1 - - uid: 246 - components: - - type: Transform - pos: -7.5,3.5 - parent: 1 - - uid: 247 - components: - - type: Transform - pos: -6.5,3.5 - parent: 1 - - uid: 248 - components: - - type: Transform - pos: -5.5,3.5 - parent: 1 - - uid: 249 - components: - - type: Transform - pos: -6.5,5.5 - parent: 1 - - uid: 250 - components: - - type: Transform - pos: -4.5,1.5 - parent: 1 - - uid: 251 - components: - - type: Transform - pos: -6.5,2.5 - parent: 1 - - uid: 252 - components: - - type: Transform - pos: -8.5,1.5 - parent: 1 - - uid: 253 - components: - - type: Transform - pos: -7.5,0.5 - parent: 1 - - uid: 254 - components: - - type: Transform - pos: -5.5,0.5 - parent: 1 - - uid: 255 - components: - - type: Transform - pos: -6.5,0.5 - parent: 1 - - uid: 256 - components: - - type: Transform - pos: 18.5,-11.5 - parent: 1 - - uid: 257 - components: - - type: Transform - pos: -5.5,-0.5 - parent: 1 - - uid: 258 - components: - - type: Transform - pos: -5.5,-1.5 - parent: 1 - - uid: 259 - components: - - type: Transform - pos: 17.5,-10.5 - parent: 1 - - uid: 260 - components: - - type: Transform - pos: -8.5,-2.5 - parent: 1 - - uid: 261 - components: - - type: Transform - pos: -7.5,-2.5 - parent: 1 - - uid: 262 - components: - - type: Transform - pos: 16.5,-8.5 - parent: 1 - - uid: 263 - components: - - type: Transform - pos: 17.5,-9.5 - parent: 1 - - uid: 264 - components: - - type: Transform - pos: -3.5,-1.5 - parent: 1 - - uid: 265 - components: - - type: Transform - pos: 19.5,-7.5 - parent: 1 - - uid: 266 - components: - - type: Transform - pos: -5.5,-2.5 - parent: 1 - - uid: 267 - components: - - type: Transform - pos: -6.5,-7.5 - parent: 1 - - uid: 268 - components: - - type: Transform - pos: -4.5,-6.5 - parent: 1 - - uid: 269 - components: - - type: Transform - pos: -4.5,-10.5 - parent: 1 - - uid: 270 - components: - - type: Transform - pos: -4.5,-7.5 - parent: 1 - - uid: 271 - components: - - type: Transform - pos: -5.5,-8.5 - parent: 1 - - uid: 272 - components: - - type: Transform - pos: -3.5,-8.5 - parent: 1 - - uid: 273 - components: - - type: Transform - pos: -2.5,-11.5 - parent: 1 - - uid: 274 - components: - - type: Transform - pos: -3.5,-9.5 - parent: 1 - - uid: 275 - components: - - type: Transform - pos: -1.5,-10.5 - parent: 1 - - uid: 276 - components: - - type: Transform - pos: -5.5,-7.5 - parent: 1 - - uid: 277 - components: - - type: Transform - pos: -2.5,-10.5 - parent: 1 - - uid: 278 - components: - - type: Transform - pos: -0.5,-10.5 - parent: 1 - - uid: 279 - components: - - type: Transform - pos: -0.5,-11.5 - parent: 1 - - uid: 280 - components: - - type: Transform - pos: 0.5,-13.5 - parent: 1 - - uid: 281 - components: - - type: Transform - pos: 2.5,-10.5 - parent: 1 - - uid: 282 - components: - - type: Transform - pos: 2.5,-11.5 - parent: 1 - - uid: 283 - components: - - type: Transform - pos: 20.5,-7.5 - parent: 1 - - uid: 284 - components: - - type: Transform - pos: 6.5,-13.5 - parent: 1 - - uid: 285 - components: - - type: Transform - pos: 5.5,-9.5 - parent: 1 - - uid: 286 - components: - - type: Transform - pos: 6.5,-11.5 - parent: 1 - - uid: 287 - components: - - type: Transform - pos: 7.5,-10.5 - parent: 1 - - uid: 288 - components: - - type: Transform - pos: 21.5,-4.5 - parent: 1 - - uid: 289 - components: - - type: Transform - pos: 6.5,-9.5 - parent: 1 - - uid: 290 - components: - - type: Transform - pos: 21.5,-3.5 - parent: 1 - - uid: 291 - components: - - type: Transform - pos: 3.5,-11.5 - parent: 1 - - uid: 292 - components: - - type: Transform - pos: 5.5,-11.5 - parent: 1 - - uid: 293 - components: - - type: Transform - pos: 3.5,-13.5 - parent: 1 - - uid: 294 - components: - - type: Transform - pos: 0.5,-10.5 - parent: 1 - - uid: 295 - components: - - type: Transform - pos: -0.5,-9.5 - parent: 1 - - uid: 296 - components: - - type: Transform - pos: -7.5,-6.5 - parent: 1 - - uid: 297 - components: - - type: Transform - pos: -5.5,-5.5 - parent: 1 - - uid: 298 - components: - - type: Transform - pos: 8.5,-11.5 - parent: 1 - - uid: 299 - components: - - type: Transform - pos: 4.5,-12.5 - parent: 1 - - uid: 300 - components: - - type: Transform - pos: 4.5,-11.5 - parent: 1 - - uid: 301 - components: - - type: Transform - pos: 22.5,3.5 - parent: 1 - - uid: 302 - components: - - type: Transform - pos: 0.5,-11.5 - parent: 1 - - uid: 303 - components: - - type: Transform - pos: 0.5,-12.5 - parent: 1 - - uid: 304 - components: - - type: Transform - pos: 1.5,-11.5 - parent: 1 - - uid: 305 - components: - - type: Transform - pos: 23.5,9.5 - parent: 1 - - uid: 307 - components: - - type: Transform - pos: 11.5,10.5 - parent: 1 - - uid: 308 - components: - - type: Transform - pos: 7.5,12.5 - parent: 1 - - uid: 309 - components: - - type: Transform - pos: 7.5,13.5 - parent: 1 - - uid: 310 - components: - - type: Transform - pos: 7.5,14.5 - parent: 1 - - uid: 312 - components: - - type: Transform - pos: -6.5,-6.5 - parent: 1 - - uid: 313 - components: - - type: Transform - pos: -7.5,-4.5 - parent: 1 - - uid: 314 - components: - - type: Transform - pos: -6.5,-4.5 - parent: 1 - - uid: 315 - components: - - type: Transform - pos: -6.5,-2.5 - parent: 1 - - uid: 316 - components: - - type: Transform - pos: -6.5,-1.5 - parent: 1 - - uid: 317 - components: - - type: Transform - pos: -6.5,1.5 - parent: 1 - - uid: 318 - components: - - type: Transform - pos: -7.5,1.5 - parent: 1 - - uid: 319 - components: - - type: Transform - pos: 3.5,-9.5 - parent: 1 - - uid: 320 - components: - - type: Transform - pos: 4.5,-9.5 - parent: 1 - - uid: 321 - components: - - type: Transform - pos: 2.5,-9.5 - parent: 1 - - uid: 322 - components: - - type: Transform - pos: 12.5,4.5 - parent: 1 - - uid: 323 - components: - - type: Transform - pos: 10.5,4.5 - parent: 1 - - uid: 324 - components: - - type: Transform - pos: 13.5,4.5 - parent: 1 - - uid: 325 - components: - - type: Transform - pos: 12.5,5.5 - parent: 1 - - uid: 326 - components: - - type: Transform - pos: -3.5,4.5 - parent: 1 - - uid: 327 - components: - - type: Transform - pos: 10.5,5.5 - parent: 1 - - uid: 328 - components: - - type: Transform - pos: 9.5,4.5 - parent: 1 - - uid: 329 - components: - - type: Transform - pos: 9.5,5.5 - parent: 1 - - uid: 330 - components: - - type: Transform - pos: 11.5,5.5 - parent: 1 - - uid: 331 - components: - - type: Transform - pos: 10.5,6.5 - parent: 1 - - uid: 333 - components: - - type: Transform - pos: 11.5,4.5 - parent: 1 - - uid: 334 - components: - - type: Transform - pos: 0.5,-9.5 - parent: 1 - - uid: 337 - components: - - type: Transform - pos: 1.5,7.5 - parent: 1 - - uid: 339 - components: - - type: Transform - pos: 1.5,-10.5 - parent: 1 - - uid: 364 - components: - - type: Transform - pos: 8.5,4.5 - parent: 1 - - uid: 372 - components: - - type: Transform - pos: 11.5,6.5 - parent: 1 - - uid: 373 - components: - - type: Transform - pos: 1.5,-9.5 - parent: 1 - - uid: 374 - components: - - type: Transform - pos: -3.5,2.5 - parent: 1 - - uid: 609 - components: - - type: Transform - pos: -4.5,3.5 - parent: 1 - - uid: 616 - components: - - type: Transform - pos: -5.5,-3.5 - parent: 1 - - uid: 818 - components: - - type: Transform - pos: -3.5,3.5 - parent: 1 - - uid: 884 - components: - - type: Transform - pos: 17.5,-1.5 - parent: 1 -- proto: AsteroidAltRockMining - entities: - - uid: 186 - components: - - type: Transform - pos: -8.5,-5.5 - parent: 1 - - uid: 306 - components: - - type: Transform - pos: 16.5,8.5 - parent: 1 -- proto: AtmosDeviceFanTiny - entities: - - uid: 341 - components: - - type: Transform - pos: 3.5,6.5 - parent: 1 -- proto: Bed - entities: - - uid: 342 - components: - - type: Transform - pos: -0.5,1.5 - parent: 1 - - uid: 343 - components: - - type: Transform - pos: 6.5,1.5 - parent: 1 - - uid: 344 - components: - - type: Transform - pos: 6.5,3.5 - parent: 1 - - uid: 345 - components: - - type: Transform - pos: -1.5,1.5 - parent: 1 -- proto: BedsheetSyndie - entities: - - uid: 346 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: -0.5,1.5 - parent: 1 - - uid: 347 - components: - - type: Transform - pos: 6.5,1.5 - parent: 1 - - uid: 348 - components: - - type: Transform - pos: 6.5,3.5 - parent: 1 - - uid: 349 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: -1.5,1.5 - parent: 1 -- proto: BenchSofaCorpLeft - entities: - - uid: 422 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 6.5,-4.5 - parent: 1 - - uid: 423 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 8.5,-5.5 - parent: 1 - - uid: 984 - components: - - type: Transform - pos: 11.5,2.5 - parent: 1 -- proto: BenchSofaCorpRight - entities: - - uid: 404 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 6.5,-5.5 - parent: 1 - - uid: 421 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 8.5,-4.5 - parent: 1 - - uid: 985 - components: - - type: Transform - pos: 10.5,2.5 - parent: 1 -- proto: BlastDoorOpen - entities: - - uid: 350 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 6.5,-6.5 - parent: 1 - - type: DeviceLinkSink - links: - - 807 - - uid: 351 - components: - - type: Transform - pos: 13.5,0.5 - parent: 1 - - type: DeviceLinkSink - invokeCounter: 4 - links: - - 805 - - uid: 352 - components: - - type: Transform - pos: 13.5,1.5 - parent: 1 - - type: DeviceLinkSink - invokeCounter: 4 - links: - - 805 - - uid: 353 - components: - - type: Transform - pos: 13.5,-0.5 - parent: 1 - - type: DeviceLinkSink - invokeCounter: 4 - links: - - 805 - - uid: 354 - components: - - type: Transform - pos: 13.5,2.5 - parent: 1 - - type: DeviceLinkSink - invokeCounter: 4 - links: - - 805 - - uid: 355 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 7.5,-6.5 - parent: 1 - - type: DeviceLinkSink - links: - - 807 - - uid: 356 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 8.5,-6.5 - parent: 1 - - type: DeviceLinkSink - links: - - 807 - - uid: 357 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 10.5,-5.5 - parent: 1 - - type: DeviceLinkSink - links: - - 807 - - uid: 358 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 11.5,-5.5 - parent: 1 - - type: DeviceLinkSink - links: - - 807 - - uid: 359 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 12.5,-5.5 - parent: 1 - - type: DeviceLinkSink - links: - - 807 - - uid: 360 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 13.5,-5.5 - parent: 1 - - type: DeviceLinkSink - links: - - 807 - - uid: 361 - components: - - type: Transform - pos: 10.5,-1.5 - parent: 1 - - type: DeviceLinkSink - links: - - 805 -- proto: BoozeDispenser - entities: - - uid: 362 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 1.5,-3.5 - parent: 1 - - type: Emagged -- proto: BoxHandcuff - entities: - - uid: 118 - components: - - type: Transform - parent: 363 - - type: Physics - canCollide: False - - type: InsideEntityStorage -- proto: BoxLethalshot - entities: - - uid: 365 - components: - - type: Transform - parent: 363 - - type: Physics - canCollide: False - - type: InsideEntityStorage - - uid: 366 - components: - - type: Transform - parent: 363 - - type: Physics - canCollide: False - - type: InsideEntityStorage -- proto: BoxShotgunFlare - entities: - - uid: 367 - components: - - type: Transform - parent: 363 - - type: Physics - canCollide: False - - type: InsideEntityStorage -- proto: BriefcaseBrownFilled - entities: - - uid: 401 - components: - - type: Transform - pos: 12.580331,-0.57071495 - parent: 1 - - uid: 402 - components: - - type: Transform - pos: 12.615925,0.015501022 - parent: 1 - - uid: 403 - components: - - type: Transform - pos: 12.615925,-0.21887398 - parent: 1 -- proto: CableApcExtension - entities: - - uid: 440 - components: - - type: Transform - pos: 7.5,0.5 - parent: 1 - - uid: 441 - components: - - type: Transform - pos: 8.5,0.5 - parent: 1 - - uid: 442 - components: - - type: Transform - pos: 9.5,0.5 - parent: 1 - - uid: 443 - components: - - type: Transform - pos: 9.5,1.5 - parent: 1 - - uid: 444 - components: - - type: Transform - pos: 9.5,-0.5 - parent: 1 - - uid: 445 - components: - - type: Transform - pos: 2.5,4.5 - parent: 1 - - uid: 446 - components: - - type: Transform - pos: 2.5,3.5 - parent: 1 - - uid: 447 - components: - - type: Transform - pos: 1.5,2.5 - parent: 1 - - uid: 448 - components: - - type: Transform - pos: 1.5,2.5 - parent: 1 - - uid: 449 - components: - - type: Transform - pos: 3.5,2.5 - parent: 1 - - uid: 450 - components: - - type: Transform - pos: 2.5,2.5 - parent: 1 - - uid: 451 - components: - - type: Transform - pos: 3.5,1.5 - parent: 1 - - uid: 452 - components: - - type: Transform - pos: 3.5,0.5 - parent: 1 - - uid: 453 - components: - - type: Transform - pos: 3.5,-5.5 - parent: 1 - - uid: 454 - components: - - type: Transform - pos: 3.5,-4.5 - parent: 1 - - uid: 455 - components: - - type: Transform - pos: 3.5,-3.5 - parent: 1 - - uid: 456 - components: - - type: Transform - pos: -1.5,-3.5 - parent: 1 - - uid: 457 - components: - - type: Transform - pos: 4.5,-3.5 - parent: 1 - - uid: 458 - components: - - type: Transform - pos: 1.5,1.5 - parent: 1 - - uid: 459 - components: - - type: Transform - pos: 1.5,0.5 - parent: 1 - - uid: 460 - components: - - type: Transform - pos: 1.5,-0.5 - parent: 1 - - uid: 461 - components: - - type: Transform - pos: 0.5,-0.5 - parent: 1 - - uid: 462 - components: - - type: Transform - pos: -0.5,-0.5 - parent: 1 - - uid: 463 - components: - - type: Transform - pos: -0.5,0.5 - parent: 1 - - uid: 464 - components: - - type: Transform - pos: -1.5,-2.5 - parent: 1 - - uid: 465 - components: - - type: Transform - pos: -1.5,-0.5 - parent: 1 - - uid: 466 - components: - - type: Transform - pos: -1.5,-1.5 - parent: 1 - - uid: 467 - components: - - type: Transform - pos: 2.5,-5.5 - parent: 1 - - uid: 468 - components: - - type: Transform - pos: 2.5,-6.5 - parent: 1 - - uid: 469 - components: - - type: Transform - pos: 1.5,-6.5 - parent: 1 - - uid: 470 - components: - - type: Transform - pos: 0.5,-6.5 - parent: 1 - - uid: 471 - components: - - type: Transform - pos: -0.5,-6.5 - parent: 1 - - uid: 472 - components: - - type: Transform - pos: 1.5,3.5 - parent: 1 - - uid: 473 - components: - - type: Transform - pos: 0.5,3.5 - parent: 1 - - uid: 474 - components: - - type: Transform - pos: -0.5,3.5 - parent: 1 - - uid: 475 - components: - - type: Transform - pos: -1.5,3.5 - parent: 1 - - uid: 476 - components: - - type: Transform - pos: -1.5,4.5 - parent: 1 - - uid: 477 - components: - - type: Transform - pos: 10.5,-0.5 - parent: 1 - - uid: 478 - components: - - type: Transform - pos: 10.5,-1.5 - parent: 1 - - uid: 479 - components: - - type: Transform - pos: 10.5,-2.5 - parent: 1 - - uid: 480 - components: - - type: Transform - pos: 10.5,-3.5 - parent: 1 - - uid: 481 - components: - - type: Transform - pos: 11.5,-3.5 - parent: 1 - - uid: 482 - components: - - type: Transform - pos: 12.5,-3.5 - parent: 1 - - uid: 483 - components: - - type: Transform - pos: 7.5,-0.5 - parent: 1 - - uid: 484 - components: - - type: Transform - pos: 6.5,-0.5 - parent: 1 - - uid: 485 - components: - - type: Transform - pos: 6.5,-1.5 - parent: 1 - - uid: 486 - components: - - type: Transform - pos: 6.5,-2.5 - parent: 1 - - uid: 487 - components: - - type: Transform - pos: 6.5,-3.5 - parent: 1 - - uid: 488 - components: - - type: Transform - pos: 7.5,-3.5 - parent: 1 - - uid: 489 - components: - - type: Transform - pos: 7.5,-4.5 - parent: 1 - - uid: 490 - components: - - type: Transform - pos: 3.5,3.5 - parent: 1 - - uid: 491 - components: - - type: Transform - pos: 4.5,3.5 - parent: 1 - - uid: 492 - components: - - type: Transform - pos: 5.5,3.5 - parent: 1 - - uid: 954 - components: - - type: Transform - pos: 10.5,2.5 - parent: 1 - - uid: 959 - components: - - type: Transform - pos: 9.5,2.5 - parent: 1 -- proto: CableHV - entities: - - uid: 493 - components: - - type: Transform - pos: -0.5,6.5 - parent: 1 - - uid: 494 - components: - - type: Transform - pos: -0.5,5.5 - parent: 1 - - uid: 495 - components: - - type: Transform - pos: 0.5,5.5 - parent: 1 - - uid: 496 - components: - - type: Transform - pos: -1.5,5.5 - parent: 1 - - uid: 497 - components: - - type: Transform - pos: -1.5,6.5 - parent: 1 - - uid: 498 - components: - - type: Transform - pos: -1.5,4.5 - parent: 1 - - uid: 499 - components: - - type: Transform - pos: -1.5,3.5 - parent: 1 - - uid: 500 - components: - - type: Transform - pos: -0.5,3.5 - parent: 1 - - uid: 501 - components: - - type: Transform - pos: -2.5,5.5 - parent: 1 - - uid: 502 - components: - - type: Transform - pos: -2.5,4.5 - parent: 1 - - uid: 503 - components: - - type: Transform - pos: -2.5,3.5 - parent: 1 - - uid: 504 - components: - - type: Transform - pos: -1.5,2.5 - parent: 1 - - uid: 505 - components: - - type: Transform - pos: -0.5,2.5 - parent: 1 -- proto: CableMV - entities: - - uid: 506 - components: - - type: Transform - pos: -1.5,4.5 - parent: 1 - - uid: 507 - components: - - type: Transform - pos: -0.5,5.5 - parent: 1 - - uid: 508 - components: - - type: Transform - pos: 0.5,5.5 - parent: 1 - - uid: 509 - components: - - type: Transform - pos: -1.5,5.5 - parent: 1 - - uid: 510 - components: - - type: Transform - pos: -1.5,3.5 - parent: 1 - - uid: 511 - components: - - type: Transform - pos: -0.5,3.5 - parent: 1 - - uid: 512 - components: - - type: Transform - pos: 1.5,3.5 - parent: 1 - - uid: 513 - components: - - type: Transform - pos: 0.5,3.5 - parent: 1 - - uid: 514 - components: - - type: Transform - pos: 2.5,3.5 - parent: 1 - - uid: 515 - components: - - type: Transform - pos: 2.5,4.5 - parent: 1 - - uid: 516 - components: - - type: Transform - pos: 2.5,2.5 - parent: 1 - - uid: 517 - components: - - type: Transform - pos: 2.5,1.5 - parent: 1 - - uid: 518 - components: - - type: Transform - pos: 2.5,0.5 - parent: 1 - - uid: 519 - components: - - type: Transform - pos: 2.5,-0.5 - parent: 1 - - uid: 520 - components: - - type: Transform - pos: 2.5,-1.5 - parent: 1 - - uid: 521 - components: - - type: Transform - pos: 2.5,-2.5 - parent: 1 - - uid: 522 - components: - - type: Transform - pos: 2.5,-3.5 - parent: 1 - - uid: 523 - components: - - type: Transform - pos: 2.5,-4.5 - parent: 1 - - uid: 524 - components: - - type: Transform - pos: 3.5,-4.5 - parent: 1 - - uid: 525 - components: - - type: Transform - pos: 3.5,-5.5 - parent: 1 - - uid: 526 - components: - - type: Transform - pos: 3.5,-0.5 - parent: 1 - - uid: 527 - components: - - type: Transform - pos: 4.5,-0.5 - parent: 1 - - uid: 528 - components: - - type: Transform - pos: 5.5,-0.5 - parent: 1 - - uid: 529 - components: - - type: Transform - pos: 6.5,-0.5 - parent: 1 - - uid: 530 - components: - - type: Transform - pos: 6.5,-0.5 - parent: 1 - - uid: 531 - components: - - type: Transform - pos: 7.5,-0.5 - parent: 1 - - uid: 532 - components: - - type: Transform - pos: 8.5,-0.5 - parent: 1 - - uid: 533 - components: - - type: Transform - pos: 8.5,0.5 - parent: 1 - - uid: 534 - components: - - type: Transform - pos: 7.5,0.5 - parent: 1 -- proto: CarpetBlack - entities: - - uid: 381 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 3.5,2.5 - parent: 1 - - uid: 570 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 6.5,-4.5 - parent: 1 - - uid: 571 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 6.5,-5.5 - parent: 1 - - uid: 572 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 7.5,-4.5 - parent: 1 - - uid: 573 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 7.5,-5.5 - parent: 1 - - uid: 574 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 8.5,-5.5 - parent: 1 - - uid: 575 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 8.5,-4.5 - parent: 1 - - uid: 960 - components: - - type: Transform - pos: 2.5,2.5 - parent: 1 - - uid: 964 - components: - - type: Transform - pos: 3.5,1.5 - parent: 1 - - uid: 965 - components: - - type: Transform - pos: 2.5,0.5 - parent: 1 - - uid: 966 - components: - - type: Transform - pos: 3.5,0.5 - parent: 1 - - uid: 967 - components: - - type: Transform - pos: 2.5,1.5 - parent: 1 - - uid: 968 - components: - - type: Transform - pos: -1.5,1.5 - parent: 1 - - uid: 969 - components: - - type: Transform - pos: -1.5,0.5 - parent: 1 - - uid: 970 - components: - - type: Transform - pos: -2.5,0.5 - parent: 1 - - uid: 971 - components: - - type: Transform - pos: -1.5,-0.5 - parent: 1 - - uid: 972 - components: - - type: Transform - pos: -0.5,-0.5 - parent: 1 - - uid: 973 - components: - - type: Transform - pos: -0.5,0.5 - parent: 1 - - uid: 974 - components: - - type: Transform - pos: -0.5,1.5 - parent: 1 - - uid: 975 - components: - - type: Transform - pos: 6.5,1.5 - parent: 1 - - uid: 976 - components: - - type: Transform - pos: 6.5,2.5 - parent: 1 - - uid: 977 - components: - - type: Transform - pos: 6.5,3.5 - parent: 1 - - uid: 978 - components: - - type: Transform - pos: 9.5,1.5 - parent: 1 - - uid: 979 - components: - - type: Transform - pos: 9.5,0.5 - parent: 1 - - uid: 980 - components: - - type: Transform - pos: 10.5,0.5 - parent: 1 - - uid: 981 - components: - - type: Transform - pos: 10.5,1.5 - parent: 1 - - uid: 982 - components: - - type: Transform - pos: 11.5,1.5 - parent: 1 - - uid: 983 - components: - - type: Transform - pos: 11.5,0.5 - parent: 1 -- proto: ChairOfficeDark - entities: - - uid: 535 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 9.5,1.5 - parent: 1 - - uid: 536 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 11.5,1.5 - parent: 1 -- proto: CigPackSyndicate - entities: - - uid: 558 - components: - - type: Transform - pos: 4.7080255,1.4853511 - parent: 1 - - uid: 569 - components: - - type: Transform - pos: 4.7236505,1.7666011 - parent: 1 -- proto: ClosetWallBlack - entities: - - uid: 405 - components: - - type: MetaData - desc: A closet packed with forged stamps - name: stamp closet - - type: Transform - pos: 10.5,3.5 - parent: 1 - - type: EntityStorage - air: - volume: 200 - immutable: False - temperature: 293.14673 - moles: - - 1.7459903 - - 6.568249 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - type: ContainerContainer - containers: - entity_storage: !type:Container - showEnts: False - occludes: True - ents: - - 411 - - 410 - - 409 - - 408 - - 407 - - 406 - - 412 - - 413 - - 414 - - 415 - - 416 - - 417 - - 418 - - 419 - - 420 -- proto: ClosetWallMixed - entities: - - uid: 388 - components: - - type: MetaData - name: pyjama closet - - type: Transform - rot: -1.5707963267948966 rad - pos: 7.5,2.5 - parent: 1 - - type: EntityStorage - air: - volume: 200 - immutable: False - temperature: 293.14673 - moles: - - 1.7459903 - - 6.568249 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - type: ContainerContainer - containers: - entity_storage: !type:Container - showEnts: False - occludes: True - ents: - - 400 - - 399 - - 393 - - 392 - - 391 - - 390 - - 398 - - 397 - - 396 - - 395 - - 394 - - 389 -- proto: ClothingHeadPyjamaSyndicateBlack - entities: - - uid: 393 - components: - - type: Transform - parent: 388 - - type: Physics - canCollide: False - - type: InsideEntityStorage - - uid: 395 - components: - - type: Transform - parent: 388 - - type: Physics - canCollide: False - - type: InsideEntityStorage -- proto: ClothingHeadPyjamaSyndicatePink - entities: - - uid: 396 - components: - - type: Transform - parent: 388 - - type: Physics - canCollide: False - - type: InsideEntityStorage - - uid: 399 - components: - - type: Transform - parent: 388 - - type: Physics - canCollide: False - - type: InsideEntityStorage -- proto: ClothingHeadPyjamaSyndicateRed - entities: - - uid: 389 - components: - - type: Transform - parent: 388 - - type: Physics - canCollide: False - - type: InsideEntityStorage - - uid: 391 - components: - - type: Transform - parent: 388 - - type: Physics - canCollide: False - - type: InsideEntityStorage -- proto: ClothingMaskGasVoiceChameleon - entities: - - uid: 540 - components: - - type: Transform - pos: 11.346417,2.446512 - parent: 1 - - uid: 541 - components: - - type: Transform - pos: 10.971417,2.462137 - parent: 1 -- proto: ClothingUniformJumpsuitPyjamaSyndicateBlack - entities: - - uid: 392 - components: - - type: Transform - parent: 388 - - type: Physics - canCollide: False - - type: InsideEntityStorage - - uid: 397 - components: - - type: Transform - parent: 388 - - type: Physics - canCollide: False - - type: InsideEntityStorage -- proto: ClothingUniformJumpsuitPyjamaSyndicatePink - entities: - - uid: 390 - components: - - type: Transform - parent: 388 - - type: Physics - canCollide: False - - type: InsideEntityStorage - - uid: 400 - components: - - type: Transform - parent: 388 - - type: Physics - canCollide: False - - type: InsideEntityStorage -- proto: ClothingUniformJumpsuitPyjamaSyndicateRed - entities: - - uid: 394 - components: - - type: Transform - parent: 388 - - type: Physics - canCollide: False - - type: InsideEntityStorage - - uid: 398 - components: - - type: Transform - parent: 388 - - type: Physics - canCollide: False - - type: InsideEntityStorage -- proto: CombatKnife - entities: - - uid: 376 - components: - - type: Transform - parent: 363 - - type: Physics - canCollide: False - - type: InsideEntityStorage - - uid: 377 - components: - - type: Transform - parent: 363 - - type: Physics - canCollide: False - - type: InsideEntityStorage -- proto: ComfyChair - entities: - - uid: 543 - components: - - type: Transform - pos: 1.5,2.5 - parent: 1 -- proto: ComputerCrewMonitoring - entities: - - uid: 548 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 12.5,0.5 - parent: 1 -- proto: ComputerMassMedia - entities: - - uid: 311 - components: - - type: Transform - pos: 9.5,2.5 - parent: 1 -- proto: ComputerRadar - entities: - - uid: 550 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 12.5,1.5 - parent: 1 - - type: RadarConsole - maxRange: 2048 - - type: WorldLoader - radius: 2048 -- proto: CrateFoodDonkpocketSavory - entities: - - uid: 551 - components: - - type: Transform - pos: -0.5,-5.5 - parent: 1 - - type: EntityStorage - air: - volume: 200 - immutable: False - temperature: 293.1496 - moles: - - 1.8856695 - - 7.0937095 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 -- proto: CrateServiceBureaucracy - entities: - - uid: 577 - components: - - type: Transform - pos: -1.5,4.5 - parent: 1 -- proto: CrowbarRed - entities: - - uid: 378 - components: - - type: Transform - parent: 363 - - type: Physics - canCollide: False - - type: InsideEntityStorage -- proto: DefibrillatorCabinetFilled - entities: - - uid: 552 - components: - - type: Transform - pos: -0.5,-1.5 - parent: 1 -- proto: DiagnosisReportPaper - entities: - - uid: 432 - components: - - type: Transform - pos: 8.472298,0.6316006 - parent: 1 - - uid: 554 - components: - - type: Transform - pos: 8.472298,0.6316006 - parent: 1 - - uid: 562 - components: - - type: Transform - pos: 8.472298,0.6316006 - parent: 1 - - uid: 564 - components: - - type: Transform - pos: 8.472298,0.6316006 - parent: 1 - - uid: 566 - components: - - type: Transform - pos: 8.472298,0.6316006 - parent: 1 -- proto: DisposalPipe - entities: - - uid: 599 - components: - - type: Transform - pos: 10.5,-3.5 - parent: 1 - - uid: 600 - components: - - type: Transform - pos: 10.5,-4.5 - parent: 1 - - uid: 601 - components: - - type: Transform - pos: 10.5,-5.5 - parent: 1 -- proto: DisposalTrunk - entities: - - uid: 602 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 10.5,-6.5 - parent: 1 - - uid: 603 - components: - - type: Transform - pos: 10.5,-2.5 - parent: 1 -- proto: DisposalUnit - entities: - - uid: 604 - components: - - type: MetaData - name: escape hatch - - type: Transform - pos: 10.5,-2.5 - parent: 1 -- proto: DrinkMugDog - entities: - - uid: 605 - components: - - type: Transform - rot: 6.283185307179586 rad - pos: 1.6752021,1.4137775 - parent: 1 -- proto: DrinkMugHeart - entities: - - uid: 606 - components: - - type: Transform - pos: 1.2883832,1.4021075 - parent: 1 -- proto: DrinkMugOne - entities: - - uid: 607 - components: - - type: Transform - pos: 1.6477582,1.7614824 - parent: 1 -- proto: DrinkMugRainbow - entities: - - uid: 608 - components: - - type: Transform - pos: 1.3040082,1.7302322 - parent: 1 -- proto: EncryptionKeyCommand - entities: - - uid: 613 - components: - - type: Transform - parent: 612 - - type: Physics - canCollide: False -- proto: EncryptionKeyStationMaster - entities: - - uid: 614 - components: - - type: Transform - parent: 612 - - type: Physics - canCollide: False -- proto: EncryptionKeySyndie - entities: - - uid: 615 - components: - - type: Transform - parent: 612 - - type: Physics - canCollide: False -- proto: ExtinguisherCabinet - entities: - - uid: 619 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 6.5,0.5 - parent: 1 -- proto: FaxMachineListeningSyndie - entities: - - uid: 368 - components: - - type: Transform - pos: 8.5,2.5 - parent: 1 -- proto: filingCabinetDrawerRandom - entities: - - uid: 567 - components: - - type: Transform - pos: 9.5,0.5 - parent: 1 -- proto: FireAlarm - entities: - - uid: 621 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 3.5,-1.5 - parent: 1 - - type: DeviceList - devices: - - 628 - - 629 - - 622 - - 623 - - 624 - - 632 - - 631 - - 625 - - 627 - - 633 - - type: AtmosDevice - joinedGrid: 1 -- proto: Firelock - entities: - - uid: 622 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 5.5,-0.5 - parent: 1 - - uid: 623 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 7.5,-0.5 - parent: 1 - - uid: 624 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 6.5,-1.5 - parent: 1 - - uid: 625 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 5.5,2.5 - parent: 1 - - uid: 626 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 3.5,4.5 - parent: 1 - - uid: 627 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 2.5,5.5 - parent: 1 - - uid: 628 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 0.5,3.5 - parent: 1 - - uid: 629 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 0.5,-0.5 - parent: 1 - - uid: 630 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: -1.5,-1.5 - parent: 1 - - uid: 631 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 2.5,-5.5 - parent: 1 - - uid: 632 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 2.5,-1.5 - parent: 1 - - uid: 633 - components: - - type: Transform - pos: 9.5,-3.5 - parent: 1 -- proto: ForensicReportPaper - entities: - - uid: 427 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 8.519173,0.6784754 - parent: 1 - - uid: 555 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 8.519173,0.6784754 - parent: 1 - - uid: 557 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 8.519173,0.6784754 - parent: 1 - - uid: 561 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 8.519173,0.6784754 - parent: 1 - - uid: 563 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 8.519173,0.6784754 - parent: 1 -- proto: GasPassiveVent - entities: - - uid: 634 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: -5.5,1.5 - parent: 1 - - type: AtmosDevice - joinedGrid: 1 - - type: AtmosPipeColor - color: '#FF1212FF' - - uid: 635 - components: - - type: Transform - pos: 6.5,6.5 - parent: 1 - - type: AtmosDevice - joinedGrid: 1 - - type: AtmosPipeColor - color: '#FF1212FF' - - uid: 636 - components: - - type: Transform - pos: 4.5,10.5 - parent: 1 - - type: AtmosDevice - joinedGrid: 1 - - type: AtmosPipeColor - color: '#FF1212FF' - - uid: 637 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 14.5,-0.5 - parent: 1 - - type: AtmosDevice - joinedGrid: 1 - - type: AtmosPipeColor - color: '#FF1212FF' - - uid: 638 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 13.5,-6.5 - parent: 1 - - type: AtmosDevice - joinedGrid: 1 - - type: AtmosPipeColor - color: '#FF1212FF' - - uid: 639 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 8.5,-7.5 - parent: 1 - - type: AtmosDevice - joinedGrid: 1 - - type: AtmosPipeColor - color: '#FF1212FF' - - uid: 640 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 4.5,-10.5 - parent: 1 - - type: AtmosDevice - joinedGrid: 1 - - type: AtmosPipeColor - color: '#FF1212FF' - - uid: 641 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: -4.5,-3.5 - parent: 1 - - type: AtmosDevice - joinedGrid: 1 - - type: AtmosPipeColor - color: '#FF1212FF' - - uid: 642 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: -4.5,5.5 - parent: 1 - - type: AtmosDevice - joinedGrid: 1 - - type: AtmosPipeColor - color: '#FF1212FF' -- proto: GasPipeBend - entities: - - uid: 643 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: -3.5,0.5 - parent: 1 - - type: AtmosPipeColor - color: '#FF1212FF' - - uid: 644 - components: - - type: Transform - pos: -3.5,1.5 - parent: 1 - - type: AtmosPipeColor - color: '#FF1212FF' - - uid: 645 - components: - - type: Transform - pos: 6.5,2.5 - parent: 1 - - type: AtmosPipeColor - color: '#0335FCFF' - - uid: 646 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: -1.5,-0.5 - parent: 1 - - type: AtmosPipeColor - color: '#0335FCFF' - - uid: 647 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 2.5,-6.5 - parent: 1 - - type: AtmosPipeColor - color: '#0335FCFF' - - uid: 648 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 6.5,-3.5 - parent: 1 - - type: AtmosPipeColor - color: '#0335FCFF' - - uid: 649 - components: - - type: Transform - pos: 10.5,-0.5 - parent: 1 - - type: AtmosPipeColor - color: '#0335FCFF' - - uid: 650 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 10.5,-3.5 - parent: 1 - - type: AtmosPipeColor - color: '#0335FCFF' -- proto: GasPipeFourway - entities: - - uid: 651 - components: - - type: Transform - pos: 2.5,-0.5 - parent: 1 - - type: AtmosPipeColor - color: '#0335FCFF' -- proto: GasPipeStraight - entities: - - uid: 652 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 1.5,4.5 - parent: 1 - - type: AtmosPipeColor - color: '#0335FCFF' - - uid: 653 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 0.5,3.5 - parent: 1 - - type: AtmosPipeColor - color: '#0335FCFF' - - uid: 654 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: -4.5,1.5 - parent: 1 - - type: AtmosPipeColor - color: '#FF1212FF' - - uid: 655 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: -2.5,0.5 - parent: 1 - - type: AtmosPipeColor - color: '#FF1212FF' - - uid: 656 - components: - - type: Transform - pos: 4.5,-7.5 - parent: 1 - - type: AtmosPipeColor - color: '#FF1212FF' - - uid: 657 - components: - - type: Transform - pos: 4.5,-8.5 - parent: 1 - - type: AtmosPipeColor - color: '#FF1212FF' - - uid: 658 - components: - - type: Transform - pos: 4.5,-9.5 - parent: 1 - - type: AtmosPipeColor - color: '#FF1212FF' - - uid: 659 - components: - - type: Transform - pos: 8.5,-6.5 - parent: 1 - - type: AtmosPipeColor - color: '#FF1212FF' - - uid: 660 - components: - - type: Transform - pos: 8.5,-5.5 - parent: 1 - - type: AtmosPipeColor - color: '#FF1212FF' - - uid: 661 - components: - - type: Transform - pos: 8.5,-4.5 - parent: 1 - - type: AtmosPipeColor - color: '#FF1212FF' - - uid: 662 - components: - - type: Transform - pos: 13.5,-4.5 - parent: 1 - - type: AtmosPipeColor - color: '#FF1212FF' - - uid: 663 - components: - - type: Transform - pos: 13.5,-5.5 - parent: 1 - - type: AtmosPipeColor - color: '#FF1212FF' - - uid: 664 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 13.5,-0.5 - parent: 1 - - type: AtmosPipeColor - color: '#FF1212FF' - - uid: 665 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 12.5,-0.5 - parent: 1 - - type: AtmosPipeColor - color: '#FF1212FF' - - uid: 666 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 6.5,4.5 - parent: 1 - - type: AtmosPipeColor - color: '#FF1212FF' - - uid: 667 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 6.5,5.5 - parent: 1 - - type: AtmosPipeColor - color: '#FF1212FF' - - uid: 668 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 4.5,4.5 - parent: 1 - - type: AtmosPipeColor - color: '#FF1212FF' - - uid: 669 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 4.5,5.5 - parent: 1 - - type: AtmosPipeColor - color: '#FF1212FF' - - uid: 670 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 4.5,6.5 - parent: 1 - - type: AtmosPipeColor - color: '#FF1212FF' - - uid: 671 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 4.5,7.5 - parent: 1 - - type: AtmosPipeColor - color: '#FF1212FF' - - uid: 672 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 4.5,8.5 - parent: 1 - - type: AtmosPipeColor - color: '#FF1212FF' - - uid: 673 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 4.5,9.5 - parent: 1 - - type: AtmosPipeColor - color: '#FF1212FF' - - uid: 674 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 7.5,-0.5 - parent: 1 - - type: AtmosPipeColor - color: '#0335FCFF' - - uid: 675 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 5.5,-0.5 - parent: 1 - - type: AtmosPipeColor - color: '#0335FCFF' - - uid: 676 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 5.5,2.5 - parent: 1 - - type: AtmosPipeColor - color: '#0335FCFF' - - uid: 677 - components: - - type: Transform - pos: -1.5,-1.5 - parent: 1 - - type: AtmosPipeColor - color: '#0335FCFF' - - uid: 678 - components: - - type: Transform - pos: 2.5,-5.5 - parent: 1 - - type: AtmosPipeColor - color: '#0335FCFF' - - uid: 679 - components: - - type: Transform - pos: 2.5,-4.5 - parent: 1 - - type: AtmosPipeColor - color: '#0335FCFF' - - uid: 680 - components: - - type: Transform - pos: 2.5,-3.5 - parent: 1 - - type: AtmosPipeColor - color: '#0335FCFF' - - uid: 681 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: -2.5,-3.5 - parent: 1 - - type: AtmosPipeColor - color: '#FF1212FF' - - uid: 682 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: -3.5,-3.5 - parent: 1 - - type: AtmosPipeColor - color: '#FF1212FF' - - uid: 683 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: -2.5,5.5 - parent: 1 - - type: AtmosPipeColor - color: '#FF1212FF' - - uid: 684 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: -3.5,5.5 - parent: 1 - - type: AtmosPipeColor - color: '#FF1212FF' - - uid: 685 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 6.5,-2.5 - parent: 1 - - type: AtmosPipeColor - color: '#0335FCFF' - - uid: 686 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 6.5,-1.5 - parent: 1 - - type: AtmosPipeColor - color: '#0335FCFF' - - uid: 687 - components: - - type: Transform - pos: 10.5,-1.5 - parent: 1 - - type: AtmosPipeColor - color: '#0335FCFF' - - uid: 688 - components: - - type: Transform - pos: 10.5,-2.5 - parent: 1 - - type: AtmosPipeColor - color: '#0335FCFF' - - uid: 689 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 9.5,-0.5 - parent: 1 - - type: AtmosPipeColor - color: '#0335FCFF' - - uid: 690 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 4.5,2.5 - parent: 1 - - type: AtmosPipeColor - color: '#0335FCFF' - - uid: 691 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 3.5,2.5 - parent: 1 - - type: AtmosPipeColor - color: '#0335FCFF' - - uid: 692 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 2.5,1.5 - parent: 1 - - type: AtmosPipeColor - color: '#0335FCFF' - - uid: 693 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 2.5,0.5 - parent: 1 - - type: AtmosPipeColor - color: '#0335FCFF' - - uid: 694 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 1.5,-0.5 - parent: 1 - - type: AtmosPipeColor - color: '#0335FCFF' - - uid: 695 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 0.5,-0.5 - parent: 1 - - type: AtmosPipeColor - color: '#0335FCFF' - - uid: 696 - components: - - type: Transform - pos: 2.5,-1.5 - parent: 1 - - type: AtmosPipeColor - color: '#0335FCFF' - - uid: 697 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 3.5,-0.5 - parent: 1 - - type: AtmosPipeColor - color: '#0335FCFF' - - uid: 698 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 4.5,-0.5 - parent: 1 - - type: AtmosPipeColor - color: '#0335FCFF' -- proto: GasPipeTJunction - entities: - - uid: 699 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 1.5,3.5 - parent: 1 - - type: AtmosPipeColor - color: '#0335FCFF' - - uid: 700 - components: - - type: Transform - pos: 2.5,3.5 - parent: 1 - - type: AtmosPipeColor - color: '#0335FCFF' - - uid: 701 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 2.5,2.5 - parent: 1 - - type: AtmosPipeColor - color: '#0335FCFF' - - uid: 702 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: -0.5,-0.5 - parent: 1 - - type: AtmosPipeColor - color: '#0335FCFF' - - uid: 703 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 2.5,-2.5 - parent: 1 - - type: AtmosPipeColor - color: '#0335FCFF' - - uid: 704 - components: - - type: Transform - pos: 6.5,-0.5 - parent: 1 - - type: AtmosPipeColor - color: '#0335FCFF' - - uid: 705 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 8.5,-0.5 - parent: 1 - - type: AtmosPipeColor - color: '#0335FCFF' -- proto: GasPort - entities: - - uid: 706 - components: - - type: Transform - pos: 1.5,5.5 - parent: 1 - - type: AtmosDevice - joinedGrid: 1 - - type: AtmosPipeColor - color: '#0335FCFF' -- proto: GasVentPump - entities: - - uid: 384 - components: - - type: Transform - pos: 8.5,0.5 - parent: 1 - - type: DeviceNetwork - deviceLists: - - 576 - - type: AtmosDevice - joinedGrid: 1 - - type: AtmosPipeColor - color: '#0335FCFF' - - uid: 707 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: -0.5,3.5 - parent: 1 - - type: DeviceNetwork - deviceLists: - - 576 - - type: AtmosDevice - joinedGrid: 1 - - type: AtmosPipeColor - color: '#0335FCFF' - - uid: 708 - components: - - type: Transform - pos: -0.5,0.5 - parent: 1 - - type: DeviceNetwork - deviceLists: - - 576 - - type: AtmosDevice - joinedGrid: 1 - - type: AtmosPipeColor - color: '#0335FCFF' - - uid: 709 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: -1.5,-2.5 - parent: 1 - - type: DeviceNetwork - deviceLists: - - 576 - - type: AtmosDevice - joinedGrid: 1 - - type: AtmosPipeColor - color: '#0335FCFF' - - uid: 710 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 3.5,-2.5 - parent: 1 - - type: DeviceNetwork - deviceLists: - - 576 - - type: AtmosDevice - joinedGrid: 1 - - type: AtmosPipeColor - color: '#0335FCFF' - - uid: 711 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 3.5,-6.5 - parent: 1 - - type: DeviceNetwork - deviceLists: - - 576 - - type: AtmosDevice - joinedGrid: 1 - - type: AtmosPipeColor - color: '#0335FCFF' - - uid: 712 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 7.5,-3.5 - parent: 1 - - type: DeviceNetwork - deviceLists: - - 576 - - type: AtmosDevice - joinedGrid: 1 - - type: AtmosPipeColor - color: '#0335FCFF' - - uid: 713 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 6.5,1.5 - parent: 1 - - type: DeviceNetwork - deviceLists: - - 576 - - type: AtmosDevice - joinedGrid: 1 - - type: AtmosPipeColor - color: '#0335FCFF' - - uid: 714 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 3.5,3.5 - parent: 1 - - type: DeviceNetwork - deviceLists: - - 576 - - type: AtmosDevice - joinedGrid: 1 - - type: AtmosPipeColor - color: '#0335FCFF' - - uid: 716 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 11.5,-3.5 - parent: 1 - - type: DeviceNetwork - deviceLists: - - 576 - - type: AtmosDevice - joinedGrid: 1 - - type: AtmosPipeColor - color: '#0335FCFF' - - uid: 717 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: -0.5,-3.5 - parent: 1 - - type: AtmosDevice - joinedGrid: 1 -- proto: GasVentScrubber - entities: - - uid: 718 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 6.5,3.5 - parent: 1 - - type: DeviceNetwork - deviceLists: - - 576 - - type: AtmosDevice - joinedGrid: 1 - - type: AtmosPipeColor - color: '#FF1212FF' - - uid: 719 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: -1.5,5.5 - parent: 1 - - type: DeviceNetwork - deviceLists: - - 576 - - type: AtmosDevice - joinedGrid: 1 - - type: AtmosPipeColor - color: '#FF1212FF' - - uid: 720 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: -1.5,0.5 - parent: 1 - - type: DeviceNetwork - deviceLists: - - 576 - - type: AtmosDevice - joinedGrid: 1 - - type: AtmosPipeColor - color: '#FF1212FF' - - uid: 721 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: -1.5,-3.5 - parent: 1 - - type: DeviceNetwork - deviceLists: - - 576 - - type: AtmosDevice - joinedGrid: 1 - - type: AtmosPipeColor - color: '#FF1212FF' - - uid: 722 - components: - - type: Transform - pos: 4.5,-6.5 - parent: 1 - - type: DeviceNetwork - deviceLists: - - 576 - - type: AtmosDevice - joinedGrid: 1 - - type: AtmosPipeColor - color: '#FF1212FF' - - uid: 723 - components: - - type: Transform - pos: 8.5,-3.5 - parent: 1 - - type: DeviceNetwork - deviceLists: - - 576 - - type: AtmosDevice - joinedGrid: 1 - - type: AtmosPipeColor - color: '#FF1212FF' - - uid: 724 - components: - - type: Transform - pos: 13.5,-3.5 - parent: 1 - - type: DeviceNetwork - deviceLists: - - 576 - - type: AtmosDevice - joinedGrid: 1 - - type: AtmosPipeColor - color: '#FF1212FF' - - uid: 725 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 11.5,-0.5 - parent: 1 - - type: DeviceNetwork - deviceLists: - - 576 - - type: AtmosDevice - joinedGrid: 1 - - type: AtmosPipeColor - color: '#FF1212FF' - - uid: 726 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 4.5,3.5 - parent: 1 - - type: DeviceNetwork - deviceLists: - - 576 - - type: AtmosDevice - joinedGrid: 1 - - type: AtmosPipeColor - color: '#FF1212FF' -- proto: GeneratorWallmountAPU - entities: - - uid: 727 - components: - - type: Transform - pos: -1.5,6.5 - parent: 1 - - uid: 728 - components: - - type: Transform - pos: -0.5,6.5 - parent: 1 - - uid: 729 - components: - - type: Transform - pos: -2.5,5.5 - parent: 1 - - uid: 730 - components: - - type: Transform - pos: -2.5,4.5 - parent: 1 - - uid: 732 - components: - - type: Transform - pos: -1.5,2.5 - parent: 1 - - uid: 733 - components: - - type: Transform - pos: -0.5,2.5 - parent: 1 -- proto: GravityGeneratorMini - entities: - - uid: 734 - components: - - type: Transform - pos: -0.5,5.5 - parent: 1 -- proto: Grille - entities: - - uid: 735 - components: - - type: Transform - pos: 10.5,-1.5 - parent: 1 - - uid: 736 - components: - - type: Transform - pos: 12.5,-5.5 - parent: 1 - - uid: 737 - components: - - type: Transform - pos: 13.5,-5.5 - parent: 1 - - uid: 738 - components: - - type: Transform - pos: 13.5,0.5 - parent: 1 - - uid: 739 - components: - - type: Transform - pos: 13.5,-0.5 - parent: 1 - - uid: 740 - components: - - type: Transform - pos: 13.5,1.5 - parent: 1 - - uid: 741 - components: - - type: Transform - pos: 13.5,2.5 - parent: 1 - - uid: 742 - components: - - type: Transform - pos: 7.5,-6.5 - parent: 1 - - uid: 743 - components: - - type: Transform - pos: 10.5,-5.5 - parent: 1 - - uid: 744 - components: - - type: Transform - pos: 8.5,-6.5 - parent: 1 - - uid: 745 - components: - - type: Transform - pos: 6.5,-6.5 - parent: 1 - - uid: 746 - components: - - type: Transform - pos: 11.5,-5.5 - parent: 1 -- proto: HospitalCurtainsOpen - entities: - - uid: 747 - components: - - type: Transform - pos: 6.5,3.5 - parent: 1 - - uid: 748 - components: - - type: Transform - pos: 6.5,1.5 - parent: 1 - - uid: 749 - components: - - type: Transform - pos: -1.5,1.5 - parent: 1 - - uid: 750 - components: - - type: Transform - pos: -0.5,1.5 - parent: 1 -- proto: hydroponicsTray - entities: - - uid: 751 - components: - - type: Transform - pos: 12.5,-4.5 - parent: 1 - - uid: 752 - components: - - type: Transform - pos: 13.5,-4.5 - parent: 1 - - uid: 753 - components: - - type: Transform - pos: 11.5,-4.5 - parent: 1 - - uid: 754 - components: - - type: Transform - pos: 10.5,-4.5 - parent: 1 -- proto: KitchenMicrowave - entities: - - uid: 755 - components: - - type: Transform - pos: 1.5,-2.5 - parent: 1 -- proto: LandMineExplosive - entities: - - uid: 756 - components: - - type: Transform - pos: 12.446557,-9.501101 - parent: 1 - - uid: 757 - components: - - type: Transform - pos: 17.449137,2.529461 - parent: 1 -- proto: LockerSyndicatePersonal - entities: - - uid: 363 - components: - - type: Transform - pos: -1.5,-5.5 - parent: 1 - - type: EntityStorage - air: - volume: 200 - immutable: False - temperature: 293.1496 - moles: - - 1.8978093 - - 7.139378 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - type: ContainerContainer - containers: - entity_storage: !type:Container - showEnts: False - occludes: True - ents: - - 378 - - 377 - - 379 - - 367 - - 366 - - 380 - - 376 - - 365 - - 118 - paper_label: !type:ContainerSlot - showEnts: False - occludes: True - ent: null -- proto: MedkitCombatFilled - entities: - - uid: 758 - components: - - type: Transform - pos: -0.28039455,-3.70372 - parent: 1 -- proto: MedkitFilled - entities: - - uid: 759 - components: - - type: Transform - pos: -0.57726955,-3.500595 - parent: 1 -- proto: Mirror - entities: - - uid: 957 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: -2.5,-3.5 - parent: 1 -- proto: NukeDiskFake - entities: - - uid: 760 - components: - - type: Transform - pos: 16.778532,-0.7526432 - parent: 1 -- proto: PaperArtifactAnalyzer - entities: - - uid: 424 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 8.456674,0.66285014 - parent: 1 - - uid: 428 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 8.456674,0.66285014 - parent: 1 - - uid: 433 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 8.456674,0.66285014 - parent: 1 - - uid: 542 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 8.456674,0.66285014 - parent: 1 - - uid: 546 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 8.456674,0.66285014 - parent: 1 -- proto: PaperBin10 - entities: - - uid: 87 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 8.5,0.5 - parent: 1 - - uid: 761 - components: - - type: Transform - pos: 7.5,-5.5 - parent: 1 -- proto: PaperCaptainsThoughts - entities: - - uid: 430 - components: - - type: Transform - pos: 8.519173,0.64722514 - parent: 1 - - uid: 437 - components: - - type: Transform - pos: 8.519173,0.64722514 - parent: 1 - - uid: 537 - components: - - type: Transform - pos: 8.519173,0.64722514 - parent: 1 - - uid: 547 - components: - - type: Transform - pos: 8.519173,0.64722514 - parent: 1 - - uid: 553 - components: - - type: Transform - pos: 8.519173,0.64722514 - parent: 1 -- proto: PaperCargoBountyManifest - entities: - - uid: 426 - components: - - type: Transform - pos: 8.503549,0.6316006 - parent: 1 - - uid: 429 - components: - - type: Transform - pos: 8.503549,0.6316006 - parent: 1 - - uid: 434 - components: - - type: Transform - pos: 8.503549,0.6316006 - parent: 1 - - uid: 435 - components: - - type: Transform - pos: 8.503549,0.6316006 - parent: 1 - - uid: 544 - components: - - type: Transform - pos: 8.503549,0.6316006 - parent: 1 -- proto: PaperCargoInvoice - entities: - - uid: 431 - components: - - type: Transform - pos: 8.487924,0.6316004 - parent: 1 - - uid: 436 - components: - - type: Transform - pos: 8.487924,0.6316004 - parent: 1 - - uid: 438 - components: - - type: Transform - pos: 8.487924,0.6316004 - parent: 1 - - uid: 439 - components: - - type: Transform - pos: 8.487924,0.6316004 - parent: 1 - - uid: 538 - components: - - type: Transform - pos: 8.487924,0.6316004 - parent: 1 -- proto: PaperCNCSheet - entities: - - uid: 539 - components: - - type: Transform - pos: 8.503549,0.6316006 - parent: 1 - - uid: 545 - components: - - type: Transform - pos: 8.503549,0.6316006 - parent: 1 - - uid: 556 - components: - - type: Transform - pos: 8.503549,0.6316006 - parent: 1 - - uid: 559 - components: - - type: Transform - pos: 8.503549,0.6316006 - parent: 1 - - uid: 560 - components: - - type: Transform - pos: 8.503549,0.6316006 - parent: 1 -- proto: PlushieNuke - entities: - - uid: 762 - components: - - type: Transform - pos: 6.565984,3.4567177 - parent: 1 - - uid: 763 - components: - - type: Transform - pos: 16.527655,-0.5029521 - parent: 1 -- proto: PlushieRouny - entities: - - uid: 764 - components: - - type: Transform - pos: 6.487859,1.4254675 - parent: 1 -- proto: PosterContrabandDonk - entities: - - uid: 765 - components: - - type: Transform - pos: 1.5,-1.5 - parent: 1 -- proto: PosterContrabandSyndicateRecruitment - entities: - - uid: 766 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 4.5,6.5 - parent: 1 -- proto: PosterLegitHotDonkExplosion - entities: - - uid: 767 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: -0.5,-4.5 - parent: 1 -- proto: PosterLegitNoERP - entities: - - uid: 578 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 11.5,3.5 - parent: 1 -- proto: PowerCellRecharger - entities: - - uid: 768 - components: - - type: Transform - pos: 8.5,1.5 - parent: 1 -- proto: Poweredlight - entities: - - uid: 18 - components: - - type: Transform - pos: 6.5,3.5 - parent: 1 - - uid: 19 - components: - - type: Transform - pos: 12.5,-2.5 - parent: 1 - - type: DeviceLinkSink - links: - - 46 - - uid: 21 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 4.5,1.5 - parent: 1 - - type: DeviceLinkSink - links: - - 46 - - uid: 375 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: -0.5,0.5 - parent: 1 - - type: DeviceLinkSink - links: - - 46 - - uid: 731 - components: - - type: Transform - pos: 3.5,-2.5 - parent: 1 - - type: DeviceLinkSink - links: - - 46 - - uid: 896 - components: - - type: Transform - pos: 0.5,-6.5 - parent: 1 - - type: DeviceLinkSink - links: - - 46 - - uid: 917 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 1.5,1.5 - parent: 1 - - type: DeviceLinkSink - links: - - 46 - - uid: 956 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 6.5,-3.5 - parent: 1 - - type: DeviceLinkSink - links: - - 46 - - uid: 958 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 8.5,1.5 - parent: 1 - - type: DeviceLinkSink - links: - - 46 - - uid: 963 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: -0.5,4.5 - parent: 1 - - type: DeviceLinkSink - links: - - 46 -- proto: PoweredLightColoredRed - entities: - - uid: 20 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 6.5,-4.5 - parent: 1 - - type: DeviceLinkSink - links: - - 46 - - uid: 22 - components: - - type: Transform - pos: 12.5,2.5 - parent: 1 - - type: DeviceLinkSink - links: - - 46 - - uid: 23 - components: - - type: Transform - pos: -1.5,-5.5 - parent: 1 - - type: DeviceLinkSink - links: - - 46 - - uid: 769 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: -1.5,-0.5 - parent: 1 - - type: DeviceLinkSink - links: - - 46 - - type: Timer - - uid: 770 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: -1.5,4.5 - parent: 1 - - type: DeviceLinkSink - links: - - 46 - - uid: 771 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 1.5,2.5 - parent: 1 - - type: DeviceLinkSink - links: - - 46 - - type: Timer - - uid: 772 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 1.5,-3.5 - parent: 1 - - type: DeviceLinkSink - links: - - 46 - - uid: 773 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 4.5,0.5 - parent: 1 - - type: DeviceLinkSink - links: - - 46 - - type: Timer - - uid: 774 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 8.5,0.5 - parent: 1 - - type: DeviceLinkSink - links: - - 46 - - uid: 775 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 4.5,-6.5 - parent: 1 - - type: DeviceLinkSink - links: - - 46 - - uid: 952 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 6.5,1.5 - parent: 1 - - uid: 955 - components: - - type: Transform - pos: 11.5,-2.5 - parent: 1 - - type: DeviceLinkSink - links: - - 46 -- proto: Rack - entities: - - uid: 776 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: -1.5,5.5 - parent: 1 - - uid: 777 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 4.5,5.5 - parent: 1 - - uid: 778 - components: - - type: Transform - pos: -0.5,-3.5 - parent: 1 -- proto: RandomArcade - entities: - - uid: 779 - components: - - type: Transform - pos: 7.5,-2.5 - parent: 1 -- proto: RandomPosterContraband - entities: - - uid: 780 - components: - - type: Transform - pos: 9.5,3.5 - parent: 1 - - uid: 781 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 0.5,0.5 - parent: 1 - - uid: 782 - components: - - type: Transform - pos: 5.5,0.5 - parent: 1 - - uid: 784 - components: - - type: Transform - pos: -1.5,-4.5 - parent: 1 - - uid: 785 - components: - - type: Transform - pos: 14.5,-3.5 - parent: 1 - - uid: 786 - components: - - type: Transform - pos: 5.5,-3.5 - parent: 1 -- proto: RandomVendingDrinks - entities: - - uid: 787 - components: - - type: Transform - pos: 4.5,-2.5 - parent: 1 - - type: Emagged -- proto: RandomVendingSnacks - entities: - - uid: 788 - components: - - type: Transform - pos: 4.5,-3.5 - parent: 1 - - type: Emagged -- proto: ReinforcedPlasmaWindow - entities: - - uid: 789 - components: - - type: Transform - pos: 10.5,-1.5 - parent: 1 - - uid: 790 - components: - - type: Transform - pos: 10.5,-5.5 - parent: 1 - - uid: 791 - components: - - type: Transform - pos: 12.5,-5.5 - parent: 1 - - uid: 792 - components: - - type: Transform - pos: 13.5,-0.5 - parent: 1 - - uid: 793 - components: - - type: Transform - pos: 13.5,0.5 - parent: 1 - - uid: 794 - components: - - type: Transform - pos: 13.5,2.5 - parent: 1 - - uid: 795 - components: - - type: Transform - pos: 13.5,1.5 - parent: 1 - - uid: 796 - components: - - type: Transform - pos: 11.5,-5.5 - parent: 1 - - uid: 797 - components: - - type: Transform - pos: 13.5,-5.5 - parent: 1 - - uid: 798 - components: - - type: Transform - pos: 6.5,-6.5 - parent: 1 - - uid: 799 - components: - - type: Transform - pos: 7.5,-6.5 - parent: 1 - - uid: 800 - components: - - type: Transform - pos: 8.5,-6.5 - parent: 1 -- proto: RubberStampApproved - entities: - - uid: 407 - components: - - type: Transform - parent: 405 - - type: Physics - canCollide: False - - type: InsideEntityStorage -- proto: RubberStampCaptain - entities: - - uid: 417 - components: - - type: Transform - parent: 405 - - type: Physics - canCollide: False - - type: InsideEntityStorage -- proto: RubberStampCE - entities: - - uid: 413 - components: - - type: Transform - parent: 405 - - type: Physics - canCollide: False - - type: InsideEntityStorage -- proto: RubberStampChaplain - entities: - - uid: 416 - components: - - type: Transform - parent: 405 - - type: Physics - canCollide: False - - type: InsideEntityStorage -- proto: RubberStampClown - entities: - - uid: 414 - components: - - type: Transform - parent: 405 - - type: Physics - canCollide: False - - type: InsideEntityStorage -- proto: RubberStampCMO - entities: - - uid: 412 - components: - - type: Transform - parent: 405 - - type: Physics - canCollide: False - - type: InsideEntityStorage -- proto: RubberStampDenied - entities: - - uid: 411 - components: - - type: Transform - parent: 405 - - type: Physics - canCollide: False - - type: InsideEntityStorage -- proto: RubberStampHop - entities: - - uid: 419 - components: - - type: Transform - parent: 405 - - type: Physics - canCollide: False - - type: InsideEntityStorage -- proto: RubberStampHos - entities: - - uid: 409 - components: - - type: Transform - parent: 405 - - type: Physics - canCollide: False - - type: InsideEntityStorage -- proto: RubberStampMantis - entities: - - uid: 415 - components: - - type: Transform - parent: 405 - - type: Physics - canCollide: False - - type: InsideEntityStorage -- proto: RubberStampMime - entities: - - uid: 420 - components: - - type: Transform - parent: 405 - - type: Physics - canCollide: False - - type: InsideEntityStorage -- proto: RubberStampQm - entities: - - uid: 408 - components: - - type: Transform - parent: 405 - - type: Physics - canCollide: False - - type: InsideEntityStorage -- proto: RubberStampRd - entities: - - uid: 418 - components: - - type: Transform - parent: 405 - - type: Physics - canCollide: False - - type: InsideEntityStorage -- proto: RubberStampSyndicate - entities: - - uid: 410 - components: - - type: Transform - parent: 405 - - type: Physics - canCollide: False - - type: InsideEntityStorage -- proto: RubberStampWarden - entities: - - uid: 406 - components: - - type: Transform - parent: 405 - - type: Physics - canCollide: False - - type: InsideEntityStorage -- proto: SalvageLorePaperGamingSpawner - entities: - - uid: 801 - components: - - type: Transform - pos: 12.5,-9.5 - parent: 1 -- proto: SheetPlasteel - entities: - - uid: 802 - components: - - type: Transform - pos: -1.4864182,5.563302 - parent: 1 -- proto: SheetRPGlass - entities: - - uid: 803 - components: - - type: Transform - pos: -1.2364185,5.563302 - parent: 1 -- proto: SheetSteel - entities: - - uid: 804 - components: - - type: Transform - pos: -1.6895431,5.500802 - parent: 1 -- proto: SignalButton - entities: - - uid: 805 - components: - - type: MetaData - name: bridge blast doors button - - type: Transform - rot: 3.141592653589793 rad - pos: 11.5,-1.5 - parent: 1 - - type: DeviceLinkSource - linkedPorts: - 354: - - Pressed: Toggle - 352: - - Pressed: Toggle - 351: - - Pressed: Toggle - 353: - - Pressed: Toggle - 361: - - Pressed: Toggle - - uid: 806 - components: - - type: MetaData - desc: A note taped to it reads, "Be sure to keep it bolted, else those crazy guards might try to drink vacuum" - name: external bolts button - - type: Transform - pos: 4.5,4.5 - parent: 1 - - type: DeviceLinkSource - linkedPorts: - 4: - - Pressed: DoorBolt - - uid: 807 - components: - - type: MetaData - name: rec room blast shields button - - type: Transform - rot: 1.5707963267948966 rad - pos: 5.5,-2.5 - parent: 1 - - type: DeviceLinkSource - linkedPorts: - 350: - - Pressed: Toggle - 355: - - Pressed: Toggle - 356: - - Pressed: Toggle - 357: - - Pressed: Toggle - 358: - - Pressed: Toggle - 359: - - Pressed: Toggle - 360: - - Pressed: Toggle -- proto: SignalSwitchDirectional - entities: - - uid: 46 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 4.5,-1.5 - parent: 1 - - type: DeviceLinkSource - linkedPorts: - 773: - - On: Off - - Off: On - 21: - - On: On - - Off: Off - 917: - - On: On - - Off: Off - 771: - - On: Off - - Off: On - 770: - - Off: On - - On: Off - 963: - - On: On - - Off: Off - 769: - - Off: On - - On: Off - 375: - - On: On - - Off: Off - 774: - - Off: On - - On: Off - 958: - - On: On - - Off: Off - 731: - - On: On - - Off: Off - 772: - - On: Off - - Off: On - 896: - - On: On - - Off: Off - 775: - - On: Off - - Off: On - 956: - - On: On - - Off: Off - 20: - - On: Off - - Off: On - 23: - - On: Off - - Off: On - 955: - - On: Off - - Off: On - 19: - - On: On - - Off: Off - 22: - - On: Off - - Off: On -- proto: SinkStemlessWater - entities: - - uid: 808 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: -1.5,-3.5 - parent: 1 -- proto: SoapSyndie - entities: - - uid: 809 - components: - - type: Transform - rot: 6.283185307179586 rad - pos: -1.5109272,-2.2405348 - parent: 1 -- proto: soda_dispenser - entities: - - uid: 810 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 1.5,-4.5 - parent: 1 - - type: Emagged -- proto: SpawnMobRadioGuard - entities: - - uid: 811 - components: - - type: Transform - pos: 9.5,-0.5 - parent: 1 - - uid: 812 - components: - - type: Transform - pos: 2.5,2.5 - parent: 1 -- proto: SpawnPointGhostSyndicateListener - entities: - - uid: 813 - components: - - type: Transform - pos: -0.5,1.5 - parent: 1 - - uid: 814 - components: - - type: Transform - pos: -1.5,1.5 - parent: 1 -- proto: StationMapBroken - entities: - - uid: 815 - components: - - type: Transform - pos: 1.5,4.5 - parent: 1 -- proto: SubstationWallBasic - entities: - - uid: 816 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 0.5,5.5 - parent: 1 -- proto: SuitStorageEVASyndicate - entities: - - uid: 611 - components: - - type: Transform - pos: -0.5,-7.5 - parent: 1 - - uid: 817 - components: - - type: Transform - pos: -1.5,-7.5 - parent: 1 - - type: EntityStorage - air: - volume: 200 - immutable: False - temperature: 293.14673 - moles: - - 1.7459903 - - 6.568249 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 -- proto: SyndicateMonitoringServer - entities: - - uid: 338 - components: - - type: Transform - pos: 2.5,-7.5 - parent: 1 - - type: SingletonDeviceNetServer - active: False - available: False -- proto: SyndicatePersonalAI - entities: - - uid: 819 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 7.4855704,-4.514364 - parent: 1 -- proto: SyndieHandyFlag - entities: - - uid: 951 - components: - - type: Transform - pos: 1.9013485,-7 - parent: 1 -- proto: SynthesizerInstrument - entities: - - uid: 820 - components: - - type: Transform - pos: 6.5734386,-5.497705 - parent: 1 -- proto: TableCarpet - entities: - - uid: 821 - components: - - type: Transform - pos: 7.5,-5.5 - parent: 1 - - uid: 822 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 1.5,1.5 - parent: 1 - - uid: 823 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 7.5,-4.5 - parent: 1 -- proto: TableGlass - entities: - - uid: 824 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 1.5,-2.5 - parent: 1 - - uid: 825 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 1.5,-3.5 - parent: 1 - - uid: 826 - components: - - type: Transform - pos: 1.5,-4.5 - parent: 1 -- proto: TableWoodReinforced - entities: - - uid: 382 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 8.5,0.5 - parent: 1 - - uid: 383 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 12.5,2.5 - parent: 1 - - uid: 385 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 8.5,1.5 - parent: 1 - - uid: 386 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 12.5,-0.5 - parent: 1 - - uid: 387 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 8.5,2.5 - parent: 1 - - uid: 568 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 4.5,1.5 - parent: 1 -- proto: TelecomServerFilled - entities: - - uid: 612 - components: - - type: Transform - pos: 1.5,-7.5 - parent: 1 - - type: ContainerContainer - containers: - key_slots: !type:Container - showEnts: False - occludes: True - ents: - - 613 - - 614 - - 615 - machine_board: !type:Container - showEnts: False - occludes: True - ents: [] - machine_parts: !type:Container - showEnts: False - occludes: True - ents: [] -- proto: ToiletEmpty - entities: - - uid: 832 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: -0.5,-2.5 - parent: 1 -- proto: ToolboxSyndicateFilled - entities: - - uid: 833 - components: - - type: Transform - pos: 4.5174317,5.701203 - parent: 1 - - uid: 834 - components: - - type: Transform - pos: 4.5330567,5.310578 - parent: 1 -- proto: ToyFigurineNukie - entities: - - uid: 835 - components: - - type: Transform - pos: 12.208444,2.8347092 - parent: 1 -- proto: ToyFigurineNukieCommander - entities: - - uid: 836 - components: - - type: Transform - pos: 12.791773,2.838885 - parent: 1 -- proto: ToyFigurineNukieElite - entities: - - uid: 837 - components: - - type: Transform - pos: 12.489692,2.4701266 - parent: 1 -- proto: ToyNuke - entities: - - uid: 838 - components: - - type: Transform - pos: 16.198624,-0.765656 - parent: 1 -- proto: VendingMachineBooze - entities: - - uid: 839 - components: - - type: Transform - pos: 4.5,-4.5 - parent: 1 - - type: Emagged -- proto: VendingMachineCigs - entities: - - uid: 565 - components: - - type: Transform - pos: 4.5,0.5 - parent: 1 -- proto: VendingMachineGames - entities: - - uid: 840 - components: - - type: Transform - pos: 8.5,-2.5 - parent: 1 - - type: Emagged -- proto: VendingMachineNutri - entities: - - uid: 841 - components: - - type: Transform - pos: 12.5,-2.5 - parent: 1 - - type: Emagged -- proto: VendingMachineSeedsUnlocked - entities: - - uid: 842 - components: - - type: Transform - pos: 11.5,-2.5 - parent: 1 - - type: Emagged -- proto: VendingMachineSyndieDrobe - entities: - - uid: 843 - components: - - type: Transform - pos: -2.5,0.5 - parent: 1 - - type: Emagged -- proto: VendingMachineTankDispenserEVA - entities: - - uid: 844 - components: - - type: Transform - pos: -1.5,3.5 - parent: 1 -- proto: WallPlastitanium - entities: - - uid: 54 - components: - - type: Transform - pos: 5.5,-8.5 - parent: 1 - - uid: 332 - components: - - type: Transform - pos: 10.5,3.5 - parent: 1 - - uid: 335 - components: - - type: Transform - pos: 2.5,-8.5 - parent: 1 - - uid: 336 - components: - - type: Transform - pos: 1.5,-8.5 - parent: 1 - - uid: 340 - components: - - type: Transform - pos: 0.5,-8.5 - parent: 1 - - uid: 425 - components: - - type: Transform - pos: 4.5,-8.5 - parent: 1 - - uid: 617 - components: - - type: Transform - pos: 3.5,-8.5 - parent: 1 - - uid: 845 - components: - - type: Transform - pos: -2.5,-8.5 - parent: 1 - - uid: 846 - components: - - type: Transform - pos: -1.5,-8.5 - parent: 1 - - uid: 847 - components: - - type: Transform - pos: -0.5,-8.5 - parent: 1 - - uid: 848 - components: - - type: Transform - pos: -0.5,-1.5 - parent: 1 - - uid: 849 - components: - - type: Transform - pos: -2.5,6.5 - parent: 1 - - uid: 850 - components: - - type: Transform - pos: 3.5,-1.5 - parent: 1 - - uid: 851 - components: - - type: Transform - pos: 14.5,-2.5 - parent: 1 - - uid: 852 - components: - - type: Transform - pos: 14.5,-1.5 - parent: 1 - - uid: 853 - components: - - type: Transform - pos: 8.5,-1.5 - parent: 1 - - uid: 854 - components: - - type: Transform - pos: -3.5,1.5 - parent: 1 - - uid: 855 - components: - - type: Transform - pos: -3.5,0.5 - parent: 1 - - uid: 856 - components: - - type: Transform - pos: 5.5,5.5 - parent: 1 - - uid: 857 - components: - - type: Transform - pos: -1.5,-4.5 - parent: 1 - - uid: 858 - components: - - type: Transform - pos: -2.5,-5.5 - parent: 1 - - uid: 859 - components: - - type: Transform - pos: 2.5,6.5 - parent: 1 - - uid: 860 - components: - - type: Transform - pos: -0.5,6.5 - parent: 1 - - uid: 861 - components: - - type: Transform - pos: 4.5,6.5 - parent: 1 - - uid: 862 - components: - - type: Transform - pos: 5.5,-4.5 - parent: 1 - - uid: 863 - components: - - type: Transform - pos: 9.5,-1.5 - parent: 1 - - uid: 864 - components: - - type: Transform - pos: 11.5,-1.5 - parent: 1 - - uid: 865 - components: - - type: Transform - pos: 12.5,-1.5 - parent: 1 - - uid: 866 - components: - - type: Transform - pos: 5.5,-3.5 - parent: 1 - - uid: 867 - components: - - type: Transform - pos: 5.5,-2.5 - parent: 1 - - uid: 868 - components: - - type: Transform - pos: 5.5,-7.5 - parent: 1 - - uid: 869 - components: - - type: Transform - pos: 7.5,-1.5 - parent: 1 - - uid: 870 - components: - - type: Transform - pos: 13.5,-1.5 - parent: 1 - - uid: 871 - components: - - type: Transform - pos: 13.5,3.5 - parent: 1 - - uid: 872 - components: - - type: Transform - pos: 3.5,-5.5 - parent: 1 - - uid: 873 - components: - - type: Transform - pos: 4.5,-5.5 - parent: 1 - - uid: 874 - components: - - type: Transform - pos: -2.5,4.5 - parent: 1 - - uid: 875 - components: - - type: Transform - pos: 5.5,-5.5 - parent: 1 - - uid: 876 - components: - - type: Transform - pos: 5.5,-6.5 - parent: 1 - - uid: 877 - components: - - type: Transform - pos: 1.5,-5.5 - parent: 1 - - uid: 878 - components: - - type: Transform - pos: 0.5,-5.5 - parent: 1 - - uid: 879 - components: - - type: Transform - pos: 0.5,-4.5 - parent: 1 - - uid: 880 - components: - - type: Transform - pos: 12.5,3.5 - parent: 1 - - uid: 881 - components: - - type: Transform - pos: -2.5,-4.5 - parent: 1 - - uid: 882 - components: - - type: Transform - pos: -2.5,-2.5 - parent: 1 - - uid: 883 - components: - - type: Transform - pos: 0.5,-7.5 - parent: 1 - - uid: 885 - components: - - type: Transform - pos: -2.5,-6.5 - parent: 1 - - uid: 886 - components: - - type: Transform - pos: -0.5,-4.5 - parent: 1 - - uid: 887 - components: - - type: Transform - pos: 0.5,0.5 - parent: 1 - - uid: 888 - components: - - type: Transform - pos: 0.5,1.5 - parent: 1 - - uid: 889 - components: - - type: Transform - pos: -1.5,2.5 - parent: 1 - - uid: 890 - components: - - type: Transform - pos: 7.5,4.5 - parent: 1 - - uid: 891 - components: - - type: Transform - pos: 0.5,5.5 - parent: 1 - - uid: 892 - components: - - type: Transform - pos: -2.5,2.5 - parent: 1 - - uid: 893 - components: - - type: Transform - pos: -2.5,3.5 - parent: 1 - - uid: 894 - components: - - type: Transform - pos: -2.5,5.5 - parent: 1 - - uid: 895 - components: - - type: Transform - pos: 1.5,4.5 - parent: 1 - - uid: 897 - components: - - type: Transform - pos: -2.5,-1.5 - parent: 1 - - uid: 898 - components: - - type: Transform - pos: 1.5,-1.5 - parent: 1 - - uid: 899 - components: - - type: Transform - pos: 0.5,-1.5 - parent: 1 - - uid: 900 - components: - - type: Transform - pos: 0.5,-3.5 - parent: 1 - - uid: 901 - components: - - type: Transform - pos: -2.5,-3.5 - parent: 1 - - uid: 902 - components: - - type: Transform - pos: 0.5,-2.5 - parent: 1 - - uid: 903 - components: - - type: Transform - pos: 11.5,3.5 - parent: 1 - - uid: 905 - components: - - type: Transform - pos: 9.5,3.5 - parent: 1 - - uid: 906 - components: - - type: Transform - pos: 5.5,-1.5 - parent: 1 - - uid: 907 - components: - - type: Transform - pos: 4.5,-1.5 - parent: 1 - - uid: 908 - components: - - type: Transform - pos: 7.5,2.5 - parent: 1 - - uid: 909 - components: - - type: Transform - pos: 7.5,1.5 - parent: 1 - - uid: 910 - components: - - type: Transform - pos: 7.5,0.5 - parent: 1 - - uid: 911 - components: - - type: Transform - pos: 7.5,3.5 - parent: 1 - - uid: 912 - components: - - type: Transform - pos: 8.5,3.5 - parent: 1 - - uid: 913 - components: - - type: Transform - pos: 5.5,0.5 - parent: 1 - - uid: 914 - components: - - type: Transform - pos: 6.5,0.5 - parent: 1 - - uid: 915 - components: - - type: Transform - pos: -2.5,-7.5 - parent: 1 - - uid: 916 - components: - - type: Transform - pos: 3.5,-7.5 - parent: 1 - - uid: 918 - components: - - type: Transform - pos: -2.5,-0.5 - parent: 1 - - uid: 920 - components: - - type: Transform - pos: 0.5,2.5 - parent: 1 - - uid: 921 - components: - - type: Transform - pos: -0.5,2.5 - parent: 1 - - uid: 922 - components: - - type: Transform - pos: -2.5,1.5 - parent: 1 - - uid: 923 - components: - - type: Transform - pos: 0.5,4.5 - parent: 1 - - uid: 924 - components: - - type: Transform - pos: 2.5,4.5 - parent: 1 - - uid: 925 - components: - - type: Transform - pos: 5.5,1.5 - parent: 1 - - uid: 926 - components: - - type: Transform - pos: 5.5,4.5 - parent: 1 - - uid: 927 - components: - - type: Transform - pos: 5.5,3.5 - parent: 1 - - uid: 928 - components: - - type: Transform - pos: 4.5,4.5 - parent: 1 - - uid: 929 - components: - - type: Transform - pos: -1.5,6.5 - parent: 1 - - uid: 930 - components: - - type: Transform - pos: 0.5,6.5 - parent: 1 - - uid: 931 - components: - - type: Transform - pos: 6.5,4.5 - parent: 1 - - uid: 932 - components: - - type: Transform - pos: 9.5,-6.5 - parent: 1 - - uid: 933 - components: - - type: Transform - pos: 9.5,-4.5 - parent: 1 - - uid: 934 - components: - - type: Transform - pos: 5.5,6.5 - parent: 1 - - uid: 935 - components: - - type: Transform - pos: 9.5,-2.5 - parent: 1 - - uid: 936 - components: - - type: Transform - pos: 1.5,6.5 - parent: 1 - - uid: 937 - components: - - type: Transform - pos: 14.5,-3.5 - parent: 1 - - uid: 938 - components: - - type: Transform - pos: 14.5,-4.5 - parent: 1 - - uid: 939 - components: - - type: Transform - pos: 14.5,-5.5 - parent: 1 - - uid: 940 - components: - - type: Transform - pos: 9.5,-5.5 - parent: 1 - - uid: 941 - components: - - type: Transform - pos: -3.5,-0.5 - parent: 1 -- proto: WarpPoint - entities: - - uid: 942 - components: - - type: MetaData - name: Syndicate Listening Post - - type: Transform - pos: 10.5,0.5 - parent: 1 -- proto: WashingMachineFilledClothes - entities: - - uid: 2 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 4.5,-7.5 - parent: 1 -- proto: WaterCooler - entities: - - uid: 943 - components: - - type: Transform - pos: 1.5,0.5 - parent: 1 -- proto: WaterTankHighCapacity - entities: - - uid: 944 - components: - - type: Transform - pos: 13.5,-2.5 - parent: 1 -- proto: WeaponShotgunKammerer - entities: - - uid: 379 - components: - - type: Transform - parent: 363 - - type: Physics - canCollide: False - - type: InsideEntityStorage - - uid: 380 - components: - - type: Transform - parent: 363 - - type: Physics - canCollide: False - - type: InsideEntityStorage -- proto: Windoor - entities: - - uid: 618 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: -0.5,-7.5 - parent: 1 - - uid: 945 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: -1.5,-7.5 - parent: 1 - - uid: 946 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 4.5,5.5 - parent: 1 - - uid: 947 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: -2.5,0.5 - parent: 1 -- proto: WindoorSecure - entities: - - uid: 610 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 2.5,-7.5 - parent: 1 - - uid: 783 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 1.5,-7.5 - parent: 1 - - uid: 948 - components: - - type: Transform - pos: -1.5,5.5 - parent: 1 - - uid: 949 - components: - - type: Transform - pos: -0.5,5.5 - parent: 1 -- proto: WoodDoor - entities: - - uid: 12 - components: - - type: Transform - pos: -1.5,-1.5 - parent: 1 -... diff --git a/Resources/Maps/Shuttles/DeltaV/NTES_Box.yml b/Resources/Maps/Shuttles/DeltaV/NTES_Box.yml index 61831cd089..30e9979650 100644 --- a/Resources/Maps/Shuttles/DeltaV/NTES_Box.yml +++ b/Resources/Maps/Shuttles/DeltaV/NTES_Box.yml @@ -61,6 +61,10 @@ entities: - type: Gravity gravityShakeSound: !type:SoundPathSpecifier path: /Audio/Effects/alert.ogg + - type: DeviceNetwork + configurators: [] + deviceLists: [] + transmitFrequencyId: ShuttleTimer - type: DecalGrid chunkCollection: version: 2 diff --git a/Resources/Maps/Shuttles/DeltaV/NTES_Delta.yml b/Resources/Maps/Shuttles/DeltaV/NTES_Delta.yml index ea42d89ddd..764a81b452 100644 --- a/Resources/Maps/Shuttles/DeltaV/NTES_Delta.yml +++ b/Resources/Maps/Shuttles/DeltaV/NTES_Delta.yml @@ -68,6 +68,10 @@ entities: - type: Gravity gravityShakeSound: !type:SoundPathSpecifier path: /Audio/Effects/alert.ogg + - type: DeviceNetwork + configurators: [] + deviceLists: [] + transmitFrequencyId: ShuttleTimer - type: DecalGrid chunkCollection: version: 2 diff --git a/Resources/Maps/Shuttles/DeltaV/NTES_Kaeri.yml b/Resources/Maps/Shuttles/DeltaV/NTES_Kaeri.yml index 0a9fc598df..35a8eb637a 100644 --- a/Resources/Maps/Shuttles/DeltaV/NTES_Kaeri.yml +++ b/Resources/Maps/Shuttles/DeltaV/NTES_Kaeri.yml @@ -61,6 +61,10 @@ entities: - type: Gravity gravityShakeSound: !type:SoundPathSpecifier path: /Audio/Effects/alert.ogg + - type: DeviceNetwork + configurators: [] + deviceLists: [] + transmitFrequencyId: ShuttleTimer - type: DecalGrid chunkCollection: version: 2 diff --git a/Resources/Maps/Shuttles/DeltaV/NTES_Lox.yml b/Resources/Maps/Shuttles/DeltaV/NTES_Lox.yml index 9522cd74d3..e04c801d28 100644 --- a/Resources/Maps/Shuttles/DeltaV/NTES_Lox.yml +++ b/Resources/Maps/Shuttles/DeltaV/NTES_Lox.yml @@ -66,6 +66,10 @@ entities: - type: Gravity gravityShakeSound: !type:SoundPathSpecifier path: /Audio/Effects/alert.ogg + - type: DeviceNetwork + configurators: [] + deviceLists: [] + transmitFrequencyId: ShuttleTimer - type: DecalGrid chunkCollection: version: 2 diff --git a/Resources/Maps/Shuttles/DeltaV/NTES_Propeller.yml b/Resources/Maps/Shuttles/DeltaV/NTES_Propeller.yml index 929103c42a..c814fb0b34 100644 --- a/Resources/Maps/Shuttles/DeltaV/NTES_Propeller.yml +++ b/Resources/Maps/Shuttles/DeltaV/NTES_Propeller.yml @@ -90,6 +90,10 @@ entities: - type: Gravity gravityShakeSound: !type:SoundPathSpecifier path: /Audio/Effects/alert.ogg + - type: DeviceNetwork + configurators: [] + deviceLists: [] + transmitFrequencyId: ShuttleTimer - type: DecalGrid chunkCollection: version: 2 diff --git a/Resources/Maps/Shuttles/ShuttleEvent/disaster_evacpod.yml b/Resources/Maps/Shuttles/ShuttleEvent/disaster_evacpod.yml new file mode 100644 index 0000000000..be1ca4ffde --- /dev/null +++ b/Resources/Maps/Shuttles/ShuttleEvent/disaster_evacpod.yml @@ -0,0 +1,350 @@ +meta: + format: 6 + postmapinit: false +tilemap: + 0: Space + 82: FloorShuttleOrange + 85: FloorShuttleWhite + 120: Lattice + 121: Plating +entities: +- proto: "" + entities: + - uid: 1 + components: + - type: MetaData + name: Evacuation pod + - type: Transform + parent: invalid + - type: MapGrid + chunks: + 0,0: + ind: 0,0 + tiles: eQAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + version: 6 + -1,0: + ind: -1,0 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + version: 6 + 0,-1: + ind: 0,-1 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + version: 6 + -1,-1: + ind: -1,-1 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAA + version: 6 + - type: Broadphase + - type: Physics + bodyStatus: InAir + angularDamping: 0.05 + linearDamping: 0.05 + fixedRotation: False + bodyType: Dynamic + - type: Fixtures + fixtures: {} + - type: OccluderTree + - type: SpreaderGrid + - type: Shuttle + - type: GridPathfinding + - type: Gravity + gravityShakeSound: !type:SoundPathSpecifier + path: /Audio/Effects/alert.ogg + - type: DecalGrid + chunkCollection: + version: 2 + nodes: [] + - type: GridAtmosphere + version: 2 + data: + tiles: + 0,-1: + 0: 4368 + 1: 32 + 0,0: + 1: 2 + -1,0: + 1: 8 + -1,-1: + 1: 128 + uniqueMixes: + - volume: 2500 + temperature: 293.15 + moles: + - 21.824879 + - 82.10312 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - volume: 2500 + immutable: True + moles: + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + chunkSize: 4 + - type: GasTileOverlay + - type: RadiationGridResistance + - type: NavMap +- proto: AirlockShuttle + entities: + - uid: 2 + components: + - type: Transform + pos: 0.5,-2.5 + parent: 1 +- proto: APCBasic + entities: + - uid: 3 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.5,-0.5 + parent: 1 +- proto: AtmosDeviceFanTiny + entities: + - uid: 4 + components: + - type: Transform + pos: 0.5,-2.5 + parent: 1 +- proto: BoxMRE + entities: + - uid: 6 + components: + - type: Transform + parent: 5 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: CableApcExtension + entities: + - uid: 10 + components: + - type: Transform + pos: 1.5,-0.5 + parent: 1 + - uid: 11 + components: + - type: Transform + pos: 0.5,-0.5 + parent: 1 + - uid: 12 + components: + - type: Transform + pos: 0.5,-1.5 + parent: 1 +- proto: CableHV + entities: + - uid: 13 + components: + - type: Transform + pos: -0.5,-1.5 + parent: 1 + - uid: 14 + components: + - type: Transform + pos: -0.5,-0.5 + parent: 1 +- proto: CableMV + entities: + - uid: 15 + components: + - type: Transform + pos: -0.5,-0.5 + parent: 1 + - uid: 16 + components: + - type: Transform + pos: 0.5,-0.5 + parent: 1 + - uid: 17 + components: + - type: Transform + pos: 1.5,-0.5 + parent: 1 +- proto: ChairPilotSeat + entities: + - uid: 18 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 0.5,-1.5 + parent: 1 +- proto: ClosetWallMaintenanceFilledRandom + entities: + - uid: 5 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.5,-1.5 + parent: 1 + - type: EntityStorage + air: + volume: 200 + immutable: False + temperature: 293.14673 + moles: + - 1.7459903 + - 6.568249 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - type: ContainerContainer + containers: + entity_storage: !type:Container + showEnts: False + occludes: True + ents: + - 8 + - 9 + - 6 + - 7 +- proto: ClothingOuterSuitEmergency + entities: + - uid: 7 + components: + - type: Transform + parent: 5 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: ComputerShuttle + entities: + - uid: 19 + components: + - type: Transform + pos: 0.5,-0.5 + parent: 1 +- proto: DisasterVictimSpawner + entities: + - uid: 20 + components: + - type: Transform + pos: 0.5,-1.5 + parent: 1 +- proto: GeneratorWallmountAPU + entities: + - uid: 21 + components: + - type: Transform + pos: -0.5,-1.5 + parent: 1 +- proto: Gyroscope + entities: + - uid: 32 + components: + - type: Transform + pos: -0.5,-2.5 + parent: 1 +- proto: HandheldGPSBasic + entities: + - uid: 8 + components: + - type: Transform + parent: 5 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: Poweredlight + entities: + - uid: 22 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 0.5,-1.5 + parent: 1 +- proto: ShuttleWindow + entities: + - uid: 23 + components: + - type: Transform + pos: 0.5,0.5 + parent: 1 +- proto: SubstationWallBasic + entities: + - uid: 24 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -0.5,-0.5 + parent: 1 +- proto: Thruster + entities: + - uid: 25 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.5,-2.5 + parent: 1 +- proto: WallShuttle + entities: + - uid: 26 + components: + - type: Transform + pos: -0.5,-0.5 + parent: 1 + - uid: 27 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -0.5,-1.5 + parent: 1 + - uid: 28 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.5,-0.5 + parent: 1 + - uid: 29 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.5,-1.5 + parent: 1 +- proto: WallShuttleDiagonal + entities: + - uid: 30 + components: + - type: Transform + pos: -0.5,0.5 + parent: 1 + - uid: 31 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.5,0.5 + parent: 1 +- proto: WeaponLaserSvalinn + entities: + - uid: 9 + components: + - type: Transform + parent: 5 + - type: Physics + canCollide: False + - type: InsideEntityStorage +... diff --git a/Resources/Maps/Shuttles/ShuttleEvent/honki.yml b/Resources/Maps/Shuttles/ShuttleEvent/honki.yml new file mode 100644 index 0000000000..0fca6a8ba9 --- /dev/null +++ b/Resources/Maps/Shuttles/ShuttleEvent/honki.yml @@ -0,0 +1,2666 @@ +meta: + format: 6 + postmapinit: false +tilemap: + 0: Space + 20: FloorCarpetClown + 25: FloorClown + 120: Lattice + 121: Plating +entities: +- proto: "" + entities: + - uid: 1 + components: + - type: MetaData + name: Honkomother + - type: Transform + pos: 1.212189,-1.7551664 + parent: invalid + - type: MapGrid + chunks: + 0,0: + ind: 0,0 + tiles: GQAAAAAAGQAAAAAAGQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAFAAAAAAAGQAAAAAAeQAAAAAAeAAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAFAAAAAAAeQAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + version: 6 + 0,-1: + ind: 0,-1 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeAAAAAAAeAAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAAGQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAAGQAAAAAAeQAAAAAAGQAAAAAAGQAAAAAAGQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAAGQAAAAAAGQAAAAAAGQAAAAAAGQAAAAAAGQAAAAAAeQAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAAGQAAAAAAeQAAAAAAGQAAAAAAGQAAAAAAGQAAAAAAeQAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAAGQAAAAAAGQAAAAAAeQAAAAAAGQAAAAAAGQAAAAAAGQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAAGQAAAAAAGQAAAAAAGQAAAAAAGQAAAAAAGQAAAAAAGQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAAGQAAAAAAGQAAAAAAeQAAAAAAGQAAAAAAGQAAAAAAGQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + version: 6 + -1,0: + ind: -1,0 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAGQAAAAAAGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAeAAAAAAAeQAAAAAAGQAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAeQAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + version: 6 + -1,-1: + ind: -1,-1 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAeAAAAAAAeAAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAGQAAAAAAGQAAAAAAGQAAAAAAeQAAAAAAGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAeQAAAAAAGQAAAAAAGQAAAAAAGQAAAAAAGQAAAAAAGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAeQAAAAAAGQAAAAAAGQAAAAAAGQAAAAAAeQAAAAAAGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAGQAAAAAAGQAAAAAAGQAAAAAAeQAAAAAAGQAAAAAAGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAGQAAAAAAGQAAAAAAGQAAAAAAGQAAAAAAGQAAAAAAGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAGQAAAAAAGQAAAAAAGQAAAAAAeQAAAAAAGQAAAAAAGQAAAAAA + version: 6 + - type: Broadphase + - type: Physics + bodyStatus: InAir + angularDamping: 0.05 + linearDamping: 0.05 + fixedRotation: False + bodyType: Dynamic + - type: Fixtures + fixtures: {} + - type: OccluderTree + - type: SpreaderGrid + - type: Shuttle + - type: GridPathfinding + - type: Gravity + gravityShakeSound: !type:SoundPathSpecifier + path: /Audio/Effects/alert.ogg + - type: DecalGrid + chunkCollection: + version: 2 + nodes: + - node: + color: '#EFB34196' + id: MiniTileWhiteCornerNe + decals: + 21: 5,-5 + 39: -4,-1 + 40: 6,-1 + - node: + color: '#EFB34196' + id: MiniTileWhiteCornerNw + decals: + 20: -5,-5 + 41: -6,-1 + 42: 4,-1 + - node: + color: '#EFB34196' + id: MiniTileWhiteCornerSe + decals: + 0: 2,-3 + 23: 5,-7 + 35: -4,-3 + 36: 6,-3 + - node: + color: '#EFB34196' + id: MiniTileWhiteCornerSw + decals: + 1: -2,-3 + 33: -5,-7 + 37: -6,-3 + 38: 4,-3 + - node: + color: '#EFB34196' + id: MiniTileWhiteInnerNe + decals: + 57: -2,0 + - node: + color: '#EFB34196' + id: MiniTileWhiteInnerNw + decals: + 56: 2,0 + - node: + color: '#EFB34196' + id: MiniTileWhiteLineE + decals: + 2: 2,-2 + 3: 2,-1 + 4: 2,0 + 5: 2,1 + 22: 5,-6 + 47: -4,-2 + 48: 6,-2 + 55: -2,1 + - node: + color: '#EFB34196' + id: MiniTileWhiteLineN + decals: + 13: 0,-5 + 14: 4,-5 + 15: -1,-5 + 16: 1,-5 + 17: 3,-5 + 18: -3,-5 + 19: -4,-5 + 49: -5,-1 + 50: 5,-1 + 51: -1,0 + 52: 0,0 + 53: 1,0 + - node: + color: '#EFB34196' + id: MiniTileWhiteLineS + decals: + 10: -1,-3 + 11: 0,-3 + 12: 1,-3 + 24: 4,-7 + 25: 3,-7 + 26: 2,-7 + 27: 1,-7 + 28: 0,-7 + 29: -1,-7 + 30: -2,-7 + 31: -3,-7 + 32: -4,-7 + 43: -5,-3 + 44: 5,-3 + - node: + color: '#EFB34196' + id: MiniTileWhiteLineW + decals: + 6: -2,-2 + 7: -2,-1 + 8: -2,0 + 9: -2,1 + 34: -5,-6 + 45: 4,-2 + 46: -6,-2 + 54: 2,1 + - type: GridAtmosphere + version: 2 + data: + tiles: + 0,0: + 0: 887 + 1: 18432 + 0,-1: + 0: 30576 + -1,0: + 0: 2252 + 1: 16912 + 1,0: + 1: 56 + 1,-1: + 0: 30576 + 0,-2: + 0: 48051 + -1,-2: + 0: 48056 + -1,-1: + 0: 56784 + 0,-3: + 1: 32768 + 1,-3: + 1: 12288 + 1,-2: + 0: 13104 + 1: 34820 + -2,0: + 1: 130 + -2,-1: + 0: 52928 + -2,-2: + 1: 8708 + 0: 34944 + -2,-3: + 1: 32768 + -1,-3: + 1: 12288 + uniqueMixes: + - volume: 2500 + temperature: 293.15 + moles: + - 21.824879 + - 82.10312 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - volume: 2500 + immutable: True + moles: + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + chunkSize: 4 + - type: GasTileOverlay + - type: RadiationGridResistance +- proto: AirCanister + entities: + - uid: 2 + components: + - type: Transform + anchored: True + pos: -1.5,-2.5 + parent: 1 + - type: Physics + bodyType: Static + - uid: 3 + components: + - type: Transform + anchored: True + pos: 2.5,-2.5 + parent: 1 + - type: Physics + bodyType: Static +- proto: AirlockShuttle + entities: + - uid: 4 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -6.5,-1.5 + parent: 1 +- proto: AltarBananium + entities: + - uid: 374 + components: + - type: Transform + pos: 6.5,-1.5 + parent: 1 +- proto: APCBasic + entities: + - uid: 5 + components: + - type: Transform + pos: 1.5,-3.5 + parent: 1 +- proto: AtmosDeviceFanTiny + entities: + - uid: 6 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -6.5,-1.5 + parent: 1 +- proto: BananaPhoneInstrument + entities: + - uid: 7 + components: + - type: Transform + pos: -1.4946816,-0.2775966 + parent: 1 +- proto: BananiumDoor + entities: + - uid: 8 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 2.5,-5.5 + parent: 1 + - uid: 9 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -2.5,-1.5 + parent: 1 + - uid: 10 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 3.5,-1.5 + parent: 1 + - uid: 11 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -1.5,-5.5 + parent: 1 + - uid: 12 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 0.5,-3.5 + parent: 1 +- proto: BarricadeBlock + entities: + - uid: 372 + components: + - type: Transform + pos: -1.5,-5.5 + parent: 1 +- proto: Bed + entities: + - uid: 13 + components: + - type: Transform + pos: 3.5,-6.5 + parent: 1 + - uid: 14 + components: + - type: Transform + pos: 3.5,-4.5 + parent: 1 + - uid: 15 + components: + - type: Transform + pos: 5.5,-6.5 + parent: 1 + - uid: 16 + components: + - type: Transform + pos: 5.5,-4.5 + parent: 1 +- proto: BedsheetSpawner + entities: + - uid: 17 + components: + - type: Transform + pos: 3.5,-6.5 + parent: 1 + - uid: 18 + components: + - type: Transform + pos: 5.5,-6.5 + parent: 1 + - uid: 19 + components: + - type: Transform + pos: 3.5,-4.5 + parent: 1 + - uid: 20 + components: + - type: Transform + pos: 5.5,-4.5 + parent: 1 +- proto: BikeHornImplanter + entities: + - uid: 22 + components: + - type: Transform + parent: 21 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 27 + components: + - type: Transform + parent: 26 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: CableApcExtension + entities: + - uid: 31 + components: + - type: Transform + pos: -0.5,-2.5 + parent: 1 + - uid: 32 + components: + - type: Transform + pos: -0.5,-1.5 + parent: 1 + - uid: 33 + components: + - type: Transform + pos: 1.5,-3.5 + parent: 1 + - uid: 34 + components: + - type: Transform + pos: 1.5,-4.5 + parent: 1 + - uid: 35 + components: + - type: Transform + pos: 1.5,-5.5 + parent: 1 + - uid: 36 + components: + - type: Transform + pos: 1.5,-6.5 + parent: 1 + - uid: 37 + components: + - type: Transform + pos: 0.5,-5.5 + parent: 1 + - uid: 38 + components: + - type: Transform + pos: -0.5,-5.5 + parent: 1 + - uid: 39 + components: + - type: Transform + pos: -0.5,-6.5 + parent: 1 + - uid: 40 + components: + - type: Transform + pos: -0.5,-7.5 + parent: 1 + - uid: 41 + components: + - type: Transform + pos: 1.5,-2.5 + parent: 1 + - uid: 42 + components: + - type: Transform + pos: -0.5,-4.5 + parent: 1 + - uid: 43 + components: + - type: Transform + pos: -1.5,-5.5 + parent: 1 + - uid: 44 + components: + - type: Transform + pos: -2.5,-5.5 + parent: 1 + - uid: 45 + components: + - type: Transform + pos: -3.5,-5.5 + parent: 1 + - uid: 46 + components: + - type: Transform + pos: -4.5,-5.5 + parent: 1 + - uid: 47 + components: + - type: Transform + pos: 3.5,-5.5 + parent: 1 + - uid: 48 + components: + - type: Transform + pos: 4.5,-5.5 + parent: 1 + - uid: 49 + components: + - type: Transform + pos: 5.5,-5.5 + parent: 1 + - uid: 50 + components: + - type: Transform + pos: -0.5,-2.5 + parent: 1 + - uid: 51 + components: + - type: Transform + pos: -0.5,-1.5 + parent: 1 + - uid: 52 + components: + - type: Transform + pos: -0.5,-0.5 + parent: 1 + - uid: 53 + components: + - type: Transform + pos: 1.5,-1.5 + parent: 1 + - uid: 54 + components: + - type: Transform + pos: 4.5,-1.5 + parent: 1 + - uid: 55 + components: + - type: Transform + pos: 1.5,-0.5 + parent: 1 + - uid: 56 + components: + - type: Transform + pos: 1.5,0.5 + parent: 1 + - uid: 57 + components: + - type: Transform + pos: 1.5,1.5 + parent: 1 + - uid: 58 + components: + - type: Transform + pos: -0.5,1.5 + parent: 1 + - uid: 59 + components: + - type: Transform + pos: -0.5,0.5 + parent: 1 + - uid: 60 + components: + - type: Transform + pos: 2.5,-1.5 + parent: 1 + - uid: 61 + components: + - type: Transform + pos: 3.5,-1.5 + parent: 1 + - uid: 62 + components: + - type: Transform + pos: 5.5,-1.5 + parent: 1 + - uid: 63 + components: + - type: Transform + pos: -2.5,-1.5 + parent: 1 + - uid: 64 + components: + - type: Transform + pos: -3.5,-1.5 + parent: 1 + - uid: 65 + components: + - type: Transform + pos: -4.5,-1.5 + parent: 1 + - uid: 66 + components: + - type: Transform + pos: -5.5,-1.5 + parent: 1 + - uid: 67 + components: + - type: Transform + pos: -4.5,-0.5 + parent: 1 + - uid: 68 + components: + - type: Transform + pos: -4.5,0.5 + parent: 1 + - uid: 69 + components: + - type: Transform + pos: 5.5,-0.5 + parent: 1 + - uid: 70 + components: + - type: Transform + pos: 5.5,0.5 + parent: 1 + - uid: 71 + components: + - type: Transform + pos: 6.5,-5.5 + parent: 1 + - uid: 72 + components: + - type: Transform + pos: 5.5,-5.5 + parent: 1 + - uid: 73 + components: + - type: Transform + pos: 4.5,-6.5 + parent: 1 + - uid: 74 + components: + - type: Transform + pos: 4.5,-7.5 + parent: 1 + - uid: 75 + components: + - type: Transform + pos: -3.5,-7.5 + parent: 1 + - uid: 76 + components: + - type: Transform + pos: -3.5,-6.5 + parent: 1 + - uid: 77 + components: + - type: Transform + pos: -5.5,-5.5 + parent: 1 + - uid: 78 + components: + - type: Transform + pos: -0.5,-3.5 + parent: 1 + - uid: 79 + components: + - type: Transform + pos: 0.5,-1.5 + parent: 1 + - uid: 80 + components: + - type: Transform + pos: -1.5,-1.5 + parent: 1 + - uid: 81 + components: + - type: Transform + pos: 2.5,-5.5 + parent: 1 +- proto: CableHV + entities: + - uid: 82 + components: + - type: Transform + pos: -0.5,-8.5 + parent: 1 + - uid: 83 + components: + - type: Transform + pos: 0.5,-8.5 + parent: 1 + - uid: 84 + components: + - type: Transform + pos: 1.5,-8.5 + parent: 1 + - uid: 85 + components: + - type: Transform + pos: 0.5,-7.5 + parent: 1 +- proto: CableMV + entities: + - uid: 86 + components: + - type: Transform + pos: 0.5,-7.5 + parent: 1 + - uid: 87 + components: + - type: Transform + pos: 1.5,-3.5 + parent: 1 + - uid: 88 + components: + - type: Transform + pos: 0.5,-6.5 + parent: 1 + - uid: 89 + components: + - type: Transform + pos: 0.5,-5.5 + parent: 1 + - uid: 90 + components: + - type: Transform + pos: 0.5,-4.5 + parent: 1 + - uid: 91 + components: + - type: Transform + pos: 1.5,-4.5 + parent: 1 +- proto: ChairPilotSeat + entities: + - uid: 92 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 0.5,1.5 + parent: 1 +- proto: CigaretteBanana + entities: + - uid: 94 + components: + - type: Transform + parent: 93 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 95 + components: + - type: Transform + parent: 93 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 96 + components: + - type: Transform + parent: 93 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: ClothingOuterHardsuitClown + entities: + - uid: 97 + components: + - type: Transform + parent: 93 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 100 + components: + - type: Transform + parent: 99 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 104 + components: + - type: Transform + parent: 103 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: ClothingOuterWinterClown + entities: + - uid: 105 + components: + - type: Transform + parent: 103 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: ClownRecorder + entities: + - uid: 107 + components: + - type: Transform + pos: 2.6453638,0.42502415 + parent: 1 +- proto: ClownTroupeSpawner + entities: + - uid: 108 + components: + - type: Transform + pos: 3.5,-6.5 + parent: 1 + - uid: 109 + components: + - type: Transform + pos: 5.5,-4.5 + parent: 1 + - uid: 110 + components: + - type: Transform + pos: 5.5,-6.5 + parent: 1 +- proto: CluwneHorn + entities: + - uid: 23 + components: + - type: Transform + parent: 21 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 28 + components: + - type: Transform + parent: 26 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: ComfyChair + entities: + - uid: 111 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -0.5,-0.5 + parent: 1 + - uid: 112 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 1.5,-0.5 + parent: 1 +- proto: ComputerShuttle + entities: + - uid: 113 + components: + - type: Transform + pos: 0.5,2.5 + parent: 1 +- proto: CrateCargoGambling + entities: + - uid: 114 + components: + - type: Transform + pos: -0.5,-2.5 + parent: 1 +- proto: CratePirate + entities: + - uid: 21 + components: + - type: Transform + pos: 4.5,-0.5 + parent: 1 + - type: EntityStorage + air: + volume: 200 + immutable: False + temperature: 293.14673 + moles: + - 1.7459903 + - 6.568249 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - type: ContainerContainer + containers: + entity_storage: !type:Container + showEnts: False + occludes: True + ents: + - 25 + - 23 + - 22 + - 24 + - 261 + paper_label: !type:ContainerSlot + showEnts: False + occludes: True + ent: null + - uid: 26 + components: + - type: Transform + pos: 4.5,-2.5 + parent: 1 + - type: EntityStorage + air: + volume: 200 + immutable: False + temperature: 293.14673 + moles: + - 1.7459903 + - 6.568249 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - type: ContainerContainer + containers: + entity_storage: !type:Container + showEnts: False + occludes: True + ents: + - 29 + - 315 + - 27 + - 28 + - 30 + paper_label: !type:ContainerSlot + showEnts: False + occludes: True + ent: null + - uid: 115 + components: + - type: Transform + pos: 6.5,-2.5 + parent: 1 + - type: ContainerContainer + containers: + entity_storage: !type:Container + showEnts: False + occludes: True + ents: + - 125 + - 124 + - 123 + - 122 + - 121 + - 126 + - 120 + - 119 + - 118 + - 117 + - 116 + paper_label: !type:ContainerSlot + showEnts: False + occludes: True + ent: null + - uid: 127 + components: + - type: Transform + pos: 6.5,-0.5 + parent: 1 + - type: ContainerContainer + containers: + entity_storage: !type:Container + showEnts: False + occludes: True + ents: + - 128 + - 129 + - 130 + - 131 + - 132 + - 133 + - 134 + - 135 + - 138 + - 136 + - 137 + paper_label: !type:ContainerSlot + showEnts: False + occludes: True + ent: null +- proto: DehydratedSpaceCarp + entities: + - uid: 24 + components: + - type: Transform + parent: 21 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 29 + components: + - type: Transform + parent: 26 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: DeviceQuantumSpinInverter + entities: + - uid: 25 + components: + - type: Transform + parent: 21 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 30 + components: + - type: Transform + parent: 26 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: EmergencyFunnyOxygenTankFilled + entities: + - uid: 98 + components: + - type: Transform + parent: 93 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 101 + components: + - type: Transform + parent: 99 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 106 + components: + - type: Transform + parent: 103 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: FloorBananiumEntity + entities: + - uid: 139 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -0.5,-7.5 + parent: 1 + - uid: 140 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 0.5,-7.5 + parent: 1 + - uid: 141 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.5,-7.5 + parent: 1 + - uid: 142 + components: + - type: Transform + pos: -1.5,-6.5 + parent: 1 + - uid: 143 + components: + - type: Transform + pos: -1.5,-4.5 + parent: 1 + - uid: 373 + components: + - type: Transform + pos: 0.5,-3.5 + parent: 1 +- proto: FoodBanana + entities: + - uid: 116 + components: + - type: Transform + parent: 115 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 117 + components: + - type: Transform + parent: 115 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 118 + components: + - type: Transform + parent: 115 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 119 + components: + - type: Transform + parent: 115 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 128 + components: + - type: Transform + parent: 127 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 129 + components: + - type: Transform + parent: 127 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 130 + components: + - type: Transform + parent: 127 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 131 + components: + - type: Transform + parent: 127 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: FoodBurgerClown + entities: + - uid: 144 + components: + - type: Transform + pos: -1.5971907,0.54355335 + parent: 1 + - uid: 145 + components: + - type: Transform + pos: -1.405974,0.27368832 + parent: 1 +- proto: FoodCakeClown + entities: + - uid: 146 + components: + - type: Transform + pos: 2.4229474,-0.24963892 + parent: 1 +- proto: FoodMeatClown + entities: + - uid: 147 + components: + - type: Transform + pos: 3.4782665,-4.592343 + parent: 1 +- proto: FoodPieBananaCream + entities: + - uid: 120 + components: + - type: Transform + parent: 115 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 121 + components: + - type: Transform + parent: 115 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 122 + components: + - type: Transform + parent: 115 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 123 + components: + - type: Transform + parent: 115 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 124 + components: + - type: Transform + parent: 115 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 125 + components: + - type: Transform + parent: 115 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 132 + components: + - type: Transform + parent: 127 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 133 + components: + - type: Transform + parent: 127 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 134 + components: + - type: Transform + parent: 127 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 135 + components: + - type: Transform + parent: 127 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 136 + components: + - type: Transform + parent: 127 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 137 + components: + - type: Transform + parent: 127 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: GasPassiveVent + entities: + - uid: 148 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -4.5,1.5 + parent: 1 + - uid: 149 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 5.5,1.5 + parent: 1 +- proto: GasPipeBend + entities: + - uid: 150 + components: + - type: Transform + pos: 5.5,-0.5 + parent: 1 + - uid: 151 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -4.5,-0.5 + parent: 1 + - uid: 152 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -3.5,-4.5 + parent: 1 + - uid: 153 + components: + - type: Transform + pos: 4.5,-4.5 + parent: 1 +- proto: GasPipeFourway + entities: + - uid: 154 + components: + - type: Transform + pos: 0.5,-2.5 + parent: 1 + - uid: 155 + components: + - type: Transform + pos: 0.5,-1.5 + parent: 1 + - uid: 156 + components: + - type: Transform + pos: 0.5,-5.5 + parent: 1 + - uid: 157 + components: + - type: Transform + pos: -0.5,-4.5 + parent: 1 + - uid: 158 + components: + - type: Transform + pos: 1.5,-4.5 + parent: 1 + - uid: 159 + components: + - type: Transform + pos: 1.5,-0.5 + parent: 1 + - uid: 160 + components: + - type: Transform + pos: -0.5,-0.5 + parent: 1 +- proto: GasPipeStraight + entities: + - uid: 161 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -0.5,-2.5 + parent: 1 + - uid: 162 + components: + - type: Transform + pos: 0.5,-3.5 + parent: 1 + - uid: 163 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 3.5,-1.5 + parent: 1 + - uid: 164 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.5,-1.5 + parent: 1 + - uid: 165 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.5,-5.5 + parent: 1 + - uid: 166 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 1.5,1.5 + parent: 1 + - uid: 167 + components: + - type: Transform + pos: 0.5,-4.5 + parent: 1 + - uid: 168 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 2.5,-1.5 + parent: 1 + - uid: 169 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -0.5,-1.5 + parent: 1 + - uid: 170 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -1.5,-1.5 + parent: 1 + - uid: 171 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -2.5,-1.5 + parent: 1 + - uid: 172 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -0.5,-5.5 + parent: 1 + - uid: 173 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -1.5,-5.5 + parent: 1 + - uid: 174 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 2.5,-5.5 + parent: 1 + - uid: 175 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 2.5,-0.5 + parent: 1 + - uid: 176 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -1.5,-4.5 + parent: 1 + - uid: 177 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 2.5,-4.5 + parent: 1 + - uid: 178 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.5,-3.5 + parent: 1 + - uid: 179 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -0.5,-3.5 + parent: 1 + - uid: 180 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 3.5,-0.5 + parent: 1 + - uid: 181 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -2.5,-0.5 + parent: 1 + - uid: 182 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -1.5,-0.5 + parent: 1 + - uid: 183 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -0.5,-1.5 + parent: 1 + - uid: 184 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.5,-1.5 + parent: 1 + - uid: 185 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.5,-2.5 + parent: 1 + - uid: 186 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 0.5,0.5 + parent: 1 + - uid: 187 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 2.5,1.5 + parent: 1 + - uid: 188 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 3.5,1.5 + parent: 1 + - uid: 189 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 4.5,1.5 + parent: 1 + - uid: 190 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -0.5,1.5 + parent: 1 + - uid: 191 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -1.5,1.5 + parent: 1 + - uid: 192 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -2.5,1.5 + parent: 1 + - uid: 193 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -3.5,1.5 + parent: 1 + - uid: 194 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 0.5,-4.5 + parent: 1 + - uid: 195 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -3.5,-0.5 + parent: 1 + - uid: 196 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 4.5,-0.5 + parent: 1 + - uid: 197 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 3.5,-4.5 + parent: 1 + - uid: 198 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -2.5,-4.5 + parent: 1 + - uid: 199 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 1.5,-2.5 + parent: 1 + - uid: 200 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -0.5,-2.5 + parent: 1 +- proto: GasPipeTJunction + entities: + - uid: 201 + components: + - type: Transform + pos: 0.5,1.5 + parent: 1 + - uid: 202 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 0.5,-0.5 + parent: 1 +- proto: GasPort + entities: + - uid: 203 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -1.5,-2.5 + parent: 1 + - uid: 204 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 2.5,-2.5 + parent: 1 +- proto: GasVentPump + entities: + - uid: 205 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 3.5,-5.5 + parent: 1 + - uid: 206 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 4.5,-1.5 + parent: 1 + - uid: 207 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -2.5,-5.5 + parent: 1 + - uid: 208 + components: + - type: Transform + pos: 0.5,-0.5 + parent: 1 + - uid: 209 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 0.5,-6.5 + parent: 1 + - uid: 210 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -3.5,-1.5 + parent: 1 +- proto: GasVentScrubber + entities: + - uid: 211 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -3.5,-5.5 + parent: 1 + - uid: 212 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 5.5,-1.5 + parent: 1 + - uid: 213 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 4.5,-5.5 + parent: 1 + - uid: 214 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.5,-5.5 + parent: 1 + - uid: 215 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -0.5,-5.5 + parent: 1 + - uid: 216 + components: + - type: Transform + pos: -0.5,0.5 + parent: 1 + - uid: 217 + components: + - type: Transform + pos: 1.5,0.5 + parent: 1 + - uid: 218 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -4.5,-1.5 + parent: 1 +- proto: GeneratorWallmountAPU + entities: + - uid: 219 + components: + - type: Transform + pos: -0.5,-8.5 + parent: 1 + - uid: 220 + components: + - type: Transform + pos: 0.5,-8.5 + parent: 1 + - uid: 221 + components: + - type: Transform + pos: 1.5,-8.5 + parent: 1 +- proto: GoldenBikeHorn + entities: + - uid: 222 + components: + - type: Transform + pos: 6.4591703,-1.2964956 + parent: 1 +- proto: GravityGeneratorMini + entities: + - uid: 223 + components: + - type: Transform + pos: -0.5,-7.5 + parent: 1 +- proto: Grille + entities: + - uid: 224 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -1.5,2.5 + parent: 1 + - uid: 225 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -0.5,3.5 + parent: 1 + - uid: 226 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 0.5,3.5 + parent: 1 + - uid: 227 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.5,3.5 + parent: 1 + - uid: 228 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 2.5,2.5 + parent: 1 + - uid: 229 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 4.5,0.5 + parent: 1 + - uid: 230 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 5.5,0.5 + parent: 1 + - uid: 231 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 6.5,0.5 + parent: 1 + - uid: 232 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -3.5,0.5 + parent: 1 + - uid: 233 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -4.5,0.5 + parent: 1 + - uid: 234 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -5.5,0.5 + parent: 1 + - uid: 235 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -5.5,-4.5 + parent: 1 + - uid: 236 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -5.5,-5.5 + parent: 1 + - uid: 237 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -5.5,-6.5 + parent: 1 + - uid: 238 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 6.5,-4.5 + parent: 1 + - uid: 239 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 6.5,-5.5 + parent: 1 + - uid: 240 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 6.5,-6.5 + parent: 1 + - uid: 241 + components: + - type: Transform + pos: -1.5,-6.5 + parent: 1 + - uid: 242 + components: + - type: Transform + pos: -1.5,-4.5 + parent: 1 +- proto: GrilleDiagonal + entities: + - uid: 243 + components: + - type: Transform + pos: -3.5,1.5 + parent: 1 + - uid: 244 + components: + - type: Transform + pos: -2.5,2.5 + parent: 1 + - uid: 245 + components: + - type: Transform + pos: -1.5,3.5 + parent: 1 + - uid: 246 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 2.5,3.5 + parent: 1 + - uid: 247 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 3.5,2.5 + parent: 1 + - uid: 248 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 2.5,1.5 + parent: 1 + - uid: 249 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 1.5,2.5 + parent: 1 + - uid: 250 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -0.5,2.5 + parent: 1 + - uid: 251 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -1.5,1.5 + parent: 1 + - uid: 252 + components: + - type: Transform + pos: -6.5,0.5 + parent: 1 + - uid: 253 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 4.5,1.5 + parent: 1 + - uid: 254 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 7.5,0.5 + parent: 1 + - uid: 255 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 7.5,-4.5 + parent: 1 + - uid: 256 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 6.5,-7.5 + parent: 1 + - uid: 257 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -5.5,-7.5 + parent: 1 + - uid: 258 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -6.5,-4.5 + parent: 1 +- proto: Gyroscope + entities: + - uid: 259 + components: + - type: Transform + pos: 1.5,-7.5 + parent: 1 +- proto: LampBanana + entities: + - uid: 260 + components: + - type: Transform + pos: 2.532099,1.060484 + parent: 1 +- proto: LauncherCreamPie + entities: + - uid: 126 + components: + - type: Transform + parent: 115 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 138 + components: + - type: Transform + parent: 127 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: PlasmaWindow + entities: + - uid: 262 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -0.5,3.5 + parent: 1 + - uid: 263 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 0.5,3.5 + parent: 1 + - uid: 264 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.5,3.5 + parent: 1 + - uid: 265 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -1.5,2.5 + parent: 1 + - uid: 266 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 2.5,2.5 + parent: 1 + - uid: 267 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -5.5,0.5 + parent: 1 + - uid: 268 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -4.5,0.5 + parent: 1 + - uid: 269 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -3.5,0.5 + parent: 1 + - uid: 270 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 4.5,0.5 + parent: 1 + - uid: 271 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 5.5,0.5 + parent: 1 + - uid: 272 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 6.5,0.5 + parent: 1 + - uid: 273 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -5.5,-4.5 + parent: 1 + - uid: 274 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -5.5,-5.5 + parent: 1 + - uid: 275 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -5.5,-6.5 + parent: 1 + - uid: 276 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 6.5,-6.5 + parent: 1 + - uid: 277 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 6.5,-5.5 + parent: 1 + - uid: 278 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 6.5,-4.5 + parent: 1 + - uid: 279 + components: + - type: Transform + pos: -1.5,-6.5 + parent: 1 + - uid: 280 + components: + - type: Transform + pos: -1.5,-4.5 + parent: 1 +- proto: PlasmaWindowDiagonal + entities: + - uid: 281 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -1.5,1.5 + parent: 1 + - uid: 282 + components: + - type: Transform + pos: -2.5,2.5 + parent: 1 + - uid: 283 + components: + - type: Transform + pos: -1.5,3.5 + parent: 1 + - uid: 284 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 2.5,3.5 + parent: 1 + - uid: 285 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 3.5,2.5 + parent: 1 + - uid: 286 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -0.5,2.5 + parent: 1 + - uid: 287 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 1.5,2.5 + parent: 1 + - uid: 288 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 2.5,1.5 + parent: 1 + - uid: 289 + components: + - type: Transform + pos: -6.5,0.5 + parent: 1 + - uid: 290 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 7.5,0.5 + parent: 1 + - uid: 291 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -6.5,-4.5 + parent: 1 + - uid: 292 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 7.5,-4.5 + parent: 1 + - uid: 293 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 6.5,-7.5 + parent: 1 + - uid: 294 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -5.5,-7.5 + parent: 1 + - uid: 295 + components: + - type: Transform + pos: -3.5,1.5 + parent: 1 + - uid: 296 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 4.5,1.5 + parent: 1 +- proto: PoweredlightLED + entities: + - uid: 297 + components: + - type: Transform + pos: 4.5,-4.5 + parent: 1 + - uid: 298 + components: + - type: Transform + pos: -3.5,-4.5 + parent: 1 + - uid: 299 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 2.5,-0.5 + parent: 1 + - uid: 300 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -1.5,-0.5 + parent: 1 +- proto: PoweredlightOrange + entities: + - uid: 301 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 0.5,-7.5 + parent: 1 +- proto: PoweredlightPink + entities: + - uid: 302 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -3.5,-6.5 + parent: 1 + - uid: 303 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 4.5,-6.5 + parent: 1 + - uid: 304 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 4.5,-0.5 + parent: 1 + - uid: 305 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -3.5,-0.5 + parent: 1 + - uid: 306 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -2.5,2.5 + parent: 1 + - uid: 307 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 3.5,2.5 + parent: 1 +- proto: RailingCorner + entities: + - uid: 308 + components: + - type: Transform + pos: 1.5,1.5 + parent: 1 + - uid: 309 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -0.5,1.5 + parent: 1 +- proto: RailingCornerSmall + entities: + - uid: 310 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.5,2.5 + parent: 1 + - uid: 311 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -0.5,2.5 + parent: 1 +- proto: RubberStampClown + entities: + - uid: 102 + components: + - type: Transform + parent: 99 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: SpawnClownSpider + entities: + - uid: 312 + components: + - type: Transform + pos: -4.5,-4.5 + parent: 1 + - uid: 313 + components: + - type: Transform + pos: -2.5,-5.5 + parent: 1 + - uid: 314 + components: + - type: Transform + pos: -3.5,-6.5 + parent: 1 +- proto: SpiderWebClown + entities: + - uid: 376 + components: + - type: Transform + pos: -4.5,-6.5 + parent: 1 + - uid: 377 + components: + - type: Transform + pos: -2.5,-4.5 + parent: 1 + - uid: 378 + components: + - type: Transform + pos: -2.5,-5.5 + parent: 1 +- proto: StatueBananiumClown + entities: + - uid: 375 + components: + - type: Transform + pos: -5.5,-0.5 + parent: 1 +- proto: SubstationBasic + entities: + - uid: 316 + components: + - type: Transform + pos: 0.5,-7.5 + parent: 1 +- proto: SuitStorageBase + entities: + - uid: 93 + components: + - type: Transform + pos: -3.5,-2.5 + parent: 1 + - type: Lock + locked: False + - type: ContainerContainer + containers: + entity_storage: !type:Container + showEnts: False + occludes: True + ents: + - 96 + - 95 + - 97 + - 94 + - 98 + - uid: 99 + components: + - type: Transform + pos: -5.5,-2.5 + parent: 1 + - type: Lock + locked: False + - type: ContainerContainer + containers: + entity_storage: !type:Container + showEnts: False + occludes: True + ents: + - 101 + - 100 + - 102 + - uid: 103 + components: + - type: Transform + pos: -4.5,-2.5 + parent: 1 + - type: Lock + locked: False + - type: ContainerContainer + containers: + entity_storage: !type:Container + showEnts: False + occludes: True + ents: + - 104 + - 105 + - 106 +- proto: TablePlasmaGlass + entities: + - uid: 317 + components: + - type: Transform + pos: -1.5,0.5 + parent: 1 + - uid: 318 + components: + - type: Transform + pos: 2.5,-0.5 + parent: 1 + - uid: 319 + components: + - type: Transform + pos: -1.5,-0.5 + parent: 1 + - uid: 320 + components: + - type: Transform + pos: 2.5,0.5 + parent: 1 +- proto: Thruster + entities: + - uid: 321 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 3.5,-8.5 + parent: 1 + - uid: 322 + components: + - type: Transform + rot: -3.141592653589793 rad + pos: -2.5,-8.5 + parent: 1 + - uid: 323 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 7.5,-5.5 + parent: 1 + - uid: 324 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -6.5,-5.5 + parent: 1 + - uid: 325 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -4.5,-8.5 + parent: 1 + - uid: 326 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 5.5,-8.5 + parent: 1 + - uid: 327 + components: + - type: Transform + pos: 5.5,1.5 + parent: 1 + - uid: 328 + components: + - type: Transform + pos: -4.5,1.5 + parent: 1 +- proto: ToolboxMechanicalFilled + entities: + - uid: 261 + components: + - type: Transform + parent: 21 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 315 + components: + - type: Transform + parent: 26 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: VendingMachineTankDispenserEVA + entities: + - uid: 329 + components: + - type: Transform + pos: -3.5,-0.5 + parent: 1 +- proto: WallClown + entities: + - uid: 330 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -6.5,-3.5 + parent: 1 + - uid: 331 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 7.5,-0.5 + parent: 1 + - uid: 332 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 7.5,-1.5 + parent: 1 + - uid: 333 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 7.5,-2.5 + parent: 1 + - uid: 334 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 7.5,-3.5 + parent: 1 + - uid: 335 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 6.5,-3.5 + parent: 1 + - uid: 336 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -5.5,-3.5 + parent: 1 + - uid: 337 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -4.5,-7.5 + parent: 1 + - uid: 338 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -3.5,-7.5 + parent: 1 + - uid: 339 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -2.5,-7.5 + parent: 1 + - uid: 340 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -1.5,-7.5 + parent: 1 + - uid: 341 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 5.5,-7.5 + parent: 1 + - uid: 342 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 4.5,-7.5 + parent: 1 + - uid: 343 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 3.5,-7.5 + parent: 1 + - uid: 344 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 2.5,-7.5 + parent: 1 + - uid: 345 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 2.5,-6.5 + parent: 1 + - uid: 346 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 2.5,-4.5 + parent: 1 + - uid: 347 + components: + - type: Transform + pos: -0.5,-3.5 + parent: 1 + - uid: 348 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -1.5,-3.5 + parent: 1 + - uid: 349 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -2.5,-3.5 + parent: 1 + - uid: 350 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -3.5,-3.5 + parent: 1 + - uid: 351 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -4.5,-3.5 + parent: 1 + - uid: 352 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -2.5,-2.5 + parent: 1 + - uid: 353 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 1.5,-3.5 + parent: 1 + - uid: 354 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 2.5,-3.5 + parent: 1 + - uid: 355 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 3.5,-3.5 + parent: 1 + - uid: 356 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 4.5,-3.5 + parent: 1 + - uid: 357 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 5.5,-3.5 + parent: 1 + - uid: 358 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 3.5,-0.5 + parent: 1 + - uid: 359 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 3.5,0.5 + parent: 1 + - uid: 360 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 3.5,1.5 + parent: 1 + - uid: 361 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -2.5,-0.5 + parent: 1 + - uid: 362 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -2.5,0.5 + parent: 1 + - uid: 363 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -2.5,1.5 + parent: 1 + - uid: 364 + components: + - type: Transform + pos: -1.5,-8.5 + parent: 1 + - uid: 365 + components: + - type: Transform + pos: -0.5,-8.5 + parent: 1 + - uid: 366 + components: + - type: Transform + pos: 0.5,-8.5 + parent: 1 + - uid: 367 + components: + - type: Transform + pos: 1.5,-8.5 + parent: 1 + - uid: 368 + components: + - type: Transform + pos: 2.5,-8.5 + parent: 1 + - uid: 369 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -6.5,-0.5 + parent: 1 + - uid: 370 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -6.5,-2.5 + parent: 1 + - uid: 371 + components: + - type: Transform + pos: 3.5,-2.5 + parent: 1 +... diff --git a/Resources/Maps/Shuttles/ShuttleEvent/lost_cargo.yml b/Resources/Maps/Shuttles/ShuttleEvent/lost_cargo.yml new file mode 100644 index 0000000000..148577363f --- /dev/null +++ b/Resources/Maps/Shuttles/ShuttleEvent/lost_cargo.yml @@ -0,0 +1,1411 @@ +meta: + format: 6 + postmapinit: false +tilemap: + 0: Space + 54: FloorGreenCircuit + 85: FloorShuttleWhite + 89: FloorSteel + 104: FloorTechMaint + 121: Plating +entities: +- proto: "" + entities: + - uid: 1 + components: + - type: MetaData + name: Cargo shuttle + - type: Transform + pos: 2.2710133,-2.4148211 + parent: invalid + - type: MapGrid + chunks: + -1,0: + ind: -1,0 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWQAAAAAAWQAAAAAAWQAAAAAAWQAAAAAAWQAAAAAAWQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWQAAAAAAWQAAAAAAWQAAAAAAWQAAAAAAWQAAAAAAWQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAWQAAAAAAWQAAAAAAWQAAAAAAWQAAAAAAWQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWQAAAAAAWQAAAAAAWQAAAAAAWQAAAAAAWQAAAAAAWQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWQAAAAAAWQAAAAAAWQAAAAAAWQAAAAAAWQAAAAAAWQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAWQAAAAAAWQAAAAAAWQAAAAAAWQAAAAAAWQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAWQAAAAAAWQAAAAAAWQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAVQAAAAAANgAAAAAAVQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAANgAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + version: 6 + 0,0: + ind: 0,0 + tiles: WQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + version: 6 + -1,-1: + ind: -1,-1 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAaAAAAAAAeQAAAAAAaAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAWQAAAAAAWQAAAAAAWQAAAAAAWQAAAAAAWQAAAAAA + version: 6 + 0,-1: + ind: 0,-1 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + version: 6 + - type: Broadphase + - type: Physics + bodyStatus: InAir + angularDamping: 0.05 + linearDamping: 0.05 + fixedRotation: False + bodyType: Dynamic + - type: Fixtures + fixtures: {} + - type: Gravity + gravityShakeSound: !type:SoundPathSpecifier + path: /Audio/Effects/alert.ogg + - type: CargoShuttle + - type: DecalGrid + chunkCollection: + version: 2 + nodes: + - node: + color: '#FFFFFFFF' + id: Bot + decals: + 0: -5,5 + 1: -4,5 + 2: -1,5 + 3: -1,-1 + 4: -2,-1 + 5: -4,-1 + 6: -1,2 + 7: -4,2 + 8: -5,2 + - node: + color: '#9FED5896' + id: CheckerNESW + decals: + 9: -3,-1 + 10: -3,0 + 11: -3,1 + 12: -3,2 + 13: -3,3 + 14: -3,4 + 15: -3,5 + - node: + color: '#9FED5896' + id: MiniTileWhiteCornerNe + decals: + 29: -1,5 + 30: -2,6 + - node: + color: '#9FED5896' + id: MiniTileWhiteCornerNw + decals: + 19: -5,5 + 20: -4,6 + - node: + color: '#9FED5896' + id: MiniTileWhiteCornerSe + decals: + 21: -1,-1 + - node: + color: '#9FED5896' + id: MiniTileWhiteCornerSw + decals: + 22: -5,-1 + - node: + color: '#9FED5896' + id: MiniTileWhiteInnerNe + decals: + 33: -2,5 + - node: + color: '#9FED5896' + id: MiniTileWhiteInnerNw + decals: + 32: -4,5 + - node: + color: '#9FED5896' + id: MiniTileWhiteLineE + decals: + 26: -1,0 + 27: -1,2 + 28: -1,4 + - node: + color: '#9FED5896' + id: MiniTileWhiteLineN + decals: + 31: -3,6 + - node: + color: '#9FED5896' + id: MiniTileWhiteLineS + decals: + 23: -4,-1 + 24: -3,-1 + 25: -2,-1 + - node: + color: '#9FED5896' + id: MiniTileWhiteLineW + decals: + 16: -5,0 + 17: -5,2 + 18: -5,4 + - node: + color: '#9FED5896' + id: WarnLineE + decals: + 36: -1,1 + 37: -1,3 + - node: + color: '#9FED5896' + id: WarnLineS + decals: + 34: -5,1 + 35: -5,3 + - type: GridAtmosphere + version: 2 + data: + tiles: + -2,0: + 0: 51400 + -2,1: + 0: 16520 + -2,-1: + 0: 32832 + -1,0: + 0: 65535 + -1,1: + 0: 30719 + -2,2: + 0: 128 + -1,-1: + 0: 63346 + -1,2: + 0: 130 + 0,0: + 0: 4112 + 0,1: + 0: 4096 + -2,-2: + 0: 32768 + -1,-2: + 0: 32768 + 0,-1: + 0: 16 + uniqueMixes: + - volume: 2500 + temperature: 293.15 + moles: + - 21.824879 + - 82.10312 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + chunkSize: 4 + - type: OccluderTree + - type: Shuttle + - type: GridPathfinding + - type: RadiationGridResistance + - type: SpreaderGrid + - type: GravityShake + shakeTimes: 10 + - type: GasTileOverlay +- proto: AirCanister + entities: + - uid: 2 + components: + - type: Transform + pos: -3.5,-1.5 + parent: 1 +- proto: AirlockGlassShuttle + entities: + - uid: 3 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -5.5,3.5 + parent: 1 + - uid: 4 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -5.5,1.5 + parent: 1 + - uid: 5 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 0.5,3.5 + parent: 1 + - uid: 6 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 0.5,1.5 + parent: 1 +- proto: APCHyperCapacity + entities: + - uid: 7 + components: + - type: Transform + pos: -0.5,6.5 + parent: 1 +- proto: AtmosDeviceFanTiny + entities: + - uid: 8 + components: + - type: Transform + pos: -5.5,1.5 + parent: 1 + - uid: 9 + components: + - type: Transform + pos: -5.5,3.5 + parent: 1 + - uid: 10 + components: + - type: Transform + pos: 0.5,3.5 + parent: 1 + - uid: 11 + components: + - type: Transform + pos: 0.5,1.5 + parent: 1 +- proto: BlastDoor + entities: + - uid: 12 + components: + - type: Transform + pos: 0.5,4.5 + parent: 1 + - type: DeviceLinkSink + links: + - 135 + - uid: 13 + components: + - type: Transform + pos: -5.5,0.5 + parent: 1 + - type: DeviceLinkSink + links: + - 134 + - uid: 14 + components: + - type: Transform + pos: 0.5,0.5 + parent: 1 + - type: DeviceLinkSink + links: + - 136 + - uid: 15 + components: + - type: Transform + pos: -5.5,4.5 + parent: 1 + - type: DeviceLinkSink + links: + - 133 +- proto: BoxMRE + entities: + - uid: 93 + components: + - type: Transform + pos: -1.3692467,7.6214685 + parent: 1 +- proto: CableApcExtension + entities: + - uid: 16 + components: + - type: Transform + pos: -0.5,6.5 + parent: 1 + - uid: 17 + components: + - type: Transform + pos: -1.5,6.5 + parent: 1 + - uid: 18 + components: + - type: Transform + pos: -2.5,6.5 + parent: 1 + - uid: 19 + components: + - type: Transform + pos: -2.5,5.5 + parent: 1 + - uid: 20 + components: + - type: Transform + pos: -2.5,8.5 + parent: 1 + - uid: 21 + components: + - type: Transform + pos: -3.5,8.5 + parent: 1 + - uid: 22 + components: + - type: Transform + pos: -1.5,8.5 + parent: 1 + - uid: 23 + components: + - type: Transform + pos: -3.5,6.5 + parent: 1 + - uid: 24 + components: + - type: Transform + pos: -3.5,7.5 + parent: 1 + - uid: 25 + components: + - type: Transform + pos: -2.5,4.5 + parent: 1 + - uid: 26 + components: + - type: Transform + pos: -2.5,3.5 + parent: 1 + - uid: 27 + components: + - type: Transform + pos: -2.5,2.5 + parent: 1 + - uid: 28 + components: + - type: Transform + pos: -2.5,1.5 + parent: 1 + - uid: 29 + components: + - type: Transform + pos: -2.5,0.5 + parent: 1 + - uid: 30 + components: + - type: Transform + pos: -2.5,-0.5 + parent: 1 + - uid: 31 + components: + - type: Transform + pos: -2.5,-1.5 + parent: 1 + - uid: 32 + components: + - type: Transform + pos: -1.5,-4.5 + parent: 1 + - uid: 33 + components: + - type: Transform + pos: -1.5,-1.5 + parent: 1 + - uid: 34 + components: + - type: Transform + pos: -1.5,-2.5 + parent: 1 + - uid: 35 + components: + - type: Transform + pos: -2.5,-4.5 + parent: 1 + - uid: 36 + components: + - type: Transform + pos: -1.5,-3.5 + parent: 1 + - uid: 37 + components: + - type: Transform + pos: -3.5,-4.5 + parent: 1 + - uid: 38 + components: + - type: Transform + pos: -3.5,-3.5 + parent: 1 + - uid: 39 + components: + - type: Transform + pos: -4.5,7.5 + parent: 1 + - uid: 40 + components: + - type: Transform + pos: -0.5,7.5 + parent: 1 + - uid: 41 + components: + - type: Transform + pos: -1.5,3.5 + parent: 1 + - uid: 42 + components: + - type: Transform + pos: -0.5,3.5 + parent: 1 + - uid: 43 + components: + - type: Transform + pos: -1.5,1.5 + parent: 1 + - uid: 44 + components: + - type: Transform + pos: -0.5,1.5 + parent: 1 + - uid: 45 + components: + - type: Transform + pos: -3.5,1.5 + parent: 1 + - uid: 46 + components: + - type: Transform + pos: -4.5,1.5 + parent: 1 + - uid: 47 + components: + - type: Transform + pos: -3.5,3.5 + parent: 1 + - uid: 48 + components: + - type: Transform + pos: -4.5,3.5 + parent: 1 + - uid: 49 + components: + - type: Transform + pos: -4.5,-3.5 + parent: 1 +- proto: CableHV + entities: + - uid: 50 + components: + - type: Transform + pos: -1.5,-2.5 + parent: 1 + - uid: 51 + components: + - type: Transform + pos: -2.5,-2.5 + parent: 1 + - uid: 52 + components: + - type: Transform + pos: -2.5,-3.5 + parent: 1 +- proto: CableMV + entities: + - uid: 53 + components: + - type: Transform + pos: -1.5,-2.5 + parent: 1 + - uid: 54 + components: + - type: Transform + pos: -1.5,-1.5 + parent: 1 + - uid: 55 + components: + - type: Transform + pos: -2.5,-1.5 + parent: 1 + - uid: 56 + components: + - type: Transform + pos: -2.5,-0.5 + parent: 1 + - uid: 57 + components: + - type: Transform + pos: -2.5,0.5 + parent: 1 + - uid: 58 + components: + - type: Transform + pos: -2.5,1.5 + parent: 1 + - uid: 59 + components: + - type: Transform + pos: -2.5,2.5 + parent: 1 + - uid: 60 + components: + - type: Transform + pos: -2.5,3.5 + parent: 1 + - uid: 61 + components: + - type: Transform + pos: -2.5,4.5 + parent: 1 + - uid: 62 + components: + - type: Transform + pos: -2.5,5.5 + parent: 1 + - uid: 63 + components: + - type: Transform + pos: -2.5,6.5 + parent: 1 + - uid: 64 + components: + - type: Transform + pos: -1.5,6.5 + parent: 1 + - uid: 65 + components: + - type: Transform + pos: -0.5,6.5 + parent: 1 +- proto: CableTerminal + entities: + - uid: 66 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -2.5,-3.5 + parent: 1 +- proto: Catwalk + entities: + - uid: 67 + components: + - type: Transform + pos: -1.5,-0.5 + parent: 1 + - uid: 68 + components: + - type: Transform + pos: -0.5,-0.5 + parent: 1 + - uid: 71 + components: + - type: Transform + pos: -4.5,2.5 + parent: 1 + - uid: 72 + components: + - type: Transform + pos: -3.5,2.5 + parent: 1 + - uid: 73 + components: + - type: Transform + pos: -3.5,5.5 + parent: 1 + - uid: 74 + components: + - type: Transform + pos: -4.5,5.5 + parent: 1 + - uid: 75 + components: + - type: Transform + pos: -3.5,-0.5 + parent: 1 + - uid: 76 + components: + - type: Transform + pos: -4.5,-0.5 + parent: 1 + - uid: 77 + components: + - type: Transform + pos: -1.5,5.5 + parent: 1 + - uid: 78 + components: + - type: Transform + pos: -0.5,5.5 + parent: 1 +- proto: ChairPilotSeat + entities: + - uid: 70 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -0.5,2.5 + parent: 1 + - uid: 79 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -2.5,7.5 + parent: 1 +- proto: ComputerShuttle + entities: + - uid: 80 + components: + - type: Transform + pos: -2.5,8.5 + parent: 1 +- proto: ConveyorBelt + entities: + - uid: 81 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 0.5,4.5 + parent: 1 + - type: DeviceLinkSink + links: + - 151 + - uid: 82 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -0.5,4.5 + parent: 1 + - type: DeviceLinkSink + links: + - 151 + - uid: 83 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -1.5,4.5 + parent: 1 + - type: DeviceLinkSink + links: + - 151 + - uid: 84 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -5.5,0.5 + parent: 1 + - type: DeviceLinkSink + links: + - 149 + - uid: 85 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -4.5,0.5 + parent: 1 + - type: DeviceLinkSink + links: + - 149 + - uid: 86 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -3.5,0.5 + parent: 1 + - type: DeviceLinkSink + links: + - 149 + - uid: 87 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -0.5,0.5 + parent: 1 + - type: DeviceLinkSink + links: + - 150 + - uid: 88 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -3.5,4.5 + parent: 1 + - type: DeviceLinkSink + links: + - 152 + - uid: 89 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -4.5,4.5 + parent: 1 + - type: DeviceLinkSink + links: + - 152 + - uid: 90 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -5.5,4.5 + parent: 1 + - type: DeviceLinkSink + links: + - 152 + - uid: 91 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -1.5,0.5 + parent: 1 + - type: DeviceLinkSink + links: + - 150 + - uid: 92 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 0.5,0.5 + parent: 1 + - type: DeviceLinkSink + links: + - 150 +- proto: CrateFilledSpawner + entities: + - uid: 95 + components: + - type: Transform + pos: -0.5,-0.5 + parent: 1 + - uid: 96 + components: + - type: Transform + pos: -4.5,2.5 + parent: 1 + - uid: 97 + components: + - type: Transform + pos: -0.5,5.5 + parent: 1 + - uid: 98 + components: + - type: Transform + pos: -3.5,2.5 + parent: 1 +- proto: DrinkBeerBottleFull + entities: + - uid: 99 + components: + - type: Transform + pos: -3.2915764,7.812316 + parent: 1 + - uid: 100 + components: + - type: Transform + pos: -3.4715438,7.654894 + parent: 1 +- proto: GasPipeBend + entities: + - uid: 101 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -2.5,-1.5 + parent: 1 +- proto: GasPipeStraight + entities: + - uid: 102 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -2.5,0.5 + parent: 1 + - uid: 103 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -2.5,1.5 + parent: 1 + - uid: 104 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -2.5,-0.5 + parent: 1 +- proto: GasPort + entities: + - uid: 105 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -3.5,-1.5 + parent: 1 +- proto: GasVentPump + entities: + - uid: 106 + components: + - type: Transform + pos: -2.5,2.5 + parent: 1 +- proto: GeneratorBasic15kW + entities: + - uid: 107 + components: + - type: Transform + pos: -2.5,-3.5 + parent: 1 +- proto: GravityGeneratorMini + entities: + - uid: 108 + components: + - type: Transform + pos: -1.5,-1.5 + parent: 1 +- proto: Grille + entities: + - uid: 109 + components: + - type: Transform + pos: -3.5,8.5 + parent: 1 + - uid: 110 + components: + - type: Transform + pos: -3.5,9.5 + parent: 1 + - uid: 111 + components: + - type: Transform + pos: -2.5,9.5 + parent: 1 + - uid: 112 + components: + - type: Transform + pos: -1.5,9.5 + parent: 1 + - uid: 113 + components: + - type: Transform + pos: -1.5,8.5 + parent: 1 +- proto: Gyroscope + entities: + - uid: 114 + components: + - type: Transform + pos: -3.5,-2.5 + parent: 1 +- proto: LostCargoTechnicianSpawner + entities: + - uid: 69 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -0.5,2.5 + parent: 1 + - uid: 187 + components: + - type: Transform + pos: -2.5,7.5 + parent: 1 +- proto: PlasticFlapsAirtightClear + entities: + - uid: 116 + components: + - type: Transform + pos: 0.5,0.5 + parent: 1 + - uid: 117 + components: + - type: Transform + pos: -5.5,0.5 + parent: 1 + - uid: 118 + components: + - type: Transform + pos: -5.5,4.5 + parent: 1 + - uid: 119 + components: + - type: Transform + pos: 0.5,4.5 + parent: 1 +- proto: Poweredlight + entities: + - uid: 120 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -0.5,2.5 + parent: 1 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 121 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -4.5,2.5 + parent: 1 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 122 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -3.5,7.5 + parent: 1 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 123 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -1.5,7.5 + parent: 1 + - type: ApcPowerReceiver + powerLoad: 0 +- proto: RandomPosterAny + entities: + - uid: 124 + components: + - type: Transform + pos: -4.5,6.5 + parent: 1 +- proto: RandomSpawner100 + entities: + - uid: 188 + components: + - type: Transform + pos: -4.5,1.5 + parent: 1 + - uid: 189 + components: + - type: Transform + pos: -0.5,3.5 + parent: 1 + - uid: 190 + components: + - type: Transform + pos: -2.5,4.5 + parent: 1 + - uid: 191 + components: + - type: Transform + pos: -1.5,-0.5 + parent: 1 + - uid: 192 + components: + - type: Transform + pos: -0.5,0.5 + parent: 1 + - uid: 193 + components: + - type: Transform + pos: -4.5,4.5 + parent: 1 +- proto: SalvageLootSpawner + entities: + - uid: 115 + components: + - type: Transform + pos: -1.5,2.5 + parent: 1 +- proto: SalvageMaterialCrateSpawner + entities: + - uid: 94 + components: + - type: Transform + pos: -3.5,-0.5 + parent: 1 + - uid: 125 + components: + - type: Transform + pos: -4.5,-0.5 + parent: 1 + - uid: 127 + components: + - type: Transform + pos: -3.5,5.5 + parent: 1 +- proto: ShuttleWindow + entities: + - uid: 128 + components: + - type: Transform + pos: -1.5,8.5 + parent: 1 + - uid: 129 + components: + - type: Transform + pos: -1.5,9.5 + parent: 1 + - uid: 130 + components: + - type: Transform + pos: -2.5,9.5 + parent: 1 + - uid: 131 + components: + - type: Transform + pos: -3.5,9.5 + parent: 1 + - uid: 132 + components: + - type: Transform + pos: -3.5,8.5 + parent: 1 +- proto: SignalButton + entities: + - uid: 133 + components: + - type: Transform + pos: -5.5,5.5 + parent: 1 + - type: DeviceLinkSource + linkedPorts: + 15: + - Pressed: Toggle + - uid: 134 + components: + - type: Transform + pos: -5.5,-0.5 + parent: 1 + - type: DeviceLinkSource + linkedPorts: + 13: + - Pressed: Toggle + - uid: 135 + components: + - type: Transform + pos: 0.5,5.5 + parent: 1 + - type: DeviceLinkSource + linkedPorts: + 12: + - Pressed: Toggle + - uid: 136 + components: + - type: Transform + pos: 0.5,-0.5 + parent: 1 + - type: DeviceLinkSource + linkedPorts: + 14: + - Pressed: Toggle +- proto: SMESBasic + entities: + - uid: 137 + components: + - type: Transform + pos: -2.5,-2.5 + parent: 1 +- proto: SubstationBasic + entities: + - uid: 138 + components: + - type: Transform + pos: -1.5,-2.5 + parent: 1 +- proto: TableReinforced + entities: + - uid: 126 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -1.5,2.5 + parent: 1 + - uid: 139 + components: + - type: Transform + pos: -3.5,7.5 + parent: 1 + - uid: 140 + components: + - type: Transform + pos: -1.5,7.5 + parent: 1 +- proto: Thruster + entities: + - uid: 141 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 0.5,-2.5 + parent: 1 + - uid: 142 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 0.5,7.5 + parent: 1 + - uid: 143 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -5.5,7.5 + parent: 1 + - uid: 144 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -5.5,-2.5 + parent: 1 + - uid: 145 + components: + - type: Transform + pos: -4.5,9.5 + parent: 1 + - uid: 146 + components: + - type: Transform + pos: -0.5,9.5 + parent: 1 + - uid: 147 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -0.5,-4.5 + parent: 1 + - uid: 148 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -4.5,-4.5 + parent: 1 +- proto: TwoWayLever + entities: + - uid: 149 + components: + - type: Transform + pos: -3.5,1.5 + parent: 1 + - type: DeviceLinkSource + linkedPorts: + 86: + - Left: Forward + - Right: Reverse + - Middle: Off + 85: + - Left: Forward + - Right: Reverse + - Middle: Off + 84: + - Left: Forward + - Right: Reverse + - Middle: Off + - uid: 150 + components: + - type: Transform + pos: -1.5,1.5 + parent: 1 + - type: DeviceLinkSource + linkedPorts: + 91: + - Left: Forward + - Right: Reverse + - Middle: Off + 87: + - Left: Forward + - Right: Reverse + - Middle: Off + 92: + - Left: Forward + - Right: Reverse + - Middle: Off + - uid: 151 + components: + - type: Transform + pos: -1.5,3.5 + parent: 1 + - type: DeviceLinkSource + linkedPorts: + 81: + - Left: Forward + - Right: Reverse + - Middle: Off + 82: + - Left: Forward + - Right: Reverse + - Middle: Off + 83: + - Left: Forward + - Right: Reverse + - Middle: Off + - uid: 152 + components: + - type: Transform + pos: -3.5,3.5 + parent: 1 + - type: DeviceLinkSource + linkedPorts: + 90: + - Left: Forward + - Right: Reverse + - Middle: Off + 89: + - Left: Forward + - Right: Reverse + - Middle: Off + 88: + - Left: Forward + - Right: Reverse + - Middle: Off +- proto: WallShuttle + entities: + - uid: 153 + components: + - type: Transform + pos: -5.5,8.5 + parent: 1 + - uid: 154 + components: + - type: Transform + pos: -4.5,8.5 + parent: 1 + - uid: 155 + components: + - type: Transform + pos: -0.5,8.5 + parent: 1 + - uid: 156 + components: + - type: Transform + pos: 0.5,8.5 + parent: 1 + - uid: 157 + components: + - type: Transform + pos: -0.5,-3.5 + parent: 1 + - uid: 158 + components: + - type: Transform + pos: 0.5,-3.5 + parent: 1 + - uid: 159 + components: + - type: Transform + pos: -4.5,-3.5 + parent: 1 + - uid: 160 + components: + - type: Transform + pos: -5.5,-3.5 + parent: 1 + - uid: 161 + components: + - type: Transform + pos: -0.5,-1.5 + parent: 1 + - uid: 162 + components: + - type: Transform + pos: -0.5,-2.5 + parent: 1 + - uid: 163 + components: + - type: Transform + pos: -4.5,-1.5 + parent: 1 + - uid: 164 + components: + - type: Transform + pos: 0.5,-1.5 + parent: 1 + - uid: 165 + components: + - type: Transform + pos: -4.5,-2.5 + parent: 1 + - uid: 166 + components: + - type: Transform + pos: -5.5,-1.5 + parent: 1 + - uid: 167 + components: + - type: Transform + pos: -0.5,7.5 + parent: 1 + - uid: 168 + components: + - type: Transform + pos: -0.5,6.5 + parent: 1 + - uid: 169 + components: + - type: Transform + pos: 0.5,6.5 + parent: 1 + - uid: 170 + components: + - type: Transform + pos: -4.5,7.5 + parent: 1 + - uid: 171 + components: + - type: Transform + pos: -4.5,6.5 + parent: 1 + - uid: 172 + components: + - type: Transform + pos: -5.5,6.5 + parent: 1 + - uid: 173 + components: + - type: Transform + pos: -5.5,-0.5 + parent: 1 + - uid: 174 + components: + - type: Transform + pos: -5.5,5.5 + parent: 1 + - uid: 175 + components: + - type: Transform + pos: 0.5,5.5 + parent: 1 + - uid: 176 + components: + - type: Transform + pos: 0.5,-0.5 + parent: 1 + - uid: 177 + components: + - type: Transform + pos: 0.5,2.5 + parent: 1 + - uid: 178 + components: + - type: Transform + pos: -5.5,2.5 + parent: 1 + - uid: 179 + components: + - type: Transform + pos: -3.5,-4.5 + parent: 1 + - uid: 180 + components: + - type: Transform + pos: -2.5,-4.5 + parent: 1 + - uid: 181 + components: + - type: Transform + pos: -1.5,-4.5 + parent: 1 + - uid: 182 + components: + - type: Transform + pos: -3.5,-3.5 + parent: 1 + - uid: 183 + components: + - type: Transform + pos: -1.5,-3.5 + parent: 1 +- proto: WindoorCargoLocked + entities: + - uid: 184 + components: + - type: Transform + pos: -2.5,7.5 + parent: 1 +- proto: WindowReinforcedDirectional + entities: + - uid: 185 + components: + - type: Transform + pos: -1.5,7.5 + parent: 1 + - uid: 186 + components: + - type: Transform + pos: -3.5,7.5 + parent: 1 +... diff --git a/Resources/Maps/Shuttles/striker.yml b/Resources/Maps/Shuttles/ShuttleEvent/striker.yml similarity index 95% rename from Resources/Maps/Shuttles/striker.yml rename to Resources/Maps/Shuttles/ShuttleEvent/striker.yml index 35b6178bd4..ada502275f 100644 --- a/Resources/Maps/Shuttles/striker.yml +++ b/Resources/Maps/Shuttles/ShuttleEvent/striker.yml @@ -1771,7 +1771,7 @@ entities: - type: Transform pos: 0.5436061,-7.5129323 parent: 325 -- proto: SpawnPointLoneNukeOperative +- proto: SpawnPointNukies entities: - uid: 322 components: diff --git a/Resources/Maps/Shuttles/ShuttleEvent/syndie_evacpod.yml b/Resources/Maps/Shuttles/ShuttleEvent/syndie_evacpod.yml new file mode 100644 index 0000000000..d90fadba23 --- /dev/null +++ b/Resources/Maps/Shuttles/ShuttleEvent/syndie_evacpod.yml @@ -0,0 +1,1188 @@ +meta: + format: 6 + postmapinit: false +tilemap: + 0: Space + 29: FloorDark + 84: FloorShuttleRed + 101: FloorSteelOffset + 104: FloorTechMaint + 120: Lattice + 121: Plating +entities: +- proto: "" + entities: + - uid: 1 + components: + - type: MetaData + desc: Evacuation pod + name: Evacuation pod + - type: Transform + parent: invalid + - type: MapGrid + chunks: + -1,-1: + ind: -1,-1 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAAAAAAAAAeAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAeQAAAAAAeAAAAAAAeQAAAAAAHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAZQAAAAAAZQAAAAAA + version: 6 + -1,0: + ind: -1,0 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAZQAAAAAAZQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAZQAAAAAAVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeAAAAAAAeQAAAAAAVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAeAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + version: 6 + 0,-1: + ind: 0,-1 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeAAAAAAAAAAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHQAAAAAAHQAAAAAAeQAAAAAAeAAAAAAAeQAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZQAAAAAAZQAAAAAAZQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + version: 6 + 0,0: + ind: 0,0 + tiles: ZQAAAAAAZQAAAAAAZQAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAAAAAAAVAAAAAAAZQAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAAAAAAAVAAAAAAAeQAAAAAAeAAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + version: 6 + - type: Broadphase + - type: Physics + bodyStatus: InAir + angularDamping: 0.05 + linearDamping: 0.05 + fixedRotation: False + bodyType: Dynamic + - type: Fixtures + fixtures: {} + - type: OccluderTree + - type: SpreaderGrid + - type: Shuttle + - type: GridPathfinding + - type: Gravity + gravityShakeSound: !type:SoundPathSpecifier + path: /Audio/Effects/alert.ogg + - type: DecalGrid + chunkCollection: + version: 2 + nodes: + - node: + color: '#A91409FF' + id: StandClearGreyscale + decals: + 18: 0,-1 + - node: + color: '#A91409FF' + id: WarnCornerSmallGreyscaleNE + decals: + 15: -2,0 + - node: + color: '#A91409FF' + id: WarnCornerSmallGreyscaleNW + decals: + 14: 2,0 + - node: + color: '#A91409FF' + id: WarnEndGreyscaleN + decals: + 9: -2,1 + 10: 2,1 + - node: + color: '#A91409FF' + id: WarnLineGreyscaleE + decals: + 0: 2,0 + 8: 2,-1 + 17: 5,0 + - node: + color: '#A91409FF' + id: WarnLineGreyscaleN + decals: + 11: -1,0 + 12: 0,0 + 13: 1,0 + - node: + color: '#A91409FF' + id: WarnLineGreyscaleS + decals: + 1: 1,-1 + 2: 0,-1 + 3: -1,-1 + 4: -2,-1 + 5: 2,-1 + - node: + color: '#A91409FF' + id: WarnLineGreyscaleW + decals: + 6: -2,-1 + 7: -2,0 + 16: -5,0 + - type: GridAtmosphere + version: 2 + data: + tiles: + -2,-1: + 0: 18432 + -2,0: + 1: 12 + 0: 64 + -1,-1: + 0: 601 + 1: 51200 + -1,0: + 1: 2255 + 0: 16896 + 0,-1: + 0: 2115 + 1: 29440 + -2,1: + 0: 8 + -1,1: + 0: 4096 + 0,0: + 1: 895 + 0: 18432 + 1,-1: + 0: 16913 + 1,0: + 1: 7 + 0: 64 + 1,1: + 0: 4098 + uniqueMixes: + - volume: 2500 + immutable: True + moles: + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - volume: 2500 + temperature: 293.15 + moles: + - 21.824879 + - 82.10312 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + chunkSize: 4 + - type: GasTileOverlay + - type: NavMap + - type: RadiationGridResistance +- proto: AirlockShuttleSyndicate + entities: + - uid: 2 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 6.5,0.5 + parent: 1 + - uid: 3 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -5.5,0.5 + parent: 1 +- proto: AirlockSyndicate + entities: + - uid: 4 + components: + - type: Transform + pos: 3.5,0.5 + parent: 1 + - uid: 5 + components: + - type: Transform + pos: -2.5,0.5 + parent: 1 +- proto: APCBasic + entities: + - uid: 6 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -2.5,1.5 + parent: 1 +- proto: AtmosDeviceFanTiny + entities: + - uid: 7 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -5.5,0.5 + parent: 1 + - uid: 8 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 6.5,0.5 + parent: 1 +- proto: BannerSyndicate + entities: + - uid: 9 + components: + - type: Transform + pos: 1.5,1.5 + parent: 1 +- proto: CableApcExtension + entities: + - uid: 10 + components: + - type: Transform + pos: -2.5,1.5 + parent: 1 + - uid: 11 + components: + - type: Transform + pos: -2.5,0.5 + parent: 1 + - uid: 12 + components: + - type: Transform + pos: -3.5,0.5 + parent: 1 + - uid: 13 + components: + - type: Transform + pos: -4.5,0.5 + parent: 1 + - uid: 14 + components: + - type: Transform + pos: -5.5,0.5 + parent: 1 + - uid: 15 + components: + - type: Transform + pos: -1.5,0.5 + parent: 1 + - uid: 16 + components: + - type: Transform + pos: -0.5,0.5 + parent: 1 + - uid: 17 + components: + - type: Transform + pos: 0.5,0.5 + parent: 1 + - uid: 18 + components: + - type: Transform + pos: 1.5,0.5 + parent: 1 + - uid: 19 + components: + - type: Transform + pos: 2.5,0.5 + parent: 1 + - uid: 20 + components: + - type: Transform + pos: 3.5,0.5 + parent: 1 + - uid: 21 + components: + - type: Transform + pos: 4.5,0.5 + parent: 1 + - uid: 22 + components: + - type: Transform + pos: 5.5,0.5 + parent: 1 + - uid: 23 + components: + - type: Transform + pos: 0.5,-0.5 + parent: 1 + - uid: 24 + components: + - type: Transform + pos: 0.5,-1.5 + parent: 1 + - uid: 25 + components: + - type: Transform + pos: 0.5,-2.5 + parent: 1 + - uid: 26 + components: + - type: Transform + pos: -0.5,-2.5 + parent: 1 + - uid: 27 + components: + - type: Transform + pos: 1.5,-2.5 + parent: 1 + - uid: 28 + components: + - type: Transform + pos: -0.5,-1.5 + parent: 1 + - uid: 29 + components: + - type: Transform + pos: -1.5,-1.5 + parent: 1 + - uid: 30 + components: + - type: Transform + pos: 1.5,-1.5 + parent: 1 + - uid: 31 + components: + - type: Transform + pos: 2.5,-1.5 + parent: 1 + - uid: 32 + components: + - type: Transform + pos: 0.5,1.5 + parent: 1 +- proto: CableHV + entities: + - uid: 33 + components: + - type: Transform + pos: -2.5,-0.5 + parent: 1 + - uid: 34 + components: + - type: Transform + pos: -1.5,-0.5 + parent: 1 + - uid: 35 + components: + - type: Transform + pos: -0.5,-0.5 + parent: 1 + - uid: 36 + components: + - type: Transform + pos: 0.5,-0.5 + parent: 1 + - uid: 37 + components: + - type: Transform + pos: 1.5,-0.5 + parent: 1 + - uid: 38 + components: + - type: Transform + pos: 2.5,-0.5 + parent: 1 + - uid: 39 + components: + - type: Transform + pos: 3.5,-0.5 + parent: 1 + - uid: 40 + components: + - type: Transform + pos: 3.5,-0.5 + parent: 1 + - uid: 41 + components: + - type: Transform + pos: 3.5,0.5 + parent: 1 + - uid: 42 + components: + - type: Transform + pos: 3.5,1.5 + parent: 1 +- proto: CableMV + entities: + - uid: 43 + components: + - type: Transform + pos: 3.5,1.5 + parent: 1 + - uid: 44 + components: + - type: Transform + pos: 2.5,1.5 + parent: 1 + - uid: 45 + components: + - type: Transform + pos: 1.5,1.5 + parent: 1 + - uid: 46 + components: + - type: Transform + pos: 0.5,1.5 + parent: 1 + - uid: 47 + components: + - type: Transform + pos: -0.5,1.5 + parent: 1 + - uid: 48 + components: + - type: Transform + pos: -1.5,1.5 + parent: 1 + - uid: 49 + components: + - type: Transform + pos: -2.5,1.5 + parent: 1 +- proto: ChairPilotSeat + entities: + - uid: 50 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 0.5,1.5 + parent: 1 + - uid: 51 + components: + - type: Transform + pos: -1.5,1.5 + parent: 1 + - uid: 52 + components: + - type: Transform + pos: 2.5,1.5 + parent: 1 +- proto: ClosetWallEmergencyFilledRandom + entities: + - uid: 53 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -3.5,-0.5 + parent: 1 +- proto: ClosetWallFireFilledRandom + entities: + - uid: 54 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 4.5,-0.5 + parent: 1 +- proto: ClothingHeadPyjamaSyndicateRed + entities: + - uid: 98 + components: + - type: Transform + parent: 92 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: ClothingNeckScarfStripedSyndieRed + entities: + - uid: 101 + components: + - type: Transform + parent: 92 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 102 + components: + - type: Transform + parent: 92 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: ComputerShuttleSyndie + entities: + - uid: 55 + components: + - type: Transform + pos: 0.5,2.5 + parent: 1 +- proto: CrateSyndicate + entities: + - uid: 92 + components: + - type: Transform + pos: 1.5,-0.5 + parent: 1 + - type: EntityStorage + air: + volume: 200 + immutable: False + temperature: 293.14673 + moles: + - 1.7459903 + - 6.568249 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - type: ContainerContainer + containers: + entity_storage: !type:Container + showEnts: False + occludes: True + ents: + - 102 + - 101 + - 100 + - 99 + - 98 + - 97 + - 96 + - 95 + - 94 + - 93 + paper_label: !type:ContainerSlot + showEnts: False + occludes: True + ent: null +- proto: CyberPen + entities: + - uid: 93 + components: + - type: Transform + parent: 92 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: FaxMachineSyndie + entities: + - uid: 162 + components: + - type: Transform + pos: -0.5,1.5 + parent: 1 +- proto: GeneratorWallmountAPU + entities: + - uid: 56 + components: + - type: Transform + pos: 3.5,-0.5 + parent: 1 +- proto: GeneratorWallmountBasic + entities: + - uid: 57 + components: + - type: Transform + pos: -2.5,-0.5 + parent: 1 +- proto: Grille + entities: + - uid: 58 + components: + - type: Transform + pos: 2.5,2.5 + parent: 1 + - uid: 59 + components: + - type: Transform + pos: 0.5,3.5 + parent: 1 + - uid: 60 + components: + - type: Transform + pos: -1.5,2.5 + parent: 1 + - uid: 61 + components: + - type: Transform + pos: -0.5,3.5 + parent: 1 + - uid: 62 + components: + - type: Transform + pos: 1.5,3.5 + parent: 1 +- proto: GrilleDiagonal + entities: + - uid: 63 + components: + - type: Transform + pos: -2.5,2.5 + parent: 1 + - uid: 64 + components: + - type: Transform + pos: -1.5,3.5 + parent: 1 + - uid: 65 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 2.5,3.5 + parent: 1 + - uid: 66 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 3.5,2.5 + parent: 1 + - uid: 67 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -0.5,2.5 + parent: 1 + - uid: 68 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 1.5,2.5 + parent: 1 +- proto: Gyroscope + entities: + - uid: 69 + components: + - type: Transform + pos: 0.5,-1.5 + parent: 1 +- proto: Paper + entities: + - uid: 95 + components: + - type: Transform + parent: 92 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 97 + components: + - type: Transform + parent: 92 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 99 + components: + - type: Transform + parent: 92 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 100 + components: + - type: Transform + parent: 92 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: PlasmaWindowDiagonal + entities: + - uid: 70 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -0.5,2.5 + parent: 1 + - uid: 71 + components: + - type: Transform + pos: -1.5,3.5 + parent: 1 + - uid: 72 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 1.5,2.5 + parent: 1 + - uid: 73 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 3.5,2.5 + parent: 1 + - uid: 74 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 2.5,3.5 + parent: 1 + - uid: 75 + components: + - type: Transform + pos: -2.5,2.5 + parent: 1 +- proto: Poweredlight + entities: + - uid: 76 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 0.5,-1.5 + parent: 1 +- proto: PoweredSmallLight + entities: + - uid: 77 + components: + - type: Transform + pos: -4.5,0.5 + parent: 1 + - uid: 78 + components: + - type: Transform + pos: 5.5,0.5 + parent: 1 + - uid: 79 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 2.5,1.5 + parent: 1 + - uid: 80 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -1.5,1.5 + parent: 1 +- proto: RandomPosterContraband + entities: + - uid: 81 + components: + - type: Transform + pos: -3.5,1.5 + parent: 1 + - uid: 82 + components: + - type: Transform + pos: 4.5,1.5 + parent: 1 +- proto: ReinforcedPlasmaWindow + entities: + - uid: 83 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 0.5,3.5 + parent: 1 + - uid: 84 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -0.5,3.5 + parent: 1 + - uid: 85 + components: + - type: Transform + pos: 2.5,2.5 + parent: 1 + - uid: 86 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.5,3.5 + parent: 1 + - uid: 87 + components: + - type: Transform + pos: -1.5,2.5 + parent: 1 +- proto: RubberStampSyndicate + entities: + - uid: 94 + components: + - type: Transform + parent: 92 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: SubstationWallBasic + entities: + - uid: 88 + components: + - type: Transform + pos: 3.5,1.5 + parent: 1 +- proto: SyndieDisasterVictimSpawner + entities: + - uid: 89 + components: + - type: Transform + pos: -1.5,1.5 + parent: 1 + - uid: 90 + components: + - type: Transform + pos: 0.5,1.5 + parent: 1 + - uid: 91 + components: + - type: Transform + pos: 2.5,1.5 + parent: 1 +- proto: TableGlass + entities: + - uid: 163 + components: + - type: Transform + pos: -0.5,1.5 + parent: 1 +- proto: Thruster + entities: + - uid: 103 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -0.5,-3.5 + parent: 1 + - uid: 104 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 0.5,-3.5 + parent: 1 + - uid: 105 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.5,-3.5 + parent: 1 + - uid: 106 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -2.5,-1.5 + parent: 1 + - uid: 107 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 3.5,-1.5 + parent: 1 +- proto: ToolboxSyndicateFilled + entities: + - uid: 96 + components: + - type: Transform + parent: 92 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: VendingMachineTankDispenserEVA + entities: + - uid: 108 + components: + - type: Transform + pos: -0.5,-0.5 + parent: 1 +- proto: WallPlastitanium + entities: + - uid: 109 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 4.5,-0.5 + parent: 1 + - uid: 110 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -4.5,1.5 + parent: 1 + - uid: 111 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -3.5,5.5 + parent: 1 + - uid: 112 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 4.5,6.5 + parent: 1 + - uid: 113 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 4.5,3.5 + parent: 1 + - uid: 114 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 5.5,-0.5 + parent: 1 + - uid: 115 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -3.5,2.5 + parent: 1 + - uid: 116 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 5.5,2.5 + parent: 1 + - uid: 117 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -3.5,1.5 + parent: 1 + - uid: 118 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -3.5,-0.5 + parent: 1 + - uid: 119 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -4.5,3.5 + parent: 1 + - uid: 120 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -4.5,-0.5 + parent: 1 + - uid: 121 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -3.5,3.5 + parent: 1 + - uid: 122 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 5.5,1.5 + parent: 1 + - uid: 123 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 4.5,4.5 + parent: 1 + - uid: 124 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 4.5,1.5 + parent: 1 + - uid: 125 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 4.5,-1.5 + parent: 1 + - uid: 126 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -2.5,1.5 + parent: 1 + - uid: 127 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 4.5,5.5 + parent: 1 + - uid: 128 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 4.5,2.5 + parent: 1 + - uid: 129 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -3.5,-1.5 + parent: 1 + - uid: 130 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 5.5,3.5 + parent: 1 + - uid: 131 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -3.5,4.5 + parent: 1 + - uid: 132 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -3.5,6.5 + parent: 1 + - uid: 133 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -4.5,2.5 + parent: 1 + - uid: 134 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 3.5,1.5 + parent: 1 + - uid: 135 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 2.5,-1.5 + parent: 1 + - uid: 136 + components: + - type: Transform + pos: -0.5,-2.5 + parent: 1 + - uid: 137 + components: + - type: Transform + pos: 0.5,-2.5 + parent: 1 + - uid: 138 + components: + - type: Transform + pos: 1.5,-2.5 + parent: 1 + - uid: 139 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -1.5,-1.5 + parent: 1 + - uid: 140 + components: + - type: Transform + pos: 3.5,-0.5 + parent: 1 + - uid: 141 + components: + - type: Transform + pos: -2.5,-0.5 + parent: 1 + - uid: 142 + components: + - type: Transform + pos: -3.5,-2.5 + parent: 1 + - uid: 143 + components: + - type: Transform + pos: 4.5,-2.5 + parent: 1 +- proto: WallPlastitaniumDiagonal + entities: + - uid: 144 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 6.5,1.5 + parent: 1 + - uid: 145 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -5.5,-0.5 + parent: 1 + - uid: 146 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -4.5,-1.5 + parent: 1 + - uid: 147 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 5.5,4.5 + parent: 1 + - uid: 148 + components: + - type: Transform + pos: -3.5,7.5 + parent: 1 + - uid: 149 + components: + - type: Transform + pos: -4.5,4.5 + parent: 1 + - uid: 150 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 4.5,7.5 + parent: 1 + - uid: 151 + components: + - type: Transform + pos: -5.5,1.5 + parent: 1 + - uid: 152 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 6.5,-0.5 + parent: 1 + - uid: 153 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 5.5,-1.5 + parent: 1 + - uid: 154 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 2.5,-2.5 + parent: 1 + - uid: 155 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -1.5,-2.5 + parent: 1 + - uid: 156 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -1.5,-0.5 + parent: 1 + - uid: 157 + components: + - type: Transform + pos: 2.5,-0.5 + parent: 1 + - uid: 158 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -0.5,-1.5 + parent: 1 + - uid: 159 + components: + - type: Transform + pos: 1.5,-1.5 + parent: 1 + - uid: 160 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -3.5,-3.5 + parent: 1 + - uid: 161 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 4.5,-3.5 + parent: 1 +... diff --git a/Resources/Maps/Shuttles/ShuttleEvent/traveling_china_cuisine.yml b/Resources/Maps/Shuttles/ShuttleEvent/traveling_china_cuisine.yml new file mode 100644 index 0000000000..4b3bf617e2 --- /dev/null +++ b/Resources/Maps/Shuttles/ShuttleEvent/traveling_china_cuisine.yml @@ -0,0 +1,1872 @@ +meta: + format: 6 + postmapinit: false +tilemap: + 0: Space + 14: FloorBar + 40: FloorDirt + 44: FloorFreezer + 50: FloorGrassLight + 76: FloorRGlass + 85: FloorShuttleWhite + 96: FloorSteelDirty + 98: FloorSteelLime + 106: FloorTechMaint3 + 112: FloorWhiteMini + 117: FloorWhitePlastic + 119: FloorWoodTile + 120: Lattice + 121: Plating +entities: +- proto: "" + entities: + - uid: 1 + components: + - type: MetaData + name: SRV "Salami-Salami" + - type: Transform + pos: -0.5104167,-0.5 + parent: invalid + - type: MapGrid + chunks: + 0,0: + ind: 0,0 + tiles: YgAAAAAAYgAAAAAAYgAAAAAAVQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + version: 6 + -1,0: + ind: -1,0 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAeQAAAAAAeQAAAAAAVQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + version: 6 + -1,-1: + ind: -1,-1 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAMgAAAAAAMgAAAAAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAAAADgAAAAAADgAAAAAADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAAAATAAAAAAADgAAAAAADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAAAADgAAAAAADgAAAAAADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAdwAAAAAAdwAAAAAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAeQAAAAAAeQAAAAAAdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYgAAAAAAYgAAAAAAYgAAAAAAYgAAAAAA + version: 6 + 0,-1: + ind: 0,-1 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAeAAAAAAAeAAAAAAAeAAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAAAAcAAAAAAAcAAAAAAAeAAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALAAAAAAALAAAAAAALAAAAAAALAAAAAAAeQAAAAAAeQAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALAAAAAAATAAAAAAALAAAAAAAYAAAAAAAYAAAAAAAYAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALAAAAAAATAAAAAAALAAAAAAAcAAAAAAAagAAAAAAKAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALAAAAAAATAAAAAAALAAAAAAAcAAAAAAAYAAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALAAAAAAATAAAAAAALAAAAAAAcAAAAAAAeQAAAAAAeQAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALAAAAAAALAAAAAAALAAAAAAAdQAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYgAAAAAAYgAAAAAAYgAAAAAAdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + version: 6 + - type: Broadphase + - type: Physics + bodyStatus: InAir + angularDamping: 0.05 + linearDamping: 0.05 + fixedRotation: False + bodyType: Dynamic + - type: Fixtures + fixtures: {} + - type: OccluderTree + - type: SpreaderGrid + - type: Shuttle + - type: GridPathfinding + - type: Gravity + gravityShakeSound: !type:SoundPathSpecifier + path: /Audio/Effects/alert.ogg + - type: DecalGrid + chunkCollection: + version: 2 + nodes: + - node: + color: '#FFFFFFFF' + id: FlowersBROne + decals: + 3: -2,-7 + - node: + color: '#FFFFFFFF' + id: Flowerspv2 + decals: + 2: -3,-7 + - node: + color: '#FFFFFFFF' + id: Flowersy3 + decals: + 0: -2,-7 + 1: -3,-7 + - type: GridAtmosphere + version: 2 + data: + tiles: + 0,0: + 0: 7 + 1: 128 + 0,-1: + 0: 29303 + -1,0: + 1: 129 + -1,-1: + 0: 28686 + 1: 272 + -1,-2: + 1: 28 + 0: 65024 + -1,-3: + 1: 49152 + 0,-3: + 1: 61440 + 0,-2: + 0: 32624 + 1: 8 + 1,-3: + 1: 4096 + 1,-2: + 1: 65 + 0: 13056 + 1,-1: + 0: 3 + 1: 320 + uniqueMixes: + - volume: 2500 + temperature: 293.15 + moles: + - 21.824879 + - 82.10312 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - volume: 2500 + immutable: True + moles: + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + chunkSize: 4 + - type: GasTileOverlay + - type: RadiationGridResistance + - type: NavMap +- proto: AirlockShuttle + entities: + - uid: 2 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -3.5,-4.5 + parent: 1 + - uid: 3 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -3.5,-0.5 + parent: 1 +- proto: APCBasic + entities: + - uid: 4 + components: + - type: Transform + pos: 2.5,-1.5 + parent: 1 +- proto: AtmosDeviceFanTiny + entities: + - uid: 5 + components: + - type: Transform + pos: -3.5,-4.5 + parent: 1 + - uid: 6 + components: + - type: Transform + pos: -3.5,-0.5 + parent: 1 +- proto: BarSignMaidCafe + entities: + - uid: 7 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 3.5,-3.5 + parent: 1 +- proto: BlastDoor + entities: + - uid: 8 + components: + - type: Transform + pos: -0.5,-0.5 + parent: 1 + - type: DeviceLinkSink + links: + - 161 +- proto: Bucket + entities: + - uid: 9 + components: + - type: Transform + pos: 0.32937366,-3.3336349 + parent: 1 +- proto: ButchCleaver + entities: + - uid: 11 + components: + - type: Transform + parent: 10 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: CableApcExtension + entities: + - uid: 41 + components: + - type: Transform + pos: 2.5,-3.5 + parent: 1 + - uid: 42 + components: + - type: Transform + pos: 2.5,-1.5 + parent: 1 + - uid: 43 + components: + - type: Transform + pos: 2.5,-0.5 + parent: 1 + - uid: 44 + components: + - type: Transform + pos: 2.5,0.5 + parent: 1 + - uid: 45 + components: + - type: Transform + pos: 1.5,0.5 + parent: 1 + - uid: 46 + components: + - type: Transform + pos: 0.5,0.5 + parent: 1 + - uid: 47 + components: + - type: Transform + pos: -0.5,0.5 + parent: 1 + - uid: 48 + components: + - type: Transform + pos: 2.5,-2.5 + parent: 1 + - uid: 49 + components: + - type: Transform + pos: 2.5,-4.5 + parent: 1 + - uid: 50 + components: + - type: Transform + pos: 2.5,-5.5 + parent: 1 + - uid: 51 + components: + - type: Transform + pos: 2.5,-6.5 + parent: 1 + - uid: 52 + components: + - type: Transform + pos: 1.5,-6.5 + parent: 1 + - uid: 53 + components: + - type: Transform + pos: 0.5,-6.5 + parent: 1 + - uid: 54 + components: + - type: Transform + pos: 1.5,-7.5 + parent: 1 + - uid: 55 + components: + - type: Transform + pos: -0.5,-0.5 + parent: 1 + - uid: 56 + components: + - type: Transform + pos: -0.5,-1.5 + parent: 1 + - uid: 57 + components: + - type: Transform + pos: 3.5,-3.5 + parent: 1 + - uid: 58 + components: + - type: Transform + pos: 4.5,-3.5 + parent: 1 + - uid: 59 + components: + - type: Transform + pos: 5.5,-3.5 + parent: 1 + - uid: 60 + components: + - type: Transform + pos: -0.5,-7.5 + parent: 1 + - uid: 61 + components: + - type: Transform + pos: 3.5,-7.5 + parent: 1 + - uid: 62 + components: + - type: Transform + pos: 0.5,-7.5 + parent: 1 + - uid: 63 + components: + - type: Transform + pos: 2.5,-7.5 + parent: 1 + - uid: 64 + components: + - type: Transform + pos: -1.5,-0.5 + parent: 1 +- proto: CableHV + entities: + - uid: 65 + components: + - type: Transform + pos: 3.5,-0.5 + parent: 1 + - uid: 66 + components: + - type: Transform + pos: 3.5,0.5 + parent: 1 +- proto: CableMV + entities: + - uid: 67 + components: + - type: Transform + pos: 2.5,-1.5 + parent: 1 + - uid: 68 + components: + - type: Transform + pos: 3.5,-1.5 + parent: 1 + - uid: 69 + components: + - type: Transform + pos: 3.5,-0.5 + parent: 1 +- proto: ChairPilotSeat + entities: + - uid: 70 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.5,-0.5 + parent: 1 +- proto: ClosetMaintenance + entities: + - uid: 71 + components: + - type: Transform + pos: 0.5,0.5 + parent: 1 + - type: EntityStorage + air: + volume: 200 + immutable: False + temperature: 293.14786 + moles: + - 1.8856695 + - 7.0937095 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - type: ContainerContainer + containers: + entity_storage: !type:Container + showEnts: False + occludes: True + ents: + - 80 + - 76 + - 73 + - 74 + - 78 + - 81 + - 77 + - 72 + - 75 + - 79 + paper_label: !type:ContainerSlot + showEnts: False + occludes: True + ent: null +- proto: ClothingHeadHatCasa + entities: + - uid: 72 + components: + - type: Transform + parent: 71 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: ClothingHeadHelmetHardsuitBasic + entities: + - uid: 82 + components: + - type: Transform + pos: 0.30235916,-0.6487955 + parent: 1 +- proto: ClothingOuterDameDane + entities: + - uid: 79 + components: + - type: Transform + parent: 71 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: ClothingOuterDogi + entities: + - uid: 73 + components: + - type: Transform + parent: 71 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: ClothingOuterHardsuitAncientEVA + entities: + - uid: 74 + components: + - type: Transform + parent: 71 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: ClothingShoesDameDane + entities: + - uid: 75 + components: + - type: Transform + parent: 71 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: ClothingUniformJumpsuitDameDane + entities: + - uid: 76 + components: + - type: Transform + parent: 71 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: ClothingUniformJumpsuitFamilyGuy + entities: + - uid: 83 + components: + - type: Transform + pos: 4.1954827,-4.679846 + parent: 1 +- proto: ClothingUniformJumpsuitKimono + entities: + - uid: 77 + components: + - type: Transform + parent: 71 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: ComputerShuttle + entities: + - uid: 84 + components: + - type: Transform + pos: 1.5,0.5 + parent: 1 +- proto: CrateFreezer + entities: + - uid: 10 + components: + - type: Transform + pos: 5.5,-5.5 + parent: 1 + - type: EntityStorage + air: + volume: 200 + immutable: False + temperature: 293.14783 + moles: + - 1.7459903 + - 6.568249 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - type: ContainerContainer + containers: + entity_storage: !type:Container + showEnts: False + occludes: True + ents: + - 11 + - 29 + - 24 + - 13 + - 25 + - 37 + - 33 + - 32 + - 38 + - 16 + - 15 + - 19 + - 17 + - 18 + - 20 + - 40 + - 14 + - 22 + - 30 + - 21 + - 34 + - 23 + - 35 + - 28 + - 36 + - 27 + - 39 + - 31 + - 26 + - 12 + paper_label: !type:ContainerSlot + showEnts: False + occludes: True + ent: null +- proto: DrinkCanPack + entities: + - uid: 85 + components: + - type: Transform + pos: 2.4942255,-4.6459746 + parent: 1 +- proto: DrinkGlass + entities: + - uid: 86 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.9675496,-6.258177 + parent: 1 + - uid: 87 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.9050496,-6.383177 + parent: 1 + - uid: 88 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.8425496,-6.086302 + parent: 1 +- proto: EggySeeds + entities: + - uid: 89 + components: + - type: Transform + pos: 4.8663497,-4.2508607 + parent: 1 +- proto: ExtendedEmergencyOxygenTankFilled + entities: + - uid: 78 + components: + - type: Transform + parent: 71 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: FolderSpawner + entities: + - uid: 90 + components: + - type: Transform + pos: 2.6497245,-0.5822141 + parent: 1 +- proto: FoodBowlBig + entities: + - uid: 12 + components: + - type: Transform + parent: 10 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 13 + components: + - type: Transform + parent: 10 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: FoodBreadMoldySlice + entities: + - uid: 91 + components: + - type: Transform + pos: 4.5392327,-5.695471 + parent: 1 +- proto: FoodBreadTofu + entities: + - uid: 92 + components: + - type: Transform + pos: -0.56204444,-5.412466 + parent: 1 +- proto: FoodButter + entities: + - uid: 14 + components: + - type: Transform + parent: 10 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: FoodEgg + entities: + - uid: 15 + components: + - type: Transform + parent: 10 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 16 + components: + - type: Transform + parent: 10 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 17 + components: + - type: Transform + parent: 10 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 18 + components: + - type: Transform + parent: 10 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 19 + components: + - type: Transform + parent: 10 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 20 + components: + - type: Transform + parent: 10 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: FoodMealSashimi + entities: + - uid: 93 + components: + - type: Transform + pos: 2.532601,-3.619526 + parent: 1 + - uid: 94 + components: + - type: Transform + pos: 2.5117679,-3.192443 + parent: 1 +- proto: FoodMeat + entities: + - uid: 21 + components: + - type: Transform + parent: 10 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 22 + components: + - type: Transform + parent: 10 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: FoodMeatBear + entities: + - uid: 23 + components: + - type: Transform + parent: 10 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 24 + components: + - type: Transform + parent: 10 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: FoodMeatBearCooked + entities: + - uid: 25 + components: + - type: Transform + parent: 10 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: FoodMeatClown + entities: + - uid: 26 + components: + - type: Transform + parent: 10 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: FoodMeatCorgi + entities: + - uid: 27 + components: + - type: Transform + parent: 10 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: FoodMeatDragon + entities: + - uid: 96 + components: + - type: Transform + parent: 95 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 97 + components: + - type: Transform + parent: 95 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 98 + components: + - type: Transform + parent: 95 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: FoodMeatFish + entities: + - uid: 112 + components: + - type: Transform + pos: 2.3519397,-4.0480876 + parent: 1 + - uid: 113 + components: + - type: Transform + pos: 2.5602732,-4.4543376 + parent: 1 +- proto: FoodMeatGoliath + entities: + - uid: 28 + components: + - type: Transform + parent: 10 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 29 + components: + - type: Transform + parent: 10 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: FoodMeatHuman + entities: + - uid: 30 + components: + - type: Transform + parent: 10 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: FoodMeatLizard + entities: + - uid: 31 + components: + - type: Transform + parent: 10 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: FoodMeatPlant + entities: + - uid: 32 + components: + - type: Transform + parent: 10 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: FoodMeatRat + entities: + - uid: 33 + components: + - type: Transform + parent: 10 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: FoodMeatRouny + entities: + - uid: 34 + components: + - type: Transform + parent: 10 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 35 + components: + - type: Transform + parent: 10 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: FoodMeatSnake + entities: + - uid: 36 + components: + - type: Transform + parent: 10 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 37 + components: + - type: Transform + parent: 10 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: FoodMeatSpider + entities: + - uid: 38 + components: + - type: Transform + parent: 10 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: FoodNoodlesButter + entities: + - uid: 99 + components: + - type: Transform + parent: 95 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 100 + components: + - type: Transform + parent: 95 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: FoodNoodlesChowmein + entities: + - uid: 101 + components: + - type: Transform + parent: 95 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: FoodNoodlesMeatball + entities: + - uid: 102 + components: + - type: Transform + parent: 95 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: FoodRiceBoiled + entities: + - uid: 103 + components: + - type: Transform + parent: 95 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 104 + components: + - type: Transform + parent: 95 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 105 + components: + - type: Transform + parent: 95 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 114 + components: + - type: Transform + pos: 4.2319775,-4.0944357 + parent: 1 +- proto: FoodRiceEgg + entities: + - uid: 106 + components: + - type: Transform + parent: 95 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 107 + components: + - type: Transform + parent: 95 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: FoodRiceGumbo + entities: + - uid: 108 + components: + - type: Transform + parent: 95 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: FoodRicePork + entities: + - uid: 109 + components: + - type: Transform + parent: 95 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 110 + components: + - type: Transform + parent: 95 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: FoodRicePudding + entities: + - uid: 111 + components: + - type: Transform + parent: 95 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: FoodTofu + entities: + - uid: 115 + components: + - type: Transform + pos: -2.4682944,-0.4437158 + parent: 1 +- proto: GeneratorWallmountAPU + entities: + - uid: 116 + components: + - type: Transform + pos: 3.5,0.5 + parent: 1 + - type: PowerSupplier + supplyRampRate: 1000 + supplyRampTolerance: 1000 + supplyRate: 16000 +- proto: GravityGeneratorMini + entities: + - uid: 117 + components: + - type: Transform + pos: 5.5,-3.5 + parent: 1 +- proto: Grille + entities: + - uid: 118 + components: + - type: Transform + pos: 1.5,1.5 + parent: 1 + - uid: 119 + components: + - type: Transform + pos: 0.5,1.5 + parent: 1 + - uid: 120 + components: + - type: Transform + pos: 2.5,1.5 + parent: 1 + - uid: 121 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -2.5,0.5 + parent: 1 + - uid: 122 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -1.5,0.5 + parent: 1 +- proto: Gyroscope + entities: + - uid: 123 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 4.5,-3.5 + parent: 1 +- proto: HospitalCurtains + entities: + - uid: 124 + components: + - type: Transform + pos: 3.5,-5.5 + parent: 1 +- proto: HospitalCurtainsOpen + entities: + - uid: 125 + components: + - type: Transform + pos: 1.5,-1.5 + parent: 1 +- proto: hydroponicsSoil + entities: + - uid: 126 + components: + - type: Transform + pos: 5.5,-4.5 + parent: 1 +- proto: HydroponicsToolClippers + entities: + - uid: 127 + components: + - type: Transform + pos: 4.6706424,-4.7269287 + parent: 1 +- proto: HydroponicsToolMiniHoe + entities: + - uid: 128 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 5.0306153,-4.877862 + parent: 1 +- proto: KitchenElectricGrill + entities: + - uid: 129 + components: + - type: Transform + pos: 0.5,-6.5 + parent: 1 +- proto: KitchenKnife + entities: + - uid: 80 + components: + - type: Transform + parent: 71 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: KitchenMicrowave + entities: + - uid: 130 + components: + - type: Transform + pos: 2.5,-2.5 + parent: 1 +- proto: KitchenReagentGrinder + entities: + - uid: 131 + components: + - type: Transform + pos: 1.5,-6.5 + parent: 1 +- proto: LockerFreezerBase + entities: + - uid: 95 + components: + - type: Transform + pos: 0.5,-2.5 + parent: 1 + - type: EntityStorage + air: + volume: 200 + immutable: False + temperature: 293.14798 + moles: + - 1.7459903 + - 6.568249 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - type: ContainerContainer + containers: + entity_storage: !type:Container + showEnts: False + occludes: True + ents: + - 110 + - 109 + - 103 + - 104 + - 105 + - 96 + - 98 + - 97 + - 99 + - 101 + - 100 + - 106 + - 102 + - 108 + - 107 + - 111 + paper_label: !type:ContainerSlot + showEnts: False + occludes: True + ent: null +- proto: MaterialSheetMeat + entities: + - uid: 39 + components: + - type: Transform + parent: 10 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: Mattress + entities: + - uid: 132 + components: + - type: Transform + pos: 4.5,-4.5 + parent: 1 +- proto: NitrogenTankFilled + entities: + - uid: 81 + components: + - type: Transform + parent: 71 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: NoticeBoard + entities: + - uid: 133 + components: + - type: Transform + pos: -1.5,-2.5 + parent: 1 +- proto: Pen + entities: + - uid: 232 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 2.27949,-0.47790605 + parent: 1 +- proto: PlushieVox + entities: + - uid: 134 + components: + - type: Transform + pos: -0.5571752,-4.2839594 + parent: 1 +- proto: PosterLegitFruitBowl + entities: + - uid: 135 + components: + - type: Transform + pos: -2.5,-2.5 + parent: 1 +- proto: PottedPlantRandom + entities: + - uid: 136 + components: + - type: Transform + pos: -2.5,-3.5 + parent: 1 + - uid: 137 + components: + - type: Transform + pos: -2.5,-5.5 + parent: 1 +- proto: Poweredlight + entities: + - uid: 138 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -2.5,-5.5 + parent: 1 + - uid: 139 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 2.5,-4.5 + parent: 1 + - uid: 140 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 2.5,-0.5 + parent: 1 +- proto: PoweredSmallLight + entities: + - uid: 141 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -1.5,-0.5 + parent: 1 + - uid: 142 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 5.5,-4.5 + parent: 1 + - uid: 143 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.5,-6.5 + parent: 1 +- proto: RagItem + entities: + - uid: 144 + components: + - type: Transform + pos: -0.47175223,-3.4022202 + parent: 1 +- proto: RandomSpawner100 + entities: + - uid: 145 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 0.5,-4.5 + parent: 1 +- proto: ReagentContainerFlour + entities: + - uid: 40 + components: + - type: Transform + parent: 10 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: RiceSeeds + entities: + - uid: 146 + components: + - type: Transform + pos: 4.716352,-4.1569357 + parent: 1 +- proto: RubberStampTrader + entities: + - uid: 147 + components: + - type: Transform + pos: 2.6184745,-0.09783912 + parent: 1 +- proto: Shovel + entities: + - uid: 148 + components: + - type: Transform + pos: 5.0644813,-4.460167 + parent: 1 +- proto: ShuttersWindowOpen + entities: + - uid: 149 + components: + - type: Transform + pos: -0.5,-4.5 + parent: 1 + - type: DeviceLinkSink + links: + - 160 + - uid: 150 + components: + - type: Transform + pos: -0.5,-3.5 + parent: 1 + - type: DeviceLinkSink + links: + - 160 + - uid: 151 + components: + - type: Transform + pos: -0.5,-5.5 + parent: 1 + - type: DeviceLinkSink + links: + - 160 + - uid: 152 + components: + - type: Transform + pos: -3.5,-4.5 + parent: 1 + - type: DeviceLinkSink + links: + - 160 +- proto: ShuttleWindow + entities: + - uid: 153 + components: + - type: Transform + pos: 1.5,1.5 + parent: 1 + - uid: 154 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 2.5,1.5 + parent: 1 + - uid: 155 + components: + - type: Transform + pos: -2.5,-6.5 + parent: 1 + - uid: 156 + components: + - type: Transform + pos: -1.5,-6.5 + parent: 1 + - uid: 157 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -2.5,0.5 + parent: 1 + - uid: 158 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -1.5,0.5 + parent: 1 + - uid: 159 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 0.5,1.5 + parent: 1 +- proto: SignalButton + entities: + - uid: 160 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 0.5,-1.5 + parent: 1 + - type: DeviceLinkSource + linkedPorts: + 150: + - Pressed: Toggle + 149: + - Pressed: Toggle + 151: + - Pressed: Toggle + 152: + - Pressed: Toggle + - uid: 161 + components: + - type: Transform + pos: 1.5,0.5 + parent: 1 + - type: DeviceLinkSource + linkedPorts: + 8: + - Pressed: Toggle +- proto: SignDoors + entities: + - uid: 162 + components: + - type: Transform + pos: -0.5,0.5 + parent: 1 +- proto: SinkStemlessWater + entities: + - uid: 163 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 0.5,-6.5 + parent: 1 +- proto: soda_dispenser + entities: + - uid: 164 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 2.5,-6.5 + parent: 1 +- proto: StoolBar + entities: + - uid: 165 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -1.5,-3.5 + parent: 1 + - uid: 166 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -1.5,-4.5 + parent: 1 + - uid: 167 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -1.5,-5.5 + parent: 1 +- proto: SubstationWallBasic + entities: + - uid: 168 + components: + - type: Transform + pos: 3.5,-0.5 + parent: 1 +- proto: Table + entities: + - uid: 169 + components: + - type: Transform + pos: 2.5,-6.5 + parent: 1 + - uid: 170 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.5,-6.5 + parent: 1 + - uid: 171 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 0.5,-6.5 + parent: 1 +- proto: TableGlass + entities: + - uid: 172 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 2.5,-4.5 + parent: 1 + - uid: 173 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 2.5,-3.5 + parent: 1 + - uid: 174 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 2.5,-2.5 + parent: 1 +- proto: TableStone + entities: + - uid: 175 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -0.5,-4.5 + parent: 1 + - uid: 176 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -0.5,-3.5 + parent: 1 + - uid: 177 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -0.5,-5.5 + parent: 1 +- proto: TableWood + entities: + - uid: 178 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 2.5,-0.5 + parent: 1 +- proto: Thruster + entities: + - uid: 179 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.5,-8.5 + parent: 1 + - uid: 180 + components: + - type: Transform + pos: 4.5,-1.5 + parent: 1 + - uid: 181 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 0.5,-8.5 + parent: 1 + - uid: 182 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 4.5,-7.5 + parent: 1 + - uid: 183 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 2.5,-8.5 + parent: 1 + - uid: 184 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -1.5,-7.5 + parent: 1 +- proto: ToolboxMechanicalFilled + entities: + - uid: 185 + components: + - type: Transform + pos: 0.5731925,-0.16962886 + parent: 1 +- proto: TravelingChefSpawner + entities: + - uid: 186 + components: + - type: Transform + pos: 1.5,-0.5 + parent: 1 + - uid: 187 + components: + - type: Transform + pos: 4.5,-5.5 + parent: 1 +- proto: VendingMachineChang + entities: + - uid: 188 + components: + - type: Transform + pos: 2.5,0.5 + parent: 1 +- proto: WallShuttle + entities: + - uid: 189 + components: + - type: Transform + pos: -1.5,-1.5 + parent: 1 + - uid: 190 + components: + - type: Transform + pos: -0.5,-7.5 + parent: 1 + - uid: 191 + components: + - type: Transform + pos: 3.5,-0.5 + parent: 1 + - uid: 192 + components: + - type: Transform + pos: 3.5,0.5 + parent: 1 + - uid: 193 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -0.5,-1.5 + parent: 1 + - uid: 194 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 3.5,-1.5 + parent: 1 + - uid: 195 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 3.5,-2.5 + parent: 1 + - uid: 196 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 3.5,-3.5 + parent: 1 + - uid: 197 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 3.5,-4.5 + parent: 1 + - uid: 198 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 2.5,-7.5 + parent: 1 + - uid: 199 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 1.5,-7.5 + parent: 1 + - uid: 200 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 0.5,-7.5 + parent: 1 + - uid: 201 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -0.5,-6.5 + parent: 1 + - uid: 202 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -0.5,-2.5 + parent: 1 + - uid: 203 + components: + - type: Transform + pos: 2.5,-1.5 + parent: 1 + - uid: 204 + components: + - type: Transform + pos: 0.5,-1.5 + parent: 1 + - uid: 205 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -1.5,-2.5 + parent: 1 + - uid: 206 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -2.5,-2.5 + parent: 1 + - uid: 207 + components: + - type: Transform + pos: -3.5,-3.5 + parent: 1 + - uid: 208 + components: + - type: Transform + pos: -3.5,-5.5 + parent: 1 + - uid: 209 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 5.5,-2.5 + parent: 1 + - uid: 210 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -0.5,0.5 + parent: 1 + - uid: 211 + components: + - type: Transform + pos: 4.5,-2.5 + parent: 1 + - uid: 212 + components: + - type: Transform + pos: 4.5,-6.5 + parent: 1 + - uid: 213 + components: + - type: Transform + pos: 5.5,-6.5 + parent: 1 + - uid: 214 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 6.5,-3.5 + parent: 1 + - uid: 215 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 6.5,-4.5 + parent: 1 + - uid: 216 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 6.5,-5.5 + parent: 1 + - uid: 217 + components: + - type: Transform + pos: 3.5,-6.5 + parent: 1 + - uid: 218 + components: + - type: Transform + pos: 3.5,-7.5 + parent: 1 + - uid: 219 + components: + - type: Transform + pos: -2.5,-1.5 + parent: 1 +- proto: WallShuttleDiagonal + entities: + - uid: 220 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -0.5,-8.5 + parent: 1 + - uid: 221 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 3.5,1.5 + parent: 1 + - uid: 222 + components: + - type: Transform + pos: -0.5,1.5 + parent: 1 + - uid: 223 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -3.5,-6.5 + parent: 1 + - uid: 224 + components: + - type: Transform + pos: -3.5,-2.5 + parent: 1 + - uid: 225 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 3.5,-8.5 + parent: 1 + - uid: 226 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 6.5,-2.5 + parent: 1 + - uid: 227 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 6.5,-6.5 + parent: 1 + - uid: 228 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -3.5,-1.5 + parent: 1 + - uid: 229 + components: + - type: Transform + pos: -3.5,0.5 + parent: 1 + - uid: 230 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -1.5,-8.5 + parent: 1 + - uid: 231 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 4.5,-8.5 + parent: 1 +... diff --git a/Resources/Maps/Shuttles/emergency_neol.yml b/Resources/Maps/Shuttles/emergency_neol.yml new file mode 100644 index 0000000000..e5c359a4f5 --- /dev/null +++ b/Resources/Maps/Shuttles/emergency_neol.yml @@ -0,0 +1,6758 @@ +meta: + format: 6 + postmapinit: false +tilemap: + 0: Space + 31: FloorDarkMini + 34: FloorDarkPavement + 48: FloorGrassLight + 72: FloorReinforced + 84: FloorSteel + 89: FloorSteelDirty + 91: FloorSteelMini + 96: FloorTechMaint + 97: FloorTechMaint2 + 104: FloorWhiteMini + 110: FloorWood + 112: Lattice + 113: Plating +entities: +- proto: "" + entities: + - uid: 1 + components: + - name: NT-LMEST-T14 "Neol" + type: MetaData + - pos: -0.5124855,0.9164498 + parent: invalid + type: Transform + - chunks: + 0,0: + ind: 0,0 + tiles: HwAAAAAAHwAAAAAAWwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAWwAAAAAAWwAAAAAAWwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAWwAAAAAAHwAAAAAAcQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbgAAAAAAcQAAAAAAbgAAAAAAcQAAAAAAcQAAAAAAHwAAAAAAWwAAAAAAWwAAAAAAWwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbgAAAAAAbgAAAAAAbgAAAAAAcQAAAAAAHwAAAAAAWwAAAAAAHwAAAAAAcQAAAAAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbgAAAAAAcQAAAAAAcQAAAAAAcQAAAAAAHwAAAAAAWwAAAAAAHwAAAAAAcQAAAAAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbgAAAAAAbgAAAAAAbgAAAAAAcQAAAAAAHwAAAAAAWwAAAAAAHwAAAAAAcQAAAAAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcQAAAAAAcQAAAAAAcQAAAAAAcQAAAAAAHwAAAAAAWwAAAAAAHwAAAAAAcQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbgAAAAAAbgAAAAAAbgAAAAAAcQAAAAAAIgAAAAAAcQAAAAAAcQAAAAAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbgAAAAAAbgAAAAAAIgAAAAAAIgAAAAAAIgAAAAAAIgAAAAAAcQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgAAAAAAIgAAAAAAIgAAAAAAIgAAAAAAIgAAAAAAIgAAAAAAcQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcQAAAAAAcQAAAAAAcQAAAAAAcQAAAAAAIgAAAAAAcQAAAAAAcQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgAAAAAAIgAAAAAAIgAAAAAAIgAAAAAAIgAAAAAAcQAAAAAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgAAAAAAIgAAAAAAIgAAAAAAIgAAAAAAIgAAAAAAcQAAAAAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgAAAAAAIgAAAAAAIgAAAAAAIgAAAAAAcQAAAAAAcQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcQAAAAAAcQAAAAAAcQAAAAAAcQAAAAAAcQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + version: 6 + -1,0: + ind: -1,0 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWwAAAAAAWwAAAAAAWwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAWwAAAAAAHwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcQAAAAAAHwAAAAAAWwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWwAAAAAAWwAAAAAAWwAAAAAAHwAAAAAAcQAAAAAAcQAAAAAAbgAAAAAAbgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAAAAcQAAAAAAHwAAAAAAWwAAAAAAHwAAAAAAcQAAAAAAbgAAAAAAbgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAAAAcQAAAAAAHwAAAAAAWwAAAAAAHwAAAAAAcQAAAAAAbgAAAAAAbgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAAAAcQAAAAAAHwAAAAAAWwAAAAAAHwAAAAAAcQAAAAAAbgAAAAAAbgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcQAAAAAAHwAAAAAAWwAAAAAAHwAAAAAAcQAAAAAAcQAAAAAAcQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAAAAcQAAAAAAcQAAAAAAIgAAAAAAcQAAAAAAbgAAAAAAbgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcQAAAAAAIgAAAAAAIgAAAAAAIgAAAAAAIgAAAAAAbgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcQAAAAAAIgAAAAAAIgAAAAAAIgAAAAAAIgAAAAAAIgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcQAAAAAAcQAAAAAAIgAAAAAAcQAAAAAAcQAAAAAAcQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAAAAcQAAAAAAIgAAAAAAIgAAAAAAIgAAAAAAIgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAAAAcQAAAAAAIgAAAAAAIgAAAAAAIgAAAAAAIgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcQAAAAAAcQAAAAAAIgAAAAAAIgAAAAAAIgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcQAAAAAAcQAAAAAAcQAAAAAAcQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + version: 6 + 0,-1: + ind: 0,-1 + tiles: cQAAAAAAcQAAAAAAcQAAAAAAcQAAAAAAcQAAAAAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYQAAAAAAYQAAAAAAYQAAAAAASAAAAAAAcQAAAAAAcQAAAAAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYQAAAAAAYQAAAAAAYQAAAAAASAAAAAAASAAAAAAAcQAAAAAAcQAAAAAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYQAAAAAAYQAAAAAAcQAAAAAAcQAAAAAAcQAAAAAAcQAAAAAAcQAAAAAAcQAAAAAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcQAAAAAAYQAAAAAAcQAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAcQAAAAAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHwAAAAAAWwAAAAAAcQAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAcQAAAAAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWwAAAAAAWwAAAAAAcQAAAAAAaAAAAAAAaAAAAAAAcQAAAAAAcQAAAAAAcQAAAAAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWwAAAAAAHwAAAAAAcQAAAAAAaAAAAAAAaAAAAAAAcQAAAAAAHwAAAAAAHwAAAAAAcQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWwAAAAAAWwAAAAAAaAAAAAAAaAAAAAAAaAAAAAAAcQAAAAAAWwAAAAAAWwAAAAAAWwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWwAAAAAAHwAAAAAAcQAAAAAAcQAAAAAAcQAAAAAAcQAAAAAAWwAAAAAAVAAAAAAAcQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAWwAAAAAAWwAAAAAAWwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHwAAAAAAHwAAAAAAWwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAWwAAAAAAHwAAAAAAcQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAMAAAAAAAWwAAAAAAHwAAAAAAMAAAAAAAHwAAAAAAWwAAAAAAHwAAAAAAcQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAMAAAAAAAWwAAAAAAHwAAAAAAMAAAAAAAHwAAAAAAWwAAAAAAHwAAAAAAcQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAMAAAAAAAWwAAAAAAHwAAAAAAMAAAAAAAHwAAAAAAWwAAAAAAHwAAAAAAcQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAMAAAAAAAWwAAAAAAHwAAAAAAMAAAAAAAHwAAAAAAWwAAAAAAHwAAAAAAcQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + version: 6 + -1,-1: + ind: -1,-1 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAAAAcQAAAAAAcQAAAAAAcQAAAAAAcQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAAAAcQAAAAAAcQAAAAAAYAAAAAAAYAAAAAAAYQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAAAAcQAAAAAAcQAAAAAAcQAAAAAAcQAAAAAAcQAAAAAAYQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAAAAcQAAAAAAcQAAAAAAVAAAAAAAcQAAAAAAYAAAAAAAYAAAAAAAYQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAAAAcQAAAAAAWQAAAAAAWQAAAAAAcQAAAAAAcQAAAAAAcQAAAAAAYQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAAAAcQAAAAAAWQAAAAAAVAAAAAAAHwAAAAAAHwAAAAAAcQAAAAAAWwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAAAAcQAAAAAAcQAAAAAAcQAAAAAAcQAAAAAAHwAAAAAAcQAAAAAAWwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcQAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAcQAAAAAAHwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAWwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcQAAAAAAcQAAAAAAcQAAAAAAcQAAAAAAcQAAAAAAcQAAAAAAcQAAAAAAHwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWwAAAAAAWwAAAAAAWwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcQAAAAAAHwAAAAAAWwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAWwAAAAAAHwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcQAAAAAAHwAAAAAAWwAAAAAAHwAAAAAAMAAAAAAAHwAAAAAAWwAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcQAAAAAAHwAAAAAAWwAAAAAAHwAAAAAAMAAAAAAAHwAAAAAAWwAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcQAAAAAAHwAAAAAAWwAAAAAAHwAAAAAAMAAAAAAAHwAAAAAAWwAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcQAAAAAAHwAAAAAAWwAAAAAAHwAAAAAAMAAAAAAAHwAAAAAAWwAAAAAAMAAAAAAA + version: 6 + -1,-2: + ind: -1,-2 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAAAAcAAAAAAAcAAAAAAAAAAAAAAA + version: 6 + 0,-2: + ind: 0,-2 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAAAAcAAAAAAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + version: 6 + type: MapGrid + - type: Broadphase + - bodyStatus: InAir + angularDamping: 0.05 + linearDamping: 0.05 + fixedRotation: False + bodyType: Dynamic + type: Physics + - fixtures: {} + type: Fixtures + - type: OccluderTree + - type: SpreaderGrid + - type: Shuttle + - type: GridPathfinding + - gravityShakeSound: !type:SoundPathSpecifier + path: /Audio/Effects/alert.ogg + type: Gravity + - chunkCollection: + version: 2 + nodes: + - node: + angle: -1.5707963267948966 rad + color: '#FFFFFFFF' + id: Arrows + decals: + 180: -7,2 + 181: -7,0 + 182: -7,-6 + 183: -7,-8 + - node: + angle: 1.5707963267948966 rad + color: '#FFFFFFFF' + id: Arrows + decals: + 174: 7,-6 + 176: 7,-8 + 177: 7,0 + 178: 7,2 + - node: + angle: 1.5707963267948966 rad + color: '#FF0000FF' + id: ArrowsGreyscale + decals: + 185: 7,-6 + 186: 7,-8 + 187: 7,0 + 188: 7,2 + - node: + angle: 4.71238898038469 rad + color: '#FF0000FF' + id: ArrowsGreyscale + decals: + 184: -7,-6 + 189: -7,0 + 190: -7,2 + 191: -7,-8 + - node: + color: '#FFFFFFFF' + id: ArrowsGreyscale + decals: + 179: -4,1 + 202: 4,1 + 203: 4,-5 + 204: -4,-5 + - node: + angle: 3.141592653589793 rad + color: '#FFFFFFFF' + id: ArrowsGreyscale + decals: + 200: -4,0 + 201: 4,0 + 205: -4,-6 + 206: 4,-6 + - node: + color: '#FFFFFFFF' + id: Basalt4 + decals: + 23: -1,-1 + - node: + color: '#FFFFFFFF' + id: Basalt5 + decals: + 24: 4,-3 + - node: + color: '#FFFFFFFF' + id: Basalt7 + decals: + 20: -4,-4 + - node: + color: '#FFFFFFFF' + id: Basalt8 + decals: + 21: -4,-1 + 22: -1,-3 + - node: + color: '#FFFFFFFF' + id: BotLeft + decals: + 134: -7,-1 + 135: -7,-2 + 136: -7,-3 + 137: -7,-4 + - node: + color: '#FFFFFFFF' + id: BotLeftGreyscale + decals: + 142: -7,-4 + 143: -7,-4 + 144: -7,-4 + 145: -7,-4 + 146: -7,-3 + 147: -7,-3 + 148: -7,-3 + 149: -7,-2 + 150: -7,-2 + 151: -7,-2 + 152: -7,-1 + 153: -7,-1 + 154: -7,-1 + 159: -3,-1 + 160: -3,-2 + 161: -3,-3 + 162: -3,-4 + 167: 5,-1 + 168: 5,-2 + 169: 5,-3 + 170: 5,-4 + - node: + color: '#FFFFFFFF' + id: BotRight + decals: + 138: 7,-1 + 139: 7,-2 + 140: 7,-3 + 141: 7,-4 + - node: + color: '#FFFFFFFF' + id: BotRightGreyscale + decals: + 155: -5,-4 + 156: -5,-3 + 157: -5,-2 + 158: -5,-1 + 163: 3,-4 + 164: 3,-3 + 165: 3,-2 + 166: 3,-1 + 171: 7,-4 + 172: 7,-3 + 173: 7,-2 + 175: 7,-1 + - node: + color: '#334E6DFF' + id: BoxGreyscale + decals: + 313: 4,-9 + - node: + color: '#334E6DC8' + id: BrickTileWhiteLineE + decals: + 240: 1,11 + 241: 1,12 + 242: 1,13 + 249: 0,11 + 250: 0,12 + 251: 0,13 + - node: + color: '#334E6DC8' + id: BrickTileWhiteLineW + decals: + 243: -1,11 + 244: -1,12 + 245: -1,13 + 246: 0,11 + 247: 0,12 + 248: 0,13 + - node: + color: '#FFFFFFFF' + id: Bushf1 + decals: + 0: -1,-4 + 1: 1,-3 + 2: -1,-3 + 3: 0,-1 + 4: 4,-1 + - node: + color: '#FFFFFFFF' + id: Bushf2 + decals: + 5: 1,-4 + 6: -1,-2 + 7: 0,-1 + 8: -4,-4 + 9: -4,-3 + 10: -4,-1 + - node: + color: '#FFFFFFFF' + id: Bushf3 + decals: + 11: -4,-2 + 12: -4,-3 + 13: 0,-4 + 14: -1,-3 + 15: 1,-3 + 16: 0,-1 + 17: 4,-2 + 18: 4,-4 + 19: 4,-3 + - node: + color: '#EFB3413E' + id: CheckerNWSE + decals: + 291: -1,-12 + 292: -1,-13 + 293: -1,-14 + 294: -1,-15 + 295: 0,-15 + 296: 0,-14 + 297: 0,-13 + 298: 1,-13 + 299: 1,-14 + 300: 1,-15 + 301: 2,-15 + 302: 2,-14 + 303: 1,-12 + - node: + color: '#FFFFFFFF' + id: Delivery + decals: + 207: -1,-7 + 208: 0,-7 + 209: 1,-7 + - node: + color: '#FFFFFFFF' + id: Dirt + decals: + 258: -6,-11 + 259: -5,-12 + 260: -5,-13 + - node: + color: '#FFFFFFFF' + id: DirtHeavy + decals: + 252: -5,-12 + 253: -6,-12 + 254: -6,-12 + 255: -6,-11 + 256: -5,-13 + 257: -5,-11 + - node: + color: '#FFFFFFFF' + id: Flowersy1 + decals: + 25: 1,-4 + - node: + color: '#FFFFFFFF' + id: Flowersy2 + decals: + 26: -1,-3 + 29: -4,-3 + - node: + color: '#FFFFFFFF' + id: Flowersy3 + decals: + 30: -1,-4 + 31: 4,-4 + - node: + color: '#FFFFFFFF' + id: Flowersy4 + decals: + 27: 1,-1 + 28: 4,-2 + - node: + color: '#DE3A3A96' + id: MiniTileCheckerAOverlay + decals: + 261: -3,-11 + 262: -3,-10 + 263: -3,-9 + 264: -3,-8 + 265: -2,-8 + 266: -4,-9 + 267: -4,-8 + 268: -5,-8 + 269: -5,-9 + 270: -6,-9 + 271: -6,-8 + 272: -7,-8 + 273: -7,-9 + 274: -8,-8 + 275: -4,-11 + - node: + color: '#52B4E996' + id: MiniTileCheckerBOverlay + decals: + 276: 2,-8 + 277: 3,-8 + 278: 4,-8 + 279: 4,-9 + 280: 3,-9 + 281: 3,-10 + 282: 4,-10 + 283: 4,-11 + 284: 3,-11 + 285: 3,-12 + 286: 4,-12 + - node: + color: '#9FED5896' + id: MiniTileCheckerBOverlay + decals: + 287: 6,-11 + 288: 5,-11 + 289: 5,-12 + 290: 6,-12 + - node: + color: '#919191FF' + id: MiniTileWhiteCornerNe + decals: + 41: -7,-1 + 64: -1,-6 + 82: 5,-6 + 89: 5,0 + 90: 5,2 + 97: -3,0 + 100: 1,0 + - node: + color: '#919191FF' + id: MiniTileWhiteCornerNw + decals: + 55: -5,2 + 57: -5,0 + 62: -5,-6 + 65: 1,-6 + 98: 3,0 + 99: -1,0 + 121: 7,-1 + - node: + color: '#919191FF' + id: MiniTileWhiteCornerSe + decals: + 45: -7,-5 + 47: -6,3 + 83: 5,-5 + 88: 5,1 + 109: -3,-5 + 110: 1,-5 + 112: -1,-7 + - node: + color: '#919191FF' + id: MiniTileWhiteCornerSw + decals: + 56: -5,1 + 63: -5,-5 + 107: 3,-5 + 108: -1,-5 + 113: 1,-7 + 122: 7,-5 + 126: 6,3 + - node: + color: '#919191FF' + id: MiniTileWhiteEndE + decals: + 46: -7,1 + 114: -1,-9 + - node: + color: '#919191FF' + id: MiniTileWhiteEndN + decals: + 116: 0,-11 + - node: + color: '#919191FF' + id: MiniTileWhiteEndW + decals: + 86: 7,-7 + 87: 7,1 + 115: 1,-9 + - node: + color: '#919191FF' + id: MiniTileWhiteLineE + decals: + 42: -7,-2 + 43: -7,-3 + 44: -7,-4 + 48: -6,4 + 49: -6,5 + 50: -6,6 + 66: -3,-1 + 67: -3,-2 + 68: -3,-3 + 69: -3,-4 + 117: 5,-4 + 118: 5,-3 + 119: 5,-2 + 120: 5,-1 + 130: 4,6 + 131: 4,5 + 132: 4,4 + 133: 4,3 + - node: + color: '#919191FF' + id: MiniTileWhiteLineN + decals: + 74: -4,-6 + 75: -3,-6 + 76: -2,-6 + 79: 4,-6 + 80: 3,-6 + 81: 2,-6 + 84: 7,-9 + 85: 6,-9 + 91: 4,0 + 92: -4,0 + 101: 0,0 + - node: + color: '#919191FF' + id: MiniTileWhiteLineS + decals: + 77: -4,-5 + 78: 4,-5 + 93: -4,1 + 94: -3,1 + 95: 4,1 + 96: 3,1 + 102: -2,1 + 103: -1,1 + 104: 0,1 + 105: 1,1 + 106: 2,1 + 111: 0,-5 + - node: + color: '#919191FF' + id: MiniTileWhiteLineW + decals: + 51: -4,6 + 52: -4,5 + 53: -4,4 + 54: -4,3 + 58: -5,-1 + 59: -5,-2 + 60: -5,-3 + 61: -5,-4 + 70: 3,-1 + 71: 3,-2 + 72: 3,-3 + 73: 3,-4 + 123: 7,-4 + 124: 7,-3 + 125: 7,-2 + 127: 6,4 + 128: 6,5 + 129: 6,6 + - node: + color: '#334E6DC8' + id: PavementCheckerBOverlay + decals: + 210: -4,8 + 211: -5,8 + 212: -5,9 + 213: -4,9 + 214: 5,8 + 215: 4,8 + 216: 4,9 + 217: 5,9 + 218: -3,8 + 219: -3,9 + 220: 3,8 + 221: 3,9 + - node: + color: '#334E6DC8' + id: PavementOverlay + decals: + 222: -4,10 + 223: -4,11 + 224: -4,12 + 225: -3,12 + 226: -3,11 + 227: -2,11 + 228: -2,12 + 229: -2,13 + 230: -3,13 + 231: 2,11 + 232: 2,12 + 233: 2,13 + 234: 3,13 + 235: 3,12 + 236: 3,11 + 237: 4,11 + 238: 4,12 + 239: 4,10 + - node: + color: '#D4D4D4FF' + id: WarnLineE + decals: + 311: 2,-14 + 312: 2,-15 + - node: + color: '#52B4E9FF' + id: WarnLineGreyscaleE + decals: + 305: 1,-8 + - node: + color: '#334E6DFF' + id: WarnLineGreyscaleN + decals: + 308: 4,6 + 309: -4,6 + - node: + color: '#D4D4D4DE' + id: WarnLineGreyscaleN + decals: + 310: 2,1 + - node: + color: '#EFB341FF' + id: WarnLineGreyscaleS + decals: + 306: -1,-11 + 307: 1,-11 + - node: + color: '#DE3A3AFF' + id: WarnLineGreyscaleW + decals: + 304: -1,-8 + - node: + color: '#FFFFFFFF' + id: WarnLineN + decals: + 196: -4,-6 + 197: 4,-6 + 198: 4,0 + 199: -4,0 + - node: + color: '#FFFFFFFF' + id: WarnLineW + decals: + 192: -4,-5 + 193: 4,-5 + 194: 4,1 + 195: -4,1 + - node: + color: '#FFFFFFFF' + id: WoodTrimThinCornerSe + decals: + 33: -2,8 + - node: + color: '#FFFFFFFF' + id: WoodTrimThinCornerSw + decals: + 32: 2,8 + - node: + color: '#FFFFFFFF' + id: WoodTrimThinInnerSe + decals: + 35: -2,9 + 40: -3,8 + - node: + color: '#FFFFFFFF' + id: WoodTrimThinInnerSw + decals: + 34: 2,9 + 39: 3,8 + - node: + color: '#FFFFFFFF' + id: WoodTrimThinLineS + decals: + 36: -1,9 + 37: 0,9 + 38: 1,9 + type: DecalGrid + - version: 2 + data: + tiles: + 0,0: + 0: 65535 + -2,0: + 0: 65535 + -1,0: + 0: 65535 + 0,1: + 0: 65535 + 0,2: + 0: 65535 + 0,3: + 0: 4095 + 1,0: + 0: 65535 + 1,1: + 0: 65535 + 1,2: + 0: 30583 + 1,3: + 0: 311 + 2,0: + 0: 4369 + 2,1: + 0: 17 + -2,1: + 0: 61183 + -2,2: + 0: 52428 + -2,3: + 0: 140 + -1,1: + 0: 65535 + -1,2: + 0: 65535 + -1,3: + 0: 4095 + 0,-4: + 0: 65535 + 0,-3: + 0: 65535 + 0,-2: + 0: 65535 + 0,-1: + 0: 65535 + 1,-4: + 0: 65395 + 1,-3: + 0: 65535 + 1,-2: + 0: 65535 + 1,-1: + 0: 65535 + 2,-4: + 0: 4096 + 2,-3: + 0: 4369 + 2,-2: + 0: 4369 + 2,-1: + 0: 4369 + -2,-4: + 0: 65224 + -2,-3: + 0: 65535 + -2,-2: + 0: 65535 + -2,-1: + 0: 65535 + -1,-4: + 0: 65535 + -1,-3: + 0: 65535 + -1,-2: + 0: 65535 + -1,-1: + 0: 65535 + -1,-5: + 0: 28672 + 0,-5: + 0: 53248 + 1,-5: + 0: 4096 + uniqueMixes: + - volume: 2500 + temperature: 293.15 + moles: + - 21.824879 + - 82.10312 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + chunkSize: 4 + type: GridAtmosphere + - type: GasTileOverlay + - type: RadiationGridResistance +- proto: AirCanister + entities: + - uid: 2 + components: + - pos: 2.5,-14.5 + parent: 1 + type: Transform +- proto: AirlockBarGlassLocked + entities: + - uid: 3 + components: + - pos: 2.5,2.5 + parent: 1 + type: Transform +- proto: AirlockBarLocked + entities: + - uid: 4 + components: + - rot: 3.141592653589793 rad + pos: 1.5,5.5 + parent: 1 + type: Transform +- proto: AirlockBrigGlassLocked + entities: + - uid: 5 + components: + - rot: 3.141592653589793 rad + pos: -3.5,-10.5 + parent: 1 + type: Transform +- proto: AirlockCommandGlassLocked + entities: + - uid: 6 + components: + - pos: -3.5,7.5 + parent: 1 + type: Transform + - links: + - 778 + type: DeviceLinkSink + - uid: 7 + components: + - pos: 4.5,7.5 + parent: 1 + type: Transform + - links: + - 778 + type: DeviceLinkSink + - uid: 8 + components: + - rot: 3.141592653589793 rad + pos: -3.5,10.5 + parent: 1 + type: Transform + - links: + - 780 + type: DeviceLinkSink + - uid: 9 + components: + - rot: 3.141592653589793 rad + pos: 4.5,10.5 + parent: 1 + type: Transform + - links: + - 780 + type: DeviceLinkSink +- proto: AirlockEngineeringLocked + entities: + - uid: 10 + components: + - pos: 1.5,-11.5 + parent: 1 + type: Transform + - uid: 11 + components: + - pos: -0.5,-11.5 + parent: 1 + type: Transform +- proto: AirlockExternalGlassShuttleEmergencyLocked + entities: + - uid: 12 + components: + - rot: 1.5707963267948966 rad + pos: 8.5,2.5 + parent: 1 + type: Transform + - uid: 13 + components: + - rot: -1.5707963267948966 rad + pos: -7.5,-5.5 + parent: 1 + type: Transform + - uid: 14 + components: + - rot: -1.5707963267948966 rad + pos: -7.5,2.5 + parent: 1 + type: Transform + - uid: 15 + components: + - rot: -1.5707963267948966 rad + pos: -7.5,0.5 + parent: 1 + type: Transform + - uid: 16 + components: + - rot: -1.5707963267948966 rad + pos: -7.5,-7.5 + parent: 1 + type: Transform + - uid: 17 + components: + - rot: 1.5707963267948966 rad + pos: 8.5,0.5 + parent: 1 + type: Transform + - uid: 18 + components: + - rot: 1.5707963267948966 rad + pos: 8.5,-5.5 + parent: 1 + type: Transform + - uid: 19 + components: + - rot: 1.5707963267948966 rad + pos: 8.5,-7.5 + parent: 1 + type: Transform +- proto: AirlockMedicalGlassLocked + entities: + - uid: 20 + components: + - pos: 2.5,-7.5 + parent: 1 + type: Transform +- proto: AirlockSecurityGlassLocked + entities: + - uid: 21 + components: + - pos: -1.5,-7.5 + parent: 1 + type: Transform +- proto: APCHyperCapacity + entities: + - uid: 22 + components: + - pos: -4.5,-6.5 + parent: 1 + type: Transform + - uid: 23 + components: + - pos: 4.5,-6.5 + parent: 1 + type: Transform + - uid: 24 + components: + - pos: 1.5,2.5 + parent: 1 + type: Transform + - uid: 25 + components: + - pos: 3.5,10.5 + parent: 1 + type: Transform + - uid: 26 + components: + - pos: -3.5,-13.5 + parent: 1 + type: Transform + - uid: 27 + components: + - pos: 0.5,-0.5 + parent: 1 + type: Transform +- proto: AtmosDeviceFanTiny + entities: + - uid: 28 + components: + - rot: 1.5707963267948966 rad + pos: -7.5,-7.5 + parent: 1 + type: Transform + - uid: 29 + components: + - rot: 1.5707963267948966 rad + pos: -7.5,-5.5 + parent: 1 + type: Transform + - uid: 30 + components: + - rot: 1.5707963267948966 rad + pos: -7.5,0.5 + parent: 1 + type: Transform + - uid: 31 + components: + - rot: 1.5707963267948966 rad + pos: -7.5,2.5 + parent: 1 + type: Transform + - uid: 32 + components: + - rot: 1.5707963267948966 rad + pos: 8.5,2.5 + parent: 1 + type: Transform + - uid: 33 + components: + - rot: 1.5707963267948966 rad + pos: 8.5,0.5 + parent: 1 + type: Transform + - uid: 34 + components: + - rot: 1.5707963267948966 rad + pos: 8.5,-5.5 + parent: 1 + type: Transform + - uid: 35 + components: + - rot: 1.5707963267948966 rad + pos: 8.5,-7.5 + parent: 1 + type: Transform +- proto: Bed + entities: + - uid: 36 + components: + - pos: 6.5,-11.5 + parent: 1 + type: Transform + - uid: 37 + components: + - pos: -5.5,-11.5 + parent: 1 + type: Transform + - uid: 38 + components: + - pos: 4.5,-7.5 + parent: 1 + type: Transform + - uid: 39 + components: + - pos: 4.5,-9.5 + parent: 1 + type: Transform +- proto: BedsheetBrown + entities: + - uid: 40 + components: + - rot: 3.141592653589793 rad + pos: -5.5,-11.5 + parent: 1 + type: Transform +- proto: BedsheetGreen + entities: + - uid: 41 + components: + - rot: -1.5707963267948966 rad + pos: 6.5,-11.5 + parent: 1 + type: Transform +- proto: BedsheetMedical + entities: + - uid: 42 + components: + - rot: 3.141592653589793 rad + pos: 4.5,-7.5 + parent: 1 + type: Transform + - uid: 43 + components: + - rot: 3.141592653589793 rad + pos: 4.5,-9.5 + parent: 1 + type: Transform +- proto: BlastDoorOpen + entities: + - uid: 44 + components: + - rot: 1.5707963267948966 rad + pos: -0.5,10.5 + parent: 1 + type: Transform + - links: + - 780 + type: DeviceLinkSink + - uid: 45 + components: + - rot: 1.5707963267948966 rad + pos: 0.5,10.5 + parent: 1 + type: Transform + - links: + - 780 + type: DeviceLinkSink + - uid: 46 + components: + - rot: 1.5707963267948966 rad + pos: 1.5,10.5 + parent: 1 + type: Transform + - links: + - 780 + type: DeviceLinkSink +- proto: BoozeDispenser + entities: + - uid: 47 + components: + - pos: -1.5,5.5 + parent: 1 + type: Transform +- proto: CableApcExtension + entities: + - uid: 48 + components: + - pos: 1.5,0.5 + parent: 1 + type: Transform + - uid: 49 + components: + - pos: -3.5,-13.5 + parent: 1 + type: Transform + - uid: 50 + components: + - pos: -2.5,-13.5 + parent: 1 + type: Transform + - uid: 51 + components: + - pos: -1.5,-13.5 + parent: 1 + type: Transform + - uid: 52 + components: + - pos: -0.5,-13.5 + parent: 1 + type: Transform + - uid: 53 + components: + - pos: 0.5,-13.5 + parent: 1 + type: Transform + - uid: 54 + components: + - pos: 1.5,-13.5 + parent: 1 + type: Transform + - uid: 55 + components: + - pos: 2.5,-13.5 + parent: 1 + type: Transform + - uid: 56 + components: + - pos: 3.5,-13.5 + parent: 1 + type: Transform + - uid: 57 + components: + - pos: 4.5,-13.5 + parent: 1 + type: Transform + - uid: 58 + components: + - pos: -0.5,-14.5 + parent: 1 + type: Transform + - uid: 59 + components: + - pos: -0.5,-15.5 + parent: 1 + type: Transform + - uid: 60 + components: + - pos: 0.5,-15.5 + parent: 1 + type: Transform + - uid: 61 + components: + - pos: 1.5,-15.5 + parent: 1 + type: Transform + - uid: 62 + components: + - pos: 1.5,-14.5 + parent: 1 + type: Transform + - uid: 63 + components: + - pos: 3.5,-14.5 + parent: 1 + type: Transform + - uid: 64 + components: + - pos: -2.5,-14.5 + parent: 1 + type: Transform + - uid: 65 + components: + - pos: -4.5,-12.5 + parent: 1 + type: Transform + - uid: 66 + components: + - pos: -4.5,-11.5 + parent: 1 + type: Transform + - uid: 67 + components: + - pos: -5.5,-9.5 + parent: 1 + type: Transform + - uid: 68 + components: + - pos: -5.5,-10.5 + parent: 1 + type: Transform + - uid: 69 + components: + - pos: -5.5,-11.5 + parent: 1 + type: Transform + - uid: 70 + components: + - pos: -5.5,-8.5 + parent: 1 + type: Transform + - uid: 71 + components: + - pos: -5.5,-7.5 + parent: 1 + type: Transform + - uid: 72 + components: + - pos: -6.5,-7.5 + parent: 1 + type: Transform + - uid: 73 + components: + - pos: -4.5,-7.5 + parent: 1 + type: Transform + - uid: 74 + components: + - pos: -4.5,-6.5 + parent: 1 + type: Transform + - uid: 75 + components: + - pos: -3.5,-7.5 + parent: 1 + type: Transform + - uid: 76 + components: + - pos: -3.5,-8.5 + parent: 1 + type: Transform + - uid: 77 + components: + - pos: -2.5,-7.5 + parent: 1 + type: Transform + - uid: 78 + components: + - pos: 4.5,-6.5 + parent: 1 + type: Transform + - uid: 79 + components: + - pos: 4.5,-7.5 + parent: 1 + type: Transform + - uid: 80 + components: + - pos: 4.5,-8.5 + parent: 1 + type: Transform + - uid: 81 + components: + - pos: 4.5,-9.5 + parent: 1 + type: Transform + - uid: 82 + components: + - pos: 4.5,-10.5 + parent: 1 + type: Transform + - uid: 83 + components: + - pos: 4.5,-11.5 + parent: 1 + type: Transform + - uid: 84 + components: + - pos: 5.5,-11.5 + parent: 1 + type: Transform + - uid: 85 + components: + - pos: 6.5,-11.5 + parent: 1 + type: Transform + - uid: 86 + components: + - pos: 6.5,-10.5 + parent: 1 + type: Transform + - uid: 87 + components: + - pos: 0.5,-0.5 + parent: 1 + type: Transform + - uid: 88 + components: + - pos: 0.5,0.5 + parent: 1 + type: Transform + - uid: 89 + components: + - pos: 2.5,0.5 + parent: 1 + type: Transform + - uid: 90 + components: + - pos: 2.5,-0.5 + parent: 1 + type: Transform + - uid: 91 + components: + - pos: 2.5,-1.5 + parent: 1 + type: Transform + - uid: 92 + components: + - pos: 2.5,-2.5 + parent: 1 + type: Transform + - uid: 93 + components: + - pos: 2.5,-3.5 + parent: 1 + type: Transform + - uid: 94 + components: + - pos: 2.5,-4.5 + parent: 1 + type: Transform + - uid: 95 + components: + - pos: 2.5,-5.5 + parent: 1 + type: Transform + - uid: 96 + components: + - pos: -1.5,-5.5 + parent: 1 + type: Transform + - uid: 97 + components: + - pos: -1.5,-4.5 + parent: 1 + type: Transform + - uid: 98 + components: + - pos: -1.5,-3.5 + parent: 1 + type: Transform + - uid: 99 + components: + - pos: -1.5,-2.5 + parent: 1 + type: Transform + - uid: 100 + components: + - pos: -1.5,-1.5 + parent: 1 + type: Transform + - uid: 101 + components: + - pos: -2.5,-5.5 + parent: 1 + type: Transform + - uid: 102 + components: + - pos: -1.5,-0.5 + parent: 1 + type: Transform + - uid: 103 + components: + - pos: -1.5,0.5 + parent: 1 + type: Transform + - uid: 104 + components: + - pos: -0.5,0.5 + parent: 1 + type: Transform + - uid: 105 + components: + - pos: -3.5,-5.5 + parent: 1 + type: Transform + - uid: 106 + components: + - pos: -6.5,-2.5 + parent: 1 + type: Transform + - uid: 107 + components: + - pos: -6.5,-3.5 + parent: 1 + type: Transform + - uid: 108 + components: + - pos: -6.5,-5.5 + parent: 1 + type: Transform + - uid: 109 + components: + - pos: -6.5,-4.5 + parent: 1 + type: Transform + - uid: 110 + components: + - pos: -3.5,-4.5 + parent: 1 + type: Transform + - uid: 111 + components: + - pos: -4.5,-4.5 + parent: 1 + type: Transform + - uid: 112 + components: + - pos: -5.5,-4.5 + parent: 1 + type: Transform + - uid: 113 + components: + - pos: -6.5,-1.5 + parent: 1 + type: Transform + - uid: 114 + components: + - pos: -6.5,-0.5 + parent: 1 + type: Transform + - uid: 115 + components: + - pos: -6.5,0.5 + parent: 1 + type: Transform + - uid: 116 + components: + - pos: -6.5,1.5 + parent: 1 + type: Transform + - uid: 117 + components: + - pos: -6.5,2.5 + parent: 1 + type: Transform + - uid: 118 + components: + - pos: -5.5,2.5 + parent: 1 + type: Transform + - uid: 119 + components: + - pos: -5.5,3.5 + parent: 1 + type: Transform + - uid: 120 + components: + - pos: -5.5,4.5 + parent: 1 + type: Transform + - uid: 121 + components: + - pos: -5.5,5.5 + parent: 1 + type: Transform + - uid: 122 + components: + - pos: -5.5,6.5 + parent: 1 + type: Transform + - uid: 123 + components: + - pos: -4.5,6.5 + parent: 1 + type: Transform + - uid: 124 + components: + - pos: -3.5,6.5 + parent: 1 + type: Transform + - uid: 125 + components: + - pos: -3.5,5.5 + parent: 1 + type: Transform + - uid: 126 + components: + - pos: -3.5,4.5 + parent: 1 + type: Transform + - uid: 127 + components: + - pos: -3.5,3.5 + parent: 1 + type: Transform + - uid: 128 + components: + - pos: -2.5,0.5 + parent: 1 + type: Transform + - uid: 129 + components: + - pos: -3.5,0.5 + parent: 1 + type: Transform + - uid: 130 + components: + - pos: -3.5,1.5 + parent: 1 + type: Transform + - uid: 131 + components: + - pos: 4.5,1.5 + parent: 1 + type: Transform + - uid: 132 + components: + - pos: 4.5,0.5 + parent: 1 + type: Transform + - uid: 133 + components: + - pos: 3.5,0.5 + parent: 1 + type: Transform + - uid: 134 + components: + - pos: 4.5,3.5 + parent: 1 + type: Transform + - uid: 135 + components: + - pos: 4.5,4.5 + parent: 1 + type: Transform + - uid: 136 + components: + - pos: 4.5,5.5 + parent: 1 + type: Transform + - uid: 137 + components: + - pos: 4.5,6.5 + parent: 1 + type: Transform + - uid: 138 + components: + - pos: 5.5,6.5 + parent: 1 + type: Transform + - uid: 139 + components: + - pos: 6.5,6.5 + parent: 1 + type: Transform + - uid: 140 + components: + - pos: 6.5,5.5 + parent: 1 + type: Transform + - uid: 141 + components: + - pos: 6.5,4.5 + parent: 1 + type: Transform + - uid: 142 + components: + - pos: 6.5,3.5 + parent: 1 + type: Transform + - uid: 143 + components: + - pos: 6.5,2.5 + parent: 1 + type: Transform + - uid: 144 + components: + - pos: 7.5,2.5 + parent: 1 + type: Transform + - uid: 145 + components: + - pos: 7.5,0.5 + parent: 1 + type: Transform + - uid: 146 + components: + - pos: 7.5,1.5 + parent: 1 + type: Transform + - uid: 147 + components: + - pos: 7.5,-0.5 + parent: 1 + type: Transform + - uid: 148 + components: + - pos: 7.5,-1.5 + parent: 1 + type: Transform + - uid: 149 + components: + - pos: 7.5,-2.5 + parent: 1 + type: Transform + - uid: 150 + components: + - pos: 7.5,-3.5 + parent: 1 + type: Transform + - uid: 151 + components: + - pos: 7.5,-4.5 + parent: 1 + type: Transform + - uid: 152 + components: + - pos: 7.5,-5.5 + parent: 1 + type: Transform + - uid: 153 + components: + - pos: 7.5,-6.5 + parent: 1 + type: Transform + - uid: 154 + components: + - pos: 7.5,-7.5 + parent: 1 + type: Transform + - uid: 155 + components: + - pos: 7.5,-8.5 + parent: 1 + type: Transform + - uid: 156 + components: + - pos: 6.5,-8.5 + parent: 1 + type: Transform + - uid: 157 + components: + - pos: 1.5,-5.5 + parent: 1 + type: Transform + - uid: 158 + components: + - pos: 0.5,-5.5 + parent: 1 + type: Transform + - uid: 159 + components: + - pos: 5.5,-5.5 + parent: 1 + type: Transform + - uid: 160 + components: + - pos: 6.5,-5.5 + parent: 1 + type: Transform + - uid: 161 + components: + - pos: 5.5,-4.5 + parent: 1 + type: Transform + - uid: 162 + components: + - pos: 4.5,-4.5 + parent: 1 + type: Transform + - uid: 163 + components: + - pos: 3.5,-4.5 + parent: 1 + type: Transform + - uid: 164 + components: + - pos: -0.5,-5.5 + parent: 1 + type: Transform + - uid: 165 + components: + - pos: 0.5,-6.5 + parent: 1 + type: Transform + - uid: 166 + components: + - pos: 0.5,-7.5 + parent: 1 + type: Transform + - uid: 167 + components: + - pos: 0.5,-8.5 + parent: 1 + type: Transform + - uid: 168 + components: + - pos: 0.5,-9.5 + parent: 1 + type: Transform + - uid: 169 + components: + - pos: 0.5,-10.5 + parent: 1 + type: Transform + - uid: 170 + components: + - pos: 1.5,2.5 + parent: 1 + type: Transform + - uid: 171 + components: + - pos: 2.5,3.5 + parent: 1 + type: Transform + - uid: 172 + components: + - pos: 1.5,3.5 + parent: 1 + type: Transform + - uid: 173 + components: + - pos: 0.5,3.5 + parent: 1 + type: Transform + - uid: 174 + components: + - pos: -0.5,3.5 + parent: 1 + type: Transform + - uid: 175 + components: + - pos: -1.5,3.5 + parent: 1 + type: Transform + - uid: 176 + components: + - pos: -1.5,4.5 + parent: 1 + type: Transform + - uid: 177 + components: + - pos: -1.5,5.5 + parent: 1 + type: Transform + - uid: 178 + components: + - pos: -0.5,5.5 + parent: 1 + type: Transform + - uid: 179 + components: + - pos: 0.5,5.5 + parent: 1 + type: Transform + - uid: 180 + components: + - pos: 1.5,5.5 + parent: 1 + type: Transform + - uid: 181 + components: + - pos: 2.5,5.5 + parent: 1 + type: Transform + - uid: 182 + components: + - pos: 3.5,10.5 + parent: 1 + type: Transform + - uid: 183 + components: + - pos: 3.5,9.5 + parent: 1 + type: Transform + - uid: 184 + components: + - pos: 3.5,8.5 + parent: 1 + type: Transform + - uid: 185 + components: + - pos: 2.5,8.5 + parent: 1 + type: Transform + - uid: 186 + components: + - pos: 1.5,8.5 + parent: 1 + type: Transform + - uid: 187 + components: + - pos: 0.5,8.5 + parent: 1 + type: Transform + - uid: 188 + components: + - pos: -0.5,8.5 + parent: 1 + type: Transform + - uid: 189 + components: + - pos: -1.5,8.5 + parent: 1 + type: Transform + - uid: 190 + components: + - pos: -2.5,8.5 + parent: 1 + type: Transform + - uid: 191 + components: + - pos: -3.5,8.5 + parent: 1 + type: Transform + - uid: 192 + components: + - pos: -3.5,9.5 + parent: 1 + type: Transform + - uid: 193 + components: + - pos: -3.5,10.5 + parent: 1 + type: Transform + - uid: 194 + components: + - pos: -3.5,11.5 + parent: 1 + type: Transform + - uid: 195 + components: + - pos: -3.5,12.5 + parent: 1 + type: Transform + - uid: 196 + components: + - pos: -2.5,12.5 + parent: 1 + type: Transform + - uid: 197 + components: + - pos: -1.5,12.5 + parent: 1 + type: Transform + - uid: 198 + components: + - pos: -0.5,12.5 + parent: 1 + type: Transform + - uid: 199 + components: + - pos: 0.5,12.5 + parent: 1 + type: Transform + - uid: 200 + components: + - pos: 1.5,12.5 + parent: 1 + type: Transform + - uid: 201 + components: + - pos: 2.5,12.5 + parent: 1 + type: Transform + - uid: 202 + components: + - pos: 3.5,12.5 + parent: 1 + type: Transform + - uid: 203 + components: + - pos: 3.5,11.5 + parent: 1 + type: Transform +- proto: CableHV + entities: + - uid: 204 + components: + - pos: -2.5,-12.5 + parent: 1 + type: Transform + - uid: 205 + components: + - pos: -2.5,-13.5 + parent: 1 + type: Transform + - uid: 206 + components: + - pos: -0.5,-11.5 + parent: 1 + type: Transform + - uid: 207 + components: + - pos: 2.5,-12.5 + parent: 1 + type: Transform + - uid: 208 + components: + - pos: 2.5,-13.5 + parent: 1 + type: Transform + - uid: 209 + components: + - pos: 2.5,-14.5 + parent: 1 + type: Transform + - uid: 210 + components: + - pos: 2.5,-15.5 + parent: 1 + type: Transform + - uid: 211 + components: + - pos: -2.5,-14.5 + parent: 1 + type: Transform + - uid: 212 + components: + - pos: 2.5,-12.5 + parent: 1 + type: Transform + - uid: 213 + components: + - pos: 1.5,-12.5 + parent: 1 + type: Transform + - uid: 214 + components: + - pos: 0.5,-12.5 + parent: 1 + type: Transform + - uid: 215 + components: + - pos: -0.5,-12.5 + parent: 1 + type: Transform + - uid: 216 + components: + - pos: -1.5,-11.5 + parent: 1 + type: Transform + - uid: 217 + components: + - pos: -1.5,-12.5 + parent: 1 + type: Transform + - uid: 218 + components: + - pos: -1.5,-13.5 + parent: 1 + type: Transform + - uid: 219 + components: + - pos: -0.5,-10.5 + parent: 1 + type: Transform + - uid: 220 + components: + - pos: -0.5,-9.5 + parent: 1 + type: Transform + - uid: 221 + components: + - pos: -0.5,-8.5 + parent: 1 + type: Transform + - uid: 222 + components: + - pos: -0.5,-7.5 + parent: 1 + type: Transform + - uid: 223 + components: + - pos: -0.5,-6.5 + parent: 1 + type: Transform + - uid: 224 + components: + - pos: -2.5,10.5 + parent: 1 + type: Transform + - uid: 225 + components: + - pos: -3.5,10.5 + parent: 1 + type: Transform + - uid: 226 + components: + - pos: -3.5,9.5 + parent: 1 + type: Transform + - uid: 227 + components: + - pos: -3.5,8.5 + parent: 1 + type: Transform + - uid: 228 + components: + - pos: -3.5,7.5 + parent: 1 + type: Transform + - uid: 229 + components: + - pos: -3.5,6.5 + parent: 1 + type: Transform + - uid: 230 + components: + - pos: -3.5,5.5 + parent: 1 + type: Transform + - uid: 231 + components: + - pos: -4.5,5.5 + parent: 1 + type: Transform + - uid: 232 + components: + - pos: -4.5,4.5 + parent: 1 + type: Transform + - uid: 233 + components: + - pos: -4.5,3.5 + parent: 1 + type: Transform + - uid: 234 + components: + - pos: -4.5,2.5 + parent: 1 + type: Transform + - uid: 235 + components: + - pos: -4.5,1.5 + parent: 1 + type: Transform + - uid: 236 + components: + - pos: -4.5,0.5 + parent: 1 + type: Transform + - uid: 237 + components: + - pos: -4.5,-0.5 + parent: 1 + type: Transform + - uid: 238 + components: + - pos: -4.5,-1.5 + parent: 1 + type: Transform + - uid: 239 + components: + - pos: -4.5,-2.5 + parent: 1 + type: Transform + - uid: 240 + components: + - pos: -4.5,-3.5 + parent: 1 + type: Transform + - uid: 241 + components: + - pos: -4.5,-4.5 + parent: 1 + type: Transform + - uid: 242 + components: + - pos: -4.5,-5.5 + parent: 1 + type: Transform + - uid: 243 + components: + - pos: -3.5,-5.5 + parent: 1 + type: Transform + - uid: 244 + components: + - pos: -2.5,-5.5 + parent: 1 + type: Transform + - uid: 245 + components: + - pos: -1.5,-5.5 + parent: 1 + type: Transform + - uid: 246 + components: + - pos: -0.5,-5.5 + parent: 1 + type: Transform + - uid: 247 + components: + - pos: -3.5,11.5 + parent: 1 + type: Transform + - uid: 248 + components: + - pos: -3.5,12.5 + parent: 1 + type: Transform + - uid: 249 + components: + - pos: -4.5,12.5 + parent: 1 + type: Transform + - uid: 250 + components: + - pos: -4.5,13.5 + parent: 1 + type: Transform + - uid: 251 + components: + - pos: -3.5,13.5 + parent: 1 + type: Transform + - uid: 252 + components: + - pos: -3.5,14.5 + parent: 1 + type: Transform + - uid: 253 + components: + - pos: -2.5,14.5 + parent: 1 + type: Transform + - uid: 254 + components: + - pos: -1.5,14.5 + parent: 1 + type: Transform + - uid: 255 + components: + - pos: -0.5,14.5 + parent: 1 + type: Transform + - uid: 256 + components: + - pos: 0.5,14.5 + parent: 1 + type: Transform + - uid: 257 + components: + - pos: 1.5,14.5 + parent: 1 + type: Transform + - uid: 258 + components: + - pos: 2.5,14.5 + parent: 1 + type: Transform + - uid: 259 + components: + - pos: 3.5,14.5 + parent: 1 + type: Transform + - uid: 260 + components: + - pos: 4.5,14.5 + parent: 1 + type: Transform + - uid: 261 + components: + - pos: 4.5,13.5 + parent: 1 + type: Transform + - uid: 262 + components: + - pos: 5.5,13.5 + parent: 1 + type: Transform + - uid: 263 + components: + - pos: 5.5,12.5 + parent: 1 + type: Transform + - uid: 264 + components: + - pos: 4.5,12.5 + parent: 1 + type: Transform + - uid: 265 + components: + - pos: 2.5,10.5 + parent: 1 + type: Transform + - uid: 266 + components: + - pos: 2.5,11.5 + parent: 1 + type: Transform + - uid: 267 + components: + - pos: 3.5,11.5 + parent: 1 + type: Transform + - uid: 268 + components: + - pos: 4.5,11.5 + parent: 1 + type: Transform + - uid: 269 + components: + - pos: 4.5,10.5 + parent: 1 + type: Transform + - uid: 270 + components: + - pos: 4.5,9.5 + parent: 1 + type: Transform + - uid: 271 + components: + - pos: 4.5,8.5 + parent: 1 + type: Transform + - uid: 272 + components: + - pos: 3.5,8.5 + parent: 1 + type: Transform + - uid: 273 + components: + - pos: 2.5,8.5 + parent: 1 + type: Transform + - uid: 274 + components: + - pos: 1.5,8.5 + parent: 1 + type: Transform + - uid: 275 + components: + - pos: 0.5,8.5 + parent: 1 + type: Transform + - uid: 276 + components: + - pos: -0.5,8.5 + parent: 1 + type: Transform + - uid: 277 + components: + - pos: -1.5,8.5 + parent: 1 + type: Transform + - uid: 278 + components: + - pos: -2.5,8.5 + parent: 1 + type: Transform + - uid: 279 + components: + - pos: 4.5,-0.5 + parent: 1 + type: Transform + - uid: 280 + components: + - pos: 4.5,0.5 + parent: 1 + type: Transform + - uid: 281 + components: + - pos: 3.5,0.5 + parent: 1 + type: Transform + - uid: 282 + components: + - pos: 2.5,0.5 + parent: 1 + type: Transform + - uid: 283 + components: + - pos: 1.5,0.5 + parent: 1 + type: Transform + - uid: 284 + components: + - pos: 0.5,0.5 + parent: 1 + type: Transform + - uid: 285 + components: + - pos: -0.5,0.5 + parent: 1 + type: Transform + - uid: 286 + components: + - pos: -1.5,0.5 + parent: 1 + type: Transform + - uid: 287 + components: + - pos: -2.5,0.5 + parent: 1 + type: Transform + - uid: 288 + components: + - pos: -3.5,0.5 + parent: 1 + type: Transform + - uid: 289 + components: + - pos: -3.5,-0.5 + parent: 1 + type: Transform +- proto: CableMV + entities: + - uid: 290 + components: + - pos: 2.5,-12.5 + parent: 1 + type: Transform + - uid: 291 + components: + - pos: 2.5,-13.5 + parent: 1 + type: Transform + - uid: 292 + components: + - pos: 1.5,-13.5 + parent: 1 + type: Transform + - uid: 293 + components: + - pos: 0.5,-13.5 + parent: 1 + type: Transform + - uid: 294 + components: + - pos: -0.5,-13.5 + parent: 1 + type: Transform + - uid: 295 + components: + - pos: -1.5,-13.5 + parent: 1 + type: Transform + - uid: 296 + components: + - pos: -2.5,-13.5 + parent: 1 + type: Transform + - uid: 297 + components: + - pos: -3.5,-13.5 + parent: 1 + type: Transform + - uid: 298 + components: + - pos: -0.5,-14.5 + parent: 1 + type: Transform + - uid: 299 + components: + - pos: -0.5,-15.5 + parent: 1 + type: Transform + - uid: 300 + components: + - pos: 0.5,-15.5 + parent: 1 + type: Transform + - uid: 301 + components: + - pos: 1.5,-15.5 + parent: 1 + type: Transform + - uid: 302 + components: + - pos: 1.5,-14.5 + parent: 1 + type: Transform + - uid: 303 + components: + - pos: -0.5,-12.5 + parent: 1 + type: Transform + - uid: 304 + components: + - pos: -0.5,-11.5 + parent: 1 + type: Transform + - uid: 305 + components: + - pos: -0.5,-9.5 + parent: 1 + type: Transform + - uid: 306 + components: + - pos: -0.5,-8.5 + parent: 1 + type: Transform + - uid: 307 + components: + - pos: -0.5,-7.5 + parent: 1 + type: Transform + - uid: 308 + components: + - pos: -1.5,-7.5 + parent: 1 + type: Transform + - uid: 309 + components: + - pos: -2.5,-7.5 + parent: 1 + type: Transform + - uid: 310 + components: + - pos: -3.5,-7.5 + parent: 1 + type: Transform + - uid: 311 + components: + - pos: -4.5,-7.5 + parent: 1 + type: Transform + - uid: 312 + components: + - pos: -4.5,-6.5 + parent: 1 + type: Transform + - uid: 313 + components: + - pos: -0.5,-10.5 + parent: 1 + type: Transform + - uid: 314 + components: + - pos: 1.5,-10.5 + parent: 1 + type: Transform + - uid: 315 + components: + - pos: 1.5,-11.5 + parent: 1 + type: Transform + - uid: 316 + components: + - pos: 1.5,-12.5 + parent: 1 + type: Transform + - uid: 317 + components: + - pos: 1.5,-7.5 + parent: 1 + type: Transform + - uid: 318 + components: + - pos: 1.5,-8.5 + parent: 1 + type: Transform + - uid: 319 + components: + - pos: 2.5,-7.5 + parent: 1 + type: Transform + - uid: 320 + components: + - pos: 3.5,-7.5 + parent: 1 + type: Transform + - uid: 321 + components: + - pos: 4.5,-7.5 + parent: 1 + type: Transform + - uid: 322 + components: + - pos: 4.5,-6.5 + parent: 1 + type: Transform + - uid: 323 + components: + - pos: 1.5,-9.5 + parent: 1 + type: Transform + - uid: 324 + components: + - pos: 1.5,-6.5 + parent: 1 + type: Transform + - uid: 325 + components: + - pos: 1.5,-5.5 + parent: 1 + type: Transform + - uid: 326 + components: + - pos: 2.5,-5.5 + parent: 1 + type: Transform + - uid: 327 + components: + - pos: 2.5,-4.5 + parent: 1 + type: Transform + - uid: 328 + components: + - pos: 2.5,-3.5 + parent: 1 + type: Transform + - uid: 329 + components: + - pos: 2.5,-2.5 + parent: 1 + type: Transform + - uid: 330 + components: + - pos: 2.5,-1.5 + parent: 1 + type: Transform + - uid: 331 + components: + - pos: 2.5,-0.5 + parent: 1 + type: Transform + - uid: 332 + components: + - pos: 2.5,0.5 + parent: 1 + type: Transform + - uid: 333 + components: + - pos: 1.5,0.5 + parent: 1 + type: Transform + - uid: 334 + components: + - pos: 0.5,0.5 + parent: 1 + type: Transform + - uid: 335 + components: + - pos: 0.5,-0.5 + parent: 1 + type: Transform + - uid: 336 + components: + - pos: -0.5,0.5 + parent: 1 + type: Transform + - uid: 337 + components: + - pos: -1.5,0.5 + parent: 1 + type: Transform + - uid: 338 + components: + - pos: -1.5,-0.5 + parent: 1 + type: Transform + - uid: 339 + components: + - pos: -1.5,-1.5 + parent: 1 + type: Transform + - uid: 340 + components: + - pos: -1.5,-2.5 + parent: 1 + type: Transform + - uid: 341 + components: + - pos: -1.5,-3.5 + parent: 1 + type: Transform + - uid: 342 + components: + - pos: -1.5,-4.5 + parent: 1 + type: Transform + - uid: 343 + components: + - pos: -1.5,-5.5 + parent: 1 + type: Transform + - uid: 344 + components: + - pos: -0.5,-5.5 + parent: 1 + type: Transform + - uid: 345 + components: + - pos: -0.5,-6.5 + parent: 1 + type: Transform + - uid: 346 + components: + - pos: 1.5,1.5 + parent: 1 + type: Transform + - uid: 347 + components: + - pos: 1.5,2.5 + parent: 1 + type: Transform + - uid: 348 + components: + - pos: -2.5,10.5 + parent: 1 + type: Transform + - uid: 349 + components: + - pos: -2.5,9.5 + parent: 1 + type: Transform + - uid: 350 + components: + - pos: -1.5,9.5 + parent: 1 + type: Transform + - uid: 351 + components: + - pos: -0.5,9.5 + parent: 1 + type: Transform + - uid: 352 + components: + - pos: 0.5,9.5 + parent: 1 + type: Transform + - uid: 353 + components: + - pos: 1.5,9.5 + parent: 1 + type: Transform + - uid: 354 + components: + - pos: 2.5,9.5 + parent: 1 + type: Transform + - uid: 355 + components: + - pos: 3.5,9.5 + parent: 1 + type: Transform + - uid: 356 + components: + - pos: 3.5,10.5 + parent: 1 + type: Transform + - uid: 357 + components: + - pos: 4.5,9.5 + parent: 1 + type: Transform + - uid: 358 + components: + - pos: 5.5,9.5 + parent: 1 + type: Transform + - uid: 359 + components: + - pos: 6.5,9.5 + parent: 1 + type: Transform + - uid: 360 + components: + - pos: 6.5,8.5 + parent: 1 + type: Transform + - uid: 361 + components: + - pos: -5.5,8.5 + parent: 1 + type: Transform + - uid: 362 + components: + - pos: -5.5,9.5 + parent: 1 + type: Transform + - uid: 363 + components: + - pos: -4.5,9.5 + parent: 1 + type: Transform + - uid: 364 + components: + - pos: -3.5,9.5 + parent: 1 + type: Transform + - uid: 365 + components: + - pos: -2.5,0.5 + parent: 1 + type: Transform + - uid: 366 + components: + - pos: -3.5,0.5 + parent: 1 + type: Transform + - uid: 367 + components: + - pos: -3.5,-0.5 + parent: 1 + type: Transform +- proto: CableTerminal + entities: + - uid: 368 + components: + - rot: 3.141592653589793 rad + pos: -1.5,-13.5 + parent: 1 + type: Transform +- proto: CandleBlueInfinite + entities: + - uid: 369 + components: + - pos: 0.40085018,8.408222 + parent: 1 + type: Transform +- proto: CandleGreenInfinite + entities: + - uid: 370 + components: + - pos: 0.57516325,8.27147 + parent: 1 + type: Transform +- proto: CandlePurpleInfinite + entities: + - uid: 371 + components: + - pos: 0.45705903,8.178229 + parent: 1 + type: Transform +- proto: CarpetBlue + entities: + - uid: 372 + components: + - rot: 1.5707963267948966 rad + pos: 0.5,5.5 + parent: 1 + type: Transform + - uid: 373 + components: + - rot: 1.5707963267948966 rad + pos: 0.5,4.5 + parent: 1 + type: Transform + - uid: 374 + components: + - rot: 1.5707963267948966 rad + pos: -0.5,4.5 + parent: 1 + type: Transform + - uid: 375 + components: + - rot: 1.5707963267948966 rad + pos: -1.5,4.5 + parent: 1 + type: Transform +- proto: Catwalk + entities: + - uid: 376 + components: + - pos: -1.5,-13.5 + parent: 1 + type: Transform + - uid: 377 + components: + - pos: -2.5,-13.5 + parent: 1 + type: Transform +- proto: Chair + entities: + - uid: 378 + components: + - rot: 1.5707963267948966 rad + pos: -1.5,3.5 + parent: 1 + type: Transform +- proto: ChairOfficeLight + entities: + - uid: 379 + components: + - rot: 3.141592653589793 rad + pos: 4.5,-11.5 + parent: 1 + type: Transform +- proto: ChairPilotSeat + entities: + - uid: 380 + components: + - rot: 3.141592653589793 rad + pos: 0.5,-14.5 + parent: 1 + type: Transform + - uid: 381 + components: + - rot: -1.5707963267948966 rad + pos: 6.5,4.5 + parent: 1 + type: Transform + - uid: 382 + components: + - rot: -1.5707963267948966 rad + pos: 6.5,5.5 + parent: 1 + type: Transform + - uid: 383 + components: + - rot: 3.141592653589793 rad + pos: -4.5,-8.5 + parent: 1 + type: Transform + - uid: 384 + components: + - rot: 3.141592653589793 rad + pos: -5.5,-8.5 + parent: 1 + type: Transform + - uid: 385 + components: + - rot: 3.141592653589793 rad + pos: -0.5,-14.5 + parent: 1 + type: Transform + - uid: 386 + components: + - rot: 3.141592653589793 rad + pos: 1.5,-14.5 + parent: 1 + type: Transform + - uid: 387 + components: + - rot: -1.5707963267948966 rad + pos: 3.5,-3.5 + parent: 1 + type: Transform + - uid: 388 + components: + - rot: 1.5707963267948966 rad + pos: -6.5,-1.5 + parent: 1 + type: Transform + - uid: 389 + components: + - rot: 1.5707963267948966 rad + pos: -6.5,-2.5 + parent: 1 + type: Transform + - uid: 390 + components: + - rot: 1.5707963267948966 rad + pos: -2.5,-3.5 + parent: 1 + type: Transform + - uid: 391 + components: + - rot: 1.5707963267948966 rad + pos: -2.5,-2.5 + parent: 1 + type: Transform + - uid: 392 + components: + - rot: 1.5707963267948966 rad + pos: -2.5,-1.5 + parent: 1 + type: Transform + - uid: 393 + components: + - rot: 1.5707963267948966 rad + pos: -2.5,-0.5 + parent: 1 + type: Transform + - uid: 394 + components: + - rot: -1.5707963267948966 rad + pos: 3.5,-0.5 + parent: 1 + type: Transform + - uid: 395 + components: + - rot: -1.5707963267948966 rad + pos: 3.5,-1.5 + parent: 1 + type: Transform + - uid: 396 + components: + - rot: -1.5707963267948966 rad + pos: 3.5,-2.5 + parent: 1 + type: Transform + - uid: 397 + components: + - rot: 1.5707963267948966 rad + pos: -6.5,-0.5 + parent: 1 + type: Transform + - uid: 398 + components: + - rot: 1.5707963267948966 rad + pos: -6.5,-3.5 + parent: 1 + type: Transform + - uid: 399 + components: + - rot: -1.5707963267948966 rad + pos: -4.5,-3.5 + parent: 1 + type: Transform + - uid: 400 + components: + - rot: -1.5707963267948966 rad + pos: -4.5,-2.5 + parent: 1 + type: Transform + - uid: 401 + components: + - rot: -1.5707963267948966 rad + pos: -4.5,-1.5 + parent: 1 + type: Transform + - uid: 402 + components: + - rot: -1.5707963267948966 rad + pos: -4.5,-0.5 + parent: 1 + type: Transform + - uid: 403 + components: + - rot: 1.5707963267948966 rad + pos: 5.5,-3.5 + parent: 1 + type: Transform + - uid: 404 + components: + - rot: 1.5707963267948966 rad + pos: 5.5,-2.5 + parent: 1 + type: Transform + - uid: 405 + components: + - rot: 1.5707963267948966 rad + pos: 5.5,-1.5 + parent: 1 + type: Transform + - uid: 406 + components: + - rot: 1.5707963267948966 rad + pos: 5.5,-0.5 + parent: 1 + type: Transform + - uid: 407 + components: + - rot: -1.5707963267948966 rad + pos: 7.5,-0.5 + parent: 1 + type: Transform + - uid: 408 + components: + - rot: -1.5707963267948966 rad + pos: 7.5,-1.5 + parent: 1 + type: Transform + - uid: 409 + components: + - rot: -1.5707963267948966 rad + pos: 7.5,-2.5 + parent: 1 + type: Transform + - uid: 410 + components: + - rot: -1.5707963267948966 rad + pos: 7.5,-3.5 + parent: 1 + type: Transform + - uid: 411 + components: + - rot: 1.5707963267948966 rad + pos: -5.5,5.5 + parent: 1 + type: Transform + - uid: 412 + components: + - rot: 1.5707963267948966 rad + pos: -5.5,4.5 + parent: 1 + type: Transform + - uid: 413 + components: + - rot: -1.5707963267948966 rad + pos: -2.5,12.5 + parent: 1 + type: Transform + - uid: 414 + components: + - rot: 3.141592653589793 rad + pos: 0.5,12.5 + parent: 1 + type: Transform + - uid: 415 + components: + - rot: 3.141592653589793 rad + pos: -1.5,12.5 + parent: 1 + type: Transform + - uid: 416 + components: + - rot: 3.141592653589793 rad + pos: 2.5,12.5 + parent: 1 + type: Transform + - uid: 417 + components: + - rot: 1.5707963267948966 rad + pos: 3.5,12.5 + parent: 1 + type: Transform +- proto: ClosetEmergencyFilledRandom + entities: + - uid: 418 + components: + - pos: -6.5,-4.5 + parent: 1 + type: Transform + - uid: 419 + components: + - pos: -5.5,6.5 + parent: 1 + type: Transform +- proto: ClosetFireFilled + entities: + - uid: 420 + components: + - pos: 7.5,-4.5 + parent: 1 + type: Transform + - uid: 421 + components: + - pos: 6.5,6.5 + parent: 1 + type: Transform +- proto: ClosetL3VirologyFilled + entities: + - uid: 422 + components: + - pos: 5.5,-11.5 + parent: 1 + type: Transform +- proto: ClosetWallEmergencyFilledRandom + entities: + - uid: 423 + components: + - rot: 3.141592653589793 rad + pos: 7.5,-9.5 + parent: 1 + type: Transform + - uid: 424 + components: + - pos: -2.5,2.5 + parent: 1 + type: Transform +- proto: ClosetWallFireFilledRandom + entities: + - uid: 425 + components: + - rot: 3.141592653589793 rad + pos: 6.5,-9.5 + parent: 1 + type: Transform + - uid: 426 + components: + - pos: 3.5,2.5 + parent: 1 + type: Transform +- proto: ComfyChair + entities: + - uid: 428 + components: + - rot: 1.5707963267948966 rad + pos: -0.5,7.5 + parent: 1 + type: Transform + - uid: 429 + components: + - rot: 1.5707963267948966 rad + pos: -0.5,8.5 + parent: 1 + type: Transform + - uid: 430 + components: + - rot: -1.5707963267948966 rad + pos: 1.5,8.5 + parent: 1 + type: Transform + - uid: 431 + components: + - rot: -1.5707963267948966 rad + pos: 1.5,7.5 + parent: 1 + type: Transform +- proto: ComputerComms + entities: + - uid: 432 + components: + - pos: -0.5,13.5 + parent: 1 + type: Transform +- proto: ComputerCrewMonitoring + entities: + - uid: 433 + components: + - rot: 1.5707963267948966 rad + pos: -3.5,12.5 + parent: 1 + type: Transform +- proto: ComputerCriminalRecords + entities: + - uid: 434 + components: + - pos: 2.5,13.5 + parent: 1 + type: Transform +- proto: ComputerEmergencyShuttle + entities: + - uid: 435 + components: + - pos: 0.5,13.5 + parent: 1 + type: Transform +- proto: ComputerId + entities: + - uid: 436 + components: + - pos: 1.5,13.5 + parent: 1 + type: Transform +- proto: ComputerPowerMonitoring + entities: + - uid: 437 + components: + - rot: -1.5707963267948966 rad + pos: 4.5,12.5 + parent: 1 + type: Transform +- proto: ComputerResearchAndDevelopment + entities: + - uid: 438 + components: + - pos: -1.5,13.5 + parent: 1 + type: Transform +- proto: DefibrillatorCabinetFilled + entities: + - uid: 439 + components: + - pos: 3.5,-6.5 + parent: 1 + type: Transform +- proto: DogBed + entities: + - uid: 440 + components: + - pos: 2.5,7.5 + parent: 1 + type: Transform +- proto: EmergencyLight + entities: + - uid: 441 + components: + - pos: 2.5,9.5 + parent: 1 + type: Transform + - uid: 442 + components: + - rot: 3.141592653589793 rad + pos: -1.5,11.5 + parent: 1 + type: Transform + - uid: 443 + components: + - pos: 2.5,-13.5 + parent: 1 + type: Transform + - uid: 444 + components: + - rot: 1.5707963267948966 rad + pos: -0.5,-8.5 + parent: 1 + type: Transform +- proto: Firelock + entities: + - uid: 445 + components: + - rot: 1.5707963267948966 rad + pos: -0.5,-11.5 + parent: 1 + type: Transform + - uid: 446 + components: + - rot: 1.5707963267948966 rad + pos: 1.5,-11.5 + parent: 1 + type: Transform +- proto: FirelockEdge + entities: + - uid: 447 + components: + - rot: 3.141592653589793 rad + pos: -1.5,-0.5 + parent: 1 + type: Transform + - uid: 448 + components: + - rot: 3.141592653589793 rad + pos: 2.5,-0.5 + parent: 1 + type: Transform + - uid: 449 + components: + - pos: -1.5,-3.5 + parent: 1 + type: Transform + - uid: 450 + components: + - pos: 2.5,-3.5 + parent: 1 + type: Transform + - uid: 451 + components: + - rot: 1.5707963267948966 rad + pos: -1.5,11.5 + parent: 1 + type: Transform + - uid: 452 + components: + - rot: -1.5707963267948966 rad + pos: 2.5,11.5 + parent: 1 + type: Transform +- proto: FirelockGlass + entities: + - uid: 453 + components: + - rot: -1.5707963267948966 rad + pos: -0.5,-6.5 + parent: 1 + type: Transform + - uid: 454 + components: + - rot: -1.5707963267948966 rad + pos: 0.5,-6.5 + parent: 1 + type: Transform + - uid: 455 + components: + - rot: -1.5707963267948966 rad + pos: 1.5,-6.5 + parent: 1 + type: Transform + - uid: 456 + components: + - rot: -1.5707963267948966 rad + pos: 4.5,0.5 + parent: 1 + type: Transform + - uid: 457 + components: + - rot: -1.5707963267948966 rad + pos: 4.5,-4.5 + parent: 1 + type: Transform + - uid: 458 + components: + - rot: -1.5707963267948966 rad + pos: 4.5,-5.5 + parent: 1 + type: Transform + - uid: 459 + components: + - rot: -1.5707963267948966 rad + pos: -3.5,-4.5 + parent: 1 + type: Transform + - uid: 460 + components: + - rot: -1.5707963267948966 rad + pos: -3.5,-5.5 + parent: 1 + type: Transform + - uid: 461 + components: + - rot: -1.5707963267948966 rad + pos: -3.5,0.5 + parent: 1 + type: Transform + - uid: 462 + components: + - rot: -1.5707963267948966 rad + pos: -3.5,1.5 + parent: 1 + type: Transform + - uid: 463 + components: + - rot: -1.5707963267948966 rad + pos: 4.5,1.5 + parent: 1 + type: Transform + - uid: 464 + components: + - pos: -1.5,2.5 + parent: 1 + type: Transform + - uid: 465 + components: + - pos: -0.5,2.5 + parent: 1 + type: Transform + - uid: 466 + components: + - pos: 0.5,2.5 + parent: 1 + type: Transform + - uid: 467 + components: + - rot: 1.5707963267948966 rad + pos: -1.5,-7.5 + parent: 1 + type: Transform + - uid: 468 + components: + - rot: 1.5707963267948966 rad + pos: 2.5,-7.5 + parent: 1 + type: Transform + - uid: 469 + components: + - rot: 1.5707963267948966 rad + pos: -3.5,7.5 + parent: 1 + type: Transform + - uid: 470 + components: + - rot: 1.5707963267948966 rad + pos: 4.5,7.5 + parent: 1 + type: Transform + - uid: 471 + components: + - rot: 1.5707963267948966 rad + pos: 4.5,10.5 + parent: 1 + type: Transform + - uid: 472 + components: + - rot: 1.5707963267948966 rad + pos: -3.5,10.5 + parent: 1 + type: Transform +- proto: FloraTreeLarge06 + entities: + - uid: 473 + components: + - pos: 0.5138967,-2.4482055 + parent: 1 + type: Transform +- proto: FoodCakeBirthday + entities: + - uid: 427 + components: + - pos: 0.49291778,7.754779 + parent: 1 + type: Transform +- proto: GasOutletInjector + entities: + - uid: 474 + components: + - rot: -1.5707963267948966 rad + pos: 3.5,-14.5 + parent: 1 + type: Transform +- proto: GasPassiveVent + entities: + - uid: 475 + components: + - rot: -1.5707963267948966 rad + pos: 4.5,-13.5 + parent: 1 + type: Transform + - color: '#FC6100FF' + type: AtmosPipeColor +- proto: GasPipeBend + entities: + - uid: 476 + components: + - rot: 3.141592653589793 rad + pos: 0.5,-13.5 + parent: 1 + type: Transform + - color: '#FC6100FF' + type: AtmosPipeColor + - uid: 477 + components: + - rot: 1.5707963267948966 rad + pos: -2.5,-7.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 478 + components: + - rot: -1.5707963267948966 rad + pos: -2.5,-10.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 479 + components: + - rot: 1.5707963267948966 rad + pos: -4.5,-10.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 480 + components: + - rot: -1.5707963267948966 rad + pos: 6.5,-5.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 481 + components: + - pos: 3.5,-7.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 482 + components: + - rot: 3.141592653589793 rad + pos: -5.5,-5.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 483 + components: + - pos: 2.5,3.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 484 + components: + - pos: 6.5,2.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 485 + components: + - rot: 3.141592653589793 rad + pos: 5.5,2.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 486 + components: + - rot: 1.5707963267948966 rad + pos: -5.5,2.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 487 + components: + - rot: -1.5707963267948966 rad + pos: -4.5,2.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 488 + components: + - pos: 5.5,5.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 489 + components: + - rot: 3.141592653589793 rad + pos: 4.5,5.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 490 + components: + - rot: 1.5707963267948966 rad + pos: -4.5,5.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 491 + components: + - rot: -1.5707963267948966 rad + pos: -3.5,5.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 492 + components: + - rot: -1.5707963267948966 rad + pos: -3.5,5.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 493 + components: + - rot: 1.5707963267948966 rad + pos: -3.5,11.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 494 + components: + - pos: 4.5,11.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor +- proto: GasPipeFourway + entities: + - uid: 495 + components: + - pos: 0.5,-5.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 496 + components: + - pos: 2.5,1.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor +- proto: GasPipeStraight + entities: + - uid: 497 + components: + - rot: 1.5707963267948966 rad + pos: 3.5,-13.5 + parent: 1 + type: Transform + - color: '#FC6100FF' + type: AtmosPipeColor + - uid: 498 + components: + - rot: 1.5707963267948966 rad + pos: 2.5,-13.5 + parent: 1 + type: Transform + - color: '#FC6100FF' + type: AtmosPipeColor + - uid: 499 + components: + - rot: 3.141592653589793 rad + pos: 1.5,-11.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 500 + components: + - rot: 3.141592653589793 rad + pos: 1.5,-9.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 501 + components: + - pos: 1.5,-8.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 502 + components: + - rot: -1.5707963267948966 rad + pos: -0.5,-7.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 503 + components: + - rot: -1.5707963267948966 rad + pos: -1.5,-7.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 504 + components: + - rot: -1.5707963267948966 rad + pos: -3.5,-8.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 505 + components: + - rot: -1.5707963267948966 rad + pos: -4.5,-8.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 506 + components: + - pos: 0.5,-6.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 507 + components: + - rot: 1.5707963267948966 rad + pos: -3.5,-10.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 508 + components: + - pos: -4.5,-11.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 509 + components: + - pos: -2.5,-9.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 510 + components: + - rot: -1.5707963267948966 rad + pos: 1.5,-5.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 511 + components: + - rot: -1.5707963267948966 rad + pos: 2.5,-5.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 512 + components: + - rot: -1.5707963267948966 rad + pos: 3.5,-5.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 513 + components: + - rot: -1.5707963267948966 rad + pos: 4.5,-5.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 514 + components: + - rot: -1.5707963267948966 rad + pos: 5.5,-5.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 515 + components: + - pos: 3.5,-9.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 516 + components: + - rot: -1.5707963267948966 rad + pos: 2.5,-7.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 517 + components: + - pos: 3.5,-8.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 518 + components: + - rot: -1.5707963267948966 rad + pos: -0.5,-5.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 519 + components: + - rot: 1.5707963267948966 rad + pos: 4.5,-10.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 520 + components: + - rot: 1.5707963267948966 rad + pos: 5.5,-10.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 521 + components: + - pos: 6.5,-3.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 522 + components: + - pos: -1.5,-4.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 523 + components: + - pos: -1.5,-3.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 524 + components: + - pos: -1.5,-2.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 525 + components: + - pos: -1.5,-1.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 526 + components: + - rot: -1.5707963267948966 rad + pos: -2.5,-5.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 527 + components: + - rot: -1.5707963267948966 rad + pos: -3.5,-5.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 528 + components: + - rot: -1.5707963267948966 rad + pos: -4.5,-5.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 529 + components: + - pos: 6.5,-2.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 530 + components: + - pos: 6.5,-1.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 531 + components: + - pos: 6.5,-0.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 532 + components: + - rot: 3.141592653589793 rad + pos: -5.5,-3.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 533 + components: + - rot: 3.141592653589793 rad + pos: -5.5,-2.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 534 + components: + - rot: 3.141592653589793 rad + pos: -5.5,-1.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 535 + components: + - rot: 3.141592653589793 rad + pos: -5.5,-0.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 536 + components: + - pos: 6.5,0.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 537 + components: + - pos: -5.5,0.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 538 + components: + - rot: -1.5707963267948966 rad + pos: -4.5,1.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 539 + components: + - rot: -1.5707963267948966 rad + pos: -3.5,1.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 540 + components: + - rot: -1.5707963267948966 rad + pos: -2.5,1.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 541 + components: + - rot: -1.5707963267948966 rad + pos: -1.5,1.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 542 + components: + - rot: -1.5707963267948966 rad + pos: -0.5,1.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 543 + components: + - rot: -1.5707963267948966 rad + pos: 0.5,1.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 544 + components: + - rot: -1.5707963267948966 rad + pos: 1.5,1.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 545 + components: + - rot: 3.141592653589793 rad + pos: 2.5,0.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 546 + components: + - rot: 3.141592653589793 rad + pos: 2.5,-0.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 547 + components: + - rot: 3.141592653589793 rad + pos: 2.5,-1.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 548 + components: + - rot: 3.141592653589793 rad + pos: 2.5,-2.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 549 + components: + - rot: 1.5707963267948966 rad + pos: 3.5,1.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 550 + components: + - rot: 1.5707963267948966 rad + pos: 4.5,1.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 551 + components: + - rot: 1.5707963267948966 rad + pos: 5.5,1.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 552 + components: + - pos: 2.5,2.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 553 + components: + - rot: -1.5707963267948966 rad + pos: 1.5,3.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 554 + components: + - rot: -1.5707963267948966 rad + pos: 0.5,3.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 555 + components: + - rot: -1.5707963267948966 rad + pos: -0.5,3.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 556 + components: + - pos: 5.5,3.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 557 + components: + - rot: 3.141592653589793 rad + pos: -4.5,3.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 558 + components: + - pos: 4.5,6.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 559 + components: + - pos: 4.5,7.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 560 + components: + - pos: 4.5,8.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 561 + components: + - rot: -1.5707963267948966 rad + pos: 3.5,9.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 562 + components: + - rot: -1.5707963267948966 rad + pos: 1.5,9.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 563 + components: + - rot: -1.5707963267948966 rad + pos: 2.5,9.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 564 + components: + - rot: -1.5707963267948966 rad + pos: -0.5,9.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 565 + components: + - rot: -1.5707963267948966 rad + pos: -1.5,9.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 566 + components: + - rot: -1.5707963267948966 rad + pos: -2.5,9.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 567 + components: + - rot: 3.141592653589793 rad + pos: -3.5,8.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 568 + components: + - rot: 3.141592653589793 rad + pos: -3.5,6.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 569 + components: + - rot: 3.141592653589793 rad + pos: -3.5,7.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 570 + components: + - rot: 3.141592653589793 rad + pos: -3.5,10.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 571 + components: + - rot: 3.141592653589793 rad + pos: 4.5,10.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 572 + components: + - rot: 3.141592653589793 rad + pos: -3.5,10.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 573 + components: + - rot: 3.141592653589793 rad + pos: 0.5,12.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 574 + components: + - rot: 3.141592653589793 rad + pos: -2.5,12.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 575 + components: + - rot: 3.141592653589793 rad + pos: 3.5,12.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 576 + components: + - rot: 1.5707963267948966 rad + pos: -1.5,11.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 577 + components: + - rot: 1.5707963267948966 rad + pos: -0.5,11.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 578 + components: + - rot: 1.5707963267948966 rad + pos: 2.5,11.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 579 + components: + - pos: 4.5,10.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 580 + components: + - rot: -1.5707963267948966 rad + pos: 1.5,11.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor +- proto: GasPipeTJunction + entities: + - uid: 581 + components: + - rot: 3.141592653589793 rad + pos: 1.5,-13.5 + parent: 1 + type: Transform + - color: '#FC6100FF' + type: AtmosPipeColor + - uid: 582 + components: + - rot: -1.5707963267948966 rad + pos: 1.5,-10.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 583 + components: + - rot: 3.141592653589793 rad + pos: 0.5,-7.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 584 + components: + - pos: 1.5,-7.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 585 + components: + - rot: -1.5707963267948966 rad + pos: -2.5,-8.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 586 + components: + - rot: 1.5707963267948966 rad + pos: 3.5,-10.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 587 + components: + - rot: 3.141592653589793 rad + pos: -1.5,-5.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 588 + components: + - rot: -1.5707963267948966 rad + pos: -5.5,-4.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 589 + components: + - rot: -1.5707963267948966 rad + pos: 5.5,4.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 590 + components: + - rot: 1.5707963267948966 rad + pos: -5.5,1.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 591 + components: + - rot: 1.5707963267948966 rad + pos: -4.5,4.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 592 + components: + - rot: -1.5707963267948966 rad + pos: 6.5,1.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 593 + components: + - pos: 0.5,9.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 594 + components: + - rot: -1.5707963267948966 rad + pos: 4.5,9.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 595 + components: + - rot: 1.5707963267948966 rad + pos: -3.5,9.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 596 + components: + - rot: 1.5707963267948966 rad + pos: 6.5,-4.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 597 + components: + - rot: 3.141592653589793 rad + pos: -2.5,11.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 598 + components: + - rot: 3.141592653589793 rad + pos: 0.5,11.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 599 + components: + - rot: 3.141592653589793 rad + pos: 3.5,11.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor +- proto: GasPort + entities: + - uid: 600 + components: + - rot: 1.5707963267948966 rad + pos: 2.5,-14.5 + parent: 1 + type: Transform +- proto: GasPressurePump + entities: + - uid: 601 + components: + - rot: 3.141592653589793 rad + pos: 1.5,-12.5 + parent: 1 + type: Transform + - color: '#FC6100FF' + type: AtmosPipeColor +- proto: GasVentPump + entities: + - uid: 602 + components: + - pos: 0.5,-12.5 + parent: 1 + type: Transform + - color: '#FC6100FF' + type: AtmosPipeColor + - uid: 603 + components: + - rot: 1.5707963267948966 rad + pos: 0.5,-10.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 604 + components: + - rot: 3.141592653589793 rad + pos: -4.5,-12.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 605 + components: + - rot: 1.5707963267948966 rad + pos: -1.5,3.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 606 + components: + - rot: -1.5707963267948966 rad + pos: -3.5,4.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 607 + components: + - rot: 1.5707963267948966 rad + pos: 4.5,4.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 608 + components: + - rot: -1.5707963267948966 rad + pos: 7.5,-4.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 609 + components: + - rot: 1.5707963267948966 rad + pos: -6.5,-4.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 610 + components: + - pos: -1.5,-0.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 611 + components: + - rot: 3.141592653589793 rad + pos: 2.5,-3.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 612 + components: + - rot: 1.5707963267948966 rad + pos: -5.5,-8.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 613 + components: + - rot: -1.5707963267948966 rad + pos: 6.5,-10.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 614 + components: + - rot: 3.141592653589793 rad + pos: 0.5,8.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 615 + components: + - pos: 0.5,13.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 616 + components: + - pos: 3.5,13.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 617 + components: + - pos: -2.5,13.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 618 + components: + - rot: 3.141592653589793 rad + pos: 3.5,-11.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 619 + components: + - pos: 0.5,-4.5 + parent: 1 + type: Transform + - color: '#020F73FF' + type: AtmosPipeColor + - uid: 620 + components: + - rot: 3.141592653589793 rad + pos: 0.5,0.5 + parent: 1 + type: Transform +- proto: GeneratorBasic15kW + entities: + - uid: 621 + components: + - pos: -2.5,-12.5 + parent: 1 + type: Transform + - uid: 622 + components: + - pos: -2.5,-14.5 + parent: 1 + type: Transform +- proto: GeneratorWallmountAPU + entities: + - uid: 623 + components: + - pos: 2.5,-15.5 + parent: 1 + type: Transform + - uid: 624 + components: + - pos: 4.5,-0.5 + parent: 1 + type: Transform + - uid: 625 + components: + - pos: 2.5,10.5 + parent: 1 + type: Transform +- proto: GravityGeneratorMini + entities: + - uid: 626 + components: + - pos: -1.5,-14.5 + parent: 1 + type: Transform +- proto: Grille + entities: + - uid: 627 + components: + - pos: 7.5,5.5 + parent: 1 + type: Transform + - uid: 628 + components: + - pos: -1.5,-10.5 + parent: 1 + type: Transform + - uid: 629 + components: + - rot: -1.5707963267948966 rad + pos: -4.5,13.5 + parent: 1 + type: Transform + - uid: 630 + components: + - rot: -1.5707963267948966 rad + pos: -3.5,14.5 + parent: 1 + type: Transform + - uid: 631 + components: + - pos: -5.5,-9.5 + parent: 1 + type: Transform + - uid: 632 + components: + - rot: 1.5707963267948966 rad + pos: 8.5,1.5 + parent: 1 + type: Transform + - uid: 633 + components: + - rot: 1.5707963267948966 rad + pos: -7.5,1.5 + parent: 1 + type: Transform + - uid: 634 + components: + - rot: 1.5707963267948966 rad + pos: -7.5,-4.5 + parent: 1 + type: Transform + - uid: 635 + components: + - rot: 1.5707963267948966 rad + pos: -7.5,-3.5 + parent: 1 + type: Transform + - uid: 636 + components: + - rot: 1.5707963267948966 rad + pos: -7.5,-2.5 + parent: 1 + type: Transform + - uid: 637 + components: + - rot: 1.5707963267948966 rad + pos: -7.5,-1.5 + parent: 1 + type: Transform + - uid: 638 + components: + - rot: 1.5707963267948966 rad + pos: -7.5,-0.5 + parent: 1 + type: Transform + - uid: 639 + components: + - rot: 1.5707963267948966 rad + pos: 8.5,-0.5 + parent: 1 + type: Transform + - uid: 640 + components: + - rot: 1.5707963267948966 rad + pos: 8.5,-1.5 + parent: 1 + type: Transform + - uid: 641 + components: + - rot: 1.5707963267948966 rad + pos: 8.5,-2.5 + parent: 1 + type: Transform + - uid: 642 + components: + - rot: 1.5707963267948966 rad + pos: 8.5,-3.5 + parent: 1 + type: Transform + - uid: 643 + components: + - rot: 1.5707963267948966 rad + pos: 8.5,-4.5 + parent: 1 + type: Transform + - uid: 644 + components: + - pos: -4.5,-9.5 + parent: 1 + type: Transform + - uid: 645 + components: + - rot: -1.5707963267948966 rad + pos: -3.5,13.5 + parent: 1 + type: Transform + - uid: 646 + components: + - rot: -1.5707963267948966 rad + pos: -4.5,12.5 + parent: 1 + type: Transform + - uid: 647 + components: + - rot: -1.5707963267948966 rad + pos: -1.5,14.5 + parent: 1 + type: Transform + - uid: 648 + components: + - rot: 1.5707963267948966 rad + pos: 1.5,14.5 + parent: 1 + type: Transform + - uid: 649 + components: + - rot: 1.5707963267948966 rad + pos: 0.5,14.5 + parent: 1 + type: Transform + - uid: 650 + components: + - rot: 1.5707963267948966 rad + pos: -0.5,14.5 + parent: 1 + type: Transform + - uid: 651 + components: + - pos: -1.5,-9.5 + parent: 1 + type: Transform + - uid: 652 + components: + - rot: -1.5707963267948966 rad + pos: -2.5,14.5 + parent: 1 + type: Transform + - uid: 653 + components: + - pos: -5.5,8.5 + parent: 1 + type: Transform + - uid: 654 + components: + - pos: -5.5,9.5 + parent: 1 + type: Transform + - uid: 655 + components: + - pos: -0.5,-15.5 + parent: 1 + type: Transform + - uid: 656 + components: + - pos: 0.5,-15.5 + parent: 1 + type: Transform + - uid: 657 + components: + - pos: 1.5,-15.5 + parent: 1 + type: Transform + - uid: 658 + components: + - pos: 7.5,4.5 + parent: 1 + type: Transform + - uid: 659 + components: + - pos: -6.5,5.5 + parent: 1 + type: Transform + - uid: 660 + components: + - pos: -6.5,4.5 + parent: 1 + type: Transform + - uid: 661 + components: + - pos: 2.5,-10.5 + parent: 1 + type: Transform + - uid: 662 + components: + - pos: 2.5,-9.5 + parent: 1 + type: Transform + - uid: 663 + components: + - pos: 6.5,8.5 + parent: 1 + type: Transform + - uid: 664 + components: + - pos: 6.5,9.5 + parent: 1 + type: Transform + - uid: 665 + components: + - rot: -1.5707963267948966 rad + pos: 2.5,14.5 + parent: 1 + type: Transform + - uid: 666 + components: + - rot: -1.5707963267948966 rad + pos: 3.5,14.5 + parent: 1 + type: Transform + - uid: 667 + components: + - rot: -1.5707963267948966 rad + pos: 4.5,14.5 + parent: 1 + type: Transform + - uid: 668 + components: + - rot: -1.5707963267948966 rad + pos: 4.5,13.5 + parent: 1 + type: Transform + - uid: 669 + components: + - rot: -1.5707963267948966 rad + pos: 5.5,13.5 + parent: 1 + type: Transform + - uid: 670 + components: + - rot: -1.5707963267948966 rad + pos: 5.5,12.5 + parent: 1 + type: Transform + - uid: 671 + components: + - rot: 3.141592653589793 rad + pos: 1.5,10.5 + parent: 1 + type: Transform + - uid: 672 + components: + - rot: 3.141592653589793 rad + pos: 0.5,10.5 + parent: 1 + type: Transform + - uid: 673 + components: + - rot: 3.141592653589793 rad + pos: -0.5,10.5 + parent: 1 + type: Transform +- proto: Gyroscope + entities: + - uid: 674 + components: + - rot: -1.5707963267948966 rad + pos: -1.5,-16.5 + parent: 1 + type: Transform + - uid: 675 + components: + - rot: -1.5707963267948966 rad + pos: 2.5,-16.5 + parent: 1 + type: Transform +- proto: HospitalCurtainsOpen + entities: + - uid: 676 + components: + - rot: 1.5707963267948966 rad + pos: -4.5,-12.5 + parent: 1 + type: Transform + - uid: 677 + components: + - rot: 3.141592653589793 rad + pos: 4.5,-7.5 + parent: 1 + type: Transform + - uid: 678 + components: + - rot: 3.141592653589793 rad + pos: 4.5,-9.5 + parent: 1 + type: Transform +- proto: LockerBoozeFilled + entities: + - uid: 679 + components: + - pos: 2.5,5.5 + parent: 1 + type: Transform +- proto: LockerElectricalSuppliesFilled + entities: + - uid: 680 + components: + - pos: 2.5,-13.5 + parent: 1 + type: Transform +- proto: LockerEvidence + entities: + - uid: 681 + components: + - pos: -3.5,-8.5 + parent: 1 + type: Transform +- proto: LockerMedicineFilled + entities: + - uid: 682 + components: + - pos: 3.5,-11.5 + parent: 1 + type: Transform +- proto: MedkitCombatFilled + entities: + - uid: 683 + components: + - pos: -2.5,13.5 + parent: 1 + type: Transform +- proto: PetCarrier + entities: + - uid: 684 + components: + - pos: -1.5,7.5 + parent: 1 + type: Transform +- proto: PlasmaReinforcedWindowDirectional + entities: + - uid: 685 + components: + - rot: -1.5707963267948966 rad + pos: 3.5,-14.5 + parent: 1 + type: Transform + - uid: 686 + components: + - rot: -1.5707963267948966 rad + pos: 3.5,-13.5 + parent: 1 + type: Transform +- proto: PlasmaWindowDirectional + entities: + - uid: 687 + components: + - rot: -1.5707963267948966 rad + pos: 5.5,-11.5 + parent: 1 + type: Transform +- proto: PottedPlantRandom + entities: + - uid: 688 + components: + - pos: 4.5,4.5 + parent: 1 + type: Transform + - uid: 689 + components: + - pos: -3.5,4.5 + parent: 1 + type: Transform + - uid: 690 + components: + - pos: -0.5,12.5 + parent: 1 + type: Transform + - uid: 691 + components: + - pos: 1.5,12.5 + parent: 1 + type: Transform + - uid: 692 + components: + - pos: 3.5,8.5 + parent: 1 + type: Transform + - uid: 693 + components: + - pos: 7.5,-6.5 + parent: 1 + type: Transform + - uid: 694 + components: + - pos: -2.5,8.5 + parent: 1 + type: Transform + - uid: 695 + components: + - pos: -6.5,1.5 + parent: 1 + type: Transform + - uid: 696 + components: + - pos: 7.5,1.5 + parent: 1 + type: Transform + - uid: 697 + components: + - pos: 0.5,-10.5 + parent: 1 + type: Transform +- proto: PowerCellRecharger + entities: + - uid: 698 + components: + - rot: 1.5707963267948966 rad + pos: 0.5,-12.5 + parent: 1 + type: Transform +- proto: Poweredlight + entities: + - uid: 699 + components: + - rot: 3.141592653589793 rad + pos: -2.5,11.5 + parent: 1 + type: Transform + - uid: 700 + components: + - pos: 6.5,6.5 + parent: 1 + type: Transform + - uid: 701 + components: + - pos: -5.5,6.5 + parent: 1 + type: Transform + - uid: 702 + components: + - rot: 3.141592653589793 rad + pos: 3.5,11.5 + parent: 1 + type: Transform + - uid: 703 + components: + - pos: -1.5,5.5 + parent: 1 + type: Transform + - uid: 704 + components: + - rot: 3.141592653589793 rad + pos: 1.5,3.5 + parent: 1 + type: Transform + - uid: 705 + components: + - pos: -4.5,9.5 + parent: 1 + type: Transform + - uid: 706 + components: + - pos: 5.5,9.5 + parent: 1 + type: Transform + - uid: 707 + components: + - rot: 3.141592653589793 rad + pos: 0.5,-10.5 + parent: 1 + type: Transform + - uid: 708 + components: + - pos: -2.5,1.5 + parent: 1 + type: Transform + - uid: 709 + components: + - pos: 3.5,1.5 + parent: 1 + type: Transform + - uid: 710 + components: + - rot: 3.141592653589793 rad + pos: -2.5,-5.5 + parent: 1 + type: Transform + - uid: 711 + components: + - rot: 3.141592653589793 rad + pos: 3.5,-5.5 + parent: 1 + type: Transform + - uid: 712 + components: + - rot: 1.5707963267948966 rad + pos: -6.5,-2.5 + parent: 1 + type: Transform + - uid: 713 + components: + - rot: -1.5707963267948966 rad + pos: 7.5,-2.5 + parent: 1 + type: Transform + - uid: 714 + components: + - rot: 1.5707963267948966 rad + pos: 6.5,-7.5 + parent: 1 + type: Transform + - uid: 715 + components: + - rot: 1.5707963267948966 rad + pos: 3.5,-8.5 + parent: 1 + type: Transform + - uid: 716 + components: + - rot: -1.5707963267948966 rad + pos: 6.5,-11.5 + parent: 1 + type: Transform + - uid: 717 + components: + - pos: -4.5,-7.5 + parent: 1 + type: Transform +- proto: PoweredLightPostSmall + entities: + - uid: 718 + components: + - pos: 0.5,-1.5 + parent: 1 + type: Transform +- proto: PoweredSmallLight + entities: + - uid: 719 + components: + - rot: -1.5707963267948966 rad + pos: 4.5,-13.5 + parent: 1 + type: Transform + - uid: 720 + components: + - rot: 1.5707963267948966 rad + pos: -2.5,-13.5 + parent: 1 + type: Transform + - uid: 721 + components: + - pos: 0.5,-12.5 + parent: 1 + type: Transform + - uid: 722 + components: + - rot: -1.5707963267948966 rad + pos: 2.5,5.5 + parent: 1 + type: Transform + - uid: 723 + components: + - rot: 1.5707963267948966 rad + pos: -5.5,-10.5 + parent: 1 + type: Transform +- proto: RandomDrinkBottle + entities: + - uid: 724 + components: + - pos: 0.5,2.5 + parent: 1 + type: Transform +- proto: RandomDrinkGlass + entities: + - uid: 725 + components: + - pos: -1.5,2.5 + parent: 1 + type: Transform +- proto: RandomFoodMeal + entities: + - uid: 726 + components: + - pos: -3.5,3.5 + parent: 1 + type: Transform + - uid: 727 + components: + - pos: 4.5,3.5 + parent: 1 + type: Transform +- proto: ShuttersNormalOpen + entities: + - uid: 728 + components: + - pos: -1.5,2.5 + parent: 1 + type: Transform + - links: + - 779 + type: DeviceLinkSink + - uid: 729 + components: + - pos: -0.5,2.5 + parent: 1 + type: Transform + - links: + - 779 + type: DeviceLinkSink + - uid: 730 + components: + - pos: 0.5,2.5 + parent: 1 + type: Transform + - links: + - 779 + type: DeviceLinkSink +- proto: ShuttleWindow + entities: + - uid: 731 + components: + - rot: -1.5707963267948966 rad + pos: -7.5,-4.5 + parent: 1 + type: Transform + - uid: 732 + components: + - rot: -1.5707963267948966 rad + pos: -7.5,-3.5 + parent: 1 + type: Transform + - uid: 733 + components: + - rot: -1.5707963267948966 rad + pos: -7.5,-2.5 + parent: 1 + type: Transform + - uid: 734 + components: + - rot: -1.5707963267948966 rad + pos: -7.5,-1.5 + parent: 1 + type: Transform + - uid: 735 + components: + - rot: -1.5707963267948966 rad + pos: -7.5,-0.5 + parent: 1 + type: Transform + - uid: 736 + components: + - rot: -1.5707963267948966 rad + pos: 8.5,-4.5 + parent: 1 + type: Transform + - uid: 737 + components: + - rot: -1.5707963267948966 rad + pos: 8.5,-3.5 + parent: 1 + type: Transform + - uid: 738 + components: + - rot: -1.5707963267948966 rad + pos: 8.5,-2.5 + parent: 1 + type: Transform + - uid: 739 + components: + - rot: -1.5707963267948966 rad + pos: 8.5,-1.5 + parent: 1 + type: Transform + - uid: 740 + components: + - rot: -1.5707963267948966 rad + pos: 8.5,-0.5 + parent: 1 + type: Transform + - uid: 741 + components: + - rot: -1.5707963267948966 rad + pos: 8.5,1.5 + parent: 1 + type: Transform + - uid: 742 + components: + - rot: -1.5707963267948966 rad + pos: 1.5,-15.5 + parent: 1 + type: Transform + - uid: 743 + components: + - rot: -1.5707963267948966 rad + pos: 0.5,-15.5 + parent: 1 + type: Transform + - uid: 744 + components: + - rot: -1.5707963267948966 rad + pos: -0.5,-15.5 + parent: 1 + type: Transform + - uid: 745 + components: + - pos: -6.5,5.5 + parent: 1 + type: Transform + - uid: 746 + components: + - pos: 7.5,5.5 + parent: 1 + type: Transform + - uid: 747 + components: + - pos: 7.5,4.5 + parent: 1 + type: Transform + - uid: 748 + components: + - rot: -1.5707963267948966 rad + pos: -0.5,14.5 + parent: 1 + type: Transform + - uid: 749 + components: + - rot: -1.5707963267948966 rad + pos: 0.5,14.5 + parent: 1 + type: Transform + - uid: 750 + components: + - rot: -1.5707963267948966 rad + pos: 1.5,14.5 + parent: 1 + type: Transform + - uid: 751 + components: + - rot: -1.5707963267948966 rad + pos: 5.5,12.5 + parent: 1 + type: Transform + - uid: 752 + components: + - rot: 1.5707963267948966 rad + pos: -7.5,1.5 + parent: 1 + type: Transform + - uid: 753 + components: + - pos: 6.5,9.5 + parent: 1 + type: Transform + - uid: 754 + components: + - rot: -1.5707963267948966 rad + pos: -1.5,14.5 + parent: 1 + type: Transform + - uid: 755 + components: + - rot: -1.5707963267948966 rad + pos: -2.5,14.5 + parent: 1 + type: Transform + - uid: 756 + components: + - rot: -1.5707963267948966 rad + pos: -3.5,14.5 + parent: 1 + type: Transform + - uid: 757 + components: + - rot: -1.5707963267948966 rad + pos: 2.5,14.5 + parent: 1 + type: Transform + - uid: 758 + components: + - rot: -1.5707963267948966 rad + pos: 3.5,14.5 + parent: 1 + type: Transform + - uid: 759 + components: + - rot: -1.5707963267948966 rad + pos: 4.5,14.5 + parent: 1 + type: Transform + - uid: 760 + components: + - rot: -1.5707963267948966 rad + pos: 4.5,13.5 + parent: 1 + type: Transform + - uid: 761 + components: + - rot: -1.5707963267948966 rad + pos: 5.5,13.5 + parent: 1 + type: Transform + - uid: 762 + components: + - pos: 6.5,8.5 + parent: 1 + type: Transform + - uid: 763 + components: + - rot: -1.5707963267948966 rad + pos: -4.5,12.5 + parent: 1 + type: Transform + - uid: 764 + components: + - rot: -1.5707963267948966 rad + pos: -4.5,13.5 + parent: 1 + type: Transform + - uid: 765 + components: + - rot: -1.5707963267948966 rad + pos: -3.5,13.5 + parent: 1 + type: Transform + - uid: 766 + components: + - rot: -1.5707963267948966 rad + pos: -4.5,-9.5 + parent: 1 + type: Transform + - uid: 767 + components: + - rot: -1.5707963267948966 rad + pos: -5.5,-9.5 + parent: 1 + type: Transform + - uid: 768 + components: + - rot: -1.5707963267948966 rad + pos: -1.5,-9.5 + parent: 1 + type: Transform + - uid: 769 + components: + - rot: -1.5707963267948966 rad + pos: -1.5,-10.5 + parent: 1 + type: Transform + - uid: 770 + components: + - rot: -1.5707963267948966 rad + pos: 2.5,-9.5 + parent: 1 + type: Transform + - uid: 771 + components: + - rot: -1.5707963267948966 rad + pos: 2.5,-10.5 + parent: 1 + type: Transform + - uid: 772 + components: + - pos: -6.5,4.5 + parent: 1 + type: Transform + - uid: 773 + components: + - pos: -5.5,9.5 + parent: 1 + type: Transform + - uid: 774 + components: + - pos: -5.5,8.5 + parent: 1 + type: Transform + - uid: 775 + components: + - rot: 3.141592653589793 rad + pos: 1.5,10.5 + parent: 1 + type: Transform + - uid: 776 + components: + - rot: 3.141592653589793 rad + pos: 0.5,10.5 + parent: 1 + type: Transform + - uid: 777 + components: + - rot: 3.141592653589793 rad + pos: -0.5,10.5 + parent: 1 + type: Transform +- proto: SignalButtonDirectional + entities: + - uid: 778 + components: + - pos: -1.5,10.5 + parent: 1 + type: Transform + - linkedPorts: + 6: + - Pressed: DoorBolt + 7: + - Pressed: DoorBolt + type: DeviceLinkSource + - uid: 779 + components: + - pos: 0.5,6.5 + parent: 1 + type: Transform + - linkedPorts: + 728: + - Pressed: Toggle + 729: + - Pressed: Toggle + 730: + - Pressed: Toggle + type: DeviceLinkSource + - uid: 780 + components: + - rot: 3.141592653589793 rad + pos: -1.5,10.5 + parent: 1 + type: Transform + - linkedPorts: + 44: + - Pressed: Toggle + 45: + - Pressed: Toggle + 46: + - Pressed: Toggle + 8: + - Pressed: DoorBolt + 9: + - Pressed: DoorBolt + type: DeviceLinkSource +- proto: SignBio + entities: + - uid: 781 + components: + - pos: 7.5,-10.5 + parent: 1 + type: Transform +- proto: SignEngineering + entities: + - uid: 782 + components: + - rot: 1.5707963267948966 rad + pos: 0.5,-11.5 + parent: 1 + type: Transform +- proto: SignMedical + entities: + - uid: 783 + components: + - pos: 2.5,-6.5 + parent: 1 + type: Transform +- proto: SignPrison + entities: + - uid: 784 + components: + - rot: -1.5707963267948966 rad + pos: -3.5,-9.5 + parent: 1 + type: Transform +- proto: SignShield + entities: + - uid: 785 + components: + - pos: -1.5,-6.5 + parent: 1 + type: Transform +- proto: SinkWide + entities: + - uid: 786 + components: + - rot: 1.5707963267948966 rad + pos: -1.5,4.5 + parent: 1 + type: Transform + - uid: 787 + components: + - rot: -1.5707963267948966 rad + pos: 6.5,-10.5 + parent: 1 + type: Transform +- proto: SMESBasic + entities: + - uid: 788 + components: + - pos: -1.5,-12.5 + parent: 1 + type: Transform +- proto: soda_dispenser + entities: + - uid: 789 + components: + - pos: -0.5,5.5 + parent: 1 + type: Transform +- proto: StasisBed + entities: + - uid: 790 + components: + - pos: 4.5,-8.5 + parent: 1 + type: Transform +- proto: StoolBar + entities: + - uid: 791 + components: + - rot: 3.141592653589793 rad + pos: -1.5,1.5 + parent: 1 + type: Transform + - uid: 792 + components: + - rot: 3.141592653589793 rad + pos: -0.5,1.5 + parent: 1 + type: Transform + - uid: 793 + components: + - rot: 3.141592653589793 rad + pos: 0.5,1.5 + parent: 1 + type: Transform +- proto: SubstationWallBasic + entities: + - uid: 794 + components: + - pos: -2.5,10.5 + parent: 1 + type: Transform + - uid: 795 + components: + - pos: 2.5,-12.5 + parent: 1 + type: Transform + - uid: 796 + components: + - rot: 3.141592653589793 rad + pos: -3.5,-0.5 + parent: 1 + type: Transform +- proto: Table + entities: + - uid: 797 + components: + - rot: 1.5707963267948966 rad + pos: 0.5,-12.5 + parent: 1 + type: Transform + - uid: 798 + components: + - rot: -1.5707963267948966 rad + pos: -3.5,3.5 + parent: 1 + type: Transform + - uid: 799 + components: + - rot: -1.5707963267948966 rad + pos: 4.5,3.5 + parent: 1 + type: Transform +- proto: TableCounterWood + entities: + - uid: 800 + components: + - pos: -1.5,2.5 + parent: 1 + type: Transform + - uid: 801 + components: + - pos: -0.5,2.5 + parent: 1 + type: Transform + - uid: 802 + components: + - pos: 0.5,2.5 + parent: 1 + type: Transform +- proto: TableReinforced + entities: + - uid: 803 + components: + - rot: 1.5707963267948966 rad + pos: -2.5,13.5 + parent: 1 + type: Transform + - uid: 804 + components: + - rot: -1.5707963267948966 rad + pos: 3.5,13.5 + parent: 1 + type: Transform +- proto: TableWood + entities: + - uid: 805 + components: + - pos: -1.5,5.5 + parent: 1 + type: Transform + - uid: 806 + components: + - pos: -0.5,5.5 + parent: 1 + type: Transform + - uid: 807 + components: + - rot: 3.141592653589793 rad + pos: 0.5,7.5 + parent: 1 + type: Transform + - uid: 808 + components: + - rot: 3.141592653589793 rad + pos: 0.5,8.5 + parent: 1 + type: Transform +- proto: Thruster + entities: + - uid: 809 + components: + - rot: 1.5707963267948966 rad + pos: -7.5,-10.5 + parent: 1 + type: Transform + - uid: 810 + components: + - rot: 1.5707963267948966 rad + pos: -7.5,-11.5 + parent: 1 + type: Transform + - uid: 811 + components: + - rot: 3.141592653589793 rad + pos: -7.5,-12.5 + parent: 1 + type: Transform + - uid: 812 + components: + - rot: 3.141592653589793 rad + pos: -5.5,-14.5 + parent: 1 + type: Transform + - uid: 813 + components: + - rot: 3.141592653589793 rad + pos: -4.5,-15.5 + parent: 1 + type: Transform + - uid: 814 + components: + - rot: 3.141592653589793 rad + pos: -3.5,-16.5 + parent: 1 + type: Transform + - uid: 815 + components: + - rot: 3.141592653589793 rad + pos: -2.5,-16.5 + parent: 1 + type: Transform + - uid: 816 + components: + - rot: 3.141592653589793 rad + pos: 3.5,-16.5 + parent: 1 + type: Transform + - uid: 817 + components: + - rot: 3.141592653589793 rad + pos: 4.5,-16.5 + parent: 1 + type: Transform + - uid: 818 + components: + - rot: 3.141592653589793 rad + pos: 5.5,-15.5 + parent: 1 + type: Transform + - uid: 819 + components: + - rot: 3.141592653589793 rad + pos: 6.5,-14.5 + parent: 1 + type: Transform + - uid: 820 + components: + - rot: 3.141592653589793 rad + pos: 8.5,-12.5 + parent: 1 + type: Transform + - uid: 821 + components: + - rot: -1.5707963267948966 rad + pos: 8.5,-11.5 + parent: 1 + type: Transform + - uid: 822 + components: + - rot: -1.5707963267948966 rad + pos: 8.5,-10.5 + parent: 1 + type: Transform + - uid: 823 + components: + - rot: -1.5707963267948966 rad + pos: 8.5,4.5 + parent: 1 + type: Transform + - uid: 824 + components: + - pos: 8.5,5.5 + parent: 1 + type: Transform + - uid: 825 + components: + - pos: -7.5,5.5 + parent: 1 + type: Transform + - uid: 826 + components: + - rot: 1.5707963267948966 rad + pos: -7.5,4.5 + parent: 1 + type: Transform + - uid: 827 + components: + - rot: 1.5707963267948966 rad + pos: -5.5,11.5 + parent: 1 + type: Transform + - uid: 828 + components: + - pos: -5.5,12.5 + parent: 1 + type: Transform + - uid: 829 + components: + - pos: 6.5,12.5 + parent: 1 + type: Transform + - uid: 830 + components: + - rot: -1.5707963267948966 rad + pos: 6.5,11.5 + parent: 1 + type: Transform +- proto: ToiletEmpty + entities: + - uid: 831 + components: + - rot: 1.5707963267948966 rad + pos: -4.5,-12.5 + parent: 1 + type: Transform +- proto: ToyFigurineClown + entities: + - uid: 832 + components: + - pos: 0.18010247,-3.0342817 + parent: 1 + type: Transform +- proto: ToyFigurineGreytider + entities: + - uid: 833 + components: + - pos: 7.5,-13.5 + parent: 1 + type: Transform +- proto: ToyFigurineMime + entities: + - uid: 834 + components: + - pos: 0.7675153,-2.929387 + parent: 1 + type: Transform +- proto: ToyFigurineNukieCommander + entities: + - uid: 835 + components: + - pos: -6.5,-13.5 + parent: 1 + type: Transform +- proto: ToyRubberDuck + entities: + - uid: 836 + components: + - pos: -4.5,-12.5 + parent: 1 + type: Transform +- proto: ToySpawner + entities: + - uid: 837 + components: + - pos: 0.5,12.5 + parent: 1 + type: Transform + - uid: 838 + components: + - pos: 2.5,7.5 + parent: 1 + type: Transform +- proto: VendingMachineBooze + entities: + - uid: 839 + components: + - pos: 1.5,4.5 + parent: 1 + type: Transform +- proto: VendingMachineCigs + entities: + - uid: 841 + components: + - pos: -4.5,8.5 + parent: 1 + type: Transform +- proto: VendingMachineDiscount + entities: + - uid: 842 + components: + - pos: 5.5,8.5 + parent: 1 + type: Transform +- proto: VendingMachineSec + entities: + - uid: 843 + components: + - pos: -6.5,-8.5 + parent: 1 + type: Transform +- proto: VendingMachineWallMedical + entities: + - uid: 844 + components: + - rot: 1.5707963267948966 rad + pos: 2.5,-8.5 + parent: 1 + type: Transform + - uid: 845 + components: + - rot: 1.5707963267948966 rad + pos: 2.5,-8.5 + parent: 1 + type: Transform +- proto: VendingMachineWinter + entities: + - uid: 846 + components: + - pos: -4.5,9.5 + parent: 1 + type: Transform +- proto: WallShuttle + entities: + - uid: 847 + components: + - rot: 1.5707963267948966 rad + pos: -5.5,-6.5 + parent: 1 + type: Transform + - uid: 848 + components: + - rot: -1.5707963267948966 rad + pos: -7.5,-8.5 + parent: 1 + type: Transform + - uid: 849 + components: + - rot: -1.5707963267948966 rad + pos: 8.5,-8.5 + parent: 1 + type: Transform + - uid: 850 + components: + - rot: -1.5707963267948966 rad + pos: -6.5,-9.5 + parent: 1 + type: Transform + - uid: 851 + components: + - rot: -1.5707963267948966 rad + pos: -6.5,-10.5 + parent: 1 + type: Transform + - uid: 852 + components: + - rot: -1.5707963267948966 rad + pos: -6.5,-11.5 + parent: 1 + type: Transform + - uid: 853 + components: + - rot: -1.5707963267948966 rad + pos: -6.5,-12.5 + parent: 1 + type: Transform + - uid: 854 + components: + - rot: -1.5707963267948966 rad + pos: -5.5,-12.5 + parent: 1 + type: Transform + - uid: 855 + components: + - rot: -1.5707963267948966 rad + pos: -5.5,-13.5 + parent: 1 + type: Transform + - uid: 856 + components: + - rot: -1.5707963267948966 rad + pos: -4.5,-13.5 + parent: 1 + type: Transform + - uid: 857 + components: + - rot: -1.5707963267948966 rad + pos: -4.5,-14.5 + parent: 1 + type: Transform + - uid: 858 + components: + - rot: -1.5707963267948966 rad + pos: -3.5,-14.5 + parent: 1 + type: Transform + - uid: 859 + components: + - rot: -1.5707963267948966 rad + pos: -3.5,-15.5 + parent: 1 + type: Transform + - uid: 860 + components: + - rot: -1.5707963267948966 rad + pos: -2.5,-15.5 + parent: 1 + type: Transform + - uid: 861 + components: + - rot: -1.5707963267948966 rad + pos: -1.5,-15.5 + parent: 1 + type: Transform + - uid: 862 + components: + - rot: -1.5707963267948966 rad + pos: 2.5,-15.5 + parent: 1 + type: Transform + - uid: 863 + components: + - rot: -1.5707963267948966 rad + pos: 3.5,-15.5 + parent: 1 + type: Transform + - uid: 864 + components: + - rot: -1.5707963267948966 rad + pos: 4.5,-15.5 + parent: 1 + type: Transform + - uid: 865 + components: + - rot: -1.5707963267948966 rad + pos: 4.5,-14.5 + parent: 1 + type: Transform + - uid: 866 + components: + - rot: -1.5707963267948966 rad + pos: 5.5,-14.5 + parent: 1 + type: Transform + - uid: 867 + components: + - rot: -1.5707963267948966 rad + pos: 5.5,-13.5 + parent: 1 + type: Transform + - uid: 868 + components: + - rot: -1.5707963267948966 rad + pos: 6.5,-13.5 + parent: 1 + type: Transform + - uid: 869 + components: + - rot: -1.5707963267948966 rad + pos: 6.5,-12.5 + parent: 1 + type: Transform + - uid: 870 + components: + - rot: -1.5707963267948966 rad + pos: 7.5,-12.5 + parent: 1 + type: Transform + - uid: 871 + components: + - rot: -1.5707963267948966 rad + pos: 7.5,-11.5 + parent: 1 + type: Transform + - uid: 872 + components: + - rot: -1.5707963267948966 rad + pos: 7.5,-10.5 + parent: 1 + type: Transform + - uid: 873 + components: + - rot: -1.5707963267948966 rad + pos: 7.5,-9.5 + parent: 1 + type: Transform + - uid: 874 + components: + - rot: -1.5707963267948966 rad + pos: -6.5,3.5 + parent: 1 + type: Transform + - uid: 875 + components: + - rot: -1.5707963267948966 rad + pos: -6.5,6.5 + parent: 1 + type: Transform + - uid: 876 + components: + - rot: -1.5707963267948966 rad + pos: 7.5,3.5 + parent: 1 + type: Transform + - uid: 877 + components: + - rot: -1.5707963267948966 rad + pos: 7.5,6.5 + parent: 1 + type: Transform + - uid: 878 + components: + - rot: -1.5707963267948966 rad + pos: 3.5,4.5 + parent: 1 + type: Transform + - uid: 879 + components: + - rot: -1.5707963267948966 rad + pos: -1.5,6.5 + parent: 1 + type: Transform + - uid: 880 + components: + - rot: -1.5707963267948966 rad + pos: 2.5,6.5 + parent: 1 + type: Transform + - uid: 881 + components: + - rot: 1.5707963267948966 rad + pos: -6.5,-6.5 + parent: 1 + type: Transform + - uid: 882 + components: + - rot: 3.141592653589793 rad + pos: -4.5,10.5 + parent: 1 + type: Transform + - uid: 883 + components: + - rot: 3.141592653589793 rad + pos: 5.5,10.5 + parent: 1 + type: Transform + - uid: 884 + components: + - rot: -1.5707963267948966 rad + pos: 6.5,7.5 + parent: 1 + type: Transform + - uid: 885 + components: + - rot: -1.5707963267948966 rad + pos: -5.5,7.5 + parent: 1 + type: Transform + - uid: 886 + components: + - rot: 1.5707963267948966 rad + pos: -7.5,-6.5 + parent: 1 + type: Transform + - uid: 887 + components: + - rot: -1.5707963267948966 rad + pos: -4.5,11.5 + parent: 1 + type: Transform + - uid: 888 + components: + - rot: 1.5707963267948966 rad + pos: -4.5,-6.5 + parent: 1 + type: Transform + - uid: 889 + components: + - rot: 1.5707963267948966 rad + pos: -2.5,-6.5 + parent: 1 + type: Transform + - uid: 890 + components: + - rot: 1.5707963267948966 rad + pos: -3.5,-6.5 + parent: 1 + type: Transform + - uid: 891 + components: + - rot: 1.5707963267948966 rad + pos: -1.5,-6.5 + parent: 1 + type: Transform + - uid: 892 + components: + - rot: 1.5707963267948966 rad + pos: 5.5,-8.5 + parent: 1 + type: Transform + - uid: 893 + components: + - rot: -1.5707963267948966 rad + pos: 5.5,-6.5 + parent: 1 + type: Transform + - uid: 894 + components: + - rot: -1.5707963267948966 rad + pos: 4.5,-6.5 + parent: 1 + type: Transform + - uid: 895 + components: + - rot: -1.5707963267948966 rad + pos: 3.5,-6.5 + parent: 1 + type: Transform + - uid: 896 + components: + - rot: -1.5707963267948966 rad + pos: 2.5,-6.5 + parent: 1 + type: Transform + - uid: 897 + components: + - rot: 1.5707963267948966 rad + pos: 5.5,-7.5 + parent: 1 + type: Transform + - uid: 898 + components: + - rot: 1.5707963267948966 rad + pos: 2.5,-8.5 + parent: 1 + type: Transform + - uid: 899 + components: + - rot: 1.5707963267948966 rad + pos: 5.5,-9.5 + parent: 1 + type: Transform + - uid: 900 + components: + - rot: 1.5707963267948966 rad + pos: 6.5,-9.5 + parent: 1 + type: Transform + - uid: 901 + components: + - rot: 1.5707963267948966 rad + pos: 2.5,-11.5 + parent: 1 + type: Transform + - uid: 902 + components: + - rot: 1.5707963267948966 rad + pos: 2.5,-12.5 + parent: 1 + type: Transform + - uid: 903 + components: + - rot: 3.141592653589793 rad + pos: -3.5,-9.5 + parent: 1 + type: Transform + - uid: 904 + components: + - rot: -1.5707963267948966 rad + pos: -1.5,-11.5 + parent: 1 + type: Transform + - uid: 905 + components: + - rot: -1.5707963267948966 rad + pos: -2.5,-11.5 + parent: 1 + type: Transform + - uid: 906 + components: + - rot: -1.5707963267948966 rad + pos: -3.5,-11.5 + parent: 1 + type: Transform + - uid: 907 + components: + - rot: -1.5707963267948966 rad + pos: -3.5,-12.5 + parent: 1 + type: Transform + - uid: 908 + components: + - rot: -1.5707963267948966 rad + pos: -3.5,-13.5 + parent: 1 + type: Transform + - uid: 909 + components: + - rot: -1.5707963267948966 rad + pos: -1.5,-8.5 + parent: 1 + type: Transform + - uid: 910 + components: + - rot: 3.141592653589793 rad + pos: 4.5,-12.5 + parent: 1 + type: Transform + - uid: 911 + components: + - rot: 3.141592653589793 rad + pos: 5.5,-12.5 + parent: 1 + type: Transform + - uid: 912 + components: + - rot: 3.141592653589793 rad + pos: 3.5,-12.5 + parent: 1 + type: Transform + - uid: 913 + components: + - pos: 0.5,-0.5 + parent: 1 + type: Transform + - uid: 914 + components: + - rot: -1.5707963267948966 rad + pos: -3.5,-3.5 + parent: 1 + type: Transform + - uid: 915 + components: + - rot: -1.5707963267948966 rad + pos: 4.5,-3.5 + parent: 1 + type: Transform + - uid: 916 + components: + - rot: -1.5707963267948966 rad + pos: 4.5,-0.5 + parent: 1 + type: Transform + - uid: 917 + components: + - rot: -1.5707963267948966 rad + pos: -3.5,-0.5 + parent: 1 + type: Transform + - uid: 918 + components: + - rot: 3.141592653589793 rad + pos: -3.5,2.5 + parent: 1 + type: Transform + - uid: 919 + components: + - rot: -1.5707963267948966 rad + pos: 3.5,2.5 + parent: 1 + type: Transform + - uid: 920 + components: + - rot: 3.141592653589793 rad + pos: 2.5,4.5 + parent: 1 + type: Transform + - uid: 921 + components: + - rot: -1.5707963267948966 rad + pos: 3.5,5.5 + parent: 1 + type: Transform + - uid: 922 + components: + - rot: 3.141592653589793 rad + pos: 3.5,6.5 + parent: 1 + type: Transform + - uid: 923 + components: + - rot: -1.5707963267948966 rad + pos: -2.5,2.5 + parent: 1 + type: Transform + - uid: 924 + components: + - rot: -1.5707963267948966 rad + pos: -2.5,6.5 + parent: 1 + type: Transform + - uid: 925 + components: + - rot: -1.5707963267948966 rad + pos: -2.5,3.5 + parent: 1 + type: Transform + - uid: 926 + components: + - rot: -1.5707963267948966 rad + pos: 4.5,2.5 + parent: 1 + type: Transform + - uid: 927 + components: + - rot: 3.141592653589793 rad + pos: 1.5,2.5 + parent: 1 + type: Transform + - uid: 928 + components: + - rot: 3.141592653589793 rad + pos: 1.5,6.5 + parent: 1 + type: Transform + - uid: 929 + components: + - rot: 3.141592653589793 rad + pos: 0.5,6.5 + parent: 1 + type: Transform + - uid: 930 + components: + - rot: 3.141592653589793 rad + pos: -0.5,6.5 + parent: 1 + type: Transform + - uid: 931 + components: + - rot: -1.5707963267948966 rad + pos: -2.5,5.5 + parent: 1 + type: Transform + - uid: 932 + components: + - rot: -1.5707963267948966 rad + pos: -2.5,4.5 + parent: 1 + type: Transform + - uid: 933 + components: + - rot: -1.5707963267948966 rad + pos: 3.5,3.5 + parent: 1 + type: Transform + - uid: 934 + components: + - pos: -2.5,7.5 + parent: 1 + type: Transform + - uid: 935 + components: + - pos: 3.5,7.5 + parent: 1 + type: Transform + - uid: 936 + components: + - pos: -4.5,7.5 + parent: 1 + type: Transform + - uid: 937 + components: + - pos: 5.5,7.5 + parent: 1 + type: Transform + - uid: 938 + components: + - pos: 0.5,-11.5 + parent: 1 + type: Transform + - uid: 939 + components: + - rot: -1.5707963267948966 rad + pos: 5.5,11.5 + parent: 1 + type: Transform + - uid: 940 + components: + - rot: -1.5707963267948966 rad + pos: -5.5,10.5 + parent: 1 + type: Transform + - uid: 941 + components: + - rot: -1.5707963267948966 rad + pos: 6.5,10.5 + parent: 1 + type: Transform + - uid: 942 + components: + - rot: -1.5707963267948966 rad + pos: -1.5,10.5 + parent: 1 + type: Transform + - uid: 943 + components: + - rot: -1.5707963267948966 rad + pos: 2.5,10.5 + parent: 1 + type: Transform + - uid: 944 + components: + - rot: 3.141592653589793 rad + pos: 3.5,10.5 + parent: 1 + type: Transform + - uid: 945 + components: + - rot: 3.141592653589793 rad + pos: -2.5,10.5 + parent: 1 + type: Transform + - uid: 946 + components: + - pos: 8.5,-6.5 + parent: 1 + type: Transform +- proto: WallShuttleDiagonal + entities: + - uid: 947 + components: + - rot: -1.5707963267948966 rad + pos: 8.5,3.5 + parent: 1 + type: Transform + - uid: 948 + components: + - pos: -7.5,3.5 + parent: 1 + type: Transform + - uid: 949 + components: + - rot: 1.5707963267948966 rad + pos: -7.5,-9.5 + parent: 1 + type: Transform + - uid: 950 + components: + - rot: 3.141592653589793 rad + pos: 8.5,-9.5 + parent: 1 + type: Transform + - uid: 951 + components: + - pos: -6.5,7.5 + parent: 1 + type: Transform + - uid: 952 + components: + - rot: -1.5707963267948966 rad + pos: 7.5,7.5 + parent: 1 + type: Transform +- proto: WallWeaponCapacitorRecharger + entities: + - uid: 953 + components: + - pos: -3.5,-6.5 + parent: 1 + type: Transform + - uid: 954 + components: + - pos: -5.5,-6.5 + parent: 1 + type: Transform +- proto: WardrobePrisonFilled + entities: + - uid: 955 + components: + - pos: -5.5,-10.5 + parent: 1 + type: Transform +- proto: WeaponCapacitorRecharger + entities: + - uid: 956 + components: + - pos: 3.5,13.5 + parent: 1 + type: Transform +- proto: Windoor + entities: + - uid: 957 + components: + - pos: 2.5,-3.5 + parent: 1 + type: Transform + - uid: 958 + components: + - rot: 3.141592653589793 rad + pos: 2.5,-0.5 + parent: 1 + type: Transform + - uid: 959 + components: + - rot: 3.141592653589793 rad + pos: -1.5,-0.5 + parent: 1 + type: Transform + - uid: 960 + components: + - pos: -1.5,-3.5 + parent: 1 + type: Transform +- proto: WindoorSecureCommandLocked + entities: + - uid: 961 + components: + - rot: 1.5707963267948966 rad + pos: -1.5,11.5 + parent: 1 + type: Transform + - uid: 962 + components: + - rot: -1.5707963267948966 rad + pos: 2.5,11.5 + parent: 1 + type: Transform +- proto: WindoorSecureEngineeringLocked + entities: + - uid: 963 + components: + - rot: 1.5707963267948966 rad + pos: -1.5,-13.5 + parent: 1 + type: Transform +- proto: WindoorSecureMedicalLocked + entities: + - uid: 964 + components: + - rot: -1.5707963267948966 rad + pos: 5.5,-10.5 + parent: 1 + type: Transform +- proto: WindowDirectional + entities: + - uid: 965 + components: + - rot: -1.5707963267948966 rad + pos: -3.5,-1.5 + parent: 1 + type: Transform + - uid: 966 + components: + - rot: -1.5707963267948966 rad + pos: -3.5,-2.5 + parent: 1 + type: Transform + - uid: 967 + components: + - rot: 1.5707963267948966 rad + pos: -3.5,-2.5 + parent: 1 + type: Transform + - uid: 968 + components: + - rot: 1.5707963267948966 rad + pos: -3.5,-1.5 + parent: 1 + type: Transform + - uid: 969 + components: + - pos: 1.5,-3.5 + parent: 1 + type: Transform + - uid: 970 + components: + - rot: 3.141592653589793 rad + pos: -0.5,-0.5 + parent: 1 + type: Transform + - uid: 971 + components: + - rot: 3.141592653589793 rad + pos: 1.5,-0.5 + parent: 1 + type: Transform + - uid: 972 + components: + - rot: 1.5707963267948966 rad + pos: 1.5,-0.5 + parent: 1 + type: Transform + - uid: 973 + components: + - rot: 1.5707963267948966 rad + pos: 1.5,-1.5 + parent: 1 + type: Transform + - uid: 974 + components: + - rot: 1.5707963267948966 rad + pos: 1.5,-2.5 + parent: 1 + type: Transform + - uid: 975 + components: + - rot: 1.5707963267948966 rad + pos: 1.5,-3.5 + parent: 1 + type: Transform + - uid: 976 + components: + - pos: 0.5,-3.5 + parent: 1 + type: Transform + - uid: 977 + components: + - pos: -0.5,-3.5 + parent: 1 + type: Transform + - uid: 978 + components: + - rot: -1.5707963267948966 rad + pos: -0.5,-3.5 + parent: 1 + type: Transform + - uid: 979 + components: + - rot: -1.5707963267948966 rad + pos: -0.5,-2.5 + parent: 1 + type: Transform + - uid: 980 + components: + - rot: -1.5707963267948966 rad + pos: -0.5,-1.5 + parent: 1 + type: Transform + - uid: 981 + components: + - rot: -1.5707963267948966 rad + pos: -0.5,-0.5 + parent: 1 + type: Transform + - uid: 982 + components: + - rot: -1.5707963267948966 rad + pos: 4.5,-1.5 + parent: 1 + type: Transform + - uid: 983 + components: + - rot: -1.5707963267948966 rad + pos: 4.5,-2.5 + parent: 1 + type: Transform + - uid: 984 + components: + - rot: 1.5707963267948966 rad + pos: 4.5,-2.5 + parent: 1 + type: Transform + - uid: 985 + components: + - rot: 1.5707963267948966 rad + pos: 4.5,-1.5 + parent: 1 + type: Transform + - uid: 986 + components: + - pos: 3.5,-3.5 + parent: 1 + type: Transform + - uid: 987 + components: + - rot: 3.141592653589793 rad + pos: 3.5,-0.5 + parent: 1 + type: Transform + - uid: 988 + components: + - pos: -2.5,-3.5 + parent: 1 + type: Transform + - uid: 989 + components: + - rot: 3.141592653589793 rad + pos: -2.5,-0.5 + parent: 1 + type: Transform +- proto: WindowReinforcedDirectional + entities: + - uid: 990 + components: + - rot: 1.5707963267948966 rad + pos: -1.5,13.5 + parent: 1 + type: Transform + - uid: 991 + components: + - rot: 1.5707963267948966 rad + pos: -1.5,-12.5 + parent: 1 + type: Transform + - uid: 992 + components: + - rot: 1.5707963267948966 rad + pos: -1.5,-14.5 + parent: 1 + type: Transform + - uid: 993 + components: + - rot: 1.5707963267948966 rad + pos: -1.5,12.5 + parent: 1 + type: Transform + - uid: 994 + components: + - rot: -1.5707963267948966 rad + pos: 2.5,12.5 + parent: 1 + type: Transform + - uid: 995 + components: + - rot: -1.5707963267948966 rad + pos: 2.5,13.5 + parent: 1 + type: Transform +... diff --git a/Resources/Maps/Shuttles/pathfinder.yml b/Resources/Maps/Shuttles/pathfinder.yml new file mode 100644 index 0000000000..d28c1fc820 --- /dev/null +++ b/Resources/Maps/Shuttles/pathfinder.yml @@ -0,0 +1,3172 @@ +meta: + format: 6 + postmapinit: false +tilemap: + 0: Space + 30: FloorDark + 35: FloorDarkMono + 1: FloorGlass + 65: FloorMetalDiamond + 92: FloorSteel + 99: FloorSteelDirty + 108: FloorTechMaint2 + 124: Lattice + 125: Plating +entities: +- proto: "" + entities: + - uid: 1 + components: + - type: MetaData + name: Pathfinder + - type: Transform + pos: -0.515625,-0.515625 + parent: invalid + - type: MapGrid + chunks: + 0,0: + ind: 0,0 + tiles: fQAAAAAAbAAAAAAAbAAAAAAAbAAAAAAAbAAAAAAAfQAAAAAAfAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfQAAAAAAbAAAAAAAbAAAAAAAbAAAAAAAfQAAAAAAAQAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfQAAAAAAbAAAAAAAbAAAAAAAfQAAAAAAAQAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfQAAAAAAfQAAAAAAfQAAAAAAAQAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + version: 6 + 0,-1: + ind: 0,-1 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQQAAAAAAQQAAAAAAfQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQQAAAAAAQQAAAAAAfQAAAAAAfQAAAAAAAQAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQQAAAAAAQQAAAAAAfQAAAAAAYwAAAAAAfQAAAAAAfQAAAAAAfQAAAAAAfQAAAAAAfAAAAAAAfAAAAAAAfQAAAAAAAQAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYwAAAAAAYwAAAAAAYwAAAAAAYwAAAAAAYwAAAAAAYwAAAAAAYwAAAAAAfQAAAAAAfQAAAAAAfQAAAAAAfQAAAAAAfQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYwAAAAAAYwAAAAAAYwAAAAAAYwAAAAAAYwAAAAAAYwAAAAAAYwAAAAAAYwAAAAAAYwAAAAAAYwAAAAAAYwAAAAAAfQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfQAAAAAAYwAAAAAAYwAAAAAAYwAAAAAAYwAAAAAAYwAAAAAAYwAAAAAAfQAAAAAAYwAAAAAAYwAAAAAAYwAAAAAAfQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfQAAAAAAYwAAAAAAYwAAAAAAYwAAAAAAYwAAAAAAYwAAAAAAYwAAAAAAfQAAAAAAYwAAAAAAYwAAAAAAYwAAAAAAfQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfQAAAAAAbAAAAAAAfQAAAAAAfQAAAAAAfQAAAAAAfQAAAAAAYwAAAAAAfQAAAAAAfQAAAAAAfQAAAAAAfQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfQAAAAAAbAAAAAAAbAAAAAAAbAAAAAAAbAAAAAAAfQAAAAAAYwAAAAAAYwAAAAAAYwAAAAAAfQAAAAAAfAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfQAAAAAAbAAAAAAAbAAAAAAAbAAAAAAAbAAAAAAAfQAAAAAAfQAAAAAAfQAAAAAAfQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfQAAAAAAbAAAAAAAbAAAAAAAbAAAAAAAbAAAAAAAfQAAAAAAfQAAAAAAAQAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + version: 6 + -1,0: + ind: -1,0 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfAAAAAAAfQAAAAAAHgAAAAABHgAAAAABHgAAAAACHgAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAfQAAAAAAHgAAAAABHgAAAAAAHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAABfQAAAAAAHgAAAAAAHgAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAADfQAAAAAAfQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + version: 6 + -1,-1: + ind: -1,-1 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfQAAAAAAQQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAADfQAAAAAAfQAAAAAAQQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAACfAAAAAAAfAAAAAAAfAAAAAAAfQAAAAAAfQAAAAAAfQAAAAAAfQAAAAAAYwAAAAAAfQAAAAAAQQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfQAAAAAAfQAAAAAAfQAAAAAAfQAAAAAAYwAAAAAAYwAAAAAAYwAAAAAAYwAAAAAAYwAAAAAAYwAAAAAAYwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQQAAAAAAQQAAAAAAYwAAAAAAYwAAAAAAYwAAAAAAYwAAAAAAYwAAAAAAYwAAAAAAYwAAAAAAYwAAAAAAYwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQQAAAAAAQQAAAAAAYwAAAAAAYwAAAAAAYwAAAAAAYwAAAAAAYwAAAAAAYwAAAAAAfQAAAAAAfQAAAAAAXAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQQAAAAAAQQAAAAAAYwAAAAAAYwAAAAAAYwAAAAAAYwAAAAAAYwAAAAAAfQAAAAAAXAAAAAABXAAAAAAAXAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAfQAAAAAAfQAAAAAAYwAAAAAAYwAAAAAAYwAAAAAAYwAAAAAAfQAAAAAAXAAAAAADXAAAAAACXAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfAAAAAAAfQAAAAAAYwAAAAAAYwAAAAAAYwAAAAAAYwAAAAAAfQAAAAAAXAAAAAABXAAAAAABXAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAABfQAAAAAAfQAAAAAAYwAAAAAAfQAAAAAAfQAAAAAAfQAAAAAAfQAAAAAAXAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAABfQAAAAAAfQAAAAAAHgAAAAACHgAAAAAAHgAAAAAAHgAAAAAB + version: 6 + - type: Broadphase + - type: Physics + bodyStatus: InAir + angularDamping: 0.05 + linearDamping: 0.05 + fixedRotation: False + bodyType: Dynamic + - type: Fixtures + fixtures: {} + - type: SalvageShuttle + - type: OccluderTree + - type: SpreaderGrid + - type: Shuttle + - type: GridPathfinding + - type: Gravity + gravityShakeSound: !type:SoundPathSpecifier + path: /Audio/Effects/alert.ogg + - type: DecalGrid + chunkCollection: + version: 2 + nodes: + - node: + color: '#FFFFFFFF' + id: Bot + decals: + 0: -5,-3 + 1: -6,-3 + 2: -7,-3 + 3: -8,-3 + 4: -8,-4 + 5: -7,-4 + 6: -6,-4 + 7: -5,-4 + 8: -5,-5 + 9: -6,-5 + 10: -7,-5 + 11: 2,-5 + 12: 2,-6 + 13: 5,-6 + 14: 5,-5 + 17: 3,-5 + 18: 4,-5 + 19: 4,-6 + 20: 3,-6 + - node: + color: '#FFFFFFFF' + id: Box + decals: + 15: -3,-9 + 16: 3,-9 + 131: -4,-1 + 132: -2,-3 + 133: -2,-5 + 134: -4,0 + - node: + color: '#3AB3DAFF' + id: DeliveryGreyscale + decals: + 136: 1,2 + - node: + color: '#F9801DFF' + id: DeliveryGreyscale + decals: + 135: 2,2 + - node: + cleanable: True + color: '#FFFFFFFF' + id: Dirt + decals: + 111: 8,-7 + 112: 8,-7 + 113: 8,-5 + 114: 9,-5 + 115: 9,-7 + 116: 10,-7 + 117: 10,-6 + 118: 8,-6 + 119: 6,-3 + 120: 6,-3 + 121: 6,-4 + 122: 6,-3 + 123: 7,-3 + 124: 6,-3 + 125: 8,-3 + 126: 8,-3 + 127: 8,-3 + 128: 8,-3 + 129: 7,-3 + 130: 7,-3 + - node: + cleanable: True + color: '#FFFFFFFF' + id: DirtHeavy + decals: + 21: -4,-6 + 22: -7,-6 + 23: -7,-6 + 24: -8,-5 + 25: -9,-5 + 26: -6,-8 + 27: -4,-8 + 28: -3,-8 + 29: -6,-7 + 30: -6,-8 + 31: 2,-8 + 32: 4,-7 + 33: 1,-6 + 34: -1,-7 + 35: 3,-7 + 36: 3,-5 + 37: 0,-6 + 38: -1,-7 + 39: -3,-7 + 40: -3,-8 + 41: 4,-8 + 42: 4,-9 + 43: 6,-7 + 44: 6,-5 + 45: 6,-7 + 46: 4,-7 + 47: 4,-7 + 48: 1,-7 + 49: -4,-8 + 50: -3,-8 + 51: 0,-8 + 52: -5,-7 + 53: -7,-5 + 54: -6,-5 + 55: -5,-4 + 56: -6,-4 + 57: -8,-5 + 58: -8,-7 + 59: -9,-7 + 60: -8,-7 + 61: -6,-6 + 62: -6,-8 + - node: + cleanable: True + color: '#FFFFFFFF' + id: DirtMedium + decals: + 63: -7,-6 + 64: -7,-6 + 65: -5,-7 + 66: -9,-7 + 67: -9,-5 + 68: -9,-7 + 69: -8,-6 + 70: -8,-6 + 71: -6,-4 + 72: -5,-5 + 73: -6,-5 + 74: -8,-4 + 75: -7,-4 + 76: -5,-6 + 77: -4,-7 + 78: -5,-8 + 79: -2,-8 + 80: -2,-8 + 81: 0,-8 + 82: 1,-8 + 83: 3,-6 + 84: 1,-5 + 85: 2,-6 + 86: 2,-5 + 87: 2,-5 + 88: 5,-6 + 89: 6,-7 + 90: 6,-8 + 91: 6,-6 + 92: 4,-8 + 93: 2,-8 + 94: -1,-7 + 95: -4,-8 + 96: -1,-5 + 97: -2,-4 + 98: -1,-4 + 99: -2,-3 + 100: -3,-1 + 101: -1,0 + 102: -1,-1 + 103: -3,-1 + 104: -3,1 + 105: -2,1 + 106: -2,0 + 107: -1,2 + 108: -3,0 + 109: -4,-1 + 110: -4,0 + - type: GridAtmosphere + version: 2 + data: + tiles: + 0,0: + 0: 1774 + 1: 32768 + -1,0: + 0: 3311 + 1: 8448 + 0,-1: + 0: 61154 + 1,0: + 0: 1 + 1: 288 + 2: 4 + 1,-1: + 0: 4564 + 1: 32768 + 0,-3: + 0: 45872 + -1,-3: + 0: 43136 + 1: 256 + 0,-2: + 0: 61183 + -1,-2: + 0: 59647 + -1,-1: + 0: 63726 + 1,-3: + 1: 256 + 1,-2: + 0: 30711 + 2,-3: + 2: 12288 + 1: 32768 + 2,-2: + 0: 30576 + 2,-1: + 0: 16 + 1: 520 + 2: 64 + -2,0: + 2: 4 + 1: 128 + -3,-3: + 1: 8192 + 2: 49152 + -3,-2: + 0: 52416 + -3,-1: + 1: 2050 + 2: 64 + -2,-3: + 2: 4096 + -2,-2: + 0: 65532 + -2,-1: + 0: 1279 + 1: 8192 + uniqueMixes: + - volume: 2500 + temperature: 293.15 + moles: + - 21.824879 + - 82.10312 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - volume: 2500 + temperature: 293.15 + moles: + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - volume: 2500 + immutable: True + moles: + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + chunkSize: 4 + - type: GasTileOverlay + - type: RadiationGridResistance + - type: BecomesStation + id: Pathfinder +- proto: AirAlarm + entities: + - uid: 3 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 0.5,1.5 + parent: 1 + - type: DeviceList + devices: + - 153 + - 126 + - 118 + - 140 + - 104 + - 150 + - 276 + - 278 + - 273 + - 173 + - 174 + - 513 + - 512 + - 511 + - 510 + - 263 + - 21 + - 19 + - 17 +- proto: AirlockGlassShuttle + entities: + - uid: 8 + components: + - type: Transform + pos: -0.5,-10.5 + parent: 1 + - uid: 193 + components: + - type: Transform + pos: 1.5,-10.5 + parent: 1 + - uid: 226 + components: + - type: Transform + pos: 0.5,-10.5 + parent: 1 +- proto: AirlockSalvageGlassLocked + entities: + - uid: 6 + components: + - type: Transform + pos: -0.5,-5.5 + parent: 1 + - uid: 88 + components: + - type: Transform + pos: -0.5,-1.5 + parent: 1 + - uid: 187 + components: + - type: Transform + pos: 7.5,-6.5 + parent: 1 +- proto: AirlockSalvageLocked + entities: + - uid: 5 + components: + - type: Transform + pos: 1.5,-3.5 + parent: 1 + - uid: 7 + components: + - type: Transform + pos: 1.5,-8.5 + parent: 1 + - uid: 86 + components: + - type: Transform + pos: 0.5,-8.5 + parent: 1 + - uid: 144 + components: + - type: Transform + pos: -0.5,-8.5 + parent: 1 +- proto: AmeController + entities: + - uid: 70 + components: + - type: Transform + pos: 3.5,-2.5 + parent: 1 + - type: AmeController + injecting: True + - type: ContainerContainer + containers: + fuelSlot: !type:ContainerSlot + showEnts: False + occludes: True + ent: 72 +- proto: AmeJar + entities: + - uid: 72 + components: + - type: Transform + parent: 70 + - type: Physics + canCollide: False + - uid: 117 + components: + - type: Transform + pos: 2.9327545,-2.3777018 + parent: 1 + - uid: 363 + components: + - type: Transform + pos: 2.7608795,-2.3620768 + parent: 1 +- proto: AmeShielding + entities: + - uid: 14 + components: + - type: Transform + pos: 3.5,-1.5 + parent: 1 + - uid: 22 + components: + - type: Transform + pos: 4.5,0.5 + parent: 1 + - uid: 79 + components: + - type: Transform + pos: 2.5,-0.5 + parent: 1 + - uid: 102 + components: + - type: Transform + pos: 3.5,-0.5 + parent: 1 + - type: PointLight + radius: 2 + enabled: True + - uid: 103 + components: + - type: Transform + pos: 4.5,-1.5 + parent: 1 + - uid: 105 + components: + - type: Transform + pos: 4.5,-0.5 + parent: 1 + - uid: 115 + components: + - type: Transform + pos: 2.5,0.5 + parent: 1 + - uid: 172 + components: + - type: Transform + pos: 3.5,0.5 + parent: 1 + - uid: 334 + components: + - type: Transform + pos: 2.5,-1.5 + parent: 1 +- proto: APCBasic + entities: + - uid: 96 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 0.5,-3.5 + parent: 1 +- proto: AtmosDeviceFanTiny + entities: + - uid: 9 + components: + - type: Transform + pos: 0.5,-10.5 + parent: 1 + - uid: 25 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -10.5,-4.5 + parent: 1 + - uid: 26 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -10.5,-6.5 + parent: 1 + - uid: 27 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -10.5,-5.5 + parent: 1 + - uid: 28 + components: + - type: Transform + pos: 1.5,-10.5 + parent: 1 + - uid: 29 + components: + - type: Transform + pos: -0.5,-10.5 + parent: 1 +- proto: AtmosFixBlockerMarker + entities: + - uid: 23 + components: + - type: Transform + pos: 4.5,-9.5 + parent: 1 + - uid: 42 + components: + - type: Transform + pos: 10.5,-2.5 + parent: 1 + - uid: 82 + components: + - type: Transform + pos: -10.5,-3.5 + parent: 1 + - uid: 89 + components: + - type: Transform + pos: -2.5,3.5 + parent: 1 + - uid: 90 + components: + - type: Transform + pos: 11.5,-3.5 + parent: 1 + - uid: 159 + components: + - type: Transform + pos: -3.5,-9.5 + parent: 1 + - uid: 230 + components: + - type: Transform + pos: -10.5,-8.5 + parent: 1 + - uid: 232 + components: + - type: Transform + pos: -8.5,-8.5 + parent: 1 + - uid: 233 + components: + - type: Transform + pos: -7.5,-8.5 + parent: 1 + - uid: 234 + components: + - type: Transform + pos: -9.5,-2.5 + parent: 1 + - uid: 236 + components: + - type: Transform + pos: -8.5,-1.5 + parent: 1 + - uid: 238 + components: + - type: Transform + pos: -6.5,-0.5 + parent: 1 + - uid: 239 + components: + - type: Transform + pos: -5.5,0.5 + parent: 1 + - uid: 240 + components: + - type: Transform + pos: -4.5,1.5 + parent: 1 + - uid: 241 + components: + - type: Transform + pos: 3.5,3.5 + parent: 1 + - uid: 242 + components: + - type: Transform + pos: 4.5,2.5 + parent: 1 + - uid: 243 + components: + - type: Transform + pos: 5.5,1.5 + parent: 1 + - uid: 244 + components: + - type: Transform + pos: 6.5,0.5 + parent: 1 + - uid: 245 + components: + - type: Transform + pos: 7.5,-0.5 + parent: 1 + - uid: 251 + components: + - type: Transform + pos: 9.5,-1.5 + parent: 1 + - uid: 253 + components: + - type: Transform + pos: 11.5,-8.5 + parent: 1 + - uid: 254 + components: + - type: Transform + pos: 9.5,-8.5 + parent: 1 + - uid: 255 + components: + - type: Transform + pos: 8.5,-8.5 + parent: 1 + - uid: 309 + components: + - type: Transform + pos: -3.5,2.5 + parent: 1 +- proto: Bed + entities: + - uid: 30 + components: + - type: Transform + pos: -2.5,-4.5 + parent: 1 + - uid: 31 + components: + - type: Transform + pos: -2.5,-2.5 + parent: 1 +- proto: BedsheetSpawner + entities: + - uid: 32 + components: + - type: Transform + pos: -2.5,-4.5 + parent: 1 + - uid: 33 + components: + - type: Transform + pos: -2.5,-2.5 + parent: 1 +- proto: BlastDoor + entities: + - uid: 34 + components: + - type: Transform + pos: -10.5,-4.5 + parent: 1 + - type: DeviceLinkSink + links: + - 225 + - uid: 35 + components: + - type: Transform + pos: -10.5,-5.5 + parent: 1 + - type: DeviceLinkSink + links: + - 225 + - uid: 36 + components: + - type: Transform + pos: -10.5,-6.5 + parent: 1 + - type: DeviceLinkSink + links: + - 225 +- proto: ButtonFrameGrey + entities: + - uid: 157 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 0.5,-2.5 + parent: 1 + - uid: 191 + components: + - type: Transform + pos: -9.5,-3.5 + parent: 1 +- proto: CableApcExtension + entities: + - uid: 20 + components: + - type: Transform + pos: -0.5,-5.5 + parent: 1 + - uid: 24 + components: + - type: Transform + pos: 6.5,-6.5 + parent: 1 + - uid: 38 + components: + - type: Transform + pos: -4.5,-6.5 + parent: 1 + - uid: 43 + components: + - type: Transform + pos: 3.5,-7.5 + parent: 1 + - uid: 44 + components: + - type: Transform + pos: 7.5,-2.5 + parent: 1 + - uid: 45 + components: + - type: Transform + pos: 6.5,-2.5 + parent: 1 + - uid: 46 + components: + - type: Transform + pos: 6.5,-3.5 + parent: 1 + - uid: 47 + components: + - type: Transform + pos: 6.5,-4.5 + parent: 1 + - uid: 48 + components: + - type: Transform + pos: 6.5,-5.5 + parent: 1 + - uid: 49 + components: + - type: Transform + pos: -2.5,0.5 + parent: 1 + - uid: 50 + components: + - type: Transform + pos: -1.5,0.5 + parent: 1 + - uid: 51 + components: + - type: Transform + pos: -1.5,-0.5 + parent: 1 + - uid: 54 + components: + - type: Transform + pos: -0.5,-0.5 + parent: 1 + - uid: 55 + components: + - type: Transform + pos: -3.5,-7.5 + parent: 1 + - uid: 56 + components: + - type: Transform + pos: -2.5,-7.5 + parent: 1 + - uid: 57 + components: + - type: Transform + pos: -5.5,-6.5 + parent: 1 + - uid: 58 + components: + - type: Transform + pos: -6.5,-6.5 + parent: 1 + - uid: 61 + components: + - type: Transform + pos: -0.5,-1.5 + parent: 1 + - uid: 62 + components: + - type: Transform + pos: -0.5,-2.5 + parent: 1 + - uid: 64 + components: + - type: Transform + pos: 4.5,-6.5 + parent: 1 + - uid: 65 + components: + - type: Transform + pos: 7.5,-6.5 + parent: 1 + - uid: 66 + components: + - type: Transform + pos: 8.5,-6.5 + parent: 1 + - uid: 71 + components: + - type: Transform + pos: -1.5,-7.5 + parent: 1 + - uid: 77 + components: + - type: Transform + pos: 9.5,-6.5 + parent: 1 + - uid: 91 + components: + - type: Transform + pos: 2.5,-7.5 + parent: 1 + - uid: 93 + components: + - type: Transform + pos: -7.5,-6.5 + parent: 1 + - uid: 94 + components: + - type: Transform + pos: -1.5,-3.5 + parent: 1 + - uid: 97 + components: + - type: Transform + pos: 1.5,0.5 + parent: 1 + - uid: 100 + components: + - type: Transform + pos: 1.5,-2.5 + parent: 1 + - uid: 106 + components: + - type: Transform + pos: 3.5,-6.5 + parent: 1 + - uid: 108 + components: + - type: Transform + pos: -7.5,-4.5 + parent: 1 + - uid: 109 + components: + - type: Transform + pos: 1.5,-3.5 + parent: 1 + - uid: 110 + components: + - type: Transform + pos: -7.5,-3.5 + parent: 1 + - uid: 111 + components: + - type: Transform + pos: -0.5,-3.5 + parent: 1 + - uid: 112 + components: + - type: Transform + pos: -0.5,-7.5 + parent: 1 + - uid: 123 + components: + - type: Transform + pos: 1.5,-0.5 + parent: 1 + - uid: 138 + components: + - type: Transform + pos: 1.5,-7.5 + parent: 1 + - uid: 139 + components: + - type: Transform + pos: 0.5,-7.5 + parent: 1 + - uid: 145 + components: + - type: Transform + pos: 1.5,-1.5 + parent: 1 + - uid: 151 + components: + - type: Transform + pos: 0.5,-3.5 + parent: 1 + - uid: 155 + components: + - type: Transform + pos: -7.5,-5.5 + parent: 1 + - uid: 177 + components: + - type: Transform + pos: -2.5,-3.5 + parent: 1 + - uid: 208 + components: + - type: Transform + pos: -0.5,-6.5 + parent: 1 + - uid: 348 + components: + - type: Transform + pos: -3.5,-6.5 + parent: 1 + - uid: 352 + components: + - type: Transform + pos: -0.5,-4.5 + parent: 1 + - uid: 362 + components: + - type: Transform + pos: 5.5,-6.5 + parent: 1 +- proto: CableHV + entities: + - uid: 13 + components: + - type: Transform + pos: 3.5,-2.5 + parent: 1 + - uid: 299 + components: + - type: Transform + pos: 4.5,-2.5 + parent: 1 + - uid: 305 + components: + - type: Transform + pos: 4.5,-3.5 + parent: 1 +- proto: CableMV + entities: + - uid: 16 + components: + - type: Transform + pos: -0.5,-3.5 + parent: 1 + - uid: 53 + components: + - type: Transform + pos: 4.5,-3.5 + parent: 1 + - uid: 68 + components: + - type: Transform + pos: 2.5,-6.5 + parent: 1 + - uid: 73 + components: + - type: Transform + pos: 4.5,-4.5 + parent: 1 + - uid: 74 + components: + - type: Transform + pos: 4.5,-5.5 + parent: 1 + - uid: 75 + components: + - type: Transform + pos: 4.5,-6.5 + parent: 1 + - uid: 76 + components: + - type: Transform + pos: 3.5,-6.5 + parent: 1 + - uid: 78 + components: + - type: Transform + pos: 1.5,-6.5 + parent: 1 + - uid: 95 + components: + - type: Transform + pos: 0.5,-3.5 + parent: 1 + - uid: 107 + components: + - type: Transform + pos: 4.5,-5.5 + parent: 1 + - uid: 113 + components: + - type: Transform + pos: 0.5,-6.5 + parent: 1 + - uid: 281 + components: + - type: Transform + pos: -0.5,-4.5 + parent: 1 + - uid: 322 + components: + - type: Transform + pos: -0.5,-6.5 + parent: 1 + - uid: 356 + components: + - type: Transform + pos: -0.5,-5.5 + parent: 1 +- proto: CableTerminal + entities: + - uid: 98 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 3.5,-2.5 + parent: 1 +- proto: CargoPallet + entities: + - uid: 158 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -5.5,-1.5 + parent: 1 +- proto: CarpetBlack + entities: + - uid: 154 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -2.5,-2.5 + parent: 1 + - uid: 259 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -1.5,-3.5 + parent: 1 + - uid: 501 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -2.5,-3.5 + parent: 1 + - uid: 502 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -2.5,-4.5 + parent: 1 + - uid: 503 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -1.5,-2.5 + parent: 1 + - uid: 505 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -1.5,-4.5 + parent: 1 +- proto: Chair + entities: + - uid: 162 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 10.5,-4.5 + parent: 1 + - uid: 163 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 10.5,-5.5 + parent: 1 +- proto: ChairPilotSeat + entities: + - uid: 165 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -1.5,1.5 + parent: 1 +- proto: ClosetWallEmergencyFilledRandom + entities: + - uid: 160 + components: + - type: Transform + pos: -4.5,-1.5 + parent: 1 +- proto: ClosetWallFireFilledRandom + entities: + - uid: 92 + components: + - type: Transform + pos: 2.5,-3.5 + parent: 1 +- proto: ComputerBroken + entities: + - uid: 41 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 5.5,-7.5 + parent: 1 + - uid: 164 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -5.5,-7.5 + parent: 1 + - uid: 336 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 6.5,-7.5 + parent: 1 +- proto: ComputerRadar + entities: + - uid: 67 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -0.5,1.5 + parent: 1 +- proto: ComputerSalvageExpedition + entities: + - uid: 257 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -2.5,1.5 + parent: 1 +- proto: ComputerShuttleSalvage + entities: + - uid: 258 + components: + - type: Transform + pos: -1.5,2.5 + parent: 1 +- proto: DefibrillatorCabinetFilled + entities: + - uid: 283 + components: + - type: Transform + pos: -2.5,-5.5 + parent: 1 +- proto: DisposalBend + entities: + - uid: 184 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 6.5,-2.5 + parent: 1 +- proto: DisposalPipe + entities: + - uid: 185 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 7.5,-2.5 + parent: 1 + - uid: 189 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 6.5,-3.5 + parent: 1 +- proto: DisposalPipeBroken + entities: + - uid: 186 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 6.5,-4.5 + parent: 1 +- proto: DisposalTrunk + entities: + - uid: 188 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 8.5,-2.5 + parent: 1 +- proto: DrinkMugMetal + entities: + - uid: 195 + components: + - type: Transform + pos: 9.636227,-6.42377 + parent: 1 + - uid: 196 + components: + - type: Transform + pos: 10.542477,-6.408145 + parent: 1 +- proto: EmergencyLight + entities: + - uid: 183 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -0.5,-9.5 + parent: 1 + - uid: 262 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -4.5,-4.5 + parent: 1 + - uid: 265 + components: + - type: Transform + pos: 5.5,-4.5 + parent: 1 + - uid: 274 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -0.5,0.5 + parent: 1 + - uid: 275 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -1.5,-4.5 + parent: 1 + - uid: 277 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 1.5,0.5 + parent: 1 +- proto: ExtinguisherCabinetFilled + entities: + - uid: 84 + components: + - type: Transform + pos: -1.5,-5.5 + parent: 1 +- proto: FirelockEdge + entities: + - uid: 173 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -1.5,-6.5 + parent: 1 + - type: DeviceNetwork + deviceLists: + - 3 + - uid: 174 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -1.5,-7.5 + parent: 1 + - type: DeviceNetwork + deviceLists: + - 3 + - uid: 510 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 2.5,-4.5 + parent: 1 + - type: DeviceNetwork + deviceLists: + - 3 + - uid: 511 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 2.5,-5.5 + parent: 1 + - type: DeviceNetwork + deviceLists: + - 3 + - uid: 512 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 2.5,-6.5 + parent: 1 + - type: DeviceNetwork + deviceLists: + - 3 + - uid: 513 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 2.5,-7.5 + parent: 1 + - type: DeviceNetwork + deviceLists: + - 3 +- proto: FirelockGlass + entities: + - uid: 17 + components: + - type: Transform + pos: -0.5,-1.5 + parent: 1 + - type: DeviceNetwork + deviceLists: + - 3 + - uid: 19 + components: + - type: Transform + pos: -0.5,-5.5 + parent: 1 + - type: DeviceNetwork + deviceLists: + - 3 + - uid: 21 + components: + - type: Transform + pos: 1.5,-3.5 + parent: 1 + - type: DeviceNetwork + deviceLists: + - 3 + - uid: 263 + components: + - type: Transform + pos: 7.5,-6.5 + parent: 1 + - type: DeviceNetwork + deviceLists: + - 3 +- proto: FloorDrain + entities: + - uid: 198 + components: + - type: Transform + pos: 7.5,-2.5 + parent: 1 + - type: Fixtures + fixtures: {} +- proto: Fulton + entities: + - uid: 197 + components: + - type: Transform + pos: 3.7384453,-4.482957 + parent: 1 + - type: Physics + angularDamping: 0 + linearDamping: 0 + - uid: 260 + components: + - type: Transform + pos: 4.4102926,-4.1582947 + parent: 1 + - uid: 292 + components: + - type: Transform + pos: 4.722792,-4.2364197 + parent: 1 + - uid: 314 + components: + - type: Transform + pos: 3.5509453,-4.186082 + parent: 1 +- proto: GasMixerFlipped + entities: + - uid: 182 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.5,1.5 + parent: 1 + - type: GasMixer + inletTwoConcentration: 0.79 + inletOneConcentration: 0.21 + - type: AtmosPipeColor + color: '#0055CCFF' +- proto: GasPassiveVent + entities: + - uid: 37 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -8.5,-8.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' +- proto: GasPipeBend + entities: + - uid: 124 + components: + - type: Transform + pos: -5.5,-6.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 128 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 6.5,-7.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 131 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -5.5,-7.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 152 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -4.5,-6.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' + - uid: 178 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -1.5,0.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' + - uid: 203 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 2.5,1.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' + - uid: 222 + components: + - type: Transform + pos: -4.5,-5.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' + - uid: 227 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -8.5,-6.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' +- proto: GasPipeFourway + entities: + - uid: 204 + components: + - type: Transform + pos: 1.5,-6.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' +- proto: GasPipeStraight + entities: + - uid: 99 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 0.5,-0.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 114 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.5,-0.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' + - uid: 119 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -0.5,-6.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 122 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -0.5,-5.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 125 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -0.5,0.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 129 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 6.5,-6.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 130 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 5.5,-7.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 132 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 2.5,-7.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 133 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 1.5,-7.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 135 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -1.5,-7.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 141 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -0.5,-6.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' + - uid: 142 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -2.5,-7.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 146 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -3.5,-7.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 147 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -4.5,-7.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 149 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 4.5,-6.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 167 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -1.5,-4.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 168 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -0.5,-2.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 175 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.5,-1.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' + - uid: 181 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 0.5,-6.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' + - uid: 194 + components: + - type: Transform + pos: -8.5,-7.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 199 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -2.5,-6.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' + - uid: 201 + components: + - type: Transform + pos: -1.5,-1.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' + - uid: 202 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -3.5,-6.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' + - uid: 209 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 2.5,-6.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' + - uid: 211 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.5,0.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' + - uid: 213 + components: + - type: Transform + pos: -1.5,-0.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' + - uid: 214 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -1.5,-5.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' + - uid: 216 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 4.5,-6.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' + - uid: 217 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 5.5,-6.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' + - uid: 218 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 6.5,-6.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' + - uid: 219 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 7.5,-6.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' + - uid: 220 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 8.5,-6.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' + - uid: 221 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 9.5,-6.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' + - uid: 224 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 7.5,-5.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 228 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 3.5,-7.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 229 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -1.5,-4.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' + - uid: 231 + components: + - type: Transform + pos: -0.5,-1.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 237 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -0.5,-3.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 247 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -7.5,-6.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 248 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -1.5,-3.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' + - uid: 249 + components: + - type: Transform + pos: 6.5,-4.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 250 + components: + - type: Transform + pos: 6.5,-3.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 252 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 0.5,0.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' + - uid: 320 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.5,-5.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' + - uid: 321 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.5,-4.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' + - uid: 325 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.5,-3.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' +- proto: GasPipeTJunction + entities: + - uid: 120 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -0.5,-7.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 121 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 3.5,-6.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' + - uid: 127 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 6.5,-5.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 148 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 4.5,-7.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 166 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -0.5,-4.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 206 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 0.5,-7.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 207 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -1.5,-2.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' + - uid: 215 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -1.5,-6.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' + - uid: 256 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 1.5,-2.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' + - uid: 261 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -0.5,-0.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 267 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -6.5,-6.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' +- proto: GasPort + entities: + - uid: 268 + components: + - type: Transform + pos: 2.5,2.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' + - uid: 269 + components: + - type: Transform + pos: 1.5,2.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' +- proto: GasVentPump + entities: + - uid: 60 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -5.5,-5.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' + - uid: 104 + components: + - type: Transform + pos: 3.5,-5.5 + parent: 1 + - type: DeviceNetwork + deviceLists: + - 3 + - type: AtmosPipeColor + color: '#0055CCFF' + - uid: 140 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.5,-7.5 + parent: 1 + - type: DeviceNetwork + deviceLists: + - 3 + - type: AtmosPipeColor + color: '#0055CCFF' + - uid: 212 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -0.5,0.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' + - uid: 235 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 2.5,-2.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' + - uid: 273 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 10.5,-6.5 + parent: 1 + - type: DeviceNetwork + deviceLists: + - 3 + - type: AtmosPipeColor + color: '#0055CCFF' + - uid: 372 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -2.5,-2.5 + parent: 1 + - type: AtmosPipeColor + color: '#0055CCFF' +- proto: GasVentScrubber + entities: + - uid: 118 + components: + - type: Transform + pos: 0.5,-6.5 + parent: 1 + - type: DeviceNetwork + deviceLists: + - 3 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 126 + components: + - type: Transform + pos: -0.5,1.5 + parent: 1 + - type: DeviceNetwork + deviceLists: + - 3 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 150 + components: + - type: Transform + pos: 4.5,-5.5 + parent: 1 + - type: DeviceNetwork + deviceLists: + - 3 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 153 + components: + - type: Transform + pos: -6.5,-5.5 + parent: 1 + - type: DeviceNetwork + deviceLists: + - 3 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 205 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.5,-0.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 210 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -2.5,-4.5 + parent: 1 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 276 + components: + - type: Transform + pos: 6.5,-2.5 + parent: 1 + - type: DeviceNetwork + deviceLists: + - 3 + - type: AtmosPipeColor + color: '#990000FF' + - uid: 278 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 8.5,-5.5 + parent: 1 + - type: DeviceNetwork + deviceLists: + - 3 + - type: AtmosPipeColor + color: '#990000FF' +- proto: GravityGeneratorMini + entities: + - uid: 333 + components: + - type: Transform + pos: 3.5,1.5 + parent: 1 +- proto: Grille + entities: + - uid: 10 + components: + - type: Transform + pos: -1.5,3.5 + parent: 1 + - uid: 272 + components: + - type: Transform + pos: 11.5,-4.5 + parent: 1 + - uid: 279 + components: + - type: Transform + pos: 7.5,-4.5 + parent: 1 + - uid: 280 + components: + - type: Transform + pos: -0.5,3.5 + parent: 1 + - uid: 315 + components: + - type: Transform + pos: 0.5,-4.5 + parent: 1 + - uid: 318 + components: + - type: Transform + pos: 5.5,-8.5 + parent: 1 + - uid: 319 + components: + - type: Transform + pos: -4.5,-8.5 + parent: 1 + - uid: 329 + components: + - type: Transform + pos: 7.5,-5.5 + parent: 1 + - uid: 330 + components: + - type: Transform + pos: 11.5,-5.5 + parent: 1 + - uid: 340 + components: + - type: Transform + pos: -2.5,2.5 + parent: 1 + - uid: 342 + components: + - type: Transform + pos: 11.5,-6.5 + parent: 1 + - uid: 345 + components: + - type: Transform + pos: -7.5,-1.5 + parent: 1 + - uid: 392 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -2.5,-9.5 + parent: 1 + - uid: 396 + components: + - type: Transform + pos: -5.5,-8.5 + parent: 1 + - uid: 465 + components: + - type: Transform + pos: 6.5,-8.5 + parent: 1 + - uid: 494 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 3.5,-9.5 + parent: 1 + - uid: 499 + components: + - type: Transform + pos: 1.5,3.5 + parent: 1 + - uid: 500 + components: + - type: Transform + pos: 2.5,3.5 + parent: 1 +- proto: GrilleDiagonal + entities: + - uid: 294 + components: + - type: Transform + pos: -2.5,3.5 + parent: 1 + - uid: 297 + components: + - type: Transform + pos: -3.5,2.5 + parent: 1 +- proto: Gyroscope + entities: + - uid: 143 + components: + - type: Transform + pos: 2.5,1.5 + parent: 1 +- proto: HospitalCurtainsOpen + entities: + - uid: 284 + components: + - type: Transform + pos: 6.5,-2.5 + parent: 1 + - uid: 285 + components: + - type: Transform + pos: -2.5,-2.5 + parent: 1 + - uid: 286 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -2.5,-4.5 + parent: 1 +- proto: LockerFreezer + entities: + - uid: 288 + components: + - type: Transform + pos: 8.5,-4.5 + parent: 1 +- proto: LockerSalvageSpecialistFilled + entities: + - uid: 69 + components: + - type: Transform + pos: -3.5,0.5 + parent: 1 + - uid: 223 + components: + - type: Transform + pos: -1.5,-4.5 + parent: 1 +- proto: NitrogenCanister + entities: + - uid: 11 + components: + - type: Transform + anchored: True + pos: 2.5,2.5 + parent: 1 + - type: Physics + bodyType: Static +- proto: OreProcessor + entities: + - uid: 365 + components: + - type: Transform + pos: -6.5,-2.5 + parent: 1 +- proto: OxygenCanister + entities: + - uid: 12 + components: + - type: Transform + anchored: True + pos: 1.5,2.5 + parent: 1 + - type: Physics + bodyType: Static +- proto: PaperBin5 + entities: + - uid: 291 + components: + - type: Transform + pos: -0.5,0.5 + parent: 1 +- proto: PoweredSmallLight + entities: + - uid: 18 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -4.5,-2.5 + parent: 1 + - uid: 81 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -8.5,-6.5 + parent: 1 + - uid: 264 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 9.5,-6.5 + parent: 1 + - uid: 266 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -3.5,-7.5 + parent: 1 + - uid: 270 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 4.5,-7.5 + parent: 1 + - uid: 293 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -2.5,-3.5 + parent: 1 + - uid: 301 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 8.5,-2.5 + parent: 1 + - uid: 303 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 1.5,-0.5 + parent: 1 + - uid: 304 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -1.5,-0.5 + parent: 1 + - uid: 324 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.5,-9.5 + parent: 1 +- proto: Rack + entities: + - uid: 2 + components: + - type: Transform + pos: 3.5,-4.5 + parent: 1 + - uid: 39 + components: + - type: Transform + pos: 4.5,-4.5 + parent: 1 +- proto: RandomPosterAny + entities: + - uid: 4 + components: + - type: Transform + pos: -2.5,-1.5 + parent: 1 + - uid: 87 + components: + - type: Transform + pos: 0.5,0.5 + parent: 1 + - uid: 170 + components: + - type: Transform + pos: 3.5,-3.5 + parent: 1 + - uid: 171 + components: + - type: Transform + pos: 7.5,-3.5 + parent: 1 + - uid: 176 + components: + - type: Transform + pos: -7.5,-7.5 + parent: 1 + - uid: 180 + components: + - type: Transform + pos: -3.5,-3.5 + parent: 1 + - uid: 355 + components: + - type: Transform + pos: 9.5,-3.5 + parent: 1 + - uid: 360 + components: + - type: Transform + pos: -8.5,-3.5 + parent: 1 +- proto: ReinforcedWindow + entities: + - uid: 83 + components: + - type: Transform + pos: -7.5,-1.5 + parent: 1 + - uid: 85 + components: + - type: Transform + pos: 11.5,-6.5 + parent: 1 + - uid: 271 + components: + - type: Transform + pos: 11.5,-4.5 + parent: 1 + - uid: 289 + components: + - type: Transform + pos: -1.5,3.5 + parent: 1 + - uid: 323 + components: + - type: Transform + pos: 1.5,3.5 + parent: 1 + - uid: 326 + components: + - type: Transform + pos: -2.5,2.5 + parent: 1 + - uid: 327 + components: + - type: Transform + pos: 11.5,-5.5 + parent: 1 + - uid: 332 + components: + - type: Transform + pos: -0.5,3.5 + parent: 1 + - uid: 391 + components: + - type: Transform + pos: 6.5,-8.5 + parent: 1 + - uid: 394 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 3.5,-9.5 + parent: 1 + - uid: 398 + components: + - type: Transform + pos: -4.5,-8.5 + parent: 1 + - uid: 421 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -2.5,-9.5 + parent: 1 + - uid: 442 + components: + - type: Transform + pos: 0.5,-4.5 + parent: 1 + - uid: 468 + components: + - type: Transform + pos: -5.5,-8.5 + parent: 1 + - uid: 469 + components: + - type: Transform + pos: 5.5,-8.5 + parent: 1 + - uid: 490 + components: + - type: Transform + pos: 2.5,3.5 + parent: 1 +- proto: ReinforcedWindowDiagonal + entities: + - uid: 287 + components: + - type: Transform + pos: -2.5,3.5 + parent: 1 + - uid: 328 + components: + - type: Transform + pos: -3.5,2.5 + parent: 1 +- proto: SalvageMagnet + entities: + - uid: 169 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -0.5,2.5 + parent: 1 +- proto: ShuttersNormalOpen + entities: + - uid: 520 + components: + - type: Transform + pos: 0.5,-4.5 + parent: 1 + - type: DeviceLinkSink + links: + - 156 +- proto: SignalButtonDirectional + entities: + - uid: 156 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 0.5,-2.5 + parent: 1 + - type: DeviceLinkSource + linkedPorts: + 520: + - Pressed: Toggle + - uid: 225 + components: + - type: Transform + pos: -9.5,-3.5 + parent: 1 + - type: DeviceLinkSource + linkedPorts: + 34: + - Pressed: Toggle + 35: + - Pressed: Toggle + 36: + - Pressed: Toggle +- proto: SignBridge + entities: + - uid: 354 + components: + - type: Transform + pos: -1.5,-1.5 + parent: 1 +- proto: SinkEmpty + entities: + - uid: 192 + components: + - type: Transform + pos: 7.5,-2.5 + parent: 1 +- proto: SMESBasic + entities: + - uid: 59 + components: + - type: Transform + pos: 4.5,-2.5 + parent: 1 +- proto: SubstationWallBasic + entities: + - uid: 52 + components: + - type: Transform + pos: 4.5,-3.5 + parent: 1 +- proto: SuitStorageEVAAlternate + entities: + - uid: 200 + components: + - type: Transform + pos: 3.5,-8.5 + parent: 1 +- proto: SuitStorageSalv + entities: + - uid: 40 + components: + - type: Transform + pos: -1.5,-2.5 + parent: 1 + - uid: 116 + components: + - type: Transform + pos: -3.5,-0.5 + parent: 1 +- proto: Table + entities: + - uid: 364 + components: + - type: Transform + pos: 9.5,-6.5 + parent: 1 + - uid: 521 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 10.5,-6.5 + parent: 1 +- proto: TableReinforced + entities: + - uid: 367 + components: + - type: Transform + pos: -0.5,0.5 + parent: 1 +- proto: Thruster + entities: + - uid: 349 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 10.5,-2.5 + parent: 1 + - uid: 351 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -9.5,-2.5 + parent: 1 + - uid: 368 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -8.5,-8.5 + parent: 1 + - uid: 369 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -7.5,-8.5 + parent: 1 + - uid: 370 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 9.5,-8.5 + parent: 1 + - uid: 371 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 8.5,-8.5 + parent: 1 + - uid: 374 + components: + - type: Transform + pos: -5.5,0.5 + parent: 1 + - uid: 375 + components: + - type: Transform + pos: 6.5,0.5 + parent: 1 +- proto: ToiletEmpty + entities: + - uid: 337 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 8.5,-2.5 + parent: 1 +- proto: VendingMachineTankDispenserEVA + entities: + - uid: 335 + components: + - type: Transform + pos: -2.5,-8.5 + parent: 1 +- proto: WallReinforced + entities: + - uid: 190 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -8.5,-7.5 + parent: 1 + - uid: 246 + components: + - type: Transform + pos: -9.5,-7.5 + parent: 1 + - uid: 295 + components: + - type: Transform + pos: 2.5,-10.5 + parent: 1 + - uid: 296 + components: + - type: Transform + pos: 5.5,-0.5 + parent: 1 + - uid: 306 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 2.5,-9.5 + parent: 1 + - uid: 313 + components: + - type: Transform + pos: -1.5,-10.5 + parent: 1 + - uid: 338 + components: + - type: Transform + pos: -8.5,-3.5 + parent: 1 + - uid: 339 + components: + - type: Transform + pos: -4.5,-0.5 + parent: 1 + - uid: 341 + components: + - type: Transform + pos: -9.5,-8.5 + parent: 1 + - uid: 346 + components: + - type: Transform + pos: -5.5,-0.5 + parent: 1 + - uid: 347 + components: + - type: Transform + pos: -4.5,0.5 + parent: 1 + - uid: 350 + components: + - type: Transform + pos: -6.5,-7.5 + parent: 1 + - uid: 380 + components: + - type: Transform + pos: -10.5,-7.5 + parent: 1 + - uid: 383 + components: + - type: Transform + pos: -7.5,-7.5 + parent: 1 + - uid: 386 + components: + - type: Transform + pos: 11.5,-7.5 + parent: 1 + - uid: 387 + components: + - type: Transform + pos: 10.5,-7.5 + parent: 1 + - uid: 388 + components: + - type: Transform + pos: 9.5,-7.5 + parent: 1 + - uid: 389 + components: + - type: Transform + pos: 8.5,-7.5 + parent: 1 + - uid: 390 + components: + - type: Transform + pos: 7.5,-7.5 + parent: 1 + - uid: 399 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -1.5,-9.5 + parent: 1 + - uid: 404 + components: + - type: Transform + pos: -9.5,-3.5 + parent: 1 + - uid: 405 + components: + - type: Transform + pos: -8.5,-2.5 + parent: 1 + - uid: 407 + components: + - type: Transform + pos: -6.5,-1.5 + parent: 1 + - uid: 411 + components: + - type: Transform + pos: 4.5,1.5 + parent: 1 + - uid: 412 + components: + - type: Transform + pos: 5.5,0.5 + parent: 1 + - uid: 413 + components: + - type: Transform + pos: 6.5,-0.5 + parent: 1 + - uid: 414 + components: + - type: Transform + pos: 7.5,-1.5 + parent: 1 + - uid: 415 + components: + - type: Transform + pos: 8.5,-1.5 + parent: 1 + - uid: 416 + components: + - type: Transform + pos: 9.5,-2.5 + parent: 1 + - uid: 417 + components: + - type: Transform + pos: 10.5,-3.5 + parent: 1 + - uid: 418 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -1.5,-8.5 + parent: 1 + - uid: 420 + components: + - type: Transform + pos: 2.5,-8.5 + parent: 1 + - uid: 423 + components: + - type: Transform + pos: 3.5,2.5 + parent: 1 + - uid: 425 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -6.5,-8.5 + parent: 1 + - uid: 426 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 7.5,-8.5 + parent: 1 + - uid: 427 + components: + - type: Transform + pos: 10.5,-8.5 + parent: 1 + - uid: 428 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -3.5,1.5 + parent: 1 + - uid: 429 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 0.5,3.5 + parent: 1 + - uid: 467 + components: + - type: Transform + pos: 4.5,-8.5 + parent: 1 + - uid: 473 + components: + - type: Transform + pos: -3.5,-8.5 + parent: 1 +- proto: WallReinforcedDiagonal + entities: + - uid: 298 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 9.5,-1.5 + parent: 1 + - uid: 300 + components: + - type: Transform + pos: -6.5,-0.5 + parent: 1 + - uid: 302 + components: + - type: Transform + pos: -8.5,-1.5 + parent: 1 + - uid: 307 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 11.5,-8.5 + parent: 1 + - uid: 308 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 4.5,2.5 + parent: 1 + - uid: 310 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 3.5,3.5 + parent: 1 + - uid: 311 + components: + - type: Transform + pos: -4.5,1.5 + parent: 1 + - uid: 312 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -10.5,-8.5 + parent: 1 + - uid: 316 + components: + - type: Transform + pos: -10.5,-3.5 + parent: 1 + - uid: 317 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 11.5,-3.5 + parent: 1 + - uid: 343 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 4.5,-9.5 + parent: 1 + - uid: 344 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -3.5,-9.5 + parent: 1 + - uid: 353 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 5.5,1.5 + parent: 1 + - uid: 361 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 7.5,-0.5 + parent: 1 +- proto: WallSolid + entities: + - uid: 15 + components: + - type: Transform + pos: 0.5,0.5 + parent: 1 + - uid: 63 + components: + - type: Transform + pos: 3.5,-3.5 + parent: 1 + - uid: 80 + components: + - type: Transform + pos: 6.5,-1.5 + parent: 1 + - uid: 101 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -4.5,-1.5 + parent: 1 + - uid: 134 + components: + - type: Transform + pos: -2.5,-5.5 + parent: 1 + - uid: 136 + components: + - type: Transform + pos: 2.5,-3.5 + parent: 1 + - uid: 137 + components: + - type: Transform + pos: -1.5,-5.5 + parent: 1 + - uid: 179 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 0.5,-5.5 + parent: 1 + - uid: 290 + components: + - type: Transform + pos: 0.5,-0.5 + parent: 1 + - uid: 359 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -3.5,-5.5 + parent: 1 + - uid: 434 + components: + - type: Transform + pos: -2.5,-1.5 + parent: 1 + - uid: 436 + components: + - type: Transform + pos: 0.5,-3.5 + parent: 1 + - uid: 438 + components: + - type: Transform + pos: 0.5,-1.5 + parent: 1 + - uid: 439 + components: + - type: Transform + pos: -1.5,-1.5 + parent: 1 + - uid: 441 + components: + - type: Transform + pos: 0.5,-2.5 + parent: 1 + - uid: 444 + components: + - type: Transform + pos: 0.5,2.5 + parent: 1 + - uid: 446 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 9.5,-3.5 + parent: 1 + - uid: 447 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 8.5,-3.5 + parent: 1 + - uid: 448 + components: + - type: Transform + pos: 0.5,1.5 + parent: 1 + - uid: 450 + components: + - type: Transform + pos: -3.5,-3.5 + parent: 1 + - uid: 452 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 7.5,-3.5 + parent: 1 + - uid: 454 + components: + - type: Transform + pos: -3.5,-2.5 + parent: 1 + - uid: 456 + components: + - type: Transform + pos: 5.5,-1.5 + parent: 1 + - uid: 457 + components: + - type: Transform + pos: 5.5,-2.5 + parent: 1 + - uid: 458 + components: + - type: Transform + pos: 5.5,-3.5 + parent: 1 + - uid: 459 + components: + - type: Transform + pos: 4.5,-3.5 + parent: 1 + - uid: 461 + components: + - type: Transform + pos: -3.5,-1.5 + parent: 1 + - uid: 463 + components: + - type: Transform + pos: -3.5,-4.5 + parent: 1 +- proto: WaterCooler + entities: + - uid: 161 + components: + - type: Transform + pos: 9.5,-4.5 + parent: 1 +- proto: Windoor + entities: + - uid: 487 + components: + - type: Transform + pos: 6.5,-3.5 + parent: 1 +- proto: Window + entities: + - uid: 282 + components: + - type: Transform + pos: 7.5,-4.5 + parent: 1 + - uid: 331 + components: + - type: Transform + pos: 7.5,-5.5 + parent: 1 +... diff --git a/Resources/Maps/Shuttles/pirateradio.yml b/Resources/Maps/Shuttles/pirateradio.yml new file mode 100644 index 0000000000..28cd42f185 --- /dev/null +++ b/Resources/Maps/Shuttles/pirateradio.yml @@ -0,0 +1,6750 @@ +meta: + format: 6 + postmapinit: false +tilemap: + 0: Space + 7: FloorAsteroidSand + 25: FloorCave + 32: FloorDark + 38: FloorDarkOffset + 41: FloorDarkPlastic + 65: FloorLino + 67: FloorMetalDiamond + 80: FloorReinforced + 89: FloorShuttleRed + 90: FloorShuttleWhite + 96: FloorSteelCheckerDark + 109: FloorTechMaint + 123: FloorWood + 126: Lattice +entities: +- proto: "" + entities: + - uid: 1 + components: + - type: MetaData + name: unknown + - type: Transform + - type: MapGrid + chunks: + 0,0: + ind: 0,0 + tiles: QwAAAAAAewAAAAADQQAAAAAAQQAAAAAAewAAAAAAQwAAAAAAQwAAAAAAQwAAAAAAKQAAAAAAJgAAAAAAJgAAAAAAJgAAAAAAKQAAAAADUAAAAAAAfgAAAAAAfgAAAAAAQwAAAAAAewAAAAAAQQAAAAAAQQAAAAAAewAAAAADQwAAAAAAWQAAAAAAQwAAAAAAKQAAAAABJgAAAAAAJgAAAAAAJgAAAAAAKQAAAAABUAAAAAAAfgAAAAAAfgAAAAAAQwAAAAAAewAAAAAAQQAAAAAAQQAAAAAAewAAAAABKQAAAAABWQAAAAAAQwAAAAAAKQAAAAADKQAAAAACKQAAAAACKQAAAAACKQAAAAACUAAAAAAAfgAAAAAAfgAAAAAAKQAAAAAAewAAAAADewAAAAABewAAAAAAewAAAAABQwAAAAAAWQAAAAAAQwAAAAAAKQAAAAAAKQAAAAAAKQAAAAAAKQAAAAAAKQAAAAAAQwAAAAAAGQAAAAABfgAAAAAAQwAAAAAAQwAAAAAAQwAAAAAAKQAAAAABQwAAAAAAQwAAAAAABwAAAAAABwAAAAAAKQAAAAAAKQAAAAAAKQAAAAAAKQAAAAAAKQAAAAAAGQAAAAABGQAAAAACGQAAAAAEQwAAAAAAbQAAAAAAQwAAAAAAbQAAAAAAbQAAAAAABwAAAAAAGQAAAAACGQAAAAAGGQAAAAACGQAAAAAEGQAAAAAFKQAAAAAAGQAAAAACGQAAAAAEGQAAAAABGQAAAAADQwAAAAAABwAAAAAAQwAAAAAAQwAAAAAAQwAAAAAABwAAAAAAfgAAAAAAKQAAAAAAKQAAAAAAKQAAAAAAKQAAAAAAKQAAAAAAKQAAAAAAKQAAAAAAKQAAAAAAGQAAAAADGQAAAAABGQAAAAAAGQAAAAACGQAAAAAEGQAAAAACGQAAAAAEGQAAAAACGQAAAAAEKQAAAAAAKQAAAAAAKQAAAAAAKQAAAAAAKQAAAAAAKQAAAAAAKQAAAAAAGQAAAAAGGQAAAAABGQAAAAAEGQAAAAAAGQAAAAABGQAAAAAAGQAAAAAAGQAAAAABGQAAAAAGKQAAAAAAKQAAAAAAKQAAAAAAKQAAAAAAKQAAAAAAKQAAAAAAKQAAAAAAGQAAAAAEGQAAAAADGQAAAAAFGQAAAAADGQAAAAABGQAAAAAGGQAAAAADGQAAAAAFGQAAAAABGQAAAAAAKQAAAAAAKQAAAAAAbQAAAAAAGQAAAAAAGQAAAAAAGQAAAAAAGQAAAAAAGQAAAAAFGQAAAAAEGQAAAAAAGQAAAAABfgAAAAAAGQAAAAACGQAAAAAFGQAAAAAAGQAAAAAAGQAAAAAAGQAAAAAAbQAAAAAAGQAAAAAAGQAAAAAAGQAAAAAAGQAAAAAAAAAAAAAAGQAAAAAEGQAAAAACGQAAAAABGQAAAAAFGQAAAAAFGQAAAAAFGQAAAAAAGQAAAAAAGQAAAAAAGQAAAAAAbQAAAAAAGQAAAAAAfgAAAAAAGQAAAAAAAAAAAAAAGQAAAAAAGQAAAAAEAAAAAAAAGQAAAAAAGQAAAAAGGQAAAAAAGQAAAAACGQAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + version: 6 + 0,-1: + ind: 0,-1 + tiles: GQAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAAGQAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAGGQAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAGAAAAAAAAGQAAAAAGGQAAAAABAAAAAAAAAAAAAAAAGQAAAAABAAAAAAAAAAAAAAAAGQAAAAACAAAAAAAAAAAAAAAAGQAAAAABGQAAAAADAAAAAAAAAAAAAAAAGQAAAAACGQAAAAAFGQAAAAAEGQAAAAACGQAAAAABGQAAAAAAGQAAAAABGQAAAAACGQAAAAABGQAAAAABAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAACAAAAAAAAAAAAAAAAGQAAAAAEGQAAAAADGQAAAAAGGQAAAAADGQAAAAABGQAAAAAAGQAAAAAFGQAAAAACGQAAAAABGQAAAAACGQAAAAAFAAAAAAAAGQAAAAACGQAAAAAAAAAAAAAAAAAAAAAAGQAAAAAFGQAAAAAEGQAAAAAAGQAAAAAFfgAAAAAAGQAAAAABGQAAAAACGQAAAAADGQAAAAAGGQAAAAADGQAAAAAGGQAAAAAEGQAAAAADAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAGGQAAAAAFGQAAAAADGQAAAAAAGQAAAAAFGQAAAAAFKQAAAAAAGQAAAAAGGQAAAAABGQAAAAAEGQAAAAAEGQAAAAAEGQAAAAAFGQAAAAAFGQAAAAAEAAAAAAAAQwAAAAAAQwAAAAAAQwAAAAAAQwAAAAAAGQAAAAABGQAAAAAEGQAAAAABGQAAAAAGGQAAAAABGQAAAAAAGQAAAAACGQAAAAABGQAAAAAEGQAAAAABGQAAAAAEGQAAAAAAWQAAAAAAQwAAAAAAQwAAAAAAQwAAAAAAWQAAAAAAQwAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAGQAAAAAEGQAAAAADGQAAAAAGGQAAAAADGQAAAAAGGQAAAAACGQAAAAABWQAAAAAAWQAAAAAAWQAAAAAAWQAAAAAAWQAAAAAAQwAAAAAAUAAAAAAAUAAAAAAAUAAAAAAAQwAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAGQAAAAAEGQAAAAABQwAAAAAAQwAAAAAAKQAAAAACQwAAAAAAQwAAAAAAQwAAAAAAewAAAAAAewAAAAACewAAAAACQwAAAAAAUAAAAAAAUAAAAAAAUAAAAAAAUAAAAAAAQwAAAAAAGQAAAAABQwAAAAAAYAAAAAACYAAAAAAAYAAAAAADYAAAAAAAQwAAAAAAewAAAAACewAAAAADewAAAAABQwAAAAAAKQAAAAAAKQAAAAABKQAAAAADKQAAAAAAQwAAAAAAGQAAAAAGQwAAAAAAYAAAAAACYAAAAAACYAAAAAAAYAAAAAABQwAAAAAAewAAAAACewAAAAABewAAAAADKQAAAAACKQAAAAAAKQAAAAACKQAAAAADKQAAAAADQwAAAAAAGQAAAAAGQwAAAAAAYAAAAAACYAAAAAADYAAAAAACYAAAAAACQwAAAAAAewAAAAACewAAAAACewAAAAAAQwAAAAAAKQAAAAADKQAAAAAAKQAAAAABKQAAAAABQwAAAAAAGQAAAAABQwAAAAAAQwAAAAAAKQAAAAACQwAAAAAAQwAAAAAAQwAAAAAAKQAAAAAAQwAAAAAAQwAAAAAAQwAAAAAAUAAAAAAAQwAAAAAAQwAAAAAAQwAAAAAAQwAAAAAAGQAAAAADKQAAAAACewAAAAABewAAAAABewAAAAACewAAAAADKQAAAAAAKQAAAAACKQAAAAABKQAAAAACKQAAAAABKQAAAAACKQAAAAACKQAAAAAAUAAAAAAAfgAAAAAAfgAAAAAA + version: 6 + -1,0: + ind: -1,0 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAADAAAAAAAAGQAAAAACGQAAAAAFGQAAAAAEGQAAAAADQwAAAAAAWQAAAAAAWQAAAAAAWQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAGGQAAAAACGQAAAAABGQAAAAAAfgAAAAAAGQAAAAADQwAAAAAAQwAAAAAAWQAAAAAAWQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAEGQAAAAAAGQAAAAAFGQAAAAAEQwAAAAAAQwAAAAAAQwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAAGQAAAAAAGQAAAAAGGQAAAAABGQAAAAAAGQAAAAAFQwAAAAAAbQAAAAAAbQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAFGQAAAAAAGQAAAAAEAAAAAAAAGQAAAAABGQAAAAAFGQAAAAAFGQAAAAADQwAAAAAAbQAAAAAAbQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAABAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAADGQAAAAAFfgAAAAAAGQAAAAACQwAAAAAAbQAAAAAAbQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAACGQAAAAAAGQAAAAAEGQAAAAACGQAAAAAAGQAAAAAAQwAAAAAAQwAAAAAAQwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAEGQAAAAACGQAAAAAGGQAAAAAGGQAAAAAEGQAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAGGQAAAAAFGQAAAAABGQAAAAAFGQAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAADGQAAAAABGQAAAAAAfgAAAAAAGQAAAAAFGQAAAAABGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAEGQAAAAACGQAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + version: 6 + -1,-1: + ind: -1,-1 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAEAAAAAAAAAAAAAAAAGQAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAGGQAAAAACAAAAAAAAGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAADGQAAAAADAAAAAAAAAAAAAAAAGQAAAAACGQAAAAACGQAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAABAAAAAAAAGQAAAAADGQAAAAABGQAAAAADGQAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAABGQAAAAAGGQAAAAAAGQAAAAAFGQAAAAABGQAAAAADGQAAAAAGGQAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAEGQAAAAAGGQAAAAAEGQAAAAAAGQAAAAAAGQAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAAAGQAAAAADGQAAAAAFGQAAAAACQwAAAAAAQwAAAAAAQwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAEGQAAAAAAGQAAAAAAGQAAAAAAQwAAAAAAIAAAAAADIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAEGQAAAAACGQAAAAAAGQAAAAAFGQAAAAABQwAAAAAAWQAAAAAAWQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAACGQAAAAAAGQAAAAAAGQAAAAADGQAAAAACGQAAAAAGGQAAAAAEQwAAAAAAWQAAAAAAWQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAADGQAAAAABGQAAAAACGQAAAAABGQAAAAAEQwAAAAAAQwAAAAAAQwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAGGQAAAAAGfgAAAAAAGQAAAAAAQwAAAAAAWgAAAAAAWgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAAGQAAAAAFGQAAAAABGQAAAAABGQAAAAACGQAAAAABGQAAAAAEQwAAAAAAWgAAAAAAWgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAGAAAAAAAAAAAAAAAAGQAAAAAAGQAAAAAAGQAAAAADGQAAAAAFQwAAAAAAKQAAAAACQwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAEGQAAAAAEGQAAAAABQwAAAAAAQwAAAAAAWQAAAAAAWQAAAAAA + version: 6 + 1,-1: + ind: 1,-1 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAEGQAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAEGQAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAADGQAAAAAFAAAAAAAAGQAAAAABGQAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAGGQAAAAABGQAAAAACGQAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAAGQAAAAAGGQAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAADGQAAAAAGGQAAAAABAAAAAAAAAAAAAAAAGQAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAEGQAAAAAGfgAAAAAAfgAAAAAAAAAAAAAAGQAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAFGQAAAAAFGQAAAAAAGQAAAAAAGQAAAAAAGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAGGQAAAAADGQAAAAAFGQAAAAABGQAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAACGQAAAAABGQAAAAADGQAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + version: 6 + 1,0: + ind: 1,0 + tiles: GQAAAAADGQAAAAABGQAAAAADGQAAAAADGQAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAAGQAAAAAAGQAAAAAEGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAACGQAAAAAAGQAAAAAGGQAAAAADGQAAAAACGQAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAADGQAAAAACGQAAAAABGQAAAAABGQAAAAAFGQAAAAAFGQAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAACGQAAAAAEGQAAAAADGQAAAAAAGQAAAAAAGQAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAEGQAAAAAAGQAAAAAGGQAAAAAFGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAAGQAAAAAAAAAAAAAAGQAAAAADGQAAAAADGQAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAAGQAAAAAAAAAAAAAAAAAAAAAAGQAAAAAGGQAAAAAAGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAABGQAAAAAAGQAAAAAAAAAAAAAAGQAAAAAEGQAAAAAAGQAAAAAGGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAAGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAFAAAAAAAAGQAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAAGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAAGQAAAAAAGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + version: 6 + - type: Broadphase + - type: Physics + bodyStatus: InAir + angularDamping: 0.05 + linearDamping: 0.05 + fixedRotation: False + bodyType: Dynamic + - type: Fixtures + fixtures: {} + - type: OccluderTree + - type: SpreaderGrid + - type: GridPathfinding + - type: Gravity + gravityShakeSound: !type:SoundPathSpecifier + path: /Audio/Effects/alert.ogg + - type: DecalGrid + chunkCollection: + version: 2 + nodes: + - node: + color: '#FFFFFFFF' + id: Arrows + decals: + 59: 12,8 + - node: + angle: 3.141592653589793 rad + color: '#FFFFFFFF' + id: Arrows + decals: + 60: 12,6 + - node: + color: '#FF0000FF' + id: BoxGreyscale + decals: + 58: 13,7 + - node: + color: '#FFFFFFFF' + id: Caution + decals: + 0: 3.018731,5.165427 + - node: + angle: 4.71238898038469 rad + color: '#FFFFFFFF' + id: StandClear + decals: + 54: 12,7 + - node: + color: '#FFFFFFFF' + id: WarnBox + decals: + 18: 3,4 + - node: + color: '#FFFFFFFF' + id: WarnCornerSmallNW + decals: + 40: 16,-2 + - node: + color: '#FFFFFFFF' + id: WarnCornerSmallSE + decals: + 32: 5,7 + - node: + color: '#FFFFFFFF' + id: WarnCornerSmallSW + decals: + 17: 4,7 + - node: + color: '#FFFFFFFF' + id: WarnLineE + decals: + 78: 12,-10 + 80: 6,-10 + - node: + color: '#FFFFFFFF' + id: WarnLineN + decals: + 16: 3,7 + 33: 6,7 + 35: 5,7 + 36: 4,7 + - node: + color: '#FFFFFFFF' + id: WarnLineS + decals: + 34: 7,6 + 37: 16,3 + 38: 16,2 + 39: 16,-1 + - node: + color: '#FFFFFFFF' + id: WarnLineW + decals: + 41: 11,-8 + 42: 10,-8 + 43: 7,-9 + 44: 6,-9 + 79: 6,-10 + - node: + color: '#FFFFFFFF' + id: WoodTrimThinCornerNe + decals: + 45: 12,4 + - node: + color: '#FFFFFFFF' + id: WoodTrimThinCornerNw + decals: + 46: 8,4 + - node: + color: '#FFFFFFFF' + id: WoodTrimThinCornerSe + decals: + 11: 12,-1 + - node: + color: '#FFFFFFFF' + id: WoodTrimThinCornerSw + decals: + 12: 8,-1 + - node: + color: '#FFFFFFFF' + id: WoodTrimThinInnerNe + decals: + 28: 1,-1 + - node: + color: '#FFFFFFFF' + id: WoodTrimThinInnerNw + decals: + 29: 4,-1 + - node: + color: '#FFFFFFFF' + id: WoodTrimThinInnerSe + decals: + 30: 1,3 + - node: + color: '#FFFFFFFF' + id: WoodTrimThinInnerSw + decals: + 31: 4,3 + - node: + color: '#FFFFFFFF' + id: WoodTrimThinLineE + decals: + 9: 12,1 + 10: 12,0 + 22: 1,2 + 23: 1,1 + 24: 1,0 + 50: 12,3 + 51: 12,2 + - node: + color: '#FFFFFFFF' + id: WoodTrimThinLineN + decals: + 26: 2,-1 + 27: 3,-1 + 47: 9,4 + 48: 10,4 + 49: 11,4 + - node: + color: '#FFFFFFFF' + id: WoodTrimThinLineS + decals: + 13: 9,-1 + 14: 10,-1 + 15: 11,-1 + 25: 2,3 + - node: + color: '#FFFFFFFF' + id: WoodTrimThinLineW + decals: + 3: 8,0 + 4: 8,1 + 19: 4,2 + 20: 4,1 + 21: 4,0 + 52: 8,3 + 53: 8,2 + - node: + color: '#FF0000FF' + id: space + decals: + 1: 3.018731,4.884177 + - node: + color: '#FFFFFFFF' + id: syndlogo1 + decals: + 74: 8,8 + - node: + color: '#FFFFFFFF' + id: syndlogo10 + decals: + 67: 9,6 + - node: + color: '#FFFFFFFF' + id: syndlogo11 + decals: + 68: 10,6 + - node: + color: '#FFFFFFFF' + id: syndlogo12 + decals: + 69: 11,6 + - node: + color: '#FFFFFFFF' + id: syndlogo13 + decals: + 77: 10,6 + - node: + color: '#FFFFFFFF' + id: syndlogo2 + decals: + 62: 9,8 + - node: + color: '#FFFFFFFF' + id: syndlogo3 + decals: + 63: 10,8 + - node: + color: '#FFFFFFFF' + id: syndlogo4 + decals: + 73: 11,8 + - node: + color: '#FFFFFFFF' + id: syndlogo5 + decals: + 75: 8,7 + - node: + color: '#FFFFFFFF' + id: syndlogo6 + decals: + 64: 9,7 + - node: + color: '#FFFFFFFF' + id: syndlogo7 + decals: + 65: 10,7 + - node: + color: '#FFFFFFFF' + id: syndlogo8 + decals: + 72: 11,7 + - node: + color: '#FFFFFFFF' + id: syndlogo9 + decals: + 76: 8,6 + - type: GridAtmosphere + version: 2 + data: + tiles: + 0,0: + 0: 65535 + 0,-1: + 0: 65535 + -1,0: + 0: 65535 + -1,-1: + 0: 65535 + 0,1: + 0: 65535 + 1,0: + 0: 65535 + 1,1: + 0: 65535 + 2,0: + 0: 65535 + 0,-2: + 0: 65535 + 1,-2: + 0: 65535 + 1,-1: + 0: 65535 + 2,-1: + 0: 65535 + -1,1: + 0: 65535 + -1,-2: + 0: 65535 + 3,0: + 0: 65535 + 3,-1: + 0: 65535 + 0,2: + 0: 61439 + 1,2: + 0: 32767 + 2,1: + 0: 65535 + 3,1: + 0: 65535 + 0,-3: + 0: 65535 + 1,-3: + 0: 65535 + 2,-2: + 0: 65535 + 3,-2: + 0: 65535 + -2,0: + 0: 65279 + -2,1: + 0: 53230 + -1,2: + 0: 12031 + -2,-2: + 0: 65534 + -2,-1: + 0: 61182 + -2,-3: + 0: 60660 + -1,-3: + 0: 65535 + 4,-1: + 0: 65527 + 4,0: + 0: 65535 + 4,1: + 0: 2559 + 0,-4: + 0: 64897 + 2,-3: + 0: 65527 + 4,-2: + 0: 30715 + 0,3: + 0: 27 + 1,3: + 0: 2189 + 2,2: + 0: 2255 + 3,2: + 0: 9 + 1,-4: + 0: 62483 + 2,-4: + 0: 12832 + 3,-4: + 0: 8960 + 3,-3: + 0: 63251 + -3,1: + 0: 2094 + -3,0: + 0: 32964 + -2,2: + 0: 232 + -1,3: + 0: 130 + -3,-2: + 0: 3136 + -3,-1: + 0: 1216 + -2,-4: + 0: 25088 + -1,-4: + 0: 60312 + 4,-3: + 0: 4902 + 5,-2: + 0: 8193 + 5,-1: + 0: 306 + 4,2: + 0: 1 + 5,0: + 0: 29441 + 5,1: + 0: 29459 + 5,2: + 0: 175 + uniqueMixes: + - volume: 2500 + temperature: 293.15 + moles: + - 21.824879 + - 82.10312 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + chunkSize: 4 + - type: GasTileOverlay + - type: RadiationGridResistance + - type: Shuttle + - type: NavMap +- proto: AirAlarm + entities: + - uid: 576 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 5.5,3.5 + parent: 1 + - type: DeviceList + devices: + - 707 + - 719 + - 708 + - 720 + - 721 + - 709 + - 726 + - 714 + - 718 + - 713 + - 710 + - 711 + - 722 + - 723 + - 712 + - 725 + - 384 + - 716 + - 724 +- proto: AirCanister + entities: + - uid: 3 + components: + - type: Transform + anchored: True + pos: 1.5,5.5 + parent: 1 + - type: Physics + bodyType: Static +- proto: AirlockExternalShuttleSyndicateLocked + entities: + - uid: 4 + components: + - type: MetaData + desc: The ominous red light fills you with dread + name: suspicious airlock + - type: Transform + pos: 3.5,6.5 + parent: 1 + - type: DeviceLinkSink + links: + - 806 + missingComponents: + - Docking + - uid: 414 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 11.5,11.5 + parent: 1 +- proto: AirlockHatchSyndicate + entities: + - uid: 405 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 11.5,9.5 + parent: 1 +- proto: AirlockSyndicate + entities: + - uid: 5 + components: + - type: Transform + pos: 5.5,2.5 + parent: 1 + - uid: 15 + components: + - type: MetaData + name: armory airlock + - type: Transform + pos: 2.5,-5.5 + parent: 1 +- proto: AirlockSyndicateGlass + entities: + - uid: 6 + components: + - type: MetaData + name: kitchen airlock + - type: Transform + pos: 2.5,-1.5 + parent: 1 + - uid: 7 + components: + - type: MetaData + name: rec-room airlock + - type: Transform + rot: -1.5707963267948966 rad + pos: 6.5,-1.5 + parent: 1 + - uid: 8 + components: + - type: Transform + pos: 0.5,-0.5 + parent: 1 + - uid: 9 + components: + - type: MetaData + name: hydroponics airlock + - type: Transform + pos: 9.5,-3.5 + parent: 1 + - uid: 10 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 5.5,-0.5 + parent: 1 +- proto: AirlockSyndicateGlassLocked + entities: + - uid: 11 + components: + - type: MetaData + name: to-externals airlock + - type: Transform + pos: 3.5,4.5 + parent: 1 + - uid: 401 + components: + - type: Transform + pos: 11.5,5.5 + parent: 1 + - uid: 950 + components: + - type: MetaData + name: bridge airlock + - type: Transform + pos: 7.5,-0.5 + parent: 1 +- proto: AirlockSyndicateLocked + entities: + - uid: 13 + components: + - type: MetaData + name: air supply + - type: Transform + pos: 2.5,5.5 + parent: 1 + - uid: 14 + components: + - type: MetaData + name: outpost maintenance + - type: Transform + pos: 0.5,3.5 + parent: 1 +- proto: AlwaysPoweredLightColoredFrostyBlue + entities: + - uid: 16 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -0.5,-2.5 + parent: 1 +- proto: AlwaysPoweredLightColoredRed + entities: + - uid: 17 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 4.5,7.5 + parent: 1 +- proto: AlwaysPoweredLightPostSmallRed + entities: + - uid: 1006 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 13.5,11.5 + parent: 1 +- proto: AlwaysPoweredSmallLightMaintenanceRed + entities: + - uid: 953 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 4.5,5.5 + parent: 1 + - uid: 962 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 1.5,5.5 + parent: 1 +- proto: APCBasic + entities: + - uid: 24 + components: + - type: Transform + pos: 2.5,4.5 + parent: 1 + - uid: 25 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 3.5,-5.5 + parent: 1 + - uid: 26 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 7.5,0.5 + parent: 1 + - type: Apc + hasAccess: True + lastExternalState: Good + lastChargeState: Full +- proto: AsteroidAltRock + entities: + - uid: 27 + components: + - type: Transform + pos: -5.5,2.5 + parent: 1 + - uid: 28 + components: + - type: Transform + pos: -4.5,-0.5 + parent: 1 + - uid: 29 + components: + - type: Transform + pos: 4.5,8.5 + parent: 1 + - uid: 31 + components: + - type: Transform + pos: 5.5,8.5 + parent: 1 + - uid: 33 + components: + - type: Transform + pos: 2.5,7.5 + parent: 1 + - uid: 34 + components: + - type: Transform + pos: 0.5,7.5 + parent: 1 + - uid: 35 + components: + - type: Transform + pos: 3.5,8.5 + parent: 1 + - uid: 36 + components: + - type: Transform + pos: 14.5,3.5 + parent: 1 + - uid: 37 + components: + - type: Transform + pos: 14.5,4.5 + parent: 1 + - uid: 39 + components: + - type: Transform + pos: 16.5,-9.5 + parent: 1 + - uid: 40 + components: + - type: Transform + pos: 2.5,8.5 + parent: 1 + - uid: 45 + components: + - type: Transform + pos: -3.5,-10.5 + parent: 1 + - uid: 47 + components: + - type: Transform + pos: -5.5,-6.5 + parent: 1 + - uid: 48 + components: + - type: Transform + pos: -3.5,-5.5 + parent: 1 + - uid: 49 + components: + - type: Transform + pos: -3.5,-6.5 + parent: 1 + - uid: 50 + components: + - type: Transform + pos: -4.5,-2.5 + parent: 1 + - uid: 51 + components: + - type: Transform + pos: -3.5,-3.5 + parent: 1 + - uid: 52 + components: + - type: Transform + pos: -4.5,-4.5 + parent: 1 + - uid: 53 + components: + - type: Transform + pos: -5.5,-4.5 + parent: 1 + - uid: 55 + components: + - type: Transform + pos: -0.5,-12.5 + parent: 1 + - uid: 56 + components: + - type: Transform + pos: -4.5,6.5 + parent: 1 + - uid: 57 + components: + - type: Transform + pos: -3.5,6.5 + parent: 1 + - uid: 58 + components: + - type: Transform + pos: -3.5,7.5 + parent: 1 + - uid: 59 + components: + - type: Transform + pos: -2.5,7.5 + parent: 1 + - uid: 60 + components: + - type: Transform + pos: -1.5,7.5 + parent: 1 + - uid: 61 + components: + - type: Transform + pos: 0.5,8.5 + parent: 1 + - uid: 62 + components: + - type: Transform + pos: 0.5,10.5 + parent: 1 + - uid: 63 + components: + - type: Transform + pos: 3.5,9.5 + parent: 1 + - uid: 64 + components: + - type: Transform + pos: 4.5,9.5 + parent: 1 + - uid: 65 + components: + - type: Transform + pos: -0.5,8.5 + parent: 1 + - uid: 66 + components: + - type: Transform + pos: 1.5,8.5 + parent: 1 + - uid: 68 + components: + - type: Transform + pos: 0.5,9.5 + parent: 1 + - uid: 69 + components: + - type: Transform + pos: -0.5,7.5 + parent: 1 + - uid: 70 + components: + - type: Transform + pos: -4.5,7.5 + parent: 1 + - uid: 71 + components: + - type: Transform + pos: -8.5,6.5 + parent: 1 + - uid: 72 + components: + - type: Transform + pos: -1.5,-12.5 + parent: 1 + - uid: 73 + components: + - type: Transform + pos: -4.5,2.5 + parent: 1 + - uid: 74 + components: + - type: Transform + pos: -4.5,0.5 + parent: 1 + - uid: 75 + components: + - type: Transform + pos: -4.5,-1.5 + parent: 1 + - uid: 76 + components: + - type: Transform + pos: -3.5,-4.5 + parent: 1 + - uid: 77 + components: + - type: Transform + pos: -3.5,-2.5 + parent: 1 + - uid: 78 + components: + - type: Transform + pos: -3.5,-7.5 + parent: 1 + - uid: 79 + components: + - type: Transform + pos: -4.5,-8.5 + parent: 1 + - uid: 80 + components: + - type: Transform + pos: -4.5,-5.5 + parent: 1 + - uid: 81 + components: + - type: Transform + pos: -4.5,-9.5 + parent: 1 + - uid: 82 + components: + - type: Transform + pos: -2.5,-9.5 + parent: 1 + - uid: 83 + components: + - type: Transform + pos: -1.5,-9.5 + parent: 1 + - uid: 84 + components: + - type: Transform + pos: 16.5,0.5 + parent: 1 + - uid: 85 + components: + - type: Transform + pos: 16.5,1.5 + parent: 1 + - uid: 86 + components: + - type: Transform + pos: 3.5,-10.5 + parent: 1 + - uid: 88 + components: + - type: Transform + pos: 6.5,-10.5 + parent: 1 + - uid: 89 + components: + - type: Transform + pos: 7.5,-12.5 + parent: 1 + - uid: 90 + components: + - type: Transform + pos: 9.5,-7.5 + parent: 1 + - uid: 91 + components: + - type: Transform + pos: 8.5,-8.5 + parent: 1 + - uid: 92 + components: + - type: Transform + pos: 13.5,-7.5 + parent: 1 + - uid: 93 + components: + - type: Transform + pos: 14.5,-6.5 + parent: 1 + - uid: 94 + components: + - type: Transform + pos: 15.5,-6.5 + parent: 1 + - uid: 95 + components: + - type: Transform + pos: 15.5,-5.5 + parent: 1 + - uid: 96 + components: + - type: Transform + pos: 15.5,-4.5 + parent: 1 + - uid: 97 + components: + - type: Transform + pos: 16.5,-6.5 + parent: 1 + - uid: 98 + components: + - type: Transform + pos: 15.5,-1.5 + parent: 1 + - uid: 99 + components: + - type: Transform + pos: 15.5,-2.5 + parent: 1 + - uid: 100 + components: + - type: Transform + pos: 18.5,-1.5 + parent: 1 + - uid: 101 + components: + - type: Transform + pos: 16.5,-5.5 + parent: 1 + - uid: 102 + components: + - type: Transform + pos: 16.5,-4.5 + parent: 1 + - uid: 103 + components: + - type: Transform + pos: -3.5,-11.5 + parent: 1 + - uid: 104 + components: + - type: Transform + pos: -1.5,-11.5 + parent: 1 + - uid: 105 + components: + - type: Transform + pos: -2.5,11.5 + parent: 1 + - uid: 106 + components: + - type: Transform + pos: 2.5,-12.5 + parent: 1 + - uid: 107 + components: + - type: Transform + pos: 5.5,-10.5 + parent: 1 + - uid: 108 + components: + - type: Transform + pos: 7.5,-11.5 + parent: 1 + - uid: 109 + components: + - type: Transform + pos: 15.5,-8.5 + parent: 1 + - uid: 110 + components: + - type: Transform + pos: 19.5,2.5 + parent: 1 + - uid: 111 + components: + - type: Transform + pos: 17.5,5.5 + parent: 1 + - uid: 112 + components: + - type: Transform + pos: 8.5,-12.5 + parent: 1 + - uid: 113 + components: + - type: Transform + pos: 17.5,0.5 + parent: 1 + - uid: 114 + components: + - type: Transform + pos: 17.5,-2.5 + parent: 1 + - uid: 115 + components: + - type: Transform + pos: 18.5,-0.5 + parent: 1 + - uid: 116 + components: + - type: Transform + pos: 18.5,3.5 + parent: 1 + - uid: 117 + components: + - type: Transform + pos: 17.5,3.5 + parent: 1 + - uid: 119 + components: + - type: Transform + pos: 19.5,3.5 + parent: 1 + - uid: 120 + components: + - type: Transform + pos: 19.5,4.5 + parent: 1 + - uid: 121 + components: + - type: Transform + pos: 17.5,4.5 + parent: 1 + - uid: 122 + components: + - type: Transform + pos: 18.5,4.5 + parent: 1 + - uid: 123 + components: + - type: Transform + pos: 16.5,4.5 + parent: 1 + - uid: 124 + components: + - type: Transform + pos: 16.5,5.5 + parent: 1 + - uid: 138 + components: + - type: Transform + pos: 17.5,-0.5 + parent: 1 + - uid: 139 + components: + - type: Transform + pos: 20.5,0.5 + parent: 1 + - uid: 140 + components: + - type: Transform + pos: 17.5,1.5 + parent: 1 + - uid: 141 + components: + - type: Transform + pos: -0.5,13.5 + parent: 1 + - uid: 142 + components: + - type: Transform + pos: 0.5,13.5 + parent: 1 + - uid: 143 + components: + - type: Transform + pos: 19.5,1.5 + parent: 1 + - uid: 144 + components: + - type: Transform + pos: 21.5,2.5 + parent: 1 + - uid: 145 + components: + - type: Transform + pos: -6.5,9.5 + parent: 1 + - uid: 146 + components: + - type: Transform + pos: 19.5,5.5 + parent: 1 + - uid: 147 + components: + - type: Transform + pos: 20.5,6.5 + parent: 1 + - uid: 148 + components: + - type: Transform + pos: 20.5,7.5 + parent: 1 + - uid: 149 + components: + - type: Transform + pos: 1.5,12.5 + parent: 1 + - uid: 150 + components: + - type: Transform + pos: 20.5,5.5 + parent: 1 + - uid: 151 + components: + - type: Transform + pos: 22.5,8.5 + parent: 1 + - uid: 152 + components: + - type: Transform + pos: 21.5,8.5 + parent: 1 + - uid: 153 + components: + - type: Transform + pos: 21.5,6.5 + parent: 1 + - uid: 154 + components: + - type: Transform + pos: 21.5,7.5 + parent: 1 + - uid: 155 + components: + - type: Transform + pos: -5.5,9.5 + parent: 1 + - uid: 156 + components: + - type: Transform + pos: 20.5,2.5 + parent: 1 + - uid: 157 + components: + - type: Transform + pos: 16.5,-2.5 + parent: 1 + - uid: 158 + components: + - type: Transform + pos: -2.5,12.5 + parent: 1 + - uid: 159 + components: + - type: Transform + pos: -10.5,5.5 + parent: 1 + - uid: 160 + components: + - type: Transform + pos: 17.5,-3.5 + parent: 1 + - uid: 161 + components: + - type: Transform + pos: 17.5,-4.5 + parent: 1 + - uid: 162 + components: + - type: Transform + pos: 18.5,-2.5 + parent: 1 + - uid: 163 + components: + - type: Transform + pos: 14.5,-7.5 + parent: 1 + - uid: 164 + components: + - type: Transform + pos: 12.5,-7.5 + parent: 1 + - uid: 165 + components: + - type: Transform + pos: 9.5,-8.5 + parent: 1 + - uid: 166 + components: + - type: Transform + pos: 11.5,-10.5 + parent: 1 + - uid: 167 + components: + - type: Transform + pos: 11.5,-8.5 + parent: 1 + - uid: 168 + components: + - type: Transform + pos: 14.5,-8.5 + parent: 1 + - uid: 169 + components: + - type: Transform + pos: 12.5,-10.5 + parent: 1 + - uid: 170 + components: + - type: Transform + pos: 10.5,-11.5 + parent: 1 + - uid: 171 + components: + - type: Transform + pos: 8.5,-10.5 + parent: 1 + - uid: 172 + components: + - type: Transform + pos: 9.5,-10.5 + parent: 1 + - uid: 173 + components: + - type: Transform + pos: -9.5,0.5 + parent: 1 + - uid: 174 + components: + - type: Transform + pos: 13.5,-8.5 + parent: 1 + - uid: 175 + components: + - type: Transform + pos: 17.5,-6.5 + parent: 1 + - uid: 176 + components: + - type: Transform + pos: 12.5,-8.5 + parent: 1 + - uid: 177 + components: + - type: Transform + pos: 15.5,-3.5 + parent: 1 + - uid: 178 + components: + - type: Transform + pos: 16.5,-3.5 + parent: 1 + - uid: 179 + components: + - type: Transform + pos: 17.5,-7.5 + parent: 1 + - uid: 180 + components: + - type: Transform + pos: 16.5,-7.5 + parent: 1 + - uid: 181 + components: + - type: Transform + pos: 15.5,-7.5 + parent: 1 + - uid: 182 + components: + - type: Transform + pos: -9.5,-1.5 + parent: 1 + - uid: 183 + components: + - type: Transform + pos: 10.5,-10.5 + parent: 1 + - uid: 184 + components: + - type: Transform + pos: 9.5,-11.5 + parent: 1 + - uid: 185 + components: + - type: Transform + pos: 0.5,-15.5 + parent: 1 + - uid: 187 + components: + - type: Transform + pos: -9.5,-5.5 + parent: 1 + - uid: 188 + components: + - type: Transform + pos: -7.5,-10.5 + parent: 1 + - uid: 189 + components: + - type: Transform + pos: 17.5,-5.5 + parent: 1 + - uid: 190 + components: + - type: Transform + pos: -6.5,-10.5 + parent: 1 + - uid: 191 + components: + - type: Transform + pos: -5.5,-11.5 + parent: 1 + - uid: 192 + components: + - type: Transform + pos: 14.5,-9.5 + parent: 1 + - uid: 193 + components: + - type: Transform + pos: -5.5,-12.5 + parent: 1 + - uid: 194 + components: + - type: Transform + pos: 18.5,-6.5 + parent: 1 + - uid: 195 + components: + - type: Transform + pos: -6.5,-12.5 + parent: 1 + - uid: 196 + components: + - type: Transform + pos: 20.5,-2.5 + parent: 1 + - uid: 197 + components: + - type: Transform + pos: 20.5,-1.5 + parent: 1 + - uid: 198 + components: + - type: Transform + pos: 21.5,4.5 + parent: 1 + - uid: 199 + components: + - type: Transform + pos: 20.5,3.5 + parent: 1 + - uid: 200 + components: + - type: Transform + pos: 20.5,4.5 + parent: 1 + - uid: 203 + components: + - type: Transform + pos: -3.5,-13.5 + parent: 1 + - uid: 204 + components: + - type: Transform + pos: 3.5,10.5 + parent: 1 + - uid: 205 + components: + - type: Transform + pos: 2.5,10.5 + parent: 1 + - uid: 206 + components: + - type: Transform + pos: 1.5,10.5 + parent: 1 + - uid: 207 + components: + - type: Transform + pos: 5.5,10.5 + parent: 1 + - uid: 208 + components: + - type: Transform + pos: 5.5,9.5 + parent: 1 + - uid: 211 + components: + - type: Transform + pos: 3.5,11.5 + parent: 1 + - uid: 212 + components: + - type: Transform + pos: 1.5,9.5 + parent: 1 + - uid: 213 + components: + - type: Transform + pos: 2.5,9.5 + parent: 1 + - uid: 214 + components: + - type: Transform + pos: -0.5,9.5 + parent: 1 + - uid: 215 + components: + - type: Transform + pos: -1.5,8.5 + parent: 1 + - uid: 216 + components: + - type: Transform + pos: -0.5,10.5 + parent: 1 + - uid: 217 + components: + - type: Transform + pos: -2.5,9.5 + parent: 1 + - uid: 218 + components: + - type: Transform + pos: -3.5,-14.5 + parent: 1 + - uid: 219 + components: + - type: Transform + pos: -1.5,9.5 + parent: 1 + - uid: 220 + components: + - type: Transform + pos: -3.5,5.5 + parent: 1 + - uid: 221 + components: + - type: Transform + pos: -7.5,6.5 + parent: 1 + - uid: 222 + components: + - type: Transform + pos: -5.5,5.5 + parent: 1 + - uid: 223 + components: + - type: Transform + pos: -5.5,4.5 + parent: 1 + - uid: 225 + components: + - type: Transform + pos: -1.5,10.5 + parent: 1 + - uid: 226 + components: + - type: Transform + pos: -0.5,-14.5 + parent: 1 + - uid: 227 + components: + - type: Transform + pos: 9.5,-13.5 + parent: 1 + - uid: 228 + components: + - type: Transform + pos: 3.5,12.5 + parent: 1 + - uid: 229 + components: + - type: Transform + pos: 4.5,-15.5 + parent: 1 + - uid: 230 + components: + - type: Transform + pos: 4.5,-14.5 + parent: 1 + - uid: 231 + components: + - type: Transform + pos: 5.5,-15.5 + parent: 1 + - uid: 232 + components: + - type: Transform + pos: 9.5,-14.5 + parent: 1 + - uid: 234 + components: + - type: Transform + pos: 12.5,-13.5 + parent: 1 + - uid: 235 + components: + - type: Transform + pos: 13.5,-12.5 + parent: 1 + - uid: 236 + components: + - type: Transform + pos: -4.5,8.5 + parent: 1 + - uid: 237 + components: + - type: Transform + pos: -3.5,8.5 + parent: 1 + - uid: 238 + components: + - type: Transform + pos: -2.5,8.5 + parent: 1 + - uid: 239 + components: + - type: Transform + pos: -6.5,6.5 + parent: 1 + - uid: 240 + components: + - type: Transform + pos: -5.5,6.5 + parent: 1 + - uid: 241 + components: + - type: Transform + pos: -4.5,4.5 + parent: 1 + - uid: 242 + components: + - type: Transform + pos: 13.5,-11.5 + parent: 1 + - uid: 243 + components: + - type: Transform + pos: -9.5,4.5 + parent: 1 + - uid: 244 + components: + - type: Transform + pos: -8.5,4.5 + parent: 1 + - uid: 245 + components: + - type: Transform + pos: -8.5,3.5 + parent: 1 + - uid: 246 + components: + - type: Transform + pos: -7.5,3.5 + parent: 1 + - uid: 247 + components: + - type: Transform + pos: -6.5,3.5 + parent: 1 + - uid: 248 + components: + - type: Transform + pos: -5.5,3.5 + parent: 1 + - uid: 249 + components: + - type: Transform + pos: -6.5,5.5 + parent: 1 + - uid: 250 + components: + - type: Transform + pos: -4.5,1.5 + parent: 1 + - uid: 251 + components: + - type: Transform + pos: -6.5,2.5 + parent: 1 + - uid: 252 + components: + - type: Transform + pos: -8.5,1.5 + parent: 1 + - uid: 253 + components: + - type: Transform + pos: -7.5,0.5 + parent: 1 + - uid: 254 + components: + - type: Transform + pos: -5.5,0.5 + parent: 1 + - uid: 255 + components: + - type: Transform + pos: -6.5,0.5 + parent: 1 + - uid: 256 + components: + - type: Transform + pos: 18.5,-11.5 + parent: 1 + - uid: 257 + components: + - type: Transform + pos: -5.5,-0.5 + parent: 1 + - uid: 258 + components: + - type: Transform + pos: -5.5,-1.5 + parent: 1 + - uid: 259 + components: + - type: Transform + pos: 17.5,-10.5 + parent: 1 + - uid: 260 + components: + - type: Transform + pos: -8.5,-2.5 + parent: 1 + - uid: 261 + components: + - type: Transform + pos: -7.5,-2.5 + parent: 1 + - uid: 262 + components: + - type: Transform + pos: 16.5,-8.5 + parent: 1 + - uid: 263 + components: + - type: Transform + pos: 17.5,-9.5 + parent: 1 + - uid: 264 + components: + - type: Transform + pos: -3.5,-1.5 + parent: 1 + - uid: 265 + components: + - type: Transform + pos: 19.5,-7.5 + parent: 1 + - uid: 266 + components: + - type: Transform + pos: -5.5,-2.5 + parent: 1 + - uid: 267 + components: + - type: Transform + pos: -6.5,-7.5 + parent: 1 + - uid: 268 + components: + - type: Transform + pos: -4.5,-6.5 + parent: 1 + - uid: 269 + components: + - type: Transform + pos: -4.5,-10.5 + parent: 1 + - uid: 270 + components: + - type: Transform + pos: -4.5,-7.5 + parent: 1 + - uid: 271 + components: + - type: Transform + pos: -5.5,-8.5 + parent: 1 + - uid: 272 + components: + - type: Transform + pos: -3.5,-8.5 + parent: 1 + - uid: 273 + components: + - type: Transform + pos: -2.5,-11.5 + parent: 1 + - uid: 274 + components: + - type: Transform + pos: -3.5,-9.5 + parent: 1 + - uid: 275 + components: + - type: Transform + pos: -1.5,-10.5 + parent: 1 + - uid: 276 + components: + - type: Transform + pos: -5.5,-7.5 + parent: 1 + - uid: 277 + components: + - type: Transform + pos: -2.5,-10.5 + parent: 1 + - uid: 278 + components: + - type: Transform + pos: -0.5,-10.5 + parent: 1 + - uid: 279 + components: + - type: Transform + pos: -0.5,-11.5 + parent: 1 + - uid: 280 + components: + - type: Transform + pos: 0.5,-13.5 + parent: 1 + - uid: 281 + components: + - type: Transform + pos: 2.5,-10.5 + parent: 1 + - uid: 282 + components: + - type: Transform + pos: 2.5,-11.5 + parent: 1 + - uid: 283 + components: + - type: Transform + pos: 20.5,-7.5 + parent: 1 + - uid: 284 + components: + - type: Transform + pos: 6.5,-13.5 + parent: 1 + - uid: 285 + components: + - type: Transform + pos: 5.5,-9.5 + parent: 1 + - uid: 286 + components: + - type: Transform + pos: 6.5,-11.5 + parent: 1 + - uid: 287 + components: + - type: Transform + pos: 7.5,-10.5 + parent: 1 + - uid: 288 + components: + - type: Transform + pos: 21.5,-4.5 + parent: 1 + - uid: 290 + components: + - type: Transform + pos: 21.5,-3.5 + parent: 1 + - uid: 291 + components: + - type: Transform + pos: 3.5,-11.5 + parent: 1 + - uid: 292 + components: + - type: Transform + pos: 5.5,-11.5 + parent: 1 + - uid: 293 + components: + - type: Transform + pos: 3.5,-13.5 + parent: 1 + - uid: 294 + components: + - type: Transform + pos: 0.5,-10.5 + parent: 1 + - uid: 295 + components: + - type: Transform + pos: -0.5,-9.5 + parent: 1 + - uid: 296 + components: + - type: Transform + pos: -7.5,-6.5 + parent: 1 + - uid: 297 + components: + - type: Transform + pos: -5.5,-5.5 + parent: 1 + - uid: 298 + components: + - type: Transform + pos: 8.5,-11.5 + parent: 1 + - uid: 299 + components: + - type: Transform + pos: 4.5,-12.5 + parent: 1 + - uid: 300 + components: + - type: Transform + pos: 4.5,-11.5 + parent: 1 + - uid: 301 + components: + - type: Transform + pos: 22.5,3.5 + parent: 1 + - uid: 302 + components: + - type: Transform + pos: 0.5,-11.5 + parent: 1 + - uid: 303 + components: + - type: Transform + pos: 0.5,-12.5 + parent: 1 + - uid: 304 + components: + - type: Transform + pos: 1.5,-11.5 + parent: 1 + - uid: 305 + components: + - type: Transform + pos: 23.5,9.5 + parent: 1 + - uid: 312 + components: + - type: Transform + pos: -6.5,-6.5 + parent: 1 + - uid: 313 + components: + - type: Transform + pos: -7.5,-4.5 + parent: 1 + - uid: 314 + components: + - type: Transform + pos: -6.5,-4.5 + parent: 1 + - uid: 315 + components: + - type: Transform + pos: -6.5,-2.5 + parent: 1 + - uid: 316 + components: + - type: Transform + pos: -6.5,-1.5 + parent: 1 + - uid: 317 + components: + - type: Transform + pos: -6.5,1.5 + parent: 1 + - uid: 318 + components: + - type: Transform + pos: -7.5,1.5 + parent: 1 + - uid: 319 + components: + - type: Transform + pos: 3.5,-9.5 + parent: 1 + - uid: 320 + components: + - type: Transform + pos: 4.5,-9.5 + parent: 1 + - uid: 321 + components: + - type: Transform + pos: 2.5,-9.5 + parent: 1 + - uid: 326 + components: + - type: Transform + pos: -3.5,4.5 + parent: 1 + - uid: 334 + components: + - type: Transform + pos: 0.5,-9.5 + parent: 1 + - uid: 337 + components: + - type: Transform + pos: 1.5,7.5 + parent: 1 + - uid: 339 + components: + - type: Transform + pos: 1.5,-10.5 + parent: 1 + - uid: 373 + components: + - type: Transform + pos: 1.5,-9.5 + parent: 1 + - uid: 374 + components: + - type: Transform + pos: -3.5,2.5 + parent: 1 + - uid: 609 + components: + - type: Transform + pos: -4.5,3.5 + parent: 1 + - uid: 616 + components: + - type: Transform + pos: -5.5,-3.5 + parent: 1 + - uid: 818 + components: + - type: Transform + pos: -3.5,3.5 + parent: 1 + - uid: 884 + components: + - type: Transform + pos: 17.5,-1.5 + parent: 1 +- proto: AsteroidAltRockMining + entities: + - uid: 67 + components: + - type: Transform + pos: 15.5,10.5 + parent: 1 + - uid: 127 + components: + - type: Transform + pos: 16.5,10.5 + parent: 1 + - uid: 129 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 8.5,10.5 + parent: 1 + - uid: 137 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 9.5,10.5 + parent: 1 + - uid: 186 + components: + - type: Transform + pos: -8.5,-5.5 + parent: 1 + - uid: 202 + components: + - type: Transform + pos: 7.5,12.5 + parent: 1 + - uid: 210 + components: + - type: Transform + pos: 7.5,11.5 + parent: 1 + - uid: 224 + components: + - type: Transform + pos: 6.5,12.5 + parent: 1 + - uid: 306 + components: + - type: Transform + pos: 16.5,8.5 + parent: 1 + - uid: 307 + components: + - type: Transform + pos: 5.5,12.5 + parent: 1 + - uid: 310 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 9.5,11.5 + parent: 1 + - uid: 328 + components: + - type: Transform + pos: 13.5,10.5 + parent: 1 + - uid: 330 + components: + - type: Transform + pos: 16.5,9.5 + parent: 1 + - uid: 331 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 8.5,11.5 + parent: 1 + - uid: 383 + components: + - type: Transform + pos: 16.5,7.5 + parent: 1 + - uid: 412 + components: + - type: Transform + pos: 14.5,11.5 + parent: 1 + - uid: 413 + components: + - type: Transform + pos: 14.5,10.5 + parent: 1 + - uid: 415 + components: + - type: Transform + pos: 16.5,6.5 + parent: 1 + - uid: 418 + components: + - type: Transform + pos: 17.5,7.5 + parent: 1 + - uid: 419 + components: + - type: Transform + pos: 17.5,9.5 + parent: 1 + - uid: 420 + components: + - type: Transform + pos: 17.5,11.5 + parent: 1 + - uid: 536 + components: + - type: Transform + pos: 18.5,11.5 + parent: 1 + - uid: 540 + components: + - type: Transform + pos: 16.5,11.5 + parent: 1 + - uid: 581 + components: + - type: Transform + pos: 15.5,9.5 + parent: 1 + - uid: 582 + components: + - type: Transform + pos: 15.5,8.5 + parent: 1 + - uid: 583 + components: + - type: Transform + pos: 15.5,7.5 + parent: 1 + - uid: 584 + components: + - type: Transform + pos: 15.5,6.5 + parent: 1 + - uid: 585 + components: + - type: Transform + pos: 15.5,5.5 + parent: 1 +- proto: AtmosDeviceFanTiny + entities: + - uid: 341 + components: + - type: Transform + pos: 3.5,6.5 + parent: 1 +- proto: Bed + entities: + - uid: 342 + components: + - type: Transform + pos: -0.5,1.5 + parent: 1 + - uid: 343 + components: + - type: Transform + pos: 6.5,1.5 + parent: 1 + - uid: 344 + components: + - type: Transform + pos: 6.5,3.5 + parent: 1 + - uid: 345 + components: + - type: Transform + pos: -1.5,1.5 + parent: 1 +- proto: BedsheetSyndie + entities: + - uid: 346 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -0.5,1.5 + parent: 1 + - uid: 347 + components: + - type: Transform + pos: 6.5,1.5 + parent: 1 + - uid: 348 + components: + - type: Transform + pos: 6.5,3.5 + parent: 1 + - uid: 349 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -1.5,1.5 + parent: 1 +- proto: BenchSofaCorpLeft + entities: + - uid: 422 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 6.5,-4.5 + parent: 1 + - uid: 423 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 8.5,-5.5 + parent: 1 + - uid: 541 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 12.5,3.5 + parent: 1 +- proto: BenchSofaCorpRight + entities: + - uid: 402 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 12.5,4.5 + parent: 1 + - uid: 404 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 6.5,-5.5 + parent: 1 + - uid: 421 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 8.5,-4.5 + parent: 1 +- proto: BlastDoorOpen + entities: + - uid: 324 + components: + - type: Transform + pos: 12.5,7.5 + parent: 1 + - type: DeviceLinkSink + links: + - 593 + - uid: 325 + components: + - type: Transform + pos: 12.5,6.5 + parent: 1 + - type: DeviceLinkSink + links: + - 593 + - uid: 329 + components: + - type: Transform + pos: 12.5,8.5 + parent: 1 + - type: DeviceLinkSink + links: + - 593 + - uid: 350 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 6.5,-6.5 + parent: 1 + - type: DeviceLinkSink + links: + - 807 + - uid: 351 + components: + - type: Transform + pos: 13.5,0.5 + parent: 1 + - type: DeviceLinkSink + invokeCounter: 4 + links: + - 805 + - uid: 352 + components: + - type: Transform + pos: 13.5,1.5 + parent: 1 + - type: DeviceLinkSink + invokeCounter: 4 + links: + - 805 + - uid: 353 + components: + - type: Transform + pos: 13.5,-0.5 + parent: 1 + - type: DeviceLinkSink + invokeCounter: 4 + links: + - 805 + - uid: 354 + components: + - type: Transform + pos: 13.5,2.5 + parent: 1 + - type: DeviceLinkSink + invokeCounter: 4 + links: + - 805 + - uid: 355 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 7.5,-6.5 + parent: 1 + - type: DeviceLinkSink + links: + - 807 + - uid: 356 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 8.5,-6.5 + parent: 1 + - type: DeviceLinkSink + links: + - 807 + - uid: 357 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 10.5,-5.5 + parent: 1 + - type: DeviceLinkSink + links: + - 807 + - uid: 358 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 11.5,-5.5 + parent: 1 + - type: DeviceLinkSink + links: + - 807 + - uid: 359 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 12.5,-5.5 + parent: 1 + - type: DeviceLinkSink + links: + - 807 + - uid: 360 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 13.5,-5.5 + parent: 1 + - type: DeviceLinkSink + links: + - 807 + - uid: 361 + components: + - type: Transform + pos: 10.5,-1.5 + parent: 1 + - type: DeviceLinkSink + links: + - 805 +- proto: BoozeDispenser + entities: + - uid: 362 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 1.5,-3.5 + parent: 1 + - type: Emagged +- proto: BoxHandcuff + entities: + - uid: 118 + components: + - type: Transform + parent: 363 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: BoxLethalshot + entities: + - uid: 365 + components: + - type: Transform + parent: 363 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 366 + components: + - type: Transform + parent: 363 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: BoxShotgunFlare + entities: + - uid: 367 + components: + - type: Transform + parent: 363 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: BriefcaseBrownFilled + entities: + - uid: 403 + components: + - type: Transform + pos: 12.615925,-0.21887398 + parent: 1 +- proto: BriefcaseSyndieLobbyingBundleFilled + entities: + - uid: 371 + components: + - type: Transform + pos: 12.604369,-0.012328386 + parent: 1 +- proto: BriefcaseSyndieSniperBundleFilled + entities: + - uid: 370 + components: + - type: Transform + pos: 12.604369,-0.3873284 + parent: 1 +- proto: ButtonFrameCautionSecurity + entities: + - uid: 592 + components: + - type: Transform + pos: 12.5,5.5 + parent: 1 +- proto: CableApcExtension + entities: + - uid: 440 + components: + - type: Transform + pos: 7.5,0.5 + parent: 1 + - uid: 441 + components: + - type: Transform + pos: 8.5,0.5 + parent: 1 + - uid: 442 + components: + - type: Transform + pos: 9.5,0.5 + parent: 1 + - uid: 443 + components: + - type: Transform + pos: 9.5,1.5 + parent: 1 + - uid: 444 + components: + - type: Transform + pos: 9.5,-0.5 + parent: 1 + - uid: 445 + components: + - type: Transform + pos: 2.5,4.5 + parent: 1 + - uid: 446 + components: + - type: Transform + pos: 2.5,3.5 + parent: 1 + - uid: 447 + components: + - type: Transform + pos: 1.5,2.5 + parent: 1 + - uid: 448 + components: + - type: Transform + pos: 1.5,2.5 + parent: 1 + - uid: 449 + components: + - type: Transform + pos: 3.5,2.5 + parent: 1 + - uid: 450 + components: + - type: Transform + pos: 2.5,2.5 + parent: 1 + - uid: 451 + components: + - type: Transform + pos: 3.5,1.5 + parent: 1 + - uid: 452 + components: + - type: Transform + pos: 3.5,0.5 + parent: 1 + - uid: 453 + components: + - type: Transform + pos: 3.5,-5.5 + parent: 1 + - uid: 454 + components: + - type: Transform + pos: 3.5,-4.5 + parent: 1 + - uid: 455 + components: + - type: Transform + pos: 3.5,-3.5 + parent: 1 + - uid: 456 + components: + - type: Transform + pos: -1.5,-3.5 + parent: 1 + - uid: 457 + components: + - type: Transform + pos: 4.5,-3.5 + parent: 1 + - uid: 458 + components: + - type: Transform + pos: 1.5,1.5 + parent: 1 + - uid: 459 + components: + - type: Transform + pos: 1.5,0.5 + parent: 1 + - uid: 460 + components: + - type: Transform + pos: 1.5,-0.5 + parent: 1 + - uid: 461 + components: + - type: Transform + pos: 0.5,-0.5 + parent: 1 + - uid: 462 + components: + - type: Transform + pos: -0.5,-0.5 + parent: 1 + - uid: 463 + components: + - type: Transform + pos: -0.5,0.5 + parent: 1 + - uid: 464 + components: + - type: Transform + pos: -1.5,-2.5 + parent: 1 + - uid: 465 + components: + - type: Transform + pos: -1.5,-0.5 + parent: 1 + - uid: 466 + components: + - type: Transform + pos: -1.5,-1.5 + parent: 1 + - uid: 467 + components: + - type: Transform + pos: 2.5,-5.5 + parent: 1 + - uid: 468 + components: + - type: Transform + pos: 2.5,-6.5 + parent: 1 + - uid: 469 + components: + - type: Transform + pos: 1.5,-6.5 + parent: 1 + - uid: 470 + components: + - type: Transform + pos: 0.5,-6.5 + parent: 1 + - uid: 471 + components: + - type: Transform + pos: -0.5,-6.5 + parent: 1 + - uid: 472 + components: + - type: Transform + pos: 1.5,3.5 + parent: 1 + - uid: 473 + components: + - type: Transform + pos: 0.5,3.5 + parent: 1 + - uid: 474 + components: + - type: Transform + pos: -0.5,3.5 + parent: 1 + - uid: 475 + components: + - type: Transform + pos: -1.5,3.5 + parent: 1 + - uid: 476 + components: + - type: Transform + pos: -1.5,4.5 + parent: 1 + - uid: 477 + components: + - type: Transform + pos: 10.5,-0.5 + parent: 1 + - uid: 478 + components: + - type: Transform + pos: 10.5,-1.5 + parent: 1 + - uid: 479 + components: + - type: Transform + pos: 10.5,-2.5 + parent: 1 + - uid: 480 + components: + - type: Transform + pos: 10.5,-3.5 + parent: 1 + - uid: 481 + components: + - type: Transform + pos: 11.5,-3.5 + parent: 1 + - uid: 482 + components: + - type: Transform + pos: 12.5,-3.5 + parent: 1 + - uid: 483 + components: + - type: Transform + pos: 7.5,-0.5 + parent: 1 + - uid: 484 + components: + - type: Transform + pos: 6.5,-0.5 + parent: 1 + - uid: 485 + components: + - type: Transform + pos: 6.5,-1.5 + parent: 1 + - uid: 486 + components: + - type: Transform + pos: 6.5,-2.5 + parent: 1 + - uid: 487 + components: + - type: Transform + pos: 6.5,-3.5 + parent: 1 + - uid: 488 + components: + - type: Transform + pos: 7.5,-3.5 + parent: 1 + - uid: 489 + components: + - type: Transform + pos: 7.5,-4.5 + parent: 1 + - uid: 490 + components: + - type: Transform + pos: 3.5,3.5 + parent: 1 + - uid: 491 + components: + - type: Transform + pos: 4.5,3.5 + parent: 1 + - uid: 492 + components: + - type: Transform + pos: 5.5,3.5 + parent: 1 + - uid: 954 + components: + - type: Transform + pos: 10.5,2.5 + parent: 1 + - uid: 959 + components: + - type: Transform + pos: 9.5,2.5 + parent: 1 + - uid: 993 + components: + - type: Transform + pos: 9.5,1.5 + parent: 1 + - uid: 994 + components: + - type: Transform + pos: 9.5,2.5 + parent: 1 + - uid: 995 + components: + - type: Transform + pos: 9.5,3.5 + parent: 1 + - uid: 996 + components: + - type: Transform + pos: 9.5,4.5 + parent: 1 + - uid: 997 + components: + - type: Transform + pos: 10.5,4.5 + parent: 1 + - uid: 998 + components: + - type: Transform + pos: 11.5,4.5 + parent: 1 + - uid: 999 + components: + - type: Transform + pos: 11.5,5.5 + parent: 1 + - uid: 1000 + components: + - type: Transform + pos: 11.5,6.5 + parent: 1 + - uid: 1001 + components: + - type: Transform + pos: 11.5,7.5 + parent: 1 + - uid: 1002 + components: + - type: Transform + pos: 11.5,8.5 + parent: 1 + - uid: 1003 + components: + - type: Transform + pos: 11.5,9.5 + parent: 1 + - uid: 1004 + components: + - type: Transform + pos: 11.5,10.5 + parent: 1 +- proto: CableHV + entities: + - uid: 493 + components: + - type: Transform + pos: -0.5,6.5 + parent: 1 + - uid: 494 + components: + - type: Transform + pos: -0.5,5.5 + parent: 1 + - uid: 495 + components: + - type: Transform + pos: 0.5,5.5 + parent: 1 + - uid: 496 + components: + - type: Transform + pos: -1.5,5.5 + parent: 1 + - uid: 497 + components: + - type: Transform + pos: -1.5,6.5 + parent: 1 + - uid: 498 + components: + - type: Transform + pos: -1.5,4.5 + parent: 1 + - uid: 499 + components: + - type: Transform + pos: -1.5,3.5 + parent: 1 + - uid: 500 + components: + - type: Transform + pos: -0.5,3.5 + parent: 1 + - uid: 501 + components: + - type: Transform + pos: -2.5,5.5 + parent: 1 + - uid: 502 + components: + - type: Transform + pos: -2.5,4.5 + parent: 1 + - uid: 503 + components: + - type: Transform + pos: -2.5,3.5 + parent: 1 + - uid: 504 + components: + - type: Transform + pos: -1.5,2.5 + parent: 1 + - uid: 505 + components: + - type: Transform + pos: -0.5,2.5 + parent: 1 +- proto: CableMV + entities: + - uid: 506 + components: + - type: Transform + pos: -1.5,4.5 + parent: 1 + - uid: 507 + components: + - type: Transform + pos: -0.5,5.5 + parent: 1 + - uid: 508 + components: + - type: Transform + pos: 0.5,5.5 + parent: 1 + - uid: 509 + components: + - type: Transform + pos: -1.5,5.5 + parent: 1 + - uid: 510 + components: + - type: Transform + pos: -1.5,3.5 + parent: 1 + - uid: 511 + components: + - type: Transform + pos: -0.5,3.5 + parent: 1 + - uid: 512 + components: + - type: Transform + pos: 1.5,3.5 + parent: 1 + - uid: 513 + components: + - type: Transform + pos: 0.5,3.5 + parent: 1 + - uid: 514 + components: + - type: Transform + pos: 2.5,3.5 + parent: 1 + - uid: 515 + components: + - type: Transform + pos: 2.5,4.5 + parent: 1 + - uid: 516 + components: + - type: Transform + pos: 2.5,2.5 + parent: 1 + - uid: 517 + components: + - type: Transform + pos: 2.5,1.5 + parent: 1 + - uid: 518 + components: + - type: Transform + pos: 2.5,0.5 + parent: 1 + - uid: 519 + components: + - type: Transform + pos: 2.5,-0.5 + parent: 1 + - uid: 520 + components: + - type: Transform + pos: 2.5,-1.5 + parent: 1 + - uid: 521 + components: + - type: Transform + pos: 2.5,-2.5 + parent: 1 + - uid: 522 + components: + - type: Transform + pos: 2.5,-3.5 + parent: 1 + - uid: 523 + components: + - type: Transform + pos: 2.5,-4.5 + parent: 1 + - uid: 524 + components: + - type: Transform + pos: 3.5,-4.5 + parent: 1 + - uid: 525 + components: + - type: Transform + pos: 3.5,-5.5 + parent: 1 + - uid: 526 + components: + - type: Transform + pos: 3.5,-0.5 + parent: 1 + - uid: 527 + components: + - type: Transform + pos: 4.5,-0.5 + parent: 1 + - uid: 528 + components: + - type: Transform + pos: 5.5,-0.5 + parent: 1 + - uid: 529 + components: + - type: Transform + pos: 6.5,-0.5 + parent: 1 + - uid: 530 + components: + - type: Transform + pos: 6.5,-0.5 + parent: 1 + - uid: 531 + components: + - type: Transform + pos: 7.5,-0.5 + parent: 1 + - uid: 532 + components: + - type: Transform + pos: 8.5,-0.5 + parent: 1 + - uid: 533 + components: + - type: Transform + pos: 8.5,0.5 + parent: 1 + - uid: 534 + components: + - type: Transform + pos: 7.5,0.5 + parent: 1 +- proto: CarpetBlack + entities: + - uid: 381 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 3.5,2.5 + parent: 1 + - uid: 570 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 6.5,-4.5 + parent: 1 + - uid: 571 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 6.5,-5.5 + parent: 1 + - uid: 572 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 7.5,-4.5 + parent: 1 + - uid: 573 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 7.5,-5.5 + parent: 1 + - uid: 574 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 8.5,-5.5 + parent: 1 + - uid: 575 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 8.5,-4.5 + parent: 1 + - uid: 594 + components: + - type: Transform + pos: 10.5,2.5 + parent: 1 + - uid: 595 + components: + - type: Transform + pos: 9.5,2.5 + parent: 1 + - uid: 596 + components: + - type: Transform + pos: 9.5,3.5 + parent: 1 + - uid: 597 + components: + - type: Transform + pos: 10.5,3.5 + parent: 1 + - uid: 598 + components: + - type: Transform + pos: 11.5,3.5 + parent: 1 + - uid: 620 + components: + - type: Transform + pos: 11.5,2.5 + parent: 1 + - uid: 960 + components: + - type: Transform + pos: 2.5,2.5 + parent: 1 + - uid: 964 + components: + - type: Transform + pos: 3.5,1.5 + parent: 1 + - uid: 965 + components: + - type: Transform + pos: 2.5,0.5 + parent: 1 + - uid: 966 + components: + - type: Transform + pos: 3.5,0.5 + parent: 1 + - uid: 967 + components: + - type: Transform + pos: 2.5,1.5 + parent: 1 + - uid: 968 + components: + - type: Transform + pos: -1.5,1.5 + parent: 1 + - uid: 969 + components: + - type: Transform + pos: -1.5,0.5 + parent: 1 + - uid: 970 + components: + - type: Transform + pos: -2.5,0.5 + parent: 1 + - uid: 971 + components: + - type: Transform + pos: -1.5,-0.5 + parent: 1 + - uid: 972 + components: + - type: Transform + pos: -0.5,-0.5 + parent: 1 + - uid: 973 + components: + - type: Transform + pos: -0.5,0.5 + parent: 1 + - uid: 974 + components: + - type: Transform + pos: -0.5,1.5 + parent: 1 + - uid: 975 + components: + - type: Transform + pos: 6.5,1.5 + parent: 1 + - uid: 976 + components: + - type: Transform + pos: 6.5,2.5 + parent: 1 + - uid: 977 + components: + - type: Transform + pos: 6.5,3.5 + parent: 1 + - uid: 978 + components: + - type: Transform + pos: 9.5,1.5 + parent: 1 + - uid: 979 + components: + - type: Transform + pos: 9.5,0.5 + parent: 1 + - uid: 980 + components: + - type: Transform + pos: 10.5,0.5 + parent: 1 + - uid: 981 + components: + - type: Transform + pos: 10.5,1.5 + parent: 1 + - uid: 982 + components: + - type: Transform + pos: 11.5,1.5 + parent: 1 + - uid: 983 + components: + - type: Transform + pos: 11.5,0.5 + parent: 1 +- proto: ChairOfficeDark + entities: + - uid: 369 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 9.383302,3.471712 + parent: 1 + - uid: 372 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 11.762909,1.1363623 + parent: 1 + - uid: 409 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 8.407137,7.5838184 + parent: 1 + - uid: 410 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 11.684784,2.2144873 + parent: 1 + - uid: 535 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 9.5,1.5 + parent: 1 +- proto: CigPackSyndicate + entities: + - uid: 558 + components: + - type: Transform + pos: 4.7080255,1.4853511 + parent: 1 + - uid: 569 + components: + - type: Transform + pos: 4.7236505,1.7666011 + parent: 1 +- proto: ClosetWallMixed + entities: + - uid: 388 + components: + - type: MetaData + name: pyjama closet + - type: Transform + rot: -1.5707963267948966 rad + pos: 7.5,2.5 + parent: 1 + - type: EntityStorage + air: + volume: 200 + immutable: False + temperature: 293.14673 + moles: + - 1.7459903 + - 6.568249 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - type: ContainerContainer + containers: + entity_storage: !type:Container + showEnts: False + occludes: True + ents: + - 400 + - 399 + - 393 + - 392 + - 391 + - 390 + - 398 + - 397 + - 396 + - 395 + - 394 + - 389 +- proto: ClothingHeadPyjamaSyndicateBlack + entities: + - uid: 393 + components: + - type: Transform + parent: 388 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 395 + components: + - type: Transform + parent: 388 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: ClothingHeadPyjamaSyndicatePink + entities: + - uid: 396 + components: + - type: Transform + parent: 388 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 399 + components: + - type: Transform + parent: 388 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: ClothingHeadPyjamaSyndicateRed + entities: + - uid: 389 + components: + - type: Transform + parent: 388 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 391 + components: + - type: Transform + parent: 388 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: ClothingMaskGasVoiceChameleon + entities: + - uid: 549 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 12.464487,4.225065 + parent: 1 + - uid: 578 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 12.464487,3.6313155 + parent: 1 +- proto: ClothingUniformJumpsuitPyjamaSyndicateBlack + entities: + - uid: 392 + components: + - type: Transform + parent: 388 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 397 + components: + - type: Transform + parent: 388 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: ClothingUniformJumpsuitPyjamaSyndicatePink + entities: + - uid: 390 + components: + - type: Transform + parent: 388 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 400 + components: + - type: Transform + parent: 388 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: ClothingUniformJumpsuitPyjamaSyndicateRed + entities: + - uid: 394 + components: + - type: Transform + parent: 388 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 398 + components: + - type: Transform + parent: 388 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: CombatKnife + entities: + - uid: 376 + components: + - type: Transform + parent: 363 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 377 + components: + - type: Transform + parent: 363 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: ComfyChair + entities: + - uid: 543 + components: + - type: Transform + pos: 1.5,2.5 + parent: 1 +- proto: ComputerCrewMonitoring + entities: + - uid: 548 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 12.5,0.5 + parent: 1 +- proto: ComputerMassMedia + entities: + - uid: 311 + components: + - type: Transform + pos: 9.5,2.5 + parent: 1 +- proto: ComputerRadar + entities: + - uid: 550 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 12.5,1.5 + parent: 1 + - type: RadarConsole + maxRange: 2048 + - type: WorldLoader + radius: 2048 +- proto: ComputerSurveillanceCameraMonitor + entities: + - uid: 991 + components: + - type: Transform + pos: 10.5,4.5 + parent: 1 +- proto: ComputerSurveillanceWirelessCameraMonitor + entities: + - uid: 406 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 12.5,2.5 + parent: 1 +- proto: CrateFoodDonkpocketSavory + entities: + - uid: 551 + components: + - type: Transform + pos: -0.5,-5.5 + parent: 1 + - type: EntityStorage + air: + volume: 200 + immutable: False + temperature: 293.1496 + moles: + - 1.8856695 + - 7.0937095 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 +- proto: CrateServiceBureaucracy + entities: + - uid: 577 + components: + - type: Transform + pos: -1.5,4.5 + parent: 1 +- proto: CrateSyndicateSurplusBundle + entities: + - uid: 411 + components: + - type: Transform + pos: 9.5,6.5 + parent: 1 + - uid: 992 + components: + - type: Transform + pos: 10.5,6.5 + parent: 1 +- proto: CrowbarRed + entities: + - uid: 378 + components: + - type: Transform + parent: 363 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: DefibrillatorCabinetFilled + entities: + - uid: 552 + components: + - type: Transform + pos: -0.5,-1.5 + parent: 1 +- proto: DiagnosisReportPaper + entities: + - uid: 432 + components: + - type: Transform + pos: 8.472298,0.6316006 + parent: 1 + - uid: 554 + components: + - type: Transform + pos: 8.472298,0.6316006 + parent: 1 + - uid: 562 + components: + - type: Transform + pos: 8.472298,0.6316006 + parent: 1 + - uid: 564 + components: + - type: Transform + pos: 8.472298,0.6316006 + parent: 1 + - uid: 566 + components: + - type: Transform + pos: 8.472298,0.6316006 + parent: 1 +- proto: DisposalPipe + entities: + - uid: 599 + components: + - type: Transform + pos: 10.5,-3.5 + parent: 1 + - uid: 600 + components: + - type: Transform + pos: 10.5,-4.5 + parent: 1 + - uid: 601 + components: + - type: Transform + pos: 10.5,-5.5 + parent: 1 +- proto: DisposalTrunk + entities: + - uid: 602 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 10.5,-6.5 + parent: 1 + - uid: 603 + components: + - type: Transform + pos: 10.5,-2.5 + parent: 1 +- proto: DisposalUnit + entities: + - uid: 604 + components: + - type: MetaData + name: escape hatch + - type: Transform + pos: 10.5,-2.5 + parent: 1 +- proto: DrinkMugDog + entities: + - uid: 605 + components: + - type: Transform + rot: 6.283185307179586 rad + pos: 1.6752021,1.4137775 + parent: 1 +- proto: DrinkMugHeart + entities: + - uid: 606 + components: + - type: Transform + pos: 1.2883832,1.4021075 + parent: 1 +- proto: DrinkMugOne + entities: + - uid: 607 + components: + - type: Transform + pos: 1.6477582,1.7614824 + parent: 1 +- proto: DrinkMugRainbow + entities: + - uid: 608 + components: + - type: Transform + pos: 1.3040082,1.7302322 + parent: 1 +- proto: EncryptionKeyCommand + entities: + - uid: 613 + components: + - type: Transform + parent: 612 + - type: Physics + canCollide: False +- proto: EncryptionKeyStationMaster + entities: + - uid: 614 + components: + - type: Transform + parent: 612 + - type: Physics + canCollide: False +- proto: EncryptionKeySyndie + entities: + - uid: 615 + components: + - type: Transform + parent: 612 + - type: Physics + canCollide: False +- proto: ExtinguisherCabinet + entities: + - uid: 619 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 6.5,0.5 + parent: 1 +- proto: FaxMachineListeningSyndie + entities: + - uid: 368 + components: + - type: Transform + pos: 8.5,2.5 + parent: 1 +- proto: filingCabinetDrawerRandom + entities: + - uid: 567 + components: + - type: Transform + pos: 9.5,0.5 + parent: 1 +- proto: FireAlarm + entities: + - uid: 621 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 3.5,-1.5 + parent: 1 + - type: DeviceList + devices: + - 628 + - 629 + - 622 + - 623 + - 624 + - 632 + - 631 + - 625 + - 627 + - 633 +- proto: Firelock + entities: + - uid: 622 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 5.5,-0.5 + parent: 1 + - uid: 623 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 7.5,-0.5 + parent: 1 + - uid: 624 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 6.5,-1.5 + parent: 1 + - uid: 625 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 5.5,2.5 + parent: 1 + - uid: 626 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 3.5,4.5 + parent: 1 + - uid: 627 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 2.5,5.5 + parent: 1 + - uid: 628 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 0.5,3.5 + parent: 1 + - uid: 629 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 0.5,-0.5 + parent: 1 + - uid: 630 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -1.5,-1.5 + parent: 1 + - uid: 631 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 2.5,-5.5 + parent: 1 + - uid: 632 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 2.5,-1.5 + parent: 1 + - uid: 633 + components: + - type: Transform + pos: 9.5,-3.5 + parent: 1 +- proto: ForensicReportPaper + entities: + - uid: 427 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 8.519173,0.6784754 + parent: 1 + - uid: 555 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 8.519173,0.6784754 + parent: 1 + - uid: 557 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 8.519173,0.6784754 + parent: 1 + - uid: 561 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 8.519173,0.6784754 + parent: 1 + - uid: 563 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 8.519173,0.6784754 + parent: 1 +- proto: GasPassiveVent + entities: + - uid: 634 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -5.5,1.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 635 + components: + - type: Transform + pos: 6.5,6.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 636 + components: + - type: Transform + pos: 4.5,10.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 637 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 14.5,-0.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 638 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 13.5,-6.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 639 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 8.5,-7.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 640 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 4.5,-10.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 641 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -4.5,-3.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 642 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -4.5,5.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' +- proto: GasPipeBend + entities: + - uid: 643 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -3.5,0.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 644 + components: + - type: Transform + pos: -3.5,1.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 645 + components: + - type: Transform + pos: 6.5,2.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 646 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -1.5,-0.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 647 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 2.5,-6.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 648 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 6.5,-3.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 649 + components: + - type: Transform + pos: 10.5,-0.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 650 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 10.5,-3.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' +- proto: GasPipeFourway + entities: + - uid: 651 + components: + - type: Transform + pos: 2.5,-0.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' +- proto: GasPipeStraight + entities: + - uid: 652 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.5,4.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 653 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 0.5,3.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 654 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -4.5,1.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 655 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -2.5,0.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 656 + components: + - type: Transform + pos: 4.5,-7.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 657 + components: + - type: Transform + pos: 4.5,-8.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 658 + components: + - type: Transform + pos: 4.5,-9.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 659 + components: + - type: Transform + pos: 8.5,-6.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 660 + components: + - type: Transform + pos: 8.5,-5.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 661 + components: + - type: Transform + pos: 8.5,-4.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 662 + components: + - type: Transform + pos: 13.5,-4.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 663 + components: + - type: Transform + pos: 13.5,-5.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 664 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 13.5,-0.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 665 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 12.5,-0.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 666 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 6.5,4.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 667 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 6.5,5.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 668 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 4.5,4.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 669 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 4.5,5.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 670 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 4.5,6.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 671 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 4.5,7.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 672 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 4.5,8.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 673 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 4.5,9.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 674 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 7.5,-0.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 675 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 5.5,-0.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 676 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 5.5,2.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 677 + components: + - type: Transform + pos: -1.5,-1.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 678 + components: + - type: Transform + pos: 2.5,-5.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 679 + components: + - type: Transform + pos: 2.5,-4.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 680 + components: + - type: Transform + pos: 2.5,-3.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 681 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -2.5,-3.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 682 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -3.5,-3.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 683 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -2.5,5.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 684 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -3.5,5.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 685 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 6.5,-2.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 686 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 6.5,-1.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 687 + components: + - type: Transform + pos: 10.5,-1.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 688 + components: + - type: Transform + pos: 10.5,-2.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 689 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 9.5,-0.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 690 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 4.5,2.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 691 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 3.5,2.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 692 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 2.5,1.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 693 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 2.5,0.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 694 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 1.5,-0.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 695 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 0.5,-0.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 696 + components: + - type: Transform + pos: 2.5,-1.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 697 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 3.5,-0.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 698 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 4.5,-0.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' +- proto: GasPipeTJunction + entities: + - uid: 699 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.5,3.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 700 + components: + - type: Transform + pos: 2.5,3.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 701 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 2.5,2.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 702 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -0.5,-0.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 703 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 2.5,-2.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 704 + components: + - type: Transform + pos: 6.5,-0.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 705 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 8.5,-0.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' +- proto: GasPort + entities: + - uid: 706 + components: + - type: Transform + pos: 1.5,5.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' +- proto: GasVentPump + entities: + - uid: 384 + components: + - type: Transform + pos: 8.5,0.5 + parent: 1 + - type: DeviceNetwork + deviceLists: + - 576 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 707 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -0.5,3.5 + parent: 1 + - type: DeviceNetwork + deviceLists: + - 576 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 708 + components: + - type: Transform + pos: -0.5,0.5 + parent: 1 + - type: DeviceNetwork + deviceLists: + - 576 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 709 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -1.5,-2.5 + parent: 1 + - type: DeviceNetwork + deviceLists: + - 576 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 710 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 3.5,-2.5 + parent: 1 + - type: DeviceNetwork + deviceLists: + - 576 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 711 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 3.5,-6.5 + parent: 1 + - type: DeviceNetwork + deviceLists: + - 576 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 712 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 7.5,-3.5 + parent: 1 + - type: DeviceNetwork + deviceLists: + - 576 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 713 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 6.5,1.5 + parent: 1 + - type: DeviceNetwork + deviceLists: + - 576 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 714 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 3.5,3.5 + parent: 1 + - type: DeviceNetwork + deviceLists: + - 576 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 716 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 11.5,-3.5 + parent: 1 + - type: DeviceNetwork + deviceLists: + - 576 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 717 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -0.5,-3.5 + parent: 1 +- proto: GasVentScrubber + entities: + - uid: 718 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 6.5,3.5 + parent: 1 + - type: DeviceNetwork + deviceLists: + - 576 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 719 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -1.5,5.5 + parent: 1 + - type: DeviceNetwork + deviceLists: + - 576 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 720 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -1.5,0.5 + parent: 1 + - type: DeviceNetwork + deviceLists: + - 576 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 721 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -1.5,-3.5 + parent: 1 + - type: DeviceNetwork + deviceLists: + - 576 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 722 + components: + - type: Transform + pos: 4.5,-6.5 + parent: 1 + - type: DeviceNetwork + deviceLists: + - 576 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 723 + components: + - type: Transform + pos: 8.5,-3.5 + parent: 1 + - type: DeviceNetwork + deviceLists: + - 576 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 724 + components: + - type: Transform + pos: 13.5,-3.5 + parent: 1 + - type: DeviceNetwork + deviceLists: + - 576 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 725 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 11.5,-0.5 + parent: 1 + - type: DeviceNetwork + deviceLists: + - 576 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 726 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 4.5,3.5 + parent: 1 + - type: DeviceNetwork + deviceLists: + - 576 + - type: AtmosPipeColor + color: '#FF1212FF' +- proto: GeneratorWallmountAPU + entities: + - uid: 727 + components: + - type: Transform + pos: -1.5,6.5 + parent: 1 + - uid: 728 + components: + - type: Transform + pos: -0.5,6.5 + parent: 1 + - uid: 729 + components: + - type: Transform + pos: -2.5,5.5 + parent: 1 + - uid: 730 + components: + - type: Transform + pos: -2.5,4.5 + parent: 1 + - uid: 732 + components: + - type: Transform + pos: -1.5,2.5 + parent: 1 + - uid: 733 + components: + - type: Transform + pos: -0.5,2.5 + parent: 1 +- proto: GravityGeneratorMini + entities: + - uid: 734 + components: + - type: Transform + pos: -0.5,5.5 + parent: 1 +- proto: Grille + entities: + - uid: 130 + components: + - type: Transform + pos: 7.5,7.5 + parent: 1 + - uid: 309 + components: + - type: Transform + pos: 7.5,8.5 + parent: 1 + - uid: 588 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 7.5,6.5 + parent: 1 + - uid: 735 + components: + - type: Transform + pos: 10.5,-1.5 + parent: 1 + - uid: 736 + components: + - type: Transform + pos: 12.5,-5.5 + parent: 1 + - uid: 737 + components: + - type: Transform + pos: 13.5,-5.5 + parent: 1 + - uid: 738 + components: + - type: Transform + pos: 13.5,0.5 + parent: 1 + - uid: 739 + components: + - type: Transform + pos: 13.5,-0.5 + parent: 1 + - uid: 740 + components: + - type: Transform + pos: 13.5,1.5 + parent: 1 + - uid: 741 + components: + - type: Transform + pos: 13.5,2.5 + parent: 1 + - uid: 742 + components: + - type: Transform + pos: 7.5,-6.5 + parent: 1 + - uid: 743 + components: + - type: Transform + pos: 10.5,-5.5 + parent: 1 + - uid: 744 + components: + - type: Transform + pos: 8.5,-6.5 + parent: 1 + - uid: 745 + components: + - type: Transform + pos: 6.5,-6.5 + parent: 1 + - uid: 746 + components: + - type: Transform + pos: 11.5,-5.5 + parent: 1 +- proto: GrilleDiagonal + entities: + - uid: 589 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 8.5,6.5 + parent: 1 + - uid: 590 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 8.5,8.5 + parent: 1 +- proto: HospitalCurtainsOpen + entities: + - uid: 747 + components: + - type: Transform + pos: 6.5,3.5 + parent: 1 + - uid: 748 + components: + - type: Transform + pos: 6.5,1.5 + parent: 1 + - uid: 749 + components: + - type: Transform + pos: -1.5,1.5 + parent: 1 + - uid: 750 + components: + - type: Transform + pos: -0.5,1.5 + parent: 1 +- proto: hydroponicsTray + entities: + - uid: 751 + components: + - type: Transform + pos: 12.5,-4.5 + parent: 1 + - uid: 752 + components: + - type: Transform + pos: 13.5,-4.5 + parent: 1 + - uid: 753 + components: + - type: Transform + pos: 11.5,-4.5 + parent: 1 + - uid: 754 + components: + - type: Transform + pos: 10.5,-4.5 + parent: 1 +- proto: KitchenMicrowave + entities: + - uid: 755 + components: + - type: Transform + pos: 1.5,-2.5 + parent: 1 +- proto: LandMineExplosive + entities: + - uid: 364 + components: + - type: Transform + pos: 5.269056,11.251331 + parent: 1 + - uid: 579 + components: + - type: Transform + pos: 6.784681,11.813831 + parent: 1 + - uid: 756 + components: + - type: Transform + pos: 12.446557,-9.501101 + parent: 1 + - uid: 757 + components: + - type: Transform + pos: 17.449137,2.529461 + parent: 1 +- proto: LockerSyndicatePersonal + entities: + - uid: 363 + components: + - type: Transform + pos: -1.5,-5.5 + parent: 1 + - type: EntityStorage + air: + volume: 200 + immutable: False + temperature: 293.1496 + moles: + - 1.8978093 + - 7.139378 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - type: ContainerContainer + containers: + entity_storage: !type:Container + showEnts: False + occludes: True + ents: + - 378 + - 377 + - 379 + - 367 + - 366 + - 380 + - 376 + - 365 + - 118 + paper_label: !type:ContainerSlot + showEnts: False + occludes: True + ent: null +- proto: MedkitCombatFilled + entities: + - uid: 758 + components: + - type: Transform + pos: -0.28039455,-3.70372 + parent: 1 +- proto: MedkitFilled + entities: + - uid: 759 + components: + - type: Transform + pos: -0.57726955,-3.500595 + parent: 1 +- proto: Mirror + entities: + - uid: 957 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -2.5,-3.5 + parent: 1 +- proto: NukeDiskFake + entities: + - uid: 760 + components: + - type: Transform + pos: 16.778532,-0.7526432 + parent: 1 +- proto: PaperArtifactAnalyzer + entities: + - uid: 424 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 8.456674,0.66285014 + parent: 1 + - uid: 428 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 8.456674,0.66285014 + parent: 1 + - uid: 433 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 8.456674,0.66285014 + parent: 1 + - uid: 542 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 8.456674,0.66285014 + parent: 1 + - uid: 546 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 8.456674,0.66285014 + parent: 1 +- proto: PaperBin10 + entities: + - uid: 87 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 8.5,0.5 + parent: 1 + - uid: 761 + components: + - type: Transform + pos: 7.5,-5.5 + parent: 1 +- proto: PaperCaptainsThoughts + entities: + - uid: 430 + components: + - type: Transform + pos: 8.519173,0.64722514 + parent: 1 + - uid: 437 + components: + - type: Transform + pos: 8.519173,0.64722514 + parent: 1 + - uid: 537 + components: + - type: Transform + pos: 8.519173,0.64722514 + parent: 1 + - uid: 547 + components: + - type: Transform + pos: 8.519173,0.64722514 + parent: 1 + - uid: 553 + components: + - type: Transform + pos: 8.519173,0.64722514 + parent: 1 +- proto: PaperCargoBountyManifest + entities: + - uid: 426 + components: + - type: Transform + pos: 8.503549,0.6316006 + parent: 1 + - uid: 429 + components: + - type: Transform + pos: 8.503549,0.6316006 + parent: 1 + - uid: 434 + components: + - type: Transform + pos: 8.503549,0.6316006 + parent: 1 + - uid: 435 + components: + - type: Transform + pos: 8.503549,0.6316006 + parent: 1 + - uid: 544 + components: + - type: Transform + pos: 8.503549,0.6316006 + parent: 1 +- proto: PaperCargoInvoice + entities: + - uid: 431 + components: + - type: Transform + pos: 8.487924,0.6316004 + parent: 1 + - uid: 436 + components: + - type: Transform + pos: 8.487924,0.6316004 + parent: 1 + - uid: 438 + components: + - type: Transform + pos: 8.487924,0.6316004 + parent: 1 + - uid: 439 + components: + - type: Transform + pos: 8.487924,0.6316004 + parent: 1 + - uid: 538 + components: + - type: Transform + pos: 8.487924,0.6316004 + parent: 1 +- proto: PaperCNCSheet + entities: + - uid: 539 + components: + - type: Transform + pos: 8.503549,0.6316006 + parent: 1 + - uid: 545 + components: + - type: Transform + pos: 8.503549,0.6316006 + parent: 1 + - uid: 556 + components: + - type: Transform + pos: 8.503549,0.6316006 + parent: 1 + - uid: 559 + components: + - type: Transform + pos: 8.503549,0.6316006 + parent: 1 + - uid: 560 + components: + - type: Transform + pos: 8.503549,0.6316006 + parent: 1 +- proto: PlushieNuke + entities: + - uid: 762 + components: + - type: Transform + pos: 6.565984,3.4567177 + parent: 1 + - uid: 763 + components: + - type: Transform + pos: 16.527655,-0.5029521 + parent: 1 +- proto: PlushieRouny + entities: + - uid: 764 + components: + - type: Transform + pos: 6.487859,1.4254675 + parent: 1 +- proto: PosterContrabandDonk + entities: + - uid: 765 + components: + - type: Transform + pos: 1.5,-1.5 + parent: 1 +- proto: PosterContrabandSyndicateRecruitment + entities: + - uid: 766 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 4.5,6.5 + parent: 1 +- proto: PosterLegitHotDonkExplosion + entities: + - uid: 767 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -0.5,-4.5 + parent: 1 +- proto: PowerCellRecharger + entities: + - uid: 768 + components: + - type: Transform + pos: 8.5,1.5 + parent: 1 +- proto: Poweredlight + entities: + - uid: 18 + components: + - type: Transform + pos: 6.5,3.5 + parent: 1 + - uid: 19 + components: + - type: Transform + pos: 12.5,-2.5 + parent: 1 + - type: DeviceLinkSink + links: + - 46 + - uid: 21 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 4.5,1.5 + parent: 1 + - type: DeviceLinkSink + links: + - 46 + - uid: 375 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -0.5,0.5 + parent: 1 + - type: DeviceLinkSink + links: + - 46 + - uid: 731 + components: + - type: Transform + pos: 3.5,-2.5 + parent: 1 + - type: DeviceLinkSink + links: + - 46 + - uid: 896 + components: + - type: Transform + pos: 0.5,-6.5 + parent: 1 + - type: DeviceLinkSink + links: + - 46 + - uid: 917 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 1.5,1.5 + parent: 1 + - type: DeviceLinkSink + links: + - 46 + - uid: 956 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 6.5,-3.5 + parent: 1 + - type: DeviceLinkSink + links: + - 46 + - uid: 958 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 8.5,1.5 + parent: 1 + - type: DeviceLinkSink + links: + - 46 + - uid: 963 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -0.5,4.5 + parent: 1 + - type: DeviceLinkSink + links: + - 46 + - uid: 1005 + components: + - type: Transform + pos: 10.5,8.5 + parent: 1 +- proto: PoweredLightColoredRed + entities: + - uid: 20 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 6.5,-4.5 + parent: 1 + - type: DeviceLinkSink + links: + - 46 + - uid: 23 + components: + - type: Transform + pos: -1.5,-5.5 + parent: 1 + - type: DeviceLinkSink + links: + - 46 + - uid: 769 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -1.5,-0.5 + parent: 1 + - type: DeviceLinkSink + links: + - 46 + - type: Timer + - uid: 770 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -1.5,4.5 + parent: 1 + - type: DeviceLinkSink + links: + - 46 + - uid: 771 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 1.5,2.5 + parent: 1 + - type: DeviceLinkSink + links: + - 46 + - type: Timer + - uid: 772 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 1.5,-3.5 + parent: 1 + - type: DeviceLinkSink + links: + - 46 + - uid: 773 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 4.5,0.5 + parent: 1 + - type: DeviceLinkSink + links: + - 46 + - type: Timer + - uid: 774 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 8.5,0.5 + parent: 1 + - type: DeviceLinkSink + links: + - 46 + - uid: 775 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 4.5,-6.5 + parent: 1 + - type: DeviceLinkSink + links: + - 46 + - uid: 952 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 6.5,1.5 + parent: 1 + - uid: 955 + components: + - type: Transform + pos: 11.5,-2.5 + parent: 1 + - type: DeviceLinkSink + links: + - 46 +- proto: Rack + entities: + - uid: 776 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -1.5,5.5 + parent: 1 + - uid: 777 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 4.5,5.5 + parent: 1 + - uid: 778 + components: + - type: Transform + pos: -0.5,-3.5 + parent: 1 +- proto: RandomArcade + entities: + - uid: 779 + components: + - type: Transform + pos: 7.5,-2.5 + parent: 1 +- proto: RandomPosterContraband + entities: + - uid: 417 + components: + - type: Transform + pos: 9.5,5.5 + parent: 1 + - uid: 780 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 10.5,10.5 + parent: 1 + - uid: 781 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 0.5,0.5 + parent: 1 + - uid: 782 + components: + - type: Transform + pos: 5.5,0.5 + parent: 1 + - uid: 784 + components: + - type: Transform + pos: -1.5,-4.5 + parent: 1 + - uid: 785 + components: + - type: Transform + pos: 14.5,-3.5 + parent: 1 + - uid: 786 + components: + - type: Transform + pos: 5.5,-3.5 + parent: 1 + - uid: 827 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 12.5,10.5 + parent: 1 +- proto: RandomVendingDrinks + entities: + - uid: 787 + components: + - type: Transform + pos: 4.5,-2.5 + parent: 1 + - type: Emagged +- proto: RandomVendingSnacks + entities: + - uid: 788 + components: + - type: Transform + pos: 4.5,-3.5 + parent: 1 + - type: Emagged +- proto: ReinforcedPlasmaWindow + entities: + - uid: 38 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 7.5,6.5 + parent: 1 + - uid: 289 + components: + - type: Transform + pos: 7.5,7.5 + parent: 1 + - uid: 308 + components: + - type: Transform + pos: 7.5,8.5 + parent: 1 + - uid: 789 + components: + - type: Transform + pos: 10.5,-1.5 + parent: 1 + - uid: 790 + components: + - type: Transform + pos: 10.5,-5.5 + parent: 1 + - uid: 791 + components: + - type: Transform + pos: 12.5,-5.5 + parent: 1 + - uid: 792 + components: + - type: Transform + pos: 13.5,-0.5 + parent: 1 + - uid: 793 + components: + - type: Transform + pos: 13.5,0.5 + parent: 1 + - uid: 794 + components: + - type: Transform + pos: 13.5,2.5 + parent: 1 + - uid: 795 + components: + - type: Transform + pos: 13.5,1.5 + parent: 1 + - uid: 796 + components: + - type: Transform + pos: 11.5,-5.5 + parent: 1 + - uid: 797 + components: + - type: Transform + pos: 13.5,-5.5 + parent: 1 + - uid: 798 + components: + - type: Transform + pos: 6.5,-6.5 + parent: 1 + - uid: 799 + components: + - type: Transform + pos: 7.5,-6.5 + parent: 1 + - uid: 800 + components: + - type: Transform + pos: 8.5,-6.5 + parent: 1 +- proto: ReinforcedPlasmaWindowDiagonal + entities: + - uid: 586 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 8.5,8.5 + parent: 1 + - uid: 587 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 8.5,6.5 + parent: 1 +- proto: RubberStampApproved + entities: + - uid: 830 + components: + - type: Transform + pos: 8.336427,4.768587 + parent: 1 +- proto: RubberStampCaptain + entities: + - uid: 836 + components: + - type: Transform + pos: 8.570802,4.612337 + parent: 1 +- proto: RubberStampCE + entities: + - uid: 880 + components: + - type: Transform + pos: 8.883302,4.831087 + parent: 1 +- proto: RubberStampCentcom + entities: + - uid: 831 + components: + - type: Transform + pos: 8.602052,4.815462 + parent: 1 +- proto: RubberStampChaplain + entities: + - uid: 837 + components: + - type: Transform + pos: 8.539552,4.440462 + parent: 1 +- proto: RubberStampChiefJustice + entities: + - uid: 903 + components: + - type: Transform + pos: 8.836427,4.612337 + parent: 1 +- proto: RubberStampClown + entities: + - uid: 905 + components: + - type: Transform + pos: 9.164552,4.815462 + parent: 1 +- proto: RubberStampCMO + entities: + - uid: 904 + components: + - type: Transform + pos: 8.789552,4.502962 + parent: 1 +- proto: RubberStampDenied + entities: + - uid: 835 + components: + - type: Transform + pos: 8.305177,4.549837 + parent: 1 +- proto: RubberStampDetective + entities: + - uid: 912 + components: + - type: Transform + pos: 9.133302,4.674837 + parent: 1 +- proto: RubberStampHop + entities: + - uid: 919 + components: + - type: Transform + pos: 9.055177,4.518587 + parent: 1 +- proto: RubberStampHos + entities: + - uid: 961 + components: + - type: Transform + pos: 9.430177,4.862337 + parent: 1 +- proto: RubberStampLawyer + entities: + - uid: 984 + components: + - type: Transform + pos: 9.398927,4.706087 + parent: 1 +- proto: RubberStampMantis + entities: + - uid: 986 + components: + - type: Transform + pos: 9.633302,4.799837 + parent: 1 +- proto: RubberStampMime + entities: + - uid: 987 + components: + - type: Transform + pos: 9.570802,4.581087 + parent: 1 +- proto: RubberStampQm + entities: + - uid: 985 + components: + - type: Transform + pos: 9.320802,4.565462 + parent: 1 +- proto: RubberStampRd + entities: + - uid: 988 + components: + - type: Transform + pos: 9.773927,4.784212 + parent: 1 +- proto: SalvageLorePaperGamingSpawner + entities: + - uid: 801 + components: + - type: Transform + pos: 12.5,-9.5 + parent: 1 +- proto: SheetPlasteel + entities: + - uid: 802 + components: + - type: Transform + pos: -1.4864182,5.563302 + parent: 1 +- proto: SheetRPGlass + entities: + - uid: 803 + components: + - type: Transform + pos: -1.2364185,5.563302 + parent: 1 +- proto: SheetSteel + entities: + - uid: 804 + components: + - type: Transform + pos: -1.6895431,5.500802 + parent: 1 +- proto: SignalButton + entities: + - uid: 593 + components: + - type: MetaData + name: turret shutters + - type: Transform + pos: 12.5,5.5 + parent: 1 + - type: DeviceLinkSource + linkedPorts: + 329: + - Pressed: Toggle + 324: + - Pressed: Toggle + 325: + - Pressed: Toggle + - uid: 805 + components: + - type: MetaData + name: bridge blast doors button + - type: Transform + rot: 3.141592653589793 rad + pos: 11.5,-1.5 + parent: 1 + - type: DeviceLinkSource + linkedPorts: + 354: + - Pressed: Toggle + 352: + - Pressed: Toggle + 351: + - Pressed: Toggle + 353: + - Pressed: Toggle + 361: + - Pressed: Toggle + - uid: 806 + components: + - type: MetaData + desc: A note taped to it reads, "Be sure to keep it bolted, else those crazy guards might try to drink vacuum" + name: external bolts button + - type: Transform + pos: 4.5,4.5 + parent: 1 + - type: DeviceLinkSource + linkedPorts: + 4: + - Pressed: DoorBolt + - uid: 807 + components: + - type: MetaData + name: rec room blast shields button + - type: Transform + rot: 1.5707963267948966 rad + pos: 5.5,-2.5 + parent: 1 + - type: DeviceLinkSource + linkedPorts: + 350: + - Pressed: Toggle + 355: + - Pressed: Toggle + 356: + - Pressed: Toggle + 357: + - Pressed: Toggle + 358: + - Pressed: Toggle + 359: + - Pressed: Toggle + 360: + - Pressed: Toggle +- proto: SignalSwitchDirectional + entities: + - uid: 46 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 4.5,-1.5 + parent: 1 + - type: DeviceLinkSource + linkedPorts: + 773: + - On: Off + - Off: On + 21: + - On: On + - Off: Off + 917: + - On: On + - Off: Off + 771: + - On: Off + - Off: On + 770: + - Off: On + - On: Off + 963: + - On: On + - Off: Off + 769: + - Off: On + - On: Off + 375: + - On: On + - Off: Off + 774: + - Off: On + - On: Off + 958: + - On: On + - Off: Off + 731: + - On: On + - Off: Off + 772: + - On: Off + - Off: On + 896: + - On: On + - Off: Off + 775: + - On: Off + - Off: On + 956: + - On: On + - Off: Off + 20: + - On: Off + - Off: On + 23: + - On: Off + - Off: On + 955: + - On: Off + - Off: On + 19: + - On: On + - Off: Off +- proto: SinkStemlessWater + entities: + - uid: 808 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -1.5,-3.5 + parent: 1 +- proto: SoapSyndie + entities: + - uid: 809 + components: + - type: Transform + rot: 6.283185307179586 rad + pos: -1.5109272,-2.2405348 + parent: 1 +- proto: soda_dispenser + entities: + - uid: 810 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 1.5,-4.5 + parent: 1 + - type: Emagged +- proto: SpawnMobRadioGuard + entities: + - uid: 811 + components: + - type: Transform + pos: 9.5,-0.5 + parent: 1 + - uid: 812 + components: + - type: Transform + pos: 2.5,2.5 + parent: 1 +- proto: SpawnPointGhostSyndicateListener + entities: + - uid: 813 + components: + - type: Transform + pos: -0.5,1.5 + parent: 1 + - uid: 814 + components: + - type: Transform + pos: -1.5,1.5 + parent: 1 +- proto: StationMapBroken + entities: + - uid: 815 + components: + - type: Transform + pos: 1.5,4.5 + parent: 1 +- proto: SubstationWallBasic + entities: + - uid: 816 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 0.5,5.5 + parent: 1 +- proto: SuitStorageEVASyndicate + entities: + - uid: 611 + components: + - type: Transform + pos: -0.5,-7.5 + parent: 1 + - uid: 817 + components: + - type: Transform + pos: -1.5,-7.5 + parent: 1 + - type: EntityStorage + air: + volume: 200 + immutable: False + temperature: 293.14673 + moles: + - 1.7459903 + - 6.568249 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 +- proto: SyndicateComputerComms + entities: + - uid: 715 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 8.5,3.5 + parent: 1 + - type: CommunicationsConsole + color: gold + title: "Pirate Broadcast" + canShuttle: false +- proto: SyndicateMonitoringServer + entities: + - uid: 338 + components: + - type: Transform + pos: 2.5,-7.5 + parent: 1 + - type: SingletonDeviceNetServer + active: False + available: False +- proto: SyndicatePersonalAI + entities: + - uid: 819 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 7.4855704,-4.514364 + parent: 1 +- proto: SyndieHandyFlag + entities: + - uid: 951 + components: + - type: Transform + pos: 1.9013485,-7 + parent: 1 +- proto: SynthesizerInstrument + entities: + - uid: 820 + components: + - type: Transform + pos: 6.5734386,-5.497705 + parent: 1 +- proto: TableCarpet + entities: + - uid: 821 + components: + - type: Transform + pos: 7.5,-5.5 + parent: 1 + - uid: 822 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 1.5,1.5 + parent: 1 + - uid: 823 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 7.5,-4.5 + parent: 1 +- proto: TableGlass + entities: + - uid: 824 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.5,-2.5 + parent: 1 + - uid: 825 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.5,-3.5 + parent: 1 + - uid: 826 + components: + - type: Transform + pos: 1.5,-4.5 + parent: 1 +- proto: TableWoodReinforced + entities: + - uid: 382 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 8.5,0.5 + parent: 1 + - uid: 385 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 8.5,1.5 + parent: 1 + - uid: 386 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 12.5,-0.5 + parent: 1 + - uid: 387 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 8.5,2.5 + parent: 1 + - uid: 568 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 4.5,1.5 + parent: 1 + - uid: 828 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 8.5,4.5 + parent: 1 + - uid: 829 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 9.5,4.5 + parent: 1 +- proto: TelecomServerFilled + entities: + - uid: 612 + components: + - type: Transform + pos: 1.5,-7.5 + parent: 1 + - type: ContainerContainer + containers: + key_slots: !type:Container + showEnts: False + occludes: True + ents: + - 613 + - 614 + - 615 + machine_board: !type:Container + showEnts: False + occludes: True + ents: [] + machine_parts: !type:Container + showEnts: False + occludes: True + ents: [] +- proto: ToiletEmpty + entities: + - uid: 832 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -0.5,-2.5 + parent: 1 +- proto: ToolboxSyndicateFilled + entities: + - uid: 833 + components: + - type: Transform + pos: 4.5174317,5.701203 + parent: 1 + - uid: 834 + components: + - type: Transform + pos: 4.5330567,5.310578 + parent: 1 +- proto: ToyNuke + entities: + - uid: 838 + components: + - type: Transform + pos: 16.198624,-0.765656 + parent: 1 +- proto: VendingMachineBooze + entities: + - uid: 839 + components: + - type: Transform + pos: 4.5,-4.5 + parent: 1 + - type: Emagged +- proto: VendingMachineCigs + entities: + - uid: 565 + components: + - type: Transform + pos: 4.5,0.5 + parent: 1 +- proto: VendingMachineGames + entities: + - uid: 840 + components: + - type: Transform + pos: 8.5,-2.5 + parent: 1 + - type: Emagged +- proto: VendingMachineNutri + entities: + - uid: 841 + components: + - type: Transform + pos: 12.5,-2.5 + parent: 1 + - type: Emagged +- proto: VendingMachineSeedsUnlocked + entities: + - uid: 842 + components: + - type: Transform + pos: 11.5,-2.5 + parent: 1 + - type: Emagged +- proto: VendingMachineSyndieDrobe + entities: + - uid: 843 + components: + - type: Transform + pos: -2.5,0.5 + parent: 1 + - type: Emagged +- proto: VendingMachineTankDispenserEVA + entities: + - uid: 844 + components: + - type: Transform + pos: -1.5,3.5 + parent: 1 +- proto: WallPlastitanium + entities: + - uid: 22 + components: + - type: Transform + pos: 7.5,5.5 + parent: 1 + - uid: 30 + components: + - type: Transform + pos: 9.5,5.5 + parent: 1 + - uid: 41 + components: + - type: Transform + pos: 10.5,5.5 + parent: 1 + - uid: 42 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 10.5,11.5 + parent: 1 + - uid: 43 + components: + - type: Transform + pos: 12.5,9.5 + parent: 1 + - uid: 44 + components: + - type: Transform + pos: 8.5,9.5 + parent: 1 + - uid: 54 + components: + - type: Transform + pos: 5.5,-8.5 + parent: 1 + - uid: 125 + components: + - type: Transform + pos: 13.5,5.5 + parent: 1 + - uid: 126 + components: + - type: Transform + pos: 13.5,9.5 + parent: 1 + - uid: 132 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 9.5,9.5 + parent: 1 + - uid: 133 + components: + - type: Transform + pos: 12.5,5.5 + parent: 1 + - uid: 134 + components: + - type: Transform + pos: 14.5,6.5 + parent: 1 + - uid: 135 + components: + - type: Transform + pos: 14.5,7.5 + parent: 1 + - uid: 136 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 10.5,9.5 + parent: 1 + - uid: 201 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 14.5,9.5 + parent: 1 + - uid: 209 + components: + - type: Transform + pos: 8.5,5.5 + parent: 1 + - uid: 322 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 12.5,11.5 + parent: 1 + - uid: 323 + components: + - type: Transform + pos: 13.5,4.5 + parent: 1 + - uid: 327 + components: + - type: Transform + pos: 12.5,10.5 + parent: 1 + - uid: 332 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 10.5,10.5 + parent: 1 + - uid: 335 + components: + - type: Transform + pos: 2.5,-8.5 + parent: 1 + - uid: 336 + components: + - type: Transform + pos: 1.5,-8.5 + parent: 1 + - uid: 340 + components: + - type: Transform + pos: 0.5,-8.5 + parent: 1 + - uid: 407 + components: + - type: Transform + pos: 14.5,8.5 + parent: 1 + - uid: 416 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 14.5,5.5 + parent: 1 + - uid: 425 + components: + - type: Transform + pos: 4.5,-8.5 + parent: 1 + - uid: 617 + components: + - type: Transform + pos: 3.5,-8.5 + parent: 1 + - uid: 845 + components: + - type: Transform + pos: -2.5,-8.5 + parent: 1 + - uid: 846 + components: + - type: Transform + pos: -1.5,-8.5 + parent: 1 + - uid: 847 + components: + - type: Transform + pos: -0.5,-8.5 + parent: 1 + - uid: 848 + components: + - type: Transform + pos: -0.5,-1.5 + parent: 1 + - uid: 849 + components: + - type: Transform + pos: -2.5,6.5 + parent: 1 + - uid: 850 + components: + - type: Transform + pos: 3.5,-1.5 + parent: 1 + - uid: 851 + components: + - type: Transform + pos: 14.5,-2.5 + parent: 1 + - uid: 852 + components: + - type: Transform + pos: 14.5,-1.5 + parent: 1 + - uid: 853 + components: + - type: Transform + pos: 8.5,-1.5 + parent: 1 + - uid: 854 + components: + - type: Transform + pos: -3.5,1.5 + parent: 1 + - uid: 855 + components: + - type: Transform + pos: -3.5,0.5 + parent: 1 + - uid: 856 + components: + - type: Transform + pos: 5.5,5.5 + parent: 1 + - uid: 857 + components: + - type: Transform + pos: -1.5,-4.5 + parent: 1 + - uid: 858 + components: + - type: Transform + pos: -2.5,-5.5 + parent: 1 + - uid: 859 + components: + - type: Transform + pos: 2.5,6.5 + parent: 1 + - uid: 860 + components: + - type: Transform + pos: -0.5,6.5 + parent: 1 + - uid: 861 + components: + - type: Transform + pos: 4.5,6.5 + parent: 1 + - uid: 862 + components: + - type: Transform + pos: 5.5,-4.5 + parent: 1 + - uid: 863 + components: + - type: Transform + pos: 9.5,-1.5 + parent: 1 + - uid: 864 + components: + - type: Transform + pos: 11.5,-1.5 + parent: 1 + - uid: 865 + components: + - type: Transform + pos: 12.5,-1.5 + parent: 1 + - uid: 866 + components: + - type: Transform + pos: 5.5,-3.5 + parent: 1 + - uid: 867 + components: + - type: Transform + pos: 5.5,-2.5 + parent: 1 + - uid: 868 + components: + - type: Transform + pos: 5.5,-7.5 + parent: 1 + - uid: 869 + components: + - type: Transform + pos: 7.5,-1.5 + parent: 1 + - uid: 870 + components: + - type: Transform + pos: 13.5,-1.5 + parent: 1 + - uid: 871 + components: + - type: Transform + pos: 13.5,3.5 + parent: 1 + - uid: 872 + components: + - type: Transform + pos: 3.5,-5.5 + parent: 1 + - uid: 873 + components: + - type: Transform + pos: 4.5,-5.5 + parent: 1 + - uid: 874 + components: + - type: Transform + pos: -2.5,4.5 + parent: 1 + - uid: 875 + components: + - type: Transform + pos: 5.5,-5.5 + parent: 1 + - uid: 876 + components: + - type: Transform + pos: 5.5,-6.5 + parent: 1 + - uid: 877 + components: + - type: Transform + pos: 1.5,-5.5 + parent: 1 + - uid: 878 + components: + - type: Transform + pos: 0.5,-5.5 + parent: 1 + - uid: 879 + components: + - type: Transform + pos: 0.5,-4.5 + parent: 1 + - uid: 881 + components: + - type: Transform + pos: -2.5,-4.5 + parent: 1 + - uid: 882 + components: + - type: Transform + pos: -2.5,-2.5 + parent: 1 + - uid: 883 + components: + - type: Transform + pos: 0.5,-7.5 + parent: 1 + - uid: 885 + components: + - type: Transform + pos: -2.5,-6.5 + parent: 1 + - uid: 886 + components: + - type: Transform + pos: -0.5,-4.5 + parent: 1 + - uid: 887 + components: + - type: Transform + pos: 0.5,0.5 + parent: 1 + - uid: 888 + components: + - type: Transform + pos: 0.5,1.5 + parent: 1 + - uid: 889 + components: + - type: Transform + pos: -1.5,2.5 + parent: 1 + - uid: 890 + components: + - type: Transform + pos: 7.5,4.5 + parent: 1 + - uid: 891 + components: + - type: Transform + pos: 0.5,5.5 + parent: 1 + - uid: 892 + components: + - type: Transform + pos: -2.5,2.5 + parent: 1 + - uid: 893 + components: + - type: Transform + pos: -2.5,3.5 + parent: 1 + - uid: 894 + components: + - type: Transform + pos: -2.5,5.5 + parent: 1 + - uid: 895 + components: + - type: Transform + pos: 1.5,4.5 + parent: 1 + - uid: 897 + components: + - type: Transform + pos: -2.5,-1.5 + parent: 1 + - uid: 898 + components: + - type: Transform + pos: 1.5,-1.5 + parent: 1 + - uid: 899 + components: + - type: Transform + pos: 0.5,-1.5 + parent: 1 + - uid: 900 + components: + - type: Transform + pos: 0.5,-3.5 + parent: 1 + - uid: 901 + components: + - type: Transform + pos: -2.5,-3.5 + parent: 1 + - uid: 902 + components: + - type: Transform + pos: 0.5,-2.5 + parent: 1 + - uid: 906 + components: + - type: Transform + pos: 5.5,-1.5 + parent: 1 + - uid: 907 + components: + - type: Transform + pos: 4.5,-1.5 + parent: 1 + - uid: 908 + components: + - type: Transform + pos: 7.5,2.5 + parent: 1 + - uid: 909 + components: + - type: Transform + pos: 7.5,1.5 + parent: 1 + - uid: 910 + components: + - type: Transform + pos: 7.5,0.5 + parent: 1 + - uid: 911 + components: + - type: Transform + pos: 7.5,3.5 + parent: 1 + - uid: 913 + components: + - type: Transform + pos: 5.5,0.5 + parent: 1 + - uid: 914 + components: + - type: Transform + pos: 6.5,0.5 + parent: 1 + - uid: 915 + components: + - type: Transform + pos: -2.5,-7.5 + parent: 1 + - uid: 916 + components: + - type: Transform + pos: 3.5,-7.5 + parent: 1 + - uid: 918 + components: + - type: Transform + pos: -2.5,-0.5 + parent: 1 + - uid: 920 + components: + - type: Transform + pos: 0.5,2.5 + parent: 1 + - uid: 921 + components: + - type: Transform + pos: -0.5,2.5 + parent: 1 + - uid: 922 + components: + - type: Transform + pos: -2.5,1.5 + parent: 1 + - uid: 923 + components: + - type: Transform + pos: 0.5,4.5 + parent: 1 + - uid: 924 + components: + - type: Transform + pos: 2.5,4.5 + parent: 1 + - uid: 925 + components: + - type: Transform + pos: 5.5,1.5 + parent: 1 + - uid: 926 + components: + - type: Transform + pos: 5.5,4.5 + parent: 1 + - uid: 927 + components: + - type: Transform + pos: 5.5,3.5 + parent: 1 + - uid: 928 + components: + - type: Transform + pos: 4.5,4.5 + parent: 1 + - uid: 929 + components: + - type: Transform + pos: -1.5,6.5 + parent: 1 + - uid: 930 + components: + - type: Transform + pos: 0.5,6.5 + parent: 1 + - uid: 931 + components: + - type: Transform + pos: 6.5,4.5 + parent: 1 + - uid: 932 + components: + - type: Transform + pos: 9.5,-6.5 + parent: 1 + - uid: 933 + components: + - type: Transform + pos: 9.5,-4.5 + parent: 1 + - uid: 934 + components: + - type: Transform + pos: 5.5,6.5 + parent: 1 + - uid: 935 + components: + - type: Transform + pos: 9.5,-2.5 + parent: 1 + - uid: 936 + components: + - type: Transform + pos: 1.5,6.5 + parent: 1 + - uid: 937 + components: + - type: Transform + pos: 14.5,-3.5 + parent: 1 + - uid: 938 + components: + - type: Transform + pos: 14.5,-4.5 + parent: 1 + - uid: 939 + components: + - type: Transform + pos: 14.5,-5.5 + parent: 1 + - uid: 940 + components: + - type: Transform + pos: 9.5,-5.5 + parent: 1 + - uid: 941 + components: + - type: Transform + pos: -3.5,-0.5 + parent: 1 +- proto: WallPlastitaniumDiagonal + entities: + - uid: 128 + components: + - type: Transform + pos: 13.5,6.5 + parent: 1 + - uid: 333 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 13.5,8.5 + parent: 1 + - uid: 591 + components: + - type: Transform + pos: 7.5,9.5 + parent: 1 +- proto: WarpPoint + entities: + - uid: 942 + components: + - type: MetaData + name: Syndicate Listening Post + - type: Transform + pos: 10.5,0.5 + parent: 1 +- proto: WashingMachineFilledClothes + entities: + - uid: 2 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 4.5,-7.5 + parent: 1 +- proto: WaterCooler + entities: + - uid: 943 + components: + - type: Transform + pos: 1.5,0.5 + parent: 1 +- proto: WaterTankHighCapacity + entities: + - uid: 944 + components: + - type: Transform + pos: 13.5,-2.5 + parent: 1 +- proto: WeaponShotgunKammerer + entities: + - uid: 379 + components: + - type: Transform + parent: 363 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 380 + components: + - type: Transform + parent: 363 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: WeaponTurretSyndicate + entities: + - uid: 32 + components: + - type: Transform + pos: 15.5,4.5 + parent: 1 + - uid: 131 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 18.5,-3.5 + parent: 1 + - uid: 233 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 6.5,-9.5 + parent: 1 + - uid: 408 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 6.5,5.5 + parent: 1 + - uid: 580 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 13.5,7.5 + parent: 1 + - uid: 989 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -6.5,-8.5 + parent: 1 + - uid: 990 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -3.5,9.5 + parent: 1 +- proto: Windoor + entities: + - uid: 618 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -0.5,-7.5 + parent: 1 + - uid: 945 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -1.5,-7.5 + parent: 1 + - uid: 946 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 4.5,5.5 + parent: 1 + - uid: 947 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -2.5,0.5 + parent: 1 +- proto: WindoorSecure + entities: + - uid: 610 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 2.5,-7.5 + parent: 1 + - uid: 783 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.5,-7.5 + parent: 1 + - uid: 948 + components: + - type: Transform + pos: -1.5,5.5 + parent: 1 + - uid: 949 + components: + - type: Transform + pos: -0.5,5.5 + parent: 1 +- proto: WoodDoor + entities: + - uid: 12 + components: + - type: Transform + pos: -1.5,-1.5 + parent: 1 +... diff --git a/Resources/Maps/Test/dev_map.yml b/Resources/Maps/Test/dev_map.yml index f00d7049a5..910a059ee7 100644 --- a/Resources/Maps/Test/dev_map.yml +++ b/Resources/Maps/Test/dev_map.yml @@ -4358,7 +4358,6 @@ entities: solutions: absorbed: temperature: 293.15 - canMix: False canReact: True maxVol: 50 name: null diff --git a/Resources/Migrations/deltaMigrations.yml b/Resources/Migrations/deltaMigrations.yml deleted file mode 100644 index 5081d04dc5..0000000000 --- a/Resources/Migrations/deltaMigrations.yml +++ /dev/null @@ -1,101 +0,0 @@ -# 2023-09-29 -PosterContrabandSMSyndie: PosterContrabandSafetyMothSyndie -PosterLegitSMPoisoning: PosterLegitSafetyMothPoisoning -PosterLegitSMBoH: PosterLegitSafetyMothBoH -PosterLegitSMHardhats: PosterLegitSafetyMothHardhat -PosterLegitSMFires: PosterLegitSafetyMothFires -PosterLegitSMPiping: PosterLegitSafetyMothPiping -PosterLegitSMMeth: PosterLegitSafetyMothMeth -PosterLegitSMEpi: PosterLegitSafetyMothEpi -PosterLegitSMPills: PosterLegitSafetyMothPills -PosterLegitSMAnomalies: PosterLegitSafetyMothDelam -PosterLegitSMGlimmer: PosterLegitSafetyMothGlimmer -EngineeringTechFab: Autolathe -EngineeringTechFabCircuitboard: AutolatheMachineCircuitboard -ScienceTechFab: Protolathe -ScienceTechFabCircuitboard: ProtolatheMachineCircuitboard -ServiceTechFab: Autolathe -ServiceTechFabCircuitboard: AutolatheMachineCircuitboard - -# 2023-10-05 -FoodMothTomatoSauce: null -LockerEpistemicsFilled: LockerScienceFilled -LockerMystagogueFilled: LockerResearchDirectorFilled -LockerEpistemics: LockerScientist -LockerMystagogue: LockerResearchDirector -HyperlinkBookAlerts: BookRandom -HyperlinkBookAtmos: BookAtmosDistro -HyperlinkBookBartending: BookBartendersManual -HyperlinkBookBotany: BookLeafLoversSecret -HyperlinkBookChemistry: BookChemicalCompendium -HyperlinkBookCooking: BookChefGaming -HyperlinkBookGlimmer: BookScientistsGuidebook -HyperlinkBookHacking: BookEngineersHandbook -HyperlinkBookMedical: BookMedicalReferenceBook -HyperlinkBookPower: BookEngineersHandbook -HyperlinkBookProcedure: BookRandom -HyperlinkBookShuttle: BookRandom -HyperlinkBookSpaceLaw: BookSecurity -HyperlinkBookSupernanny: BookHowToSurvive -SpawnPointCataloguer: SpawnPointLibrarian -SpawnPointCyborg: SpawnPointBorg -SpawnPointMedicalCyborg: null -SpawnPointEpistemologist: SpawnPointScientist -SpawnPointMystagogue: SpawnPointResearchDirector -SpawnPointSalvageTechnician: SpawnPointSalvageSpecialist -SpawnPointValet: SpawnPointServiceWorker -VendingMachineEpiDrobe: VendingMachineSciDrobe -PlushieMoffRandom: PlushieMothRandom -PlushieMoffbar: PlushieMothBartender -PlushieMoffsician: PlushieMothMusician -PlushieMoff: PlushieMoth -ArachneWeb: SpiderWeb -PowerCellBluespace: PowerCellHigh -CrateMedicalDefib: CrateMedical -WeaponShotgunEnforcerNonLethal: WeaponShotgunEnforcerRubber - -# 2023-10-06 -FoodMealTaco: FoodMealSoftTaco -FoodTinBeansOpen: FoodTinBeans -FoodBreadMoldy: FoodBreadMoldySlice -FoodMeatPatty: FoodMeatMeatball -ClothingHeadHoodMystic: ClothingHeadHoodMysta - -# 2023-11-07 -VendingMachineMailDrobe: VendingMachineCourierDrobe - -#Delta V temporary changes. Remove When items are added. -ComputerShipyard: ComputerBroken -ShipyardComputerCircuitboard: null -SpawnMobGolemCult: null -ShogiBoard: CheckerBoard - -# 2023-11-21 -AirlockBrigLocked: AirlockSecurityLawyerLocked -AirlockBrigGlassLocked: AirlockSecurityLawyerGlassLocked -WindoorSecureBrigLocked: WindoorSecureSecurityLawyerLocked - -#Delta V Optional: Remove "#" for specific maps. -#AsteroidRock: AsteroidAltRock -#AsteroidRockMining: AsteroidAltRockMining - -# 2023-12-16 -PlushieLizardMirrored: PlushieLizard - -# 2024-1-22 -#Use these lights because they are better -PoweredlightExterior: PoweredLightBlueInterior -ExteriorLightTube: BlueLightTube - -# 2024-01-26 -SalvagePartsSpawnerSubSpace: SalvagePartsSpawnerMid - -# 2024-02-08 -SpawnVehicleAntagVehicle: null - -# 2024-02-26 -SpaceTickSpawnerNPC: SpaceTickSpawner - -# 2024-03-07 -LockableButtonBrig: LockableButtonSecurity -SignDirectionalICU: SignDirectionalIcu diff --git a/Resources/Migrations/eeMigration.yml b/Resources/Migrations/eeMigration.yml index 26713f6cba..195c1f8765 100644 --- a/Resources/Migrations/eeMigration.yml +++ b/Resources/Migrations/eeMigration.yml @@ -1,3 +1,119 @@ +# 2023-09-29 +PosterContrabandSMSyndie: PosterContrabandSafetyMothSyndie +PosterLegitSMPoisoning: PosterLegitSafetyMothPoisoning +PosterLegitSMBoH: PosterLegitSafetyMothBoH +PosterLegitSMHardhats: PosterLegitSafetyMothHardhat +PosterLegitSMFires: PosterLegitSafetyMothFires +PosterLegitSMPiping: PosterLegitSafetyMothPiping +PosterLegitSMMeth: PosterLegitSafetyMothMeth +PosterLegitSMEpi: PosterLegitSafetyMothEpi +PosterLegitSMPills: PosterLegitSafetyMothPills +PosterLegitSMAnomalies: PosterLegitSafetyMothDelam +PosterLegitSMGlimmer: PosterLegitSafetyMothGlimmer +EngineeringTechFab: Autolathe +EngineeringTechFabCircuitboard: AutolatheMachineCircuitboard +ScienceTechFab: Protolathe +ScienceTechFabCircuitboard: ProtolatheMachineCircuitboard +ServiceTechFab: Autolathe +ServiceTechFabCircuitboard: AutolatheMachineCircuitboard + +# 2023-10-05 +FoodMothTomatoSauce: null +LockerEpistemicsFilled: LockerScienceFilled +LockerMystagogueFilled: LockerResearchDirectorFilled +LockerEpistemics: LockerScientist +LockerMystagogue: LockerResearchDirector +HyperlinkBookAlerts: BookRandom +HyperlinkBookAtmos: BookAtmosDistro +HyperlinkBookBartending: BookBartendersManual +HyperlinkBookBotany: BookLeafLoversSecret +HyperlinkBookChemistry: BookChemicalCompendium +HyperlinkBookCooking: BookChefGaming +HyperlinkBookGlimmer: BookScientistsGuidebook +HyperlinkBookHacking: BookEngineersHandbook +HyperlinkBookMedical: BookMedicalReferenceBook +HyperlinkBookPower: BookEngineersHandbook +HyperlinkBookProcedure: BookRandom +HyperlinkBookShuttle: BookRandom +HyperlinkBookSpaceLaw: BookSecurity +HyperlinkBookSupernanny: BookHowToSurvive +SpawnPointCataloguer: SpawnPointLibrarian +SpawnPointCyborg: SpawnPointBorg +SpawnPointMedicalCyborg: null +SpawnPointEpistemologist: SpawnPointScientist +SpawnPointMystagogue: SpawnPointResearchDirector +SpawnPointSalvageTechnician: SpawnPointSalvageSpecialist +SpawnPointValet: SpawnPointServiceWorker +VendingMachineEpiDrobe: VendingMachineSciDrobe +PlushieMoffRandom: PlushieMothRandom +PlushieMoffbar: PlushieMothBartender +PlushieMoffsician: PlushieMothMusician +PlushieMoff: PlushieMoth +ArachneWeb: SpiderWeb +PowerCellBluespace: PowerCellHigh +CrateMedicalDefib: CrateMedical +WeaponShotgunEnforcerNonLethal: WeaponShotgunEnforcerRubber + +# 2023-10-06 +FoodMealTaco: FoodMealSoftTaco +FoodTinBeansOpen: FoodTinBeans +FoodBreadMoldy: FoodBreadMoldySlice +FoodMeatPatty: FoodMeatMeatball +ClothingHeadHoodMystic: ClothingHeadHoodMysta + +# 2023-11-07 +VendingMachineMailDrobe: VendingMachineCourierDrobe + +#Delta V temporary changes. Remove When items are added. # TODO: EE remove when Rebased +ComputerShipyard: ComputerBroken +ShipyardComputerCircuitboard: null +SpawnMobGolemCult: null +ShogiBoard: CheckerBoard + +# 2023-11-21 +AirlockBrigLocked: AirlockSecurityLawyerLocked +AirlockBrigGlassLocked: AirlockSecurityLawyerGlassLocked +WindoorSecureBrigLocked: WindoorSecureSecurityLawyerLocked + +#Delta V Optional: Remove "#" for specific maps. +#AsteroidRock: AsteroidAltRock +#AsteroidRockMining: AsteroidAltRockMining # These two rocks change between the two variants of + # Wizden Rocks, they're the same rock but with two different textures + # both are ugly, but you may have a preference. + +# 2023-12-16 +PlushieLizardMirrored: PlushieLizard + +# 2024-1-22 +#Use these lights because they are better +PoweredlightExterior: PoweredLightBlueInterior +ExteriorLightTube: BlueLightTube + +# 2024-01-26 +SalvagePartsSpawnerSubSpace: SalvagePartsSpawnerMid + +# 2024-02-08 +SpawnVehicleAntagVehicle: null + +# 2024-02-26 +SpaceTickSpawnerNPC: SpaceTickSpawner + +# 2024-03-07 +LockableButtonBrig: LockableButtonSecurity +SignDirectionalICU: SignDirectionalIcu + +# 2024-03-08 +ClothingBackpackSatchelBrigmedicDeltaVFilled: ClothingBackpackSatchelBrigmedicFilled +ClothingBackpackDuffelBrigmedicDeltaVFilled: ClothingBackpackDuffelBrigmedicFilled +ClothingBackpackBrigmedicDeltaVFilled: ClothingBackpackBrigmedicFilled + # 2024-06-08 CrateFunPlushie: CrateFunToyBox CrateFunLizardPlushieBulk: CrateFunToyBox + +# 2024-08-22 - Frontier Mail - Add more to these when they come up as mapped. Part of the Frontier Mail port, blame Tortuga. +MailPAI: MailNFPAI + +# 2024-08-27 +Oracle: OracleSpawner +SophicScribe: SophicScribeSpawner diff --git a/Resources/Migrations/eeMigrations.yml b/Resources/Migrations/eeMigrations.yml deleted file mode 100644 index 682c3c412b..0000000000 --- a/Resources/Migrations/eeMigrations.yml +++ /dev/null @@ -1,4 +0,0 @@ -# 2024-03-08 -ClothingBackpackSatchelBrigmedicDeltaVFilled: ClothingBackpackSatchelBrigmedicFilled -ClothingBackpackDuffelBrigmedicDeltaVFilled: ClothingBackpackDuffelBrigmedicFilled -ClothingBackpackBrigmedicDeltaVFilled: ClothingBackpackBrigmedicFilled diff --git a/Resources/Migrations/migration.yml b/Resources/Migrations/migration.yml index 7f19699ad8..3ac6b353a9 100644 --- a/Resources/Migrations/migration.yml +++ b/Resources/Migrations/migration.yml @@ -126,28 +126,6 @@ DrinkGoldschlagerGlass: DrinkGildlagerGlass ClosetBase: ClosetSteelBase MonkeyCubeBox: VariantCubeBox -# 2024-01-08 -SalvagePartsT4Spawner: SalvageLootSpawner -SalvagePartsT3Spawner: SalvageLootSpawner -SalvagePartsT3T4Spawner: SalvageLootSpawner -SalvagePartsT2Spawner: SalvageLootSpawner -AdvancedCapacitorStockPart: CapacitorStockPart -SuperCapacitorStockPart: CapacitorStockPart -QuadraticCapacitorStockPart: CapacitorStockPart -NanoManipulatorStockPart: MicroManipulatorStockPart -PicoManipulatorStockPart: MicroManipulatorStockPart -FemtoManipulatorStockPart: MicroManipulatorStockPart -AdvancedMatterBinStockPart: MatterBinStockPart -SuperMatterBinStockPart: MatterBinStockPart -BluespaceMatterBinStockPart: MatterBinStockPart -AnsibleSubspaceStockPart: null -FilterSubspaceStockPart: null -AmplifierSubspaceStockPart: null -TreatmentSubspaceStockPart: null -AnalyzerSubspaceStockPart: null -CrystalSubspaceStockPart: null -TransmitterSubspaceStockPart: null - # 2024-01-10 ClothingHeadHatHoodRad: null @@ -182,7 +160,7 @@ ActionVehicleHorn: null CrateFunATV: null CrateFunSyndicateSegway: null MobTaxiBot: null -MobSupplyBot: null +# MobSupplyBot: null SpawnVehicleMotobike: null SpawnVehicleATV: null SpawnVehicleSecway: null @@ -234,6 +212,8 @@ AirlockShuttleEasyPryLocked: AirlockExternalShuttleLocked ClothingBackpackFilledDetective: ClothingBackpackSecurityFilledDetective ClothingBackpackDuffelFilledDetective: ClothingBackpackDuffelSecurityFilledDetective ClothingBackpackSatchelFilledDetective: ClothingBackpackSatchelSecurityFilledDetective +FoodChili: FoodChiliPepper +FoodChilly: FoodChillyPepper # 2024-03-16 ClothingHeadHatHairflower: FoodPoppy @@ -242,6 +222,8 @@ ClothingHeadHatHairflower: FoodPoppy RPED: null # 2024-03-30 +TraversalDistorterMachineCircuitboard: null +MachineTraversalDistorter: null # These are technically not equivalent, but it probably makes more sense to replace any existing SCAF stuff with SOME kind of armor, instead of just deleting it outright. ClothingHeadHelmetScaf: ClothingHeadHelmetBasic ClothingOuterArmorScaf: ClothingOuterArmorBasic @@ -254,3 +236,11 @@ BriefcaseSyndieBase: null # 2024-04-08 BodyBag_Container: BodyBag BodyBag_Folded: BodyBagFolded + +# 2024-04-26 +GlassBoxLaserBroken: GlassBoxBroken +ReinforcementRadioSyndicateMonkey: ReinforcementRadioSyndicateAncestor +ReinforcementRadioSyndicateMonkeyNukeops: ReinforcementRadioSyndicateAncestorNukeops + +# 2024-05-01 +DrinkBottleGoldschlager: DrinkBottleGildlager diff --git a/Resources/Prototypes/Accents/word_replacements.yml b/Resources/Prototypes/Accents/word_replacements.yml index a5269924f7..cb4cdf0683 100644 --- a/Resources/Prototypes/Accents/word_replacements.yml +++ b/Resources/Prototypes/Accents/word_replacements.yml @@ -425,3 +425,50 @@ chatsan-word-42: chatsan-replacement-42 chatsan-word-43: chatsan-replacement-43 chatsan-word-44: chatsan-replacement-44 + +- type: accent + id: liar + wordReplacements: + liar-word-1: liar-word-replacement-1 + liar-word-2: liar-word-replacement-2 + liar-word-3: liar-word-replacement-3 + liar-word-4: liar-word-replacement-4 + liar-word-5: liar-word-replacement-5 + liar-word-6: liar-word-replacement-6 + liar-word-7: liar-word-replacement-7 + liar-word-8: liar-word-replacement-8 + liar-word-9: liar-word-replacement-9 + liar-word-10: liar-word-replacement-10 + liar-word-11: liar-word-replacement-11 + liar-word-12: liar-word-replacement-12 + liar-word-13: liar-word-replacement-13 + liar-word-14: liar-word-replacement-14 + liar-word-15: liar-word-replacement-15 + liar-word-16: liar-word-replacement-16 + liar-word-17: liar-word-replacement-17 + liar-word-18: liar-word-replacement-18 + liar-word-19: liar-word-replacement-19 + liar-word-20: liar-word-replacement-20 + liar-word-21: liar-word-replacement-21 + liar-word-22: liar-word-replacement-22 + liar-word-23: liar-word-replacement-23 + liar-word-24: liar-word-replacement-24 + liar-word-25: liar-word-replacement-25 + liar-word-26: liar-word-replacement-26 + liar-word-27: liar-word-replacement-27 + liar-word-28: liar-word-replacement-28 + liar-word-29: liar-word-replacement-29 + liar-word-30: liar-word-replacement-30 + liar-word-31: liar-word-replacement-31 + liar-word-32: liar-word-replacement-32 + liar-word-33: liar-word-replacement-33 + liar-word-34: liar-word-replacement-34 + liar-word-34-2: liar-word-replacement-34 + liar-word-35: liar-word-replacement-35 + liar-word-36: liar-word-replacement-36 + liar-word-37: liar-word-replacement-37 + liar-word-38: liar-word-replacement-38 + liar-word-39: liar-word-replacement-39 + liar-word-40: liar-word-replacement-40 + liar-word-41: liar-word-replacement-41 + liar-word-42: liar-word-replacement-42 \ No newline at end of file diff --git a/Resources/Prototypes/Access/security.yml b/Resources/Prototypes/Access/security.yml index cfb6e60060..b64424d855 100644 --- a/Resources/Prototypes/Access/security.yml +++ b/Resources/Prototypes/Access/security.yml @@ -10,10 +10,9 @@ id: Armory name: id-card-access-level-armory -# Delta V: Removes Brig access because redundant -#- type: accessLevel -# id: Brig -# name: id-card-access-level-brig +- type: accessLevel + id: Brig + name: id-card-access-level-brig - type: accessLevel id: Detective @@ -25,14 +24,14 @@ - HeadOfSecurity - Security - Armory - #- Brig #Delta V removed + - Brig - Detective - Cryogenics - - Corpsman # DeltaV - add Corpsman access + - Corpsman - type: accessGroup id: Armory tags: - Security - Armory - # Brig #Delta V removed + - Brig diff --git a/Resources/Prototypes/Actions/polymorph.yml b/Resources/Prototypes/Actions/polymorph.yml index 7472fc0062..445dc8d9f5 100644 --- a/Resources/Prototypes/Actions/polymorph.yml +++ b/Resources/Prototypes/Actions/polymorph.yml @@ -14,3 +14,33 @@ - type: InstantAction event: !type:PolymorphActionEvent itemIconStyle: NoItem + +- type: entity + id: ActionPolymorphWizardSpider + name: Spider Polymorph + description: Polymorphs you into a Spider. + noSpawn: true + components: + - type: InstantAction + useDelay: 60 + event: !type:PolymorphActionEvent + protoId: WizardSpider + itemIconStyle: NoItem + icon: + sprite: Mobs/Animals/spider.rsi + state: tarantula + +- type: entity + id: ActionPolymorphWizardRod + name: Rod Form + description: CLANG! + noSpawn: true + components: + - type: InstantAction + useDelay: 60 + event: !type:PolymorphActionEvent + protoId: WizardRod + itemIconStyle: NoItem + icon: + sprite: Objects/Fun/immovable_rod.rsi + state: icon diff --git a/Resources/Prototypes/Actions/psionics.yml b/Resources/Prototypes/Actions/psionics.yml index 62a7fc014c..a372a480ac 100644 --- a/Resources/Prototypes/Actions/psionics.yml +++ b/Resources/Prototypes/Actions/psionics.yml @@ -4,14 +4,18 @@ description: action-description-dispel noSpawn: true components: - - type: EntityTargetAction - icon: Interface/VerbIcons/dispel.png - useDelay: 45 - checkCanAccess: false - range: 6 - itemIconStyle: BigAction - canTargetSelf: false - event: !type:DispelPowerActionEvent + - type: EntityTargetAction + icon: Interface/VerbIcons/dispel.png + useDelay: 45 + checkCanAccess: false + range: 6 + itemIconStyle: BigAction + canTargetSelf: false + blacklist: + components: + - PsionicInsulation + - Mindbroken + event: !type:DispelPowerActionEvent - type: entity id: ActionMassSleep @@ -19,13 +23,13 @@ description: action-description-mass-sleep noSpawn: true components: - - type: WorldTargetAction - icon: Interface/VerbIcons/mass_sleep.png - useDelay: 60 - checkCanAccess: false - range: 8 - itemIconStyle: BigAction - event: !type:MassSleepPowerActionEvent + - type: WorldTargetAction + icon: Interface/VerbIcons/mass_sleep.png + useDelay: 60 + checkCanAccess: false + range: 8 + itemIconStyle: BigAction + event: !type:MassSleepPowerActionEvent - type: entity id: ActionMindSwap @@ -33,13 +37,17 @@ description: action-description-mind-swap noSpawn: true components: - - type: EntityTargetAction - icon: Interface/VerbIcons/mind_swap.png - useDelay: 240 - checkCanAccess: false - range: 8 - itemIconStyle: BigAction - event: !type:MindSwapPowerActionEvent + - type: EntityTargetAction + icon: Interface/VerbIcons/mind_swap.png + useDelay: 240 + checkCanAccess: false + range: 8 + itemIconStyle: BigAction + blacklist: + components: + - PsionicInsulation + - Mindbroken + event: !type:MindSwapPowerActionEvent - type: entity id: ActionMindSwapReturn @@ -47,11 +55,11 @@ description: action-description-mind-swap-return noSpawn: true components: - - type: InstantAction - icon: Interface/VerbIcons/mind_swap_return.png - useDelay: 20 - checkCanInteract: false - event: !type:MindSwapPowerReturnActionEvent + - type: InstantAction + icon: Interface/VerbIcons/mind_swap_return.png + useDelay: 20 + checkCanInteract: false + event: !type:MindSwapPowerReturnActionEvent - type: entity id: ActionNoosphericZap @@ -59,12 +67,16 @@ description: action-description-noospheric-zap noSpawn: true components: - - type: EntityTargetAction - icon: Interface/VerbIcons/noospheric_zap.png - useDelay: 100 - range: 5 - itemIconStyle: BigAction - event: !type:NoosphericZapPowerActionEvent + - type: EntityTargetAction + icon: Interface/VerbIcons/noospheric_zap.png + useDelay: 100 + range: 5 + itemIconStyle: BigAction + blacklist: + components: + - PsionicInsulation + - Mindbroken + event: !type:NoosphericZapPowerActionEvent - type: entity id: ActionPyrokinesis @@ -72,13 +84,13 @@ description: action-description-pyrokinesis noSpawn: true components: - - type: EntityTargetAction - icon: Interface/VerbIcons/pyrokinesis.png - useDelay: 50 - range: 6 - checkCanAccess: false - itemIconStyle: BigAction - event: !type:PyrokinesisPowerActionEvent + - type: EntityTargetAction + icon: Interface/VerbIcons/pyrokinesis.png + useDelay: 50 + range: 6 + checkCanAccess: false + itemIconStyle: BigAction + event: !type:PyrokinesisPowerActionEvent - type: entity id: ActionMetapsionic @@ -86,10 +98,10 @@ description: action-description-metapsionic noSpawn: true components: - - type: InstantAction - icon: Interface/VerbIcons/metapsionic.png - useDelay: 45 - event: !type:MetapsionicPowerActionEvent + - type: InstantAction + icon: Interface/VerbIcons/metapsionic.png + useDelay: 45 + event: !type:MetapsionicPowerActionEvent - type: entity id: ActionPsionicRegeneration @@ -97,10 +109,10 @@ description: action-description-psionic-regeneration noSpawn: true components: - - type: InstantAction - icon: Interface/VerbIcons/psionic_regeneration.png - useDelay: 120 - event: !type:PsionicRegenerationPowerActionEvent + - type: InstantAction + icon: Interface/VerbIcons/psionic_regeneration.png + useDelay: 120 + event: !type:PsionicRegenerationPowerActionEvent - type: entity id: ActionTelegnosis @@ -108,10 +120,10 @@ description: action-description-telegnosis noSpawn: true components: - - type: InstantAction - icon: Interface/VerbIcons/telegnosis.png - useDelay: 150 - event: !type:TelegnosisPowerActionEvent + - type: InstantAction + icon: Interface/VerbIcons/telegnosis.png + useDelay: 150 + event: !type:TelegnosisPowerActionEvent - type: entity id: ActionPsionicInvisibility @@ -119,18 +131,274 @@ description: action-description-psionic-invisibility noSpawn: true components: - - type: InstantAction - icon: Interface/VerbIcons/psionic_invisibility.png - useDelay: 120 - event: !type:PsionicInvisibilityPowerActionEvent + - type: InstantAction + icon: Interface/VerbIcons/psionic_invisibility.png + useDelay: 120 + event: !type:PsionicInvisibilityPowerActionEvent - type: entity id: ActionPsionicInvisibilityUsed name: action-name-psionic-invisibility-off description: action-description-psionic-invisibility-off noSpawn: true + components: + - type: InstantAction + icon: Interface/VerbIcons/psionic_invisibility_off.png + event: !type:RemovePsionicInvisibilityOffPowerActionEvent + +- type: entity + id: ActionHealingWord + name: action-name-healing-word + description: action-description-healing-word + noSpawn: true + components: + - type: EntityTargetAction + icon: { sprite: Interface/Actions/psionics.rsi, state: healing_word } + useDelay: 10 + checkCanAccess: false + range: 6 + itemIconStyle: BigAction + canTargetSelf: true + blacklist: + components: + - PsionicInsulation + - Mindbroken + event: !type:PsionicHealOtherPowerActionEvent + healingAmount: + groups: # These all get divided by the number of damage types in the group. So they're all -2.5. + Genetic: -2.5 + Toxin: -5 + Airloss: -5 + Brute: -7.5 + Burn: -10 + rotReduction: 10 + useDelay: 1 + doRevive: true + powerName: Healing Word + popupText: healing-word-begin + playSound: true + minGlimmer: 2 + maxGlimmer: 4 + glimmerSoundThreshold: 100 + glimmerPopupThreshold: 200 + glimmerDoAfterVisibilityThreshold: 70 + +- type: entity + id: ActionRevivify + name: action-name-revivify + description: action-description-revivify + noSpawn: true + components: + - type: EntityTargetAction + icon: { sprite: Interface/Actions/psionics.rsi, state: revivify } + useDelay: 120 + checkCanAccess: false + range: 2 + itemIconStyle: BigAction + canTargetSelf: false + blacklist: + components: + - PsionicInsulation + - Mindbroken + event: !type:PsionicHealOtherPowerActionEvent + healingAmount: + # These all get divided by the number of damage types in the group. So they're all -15 + # Additionally, they're multiplied by the caster's Amplification, which, + # assuming this is the only power they have, the multiplier is between 2.9-3.9 + groups: + Genetic: -15 + Toxin: -30 + Airloss: -60 # Except airloss, which heals 30 per type + Brute: -45 + Burn: -60 + rotReduction: 60 + doRevive: true + powerName: Revivify + popupText: revivify-begin + playSound: true + minGlimmer: 10 # These also get multiplied by caster stats. So, + maxGlimmer: 15 # keeping in mind the ~3.5x multiplier, this spikes glimmer by as much as 60 points. + glimmerSoundThreshold: 50 + glimmerPopupThreshold: 100 + glimmerDoAfterVisibilityThreshold: 35 + +- type: entity + id: ActionShadeskip + name: action-name-shadeskip + description: action-description-shadeskip + noSpawn: true components: - type: InstantAction - icon: Interface/VerbIcons/psionic_invisibility_off.png - event: !type:RemovePsionicInvisibilityOffPowerActionEvent + icon: { sprite : Interface/Actions/psionics.rsi, state: shadeskip } + useDelay: 45 + checkCanInteract: false + event: !type:AnomalyPowerActionEvent + settings: + powerName: "Shadeskip" + minGlimmer: 6 + maxGlimmer: 8 + glimmerSoundThreshold: 50 + doSupercritical: false + entitySpawnEntries: + - settings: + spawnOnPulse: true + spawnOnSuperCritical: true + minAmount: 5 + maxAmount: 8 + maxRange: 1.5 + spawns: + - ShadowKudzuWeak + - settings: + spawnOnPulse: true + spawnOnSuperCritical: true + minAmount: 1 + maxAmount: 1 + maxRange: 0.5 + spawns: + - EffectFlashShadeskip +- type: entity + id: ActionTelekineticPulse + name: action-name-telekinetic-pulse + description: action-description-telekinetic-pulse + noSpawn: true + components: + - type: InstantAction + icon: { sprite: Interface/Actions/psionics.rsi, state: telekinetic_pulse } + useDelay: 45 + checkCanInteract: false + event: !type:AnomalyPowerActionEvent + settings: + powerName: "Telekinetic Pulse" + overchargeFeedback: "shadeskip-overcharge-feedback" # The text behind this is fine. + overchargeCooldown: 120 + overchargeRecoil: + groups: + Burn: -100 #This will be divided by the caster's Dampening. + minGlimmer: 6 + maxGlimmer: 8 + doSupercritical: false + entitySpawnEntries: + - settings: + spawnOnPulse: true + minAmount: 1 + maxAmount: 1 + maxRange: 0.5 + spawns: + - EffectFlashTelekineticPulse + gravity: + maxThrowRange: 3 + maxThrowStrength: 5 + spaceRange: 3 + +- type: entity + id: ActionShadowkinShadeskip + name: action-name-shadeskip + description: action-description-shadowkin-shadeskip + noSpawn: true + components: + - type: InstantAction + icon: { sprite: Interface/Actions/shadowkin_icons.rsi, state: shadeskip } + useDelay: 10 + checkCanInteract: false + event: !type:AnomalyPowerActionEvent + settings: + powerName: "Shadowkin-Shadeskip" + manaCost: 25 + checkInsulation: false + minGlimmer: 0 + maxGlimmer: 0 + doSupercritical: false + entitySpawnEntries: + - settings: + spawnOnPulse: true + minAmount: 5 + maxAmount: 10 + maxRange: 2.5 + spawns: + - ShadowkinShadow + - settings: + spawnOnPulse: true + minAmount: 1 + maxAmount: 1 + maxRange: 0.5 + spawns: + - EffectFlashShadowkinShadeskip + +- type: entity + id: ActionDarkSwap + name: action-name-darkswap + description: action-description-darkswap + noSpawn: true + components: + - type: InstantAction + icon: { sprite: Interface/Actions/shadowkin_icons.rsi, state: darkswap } + useDelay: 10 + checkCanInteract: false + event: !type:DarkSwapActionEvent + manaCost: 100 + checkInsulation: false + +- type: entity + id: ActionPyrokineticFlare + name: action-name-pyrokinetic-flare + description: action-description-pyrokinetic-flare + noSpawn: true + components: + - type: InstantAction + icon: { sprite: Interface/Actions/psionics.rsi, state: pyrokinetic_flare } + useDelay: 25 + checkCanInteract: false + event: !type:AnomalyPowerActionEvent + settings: + powerName: "Pyrokinetic Flare" + minGlimmer: 3 + maxGlimmer: 5 + doSupercritical: false + entitySpawnEntries: + - settings: + spawnOnPulse: true + spawnOnSuperCritical: true + minAmount: 1 + maxAmount: 3 + maxRange: 1 + spawns: + - EffectPyrokineticFlare + +- type: entity + id: ActionSummonImp + name: action-name-summon-imp + description: action-description-summon-imp + noSpawn: true + components: + - type: InstantAction + icon: { sprite: Interface/Actions/psionics.rsi, state: summon_imp } + useDelay: 120 + checkCanInteract: false + event: !type:SummonPsionicFamiliarActionEvent + familiarProto: MobPsionicFamiliarImp + powerName: "Summon Imp" + checkInsulation: true + doGlimmerEffects: true + followMaster: true + minGlimmer: 10 + maxGlimmer: 20 + +- type: entity + id: ActionSummonRemilia + name: action-name-summon-remilia + description: action-description-summon-remilia + noSpawn: true + components: + - type: InstantAction + icon: { sprite: Interface/Actions/psionics.rsi, state: summon_remilia } + useDelay: 120 + checkCanInteract: false + event: !type:SummonPsionicFamiliarActionEvent + familiarProto: MobBatRemilia + powerName: "Summon Remilia" + checkInsulation: true + doGlimmerEffects: true + followMaster: true + minGlimmer: 5 + maxGlimmer: 10 diff --git a/Resources/Prototypes/Actions/types.yml b/Resources/Prototypes/Actions/types.yml index cf0a99f3ba..a7144cdda5 100644 --- a/Resources/Prototypes/Actions/types.yml +++ b/Resources/Prototypes/Actions/types.yml @@ -92,7 +92,6 @@ state: gib event: !type:ActivateImplantEvent - - type: entity id: ActionActivateFreedomImplant name: Break Free @@ -130,6 +129,7 @@ noSpawn: true components: - type: InstantAction + checkCanInteract: false charges: 3 useDelay: 5 itemIconStyle: BigAction @@ -171,6 +171,21 @@ state: icon event: !type:UseDnaScramblerImplantEvent +- type: entity + id: ActionMorphGeras + name: Morph into Geras + description: Morphs you into a Geras - a miniature version of you which allows you to move fast, at the cost of your inventory. + noSpawn: true + components: + - type: InstantAction + itemIconStyle: BigAction + useDelay: 10 # prevent spam + priority: -20 + icon: + sprite: Mobs/Aliens/slimes.rsi + state: blue_adult_slime + event: !type:MorphIntoGeras + - type: entity id: ActionToggleSuitPiece name: Toggle Suit Piece @@ -289,6 +304,18 @@ icon: { sprite: Clothing/Head/Hats/pyjamasyndicatered.rsi, state: icon } event: !type:SleepActionEvent +- type: entity + id: ShadowkinActionSleep + name: action-name-shadowkin-rest + description: action-description-shadowkin-rest + noSpawn: true + components: + - type: InstantAction + checkCanInteract: false + checkConsciousness: false + icon: { sprite: Interface/Actions/shadowkin_icons.rsi, state: rest } + event: !type:SleepActionEvent + - type: entity id: ActionWake name: Wake up diff --git a/Resources/Prototypes/AlertLevels/alert_levels.yml b/Resources/Prototypes/AlertLevels/alert_levels.yml index 5a8cb2dd12..db15dc1388 100644 --- a/Resources/Prototypes/AlertLevels/alert_levels.yml +++ b/Resources/Prototypes/AlertLevels/alert_levels.yml @@ -5,18 +5,22 @@ green: announcement: alert-level-green-announcement color: Green + emergencyLightColor: LawnGreen sound: /Audio/Announcements/Alerts/code_green.ogg shuttleTime: 600 blue: announcement: alert-level-blue-announcement sound: /Audio/Announcements/Alerts/code_blue.ogg color: DodgerBlue + forceEnableEmergencyLights: true + emergencyLightColor: DodgerBlue shuttleTime: 600 violet: announcement: alert-level-violet-announcement sound: /Audio/Announcements/Alerts/code_violet.ogg color: Violet emergencyLightColor: Violet + forceEnableEmergencyLights: true shuttleTime: 600 white: announcement: alert-level-white-announcement @@ -29,11 +33,14 @@ sound: /Audio/Announcements/Alerts/code_yellow.ogg color: Yellow emergencyLightColor: Goldenrod + forceEnableEmergencyLights: true shuttleTime: 600 red: announcement: alert-level-red-announcement sound: /Audio/Announcements/Alerts/code_red.ogg color: Red + emergencyLightColor: Red + forceEnableEmergencyLights: true shuttleTime: 600 gamma: announcement: alert-level-gamma-announcement diff --git a/Resources/Prototypes/Alerts/alerts.yml b/Resources/Prototypes/Alerts/alerts.yml index 00b799670f..dffebf4b1d 100644 --- a/Resources/Prototypes/Alerts/alerts.yml +++ b/Resources/Prototypes/Alerts/alerts.yml @@ -5,7 +5,9 @@ id: BaseAlertOrder order: - category: Health + - category: Mood - category: Stamina + - alertType: ShadowkinPower - alertType: SuitPower - category: Internals - alertType: Fire @@ -25,10 +27,11 @@ - alertType: Magboots - alertType: Pacified - alertType: Offer + - alertType: Deflecting - type: entity id: AlertSpriteView - categories: [ hideSpawnMenu ] + categories: [ HideSpawnMenu ] components: - type: Sprite layers: @@ -189,6 +192,7 @@ - type: alert id: HumanHealth category: Health + onClick: !type:CheckHealth { } icons: - sprite: /Textures/Interface/Alerts/human_alive.rsi state: health0 @@ -496,3 +500,122 @@ state: critical name: Debug6 description: Debug + +# Moods +- type: alert + id: Insane + category: Mood + onClick: !type:ShowMoodEffects { } + icons: + - sprite: /Textures/Interface/Alerts/mood.rsi + state: mood_insane + name: alerts-mood-insane-name + description: alerts-mood-insane-desc + +- type: alert + id: Horrible + category: Mood + onClick: !type:ShowMoodEffects { } + icons: + - sprite: /Textures/Interface/Alerts/mood.rsi + state: mood1 + name: alerts-mood-horrible-name + description: alerts-mood-horrible-desc + +- type: alert + id: Terrible + category: Mood + onClick: !type:ShowMoodEffects { } + icons: + - sprite: /Textures/Interface/Alerts/mood.rsi + state: mood2 + name: alerts-mood-terrible-name + description: alerts-mood-terrible-desc + +- type: alert + id: Bad + category: Mood + onClick: !type:ShowMoodEffects { } + icons: + - sprite: /Textures/Interface/Alerts/mood.rsi + state: mood3 + name: alerts-mood-bad-name + description: alerts-mood-bad-desc + +- type: alert + id: Meh + category: Mood + onClick: !type:ShowMoodEffects { } + icons: + - sprite: /Textures/Interface/Alerts/mood.rsi + state: mood4 + name: alerts-mood-meh-name + description: alerts-mood-meh-desc + +- type: alert + id: Neutral + category: Mood + onClick: !type:ShowMoodEffects { } + icons: + - sprite: /Textures/Interface/Alerts/mood.rsi + state: mood5 + name: alerts-mood-neutral-name + description: alerts-mood-neutral-desc + +- type: alert + id: Good + category: Mood + onClick: !type:ShowMoodEffects { } + icons: + - sprite: /Textures/Interface/Alerts/mood.rsi + state: mood6 + name: alerts-mood-good-name + description: alerts-mood-good-desc + +- type: alert + id: Great + category: Mood + onClick: !type:ShowMoodEffects { } + icons: + - sprite: /Textures/Interface/Alerts/mood.rsi + state: mood7 + name: alerts-mood-great-name + description: alerts-mood-great-desc + +- type: alert + id: Exceptional + category: Mood + onClick: !type:ShowMoodEffects { } + icons: + - sprite: /Textures/Interface/Alerts/mood.rsi + state: mood8 + name: alerts-mood-exceptional-name + description: alerts-mood-exceptional-desc + +- type: alert + id: Perfect + category: Mood + onClick: !type:ShowMoodEffects { } + icons: + - sprite: /Textures/Interface/Alerts/mood.rsi + state: mood9 + name: alerts-mood-perfect-name + description: alerts-mood-perfect-desc + +- type: alert + id: MoodDead + category: Mood + onClick: !type:ShowMoodEffects { } + icons: + - sprite: /Textures/Interface/Alerts/mood.rsi + state: mood_happiness_bad + name: alerts-mood-dead-name + description: alerts-mood-dead-desc + +- type: alert + id: Deflecting + icons: + - sprite: /Textures/Interface/Alerts/deflecting.rsi + state: deflecting0 + name: alerts-deflecting-name + description: alerts-deflecting-desc diff --git a/Resources/Prototypes/Alerts/revenant.yml b/Resources/Prototypes/Alerts/revenant.yml index a56b898351..9db5228483 100644 --- a/Resources/Prototypes/Alerts/revenant.yml +++ b/Resources/Prototypes/Alerts/revenant.yml @@ -16,7 +16,7 @@ - type: entity id: AlertEssenceSpriteView - categories: [ hideSpawnMenu ] + categories: [ HideSpawnMenu ] components: - type: Sprite sprite: /Textures/Interface/Alerts/essence_counter.rsi diff --git a/Resources/Prototypes/Alerts/shadowkin.yml b/Resources/Prototypes/Alerts/shadowkin.yml new file mode 100644 index 0000000000..66d41351ba --- /dev/null +++ b/Resources/Prototypes/Alerts/shadowkin.yml @@ -0,0 +1,23 @@ +- type: alert + id: ShadowkinPower + icons: + - sprite: /Textures/Interface/Alerts/shadowkin_power.rsi + state: power0 + - sprite: /Textures/Interface/Alerts/shadowkin_power.rsi + state: power1 + - sprite: /Textures/Interface/Alerts/shadowkin_power.rsi + state: power2 + - sprite: /Textures/Interface/Alerts/shadowkin_power.rsi + state: power3 + - sprite: /Textures/Interface/Alerts/shadowkin_power.rsi + state: power4 + - sprite: /Textures/Interface/Alerts/shadowkin_power.rsi + state: power5 + - sprite: /Textures/Interface/Alerts/shadowkin_power.rsi + state: power6 + - sprite: /Textures/Interface/Alerts/shadowkin_power.rsi + state: power7 + name: alerts-shadowkin-power-name + description: alerts-shadowkin-power-desc + minSeverity: 0 + maxSeverity: 7 \ No newline at end of file diff --git a/Resources/Prototypes/Anomaly/behaviours.yml b/Resources/Prototypes/Anomaly/behaviours.yml index aa9ad2f90d..e39933c365 100644 --- a/Resources/Prototypes/Anomaly/behaviours.yml +++ b/Resources/Prototypes/Anomaly/behaviours.yml @@ -58,14 +58,14 @@ id: DelayedForce earnPointModifier: 1.15 description: anomaly-behavior-delayed-force - pulseFrequencyModifier: 0.5 + pulseFrequencyModifier: 2 pulsePowerModifier: 2 - type: anomalyBehavior id: Rapid earnPointModifier: 1.15 description: anomaly-behavior-rapid - pulseFrequencyModifier: 2 + pulseFrequencyModifier: 0.5 pulsePowerModifier: 0.5 - type: anomalyBehavior @@ -84,6 +84,7 @@ description: anomaly-behavior-reflect components: - type: Reflect + innate: true reflectProb: 0.5 reflects: - Energy diff --git a/Resources/Prototypes/Atmospherics/thresholds.yml b/Resources/Prototypes/Atmospherics/thresholds.yml index 22ca42869e..81f7bda4d2 100644 --- a/Resources/Prototypes/Atmospherics/thresholds.yml +++ b/Resources/Prototypes/Atmospherics/thresholds.yml @@ -19,7 +19,7 @@ upperWarnAround: !type:AlarmThresholdSetting threshold: 0.7 # 385 kPa, WarningHighPressure from Atmospherics.cs lowerWarnAround: !type:AlarmThresholdSetting - threshold: 1.05 # ~90 kPa + threshold: 4.5 # ~90 kPa # a reminder that all of these are percentages (where 1 is 100%), # so 0.01 is 1%, diff --git a/Resources/Prototypes/Body/Organs/Friendstomach.yml b/Resources/Prototypes/Body/Organs/Friendstomach.yml new file mode 100644 index 0000000000..a20bbbe75b --- /dev/null +++ b/Resources/Prototypes/Body/Organs/Friendstomach.yml @@ -0,0 +1,15 @@ +- type: entity + id: OrganFriendStomach + parent: OrganAnimalStomach + noSpawn: true + components: + - type: Stomach + - type: SolutionContainerManager + solutions: + stomach: + maxVol: 100 + food: + maxVol: 50 + reagents: + - ReagentId: UncookedAnimalProteins + Quantity: 5 diff --git a/Resources/Prototypes/Body/Organs/diona.yml b/Resources/Prototypes/Body/Organs/diona.yml index 69fc630b9e..a2133f7f90 100644 --- a/Resources/Prototypes/Body/Organs/diona.yml +++ b/Resources/Prototypes/Body/Organs/diona.yml @@ -27,11 +27,12 @@ - type: entity id: OrganDionaBrain - parent: [BaseDionaOrgan, OrganHumanBrain] + parent: OrganHumanBrain name: brain description: "The source of incredible, unending intelligence. Honk." components: - type: Sprite + sprite: Mobs/Species/Diona/organs.rsi state: brain - type: SolutionContainerManager solutions: @@ -102,7 +103,7 @@ layers: - state: lung-l - state: lung-r - - type: Lung + - type: Lung - type: Metabolizer removeEmpty: true solutionOnBody: false @@ -131,7 +132,7 @@ description: "The source of incredible, unending intelligence. Honk." components: - type: Brain - - type: Nymph # This will make the organs turn into a nymph when they're removed. + - type: Nymph # This will make the organs turn into a nymph when they're removed. entityPrototype: OrganDionaNymphBrain transferMind: true @@ -170,11 +171,11 @@ - type: entity id: OrganDionaNymphStomach - parent: MobDionaNymphAccent + parent: MobDionaNymphAccent noSpawn: true name: diona nymph suffix: Stomach - description: Contains the stomach of a formerly fully-formed Diona. It doesn't taste any better for it. + description: Contains the stomach of a formerly fully-formed Diona. It doesn't taste any better for it. components: - type: IsDeadIC - type: Body @@ -186,7 +187,7 @@ noSpawn: true name: diona nymph suffix: Lungs - description: Contains the lungs of a formerly fully-formed Diona. Breathtaking. + description: Contains the lungs of a formerly fully-formed Diona. Breathtaking. components: - type: IsDeadIC - type: Body diff --git a/Resources/Prototypes/Body/Organs/felinid.yml b/Resources/Prototypes/Body/Organs/felinid.yml new file mode 100644 index 0000000000..0c6c7ea91d --- /dev/null +++ b/Resources/Prototypes/Body/Organs/felinid.yml @@ -0,0 +1,24 @@ +- type: entity + id: OrganFelinidEars + parent: OrganHumanEars + name: cat ears + description: "Holding these might potentially be contagious." + components: + - type: Sprite + sprite: Clothing/Head/Hats/catears.rsi + state: icon + - type: MarkingContainer + marking: FelinidEarsBasic + +- type: entity + id: OrganFelinidTail + parent: BaseHumanOrgan + name: cat tail + description: "Should you really have this?" + components: + - type: Sprite + sprite: Nyanotrasen/Mobs/Customization/felinid_tails.rsi + state: basic_tail_tip + - type: MarkingContainer + marking: FelinidTailBasic + - type: Tail diff --git a/Resources/Prototypes/Body/Organs/human.yml b/Resources/Prototypes/Body/Organs/human.yml index 69081020ce..b088455e6b 100644 --- a/Resources/Prototypes/Body/Organs/human.yml +++ b/Resources/Prototypes/Body/Organs/human.yml @@ -6,6 +6,13 @@ - type: Sprite sprite: Mobs/Species/Human/organs.rsi - type: Organ + +- type: entity + id: BaseHumanOrgan + parent: BaseHumanOrganUnGibbable + abstract: true + components: + - type: Gibbable - type: Food - type: Extractable grindableSolutionName: organ @@ -27,13 +34,6 @@ tags: - Meat -- type: entity - id: BaseHumanOrgan - parent: BaseHumanOrganUnGibbable - abstract: true - components: - - type: Gibbable - - type: entity id: OrganHumanBrain parent: BaseHumanOrganUnGibbable @@ -43,6 +43,7 @@ - type: Sprite state: brain - type: Organ + slotId: brain - type: Input context: "ghost" - type: Brain @@ -67,13 +68,16 @@ - type: FlavorProfile flavors: - people - + - type: entity id: OrganHumanEyes parent: BaseHumanOrgan name: eyes description: "I see you!" components: + - type: Organ + slotId: eyes + - type: Eyes - type: Sprite layers: - state: eyeball-l @@ -107,6 +111,7 @@ components: - type: Sprite state: ears + - type: Ears - type: entity id: OrganHumanLungs @@ -119,6 +124,8 @@ - state: lung-l - state: lung-r - type: Lung + - type: Organ + slotId: lungs - type: Metabolizer removeEmpty: true solutionOnBody: false @@ -148,6 +155,9 @@ name: heart description: "I feel bad for the heartless bastard who lost this." components: + - type: Heart + - type: Organ + slotId: heart - type: Sprite state: heart-on # The heart 'metabolizes' medicines and poisons that aren't filtered out by other organs. @@ -179,6 +189,8 @@ - ReagentId: UncookedAnimalProteins Quantity: 5 - type: Stomach + - type: Organ + slotId: stomach # The stomach metabolizes stuff like foods and drinks. # TODO: Have it work off of the ent's solution container, and move this # to intestines instead. @@ -196,6 +208,9 @@ name: liver description: "Pairing suggestion: chianti and fava beans." components: + - type: Liver + - type: Organ + slotId: liver - type: Sprite state: liver - type: Metabolizer # The liver metabolizes certain chemicals only, like alcohol. diff --git a/Resources/Prototypes/Body/Organs/ipc.yml b/Resources/Prototypes/Body/Organs/ipc.yml new file mode 100644 index 0000000000..bc8d6c827c --- /dev/null +++ b/Resources/Prototypes/Body/Organs/ipc.yml @@ -0,0 +1,72 @@ +- type: entity + id: BaseIPCOrgan + parent: BaseItem + abstract: true + components: + - type: Sprite + netsync: false + sprite: Mobs/Species/IPC/organs.rsi + - type: Organ + # - type: Food + # - type: Extractable + # grindableSolutionName: organ + - type: SolutionContainerManager + solutions: + organ: + reagents: + - ReagentId: Oil + Quantity: 10 + +- type: entity + id: OrganIPCEyes + parent: BaseIPCOrgan + name: robotic eyes + description: "01001001 00100000 01110011 01100101 01100101 00100000 01111001 01101111 01110101 00100001" + components: + - type: Sprite + layers: + - state: eyeball-l + - state: eyeball-r + - type: Organ + +- type: entity + id: OrganIPCTongue + parent: BaseIPCOrgan + name: vocal modulator + description: "A vocal modulator, used to produce speech." + components: + - type: Sprite + state: tongue + - type: Organ + +- type: entity + id: OrganIPCEars + parent: BaseIPCOrgan + name: "sonic receptors" + description: + components: + - type: Sprite + state: ears + - type: Organ + +- type: entity + id: OrganIPCPump + parent: BaseIPCOrgan + name: micro pump + description: "A micro pump, used to circulate coolant." + components: + - type: Sprite + state: heart-on + - type: Organ + # The heart 'metabolizes' medicines and poisons that aren't filtered out by other organs. + # This is done because these chemicals need to have some effect even if they aren't being filtered out of your body. + # You're technically 'immune to poison' without a heart, but.. uhh, you'll have bigger problems on your hands. + + # This is fine? + # - type: Metabolizer + # maxReagents: 2 + # metabolizerTypes: [Human] + # groups: + # - id: Medicine + # - id: Poison + # - id: Narcotic diff --git a/Resources/Prototypes/Body/Organs/shadowkin.yml b/Resources/Prototypes/Body/Organs/shadowkin.yml new file mode 100644 index 0000000000..695ddec1ab --- /dev/null +++ b/Resources/Prototypes/Body/Organs/shadowkin.yml @@ -0,0 +1,113 @@ +- type: entity + id: OrganShadowkinBrain + parent: OrganHumanBrain + components: + - type: Sprite + sprite: Mobs/Species/Shadowkin/organs.rsi + state: brain + +- type: entity + id: OrganShadowkinEyes + parent: OrganHumanEyes + description: I see beyond anything you ever will! + components: + - type: Sprite + sprite: Mobs/Species/Shadowkin/organs.rsi + layers: + - state: eyes + +- type: entity + id: OrganShadowkinEars + parent: OrganHumanEars + description: Hey, listen! + components: + - type: Sprite + sprite: Mobs/Species/Shadowkin/organs.rsi + state: ears + +- type: entity + id: OrganShadowkinTongue + parent: OrganHumanTongue + components: + - type: Sprite + sprite: Mobs/Species/Shadowkin/organs.rsi + state: tongue + + +- type: entity + id: OrganShadowkinAppendix + parent: OrganHumanAppendix + components: + - type: Sprite + sprite: Mobs/Species/Shadowkin/organs.rsi + layers: + - state: appendix + + +- type: entity + id: OrganShadowkinHeart + parent: OrganHumanHeart + components: + - type: Sprite + sprite: Mobs/Species/Shadowkin/organs.rsi + state: heart + - type: Metabolizer + maxReagents: 2 + metabolizerTypes: [Shadowkin] + groups: + - id: Medicine + - id: Poison + - id: Narcotic + +- type: entity + id: OrganShadowkinStomach + parent: OrganHumanStomach + description: '"Yummy!", says the stomach, although you are unable to hear it.' + components: + - type: Sprite + sprite: Mobs/Species/Shadowkin/organs.rsi + state: stomach + - type: SolutionContainerManager + solutions: + stomach: + maxVol: 40 + food: + maxVol: 5 + reagents: + - ReagentId: UncookedAnimalProteins + Quantity: 5 + - type: Metabolizer + maxReagents: 3 + metabolizerTypes: [Shadowkin] + groups: + - id: Food + - id: Drink + +- type: entity + id: OrganShadowkinLiver + parent: OrganHumanLiver + description: "Live 'er? I hardly know 'er!" + components: + - type: Sprite + sprite: Mobs/Species/Shadowkin/organs.rsi + state: liver + - type: Metabolizer + maxReagents: 1 + metabolizerTypes: [Shadowkin] + groups: + - id: Alcohol + rateModifier: 0.1 + +- type: entity + id: OrganShadowkinKidneys + parent: OrganHumanKidneys + description: Give the kid their knees back, please, this is the third time this week. + components: + - type: Sprite + sprite: Mobs/Species/Shadowkin/organs.rsi + layers: + - state: kidneys + - type: Metabolizer + maxReagents: 5 + metabolizerTypes: [Shadowkin] + removeEmpty: true \ No newline at end of file diff --git a/Resources/Prototypes/Body/Organs/slime.yml b/Resources/Prototypes/Body/Organs/slime.yml index 3da76c5d4a..5b908e75f4 100644 --- a/Resources/Prototypes/Body/Organs/slime.yml +++ b/Resources/Prototypes/Body/Organs/slime.yml @@ -1,6 +1,6 @@ - type: entity id: SentientSlimeCore - parent: [BaseItem, OrganHumanBrain] + parent: OrganHumanBrain name: sentient slime core description: "The source of incredible, unending gooeyness." components: @@ -34,7 +34,7 @@ - ReagentId: Slime Quantity: 10 - + - type: entity id: OrganSlimeLungs parent: BaseHumanOrgan diff --git a/Resources/Prototypes/Body/Parts/animal.yml b/Resources/Prototypes/Body/Parts/animal.yml index 4db026b40f..76eaf79812 100644 --- a/Resources/Prototypes/Body/Parts/animal.yml +++ b/Resources/Prototypes/Body/Parts/animal.yml @@ -12,7 +12,7 @@ - type: Sprite sprite: Mobs/Species/Reptilian/parts.rsi - type: Damageable - damageContainer: Biological + damageContainer: OrganicPart # Shitmed - type: BodyPart - type: ContainerContainer containers: diff --git a/Resources/Prototypes/Body/Parts/base.yml b/Resources/Prototypes/Body/Parts/base.yml index 836d0f140a..7b90b09794 100644 --- a/Resources/Prototypes/Body/Parts/base.yml +++ b/Resources/Prototypes/Body/Parts/base.yml @@ -1,5 +1,4 @@ -# TODO: Add descriptions (many) -# TODO BODY: Part damage + # Shitmed Change Start - type: entity id: BasePart parent: BaseItem @@ -7,8 +6,13 @@ abstract: true components: - type: Damageable - damageContainer: Biological + damageContainer: OrganicPart - type: BodyPart + - type: SurgeryTool + startSound: + path: /Audio/Medical/Surgery/organ1.ogg + endSound: + path: /Audio/Medical/Surgery/organ2.ogg - type: Gibbable - type: ContainerContainer containers: @@ -19,6 +23,35 @@ - type: Tag tags: - Trash + - type: Destructible + thresholds: + - trigger: + !type:DamageTypeTrigger + damageType: Blunt + damage: 100 + behaviors: + - !type:GibPartBehavior { } + - trigger: + !type:DamageTypeTrigger + damageType: Slash + damage: 150 + behaviors: + - !type:GibPartBehavior { } + - trigger: + !type:DamageTypeTrigger + damageType: Heat + damage: 200 + behaviors: + - !type:SpawnEntitiesBehavior + spawnInContainer: true + spawn: + Ash: + min: 1 + max: 1 + - !type:BurnBodyBehavior { } + - !type:PlaySoundBehavior + sound: + collection: MeatLaserImpact - type: entity id: BaseTorso @@ -28,6 +61,11 @@ components: - type: BodyPart partType: Torso + toolName: "a torso" + containerName: "torso_slot" + - type: ContainerContainer + containers: + torso_slot: !type:ContainerSlot {} - type: entity id: BaseHead @@ -37,6 +75,7 @@ components: - type: BodyPart partType: Head + toolName: "a head" vital: true - type: Input context: "ghost" @@ -53,6 +92,7 @@ - type: BodyPart partType: Arm symmetry: Left + toolName: "a left arm" - type: entity id: BaseRightArm @@ -63,6 +103,7 @@ - type: BodyPart partType: Arm symmetry: Right + toolName: "a right arm" - type: entity id: BaseLeftHand @@ -73,6 +114,7 @@ - type: BodyPart partType: Hand symmetry: Left + toolName: "a left hand" - type: entity id: BaseRightHand @@ -83,6 +125,7 @@ - type: BodyPart partType: Hand symmetry: Right + toolName: "a right hand" - type: entity id: BaseLeftLeg @@ -93,6 +136,7 @@ - type: BodyPart partType: Leg symmetry: Left + toolName: "a left leg" - type: MovementBodyPart - type: entity @@ -104,6 +148,7 @@ - type: BodyPart partType: Leg symmetry: Right + toolName: "a right leg" - type: MovementBodyPart - type: entity @@ -115,6 +160,7 @@ - type: BodyPart partType: Foot symmetry: Left + toolName: "a left foot" - type: entity id: BaseRightFoot @@ -125,3 +171,6 @@ - type: BodyPart partType: Foot symmetry: Right + toolName: "a right foot" + + # Shitmed Change End diff --git a/Resources/Prototypes/Body/Parts/harpy.yml b/Resources/Prototypes/Body/Parts/harpy.yml index 9e51334406..177db98c0f 100644 --- a/Resources/Prototypes/Body/Parts/harpy.yml +++ b/Resources/Prototypes/Body/Parts/harpy.yml @@ -1,3 +1,4 @@ +# Shitmed Change Start - WHY DIDNT YOU INHERIT FROM BASE PART SOLIDUS??? - type: entity id: PartHarpy parent: BaseItem @@ -5,7 +6,9 @@ abstract: true components: - type: Damageable - damageContainer: Biological + damageContainer: OrganicPart + - type: Gibbable + - type: SurgeryTool - type: BodyPart - type: ContainerContainer containers: @@ -16,6 +19,36 @@ - type: Tag tags: - Trash + - type: Destructible + thresholds: + - trigger: + !type:DamageTypeTrigger + damageType: Blunt + damage: 50 + behaviors: + - !type:GibPartBehavior { } + - trigger: + !type:DamageTypeTrigger + damageType: Slash + damage: 100 + behaviors: + - !type:GibPartBehavior { } + - trigger: + !type:DamageTypeTrigger + damageType: Heat + damage: 200 + behaviors: + - !type:SpawnEntitiesBehavior + spawnInContainer: true + spawn: + Ash: + min: 1 + max: 1 + - !type:BurnBodyBehavior { } + - !type:PlaySoundBehavior + sound: + collection: MeatLaserImpact + - type: entity id: TorsoHarpy @@ -31,6 +64,11 @@ state: "torso_m" - type: BodyPart partType: Torso + toolName: "a torso" + containerName: "torso_slot" + - type: ContainerContainer + containers: + torso_slot: !type:ContainerSlot {} - type: entity id: HeadHarpy @@ -46,6 +84,7 @@ state: "head_m" - type: BodyPart partType: Head + toolName: "a head" vital: true - type: Input context: "ghost" @@ -70,6 +109,7 @@ - type: BodyPart partType: Arm symmetry: Left + toolName: "a left arm" - type: entity id: RightArmHarpy @@ -86,6 +126,7 @@ - type: BodyPart partType: Arm symmetry: Right + toolName: "a right arm" - type: entity id: LeftHandHarpy @@ -102,6 +143,7 @@ - type: BodyPart partType: Hand symmetry: Left + toolName: "a left hand" - type: entity id: RightHandHarpy @@ -118,6 +160,7 @@ - type: BodyPart partType: Hand symmetry: Right + toolName: "a right hand" - type: entity id: LeftLegHarpy @@ -134,6 +177,7 @@ - type: BodyPart partType: Leg symmetry: Left + toolName: "a left leg" - type: MovementBodyPart - type: entity @@ -151,6 +195,7 @@ - type: BodyPart partType: Leg symmetry: Right + toolName: "a right leg" - type: MovementBodyPart - type: entity @@ -168,6 +213,7 @@ - type: BodyPart partType: Foot symmetry: Left + toolName: "a left foot" - type: entity id: RightFootHarpy @@ -184,3 +230,6 @@ - type: BodyPart partType: Foot symmetry: Right + toolName: "a right foot" + +# Shitmed Change End diff --git a/Resources/Prototypes/Body/Parts/ipc.yml b/Resources/Prototypes/Body/Parts/ipc.yml new file mode 100644 index 0000000000..aef021b8aa --- /dev/null +++ b/Resources/Prototypes/Body/Parts/ipc.yml @@ -0,0 +1,186 @@ +- type: entity + id: PartIPC + parent: BaseItem + name: "ipc body part" + abstract: true + components: + - type: Damageable + damageContainer: Inorganic + - type: BodyPart + - type: ContainerContainer + containers: + bodypart: !type:Container + ents: [] + - type: StaticPrice + price: 100 + +- type: entity + id: TorsoIPC + name: "ipc torso" + parent: PartIPC + components: + - type: Sprite + netsync: false + sprite: Mobs/Species/IPC/parts.rsi + state: "torso_m" + - type: Icon + sprite: Mobs/Species/IPC/parts.rsi + state: "torso_m" + - type: BodyPart + partType: Torso + +- type: entity + id: HeadIPC + name: "ipc head" + parent: PartIPC + components: + - type: Sprite + netsync: false + sprite: Mobs/Species/IPC/parts.rsi + state: "head_m" + - type: Icon + sprite: Mobs/Species/IPC/parts.rsi + state: "head_m" + - type: BodyPart + partType: Head + vital: true + - type: Input + context: "ghost" + - type: MovementSpeedModifier + baseWalkSpeed: 0 + baseSprintSpeed: 0 + - type: InputMover + - type: GhostOnMove + - type: Tag + tags: + - Head + +- type: entity + id: LeftArmIPC + name: "left ipc arm" + parent: PartIPC + components: + - type: Sprite + netsync: false + sprite: Mobs/Species/IPC/parts.rsi + state: "l_arm" + - type: Icon + sprite: Mobs/Species/IPC/parts.rsi + state: "l_arm" + - type: BodyPart + partType: Arm + symmetry: Left + +- type: entity + id: RightArmIPC + name: "right ipc arm" + parent: PartIPC + components: + - type: Sprite + netsync: false + sprite: Mobs/Species/IPC/parts.rsi + state: "r_arm" + - type: Icon + sprite: Mobs/Species/IPC/parts.rsi + state: "r_arm" + - type: BodyPart + partType: Arm + symmetry: Right + +- type: entity + id: LeftHandIPC + name: "left ipc hand" + parent: PartIPC + components: + - type: Sprite + netsync: false + sprite: Mobs/Species/IPC/parts.rsi + state: "l_hand" + - type: Icon + sprite: Mobs/Species/IPC/parts.rsi + state: "l_hand" + - type: BodyPart + partType: Hand + symmetry: Left + +- type: entity + id: RightHandIPC + name: "right ipc hand" + parent: PartIPC + components: + - type: Sprite + netsync: false + sprite: Mobs/Species/IPC/parts.rsi + state: "r_hand" + - type: Icon + sprite: Mobs/Species/IPC/parts.rsi + state: "r_hand" + - type: BodyPart + partType: Hand + symmetry: Right + +- type: entity + id: LeftLegIPC + name: "left ipc leg" + parent: PartIPC + components: + - type: Sprite + netsync: false + sprite: Mobs/Species/IPC/parts.rsi + state: "l_leg" + - type: Icon + sprite: Mobs/Species/IPC/parts.rsi + state: "l_leg" + - type: BodyPart + partType: Leg + symmetry: Left + - type: MovementBodyPart + +- type: entity + id: RightLegIPC + name: "right ipc leg" + parent: PartIPC + components: + - type: Sprite + netsync: false + sprite: Mobs/Species/IPC/parts.rsi + state: "r_leg" + - type: Icon + sprite: Mobs/Species/IPC/parts.rsi + state: "r_leg" + - type: BodyPart + partType: Leg + symmetry: Right + - type: MovementBodyPart + +- type: entity + id: LeftFootIPC + name: "left ipc foot" + parent: PartIPC + components: + - type: Sprite + netsync: false + sprite: Mobs/Species/IPC/parts.rsi + state: "l_foot" + - type: Icon + sprite: Mobs/Species/IPC/parts.rsi + state: "l_foot" + - type: BodyPart + partType: Foot + symmetry: Left + +- type: entity + id: RightFootIPC + name: "right ipc foot" + parent: PartIPC + components: + - type: Sprite + netsync: false + sprite: Mobs/Species/IPC/parts.rsi + state: "r_foot" + - type: Icon + sprite: Mobs/Species/IPC/parts.rsi + state: "r_foot" + - type: BodyPart + partType: Foot + symmetry: Right diff --git a/Resources/Prototypes/Body/Parts/shadowkin.yml b/Resources/Prototypes/Body/Parts/shadowkin.yml new file mode 100644 index 0000000000..f8ca620a40 --- /dev/null +++ b/Resources/Prototypes/Body/Parts/shadowkin.yml @@ -0,0 +1,155 @@ +- type: entity + id: PartShadowkin + parent: BaseItem + name: "Shadowkin body part" + abstract: true + components: + - type: Sprite + netsync: false + sprite: Mobs/Species/Shadowkin/parts.rsi + - type: Icon + sprite: Mobs/Species/Shadowkin/parts.rsi + - type: Damageable + damageContainer: OrganicPart # Shitmed + - type: BodyPart + - type: ContainerContainer + containers: + bodypart: !type:Container + ents: [] + +- type: entity + id: TorsoShadowkin + name: "Shadowkin torso" + parent: PartShadowkin + components: + - type: Sprite + state: "torso_m" + - type: Icon + state: "torso_m" + - type: BodyPart + partType: Torso + +- type: entity + id: HeadShadowkin + name: "Shadowkin head" + parent: PartShadowkin + components: + - type: Sprite + state: "head_m" + - type: Icon + state: "head_m" + - type: BodyPart + partType: Head + - type: Input + context: "ghost" + - type: MovementSpeedModifier + baseWalkSpeed: 0 + baseSprintSpeed: 0 + - type: InputMover + - type: GhostOnMove + +- type: entity + id: LeftArmShadowkin + name: "left Shadowkin arm" + parent: PartShadowkin + components: + - type: Sprite + state: "l_arm" + - type: Icon + state: "l_arm" + - type: BodyPart + partType: Arm + symmetry: Left + +- type: entity + id: RightArmShadowkin + name: "right Shadowkin arm" + parent: PartShadowkin + components: + - type: Sprite + state: "r_arm" + - type: Icon + state: "r_arm" + - type: BodyPart + partType: Arm + symmetry: Right + +- type: entity + id: LeftHandShadowkin + name: "left Shadowkin hand" + parent: PartShadowkin + components: + - type: Sprite + state: "l_hand" + - type: Icon + state: "l_hand" + - type: BodyPart + partType: Hand + symmetry: Left + +- type: entity + id: RightHandShadowkin + name: "right Shadowkin hand" + parent: PartShadowkin + components: + - type: Sprite + state: "r_hand" + - type: Icon + state: "r_hand" + - type: BodyPart + partType: Hand + symmetry: Right + +- type: entity + id: LeftLegShadowkin + name: "left Shadowkin leg" + parent: PartShadowkin + components: + - type: Sprite + state: "l_leg" + - type: Icon + state: "l_leg" + - type: BodyPart + partType: Leg + symmetry: Left + - type: MovementBodyPart + +- type: entity + id: RightLegShadowkin + name: "right Shadowkin leg" + parent: PartShadowkin + components: + - type: Sprite + state: "r_leg" + - type: Icon + state: "r_leg" + - type: BodyPart + partType: Leg + symmetry: Right + - type: MovementBodyPart + +- type: entity + id: LeftFootShadowkin + name: "left Shadowkin foot" + parent: PartShadowkin + components: + - type: Sprite + state: "l_foot" + - type: Icon + state: "l_foot" + - type: BodyPart + partType: Foot + symmetry: Left + +- type: entity + id: RightFootShadowkin + name: "right Shadowkin foot" + parent: PartShadowkin + components: + - type: Sprite + state: "r_foot" + - type: Icon + state: "r_foot" + - type: BodyPart + partType: Foot + symmetry: Right diff --git a/Resources/Prototypes/Body/Parts/skeleton.yml b/Resources/Prototypes/Body/Parts/skeleton.yml index ffba0c7c44..12ce54e614 100644 --- a/Resources/Prototypes/Body/Parts/skeleton.yml +++ b/Resources/Prototypes/Body/Parts/skeleton.yml @@ -6,7 +6,7 @@ abstract: true components: - type: Damageable - damageContainer: Biological + damageContainer: OrganicPart # Shitmed - type: BodyPart - type: ContainerContainer containers: @@ -18,6 +18,35 @@ - type: Tag tags: - Trash + - type: Destructible + thresholds: + - trigger: + !type:DamageTypeTrigger + damageType: Blunt + damage: 50 + behaviors: + - !type:GibPartBehavior { } + - trigger: + !type:DamageTypeTrigger + damageType: Slash + damage: 100 + behaviors: + - !type:GibPartBehavior { } + - trigger: + !type:DamageTypeTrigger + damageType: Heat + damage: 200 + behaviors: + - !type:SpawnEntitiesBehavior + spawnInContainer: true + spawn: + Ash: + min: 1 + max: 1 + - !type:BurnBodyBehavior { } + - !type:PlaySoundBehavior + sound: + collection: MeatLaserImpact - type: entity id: TorsoSkeleton diff --git a/Resources/Prototypes/Body/Parts/terminator.yml b/Resources/Prototypes/Body/Parts/terminator.yml deleted file mode 100644 index 58530da959..0000000000 --- a/Resources/Prototypes/Body/Parts/terminator.yml +++ /dev/null @@ -1,158 +0,0 @@ -- type: entity - abstract: true - parent: BaseItem - id: PartTerminator - name: nt-800 body part - components: - - type: Sprite - sprite: Mobs/Species/Terminator/parts.rsi - - type: Icon - sprite: Mobs/Species/Terminator/parts.rsi - - type: Damageable - damageContainer: Inorganic - damageModifierSet: Cybernetic - - type: BodyPart - - type: ContainerContainer - containers: - bodypart: !type:Container - ents: [] - - type: Gibbable - - type: StaticPrice - price: 200 - -- type: entity - parent: PartTerminator - id: TorsoTerminator - name: nt-800 torso - components: - - type: Sprite - state: torso_m - - type: Icon - state: torso_m - - type: BodyPart - partType: Torso - -- type: entity - parent: PartTerminator - id: HeadTerminator - name: nt-800 skull - description: Its red eyes have powered down... for now. - components: - - type: Sprite - state: skull_icon - - type: Icon - state: skull_icon - - type: BodyPart - partType: Head - # killing a terminators worth big bucks - - type: StaticPrice - price: 2000 - - type: Tag - tags: - - Head - -- type: entity - parent: PartTerminator - id: LeftArmTerminator - name: left nt-800 arm - components: - - type: Sprite - state: l_arm - - type: Icon - state: l_arm - - type: BodyPart - partType: Arm - symmetry: Left - -- type: entity - parent: PartTerminator - id: RightArmTerminator - name: right nt-800 arm - components: - - type: Sprite - state: r_arm - - type: Icon - state: r_arm - - type: BodyPart - partType: Arm - symmetry: Right - -- type: entity - parent: PartTerminator - id: LeftHandTerminator - name: left nt-800 hand - components: - - type: Sprite - state: l_hand - - type: Icon - state: l_hand - - type: BodyPart - partType: Hand - symmetry: Left - -- type: entity - parent: PartTerminator - id: RightHandTerminator - name: right nt-800 hand - components: - - type: Sprite - state: r_hand - - type: Icon - state: r_hand - - type: BodyPart - partType: Hand - symmetry: Right - -- type: entity - parent: PartTerminator - id: LeftLegTerminator - name: left nt-800 leg - components: - - type: Sprite - state: l_leg - - type: Icon - state: l_leg - - type: BodyPart - partType: Leg - symmetry: Left - - type: MovementBodyPart - -- type: entity - parent: PartTerminator - id: RightLegTerminator - name: right nt-800 leg - components: - - type: Sprite - state: r_leg - - type: Icon - state: r_leg - - type: BodyPart - partType: Leg - symmetry: Right - - type: MovementBodyPart - -- type: entity - parent: PartTerminator - id: LeftFootTerminator - name: left nt-800 foot - components: - - type: Sprite - state: l_foot - - type: Icon - state: l_foot - - type: BodyPart - partType: Foot - symmetry: Left - -- type: entity - parent: PartTerminator - id: RightFootTerminator - name: right nt-800 foot - components: - - type: Sprite - state: r_foot - - type: Icon - state: r_foot - - type: BodyPart - partType: Foot - symmetry: Right diff --git a/Resources/Prototypes/Body/Parts/vox.yml b/Resources/Prototypes/Body/Parts/vox.yml index b163ed0864..505eba800f 100644 --- a/Resources/Prototypes/Body/Parts/vox.yml +++ b/Resources/Prototypes/Body/Parts/vox.yml @@ -7,7 +7,7 @@ abstract: true components: - type: Damageable - damageContainer: Biological + damageContainer: OrganicPart # Shitmed - type: BodyPart - type: ContainerContainer containers: @@ -33,10 +33,10 @@ components: - type: Sprite sprite: Mobs/Species/Vox/parts.rsi - state: "torso_m" + state: "torso" - type: Icon sprite: Mobs/Species/Vox/parts.rsi - state: "torso_m" + state: "torso" - type: BodyPart partType: Torso - type: Extractable @@ -54,10 +54,10 @@ components: - type: Sprite sprite: Mobs/Species/Vox/parts.rsi - state: "head_m" + state: "head" - type: Icon sprite: Mobs/Species/Vox/parts.rsi - state: "head_m" + state: "head" - type: BodyPart partType: Head vital: true diff --git a/Resources/Prototypes/Body/Prototypes/Friendshaped.yml b/Resources/Prototypes/Body/Prototypes/Friendshaped.yml new file mode 100644 index 0000000000..0ab85a0343 --- /dev/null +++ b/Resources/Prototypes/Body/Prototypes/Friendshaped.yml @@ -0,0 +1,24 @@ +- type: body + id: Friendshaped + name: "Friend-Shaped" + root: torso + slots: + torso: + part: TorsoAnimal + connections: + - hands + - legs + organs: + lungs: OrganAnimalLungs + stomach: OrganFriendStomach + liver: OrganAnimalLiver + heart: OrganAnimalHeart + kidneys: OrganAnimalKidneys + hands: + part: HandsAnimal + legs: + part: LegsAnimal + connections: + - feet + feet: + part: FeetAnimal diff --git a/Resources/Prototypes/Body/Prototypes/a_ghost.yml b/Resources/Prototypes/Body/Prototypes/a_ghost.yml index 09784c3ef5..f04ed70197 100644 --- a/Resources/Prototypes/Body/Prototypes/a_ghost.yml +++ b/Resources/Prototypes/Body/Prototypes/a_ghost.yml @@ -6,17 +6,17 @@ torso: part: TorsoHuman connections: - - right_arm - - left_arm - right_arm: + - right arm + - left arm + right arm: part: RightArmHuman connections: - - right_hand - left_arm: + - right hand + left arm: part: LeftArmHuman connections: - - left_hand - right_hand: + - left hand + right hand: part: RightHandHuman - left_hand: + left hand: part: LeftHandHuman diff --git a/Resources/Prototypes/Body/Prototypes/human.yml b/Resources/Prototypes/Body/Prototypes/human.yml index 94c77a27d7..7a0f3bb5a7 100644 --- a/Resources/Prototypes/Body/Prototypes/human.yml +++ b/Resources/Prototypes/Body/Prototypes/human.yml @@ -13,37 +13,37 @@ torso: part: TorsoHuman connections: - - right_arm - - left_arm - - right_leg - - left_leg + - right arm + - left arm + - right leg + - left leg organs: heart: OrganHumanHeart lungs: OrganHumanLungs stomach: OrganHumanStomach liver: OrganHumanLiver kidneys: OrganHumanKidneys - right_arm: + right arm: part: RightArmHuman connections: - - right_hand - left_arm: + - right hand + left arm: part: LeftArmHuman connections: - - left_hand - right_hand: + - left hand + right hand: part: RightHandHuman - left_hand: + left hand: part: LeftHandHuman - right_leg: + right leg: part: RightLegHuman connections: - - right_foot - left_leg: + - right foot + left leg: part: LeftLegHuman connections: - - left_foot - right_foot: + - left foot + right foot: part: RightFootHuman - left_foot: + left foot: part: LeftFootHuman diff --git a/Resources/Prototypes/Body/Prototypes/ipc.yml b/Resources/Prototypes/Body/Prototypes/ipc.yml new file mode 100644 index 0000000000..6078ca7b40 --- /dev/null +++ b/Resources/Prototypes/Body/Prototypes/ipc.yml @@ -0,0 +1,45 @@ +- type: body + id: IPC + name: "ipc" + root: torso + slots: + head: + part: HeadIPC + connections: + - torso + organs: + eyes: OrganIPCEyes + torso: + part: TorsoIPC + connections: + - right arm + - left arm + - right leg + - left leg + organs: + brain: PositronicBrain + heart: OrganIPCPump + right arm: + part: RightArmIPC + connections: + - right hand + left arm: + part: LeftArmIPC + connections: + - left hand + right hand: + part: RightHandIPC + left hand: + part: LeftHandIPC + right leg: + part: RightLegIPC + connections: + - right foot + left leg: + part: LeftLegIPC + connections: + - left foot + right foot: + part: RightFootIPC + left foot: + part: LeftFootIPC diff --git a/Resources/Prototypes/Body/Prototypes/shadowkin.yml b/Resources/Prototypes/Body/Prototypes/shadowkin.yml new file mode 100644 index 0000000000..19c3f36de4 --- /dev/null +++ b/Resources/Prototypes/Body/Prototypes/shadowkin.yml @@ -0,0 +1,48 @@ +- type: body + id: Shadowkin + name: "Shadowkin" + root: torso + slots: + head: + part: HeadShadowkin + connections: + - torso + organs: + brain: OrganShadowkinBrain + eyes: OrganShadowkinEyes + torso: + part: TorsoShadowkin + connections: + - right arm + - left arm + - right leg + - left leg + organs: + heart: OrganShadowkinHeart + stomach: OrganShadowkinStomach + liver: OrganShadowkinLiver + kidneys: OrganShadowkinKidneys + right arm: + part: RightArmShadowkin + connections: + - right hand + left arm: + part: LeftArmShadowkin + connections: + - left hand + right hand: + part: RightHandShadowkin + left hand: + part: LeftHandShadowkin + right leg: + part: RightLegShadowkin + connections: + - right foot + left leg: + part: LeftLegShadowkin + connections: + - left foot + right foot: + part: RightFootShadowkin + left foot: + part: LeftFootShadowkin \ No newline at end of file diff --git a/Resources/Prototypes/Body/Prototypes/terminator.yml b/Resources/Prototypes/Body/Prototypes/terminator.yml deleted file mode 100644 index c271a89d86..0000000000 --- a/Resources/Prototypes/Body/Prototypes/terminator.yml +++ /dev/null @@ -1,85 +0,0 @@ -# not quite human... -- type: body - id: TerminatorFlesh - name: exterminator - root: torso - slots: - head: - part: HeadHuman - connections: - - torso - organs: - brain: MobTerminatorEndoskeleton - torso: - part: TorsoHuman - connections: - - left arm - - right arm - - left leg - - right leg - right arm: - part: RightArmHuman - connections: - - right hand - left arm: - part: LeftArmHuman - connections: - - left hand - right hand: - part: RightHandHuman - left hand: - part: LeftHandHuman - right leg: - part: RightLegHuman - connections: - - right foot - left leg: - part: LeftLegHuman - connections: - - left foot - right foot: - part: RightFootHuman - left foot: - part: LeftFootHuman - -# TODO: terminator body parts -- type: body - id: TerminatorEndoskeleton - name: terminatorEndoskeleton - root: torso - slots: - head: - part: HeadTerminator - connections: - - torso - torso: - part: TorsoTerminator - connections: - - left arm - - right arm - - left leg - - right leg - right arm: - part: RightArmTerminator - connections: - - right hand - left arm: - part: LeftArmTerminator - connections: - - left hand - right hand: - part: RightHandTerminator - left hand: - part: LeftHandTerminator - right leg: - part: RightLegTerminator - connections: - - right foot - left leg: - part: LeftLegTerminator - connections: - - left foot - right foot: - part: RightFootTerminator - left foot: - part: LeftFootTerminator diff --git a/Resources/Prototypes/Catalog/Cargo/cargo_engines.yml b/Resources/Prototypes/Catalog/Cargo/cargo_engines.yml index bd00b0c2d4..8d3bea5075 100644 --- a/Resources/Prototypes/Catalog/Cargo/cargo_engines.yml +++ b/Resources/Prototypes/Catalog/Cargo/cargo_engines.yml @@ -28,17 +28,17 @@ category: cargoproduct-category-name-engineering group: market -#- type: cargoProduct -# name: "emitter crate" -# id: EngineSingularityEmitter -# description: "Contains an emitter. Used only for dangerous applications." -# icon: -# sprite: Structures/Power/Generation/Singularity/emitter.rsi -# state: emitter2 -# product: CrateEngineeringSingularityEmitter -# cost: 3000 -# category: cargoproduct-category-name-engineering -# group: market +- type: cargoProduct + name: "emitter crate" + id: EngineSingularityEmitter + description: "Contains an emitter. Used only for dangerous applications." + icon: + sprite: Structures/Power/Generation/Singularity/emitter.rsi + state: emitter2 + product: CrateEngineeringSingularityEmitter + cost: 3000 + category: cargoproduct-category-name-engineering + group: market - type: cargoProduct id: EngineSingularityCollector diff --git a/Resources/Prototypes/Catalog/Cargo/cargo_fun.yml b/Resources/Prototypes/Catalog/Cargo/cargo_fun.yml index 68bb4a6b84..771c05db0d 100644 --- a/Resources/Prototypes/Catalog/Cargo/cargo_fun.yml +++ b/Resources/Prototypes/Catalog/Cargo/cargo_fun.yml @@ -68,6 +68,16 @@ category: cargoproduct-category-name-fun group: market +- type: cargoProduct + id: FunSprayPaints + icon: + sprite: Objects/Fun/spraycans.rsi + state: death2_cap + product: CrateFunSprayPaints + cost: 2000 + category: Fun + group: market + - type: cargoProduct id: FunParty icon: diff --git a/Resources/Prototypes/Catalog/Cargo/cargo_hardsuits.yml b/Resources/Prototypes/Catalog/Cargo/cargo_hardsuits.yml new file mode 100644 index 0000000000..a2f8b035b0 --- /dev/null +++ b/Resources/Prototypes/Catalog/Cargo/cargo_hardsuits.yml @@ -0,0 +1,92 @@ +# Engineering +- type: cargoProduct + id: EngineeringFotiaHardsuit + icon: + sprite: Clothing/OuterClothing/Hardsuits/atmospherics.rsi + state: icon + product: CrateEngineeringFotiaHardsuit + cost: 2250 + category: cargoproduct-category-name-hardsuits + group: market + +- type: cargoProduct + id: EngineeringLampsiHardsuit + icon: + sprite: Clothing/OuterClothing/Hardsuits/engineering.rsi + state: icon + product: CrateEngineeringLampsiHardsuit + cost: 2250 + category: cargoproduct-category-name-hardsuits + group: market + +# Logistics +- type: cargoProduct + id: LogisticsKritiHardsuit + icon: + sprite: Clothing/OuterClothing/Hardsuits/spatio.rsi + state: icon + product: CrateLogisticsKritiHardsuit + cost: 1250 + category: cargoproduct-category-name-hardsuits + group: market + +- type: cargoProduct + id: LogisticsLavrionHardsuit + icon: + sprite: Clothing/OuterClothing/Hardsuits/salvage.rsi + state: icon + product: CrateLogisticsLavrionHardsuit + cost: 3500 + category: cargoproduct-category-name-hardsuits + group: market + +# Security +- type: cargoProduct + id: SecurityShanlinTacsuit + icon: + sprite: Nyanotrasen/Clothing/OuterClothing/ReverseEngineering/syndicate.rsi + state: icon + product: CrateSecurityShanlinTacsuit + cost: 17500 + category: cargoproduct-category-name-hardsuits + group: market + +- type: cargoProduct + id: SecurityGuanYuTacsuit + icon: + sprite: Nyanotrasen/Clothing/OuterClothing/ReverseEngineering/juggernaut.rsi + state: icon + product: CrateSecurityGuanYuTacsuit + cost: 30000 + category: cargoproduct-category-name-hardsuits + group: market + +- type: cargoProduct + id: SecurityBaghaturTacsuit + icon: + sprite: DeltaV/Clothing/OuterClothing/Hardsuits/Combat/standard.rsi + state: icon + product: CrateSecurityBaghaturTacsuit + cost: 5000 + category: cargoproduct-category-name-hardsuits + group: market + +- type: cargoProduct + id: SecuritySuldeTacsuit + icon: + sprite: DeltaV/Clothing/OuterClothing/Hardsuits/Combat/riot.rsi + state: icon + product: CrateSecuritySuldeTacsuit + cost: 7500 + category: cargoproduct-category-name-hardsuits + group: market + +- type: cargoProduct + id: SecurityTsagaanTacsuit + icon: + sprite: DeltaV/Clothing/OuterClothing/Hardsuits/Combat/medical.rsi + state: icon + product: CrateSecurityTsagaanTacsuit + cost: 5500 + category: cargoproduct-category-name-hardsuits + group: market diff --git a/Resources/Prototypes/Catalog/Cargo/cargo_livestock.yml b/Resources/Prototypes/Catalog/Cargo/cargo_livestock.yml index f44cb61f73..55e256c7bb 100644 --- a/Resources/Prototypes/Catalog/Cargo/cargo_livestock.yml +++ b/Resources/Prototypes/Catalog/Cargo/cargo_livestock.yml @@ -58,6 +58,16 @@ category: cargoproduct-category-name-livestock group: market +- type: cargoProduct + id: LivestockPibble + icon: + sprite: Mobs/Pets/pitbull.rsi + state: pibble + product: CrateNPCPibble + cost: 700 + category: Livestock + group: market + - type: cargoProduct id: LivestockCorgi icon: diff --git a/Resources/Prototypes/Catalog/Cargo/cargo_materials.yml b/Resources/Prototypes/Catalog/Cargo/cargo_materials.yml index 6f945001d9..1529a05385 100644 --- a/Resources/Prototypes/Catalog/Cargo/cargo_materials.yml +++ b/Resources/Prototypes/Catalog/Cargo/cargo_materials.yml @@ -107,3 +107,33 @@ cost: 1000 category: cargoproduct-category-name-materials group: market + +- type: cargoProduct + id: MaterialUranium + icon: + sprite: Objects/Materials/Sheets/other.rsi + state: uranium_3 + product: CrateMaterialUranium + cost: 3000 + category: cargoproduct-category-name-materials + group: market + +- type: cargoProduct + id: MaterialGold + icon: + sprite: Objects/Materials/ingots.rsi + state: gold_3 + product: CrateMaterialGold + cost: 3000 + category: cargoproduct-category-name-materials + group: market + +- type: cargoProduct + id: MaterialSilver + icon: + sprite: Objects/Materials/ingots.rsi + state: silver_3 + product: CrateMaterialSilver + cost: 3000 + category: cargoproduct-category-name-materials + group: market diff --git a/Resources/Prototypes/Catalog/Cargo/cargo_security.yml b/Resources/Prototypes/Catalog/Cargo/cargo_security.yml index 2ad7628ddb..e22f9fd5e7 100644 --- a/Resources/Prototypes/Catalog/Cargo/cargo_security.yml +++ b/Resources/Prototypes/Catalog/Cargo/cargo_security.yml @@ -34,7 +34,7 @@ sprite: DeltaV/Clothing/OuterClothing/Armor/riot.rsi # DeltaV - resprite state: icon product: CrateSecurityRiot - cost: 5500 + cost: 7500 category: cargoproduct-category-name-security group: market diff --git a/Resources/Prototypes/Catalog/Fills/Backpacks/StarterGear/backpack.yml b/Resources/Prototypes/Catalog/Fills/Backpacks/StarterGear/backpack.yml index 5faab3d7ad..da46ae7597 100644 --- a/Resources/Prototypes/Catalog/Fills/Backpacks/StarterGear/backpack.yml +++ b/Resources/Prototypes/Catalog/Fills/Backpacks/StarterGear/backpack.yml @@ -150,6 +150,11 @@ parent: ClothingBackpackScience id: ClothingBackpackScienceFilled +- type: entity + noSpawn: true + parent: ClothingBackpackRobotics + id: ClothingBackpackRoboticsFilled + - type: entity noSpawn: true parent: ClothingBackpackHydroponics @@ -173,21 +178,11 @@ noSpawn: true parent: ClothingBackpack id: ClothingBackpackChaplainFilled - components: - - type: StorageFill - contents: - - id: Bible - - id: RubberStampChaplain - type: entity noSpawn: true parent: ClothingBackpack id: ClothingBackpackMusicianFilled - components: - - type: StorageFill - contents: - - id: AcousticGuitarInstrument - - id: SaxophoneInstrument - type: entity noSpawn: true diff --git a/Resources/Prototypes/Catalog/Fills/Backpacks/StarterGear/duffelbag.yml b/Resources/Prototypes/Catalog/Fills/Backpacks/StarterGear/duffelbag.yml index 0dfcf77458..07cbbeb6ca 100644 --- a/Resources/Prototypes/Catalog/Fills/Backpacks/StarterGear/duffelbag.yml +++ b/Resources/Prototypes/Catalog/Fills/Backpacks/StarterGear/duffelbag.yml @@ -147,6 +147,11 @@ parent: ClothingBackpackDuffelScience id: ClothingBackpackDuffelScienceFilled +- type: entity + noSpawn: true + parent: ClothingBackpackDuffelRobotics + id: ClothingBackpackDuffelRoboticsFilled + - type: entity noSpawn: true parent: ClothingBackpackDuffelHydroponics diff --git a/Resources/Prototypes/Catalog/Fills/Backpacks/StarterGear/satchel.yml b/Resources/Prototypes/Catalog/Fills/Backpacks/StarterGear/satchel.yml index f74207ca18..e20e27e55c 100644 --- a/Resources/Prototypes/Catalog/Fills/Backpacks/StarterGear/satchel.yml +++ b/Resources/Prototypes/Catalog/Fills/Backpacks/StarterGear/satchel.yml @@ -160,6 +160,11 @@ parent: ClothingBackpackSatchelScience id: ClothingBackpackSatchelScienceFilled +- type: entity + noSpawn: true + parent: ClothingBackpackSatchelRobotics + id: ClothingBackpackSatchelRoboticsFilled + - type: entity noSpawn: true parent: ClothingBackpackSatchelHydroponics diff --git a/Resources/Prototypes/Catalog/Fills/Backpacks/duffelbag.yml b/Resources/Prototypes/Catalog/Fills/Backpacks/duffelbag.yml index c07b0eccf1..6f4a978356 100644 --- a/Resources/Prototypes/Catalog/Fills/Backpacks/duffelbag.yml +++ b/Resources/Prototypes/Catalog/Fills/Backpacks/duffelbag.yml @@ -12,6 +12,7 @@ - id: Cautery - id: Retractor - id: Scalpel + - id: BoneGel - type: entity id: ClothingBackpackDuffelCBURNFilled @@ -46,6 +47,7 @@ - id: ScalpelAdvanced - id: ClothingHandsGlovesNitrile - id: EmergencyRollerBedSpawnFolded + - id: BoneGel - type: entity parent: ClothingBackpackDuffelSyndicateBundle diff --git a/Resources/Prototypes/Catalog/Fills/Crates/engines.yml b/Resources/Prototypes/Catalog/Fills/Crates/engines.yml index 9b47036b01..79698b550a 100644 --- a/Resources/Prototypes/Catalog/Fills/Crates/engines.yml +++ b/Resources/Prototypes/Catalog/Fills/Crates/engines.yml @@ -42,7 +42,7 @@ components: - type: StorageFill contents: - - id: Emitter # TODO change to flatpack + - id: EmitterFlatpack # TODO change to flatpack - type: entity id: CrateEngineeringSingularityCollector diff --git a/Resources/Prototypes/Catalog/Fills/Crates/fun.yml b/Resources/Prototypes/Catalog/Fills/Crates/fun.yml index b55bdd4832..a6f12bc727 100644 --- a/Resources/Prototypes/Catalog/Fills/Crates/fun.yml +++ b/Resources/Prototypes/Catalog/Fills/Crates/fun.yml @@ -248,14 +248,21 @@ contents: - id: SnapPopBox - id: CrazyGlue - amount: 2 - id: PlasticBanana + - id: FunnyPaint + orGroup: Paint + prob: 0.5 + - id: FunnyPaintYellow + orGroup: Paint + prob: 0.5 - id: WhoopieCushion - id: ToyHammer - id: MrChips - orGroup: GiftPool + prob: 0.5 + orGroup: Dummy - id: MrDips - orGroup: Giftpool + prob: 0.5 + orGroup: Dummy - id: RevolverCapGun - id: BalloonNT - id: ClothingShoesClownLarge @@ -288,6 +295,41 @@ amount: 15 prob: 0.05 +- type: entity + id: CrateFunSprayPaints + name: spray paint crate + description: a crate filled with spray paint. + parent: CratePlastic + suffix: Spray Paint + components: + - type: StorageFill + contents: + - id: SprayPaintBlue + amount: 2 + prob: 0.33 + - id: SprayPaintRed + amount: 2 + prob: 0.33 + - id: SprayPaintOrange + amount: 2 + prob: 0.33 + - id: SprayPaintBlack + amount: 2 + prob: 0.33 + - id: SprayPaintGreen + amount: 2 + prob: 0.33 + - id: SprayPaintPurple + amount: 2 + prob: 0.33 + - id: SprayPaintWhite + amount: 2 + prob: 0.33 + - id: DeathPaint + amount: 2 + - id: DeathPaintTwo + amount: 2 + - type: entity name: dartboard box set description: A box with everything you need for a fun game of darts. diff --git a/Resources/Prototypes/Catalog/Fills/Crates/hardsuits.yml b/Resources/Prototypes/Catalog/Fills/Crates/hardsuits.yml new file mode 100644 index 0000000000..9d6f46c499 --- /dev/null +++ b/Resources/Prototypes/Catalog/Fills/Crates/hardsuits.yml @@ -0,0 +1,92 @@ +# Engineering +- type: entity + id: CrateEngineeringFotiaHardsuit + parent: CrateEngineering + name: fotia hardsuit crate + description: Contains a single HpI-19t "Fotia" hardsuit. Requires Engineering access to open. + components: + - type: StorageFill + contents: + - id: ClothingOuterHardsuitAtmos + +- type: entity + id: CrateEngineeringLampsiHardsuit + parent: CrateEngineering + name: lampsi hardsuit crate + description: Contains a single HpI-19r "Lampsi" hardsuit. Requires Engineering access to open. + components: + - type: StorageFill + contents: + - id: ClothingOuterHardsuitAtmos + +# Logistics +- type: entity + id: CrateLogisticsKritiHardsuit + parent: CrateGenericSteel + name: kriti hardsuit crate + description: Contains a single HpI-20s "Kriti" hardsuit. + components: + - type: StorageFill + contents: + - id: ClothingOuterHardsuitSpatio + +- type: entity + id: CrateLogisticsLavrionHardsuit + parent: CrateGenericSteel + name: lavrion hardsuit crate + description: Contains a single HpI-20a "Lavrion" hardsuit. + components: + - type: StorageFill + contents: + - id: ClothingOuterHardsuitSalvage + +# Security +- type: entity + id: CrateSecurityShanlinTacsuit + parent: CrateSecgear + name: shanlin tacsuit crate + description: Contains a single CSA-51a "Shanlin" tacsuit. Requires Security access to open. + components: + - type: StorageFill + contents: + - id: ClothingOuterHardsuitSyndieReverseEngineered + +- type: entity + id: CrateSecurityGuanYuTacsuit + parent: CrateSecgear + name: guan-yu tacsuit crate + description: Contains a single CSA-80UA "Guan-Yu" tacsuit. Requires Security access to open. + components: + - type: StorageFill + contents: + - id: ClothingOuterHardsuitJuggernautReverseEngineered + +- type: entity + id: CrateSecurityBaghaturTacsuit + parent: CrateSecgear + name: baghatur tacsuit crate + description: Contains a single FPA-83s "Baghatur" tacsuit. Requires Security access to open. + components: + - type: StorageFill + contents: + - id: ClothingOuterHardsuitCombatStandard + +- type: entity + id: CrateSecuritySuldeTacsuit + parent: CrateSecgear + name: sulde tacsuit crate + description: Contains a single FPA-93 - "Sulde Mk.II" tacsuit. Requires Security access to open. + components: + - type: StorageFill + contents: + - id: ClothingOuterHardsuitCombatRiot + +- type: entity + id: CrateSecurityTsagaanTacsuit + parent: CrateSecgear + name: tsagaan tacsuit crate + description: Contains a single FPA-86 - "Tsagaan Mk.II" tacsuit. Requires Security access to open. + components: + - type: StorageFill + contents: + - id: ClothingOuterHardsuitCombatMedical diff --git a/Resources/Prototypes/Catalog/Fills/Crates/materials.yml b/Resources/Prototypes/Catalog/Fills/Crates/materials.yml index bd47f98477..92721208f7 100644 --- a/Resources/Prototypes/Catalog/Fills/Crates/materials.yml +++ b/Resources/Prototypes/Catalog/Fills/Crates/materials.yml @@ -127,3 +127,33 @@ # contents: # - id: WaterTankFull # amount: 1 + +- type: entity + id: CrateMaterialUranium + name: uranium crate + parent: CrateUranium + components: + - type: StorageFill + contents: + - id: SheetUranium + amount: 3 + +- type: entity + id: CrateMaterialGold + name: gold crate + parent: CrateGenericSteel + components: + - type: StorageFill + contents: + - id: IngotGold + amount: 3 + +- type: entity + id: CrateMaterialSilver + name: silver crate + parent: CrateGenericSteel + components: + - type: StorageFill + contents: + - id: IngotSilver + amount: 3 diff --git a/Resources/Prototypes/Catalog/Fills/Crates/medical.yml b/Resources/Prototypes/Catalog/Fills/Crates/medical.yml index 8b1f7fade3..2f67037d35 100644 --- a/Resources/Prototypes/Catalog/Fills/Crates/medical.yml +++ b/Resources/Prototypes/Catalog/Fills/Crates/medical.yml @@ -67,7 +67,9 @@ - id: Drill - id: Saw - id: Hemostat - - id: ClothingMaskSterile + - id: BoneGel + - id: BoxLatexGloves + - id: BoxSterileMask - type: entity id: CrateMedicalScrubs diff --git a/Resources/Prototypes/Catalog/Fills/Crates/npc.yml b/Resources/Prototypes/Catalog/Fills/Crates/npc.yml index 10c715bb99..b2dc4b223b 100644 --- a/Resources/Prototypes/Catalog/Fills/Crates/npc.yml +++ b/Resources/Prototypes/Catalog/Fills/Crates/npc.yml @@ -81,6 +81,17 @@ - id: MobDuckWhite amount: 2 +- type: entity + id: CrateNPCPibble + parent: CrateLivestock + name: pitbull crate + description: "Note from the shelter: Lab mix. Looking for a home without cats, birds, or children. Anxious when pet." + components: + - type: StorageFill + contents: + - id: MobPibble + amount: 1 + - type: entity id: CrateNPCCorgi parent: CrateLivestock diff --git a/Resources/Prototypes/Catalog/Fills/Crates/service.yml b/Resources/Prototypes/Catalog/Fills/Crates/service.yml index 141f98edab..35e66ac4d3 100644 --- a/Resources/Prototypes/Catalog/Fills/Crates/service.yml +++ b/Resources/Prototypes/Catalog/Fills/Crates/service.yml @@ -20,7 +20,7 @@ - id: Plunger amount: 2 - id: BoxCleanerGrenades - + - type: entity id: CrateServiceReplacementLights parent: CrateGenericSteel @@ -88,6 +88,10 @@ - id: ClothingNeckCloakVoid - id: RevolverCapGun - id: BarberScissors + - id: ClothingUniformJumpskirtOldDress + - id: BikeHorn + - id: ClownRecorder + - id: ClothingBeltSuspenders - type: entity id: CrateServiceCustomSmokable diff --git a/Resources/Prototypes/Catalog/Fills/Lockers/engineer.yml b/Resources/Prototypes/Catalog/Fills/Lockers/engineer.yml index e4d995e52e..b3efb6ec1d 100644 --- a/Resources/Prototypes/Catalog/Fills/Lockers/engineer.yml +++ b/Resources/Prototypes/Catalog/Fills/Lockers/engineer.yml @@ -65,13 +65,20 @@ components: - type: StorageFill contents: + - id: ClothingHeadHatWelding + - id: ClothingHeadHatWelding + - id: ClothingHeadHatWelding + prob: 0.5 + - id: Welder + - id: Welder - id: WelderMini + orGroup: thirdWelder - id: Welder - prob: 0.7 + prob: 0.33 + orGroup: thirdWelder - id: WelderIndustrial - prob: 0.5 - - id: ClothingHeadHatWelding - prob: 0.5 + prob: 0.33 + orGroup: thirdWelder - type: entity id: LockerAtmosphericsFilledHardsuit diff --git a/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml b/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml index 752591da37..9394702303 100644 --- a/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml +++ b/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml @@ -15,6 +15,8 @@ prob: 0.50 - id: DoorRemoteCargo - id: RubberStampQm + - id: RubberStampDenied + - id: RubberStampApproved - id: ClothingHeadsetAltCargo - id: BoxEncryptionKeyCargo - id: SpaceCashLuckyBill # DeltaV - LO steal objective, see Resources/Prototypes/DeltaV/Entities/Objects/Misc/first_bill.yml @@ -132,6 +134,8 @@ - id: DoorRemoteService - id: ClothingNeckGoldmedal - id: RubberStampHop + - id: RubberStampDenied + - id: RubberStampApproved - id: BoxEncryptionKeyPassenger - id: BoxEncryptionKeyService - id: AccessConfigurator @@ -213,6 +217,7 @@ - id: RubberStampCMO - id: RubberStampPsychologist # DeltaV - id: MedicalTechFabCircuitboard + - id: MedicalBiofabMachineBoard - id: BoxEncryptionKeyMedical - id: BoxPDAMedical # Delta-V - id: ClothingBeltMilitaryWebbingCMO # DeltaV - add webbing for CMO. ON THIS STATION, IT'S DRIP OR [die], CAPTAIN! @@ -238,6 +243,7 @@ - id: RubberStampCMO - id: MedicalTechFabCircuitboard - id: BoxEncryptionKeyMedical + - id: MedicalBiofabMachineBoard - id: BoxPDAMedical # Delta-V - id: ClothingBeltMilitaryWebbingCMO # DeltaV - add webbing for CMO. ON THIS STATION, IT'S DRIP OR [die], CAPTAIN! - id: CMOIDCard # Delta-V @@ -315,7 +321,6 @@ - id: HoloprojectorSecurity - id: BookSecretDocuments - id: BoxPDASecurity # Delta-V - - id: WeaponEnergyGunMultiphase # DeltaV - HoS Energy Gun - id: HoSIDCard # Delta-V - id: LunchboxCommandFilledRandom # Delta-V Lunchboxes! prob: 0.3 @@ -344,7 +349,6 @@ - id: HoloprojectorSecurity - id: BookSecretDocuments - id: BoxPDASecurity # Delta-V - - id: WeaponEnergyGunMultiphase # DeltaV - HoS Energy Gun - id: HoSIDCard # Delta-V - id: LunchboxCommandFilledRandom # Delta-V Lunchboxes! prob: 0.3 diff --git a/Resources/Prototypes/Catalog/Fills/Lockers/misc.yml b/Resources/Prototypes/Catalog/Fills/Lockers/misc.yml index 3a9211d248..feaedd924a 100644 --- a/Resources/Prototypes/Catalog/Fills/Lockers/misc.yml +++ b/Resources/Prototypes/Catalog/Fills/Lockers/misc.yml @@ -157,6 +157,10 @@ prob: 0.25 - id: StrangePill prob: 0.20 + - id: DeathPaint + prob: 0.05 + - id: DeathPaintTwo + prob: 0.05 - id: DrinkMopwataBottleRandom prob: 0.20 - id: ModularReceiver @@ -307,3 +311,15 @@ - id: WeaponSniperMosin prob: 0.0010 orGroup: syndiemaintloot + +- type: entity + id: ClosetWallRadiationFilled + suffix: Filled + parent: ClosetWallRadiation + components: + - type: StorageFill + contents: + - id: ClothingOuterSuitRad + amount: 2 + - id: GeigerCounter + amount: 2 diff --git a/Resources/Prototypes/Catalog/Fills/Lockers/service.yml b/Resources/Prototypes/Catalog/Fills/Lockers/service.yml index b86fa2e1f5..ba386a83ac 100644 --- a/Resources/Prototypes/Catalog/Fills/Lockers/service.yml +++ b/Resources/Prototypes/Catalog/Fills/Lockers/service.yml @@ -5,8 +5,6 @@ components: - type: StorageFill contents: - - id: ClothingOuterArmorDuraVest # DeltaV - ClothingOuterArmorBasicSlim replaced in favour of stabproof vest - - id: WeaponShotgunDoubleBarreledRubber - id: DrinkShaker - id: ClothingEyesHudBeer - id: HandLabeler @@ -17,8 +15,28 @@ prob: 0.5 - id: DrinkBottleBeer prob: 0.5 - - id: BoxBeanbag + - id: RagItem amount: 2 + - id: LunchboxServiceFilledRandom # Delta-V Lunchboxes! + prob: 0.3 + +- type: entity + id: LockerBartenderFilled + suffix: Filled + parent: LockerBartender + components: + - type: StorageFill + contents: + - id: DrinkShaker + - id: ClothingEyesHudBeer + - id: HandLabeler + amount: 1 + - id: DrinkBottleBeer + prob: 0.5 + - id: DrinkBottleBeer + prob: 0.5 + - id: DrinkBottleBeer + prob: 0.5 - id: RagItem amount: 2 - id: LunchboxServiceFilledRandom # Delta-V Lunchboxes! diff --git a/Resources/Prototypes/Catalog/Fills/Paper/manuals.yml b/Resources/Prototypes/Catalog/Fills/Paper/manuals.yml index e13070d1ba..4893fa2557 100644 --- a/Resources/Prototypes/Catalog/Fills/Paper/manuals.yml +++ b/Resources/Prototypes/Catalog/Fills/Paper/manuals.yml @@ -19,7 +19,7 @@ key: enum.PaperUiKey.Key - type: UserInterface interfaces: - - key: enum.PaperUiKey.Key - type: PaperBoundUserInterface + enum.PaperUiKey.Key: + type: PaperBoundUserInterface - type: Paper content: book-text-holoparasite-info diff --git a/Resources/Prototypes/Catalog/Jukebox/Standard.yml b/Resources/Prototypes/Catalog/Jukebox/Standard.yml new file mode 100644 index 0000000000..7440428bd4 --- /dev/null +++ b/Resources/Prototypes/Catalog/Jukebox/Standard.yml @@ -0,0 +1,41 @@ +- type: jukebox + id: FlipFlap + name: X-CEED - Flip Flap + path: + path: /Audio/Jukebox/flip-flap.ogg + +- type: jukebox + id: Tintin + name: Jeroen Tel - Tintin on the Moon + path: + path: /Audio/Jukebox/title3.ogg + +- type: jukebox + id: Thunderdome + name: MashedByMachines - Sector 11 + path: + path: /Audio/Jukebox/sector11.ogg + +- type: jukebox + id: Constellations + name: Qwertyquerty - Constellations + path: + path: /Audio/Jukebox/constellations.ogg + +- type: jukebox + id: Drifting + name: Qwertyquerty - Drifting + path: + path: /Audio/Jukebox/drifting.ogg + +- type: jukebox + id: starlight + name: Qwertyquerty - Starlight + path: + path: /Audio/Jukebox/starlight.ogg + +- type: jukebox + id: sunset + name: PigeonBeans - Sunset + path: + path: /Audio/Jukebox/sunset.ogg diff --git a/Resources/Prototypes/Catalog/ReagentDispensers/beverage.yml b/Resources/Prototypes/Catalog/ReagentDispensers/beverage.yml index 975541a502..4689e26f8e 100644 --- a/Resources/Prototypes/Catalog/ReagentDispensers/beverage.yml +++ b/Resources/Prototypes/Catalog/ReagentDispensers/beverage.yml @@ -6,6 +6,7 @@ - DrinkColaBottleFull - DrinkCreamCartonXL - DrinkDrGibbJug + - DrinkEnergyDrinkJug - DrinkGreenTeaJug - DrinkIceJug - DrinkJuiceLimeCartonXL diff --git a/Resources/Prototypes/Catalog/VendingMachines/Inventories/boozeomat.yml b/Resources/Prototypes/Catalog/VendingMachines/Inventories/boozeomat.yml index ac298b240a..1d86640d1c 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Inventories/boozeomat.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Inventories/boozeomat.yml @@ -7,6 +7,9 @@ DrinkVacuumFlask: 5 DrinkFlaskBar: 5 DrinkShaker: 5 + DrinkJigger: 5 + DrinkIceBucket: 2 + BarSpoon: 3 CustomDrinkJug: 2 #to allow for custom drinks in the soda/booze dispensers DrinkAbsintheBottleFull: 2 DrinkAleBottleFull: 5 @@ -15,6 +18,7 @@ DrinkCognacBottleFull: 4 DrinkCoconutWaterCarton: 3 DrinkColaBottleFull: 4 + DrinkEnergyDrinkCan: 8 DrinkMilkCarton: 2 DrinkCreamCarton: 5 DrinkGinBottleFull: 3 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Inventories/chang.yml b/Resources/Prototypes/Catalog/VendingMachines/Inventories/chang.yml index 282f58535b..5befd85ca8 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Inventories/chang.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Inventories/chang.yml @@ -7,4 +7,5 @@ DrinkHellRamen: 3 FoodSnackChowMein: 3 FoodSnackDanDanNoodles: 3 + PairedChopsticks: 3 # rice? diff --git a/Resources/Prototypes/Catalog/VendingMachines/Inventories/curadrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Inventories/curadrobe.yml index aac1cbb3f4..fe332ea52d 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Inventories/curadrobe.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Inventories/curadrobe.yml @@ -4,6 +4,7 @@ BooksBag: 2 BriefcaseBrown: 2 HandLabeler: 2 + Cane: 3 ClothingEyesGlasses: 2 ClothingEyesGlassesJamjar: 2 ClothingNeckScarfStripedGreen: 2 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Inventories/detdrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Inventories/detdrobe.yml index f45dd229a2..91e45c8391 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Inventories/detdrobe.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Inventories/detdrobe.yml @@ -4,7 +4,7 @@ ClothingUniformJumpsuitDetective: 2 ClothingUniformJumpskirtDetective: 2 ClothingShoesColorBrown: 2 - ClothingOuterCoatDetective: 2 + ClothingOuterCoatDetectiveLoadout: 2 ClothingHeadHatFedoraBrown: 2 ClothingUniformJumpsuitDetectiveGrey: 2 ClothingUniformJumpskirtDetectiveGrey: 2 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Inventories/dinnerware.yml b/Resources/Prototypes/Catalog/VendingMachines/Inventories/dinnerware.yml index 0b9a73aa6e..ba270a9af8 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Inventories/dinnerware.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Inventories/dinnerware.yml @@ -6,6 +6,7 @@ RollingPin: 4 Spoon: 4 Fork: 4 + PairedChopsticks: 4 FoodBowlBig: 10 FoodPlate: 10 FoodPlateSmall: 10 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Inventories/discount.yml b/Resources/Prototypes/Catalog/VendingMachines/Inventories/discount.yml index ddf7943217..fc8492dcf1 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Inventories/discount.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Inventories/discount.yml @@ -4,6 +4,7 @@ FoodSnackCheesie: 3 FoodSnackChips: 3 FoodSnackBoritos: 3 + DrinkEnergyDrinkCan: 4 FoodSnackPopcorn: 3 FoodSnackEnergy: 3 CigPackMixed: 2 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Inventories/medical.yml b/Resources/Prototypes/Catalog/VendingMachines/Inventories/medical.yml index 0ccb7a0be9..5f4600ed13 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Inventories/medical.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Inventories/medical.yml @@ -7,7 +7,7 @@ Bloodpack: 5 EpinephrineChemistryBottle: 3 Syringe: 5 - Portafib: 1 # DeltaV - Add Portafibs, see Prototypes/DeltaV/Entities/Objects/Devices/Medical/portafib.yml - ClothingEyesGlasses: 5 # SimpleStation14 NearsightedTrait + Portafib: 1 + ClothingEyesGlasses: 5 ClothingEyesHudMedical: 2 ClothingEyesEyepatchHudMedical: 2 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Inventories/robodrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Inventories/robodrobe.yml index 4aa4ce6972..a8178b7196 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Inventories/robodrobe.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Inventories/robodrobe.yml @@ -11,3 +11,6 @@ ClothingHeadsetRobotics: 2 ClothingOuterWinterRobo: 2 ClothingShoesBootsWinterRobo: 2 #Delta V: Add departmental winter boots + ClothingBackpackRobotics: 2 + ClothingBackpackSatchelRobotics: 2 + ClothingBackpackDuffelRobotics: 2 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Inventories/sec.yml b/Resources/Prototypes/Catalog/VendingMachines/Inventories/sec.yml index 01b58ae40f..f62ba72950 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Inventories/sec.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Inventories/sec.yml @@ -16,14 +16,16 @@ ClothingBeltSecurityWebbing: 5 CombatKnife: 3 Zipties: 12 + BolaEnergy: 6 RiotShield: 2 RiotLaserShield: 2 RiotBulletShield: 2 - ClothingHeadHelmetInsulated: 2 # Nyanotrasen - Insulative headgear - ClothingHeadCage: 2 # Nyanotrasen - Insulative headgear - ClothingOuterArmorPlateCarrier: 2 # DeltaV - moved body armour from SecDrobe to SecTech + RadioHandheldSecurity: 5 + ClothingHeadHelmetInsulated: 2 + ClothingHeadCage: 2 + ClothingOuterArmorPlateCarrier: 2 ClothingOuterArmorDuraVest: 2 - ClothingHeadHelmetBasic: 2 # DeltaV - added helmets to the SecTech. Another line of defense between the tide and your grey matter. + ClothingHeadHelmetBasic: 2 BreachingCharge: 8 # security officers need to follow a diet regimen! contrabandInventory: diff --git a/Resources/Prototypes/Catalog/spellbook_catalog.yml b/Resources/Prototypes/Catalog/spellbook_catalog.yml new file mode 100644 index 0000000000..38b95c3273 --- /dev/null +++ b/Resources/Prototypes/Catalog/spellbook_catalog.yml @@ -0,0 +1,140 @@ +# Offensive +- type: listing + id: SpellbookFireball + name: spellbook-fireball-name + description: spellbook-fireball-desc + productAction: ActionFireball + productUpgradeId: SpellbookFireballUpgrade + cost: + WizCoin: 2 + categories: + - SpellbookOffensive + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + +- type: listing + id: SpellbookRodForm + name: spellbook-polymorph-rod-name + description: spellbook-polymorph-rod-desc + productAction: ActionPolymorphWizardRod + cost: + WizCoin: 3 + categories: + - SpellbookOffensive + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + +# Defensive +- type: listing + id: SpellbookForceWall + name: spellbook-force-wall-name + description: spellbook-force-wall-desc + productAction: ActionForceWall + cost: + WizCoin: 3 + categories: + - SpellbookDefensive + +# Utility +- type: listing + id: SpellbookPolymorphSpider + name: spellbook-polymoprh-spider-name + description: spellbook-polymorph-spider-desc + productAction: ActionPolymorphWizardSpider + cost: + WizCoin: 2 + categories: + - SpellbookUtility + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + +- type: listing + id: SpellbookBlink + name: spellbook-blink-name + description: spellbook-blink-desc + productAction: ActionBlink + cost: + WizCoin: 1 + categories: + - SpellbookUtility + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + +- type: listing + id: SpellbookCharge + name: spellbook-charge-name + description: spellbook-charge-desc + productAction: ActionChargeSpell + cost: + WizCoin: 1 + categories: + - SpellbookUtility + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + +# Equipment +- type: listing + id: SpellbookWandDoor + name: spellbook-wand-polymorph-door-name + description: spellbook-wand-polymorph-door-description + productEntity: WeaponWandPolymorphDoor + cost: + WizCoin: 3 + categories: + - SpellbookEquipment + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + +- type: listing + id: SpellbookWandPolymorphCarp + name: spellbook-wand-polymorph-carp-name + description: spellbook-wand-polymorph-carp-description + productEntity: WeaponWandPolymorphCarp + cost: + WizCoin: 3 + categories: + - SpellbookEquipment + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + +# Event +- type: listing + id: SpellbookEventSummonGhosts + name: spellbook-event-summon-ghosts-name + description: spellbook-event-summon-ghosts-description + productAction: ActionSummonGhosts + cost: + WizCoin: 0 + categories: + - SpellbookEvents + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + +# Upgrades +- type: listing + id: SpellbookFireballUpgrade + productUpgradeId: SpellbookFireballUpgrade + name: spellbook-upgrade-fireball-name + description: spellbook-upgrade-fireball-description + icon: + sprite: Objects/Magic/magicactions.rsi + state: fireball + cost: + WizCoin: 2 + categories: + - SpellbookOffensive + conditions: + - !type:BuyBeforeCondition + whitelist: + - SpellbookFireball + # manual for now + - !type:ListingLimitedStockCondition + stock: 2 diff --git a/Resources/Prototypes/Catalog/thief_toolbox_sets.yml b/Resources/Prototypes/Catalog/thief_toolbox_sets.yml index 2c98b0b89d..e70e93732f 100644 --- a/Resources/Prototypes/Catalog/thief_toolbox_sets.yml +++ b/Resources/Prototypes/Catalog/thief_toolbox_sets.yml @@ -14,6 +14,7 @@ - ClothingEyesChameleon - ClothingHeadsetChameleon - ClothingShoesChameleon + - ChameleonProjector - type: thiefBackpackSet id: ToolsSet diff --git a/Resources/Prototypes/Catalog/uplink_catalog.yml b/Resources/Prototypes/Catalog/uplink_catalog.yml index a1a60e3fef..2a99a1daa0 100644 --- a/Resources/Prototypes/Catalog/uplink_catalog.yml +++ b/Resources/Prototypes/Catalog/uplink_catalog.yml @@ -10,6 +10,7 @@ Telecrystal: 3 categories: - UplinkWeapons + saleLimit: 1 - type: listing id: UplinkRevolverPython @@ -20,6 +21,7 @@ Telecrystal: 8 # Originally was 13 TC but was not used due to high cost categories: - UplinkWeapons + saleLimit: 1 # Inbuilt suppressor so it's sneaky + more expensive. - type: listing @@ -31,6 +33,7 @@ Telecrystal: 4 categories: - UplinkWeapons + saleLimit: 1 # Poor accuracy, slow to fire, cheap option - type: listing @@ -42,6 +45,7 @@ Telecrystal: 1 categories: - UplinkWeapons + saleLimit: 1 - type: listing id: UplinkEsword @@ -53,6 +57,7 @@ Telecrystal: 8 categories: - UplinkWeapons + saleLimit: 2 - type: listing id: UplinkEnergyDagger @@ -64,6 +69,7 @@ Telecrystal: 2 categories: - UplinkWeapons + saleLimit: 1 - type: listing id: UplinkThrowingKnivesKit @@ -85,6 +91,7 @@ Telecrystal: 8 categories: - UplinkWeapons + saleLimit: 1 - type: listing id: UplinkDisposableTurret @@ -100,6 +107,7 @@ blacklist: tags: - NukeOpsUplink + saleLimit: 2 - type: listing id: BaseBallBatHomeRun @@ -112,6 +120,7 @@ Telecrystal: 16 categories: - UplinkWeapons + saleLimit: 1 # Explosives @@ -214,6 +223,7 @@ whitelist: tags: - NukeOpsUplink + saleLimit: 1 - type: listing id: UplinkC4Bundle @@ -224,6 +234,7 @@ Telecrystal: 12 #you're buying bulk so its a 25% discount categories: - UplinkExplosives + saleLimit: 1 - type: listing id: UplinkEmpGrenade @@ -261,6 +272,7 @@ blacklist: tags: - NukeOpsUplink + saleLimit: 1 - type: listing id: UplinkSyndicateBombNukie @@ -276,6 +288,7 @@ whitelist: tags: - NukeOpsUplink + saleLimit: 1 - type: listing id: UplinkClusterGrenade @@ -380,21 +393,193 @@ #Utility -#- type: listing # -# id: UplinkHoloparaKit -# name: uplink-holopara-kit-name -# description: uplink-holopara-kit-desc -# icon: { sprite: /Textures/Objects/Misc/guardian_info.rsi, state: icon } -# productEntity: BoxHoloparasite -# cost: -# Telecrystal: 14 -# categories: -# - UplinkUtility -# conditions: -# - !type:StoreWhitelistCondition -# blacklist: -# tags: -# - NukeOpsUplink +- type: listing + id: UplinkZombieBundle + name: uplink-zombie-bundle-name + description: uplink-zombie-bundle-desc + icon: { sprite: /Textures/Structures/Wallmounts/signs.rsi, state: bio } + productEntity: ClothingBackpackDuffelZombieBundle + cost: + Telecrystal: 40 + categories: + - UplinkChemicals + conditions: + - !type:StoreWhitelistCondition + whitelist: + tags: + - NukeOpsUplink + - !type:BuyerWhitelistCondition + blacklist: + components: + - SurplusBundle + saleLimit: 1 + +- type: listing + id: UplinkNocturineChemistryBottle + name: uplink-nocturine-chemistry-bottle-name + description: uplink-nocturine-chemistry-bottle-desc + productEntity: NocturineChemistryBottle + cost: + Telecrystal: 6 + categories: + - UplinkChemicals + saleLimit: 1 + +- type: listing + id: UplinkCombatMedkit + name: uplink-combat-medkit-name + description: uplink-combat-medkit-desc + productEntity: MedkitCombatFilled + cost: + Telecrystal: 5 + categories: + - UplinkChemicals + saleLimit: 1 + +- type: listing + id: UplinkCombatMedipen + name: uplink-combat-medipen-name + description: uplink-combat-medipen-desc + productEntity: CombatMedipen + cost: + Telecrystal: 4 + categories: + - UplinkChemicals + saleLimit: 1 + +- type: listing + id: UplinkStimpack + name: uplink-stimpack-name + description: uplink-stimpack-desc + productEntity: Stimpack + cost: + Telecrystal: 4 + categories: + - UplinkChemicals + saleLimit: 1 + +- type: listing + id: UplinkStimkit + name: uplink-stimkit-name + description: uplink-stimkit-desc + productEntity: StimkitFilled + cost: + Telecrystal: 12 + categories: + - UplinkChemicals + saleLimit: 1 + +- type: listing + id: UplinkCigarettes + name: uplink-cigarettes-name + description: uplink-cigarettes-desc + productEntity: CigPackSyndicate + cost: + Telecrystal: 2 + categories: + - UplinkChemicals + +- type: listing + id: UplinkMedsBundle + name: uplink-meds-bundle-name + description: uplink-meds-bundle-desc + productEntity: ClothingBackpackDuffelSyndicateMedicalBundleFilled + cost: + Telecrystal: 20 + categories: + - UplinkChemicals + conditions: + - !type:StoreWhitelistCondition + whitelist: + tags: + - NukeOpsUplink + - !type:BuyerWhitelistCondition + blacklist: + components: + - SurplusBundle + saleLimit: 1 + +# Deception + +- type: listing + id: UplinkAgentIDCard + name: uplink-agent-id-card-name + description: uplink-agent-id-card-desc + productEntity: AgentIDCard + cost: + Telecrystal: 3 + categories: + - UplinkDeception + +- type: listing + id: UplinkStealthBox + name: uplink-stealth-box-name + description: uplink-stealth-box-desc + productEntity: StealthBox + cost: + Telecrystal: 5 + categories: + - UplinkDeception + +- type: listing + id: UplinkChameleonProjector + name: uplink-chameleon-projector-name + description: uplink-chameleon-projector-desc + productEntity: ChameleonProjector + cost: + Telecrystal: 7 + categories: + - UplinkDeception + +- type: listing + id: UplinkCyberpen + name: uplink-cyberpen-name + description: uplink-cyberpen-desc + productEntity: CyberPen + cost: + Telecrystal: 1 + categories: + - UplinkDeception + +- type: listing + id: UplinkDecoyDisk + name: uplink-decoy-disk-name + description: uplink-decoy-disk-desc + productEntity: NukeDiskFake + cost: + Telecrystal: 1 + categories: + - UplinkDeception + +- type: listing + id: UplinkUltrabrightLantern + name: uplink-ultrabright-lantern-name + description: uplink-ultrabright-lantern-desc + productEntity: LanternFlash + cost: + Telecrystal: 2 + categories: + - UplinkDeception + +- type: listing + id: UplinkBribe + name: uplink-bribe-name + description: uplink-bribe-desc + productEntity: BriefcaseSyndieLobbyingBundleFilled + cost: + Telecrystal: 4 + categories: + - UplinkDeception + +# - type: listing +# id: UplinkGigacancerScanner +# name: Ultragigacancer Health Analyzer +# description: Works like a normal health analyzer, other than giving everyone it scans ultragigacancer. +# productEntity: HandheldHealthAnalyzerGigacancer +# cost: +# Telecrystal: 5 +# categories: +# - UplinkDeception - type: listing id: UplinkHolster @@ -417,14 +602,15 @@ - UplinkUtility - type: listing - id: UplinkAgentIDCard - name: uplink-agent-id-card-name - description: uplink-agent-id-card-desc - productEntity: AgentIDCard + id: UplinkSyndicateMartyrModule + name: uplink-syndicate-martyr-module-name + description: uplink-syndicate-martyr-module-desc + productEntity: BorgModuleMartyr + icon: { sprite: /Textures/Objects/Specific/Robotics/borgmodule.rsi, state: syndicateborgbomb } cost: - Telecrystal: 3 + Telecrystal: 4 categories: - - UplinkUtility + - UplinkDisruption - type: listing id: UplinkJetpack @@ -451,6 +637,7 @@ blacklist: tags: - NukeOpsUplink + saleLimit: 1 - type: listing id: UplinkReinforcementRadioSyndicateNukeops # Version for Nukeops that spawns an agent with the NukeOperative component. @@ -467,6 +654,7 @@ whitelist: tags: - NukeOpsUplink + saleLimit: 1 - type: listing id: UplinkReinforcementRadioSyndicateCyborgAssault @@ -483,12 +671,13 @@ whitelist: tags: - NukeOpsUplink + saleLimit: 1 - type: listing - id: UplinkReinforcementRadioSyndicateMonkey - name: uplink-reinforcement-radio-monkey-name - description: uplink-reinforcement-radio-monkey-desc - productEntity: ReinforcementRadioSyndicateMonkey + id: UplinkReinforcementRadioSyndicateAncestor + name: uplink-reinforcement-radio-ancestor-name + description: uplink-reinforcement-radio-ancestor-desc + productEntity: ReinforcementRadioSyndicateAncestor icon: { sprite: Objects/Devices/communication.rsi, state: radio } cost: Telecrystal: 8 @@ -501,10 +690,10 @@ - NukeOpsUplink - type: listing - id: UplinkReinforcementRadioSyndicateMonkeyNukeops # Version for Nukeops that spawns a syndicate monkey with the NukeOperative component. - name: uplink-reinforcement-radio-monkey-name - description: uplink-reinforcement-radio-monkey-desc - productEntity: ReinforcementRadioSyndicateMonkeyNukeops + id: UplinkReinforcementRadioSyndicateAncestorNukeops # Version for Nukeops that spawns a syndicate monkey with the NukeOperative component. + name: uplink-reinforcement-radio-ancestor-name + description: uplink-reinforcement-radio-ancestor-desc + productEntity: ReinforcementRadioSyndicateAncestorNukeops icon: { sprite: Objects/Devices/communication.rsi, state: radio } cost: Telecrystal: 6 @@ -515,16 +704,7 @@ whitelist: tags: - NukeOpsUplink - -- type: listing - id: UplinkStealthBox - name: uplink-stealth-box-name - description: uplink-stealth-box-desc - productEntity: StealthBox - cost: - Telecrystal: 5 - categories: - - UplinkUtility + saleLimit: 1 - type: listing id: UplinkHeadsetEncryptionKey @@ -545,7 +725,6 @@ productEntity: EncryptionKeyBinary cost: Telecrystal: 1 - categories: - UplinkUtility @@ -579,11 +758,6 @@ Telecrystal: 2 categories: - UplinkUtility - conditions: - - !type:StoreWhitelistCondition - whitelist: - tags: - - NukeOpsUplink - type: listing id: UplinkRadioJammer @@ -748,6 +922,7 @@ blacklist: tags: - NukeOpsUplink + saleBlacklist: true - type: listing id: UplinkDeathRattle @@ -821,6 +996,7 @@ blacklist: components: - SurplusBundle + saleLimit: 1 - type: listing id: UplinkChemistryKitBundle @@ -844,24 +1020,6 @@ categories: - UplinkBundles -- type: listing - id: UplinkMedsBundle - name: uplink-meds-bundle-name - description: uplink-meds-bundle-desc - productEntity: ClothingBackpackDuffelSyndicateMedicalBundleFilled - cost: - Telecrystal: 20 - categories: - - UplinkBundles - conditions: - - !type:StoreWhitelistCondition - whitelist: - tags: - - NukeOpsUplink - - !type:BuyerWhitelistCondition - blacklist: - components: - - SurplusBundle - type: listing id: UplinkSniperBundle @@ -873,6 +1031,7 @@ Telecrystal: 12 categories: - UplinkBundles + saleLimit: 1 - type: listing id: UplinkC20RBundle @@ -884,6 +1043,7 @@ Telecrystal: 17 categories: - UplinkBundles + saleLimit: 1 - type: listing id: UplinkBulldogBundle @@ -895,6 +1055,7 @@ Telecrystal: 20 categories: - UplinkBundles + saleLimit: 1 - type: listing id: UplinkGrenadeLauncherBundle @@ -906,6 +1067,7 @@ Telecrystal: 25 categories: - UplinkBundles + saleLimit: 1 - type: listing id: UplinkL6SawBundle @@ -917,26 +1079,7 @@ Telecrystal: 30 categories: - UplinkBundles - -- type: listing - id: UplinkZombieBundle - name: uplink-zombie-bundle-name - description: uplink-zombie-bundle-desc - icon: { sprite: /Textures/Structures/Wallmounts/signs.rsi, state: bio } - productEntity: ClothingBackpackDuffelZombieBundle - cost: - Telecrystal: 40 - categories: - - UplinkBundles - conditions: - - !type:StoreWhitelistCondition - whitelist: - tags: - - NukeOpsUplink - - !type:BuyerWhitelistCondition - blacklist: - components: - - SurplusBundle + saleLimit: 1 - type: listing id: UplinkSurplusBundle @@ -956,6 +1099,7 @@ blacklist: components: - SurplusBundle + saleBlacklist: true - type: listing id: UplinkSuperSurplusBundle @@ -975,6 +1119,7 @@ blacklist: components: - SurplusBundle + saleBlacklist: true # Tools @@ -1113,6 +1258,7 @@ - !type:BuyerJobCondition whitelist: - Chaplain + saleLimit: 1 - type: listing id: uplinkRevolverCapGunFake @@ -1128,6 +1274,7 @@ whitelist: - Mime - Clown + saleLimit: 1 - type: listing id: uplinkBananaPeelExplosive @@ -1172,6 +1319,7 @@ - !type:BuyerJobCondition whitelist: - Clown + saleLimit: 1 - type: listing id: uplinkHotPotato @@ -1241,6 +1389,24 @@ - ResearchDirector - Chef +- type: listing + id: UplinkCaneBlade + name: uplink-cane-blade-name + description: uplink-cane-blade-desc + icon: { sprite: Objects/Weapons/Melee/cane.rsi, state: cane} + productEntity: CaneSheathFilled + cost: + Telecrystal: 5 + categories: + - UplinkJob + conditions: + - !type:BuyerJobCondition + whitelist: + - Librarian + - !type:BuyerWhitelistCondition + blacklist: + components: + - SurplusBundle - type: listing id: UplinkSingarityBeacon name: uplink-singularity-beacon-name @@ -1335,6 +1501,7 @@ Telecrystal: 8 categories: - UplinkArmor + saleLimit: 1 - type: listing id: UplinkHardsuitSyndieElite @@ -1346,6 +1513,7 @@ Telecrystal: 10 categories: - UplinkArmor + saleLimit: 1 - type: listing id: UplinkClothingOuterHardsuitJuggernaut @@ -1357,39 +1525,10 @@ Telecrystal: 12 categories: - UplinkArmor + saleLimit: 1 # Misc -- type: listing - id: UplinkCyberpen - name: uplink-cyberpen-name - description: uplink-cyberpen-desc - productEntity: CyberPen - cost: - Telecrystal: 1 - categories: - - UplinkMisc - -- type: listing - id: UplinkDecoyDisk - name: uplink-decoy-disk-name - description: uplink-decoy-disk-desc - productEntity: NukeDiskFake - cost: - Telecrystal: 1 - categories: - - UplinkMisc - -- type: listing - id: UplinkCigarettes - name: uplink-cigarettes-name - description: uplink-cigarettes-desc - productEntity: CigPackSyndicate - cost: - Telecrystal: 2 - categories: - - UplinkMisc - - type: listing id: UplinkClothingConductingGloves name: uplink-clothing-conducting-gloves-name @@ -1425,6 +1564,7 @@ whitelist: tags: - NukeOpsUplink + saleLimit: 1 - type: listing id: UplinkSoapSyndie @@ -1446,16 +1586,6 @@ categories: - UplinkMisc -- type: listing - id: UplinkUltrabrightLantern - name: uplink-ultrabright-lantern-name - description: uplink-ultrabright-lantern-desc - productEntity: LanternFlash - cost: - Telecrystal: 2 - categories: - - UplinkMisc - # - type: listing # id: UplinkGigacancerScanner # name: Ultragigacancer Health Analyzer @@ -1466,66 +1596,6 @@ # categories: # - UplinkMisc -- type: listing - id: UplinkNocturineChemistryBottle - name: uplink-nocturine-chemistry-bottle-name - description: uplink-nocturine-chemistry-bottle-desc - productEntity: NocturineChemistryBottle - cost: - Telecrystal: 6 - categories: - - UplinkMisc - -- type: listing - id: UplinkCombatMedkit - name: uplink-combat-medkit-name - description: uplink-combat-medkit-desc - productEntity: MedkitCombatFilled - cost: - Telecrystal: 5 - categories: - - UplinkMisc - -- type: listing - id: UplinkCombatMedipen - name: uplink-combat-medipen-name - description: uplink-combat-medipen-desc - productEntity: CombatMedipen - cost: - Telecrystal: 4 - categories: - - UplinkMisc - -- type: listing - id: UplinkStimpack - name: uplink-stimpack-name - description: uplink-stimpack-desc - productEntity: Stimpack - cost: - Telecrystal: 4 - categories: - - UplinkMisc - -- type: listing - id: UplinkStimkit - name: uplink-stimkit-name - description: uplink-stimkit-desc - productEntity: StimkitFilled - cost: - Telecrystal: 12 - categories: - - UplinkMisc - -- type: listing - id: UplinkBribe - name: uplink-bribe-name - description: uplink-bribe-desc - productEntity: BriefcaseSyndieLobbyingBundleFilled - cost: - Telecrystal: 4 - categories: - - UplinkMisc - - type: listing id: UplinkMobCatMicrobomb name: uplink-mobcat-microbomb-name @@ -1541,6 +1611,7 @@ whitelist: tags: - NukeOpsUplink + saleLimit: 1 - type: listing id: UplinkBackpackSyndicate diff --git a/Resources/Prototypes/CharacterItemGroups/backpackGroups.yml b/Resources/Prototypes/CharacterItemGroups/backpackGroups.yml new file mode 100644 index 0000000000..c12db85146 --- /dev/null +++ b/Resources/Prototypes/CharacterItemGroups/backpackGroups.yml @@ -0,0 +1,11 @@ +- type: characterItemGroup + id: LoadoutBackpacks + items: + - type: loadout + id: LoadoutBackpack + - type: loadout + id: LoadoutBackpackDuffel + - type: loadout + id: LoadoutBackpackSatchel + - type: loadout + id: LoadoutBackpackClown diff --git a/Resources/Prototypes/CharacterItemGroups/cargoGroups.yml b/Resources/Prototypes/CharacterItemGroups/cargoGroups.yml new file mode 100644 index 0000000000..6b12652b33 --- /dev/null +++ b/Resources/Prototypes/CharacterItemGroups/cargoGroups.yml @@ -0,0 +1,19 @@ +- type: characterItemGroup + id: LoadoutOuterCargo + items: + - type: loadout + id: LoadoutCargoOuterWinterCargo + - type: loadout + id: LoadoutCargoOuterWinterMiner + +- type: characterItemGroup + id: LoadoutNeckCargo + items: + - type: loadout + id: LoadoutCargoNeckGoliathCloak + +- type: characterItemGroup + id: LoadoutShoesCargo + items: + - type: loadout + id: LoadoutCargoShoesBootsWinterCargo diff --git a/Resources/Prototypes/CharacterItemGroups/engineeringGroups.yml b/Resources/Prototypes/CharacterItemGroups/engineeringGroups.yml new file mode 100644 index 0000000000..6db873cecf --- /dev/null +++ b/Resources/Prototypes/CharacterItemGroups/engineeringGroups.yml @@ -0,0 +1,39 @@ +- type: characterItemGroup + id: LoadoutUniformsEngineering + items: + - type: loadout + id: LoadoutEngineeringUniformHazard + - type: loadout + id: LoadoutEngineeringUniformJumpskirtSenior + - type: loadout + id: LoadoutEngineeringUniformJumpsuitSenior + +- type: characterItemGroup + id: LoadoutOuterEngineering + items: + - type: loadout + id: LoadoutEngineeringOuterHazard + - type: loadout + id: LoadoutEngineeringChickenSuit + +- type: characterItemGroup + id: LoadoutHeadEngineering + items: + - type: loadout + id: LoadoutEngineeringHeadBeret + - type: loadout + id: LoadoutEngineeringHeadHardhatBlue + - type: loadout + id: LoadoutEngineeringHeadHardhatOrange + - type: loadout + id: LoadoutEngineeringHeadHardhatRed + - type: loadout + id: LoadoutEngineeringHeadHardhatWhite + - type: loadout + id: LoadoutEngineeringHeadHardhatYellow + +- type: characterItemGroup + id: LoadoutEyesEngineering + items: + - type: loadout + id: LoadoutEngineeringEyesMeson \ No newline at end of file diff --git a/Resources/Prototypes/CharacterItemGroups/eyesGroup.yml b/Resources/Prototypes/CharacterItemGroups/eyesGroup.yml new file mode 100644 index 0000000000..1f5a2420a9 --- /dev/null +++ b/Resources/Prototypes/CharacterItemGroups/eyesGroup.yml @@ -0,0 +1,19 @@ +- type: characterItemGroup + id: LoadoutEyes + items: + - type: loadout + id: LoadoutEyesEyepatch + - type: loadout + id: LoadoutEyesGlasses + - type: loadout + id: LoadoutEyesGlassesJamjar + - type: loadout + id: LoadoutEyesGlassesJensen + - type: loadout + id: LoadoutEyesBlindfold + - type: loadout + id: LoadoutItemCheapSunglasses + - type: loadout + id: LoadoutItemSunglasses + - type: loadout + id: LoadoutItemBlindfoldFake diff --git a/Resources/Prototypes/CharacterItemGroups/gloveGroup.yml b/Resources/Prototypes/CharacterItemGroups/gloveGroup.yml new file mode 100644 index 0000000000..e8ec6f3d25 --- /dev/null +++ b/Resources/Prototypes/CharacterItemGroups/gloveGroup.yml @@ -0,0 +1,33 @@ +- type: characterItemGroup + id: LoadoutGloves + items: + - type: loadout + id: LoadoutHandsColorPurple + - type: loadout + id: LoadoutHandsColorRed + - type: loadout + id: LoadoutHandsColorBlack + - type: loadout + id: LoadoutHandsColorBlue + - type: loadout + id: LoadoutHandsColorBrown + - type: loadout + id: LoadoutHandsColorGray + - type: loadout + id: LoadoutHandsColorGreen + - type: loadout + id: LoadoutHandsColorLightBrown + - type: loadout + id: LoadoutHandsColorOrange + - type: loadout + id: LoadoutHandsColorWhite + - type: loadout + id: LoadoutHandsColorYellowBudget + - type: loadout + id: LoadoutHandsGlovesLeather + - type: loadout + id: LoadoutHandsGlovesPowerglove + - type: loadout + id: LoadoutHandsGlovesRobohands + - type: loadout + id: LoadoutHandsGlovesFingerless diff --git a/Resources/Prototypes/CharacterItemGroups/headGroup.yml b/Resources/Prototypes/CharacterItemGroups/headGroup.yml new file mode 100644 index 0000000000..ee4485757b --- /dev/null +++ b/Resources/Prototypes/CharacterItemGroups/headGroup.yml @@ -0,0 +1,119 @@ +- type: characterItemGroup + id: LoadoutHead + items: + - type: loadout + id: LoadoutHeadBeaverHat + - type: loadout + id: LoadoutHeadTophat + - type: loadout + id: LoadoutHeadFedoraBlack + - type: loadout + id: LoadoutHeadFedoraBrown + - type: loadout + id: LoadoutHeadFedoraGrey + - type: loadout + id: LoadoutHeadFedoraChoc + - type: loadout + id: LoadoutHeadFedoraWhite + - type: loadout + id: LoadoutHeadFlatBlack + - type: loadout + id: LoadoutHeadFlatBrown + - type: loadout + id: LoadoutHeadHatCowboyBrown + - type: loadout + id: LoadoutHeadHatCowboyBlack + - type: loadout + id: LoadoutHeadHatCowboyGrey + - type: loadout + id: LoadoutHeadHatCowboyRed + - type: loadout + id: LoadoutHeadHatCowboyWhite + - type: loadout + id: LoadoutHeadHatCowboyBountyHunter + - type: loadout + id: LoadoutHeadTinfoil + - type: loadout + id: LoadoutHeadBellhop + - type: loadout + id: LoadoutHeadPoppy + - type: loadout + id: LoadoutHeadHatBluesoft + - type: loadout + id: LoadoutHeadHatBluesoftFlipped + - type: loadout + id: LoadoutHeadHatCorpsoft + - type: loadout + id: LoadoutHeadHatCorpsoftFlipped + - type: loadout + id: LoadoutHeadHatGreensoft + - type: loadout + id: LoadoutHeadHatGreensoftFlipped + - type: loadout + id: LoadoutHeadHatGreysoft + - type: loadout + id: LoadoutHeadHatGreysoftFlipped + - type: loadout + id: LoadoutHeadHatMimesoft + - type: loadout + id: LoadoutHeadHatMimesoftFlipped + - type: loadout + id: LoadoutHeadHatOrangesoft + - type: loadout + id: LoadoutHeadHatOrangesoftFlipped + - type: loadout + id: LoadoutHeadHatPurplesoft + - type: loadout + id: LoadoutHeadHatPurplesoftFlipped + - type: loadout + id: LoadoutHeadHatRedsoft + - type: loadout + id: LoadoutHeadHatRedsoftFlipped + - type: loadout + id: LoadoutHeadHatYellowsoft + - type: loadout + id: LoadoutHeadHatYellowsoftFlipped + - type: loadout + id: LoadoutHeadBandBlack + - type: loadout + id: LoadoutHeadBandBlue + - type: loadout + id: LoadoutHeadBandGold + - type: loadout + id: LoadoutHeadBandGreen + - type: loadout + id: LoadoutHeadBandGrey + - type: loadout + id: LoadoutHeadBandRed + - type: loadout + id: LoadoutHeadBandSkull + - type: loadout + id: LoadoutHeadBandMerc + - type: loadout + id: LoadoutHeadBandBrown + - type: loadout + id: LoadoutHeadFishCap + - type: loadout + id: LoadoutHeadRastaHat + - type: loadout + id: LoadoutHeadFez + - type: loadout + id: LoadoutHeadBowlerHat + - type: loadout + id: LoadoutHeadGreyFlatcap + - type: loadout + id: LoadoutHeadBrownFlatcap + - type: loadout + id: LoadoutHeadBeret + - type: loadout + id: LoadoutHeadBeretFrench + - type: loadout + id: LoadoutHeadCowboyBrown + - type: loadout + id: LoadoutHeadCowboyBlack + - type: loadout + id: LoadoutHeadCowboyWhite + - type: loadout + id: LoadoutHeadCowboyGrey + - type: loadout + id: LoadoutHeadCowboyRed diff --git a/Resources/Prototypes/CharacterItemGroups/itemGroups.yml b/Resources/Prototypes/CharacterItemGroups/itemGroups.yml new file mode 100644 index 0000000000..66b7d32ea7 --- /dev/null +++ b/Resources/Prototypes/CharacterItemGroups/itemGroups.yml @@ -0,0 +1,193 @@ +# Alphabetically ordered +# Please do not flood this one file with groups, make more files to sort them + +- type: characterItemGroup + id: LoadoutUniformsCivilian + maxItems: 1 # 1 is the default, here for clarity + items: + - type: loadout + id: LoadoutUniformAncientJumpsuit + - type: loadout + id: LoadoutUniformJumpsuitColorBlack + - type: loadout + id: LoadoutUniformJumpskirtColorBlack + - type: loadout + id: LoadoutUniformJumpsuitColorBlue + - type: loadout + id: LoadoutUniformJumpskirtColorBlue + - type: loadout + id: LoadoutUniformJumpsuitColorBrown + - type: loadout + id: LoadoutUniformJumpskirtColorBrown + - type: loadout + id: LoadoutUniformJumpsuitColorDarkBlue + - type: loadout + id: LoadoutUniformJumpskirtColorDarkBlue + - type: loadout + id: LoadoutUniformJumpsuitColorDarkGreen + - type: loadout + id: LoadoutUniformJumpskirtColorDarkGreen + - type: loadout + id: LoadoutUniformJumpsuitColorGreen + - type: loadout + id: LoadoutUniformJumpskirtColorGreen + - type: loadout + id: LoadoutUniformJumpsuitColorLightBrown + - type: loadout + id: LoadoutUniformJumpskirtColorLightBrown + - type: loadout + id: LoadoutUniformJumpsuitColorMaroon + - type: loadout + id: LoadoutUniformJumpskirtColorMaroon + - type: loadout + id: LoadoutUniformJumpsuitColorOrange + - type: loadout + id: LoadoutUniformJumpskirtColorOrange + - type: loadout + id: LoadoutUniformJumpsuitColorPink + - type: loadout + id: LoadoutUniformJumpskirtColorPink + - type: loadout + id: LoadoutUniformJumpsuitColorPurple + - type: loadout + id: LoadoutUniformJumpskirtColorPurple + - type: loadout + id: LoadoutUniformJumpsuitColorRed + - type: loadout + id: LoadoutUniformJumpskirtColorRed + - type: loadout + id: LoadoutUniformJumpsuitColorTeal + - type: loadout + id: LoadoutUniformJumpskirtColorTeal + - type: loadout + id: LoadoutUniformJumpsuitColorWhite + - type: loadout + id: LoadoutUniformJumpskirtColorWhite + - type: loadout + id: LoadoutUniformJumpsuitColorYellow + - type: loadout + id: LoadoutUniformJumpskirtColorYellow + - type: loadout + id: LoadoutUniformJumpsuitFlannel + - type: loadout + id: LoadoutUniformJumpskirtCasualBlue + - type: loadout + id: LoadoutUniformJumpsuitCasualPurple + - type: loadout + id: LoadoutUniformJumpskirtCasualPurple + - type: loadout + id: LoadoutUniformJumpsuitCasualRed + - type: loadout + id: LoadoutUniformJumpskirtCasualRed + - type: loadout + id: LoadoutUniformJumpsuitTshirtJeans + - type: loadout + id: LoadoutUniformJumpsuitTshirtJeansGray + - type: loadout + id: LoadoutUniformJumpsuitTshirtJeansPeach + - type: loadout + id: LoadoutUniformJumpsuitJeansGreen + - type: loadout + id: LoadoutUniformJumpsuitJeansRed + - type: loadout + id: LoadoutUniformJumpsuitJeansBrown + - type: loadout + id: LoadoutUniformJumpsuitLostTourist + - type: loadout + id: LoadoutUniformJumpsuitHawaiBlack + - type: loadout + id: LoadoutUniformJumpsuitHawaiBlue + - type: loadout + id: LoadoutUniformJumpsuitHawaiRed + - type: loadout + id: LoadoutUniformJumpsuitHawaiYellow + - type: loadout + id: LoadoutUniformDressRed + - type: loadout + id: LoadoutUniformJumpsuitSober + - type: loadout + id: LoadoutUniformSkirtTurtle + - type: loadout + id: LoadoutUniformGeisha + - type: loadout + id: LoadoutUniformCostumeArcDress + - type: loadout + id: LoadoutUniformCostumeMioSkirt + - type: loadout + id: LoadoutUniformCostumeNaota + - type: loadout + id: LoadoutUniformJumpsuitLoungewear + - type: loadout + id: LoadoutUniformJumpsuitBartender + - type: loadout + id: LoadoutUniformJumpskirtBartender + - type: loadout + id: LoadoutUniformKendoHakama + - type: loadout + id: LoadoutUniformMartialGi + - type: loadout + id: LoadoutClothingJumpsuitKimono + - type: loadout + id: LoadoutClothingKimonoBlue + - type: loadout + id: LoadoutClothingKimonoPink + - type: loadout + id: LoadoutClothingKimonoPurple + - type: loadout + id: LoadoutClothingKimonoSky + - type: loadout + id: LoadoutClothingKimonoGreen + - type: loadout + id: LoadoutUniformSchoolGakuranBlack + - type: loadout + id: LoadoutUniformSchoolgirlBlack + - type: loadout + id: LoadoutUniformSchoolgirlBlue + - type: loadout + id: LoadoutUniformSchoolgirlCyan + - type: loadout + id: LoadoutUniformSchoolgirlGreen + - type: loadout + id: LoadoutUniformSchoolgirlOrange + - type: loadout + id: LoadoutUniformSchoolgirlPink + - type: loadout + id: LoadoutUniformSchoolgirlPurple + - type: loadout + id: LoadoutUniformSchoolgirlRed + - type: loadout + id: LoadoutUniformSchoolgirlDusk + - type: loadout + id: LoadoutUniformSchoolgirlBlazerTan + - type: loadout + id: LoadoutClothingMNKOfficeSkirt + - type: loadout + id: LoadoutClothingMNKUnderGarment + - type: loadout + id: LoadoutClothingMNKGymBra + - type: loadout + id: LoadoutClothingMNKDressBlack + - type: loadout + id: LoadoutClothingMNKBlackOveralls + - type: loadout + id: LoadoutClothingMNKBlackShoulder + - type: loadout + id: LoadoutClothingMNKTracksuitBlack + - type: loadout + id: LoadoutClothingJumpsuitSuitBlack + - type: loadout + id: LoadoutClothingJumpsuitSuitBlackAlt + - type: loadout + id: LoadoutClothingJumpsuitSuitBlackMob + - type: loadout + id: LoadoutClothingJumpsuitSuitBrown + - type: loadout + id: LoadoutClothingJumpsuitSuitBrownAlt + - type: loadout + id: LoadoutClothingJumpsuitSuitBrownMob + - type: loadout + id: LoadoutClothingJumpsuitSuitWhite + - type: loadout + id: LoadoutClothingJumpsuitSuitWhiteAlt + - type: loadout + id: LoadoutClothingJumpsuitSuitWhiteMob diff --git a/Resources/Prototypes/CharacterItemGroups/languageGroups.yml b/Resources/Prototypes/CharacterItemGroups/languageGroups.yml new file mode 100644 index 0000000000..a884992a23 --- /dev/null +++ b/Resources/Prototypes/CharacterItemGroups/languageGroups.yml @@ -0,0 +1,35 @@ +- type: characterItemGroup + id: TraitsLanguagesBasic + maxItems: 1 + items: + - type: trait + id: SignLanguage + - type: trait + id: SolCommon + - type: trait + id: Tradeband + - type: trait + id: Freespeak + - type: trait + id: Elyran + - type: trait + id: ValyrianStandard + - type: trait + id: Azaziba + +- type: characterItemGroup + id: TraitsAccents + maxItems: 1 + items: + - type: trait + id: FrontalLisp + - type: trait + id: Stutter + - type: trait + id: PirateAccent + - type: trait + id: Accentless + - type: trait + id: Southern + - type: trait + id: ScottishAccent \ No newline at end of file diff --git a/Resources/Prototypes/CharacterItemGroups/maskGroup.yml b/Resources/Prototypes/CharacterItemGroups/maskGroup.yml new file mode 100644 index 0000000000..ffe37fc042 --- /dev/null +++ b/Resources/Prototypes/CharacterItemGroups/maskGroup.yml @@ -0,0 +1,31 @@ +- type: characterItemGroup + id: LoadoutMasks + items: + - type: loadout + id: LoadoutMaskSterile + - type: loadout + id: LoadoutMaskMuzzle + - type: loadout + id: LoadoutMaskGas + - type: loadout + id: LoadoutMaskBandBlack + - type: loadout + id: LoadoutMaskBandBlue + - type: loadout + id: LoadoutMaskBandGold + - type: loadout + id: LoadoutMaskBandGreen + - type: loadout + id: LoadoutMaskBandGrey + - type: loadout + id: LoadoutMaskBandRed + - type: loadout + id: LoadoutMaskBandSkull + - type: loadout + id: LoadoutMaskBandMerc + - type: loadout + id: LoadoutMaskBandBrown + - type: loadout + id: LoadoutMaskNeckGaiter + - type: loadout + id: LoadoutMaskNeckGaiterRed diff --git a/Resources/Prototypes/CharacterItemGroups/medicalGroups.yml b/Resources/Prototypes/CharacterItemGroups/medicalGroups.yml new file mode 100644 index 0000000000..396f07290f --- /dev/null +++ b/Resources/Prototypes/CharacterItemGroups/medicalGroups.yml @@ -0,0 +1,101 @@ +- type: characterItemGroup + id: LoadoutUniformsMedical + items: + - type: loadout + id: LoadoutMedicalUniformScrubsBlue + - type: loadout + id: LoadoutMedicalUniformScrubsGreen + - type: loadout + id: LoadoutMedicalUniformScrubsPurple + - type: loadout + id: LoadoutMedicalUniformScrubsCyan + - type: loadout + id: LoadoutMedicalUniformScrubsBlack + - type: loadout + id: LoadoutMedicalUniformScrubsPink + - type: loadout + id: LoadoutMedicalUniformScrubsCybersun + - type: loadout + id: LoadoutMedicalUniformParamedicJumpsuit + - type: loadout + id: LoadoutMedicalUniformParamedicJumpskirt + - type: loadout + id: LoadoutMedicalUniformJumpskirtSenior + - type: loadout + id: LoadoutMedicalUniformJumpsuitSenior + - type: loadout + id: LoadoutMedicalUniformJumpsuitChemShirt + +- type: characterItemGroup + id: LoadoutOuterMedical + items: + - type: loadout + id: LoadoutMedicalOuterLabcoat + - type: loadout + id: LoadoutMedicalOuterCybersunWindbreaker + - type: loadout + id: LoadoutMedicalOuterLabcoatChem + - type: loadout + id: LoadoutMedicalOuterApronChemist + +- type: characterItemGroup + id: LoadoutGlovesMedical + items: + - type: loadout + id: LoadoutMedicalGlovesNitrile + - type: loadout + id: LoadoutMedicalHandsGlovesChemist + +- type: characterItemGroup + id: LoadoutNeckMedical + items: + - type: loadout + id: LoadoutMedicalNeckStethoscope + - type: loadout + id: LoadoutMedicalBedsheetMedical + - type: loadout + id: LoadoutMedicalNeckTieChem + +- type: characterItemGroup + id: LoadoutHeadMedical + items: + - type: loadout + id: LoadoutMedicalHeadNurse + - type: loadout + id: LoadoutMedicalHeadBeretSeniorPhysician + - type: loadout + id: LoadoutMedicalHeadSurgcapBlue + - type: loadout + id: LoadoutMedicalHeadSurgcapPurple + - type: loadout + id: LoadoutMedicalHeadSurgcapGreen + - type: loadout + id: LoadoutMedicalHeadSurgcapCyan + - type: loadout + id: LoadoutMedicalHeadSurgcapBlack + - type: loadout + id: LoadoutMedicalHeadSurgcapPink + - type: loadout + id: LoadoutMedicalHeadSurgcapWhite + - type: loadout + id: LoadoutMedicalHeadSurgcapCybersun + +- type: characterItemGroup + id: LoadoutEyesMedical + items: + - type: loadout + id: LoadoutMedicalEyesHudMedical + - type: loadout + id: LoadoutMedicalEyesEyepatchHudMedical + - type: loadout + id: LoadoutMedicalEyesHudMedicalPrescription + - type: loadout + id: LoadoutMedicalEyesGlassesChemical + - type: loadout + id: LoadoutMedicalEyesGlassesChemist + +- type: characterItemGroup + id: LoadoutShoesMedical + items: + - type: loadout + id: LoadoutMedicalShoesEnclosedChem diff --git a/Resources/Prototypes/CharacterItemGroups/miscItemGroups.yml b/Resources/Prototypes/CharacterItemGroups/miscItemGroups.yml new file mode 100644 index 0000000000..3cad4423f5 --- /dev/null +++ b/Resources/Prototypes/CharacterItemGroups/miscItemGroups.yml @@ -0,0 +1,123 @@ +- type: characterItemGroup + id: LoadoutSmokes + items: + - type: loadout + id: LoadoutItemCig + - type: loadout + id: LoadoutItemCigsGreen + - type: loadout + id: LoadoutItemCigsRed + - type: loadout + id: LoadoutItemCigsBlue + - type: loadout + id: LoadoutItemCigsBlack + - type: loadout + id: LoadoutItemCigsMixed + - type: loadout + id: LoadoutItemCigsSyndicate + - type: loadout + id: LoadoutItemSmokingPipeFilledTobacco + - type: loadout + id: LoadoutItemBlunt + - type: loadout + id: LoadoutItemJoint + +- type: characterItemGroup + id: LoadoutLighters + items: + - type: loadout + id: LoadoutItemLighter + - type: loadout + id: LoadoutItemLighterCheap + - type: loadout + id: LoadoutItemLighterFlippo + +- type: characterItemGroup + id: LoadoutInstrumentsAny + maxItems: 2 + items: + - type: loadout + id: LoadoutItemMicrophoneInstrument + - type: loadout + id: LoadoutItemKalimbaInstrument + - type: loadout + id: LoadoutItemTrumpetInstrument + - type: loadout + id: LoadoutItemElectricGuitar + - type: loadout + id: LoadoutItemBassGuitar + - type: loadout + id: LoadoutItemRockGuitar + - type: loadout + id: LoadoutItemAcousticGuitar + - type: loadout + id: LoadoutItemViolin + - type: loadout + id: LoadoutItemHarmonica + - type: loadout + id: LoadoutItemAccordion + - type: loadout + id: LoadoutItemFlute + - type: loadout + id: LoadoutItemOcarina + +- type: characterItemGroup + id: LoadoutAirTank + items: + - type: loadout + id: LoadoutItemsEmergencyOxygenTank + - type: loadout + id: LoadoutItemsExtendedEmergencyOxygenTank + - type: loadout + id: LoadoutItemsDoubleEmergencyOxygenTank + - type: loadout + id: LoadoutSpeciesEmergencyNitrogenTank + - type: loadout + id: LoadoutSpeciesExtendedEmergencyNitrogenTank + - type: loadout + id: LoadoutSpeciesDoubleEmergencyNitrogenTank + +- type: characterItemGroup + id: LoadoutBoxKits + items: + - type: loadout + id: LoadoutItemBoxSurvival + - type: loadout + id: LoadoutItemBoxSurvivalEngineering + - type: loadout + id: LoadoutItemBoxSurvivalSecurity + - type: loadout + id: LoadoutItemBoxSurvivalBrigmedic + - type: loadout + id: LoadoutItemBoxSurvivalMedical + - type: loadout + id: LoadoutItemBoxHug + - type: loadout + id: LoadoutMedkitFilled + - type: loadout + id: LoadoutMedkitBruteFilled + - type: loadout + id: LoadoutMedkitBurnFilled + - type: loadout + id: LoadoutMedkitToxinFilled + - type: loadout + id: LoadoutMedkitOxygenFilled + - type: loadout + id: LoadoutMedkitRadiationFilled + - type: loadout + id: LoadoutMedkitAdvancedFilled + - type: loadout + id: LoadoutMedkitCombatFilled + +- type: characterItemGroup + id: LoadoutWritables + maxItems: 2 + items: + - type: loadout + id: LoadoutItemPapers + - type: loadout + id: LoadoutItemBoxFolderGrey + - type: loadout + id: LoadoutBookRandom + - type: loadout + id: LoadoutPen diff --git a/Resources/Prototypes/CharacterItemGroups/musicianInstrumentsGroups.yml b/Resources/Prototypes/CharacterItemGroups/musicianInstrumentsGroups.yml new file mode 100644 index 0000000000..eac816b8db --- /dev/null +++ b/Resources/Prototypes/CharacterItemGroups/musicianInstrumentsGroups.yml @@ -0,0 +1,94 @@ +- type: characterItemGroup + id: LoadoutMusicianInstruments + maxItems: 3 + items: + # Brass + - type: loadout + id: LoadoutItemTrumpetInstrumentMusician + - type: loadout + id: LoadoutItemTromboneInstrumentMusician + - type: loadout + id: LoadoutItemFrenchHornInstrumentMusician + - type: loadout + id: LoadoutItemEuphoniumInstrumentMusician + # Misc + - type: loadout + id: LoadoutItemSeashellInstrumentMusician + - type: loadout + id: LoadoutItemBirdToyInstrumentMusician + # Percussion + - type: loadout + id: LoadoutItemGlockenspielInstrumentMusician + - type: loadout + id: LoadoutItemMusicBoxInstrumentMusician + - type: loadout + id: LoadoutItemXylophoneInstrumentMusician + - type: loadout + id: LoadoutItemMicrophoneInstrumentMusician + - type: loadout + id: LoadoutItemSynthesizerInstrumentMusician + - type: loadout + id: LoadoutItemKalimbaInstrumentMusician + - type: loadout + id: LoadoutItemWoodblockInstrumentMusician + # String + - type: loadout + id: LoadoutItemElectricGuitarInstrumentMusician + - type: loadout + id: LoadoutItemBassGuitarInstrumentMusician + - type: loadout + id: LoadoutItemRockGuitarInstrumentMusician + - type: loadout + id: LoadoutItemAcousticGuitarInstrumentMusician + - type: loadout + id: LoadoutItemBanjoInstrumentMusician + - type: loadout + id: LoadoutItemViolinInstrumentMusician + - type: loadout + id: LoadoutItemViolaInstrumentMusician + - type: loadout + id: LoadoutItemCelloInstrumentMusician + # Structure + - type: loadout + id: LoadoutItemPianoInstrumentMusician + - type: loadout + id: LoadoutItemUprightPianoInstrumentMusician + - type: loadout + id: LoadoutItemVibraphoneInstrumentMusician + - type: loadout + id: LoadoutItemMarimbaInstrumentMusician + - type: loadout + id: LoadoutItemChurchOrganInstrumentMusician + - type: loadout + id: LoadoutItemTubaInstrumentMusician + - type: loadout + id: LoadoutItemHarpInstrumentMusician + - type: loadout + id: LoadoutItemTimpaniInstrumentMusician + - type: loadout + id: LoadoutItemTaikoInstrumentMusician + - type: loadout + id: LoadoutItemContrabassInstrumentMusician + - type: loadout + id: LoadoutItemMinimoogInstrumentMusician + - type: loadout + id: LoadoutItemTomDrumsInstrumentMusician + # Wind + - type: loadout + id: LoadoutItemSaxophoneInstrumentMusician + - type: loadout + id: LoadoutItemAccordionInstrumentMusician + - type: loadout + id: LoadoutItemHarmonicaInstrumentMusician + - type: loadout + id: LoadoutItemClarinetInstrumentMusician + - type: loadout + id: LoadoutItemFluteInstrumentMusician + - type: loadout + id: LoadoutItemRecorderInstrumentMusician + - type: loadout + id: LoadoutItemPanFluteInstrumentMusician + - type: loadout + id: LoadoutItemOcarinaInstrumentMusician + - type: loadout + id: LoadoutItemBagpipeInstrumentMusician diff --git a/Resources/Prototypes/CharacterItemGroups/neckGroup.yml b/Resources/Prototypes/CharacterItemGroups/neckGroup.yml new file mode 100644 index 0000000000..2a7add033e --- /dev/null +++ b/Resources/Prototypes/CharacterItemGroups/neckGroup.yml @@ -0,0 +1,81 @@ +- type: characterItemGroup + id: LoadoutNeck + items: + - type: loadout + id: LoadoutNeckHeadphones + - type: loadout + id: LoadoutNeckBellCollar + - type: loadout + id: LoadoutNeckOldMantle + - type: loadout + id: LoadoutNeckUnathiMantle + - type: loadout + id: LoadoutNeckScarfStripedRed + - type: loadout + id: LoadoutNeckScarfStripedBlue + - type: loadout + id: LoadoutNeckScarfStripedGreen + - type: loadout + id: LoadoutNeckScarfStripedBlack + - type: loadout + id: LoadoutNeckScarfStripedBrown + - type: loadout + id: LoadoutNeckScarfStripedOrange + - type: loadout + id: LoadoutNeckScarfStripedPurple + - type: loadout + id: LoadoutNeckScarfStripedZebra + - type: loadout + id: LoadoutNeckTieRed + - type: loadout + id: LoadoutNeckTieWhite + - type: loadout + id: LoadoutNeckTieBlack + - type: loadout + id: LoadoutNeckTieBlue + - type: loadout + id: LoadoutNeckTieGreen + - type: loadout + id: LoadoutItemsPrideLGBTPin + - type: loadout + id: LoadoutItemsPrideAromanticPin + - type: loadout + id: LoadoutItemsPrideAsexualPin + - type: loadout + id: LoadoutItemsPrideBisexualPin + - type: loadout + id: LoadoutItemsPrideIntersexPin + - type: loadout + id: LoadoutItemsPrideLesbianPin + - type: loadout + id: LoadoutItemsPrideNonBinaryPin + - type: loadout + id: LoadoutItemsPridePansexualPin + - type: loadout + id: LoadoutItemsPrideTransPin + - type: loadout + id: LoadoutNeckBedsheetBlack + - type: loadout + id: LoadoutNeckBedsheetBlue + - type: loadout + id: LoadoutNeckBedsheetBrown + - type: loadout + id: LoadoutNeckBedsheetCosmos + - type: loadout + id: LoadoutNeckBedsheetGreen + - type: loadout + id: LoadoutNeckBedsheetGrey + - type: loadout + id: LoadoutNeckBedsheetOrange + - type: loadout + id: LoadoutNeckBedsheetPurple + - type: loadout + id: LoadoutNeckBedsheetRainbow + - type: loadout + id: LoadoutNeckBedsheetRed + - type: loadout + id: LoadoutNeckBedsheetWhite + - type: loadout + id: LoadoutNeckBedsheetYellow + - type: loadout + id: LoadoutNeckBedsheetNT diff --git a/Resources/Prototypes/CharacterItemGroups/outerwearGroup.yml b/Resources/Prototypes/CharacterItemGroups/outerwearGroup.yml new file mode 100644 index 0000000000..50eb4dc8be --- /dev/null +++ b/Resources/Prototypes/CharacterItemGroups/outerwearGroup.yml @@ -0,0 +1,71 @@ +- type: characterItemGroup + id: LoadoutOuter + items: + - type: loadout + id: LoadoutOuterCoatBomberjacket + - type: loadout + id: LoadoutOuterCoatHoodieBlack + - type: loadout + id: LoadoutOuterCoatHoodieGrey + - type: loadout + id: LoadoutOuterCoatWinterCoat + - type: loadout + id: LoadoutOuterCoatHyenhSweater + - type: loadout + id: LoadoutOuterWinterCoatLong + - type: loadout + id: LoadoutOuterWinterCoatPlaid + - type: loadout + id: LoadoutOuterVestValet + - type: loadout + id: LoadoutOuterVest + - type: loadout + id: LoadoutOuterCoatLettermanBlue + - type: loadout + id: LoadoutOuterCoatLettermanRed + - type: loadout + id: LoadoutOuterCoatMNKWhiteHoodie + - type: loadout + id: LoadoutOuterCoatMNKBlackTopCoat + - type: loadout + id: LoadoutOuterCoatMNKBlackJacket + - type: loadout + id: LoadoutOuterCorporateJacket + - type: loadout + id: LoadoutOuterCsCorporateJacket + - type: loadout + id: LoadoutOuterEeCorporateJacket + - type: loadout + id: LoadoutOuterHiCorporateJacket + - type: loadout + id: LoadoutOuterHmCorporateJacket + - type: loadout + id: LoadoutOuterIdCorporateJacket + - type: loadout + id: LoadoutOuterBcCorporateJacket + - type: loadout + id: LoadoutOuterDdCorporateJacket + - type: loadout + id: LoadoutOuterFaCorporateJacket + - type: loadout + id: LoadoutOuterGeCorporateJacket + - type: loadout + id: LoadoutOuterZhCorporateJacket + - type: loadout + id: LoadoutOuterDenimJacket + - type: loadout + id: LoadoutOuterFlannelRed + - type: loadout + id: LoadoutOuterFlannelGreen + - type: loadout + id: LoadoutOuterFlannelBlue + - type: loadout + id: LoadoutOuterCoatTrench + - type: loadout + id: LoadoutOuterCoatJensen + - type: loadout + id: LoadoutOuterCoatGentle + - type: loadout + id: LoadoutOuterCoatInspector + - type: loadout + id: LoadoutOuterCoatOvercoat diff --git a/Resources/Prototypes/CharacterItemGroups/scienceGroups.yml b/Resources/Prototypes/CharacterItemGroups/scienceGroups.yml new file mode 100644 index 0000000000..26cb07dae9 --- /dev/null +++ b/Resources/Prototypes/CharacterItemGroups/scienceGroups.yml @@ -0,0 +1,144 @@ +- type: characterItemGroup + id: LoadoutUniformsScience + items: + - type: loadout + id: LoadoutScienceUniformJumpskirtSenior + - type: loadout + id: LoadoutScienceUniformJumpsuitSenior + - type: loadout + id: LoadoutScienceUniformJumpskirtRoboticist + - type: loadout + id: LoadoutScienceUniformJumpsuitRoboticist + - type: loadout + id: LoadoutScienceUniformJumpsuitMonasticRobeDark + - type: loadout + id: LoadoutScienceUniformJumpsuitMonasticRobeLight + +- type: characterItemGroup + id: LoadoutOuterScience + items: + - type: loadout + id: LoadoutScienceOuterCoat + - type: loadout + id: LoadoutScienceOuterLabcoat + - type: loadout + id: LoadoutSciencegOuterCoatRobo + - type: loadout + id: LoadoutScienceOuterWinterSci + - type: loadout + id: LoadoutScienceOuterLabcoatSeniorResearcher + - type: loadout + id: LoadoutScienceOuterExplorerLabcoat + - type: loadout + id: LoadoutOuterRobeTechPriest + - type: loadout + id: LoadoutOuterPlagueSuit + - type: loadout + id: LoadoutOuterNunRobe + - type: loadout + id: LoadoutOuterHoodieBlack + - type: loadout + id: LoadoutOuterHoodieChaplain + - type: loadout + id: LoadoutScienceOuterWinterCoatMantis + +- type: characterItemGroup + id: LoadoutGlovesScience + items: + - type: loadout + id: LoadoutScienceHandsGlovesColorPurple + - type: loadout + id: LoadoutScienceHandsGlovesLatex + - type: loadout + id: LoadoutScienceHandsGlovesRobohands + +- type: characterItemGroup + id: LoadoutNeckScience + items: + - type: loadout + id: LoadoutScienceNeckTieSci + - type: loadout + id: LoadoutScienceNeckScarfStripedPurple + - type: loadout + id: LoadoutScienceNeckStoleChaplain + - type: loadout + id: LoadoutScienceNeckScarfStripedBlack + +- type: characterItemGroup + id: LoadoutMaskScience + items: + - type: loadout + id: LoadoutScienceMaskPlague + +- type: characterItemGroup + id: LoadoutHeadScience + items: + - type: loadout + id: LoadoutScienceHeadHatBeret + - type: loadout + id: LoadoutHeadHoodTechPriest + - type: loadout + id: LoadoutScienceHeadHatFez + - type: loadout + id: LoadoutScienceHeadHatHoodNunHood + - type: loadout + id: LoadoutScienceHeadHatPlaguedoctor + - type: loadout + id: LoadoutScienceHeadHatWitch + - type: loadout + id: LoadoutScienceHeadHatWitch1 + +- type: characterItemGroup + id: LoadoutEyesScience + items: + - type: loadout + id: LoadoutScienceEyesHudDiagnostic + - type: loadout + id: LoadoutScienceEyesEyepatchHudDiag + +- type: characterItemGroup + id: LoadoutShoesScience + items: + - type: loadout + id: LoadoutScienceShoesBootsWinterSci + +# Cataloguer +- type: characterItemGroup + id: LoadoutCataloguerUniforms + items: + - type: loadout + id: LoadoutScienceJumpsuitLibrarianNt + - type: loadout + id: LoadoutScienceJumpsuitLibrarianIdris + - type: loadout + id: LoadoutScienceJumpsuitLibrarianOrion + - type: loadout + id: LoadoutScienceJumpsuitLibrarianHeph + - type: loadout + id: LoadoutScienceJumpsuitLibrarianPMCG + - type: loadout + id: LoadoutScienceJumpsuitLibrarianZav + - type: loadout + id: LoadoutScienceJumpsuitLibrarianZeng + - type: loadout + id: LoadoutScienceJumpsuitLibrarian + - type: loadout + id: LoadoutScienceJumpskirtLibrarian + +# Chaplain +- type: characterItemGroup + id: LoadoutChaplainUniforms + items: + - type: loadout + id: LoadoutChaplainJumpsuit + - type: loadout + id: LoadoutChaplainJumpskirt + +- type: characterItemGroup + id: LoadoutChaplainEquipment + maxItems: 2 + items: + - type: loadout + id: LoadoutChaplainBible + - type: loadout + id: LoadoutChaplainStamp diff --git a/Resources/Prototypes/CharacterItemGroups/securityGroups.yml b/Resources/Prototypes/CharacterItemGroups/securityGroups.yml new file mode 100644 index 0000000000..8b9f6f1e0e --- /dev/null +++ b/Resources/Prototypes/CharacterItemGroups/securityGroups.yml @@ -0,0 +1,251 @@ +- type: characterItemGroup + id: LoadoutUniformsSecurity + items: + - type: loadout + id: LoadoutSecurityUniformJumpsuitBlue + - type: loadout + id: LoadoutSecurityUniformJumpskirtBlue + - type: loadout + id: LoadoutSecurityUniformJumpsuitGrey + - type: loadout + id: LoadoutSecurityUniformJumpskirtGrey + - type: loadout + id: LoadoutSecurityUniformJumpsuitSenior + - type: loadout + id: LoadoutSecurityUniformJumpskirtSenior + - type: loadout + id: LoadoutSecurityUniformJumpsuitWardenBlue + - type: loadout + id: LoadoutSecurityUniformJumpskirtWardenBlue + - type: loadout + id: LoadoutSecurityUniformJumpsuitWardenGrey + - type: loadout + id: LoadoutSecurityUniformJumpskirtWardenGrey + - type: loadout + id: LoadoutSecurityUniformJumpsuitHoSBlue + - type: loadout + id: LoadoutSecurityUniformJumpskirtHoSBlue + - type: loadout + id: LoadoutSecurityUniformJumpsuitHoSGrey + - type: loadout + id: LoadoutSecurityUniformJumpskirtHoSGrey + - type: loadout + id: LoadoutUniformJumpsuitSecFormal + - type: loadout + id: LoadoutUniformJumpsuitSecSummer + +- type: characterItemGroup + id: LoadoutOuterSecurity + items: + - type: loadout + id: LoadoutClothingOuterArmorPlateCarrier + - type: loadout + id: LoadoutClothingOuterArmorDuraVest + - type: loadout + id: LoadoutClothingOuterCoatDetective + - type: loadout + id: LoadoutOuterVestDetective + - type: loadout + id: LoadoutClothingOuterCoatWarden + - type: loadout + id: LoadoutClothingOuterCoatHoSTrench + - type: loadout + id: LoadoutClothingOuterWinterHoS + - type: loadout + id: LoadoutClothingOuterArmorBasic + - type: loadout + id: LoadoutClothingOuterArmorSlim + +- type: characterItemGroup + id: LoadoutGlovesSecurity + items: + - type: loadout + id: LoadoutClothingHandsGlovesNitrile + +- type: characterItemGroup + id: LoadoutNeckSecurity + items: + - type: loadout + id: LoadoutClothingNeckCloakHos + - type: loadout + id: LoadoutClothingNeckMantleHOS + - type: loadout + id: LoadoutBedsheetBrigmedic + +- type: characterItemGroup + id: LoadoutHeadSecurity + items: + - type: loadout + id: LoadoutSecurityHeadHatBeret + - type: loadout + id: LoadoutClothingHeadHelmetBasic + - type: loadout + id: LoadoutClothingHeadHatBeretBrigmedic + - type: loadout + id: LoadoutClothingHeadHatBeretCorpsman + - type: loadout + id: LoadoutClothingHeadHatBeretWarden + - type: loadout + id: LoadoutClothingHeadHatBeretHoS + - type: loadout + id: LoadoutClothingHeadHelmetInsulated + +- type: characterItemGroup + id: LoadoutMaskSecurity + items: + - type: loadout + id: LoadoutSecurityMaskGasSwat + +- type: characterItemGroup + id: LoadoutBeltSecurity + items: + - type: loadout + id: LoadoutSecurityBeltWebbing + - type: loadout + id: LoadoutClothingBeltCorpsmanWebbing + - type: loadout + id: LoadoutClothingBeltSecurity + - type: loadout + id: LoadoutClothingBeltHolster + +- type: characterItemGroup + id: LoadoutEyesSecurity + items: + - type: loadout + id: LoadoutSecurityEyesHudSecurity + - type: loadout + id: ClothingEyesGlassesSunglasses + - type: loadout + id: LoadoutSecurityEyesEyepatchHudSecurity + - type: loadout + id: LoadoutSecurityEyesHudSecurityPrescription + - type: loadout + id: LoadoutClothingEyesGlassesSecurity + +- type: characterItemGroup + id: LoadoutShoesSecurity + items: + - type: loadout + id: LoadoutSecurityShoesJackboots + - type: loadout + id: LoadoutClothingShoesBootsCombat + +- type: characterItemGroup + maxItems: 5 + id: LoadoutEquipmentSecurity + items: + - type: loadout + id: LoadoutSecurityCombatKnife + - type: loadout + id: LoadoutSecurityFlash + - type: loadout + id: LoadoutMagazinePistol + - type: loadout + id: LoadoutMagazinePistolSpare + - type: loadout + id: LoadoutMagazinePistolRubber + - type: loadout + id: LoadoutMagazinePistolRubberSpare + - type: loadout + id: LoadoutSpeedLoaderMagnum + - type: loadout + id: LoadoutSpeedLoaderMagnumSpare + - type: loadout + id: LoadoutSpeedLoaderMagnumRubber + - type: loadout + id: LoadoutSpeedLoaderMagnumRubberSpare + - type: loadout + id: LoadoutMagazineMagnum + - type: loadout + id: LoadoutMagazineMagnumRubber + - type: loadout + id: LoadoutMagazineMagnumSpare + - type: loadout + id: LoadoutMagazineMagnumRubberSpare + +- type: characterItemGroup + maxItems: 1 + id: LoadoutWeaponSecurity + items: + - type: loadout + id: LoadoutSecurityDisabler + - type: loadout + id: LoadoutSecurityMk58 + - type: loadout + id: LoadoutSecurityMk58NonLethal + - type: loadout + id: LoadoutSecurityRevolver + - type: loadout + id: LoadoutSecurityRevolverNonLethal + - type: loadout + id: LoadoutSecurityRevolverDeckard + - type: loadout + id: LoadoutSecurityRevolverDeckardNonLethal + - type: loadout + id: LoadoutSecurityPistolN1984 + - type: loadout + id: LoadoutSecurityPistolN1984NonLethal + - type: loadout + id: LoadoutSecurityPistolViper + - type: loadout + id: LoadoutSecurityPistolViperNonLethal + - type: loadout + id: LoadoutSecurityPistolViperWood + - type: loadout + id: LoadoutSecurityEquipmentTruncheon + - type: loadout + id: LoadoutSecurityPistolSvalin + - type: loadout + id: LoadoutSecurityEnergyGunMini + - type: loadout + id: LoadoutSecurityEnergyGunPistol + - type: loadout + id: LoadoutSecurityPistolPollock + - type: loadout + id: LoadoutSecurityPistolPollockNonlethal + - type: loadout + id: LoadoutSecurityRevolverSnub + - type: loadout + id: LoadoutSecurityRevolverSnubNonlethal + - type: loadout + id: LoadoutSecurityRevolverK38Master + - type: loadout + id: LoadoutSecurityRevolverK38MasterNonlethal + - type: loadout + id: LoadoutSecurityRevolverFitz + - type: loadout + id: LoadoutSecurityRevolverFitzNonlethal + - type: loadout + id: LoadoutSecurityRevolverPython + - type: loadout + id: LoadoutSecurityRevolverPythonNonlethal + +- type: characterItemGroup + maxItems: 1 + id: LoadoutBackSecurity + items: + - type: loadout + id: LoadoutClothingBackSecurity + - type: loadout + id: LoadoutClothingBackSecuritySatchel + - type: loadout + id: LoadoutClothingBackSecurityDuffel + +- type: characterItemGroup + maxItems: 1 + id: LoadoutHoSWeapon + items: + - type: loadout + id: LoadoutCommandHoSPulsePistol + - type: loadout + id: LoadoutCommandHoSWt550 + - type: loadout + id: LoadoutCommandHoSKatanaSheath + - type: loadout + id: LoadoutCommandHoSC20r + - type: loadout + id: LoadoutCommandHoSBulldog + - type: loadout + id: LoadoutCommandHoSEnergySword + - type: loadout + id: LoadoutCommandHoSEnergyGun diff --git a/Resources/Prototypes/CharacterItemGroups/serviceGroups.yml b/Resources/Prototypes/CharacterItemGroups/serviceGroups.yml new file mode 100644 index 0000000000..61c2b286b7 --- /dev/null +++ b/Resources/Prototypes/CharacterItemGroups/serviceGroups.yml @@ -0,0 +1,185 @@ +- type: characterItemGroup + id: LoadoutUniformsService + items: + - type: loadout + id: LoadoutServiceClownOutfitJester + - type: loadout + id: LoadoutServiceClownOutfitJesterAlt + - type: loadout + id: LoadoutServiceBartenderUniformPurple + - type: loadout + id: LoadoutServiceBotanistUniformOveralls + - type: loadout + id: LoadoutServiceLawyerUniformBlueSuit + - type: loadout + id: LoadoutServiceLawyerUniformBlueSkirt + - type: loadout + id: LoadoutServiceLawyerUniformRedSuit + - type: loadout + id: LoadoutServiceLawyerUniformRedSkirt + - type: loadout + id: LoadoutServiceLawyerUniformPurpleSuit + - type: loadout + id: LoadoutServiceLawyerUniformPurpleSkirt + - type: loadout + id: LoadoutServiceLawyerUniformGoodSuit + - type: loadout + id: LoadoutServiceLawyerUniformGoodSkirt + - type: loadout + id: LoadoutServiceReporterUniformJournalist + - type: loadout + id: LoadoutServiceReporterUniformDetectivesuit + - type: loadout + id: LoadoutServiceReporterUniformDetectiveskirt + +- type: characterItemGroup + id: LoadoutOuterService + items: + - type: loadout + id: LoadoutServiceClownOuterWinter + - type: loadout + id: LoadoutServiceClownOuterClownPriest + - type: loadout + id: LoadoutServiceMimeOuterWinter + +- type: characterItemGroup + id: LoadoutNeckService + items: + - type: loadout + id: LoadoutServiceClownBedsheetClown + - type: loadout + id: LoadoutServiceMimeBedsheetMime + +- type: characterItemGroup + id: LoadoutMaskService + items: + - type: loadout + id: LoadoutServiceClownMaskSexy + - type: loadout + id: LoadoutServiceMimeMaskSad + - type: loadout + id: LoadoutServiceMimeMaskScared + - type: loadout + id: LoadoutServiceMimeMaskSexy + +- type: characterItemGroup + id: LoadoutShoesService + items: + - type: loadout + id: LoadoutServiceClownBootsWinter + - type: loadout + id: LoadoutServiceMimeShoesBootsWinter + +- type: characterItemGroup + id: LoadoutEquipmentService + items: + - type: loadout + id: LoadoutServiceClownCowToolboxFilled + +- type: characterItemGroup + id: LoadoutHeadService + items: + - type: loadout + id: LoadoutServiceClownCowToolboxFilled + +# Bartender +- type: characterItemGroup + id: LoadoutBartenderOuterwear + items: + - type: loadout + id: LoadoutServiceBartenderArmorDuraVest + - type: loadout + id: LoadoutServiceOuterBartenderNt + - type: loadout + id: LoadoutServiceOuterBartenderIdris + - type: loadout + id: LoadoutServiceOuterBartenderOrion + +- type: characterItemGroup + id: LoadoutBartenderAmmo + items: + - type: loadout + id: LoadoutServiceBartenderBoxBeanbags + - type: loadout + id: LoadoutServiceBartenderBoxLightRifleRubber + +- type: characterItemGroup + id: LoadoutBartenderWeapon + items: + - type: loadout + id: LoadoutServiceBartenderShotgunDoubleBarreledRubber + - type: loadout + id: LoadoutServiceBartenderMosinRubber + +- type: characterItemGroup + id: LoadoutBartenderUniforms + items: + - type: loadout + id: LoadoutServiceJumpsuitBartenderNt + - type: loadout + id: LoadoutServiceJumpsuitBartenderIdris + - type: loadout + id: LoadoutServiceJumpsuitBartenderOrion + +- type: characterItemGroup + id: LoadoutBartenderHead + items: + - type: loadout + id: LoadoutServiceHeadBartenderNt + - type: loadout + id: LoadoutServiceHeadBartenderIdris + - type: loadout + id: LoadoutServiceHeadBartenderOrion + +# Botanist +- type: characterItemGroup + id: LoadoutBotanistUniforms + items: + - type: loadout + id: LoadoutServiceJumpsuitHydroponicsNt + - type: loadout + id: LoadoutServiceJumpsuitHydroponicsIdris + - type: loadout + id: LoadoutServiceJumpsuitHydroponicsOrion + +# Chef +- type: characterItemGroup + id: LoadoutChefUniforms + items: + - type: loadout + id: LoadoutServiceJumpsuitChefNt + - type: loadout + id: LoadoutServiceJumpsuitChefIdris + - type: loadout + id: LoadoutServiceJumpsuitChefOrion + +- type: characterItemGroup + id: LoadoutChefHead + items: + - type: loadout + id: LoadoutServiceHeadChefNt + - type: loadout + id: LoadoutServiceHeadChefIdris + - type: loadout + id: LoadoutServiceHeadChefOrion + +- type: characterItemGroup + id: LoadoutChefOuter + items: + - type: loadout + id: LoadoutServiceOuterChefNt + - type: loadout + id: LoadoutServiceOuterChefIdris + - type: loadout + id: LoadoutServiceOuterChefOrion + +# Janitor +- type: characterItemGroup + id: LoadoutJanitorUniforms + items: + - type: loadout + id: LoadoutServiceJumpsuitJanitorNt + - type: loadout + id: LoadoutServiceJumpsuitJanitorIdris + - type: loadout + id: LoadoutServiceJumpsuitJanitorOrion \ No newline at end of file diff --git a/Resources/Prototypes/CharacterItemGroups/shoeGroup.yml b/Resources/Prototypes/CharacterItemGroups/shoeGroup.yml new file mode 100644 index 0000000000..e3b71e7977 --- /dev/null +++ b/Resources/Prototypes/CharacterItemGroups/shoeGroup.yml @@ -0,0 +1,55 @@ +- type: characterItemGroup + id: LoadoutShoes + items: + - type: loadout + id: LoadoutShoesBlack + - type: loadout + id: LoadoutShoesBlue + - type: loadout + id: LoadoutShoesBrown + - type: loadout + id: LoadoutShoesGreen + - type: loadout + id: LoadoutShoesOrange + - type: loadout + id: LoadoutShoesPurple + - type: loadout + id: LoadoutShoesRed + - type: loadout + id: LoadoutShoesWhite + - type: loadout + id: LoadoutShoesYellow + - type: loadout + id: LoadoutShoesGeta + - type: loadout + id: LoadoutShoesTourist + - type: loadout + id: LoadoutShoesBootsWork + - type: loadout + id: LoadoutShoesBootsLaceup + - type: loadout + id: LoadoutShoesBootsWinter + - type: loadout + id: LoadoutShoesBootsCowboyBrown + - type: loadout + id: LoadoutShoesBootsCowboyBlack + - type: loadout + id: LoadoutShoesBootsCowboyWhite + - type: loadout + id: LoadoutShoesBootsCowboyFancy + - type: loadout + id: LoadoutShoesBootsFishing + - type: loadout + id: LoadoutShoesSlippersDuck + - type: loadout + id: LoadoutShoesLeather + - type: loadout + id: LoadoutShoesMiscWhite + - type: loadout + id: LoadoutShoesHighheelBoots + - type: loadout + id: LoadoutShoesUnderSocksCoder + - type: loadout + id: LoadoutShoesUnderSocksBee + - type: loadout + id: LoadoutShoesClothWrap diff --git a/Resources/Prototypes/Chemistry/metabolism_groups.yml b/Resources/Prototypes/Chemistry/metabolism_groups.yml index fc59edd81f..b2035671af 100644 --- a/Resources/Prototypes/Chemistry/metabolism_groups.yml +++ b/Resources/Prototypes/Chemistry/metabolism_groups.yml @@ -1,22 +1,29 @@ -# Default human metabolism groups. +# Default human metabolism groups. - type: metabolismGroup id: Poison + name: metabolism-group-poison - type: metabolismGroup id: Medicine + name: metabolism-group-medicine - type: metabolismGroup id: Narcotic + name: metabolism-group-narcotic - type: metabolismGroup id: Alcohol + name: metabolism-group-alcohol - type: metabolismGroup id: Food + name: metabolism-group-food - type: metabolismGroup id: Drink + name: metabolism-group-drink # Used for gases that have effects on being inhaled - type: metabolismGroup id: Gas + name: metabolism-group-gas diff --git a/Resources/Prototypes/Chemistry/metabolizer_types.yml b/Resources/Prototypes/Chemistry/metabolizer_types.yml index 316b8f02b5..80f69893c6 100644 --- a/Resources/Prototypes/Chemistry/metabolizer_types.yml +++ b/Resources/Prototypes/Chemistry/metabolizer_types.yml @@ -3,52 +3,56 @@ - type: metabolizerType id: Animal - name: animal + name: metabolizer-type-animal - type: metabolizerType id: Bloodsucker - name: bloodsucker + name: metabolizer-type-bloodsucker - type: metabolizerType id: Dragon - name: dragon + name: metabolizer-type-dragon - type: metabolizerType id: Human - name: human + name: metabolizer-type-human - type: metabolizerType id: Slime - name: slime + name: metabolizer-type-slime - type: metabolizerType id: Vox - name: vox + name: metabolizer-type-vox - type: metabolizerType id: Rat - name: rat + name: metabolizer-type-rat - type: metabolizerType id: Plant - name: plant + name: metabolizer-type-plant - type: metabolizerType id: Dwarf - name: dwarf + name: metabolizer-type-dwarf - type: metabolizerType id: Moth - name: moth + name: metabolizer-type-moth - type: metabolizerType id: Arachnid - name: arachnid + name: metabolizer-type-arachnid - type: metabolizerType id: Vampiric - name: vampiric + name: metabolizer-type-vampiric - type: metabolizerType id: LiquorLifeline - name: liquorlifeline + name: metabolizer-type-liquorlifeline + +- type: metabolizerType + id: Shadowkin + name: metabolizer-type-shadowkin diff --git a/Resources/Prototypes/Damage/containers.yml b/Resources/Prototypes/Damage/containers.yml index b01d22df3b..0144083b14 100644 --- a/Resources/Prototypes/Damage/containers.yml +++ b/Resources/Prototypes/Damage/containers.yml @@ -62,3 +62,11 @@ - Immaterial supportedTypes: - Poison + +# Shitmed +- type: damageContainer + id: OrganicPart + supportedGroups: + - Brute + - Burn + diff --git a/Resources/Prototypes/Damage/groups.yml b/Resources/Prototypes/Damage/groups.yml index 07bfe2edcd..71e4acdaea 100644 --- a/Resources/Prototypes/Damage/groups.yml +++ b/Resources/Prototypes/Damage/groups.yml @@ -1,5 +1,6 @@ - type: damageGroup id: Brute + name: damage-group-brute damageTypes: - Blunt - Slash @@ -7,6 +8,7 @@ - type: damageGroup id: Burn + name: damage-group-burn damageTypes: - Heat - Shock @@ -19,6 +21,7 @@ # bloodloss, not this whole group, unless you have a wonder drug that affects both. - type: damageGroup id: Airloss + name: damage-group-airloss damageTypes: - Asphyxiation - Bloodloss @@ -27,11 +30,13 @@ # Though there are probably some radioactive poisons. - type: damageGroup id: Toxin + name: damage-group-toxin damageTypes: - Poison - Radiation - type: damageGroup id: Genetic + name: damage-group-genetic damageTypes: - Cellular diff --git a/Resources/Prototypes/Damage/modifier_sets.yml b/Resources/Prototypes/Damage/modifier_sets.yml index a6798e39cf..8366453e58 100644 --- a/Resources/Prototypes/Damage/modifier_sets.yml +++ b/Resources/Prototypes/Damage/modifier_sets.yml @@ -198,12 +198,10 @@ - type: damageModifierSet id: Zombie #Blunt resistant and immune to biological threats, but can be hacked apart and burned coefficients: - Blunt: 0.7 - Slash: 1.1 - Piercing: 0.9 - Shock: 1.25 + Blunt: 0.6 + Piercing: 0.8 Cold: 0.3 - Heat: 1.5 + Heat: 1.25 Poison: 0.0 Radiation: 0.0 @@ -244,7 +242,7 @@ Piercing: 0.2 Shock: 0.0 Cold: 0.0 - Heat: -1 # heat damage cauterizes wounds, but will still hurt obviously. + Heat: -0.5 # heat damage cauterizes wounds, but will still hurt obviously. Poison: 0.0 Radiation: 0.0 Asphyxiation: 0.0 @@ -349,3 +347,45 @@ Holy: 1.5 flatReductions: Cold: 3 + +- type: damageModifierSet + id: FireSpirit + coefficients: + Heat: 0 + Cold: 1.2 + Blunt: 0.6 + Slash: 0.6 + Piercing: 0.6 + Holy: 1.5 + +- type: damageModifierSet + id: Shadowkin + coefficients: + Blunt: 0.95 + Slash: 1.2 + Piercing: 1.1 + Asphyxiation: 0 + Cold: 0.75 + Heat: 1.2 + Cellular: 0.25 + Bloodloss: 1.35 + Shock: 1.25 + Radiation: 1.3 + +- type: damageModifierSet + id: DermalArmor + coefficients: + Blunt: 0.9 + Slash: 0.9 + Piercing: 0.9 + +# Shitmed modification: Change this if you want to alter how damage types affect part severing/integrity. +- type: damageModifierSet + id: PartDamage + coefficients: + Blunt: 0.8 + Slash: 1.2 + Piercing: 0.5 + Cold: 0.5 + Heat: 0.8 + Shock: 0.5 diff --git a/Resources/Prototypes/Damage/types.yml b/Resources/Prototypes/Damage/types.yml index bacaf1f798..0107da2482 100644 --- a/Resources/Prototypes/Damage/types.yml +++ b/Resources/Prototypes/Damage/types.yml @@ -3,6 +3,7 @@ # Usually healed automatically if entity can breathe - type: damageType id: Asphyxiation + name: damage-type-asphyxiation armorCoefficientPrice: 5 armorFlatPrice: 50 @@ -11,57 +12,68 @@ # Represents there not enough blood to supply oxygen (or equivalent). - type: damageType id: Bloodloss + name: damage-type-bloodloss armorCoefficientPrice: 5 armorFlatPrice: 50 - type: damageType id: Blunt + name: damage-type-blunt armorCoefficientPrice: 2 armorFlatPrice: 10 - type: damageType id: Cellular + name: damage-type-cellular armorCoefficientPrice: 5 armorFlatPrice: 30 - type: damageType id: Caustic + name: damage-type-caustic armorCoefficientPrice: 5 armorFlatPrice: 30 - type: damageType id: Cold + name: damage-type-cold armorCoefficientPrice: 2.5 armorFlatPrice: 20 - type: damageType id: Heat + name: damage-type-heat armorCoefficientPrice: 2.5 armorFlatPrice: 20 - type: damageType id: Piercing + name: damage-type-piercing armorCoefficientPrice: 2 armorFlatPrice: 10 # Poison damage. Generally caused by various reagents being metabolised. - type: damageType id: Poison + name: damage-type-poison armorCoefficientPrice: 10 armorFlatPrice: 60 - type: damageType id: Radiation + name: damage-type-radiation armorCoefficientPrice: 2.5 armorFlatPrice: 16 - type: damageType id: Shock + name: damage-type-shock armorCoefficientPrice: 2.5 armorFlatPrice: 20 - type: damageType id: Slash + name: damage-type-slash armorCoefficientPrice: 2 armorFlatPrice: 10 @@ -69,5 +81,6 @@ # Exclusive for structures such as walls, airlocks and others. - type: damageType id: Structural + name: damage-type-structural armorCoefficientPrice: 1 armorFlatPrice: 1 diff --git a/Resources/Prototypes/Datasets/Names/pitbull.yml b/Resources/Prototypes/Datasets/Names/pitbull.yml new file mode 100644 index 0000000000..adc4778d86 --- /dev/null +++ b/Resources/Prototypes/Datasets/Names/pitbull.yml @@ -0,0 +1,40 @@ +- type: dataset + id: names_pitbull + values: + - Amon + - Arioch + - Baby + - Blaze + - Blue + - Bubbles + - Buttercup + - Cuddles + - Cujo + - Daffodil + - Daisy + - Dante + - Demon + - Diesel + - Echo + - Face Eater + - Franny + - Hades + - Killer + - Lady + - Lucifer + - Luna + - Magnum + - Mammon + - Midas + - Paisley + - Peaches + - Petunia + - Princess + - Rainbow + - Reaper + - Satan + - Sweetie + - Trixie + - Trogdor + - Tutu + - Zeus \ No newline at end of file diff --git a/Resources/Prototypes/Datasets/Names/shadowkin.yml b/Resources/Prototypes/Datasets/Names/shadowkin.yml new file mode 100644 index 0000000000..4dbf4c5dc5 --- /dev/null +++ b/Resources/Prototypes/Datasets/Names/shadowkin.yml @@ -0,0 +1,70 @@ +# Names for the shadowkin, +# Shadowkin names are descriptive of +# Their Primary Emotion, +# A State of Being, +# Or past Memories. + +- type: dataset + id: names_shadowkin + values: + # Mar + # - Mar + + # Sad + - Fragile + - Heartbreak + - Inferior + - Lone + - Lonesome + - Loss + - Solitary + - Solitude + - Sorrow + - Shade + + # Angry + - Fear + - Fearful + - Fury + - Pain + - Rage + - Rush + - Wrath + + # Happy + - Calm + - Content + - Contented + - Happy + - Hope + - Joyous + - Lovely + - Peace + - Peaceful + - Quiet + - Serene + - Serenity + - Tranquil + - Tranquility + + # Memory + - Dillusioned + - Forgotten + - Focusless + - Lost + - Memory + - Recollection + - Remembrance + - Reminisce + - Reminiscence + + # Other + - Apathy + - Collected + - Curiosity + - Free + - Interest + - Jax # White eye (jack of all trades) :) + - Still + - Unbound + - Shadows \ No newline at end of file diff --git a/Resources/Prototypes/Datasets/corporations.yml b/Resources/Prototypes/Datasets/corporations.yml new file mode 100644 index 0000000000..55b1d53d2f --- /dev/null +++ b/Resources/Prototypes/Datasets/corporations.yml @@ -0,0 +1,12 @@ +- type: dataset + id: TraitorCorporations + values: + - "CyberSun Industries" + - "Gorlex Marauders" + - "MI13" + - "Tiger Cooperative" + - "S.E.L.F." + - "Animal Rights Consortium" + - "Donk Corporation" + - "Waffle Corporation" + - "Interdyne Pharmaceutics" \ No newline at end of file diff --git a/Resources/Prototypes/Datasets/criminal_records.yml b/Resources/Prototypes/Datasets/criminal_records.yml index ee28309184..fe21757cd2 100644 --- a/Resources/Prototypes/Datasets/criminal_records.yml +++ b/Resources/Prototypes/Datasets/criminal_records.yml @@ -2,17 +2,23 @@ - type: dataset id: CriminalRecordsWantedReasonPlaceholders values: + - Ate a delicious valid salad - Ate their own shoes - Being a clown - Being a mime - Breathed the wrong way - Broke into evac - Did literally nothing + - Did their job - Didn't say hello to me - Drank one too many + - Had two toolboxes, that's too many - Lied on common radio - Looked at me funny + - Lubed up the entire way to evac + - Set AME up on time - Slipped the HoS - Stole the clown's mask - Told an unfunny joke - Wore a gasmask + - Wore boxing gloves diff --git a/Resources/Prototypes/Datasets/ipc_names.yml b/Resources/Prototypes/Datasets/ipc_names.yml new file mode 100644 index 0000000000..76a963dc51 --- /dev/null +++ b/Resources/Prototypes/Datasets/ipc_names.yml @@ -0,0 +1,1107 @@ +- type: dataset + id: IpcFirst + values: + - ABEX + - ANCL + - ANTR + - ARMA + - AURA + - BGB + - CBOS + - CDB + - CHOC + - CHRI + - COI + - CRUX + - CYBR + - DRSD + - DUNC + - EBIX + - EXOS + - FIRC + - FIZZ + - FRE + - FXMC + - GIGA + - GOOF + - GRIN + - GUN + - HBL + - HG + - HIU + - HOG + - INC + - JADE + - JJR + - JLLO + - JNLG + - JRD + - JUNO + - KALE + - KANO + - KAZA + - KENT + - KIV + - KOR + - KORA + - KOS + - LUMA + - LUNA + - LYNX + - MET + - MIW + - MNOS + - MRPR + - MSO + - NANO + - NEST + - NEXO + - NOVA + - ORNG + - OSI + - PBU + - PHL + - PKP + - PKR + - PLEX + - PLEXO + - PLIX + - QUES + - QUIN + - QWER + - RIFT + - RR + - RYNO + - RZH + - SINA + - SLI + - STLP + - TKRG + - TRIX + - VERA + - VEXA + - VITA + - VIVE + - VOLT + - WAVE + - WISP + - WJ + - WREN + - XAL + - XENA + - XIS + - XSI + - XYLO + - YAGO + - YPT + - ZACK + - ZARG + - ZEON + - ZOLT + - ZUMA + - ZYLO + - ZYVA + +- type: dataset + id: IpcLast + values: + - 000 + - 001 + - 002 + - 003 + - 004 + - 005 + - 006 + - 007 + - 008 + - 009 + - 010 + - 011 + - 012 + - 013 + - 014 + - 015 + - 016 + - 017 + - 018 + - 019 + - 020 + - 021 + - 022 + - 023 + - 024 + - 025 + - 026 + - 027 + - 028 + - 029 + - 030 + - 031 + - 032 + - 033 + - 034 + - 035 + - 036 + - 037 + - 038 + - 039 + - 040 + - 041 + - 042 + - 043 + - 044 + - 045 + - 046 + - 047 + - 048 + - 049 + - 050 + - 051 + - 052 + - 053 + - 054 + - 055 + - 056 + - 057 + - 058 + - 059 + - 060 + - 061 + - 062 + - 063 + - 064 + - 065 + - 066 + - 067 + - 068 + - 069 + - 070 + - 071 + - 072 + - 073 + - 074 + - 075 + - 076 + - 077 + - 078 + - 079 + - 080 + - 081 + - 082 + - 083 + - 084 + - 085 + - 086 + - 087 + - 088 + - 089 + - 090 + - 091 + - 092 + - 093 + - 094 + - 095 + - 096 + - 097 + - 098 + - 099 + - 100 + - 101 + - 102 + - 103 + - 104 + - 105 + - 106 + - 107 + - 108 + - 109 + - 110 + - 111 + - 112 + - 113 + - 114 + - 115 + - 116 + - 117 + - 118 + - 119 + - 120 + - 121 + - 122 + - 123 + - 124 + - 125 + - 126 + - 127 + - 128 + - 129 + - 130 + - 131 + - 132 + - 133 + - 134 + - 135 + - 136 + - 137 + - 138 + - 139 + - 140 + - 141 + - 142 + - 143 + - 144 + - 145 + - 146 + - 147 + - 148 + - 149 + - 150 + - 151 + - 152 + - 153 + - 154 + - 155 + - 156 + - 157 + - 158 + - 159 + - 160 + - 161 + - 162 + - 163 + - 164 + - 165 + - 166 + - 167 + - 168 + - 169 + - 170 + - 171 + - 172 + - 173 + - 174 + - 175 + - 176 + - 177 + - 178 + - 179 + - 180 + - 181 + - 182 + - 183 + - 184 + - 185 + - 186 + - 187 + - 188 + - 189 + - 190 + - 191 + - 192 + - 193 + - 194 + - 195 + - 196 + - 197 + - 198 + - 199 + - 200 + - 201 + - 202 + - 203 + - 204 + - 205 + - 206 + - 207 + - 208 + - 209 + - 210 + - 211 + - 212 + - 213 + - 214 + - 215 + - 216 + - 217 + - 218 + - 219 + - 220 + - 221 + - 222 + - 223 + - 224 + - 225 + - 226 + - 227 + - 228 + - 229 + - 230 + - 231 + - 232 + - 233 + - 234 + - 235 + - 236 + - 237 + - 238 + - 239 + - 240 + - 241 + - 242 + - 243 + - 244 + - 245 + - 246 + - 247 + - 248 + - 249 + - 250 + - 251 + - 252 + - 253 + - 254 + - 255 + - 256 + - 257 + - 258 + - 259 + - 260 + - 261 + - 262 + - 263 + - 264 + - 265 + - 266 + - 267 + - 268 + - 269 + - 270 + - 271 + - 272 + - 273 + - 274 + - 275 + - 276 + - 277 + - 278 + - 279 + - 280 + - 281 + - 282 + - 283 + - 284 + - 285 + - 286 + - 287 + - 288 + - 289 + - 290 + - 291 + - 292 + - 293 + - 294 + - 295 + - 296 + - 297 + - 298 + - 299 + - 300 + - 301 + - 302 + - 303 + - 304 + - 305 + - 306 + - 307 + - 308 + - 309 + - 310 + - 311 + - 312 + - 313 + - 314 + - 315 + - 316 + - 317 + - 318 + - 319 + - 320 + - 321 + - 322 + - 323 + - 324 + - 325 + - 326 + - 327 + - 328 + - 329 + - 330 + - 331 + - 332 + - 333 + - 334 + - 335 + - 336 + - 337 + - 338 + - 339 + - 340 + - 341 + - 342 + - 343 + - 344 + - 345 + - 346 + - 347 + - 348 + - 349 + - 350 + - 351 + - 352 + - 353 + - 354 + - 355 + - 356 + - 357 + - 358 + - 359 + - 360 + - 361 + - 362 + - 363 + - 364 + - 365 + - 366 + - 367 + - 368 + - 369 + - 370 + - 371 + - 372 + - 373 + - 374 + - 375 + - 376 + - 377 + - 378 + - 379 + - 380 + - 381 + - 382 + - 383 + - 384 + - 385 + - 386 + - 387 + - 388 + - 389 + - 390 + - 391 + - 392 + - 393 + - 394 + - 395 + - 396 + - 397 + - 398 + - 399 + - 400 + - 401 + - 402 + - 403 + - 404 + - 405 + - 406 + - 407 + - 408 + - 409 + - 410 + - 411 + - 412 + - 413 + - 414 + - 415 + - 416 + - 417 + - 418 + - 419 + - 420 + - 421 + - 422 + - 423 + - 424 + - 425 + - 426 + - 427 + - 428 + - 429 + - 430 + - 431 + - 432 + - 433 + - 434 + - 435 + - 436 + - 437 + - 438 + - 439 + - 440 + - 441 + - 442 + - 443 + - 444 + - 445 + - 446 + - 447 + - 448 + - 449 + - 450 + - 451 + - 452 + - 453 + - 454 + - 455 + - 456 + - 457 + - 458 + - 459 + - 460 + - 461 + - 462 + - 463 + - 464 + - 465 + - 466 + - 467 + - 468 + - 469 + - 470 + - 471 + - 472 + - 473 + - 474 + - 475 + - 476 + - 477 + - 478 + - 479 + - 480 + - 481 + - 482 + - 483 + - 484 + - 485 + - 486 + - 487 + - 488 + - 489 + - 490 + - 491 + - 492 + - 493 + - 494 + - 495 + - 496 + - 497 + - 498 + - 499 + - 500 + - 501 + - 502 + - 503 + - 504 + - 505 + - 506 + - 507 + - 508 + - 509 + - 510 + - 511 + - 512 + - 513 + - 514 + - 515 + - 516 + - 517 + - 518 + - 519 + - 520 + - 521 + - 522 + - 523 + - 524 + - 525 + - 526 + - 527 + - 528 + - 529 + - 530 + - 531 + - 532 + - 533 + - 534 + - 535 + - 536 + - 537 + - 538 + - 539 + - 540 + - 541 + - 542 + - 543 + - 544 + - 545 + - 546 + - 547 + - 548 + - 549 + - 550 + - 551 + - 552 + - 553 + - 554 + - 555 + - 556 + - 557 + - 558 + - 559 + - 560 + - 561 + - 562 + - 563 + - 564 + - 565 + - 566 + - 567 + - 568 + - 569 + - 570 + - 571 + - 572 + - 573 + - 574 + - 575 + - 576 + - 577 + - 578 + - 579 + - 580 + - 581 + - 582 + - 583 + - 584 + - 585 + - 586 + - 587 + - 588 + - 589 + - 590 + - 591 + - 592 + - 593 + - 594 + - 595 + - 596 + - 597 + - 598 + - 599 + - 600 + - 601 + - 602 + - 603 + - 604 + - 605 + - 606 + - 607 + - 608 + - 609 + - 610 + - 611 + - 612 + - 613 + - 614 + - 615 + - 616 + - 617 + - 618 + - 619 + - 620 + - 621 + - 622 + - 623 + - 624 + - 625 + - 626 + - 627 + - 628 + - 629 + - 630 + - 631 + - 632 + - 633 + - 634 + - 635 + - 636 + - 637 + - 638 + - 639 + - 640 + - 641 + - 642 + - 643 + - 644 + - 645 + - 646 + - 647 + - 648 + - 649 + - 650 + - 651 + - 652 + - 653 + - 654 + - 655 + - 656 + - 657 + - 658 + - 659 + - 660 + - 661 + - 662 + - 663 + - 664 + - 665 + - 666 + - 667 + - 668 + - 669 + - 670 + - 671 + - 672 + - 673 + - 674 + - 675 + - 676 + - 677 + - 678 + - 679 + - 680 + - 681 + - 682 + - 683 + - 684 + - 685 + - 686 + - 687 + - 688 + - 689 + - 690 + - 691 + - 692 + - 693 + - 694 + - 695 + - 696 + - 697 + - 698 + - 699 + - 700 + - 701 + - 702 + - 703 + - 704 + - 705 + - 706 + - 707 + - 708 + - 709 + - 710 + - 711 + - 712 + - 713 + - 714 + - 715 + - 716 + - 717 + - 718 + - 719 + - 720 + - 721 + - 722 + - 723 + - 724 + - 725 + - 726 + - 727 + - 728 + - 729 + - 730 + - 731 + - 732 + - 733 + - 734 + - 735 + - 736 + - 737 + - 738 + - 739 + - 740 + - 741 + - 742 + - 743 + - 744 + - 745 + - 746 + - 747 + - 748 + - 749 + - 750 + - 751 + - 752 + - 753 + - 754 + - 755 + - 756 + - 757 + - 758 + - 759 + - 760 + - 761 + - 762 + - 763 + - 764 + - 765 + - 766 + - 767 + - 768 + - 769 + - 770 + - 771 + - 772 + - 773 + - 774 + - 775 + - 776 + - 777 + - 778 + - 779 + - 780 + - 781 + - 782 + - 783 + - 784 + - 785 + - 786 + - 787 + - 788 + - 789 + - 790 + - 791 + - 792 + - 793 + - 794 + - 795 + - 796 + - 797 + - 798 + - 799 + - 800 + - 801 + - 802 + - 803 + - 804 + - 805 + - 806 + - 807 + - 808 + - 809 + - 810 + - 811 + - 812 + - 813 + - 814 + - 815 + - 816 + - 817 + - 818 + - 819 + - 820 + - 821 + - 822 + - 823 + - 824 + - 825 + - 826 + - 827 + - 828 + - 829 + - 830 + - 831 + - 832 + - 833 + - 834 + - 835 + - 836 + - 837 + - 838 + - 839 + - 840 + - 841 + - 842 + - 843 + - 844 + - 845 + - 846 + - 847 + - 848 + - 849 + - 850 + - 851 + - 852 + - 853 + - 854 + - 855 + - 856 + - 857 + - 858 + - 859 + - 860 + - 861 + - 862 + - 863 + - 864 + - 865 + - 866 + - 867 + - 868 + - 869 + - 870 + - 871 + - 872 + - 873 + - 874 + - 875 + - 876 + - 877 + - 878 + - 879 + - 880 + - 881 + - 882 + - 883 + - 884 + - 885 + - 886 + - 887 + - 888 + - 889 + - 890 + - 891 + - 892 + - 893 + - 894 + - 895 + - 896 + - 897 + - 898 + - 899 + - 900 + - 901 + - 902 + - 903 + - 904 + - 905 + - 906 + - 907 + - 908 + - 909 + - 910 + - 911 + - 912 + - 913 + - 914 + - 915 + - 916 + - 917 + - 918 + - 919 + - 920 + - 921 + - 922 + - 923 + - 924 + - 925 + - 926 + - 927 + - 928 + - 929 + - 930 + - 931 + - 932 + - 933 + - 934 + - 935 + - 936 + - 937 + - 938 + - 939 + - 940 + - 941 + - 942 + - 943 + - 944 + - 945 + - 946 + - 947 + - 948 + - 949 + - 950 + - 951 + - 952 + - 953 + - 954 + - 955 + - 956 + - 957 + - 958 + - 959 + - 960 + - 961 + - 962 + - 963 + - 964 + - 965 + - 966 + - 967 + - 968 + - 969 + - 970 + - 971 + - 972 + - 973 + - 974 + - 975 + - 976 + - 977 + - 978 + - 979 + - 980 + - 981 + - 982 + - 983 + - 984 + - 985 + - 986 + - 987 + - 988 + - 989 + - 990 + - 991 + - 992 + - 993 + - 994 + - 995 + - 996 + - 997 + - 998 + - 999 diff --git a/Resources/Prototypes/Datasets/tips.yml b/Resources/Prototypes/Datasets/tips.yml index 3ee82727fd..f94dd2638b 100644 --- a/Resources/Prototypes/Datasets/tips.yml +++ b/Resources/Prototypes/Datasets/tips.yml @@ -38,14 +38,12 @@ - "As a Traitor, any purchased grenade penguins won't attack you, and will explode if killed." - "As a Traitor, be careful when using vestine from the chemical synthesis kit. If someone checks your station, they could easily out you." - "As a Traitor, remember that power sinks will create a loud sound and alert the crew after running for long enough. Try to hide them in a tricky-to-find spot, or reinforce the area around them so that they're harder to reach." - # - "As a Traitor, plasma gas is an excellent way to create chaos. It can be ignited to make an area extra-uninhabitable, and can cause toxin damage to those that inhale it." # Parkstation-Tips + - "As a Traitor, plasma gas is an excellent way to create chaos. It can be ignited to make an area extra-uninhabitable, and can cause toxin damage to those that inhale it." - "As a Traitor, dehydrated carps are useful for killing a large hoard of people. As long as you pat it before rehydrating it, it can be used as a great distraction." - "As a Traitor, have you tried injecting plasma into batteries? In the case of a defibrillator, it explodes on use; hurting the user and the patient!" - # Parkstation-NoNukies-Start - # - "As a Nuclear Operative, stick together! While your equipment is robust, your fellow operatives are much better at saving your life: they can drag you away from danger while stunned and provide cover fire." - # - "As a Nuclear Operative, communication is key! Use your radio to speak to your fellow operatives and coordinate an attack plan." - # - "As a Nuclear Operative, remember that stealth is an option. It'll be hard for the captain to fight back if he gets caught off guard by what he thinks is just a regular passenger!" - # Parkstation-NoNukies-End + - "As a Nuclear Operative, stick together! While your equipment is robust, your fellow operatives are much better at saving your life: they can drag you away from danger while stunned and provide cover fire." + - "As a Nuclear Operative, communication is key! Use your radio to speak to your fellow operatives and coordinate an attack plan." + - "As a Nuclear Operative, remember that stealth is an option. It'll be hard for the captain to fight back if he gets caught off guard by what he thinks is just a regular passenger!" - "As an antagonist, be mindful of the power of destroying telecommunications. It'll be a lot harder for people to call you out if they can't do so effectively!" - "You can examine your headset to see which radio channels you have available and how to speak in them." - "As a Salvage Specialist, always carry a GPS on you and take note of the station's coordinates in case your salvage is lost to space." @@ -76,12 +74,10 @@ - "As an Engineer, you can electrify grilles by placing powered cables beneath them." - "As an Engineer, always double check when you're setting up the singularity. It is easier than you think to loose it!" - "As an Engineer, you can use plasma glass to reinforce an area and prevent radiation. Uranium glass can also be used to prevent radiation." - # Parkstation-Oligarchy-Start - # - "As the Captain, you are one of the highest priority targets on the station. Everything from revolutions, to nuclear operatives, to traitors that need to rob you of your unique laser pistol or your life are things to worry about." - # - "As the Captain, always take the nuclear disk and pinpointer with you every shift. It's a good idea to give one of these to another head you can trust with keeping it safe." - # - "As the Captain, you have absolute access and control over the station, but this does not mean that being a horrible person won't result in mutiny." - # - "As the Captain, try to be active and patrol the station. Staying in the bridge might be tempting, but you'll just end up putting a bigger target on your back!" - # Parkstation-Oligarchy-End + - "As the Captain, you are one of the highest priority targets on the station. Everything from revolutions, to nuclear operatives, to traitors that need to rob you of your unique laser pistol or your life are things to worry about." + - "As the Captain, always take the nuclear disk and pinpointer with you every shift. It's a good idea to give one of these to another head you can trust with keeping it safe." + - "As the Captain, you have absolute access and control over the station, but this does not mean that being a horrible person won't result in mutiny." + - "As the Captain, try to be active and patrol the station. Staying in the bridge might be tempting, but you'll just end up putting a bigger target on your back!" - "As a Scientist, you can try random things on an artifact while the scanner is on cooldown to speed up the point extraction process significantly." - "As a Scientist, you can utilize upgraded versions of machines to increase its effectiveness. This can make certain machines significantly better; salvage will love you if you upgrade their ore processor!" - "As a Scientist, you can build cyborgs using positronic brains and a chassis, they are just as useful as a new crew member." @@ -110,14 +106,13 @@ - "Speed is almost everything in combat. Using hardsuits just for their armor is usually a terrible idea unless the resistances it provides are geared towards combat, or you're not planning to go head-first into the fray." - "Just because a job can't be a traitor at the beginning of a round doesn't mean that they'll never be a traitor." - "Syndicate gas masks will both provide welding protection and block flashes. Think twice before trying to flash a Nuclear Operative!" - - "Demoman takes skill." - "You can spray a fire extinguisher, throw items or fire a gun while floating through space to give yourself a minor boost. Simply fire opposite to where you want to go." - "You can drag other players onto yourself to open the strip menu, allowing you to remove their equipment or force them to wear something. Note that exosuits or helmets will block your access to the clothing beneath them, and that certain items take longer to strip or put on than others." - "You can climb onto a table by dragging yourself onto one." - "You can move an item out of the way by dragging it, and then holding CTRL + right click and moving your mouse into the direction you want it to go." - "When dealing with security, you can often get your sentence negated entirely through cooperation and deception." - "Fire can spread to other players through touch! Be careful around flaming bodies or large crowds with people on fire in them." - - "Hull breaches take a few seconds to fully space an area. You can use this time to patch up the hole if you're confident enough, or just run away." + - "Hull breaches create sudden bursts of Space Wind, which can violently eject people and objects into deep space. Try to avoid standing near hull breaches when they're still new!" - "Burn damage, such as that from a welding tool or lightbulb, can be used to cauterize wounds and stop bleeding." - "Bleeding is no joke! If you've been shot or acquired any other major injury, make sure to treat it quickly." - "In an emergency, you can butcher a jumpsuit with a sharp object to get cloth, which can be crafted into gauze." @@ -137,49 +132,42 @@ - "You can weld glass shards into glass sheets." - "By right clicking on a player, and then clicking the heart icon, you can quickly examine them to check for injuries or how badly they're bleeding. You can also do this to yourself." - "Monkeys and kobolds have a rare chance to be sentient. Ook!" - - "Lottery crates can very rarely contain The Throngler." - "You can tell if an area with firelocks up is spaced by looking to see if the firelocks have lights beside them." - "Instead of picking it up, you can alt-click food to eat it. This also works for mice and other creatures without hands." - # Nyanotrasen - Specific tips below - "Psionic insulation prevents you from using or being targeted by psionic abilities. Look for insulative headgear or cryptobiolin pills." - "Lotophagoi oil from the oracle will drug you up for a long time, but is one of the most reliable sources of psionics." - "Turn off glimmer probers when glimmer approaches cautious levels." - "Glimmer probers power themselves when glimmer gets too high." - "Oneirophages have psionic invisiblity. Psionic insulation will allow you to see them." - "Someone who looks just like you may be a paradox anomaly." - - "Rat kings can command their rats to attack someone by pointing at them." - - "Some rat kings are known to be friendly. As long as the rat army never goes rogue, it could be a great boon to the station." - - "The reverse engineering machine benefits greatly from machine upgrades." - "The reverse engineering machine can reverse engineer blood red hardsuits." - # - "You can perform CPR by using an empty hand on someone in critical condition." - - "The metempsychotic machine's chance to reincarnate you into a humanoid is guaranteed for a number of times equal to its scanning module tier." - - "Glimmer wisps will only attack you if they detect you are psionic, or you aggravate them by damaging them or dragging away a body they are trying to drain." + - "If you have the CPR Training trait, or are any medical doctor, you can perform CPR by right clicking someone who is dead or in critical condition." + - "CPR can occasionally 'defib' dead patients back to critical." + - "Performing CPR on a dead patient will reduce their rot timer, allowing doctors more time to save a patient." - "Bibles, holy water, and the anti-psychic knife can deal holy damage, which has strong effects against some creatures." - "Ectoplasm is used in the recipe for normality crystals." - # DeltaV - Specific tips below - "As a Security Officer, if you're not sure how to determine the severity of a crime or the appropriate punishment, take a look at the Crime Assist application in your PDA!" - - # Parkstation-Tips Start - - "Remember that all Command members are equal, and should take equal responsibility in the safety of the station. No Captains here." - - "The theatre troupe is made up of the Clown, Mime, Musician, and Boxer. Remember that the goal of these roles is to improve morale and entertain the crew- not commit crime." - - "The 'among pequeno' is not a real thing. If you believe you are seeing an among pequeno please contact your station's Medical department." - - "I forgot what I was going to say." - # - "Positive traits take a certain amount of points to use, to get points you need to have negative traits active." - # - "Remember to make a nice, long, and detailed flavortext! It's better for anyone who reads it." - - "Do you like waffles? I like waffles." - - "Tips can appear in the middle of a round, just not nearly as often as the lobby." - - "You can clean a pie off your face by getting sprayed with space cleaner or a fire extinguisher." - # - "Space does NOT have friction, be careful exploring without a jetpack or anything you aren't willing to throw away." - - "Always keep an extra crate of AME fuel!" - # - "As the Station AI you are in charge of the Cyborgs and Drones, not the crew. You can speak to your inorganics via the binary (:b) radio channel." - - "If there is a lack of staff on the station, avoid breaking into departments and contact CentComm instead." - - "Don't eat too fast! Nothing will happen, just don't do it." - - "" - # - "You are unable to inject people through a hardsuit, make sure to take off your helmet before asking for medical aid." - # - "Emergency medipens are refillable, ask medical for a refill if you need one." - - "Using a metempsychotic machine will ignore any cellular damage to a dead being, allowing you to reincarnate them after ANY amount of time!" - - "You probably shouldn't eat a Slime's jelly." - # - "If you have to leave and are unlikely to return soon, head over to the station's cryosleep pods and take a nice nap!" - # - "Make sure to periodically check on the Station AI and their satellite. It may have died or been stolen without your knowledge." - - "Central Command is not omnipresent or omnipotent, if you need something, request it via a red telephone, captain fax machine, or communications console." - # Parkstation-Tipes End + - "If you do not take the Latent Psychic trait, you can never be psionic. There may however exist some unusual conditions that can grant this trait during a round." + - "The kitchen Deep Fryer is the most powerful machine ever created by man. It is not merely limited to deep frying food, but can deep fry almost ANYTHING that will fit in the basket. Try deep frying the captain's spare ID!" + - "If you see a security officer roaming with his stun baton or gun out on green, you have a moral obligation to steal his baton and feed it to the Deep Fryer." + - "Harpies have the ability to imitate several different instruments, and can 'Sing' by opening an innate midi player." + - "You can see if a Harpy is singing by looking for the musical notes floating around their head." + - "Talking mimes are not legally considered people, and should be killed on sight for their crimes against the mime gods." + - "You can pick up another person by holding Alt, and left clicking on the person. Your ability to carry them is determined by your relative size!" + - "Your character's size affects many things in the game, some of which you might not have been aware of. Larger characters experience less gun recoil. Smaller characters can slip handcuffs faster." + - "'Space Wind' is the term for how flowing air can throw objects or people. A hull breach is typically the most common cause of Space Wind, but it can also be caused by high pressure cannons!" + - "Ignore firelock warnings at your own peril. You may find yourself being violently thrown into deep space as soon as you crowbar the firelock open." + - "Your character's Mass determines how resistant they are to being thrown by space wind. A small Felinid or Harpy is easily ejected into space, while an Oni or Diona is usually unaffected." + - "Supermatter engines can be activated and powered by throwing objects into the crystal. Consider using the Clown to activate your station's Supermatter engine." + - "There is no friction in space. Unless you use Newton's 1st Law to save yourself, your corpse will float away from the station forever." + - "Cloning patients come out of the pod with extremely heavy Cellular damage. Consider using Doxarubixadone in a Cryo tube as an affordable means of 'Finishing' clones." + - "Your character's Mood stat influences both your movement speed, and critical injury threshold." + - "The Sanguine trait grants your character a permanent large mood bonus. As a result, they often move faster than other people, and will rarely ever take penalties from a net negative mood." + - "The Saturnine trait gives your character a permanent large mood penalty. As a result, they often move slower than other people, and can have a hard time getting a net positive mood." + - "If you aren't playing any Medical character, consider taking the CPR Training trait! This often highly-underrated trait allows characters to sometimes revive other people without needing medical supplies." + - "If you have the Latent Psychic trait, you can consume hallucinogenic drugs to have a chance to obtain psychic powers." + - "Having the Natural Telepath trait, or rolling the Telepathy Power, allows a person to speak telepathically by starting a message with '='. Telepathy does not require a radio, but is also completely anonymous." + - "Certain crew members, such as the Mystagogue, Cataloguer, and Psionic Mantis, all automatically start with certain Psychic Powers." + - "NanoTrasen prefers to select its command staff for high psychic potential. Thus, all command staff are inherently more likely to obtain Psychic Powers should they also have the Latent Psychic trait." + - "You can craft a tinfoil hat out of sheet metal. Wearing a tinfoil hat provides temporary protection from Psychic Powers! Beware, that it also disrupts the powers of any Psion who wears one." + - "You can press the R key to lay down on the ground. While Laying down, you can crawl underneath certain objects. Additionally, bullets will pass harmlessly over someone laying down, unless the shooter targets them directly." diff --git a/Resources/Prototypes/DeltaV/Body/Organs/harpy.yml b/Resources/Prototypes/DeltaV/Body/Organs/harpy.yml index 2d47ecd352..adc626bc11 100644 --- a/Resources/Prototypes/DeltaV/Body/Organs/harpy.yml +++ b/Resources/Prototypes/DeltaV/Body/Organs/harpy.yml @@ -10,7 +10,7 @@ - state: lung-r - type: Lung - type: Metabolizer - updateFrequency: 2.0 + updateInterval: 2.0 removeEmpty: true solutionOnBody: false solution: "Lung" diff --git a/Resources/Prototypes/DeltaV/Body/Prototypes/harpy.yml b/Resources/Prototypes/DeltaV/Body/Prototypes/harpy.yml index 25988f4a3a..b20b94cce2 100644 --- a/Resources/Prototypes/DeltaV/Body/Prototypes/harpy.yml +++ b/Resources/Prototypes/DeltaV/Body/Prototypes/harpy.yml @@ -47,3 +47,4 @@ part: RightFootHarpy left foot: part: LeftFootHarpy + diff --git a/Resources/Prototypes/DeltaV/Body/Prototypes/vulpkanin.yml b/Resources/Prototypes/DeltaV/Body/Prototypes/vulpkanin.yml index cdf787e473..4ac73acfc4 100644 --- a/Resources/Prototypes/DeltaV/Body/Prototypes/vulpkanin.yml +++ b/Resources/Prototypes/DeltaV/Body/Prototypes/vulpkanin.yml @@ -1,4 +1,4 @@ -- type: body +- type: body name: "vulpkanin" id: Vulpkanin root: torso diff --git a/Resources/Prototypes/DeltaV/Catalog/Fills/Boxes/pda.yml b/Resources/Prototypes/DeltaV/Catalog/Fills/Boxes/pda.yml index b8aff63ece..6ca3338e8d 100644 --- a/Resources/Prototypes/DeltaV/Catalog/Fills/Boxes/pda.yml +++ b/Resources/Prototypes/DeltaV/Catalog/Fills/Boxes/pda.yml @@ -41,12 +41,19 @@ id: BoxPDAScience description: A box of spare PDA microcomputers for the epistemics department. components: + - type: Item + shape: + - 0,0,4,3 + - type: Storage + grid: + - 0,0,3,2 # Increased storage space for more PDA types - type: StorageFill contents: - id: SciencePDA amount: 2 - id: ForensicMantisPDA - id: ChaplainPDA + - id: RoboticsPDA - type: entity name: engineering PDA box diff --git a/Resources/Prototypes/DeltaV/Catalog/Fills/Items/Belts/belts.yml b/Resources/Prototypes/DeltaV/Catalog/Fills/Items/Belts/belts.yml index ecffae4c36..141349e827 100644 --- a/Resources/Prototypes/DeltaV/Catalog/Fills/Items/Belts/belts.yml +++ b/Resources/Prototypes/DeltaV/Catalog/Fills/Items/Belts/belts.yml @@ -8,6 +8,16 @@ - id: Wakizashi - id: Katana +- type: entity + id: ClothingBeltKatanaSheathFilledHoS + parent: ClothingBeltKatanaSheathFilled + suffix: Filled + name: antique katana sheaths + description: An ornate belt, wrapped in gold filigree, with a ribbon made from a stasis-field preserved swatch of linen. The history of this sheath has been lost to time. + components: + - type: StealTarget + stealGroup: HoSAntiqueWeapon + - type: entity id: ClothingBeltCorpsmanWebbingFilled parent: ClothingBeltCorpsmanWebbing diff --git a/Resources/Prototypes/DeltaV/Catalog/Jukebox/Standard.yml b/Resources/Prototypes/DeltaV/Catalog/Jukebox/Standard.yml new file mode 100644 index 0000000000..aec4fc3503 --- /dev/null +++ b/Resources/Prototypes/DeltaV/Catalog/Jukebox/Standard.yml @@ -0,0 +1,104 @@ +# sorted alphabetically based on filenames in the folder Resources/Audio/DeltaV/Jukebox +# keep it ordered or I'll stab you + +- type: jukebox + id: ADiffReal + name: Andreas Viklund - A.D.R (Lagoona rmx) + path: + path: /Audio/DeltaV/Jukebox/a_different_reality_lagoona_remix.xm-MONO.ogg + +- type: jukebox + id: AggAss + name: melcom - Aggravated Assault + path: + path: /Audio/DeltaV/Jukebox/aggravated.it-MONO.ogg + +- type: jukebox + id: AutEqu + name: lemonade - Autumnal Equinox + path: + path: /Audio/DeltaV/Jukebox/autumnal_equinox.xm-MONO.ogg + +- type: jukebox + id: DosHighUmb + name: MASTER BOOT RECORD - DOS=HIGH, UMB + path: + path: /Audio/DeltaV/Jukebox/DOS=HIGH,_UMB.ogg + +- type: jukebox + id: DrozAlone + name: Drozerix - Alone + path: + path: /Audio/DeltaV/Jukebox/drozerix_-_alone.xm-MONO.ogg + +- type: jukebox + id: DrozLeisure + name: Drozerix - Leisurely Voice + path: + path: /Audio/DeltaV/Jukebox/drozerix_-_leisurely_voice.xm-MONO.ogg + +- type: jukebox + id: SunbeamEvery + name: Sunbeamstress - Every Light Is Blinking At Once + path: + path: /Audio/DeltaV/Jukebox/every_light_is_blinking_at_onceMONO.ogg + +- type: jukebox + id: KCHaxors + name: Karl Casey @ White Bat Audio - Hackers + path: + path: /Audio/DeltaV/Jukebox/hackers-MONO.ogg + +- type: jukebox + id: SunbeamLaser + name: Sunbeamstress - Lasers Rip Apart The Bulkhead + path: + path: /Audio/DeltaV/Jukebox/lasers_rip_apart_the_bulkheadMONO.ogg + +- type: jukebox + id: IanMarhaba + name: Ian Alex Mac. - Marhaba + path: + path: /Audio/DeltaV/Jukebox/marhaba-MONO.ogg + +- type: jukebox + id: PTMinute + name: Patricia Taxxon - Minute + path: + path: /Audio/DeltaV/Jukebox/Patricia_Taxxon_-_Minute_-_MONO.ogg + +- type: jukebox + id: SunbeamPhoron + name: Sunbeamstress - Phoron Will Make Us Rich + path: + path: /Audio/DeltaV/Jukebox/Phoron_Will_Make_Us_RichMONO2.ogg + +- type: jukebox + id: NymphsForest + name: Psirius - Nymphs of the Forest + path: + path: /Audio/DeltaV/Jukebox/psirius_-_nymphs_of_the_forest.mptm-MONO.ogg + +- type: jukebox + id: GhirScratch + name: ghirardelli7 - Scratch Post + path: + path: /Audio/DeltaV/Jukebox/Scratch_Post_-_OST_MONO.ogg + +- type: jukebox + id: JukeShiba + name: Dot Nigou - Shibamata + path: + path: /Audio/DeltaV/Jukebox/shibamata-MONO.ogg + +- type: jukebox + id: SpaceAsshowl + name: Chris Remo - Space Asshole + path: + path: /Audio/DeltaV/Jukebox/space_asshole-MONO.ogg + +- type: jukebox + id: AmieSuperpos + name: Amie Waters - Superposition + path: + path: /Audio/DeltaV/Jukebox/superposition-MONO.ogg diff --git a/Resources/Prototypes/DeltaV/Catalog/VendingMachines/Inventories/courierdrobe.yml b/Resources/Prototypes/DeltaV/Catalog/VendingMachines/Inventories/courierdrobe.yml index 88f691ba9b..778b372585 100644 --- a/Resources/Prototypes/DeltaV/Catalog/VendingMachines/Inventories/courierdrobe.yml +++ b/Resources/Prototypes/DeltaV/Catalog/VendingMachines/Inventories/courierdrobe.yml @@ -7,8 +7,10 @@ CourierBag: 2 ClothingHeadsetCargo: 2 ClothingOuterWinterCargo: 2 - ClothingUniformMailCarrier: 2 # Nyanotrasen - Mail Carrier old gear - ClothingUniformSkirtMailCarrier: 2 # Nyanotrasen - Mail Carrier old gear - ClothingHeadMailCarrier: 2 # Nyanotrasen - Mail Carrier old gear - MailBag: 2 # Nyanotrasen - Mail Carrier old gear - ClothingOuterWinterCoatMail: 2 # Nyanotrasen - Mail Carrier old gear + ClothingUniformMailCarrier: 2 + ClothingUniformSkirtMailCarrier: 2 + ClothingHeadMailCarrier: 2 + MailBag: 2 + ClothingOuterWinterCoatMail: 2 + WeaponMailLake: 2 + BoxMailCapsulePrimed: 4 diff --git a/Resources/Prototypes/DeltaV/Entities/Clothing/Eyes/hud.yml b/Resources/Prototypes/DeltaV/Entities/Clothing/Eyes/hud.yml index a959b64a38..1e8a64b0df 100644 --- a/Resources/Prototypes/DeltaV/Entities/Clothing/Eyes/hud.yml +++ b/Resources/Prototypes/DeltaV/Entities/Clothing/Eyes/hud.yml @@ -20,10 +20,9 @@ - type: Tag tags: - HudMedical - - GlassesNearsight - type: entity - parent: ClothingEyesBase + parent: [ ClothingEyesBase, ShowSecurityIcons ] id: ClothingEyesPrescriptionHudSecurity name: prescription security hud description: A poorly done and rushed mix between half of a pair of prescription glasses and a security HUD allowing you to see clearly out of one eye and inspect the employee's ID and warning status in the other! @@ -32,17 +31,15 @@ sprite: DeltaV/Clothing/Eyes/Hud/prescsechud.rsi - type: Clothing sprite: DeltaV/Clothing/Eyes/Hud/prescsechud.rsi - - type: ShowSecurityIcons - type: Construction graph: PrescriptionSecHud node: prescsechud - type: Tag tags: - HudSecurity - - GlassesNearsight - + - type: entity - parent: ClothingEyesBase + parent: [ ClothingEyesBase, ShowSecurityIcons ] id: ClothingEyesHudSyndicateMed name: syndicate medical visor description: An upgraded syndicate visor with automatic health readings, designed for better detection of humanoids and their subsequent elimination. @@ -52,7 +49,6 @@ - type: Clothing sprite: DeltaV/Clothing/Eyes/Hud/syndmed.rsi - type: ShowSyndicateIcons - - type: ShowSecurityIcons - type: ShowHealthBars damageContainers: - Biological diff --git a/Resources/Prototypes/DeltaV/Entities/Markers/Spawners/ghost_roles.yml b/Resources/Prototypes/DeltaV/Entities/Markers/Spawners/ghost_roles.yml index 42dd8a7fa0..b3fcbc9075 100644 --- a/Resources/Prototypes/DeltaV/Entities/Markers/Spawners/ghost_roles.yml +++ b/Resources/Prototypes/DeltaV/Entities/Markers/Spawners/ghost_roles.yml @@ -1,7 +1,3 @@ -#Delta-V - This file is licensed under AGPLv3 -# Copyright (c) 2024 Delta-V Contributors -# See AGPLv3.txt for details. - - type: entity id: SpawnPointPlayerCharacter name: ghost role spawn point diff --git a/Resources/Prototypes/DeltaV/Entities/Mobs/Customization/Markings/felinid.yml b/Resources/Prototypes/DeltaV/Entities/Mobs/Customization/Markings/felinid.yml index 795bcaacb0..34ccbf068a 100644 --- a/Resources/Prototypes/DeltaV/Entities/Mobs/Customization/Markings/felinid.yml +++ b/Resources/Prototypes/DeltaV/Entities/Mobs/Customization/Markings/felinid.yml @@ -2,7 +2,7 @@ id: FelinidFluffyTailRings bodyPart: Tail markingCategory: Tail - speciesRestriction: [Felinid] + speciesRestriction: [Felinid, Human] sprites: - sprite: DeltaV/Mobs/Customization/Felinid/felinid_tails.rsi state: Felinid_fluffy_tail_full @@ -13,7 +13,7 @@ id: FelinidFluffyTail bodyPart: Tail markingCategory: Tail - speciesRestriction: [Felinid] + speciesRestriction: [Felinid, Human] sprites: - sprite: DeltaV/Mobs/Customization/Felinid/felinid_tails.rsi state: Felinid_fluffy_tail_full @@ -22,7 +22,7 @@ id: FelinidAlternativeTail bodyPart: Tail markingCategory: Tail - speciesRestriction: [Felinid] + speciesRestriction: [Felinid, Human] sprites: - sprite: DeltaV/Mobs/Customization/Felinid/alternative_tail.rsi state: m_waggingtail_cat_FRONT diff --git a/Resources/Prototypes/DeltaV/Entities/Mobs/Customization/Markings/vulpkanin.yml b/Resources/Prototypes/DeltaV/Entities/Mobs/Customization/Markings/vulpkanin.yml index 69bbb2bd96..7b8fd987b5 100644 --- a/Resources/Prototypes/DeltaV/Entities/Mobs/Customization/Markings/vulpkanin.yml +++ b/Resources/Prototypes/DeltaV/Entities/Mobs/Customization/Markings/vulpkanin.yml @@ -5,7 +5,7 @@ id: VulpEar bodyPart: HeadTop markingCategory: HeadTop - speciesRestriction: [Vulpkanin] + speciesRestriction: [Vulpkanin, Human] sprites: - sprite: DeltaV/Mobs/Customization/Vulpkanin/ear_markings.rsi state: vulp @@ -16,7 +16,7 @@ id: VulpEarFade bodyPart: HeadTop markingCategory: HeadTop - speciesRestriction: [Vulpkanin] + speciesRestriction: [Vulpkanin, Human] sprites: - sprite: DeltaV/Mobs/Customization/Vulpkanin/ear_markings.rsi state: vulp @@ -27,7 +27,7 @@ id: VulpEarSharp bodyPart: HeadTop markingCategory: HeadTop - speciesRestriction: [Vulpkanin] + speciesRestriction: [Vulpkanin, Human] sprites: - sprite: DeltaV/Mobs/Customization/Vulpkanin/ear_markings.rsi state: vulp @@ -38,7 +38,7 @@ id: VulpEarJackal bodyPart: HeadTop markingCategory: HeadTop - speciesRestriction: [Vulpkanin] + speciesRestriction: [Vulpkanin, Human] sprites: - sprite: DeltaV/Mobs/Customization/Vulpkanin/ear_markings.rsi state: jackal @@ -49,7 +49,7 @@ id: VulpEarTerrier bodyPart: HeadTop markingCategory: HeadTop - speciesRestriction: [Vulpkanin] + speciesRestriction: [Vulpkanin, Human] sprites: - sprite: DeltaV/Mobs/Customization/Vulpkanin/ear_markings.rsi state: terrier @@ -60,7 +60,7 @@ id: VulpEarWolf bodyPart: HeadTop markingCategory: HeadTop - speciesRestriction: [Vulpkanin] + speciesRestriction: [Vulpkanin, Human] sprites: - sprite: DeltaV/Mobs/Customization/Vulpkanin/ear_markings.rsi state: wolf @@ -71,7 +71,7 @@ id: VulpEarFennec bodyPart: HeadTop markingCategory: HeadTop - speciesRestriction: [Vulpkanin] + speciesRestriction: [Vulpkanin, Human] sprites: - sprite: DeltaV/Mobs/Customization/Vulpkanin/ear_markings.rsi state: fennec @@ -82,7 +82,7 @@ id: VulpEarFox bodyPart: HeadTop markingCategory: HeadTop - speciesRestriction: [Vulpkanin] + speciesRestriction: [Vulpkanin, Human] sprites: - sprite: DeltaV/Mobs/Customization/Vulpkanin/ear_markings.rsi state: fox @@ -91,7 +91,7 @@ id: VulpEarOtie bodyPart: HeadTop markingCategory: HeadTop - speciesRestriction: [Vulpkanin] + speciesRestriction: [Vulpkanin, Human] sprites: - sprite: DeltaV/Mobs/Customization/Vulpkanin/ear_markings.rsi state: otie @@ -102,7 +102,7 @@ id: VulpEarTajaran bodyPart: HeadTop markingCategory: HeadTop - speciesRestriction: [Vulpkanin] + speciesRestriction: [Vulpkanin, Human] sprites: - sprite: DeltaV/Mobs/Customization/Vulpkanin/ear_markings.rsi state: msai @@ -113,7 +113,7 @@ id: VulpEarShock bodyPart: HeadTop markingCategory: HeadTop - speciesRestriction: [Vulpkanin] + speciesRestriction: [Vulpkanin, Human] sprites: - sprite: DeltaV/Mobs/Customization/Vulpkanin/ear_markings.rsi state: shock @@ -122,7 +122,7 @@ id: VulpEarCoyote bodyPart: HeadTop markingCategory: HeadTop - speciesRestriction: [Vulpkanin] + speciesRestriction: [Vulpkanin, Human] sprites: - sprite: DeltaV/Mobs/Customization/Vulpkanin/ear_markings.rsi state: coyote @@ -131,7 +131,7 @@ id: VulpEarDalmatian bodyPart: HeadTop markingCategory: HeadTop - speciesRestriction: [Vulpkanin] + speciesRestriction: [Vulpkanin, Human] sprites: - sprite: DeltaV/Mobs/Customization/Vulpkanin/ear_markings.rsi state: dalmatian @@ -272,7 +272,7 @@ id: VulpTail bodyPart: Tail markingCategory: Tail - speciesRestriction: [Vulpkanin] + speciesRestriction: [Vulpkanin, Human] sprites: - sprite: DeltaV/Mobs/Customization/Vulpkanin/tail_markings.rsi state: vulp @@ -294,7 +294,7 @@ id: VulpTailTip bodyPart: Tail markingCategory: Tail - speciesRestriction: [Vulpkanin] + speciesRestriction: [Vulpkanin, Human] sprites: - sprite: DeltaV/Mobs/Customization/Vulpkanin/tail_markings.rsi state: vulp @@ -316,7 +316,7 @@ id: VulpTailAlt bodyPart: Tail markingCategory: Tail - speciesRestriction: [Vulpkanin] + speciesRestriction: [Vulpkanin, Human] sprites: - sprite: DeltaV/Mobs/Customization/Vulpkanin/tail_markings.rsi state: vulp_alt @@ -327,7 +327,7 @@ id: VulpTailAltTip bodyPart: Tail markingCategory: Tail - speciesRestriction: [Vulpkanin] + speciesRestriction: [Vulpkanin, Human] sprites: - sprite: DeltaV/Mobs/Customization/Vulpkanin/tail_markings.rsi state: vulp_alt @@ -338,7 +338,7 @@ id: VulpTailLong bodyPart: Tail markingCategory: Tail - speciesRestriction: [Vulpkanin] + speciesRestriction: [Vulpkanin, Human] sprites: - sprite: DeltaV/Mobs/Customization/Vulpkanin/tail_markings.rsi state: long @@ -349,7 +349,7 @@ id: VulpTailFox bodyPart: Tail markingCategory: Tail - speciesRestriction: [Vulpkanin] + speciesRestriction: [Vulpkanin, Human] sprites: - sprite: DeltaV/Mobs/Customization/Vulpkanin/tail_markings.rsi state: fox @@ -371,7 +371,7 @@ id: VulpTailFoxTip bodyPart: Tail markingCategory: Tail - speciesRestriction: [Vulpkanin] + speciesRestriction: [Vulpkanin, Human] sprites: - sprite: DeltaV/Mobs/Customization/Vulpkanin/tail_markings.rsi state: fox @@ -393,7 +393,7 @@ id: VulpTailBushy bodyPart: Tail markingCategory: Tail - speciesRestriction: [Vulpkanin] + speciesRestriction: [Vulpkanin, Human] sprites: - sprite: DeltaV/Mobs/Customization/Vulpkanin/tail_markings.rsi state: bushfluff @@ -411,7 +411,7 @@ id: VulpTailCoyote bodyPart: Tail markingCategory: Tail - speciesRestriction: [Vulpkanin] + speciesRestriction: [Vulpkanin, Human] sprites: - sprite: DeltaV/Mobs/Customization/Vulpkanin/tail_markings.rsi state: coyote @@ -429,7 +429,7 @@ id: VulpTailCorgi bodyPart: Tail markingCategory: Tail - speciesRestriction: [Vulpkanin] + speciesRestriction: [Vulpkanin, Human] sprites: - sprite: DeltaV/Mobs/Customization/Vulpkanin/tail_markings.rsi state: corgi @@ -447,7 +447,7 @@ id: VulpTailHusky bodyPart: Tail markingCategory: Tail - speciesRestriction: [Vulpkanin] + speciesRestriction: [Vulpkanin, Human] sprites: - sprite: DeltaV/Mobs/Customization/Vulpkanin/tail_markings.rsi state: husky-inner @@ -458,7 +458,7 @@ id: VulpTailHuskyAlt bodyPart: Tail markingCategory: Tail - speciesRestriction: [Vulpkanin] + speciesRestriction: [Vulpkanin, Human] sprites: - sprite: DeltaV/Mobs/Customization/Vulpkanin/tail_markings.rsi state: husky @@ -467,7 +467,7 @@ id: VulpTailFox2 bodyPart: Tail markingCategory: Tail - speciesRestriction: [Vulpkanin] + speciesRestriction: [Vulpkanin, Human] sprites: - sprite: DeltaV/Mobs/Customization/Vulpkanin/tail_markings.rsi state: fox2 @@ -476,7 +476,7 @@ id: VulpTailFox3 bodyPart: Tail markingCategory: Tail - speciesRestriction: [Vulpkanin] + speciesRestriction: [Vulpkanin, Human] sprites: - sprite: DeltaV/Mobs/Customization/Vulpkanin/tail_markings.rsi state: fox3 @@ -487,7 +487,7 @@ id: VulpTailFennec bodyPart: Tail markingCategory: Tail - speciesRestriction: [Vulpkanin] + speciesRestriction: [Vulpkanin, Human] sprites: - sprite: DeltaV/Mobs/Customization/Vulpkanin/tail_markings.rsi state: fennec @@ -496,7 +496,7 @@ id: VulpTailOtie bodyPart: Tail markingCategory: Tail - speciesRestriction: [Vulpkanin] + speciesRestriction: [Vulpkanin, Human] sprites: - sprite: DeltaV/Mobs/Customization/Vulpkanin/tail_markings.rsi state: otie @@ -505,7 +505,7 @@ id: VulpTailFluffy bodyPart: Tail markingCategory: Tail - speciesRestriction: [Vulpkanin] + speciesRestriction: [Vulpkanin, Human] sprites: - sprite: DeltaV/Mobs/Customization/Vulpkanin/tail_markings.rsi state: fluffy @@ -514,7 +514,7 @@ id: VulpTailDalmatian bodyPart: Tail markingCategory: Tail - speciesRestriction: [Vulpkanin] + speciesRestriction: [Vulpkanin, Human] sprites: - sprite: DeltaV/Mobs/Customization/Vulpkanin/tail_markings.rsi state: dalmatian @@ -588,7 +588,7 @@ # Leg Markings - type: marking id: VulpPointsFeet - markingCategory: Overlay + markingCategory: RightFoot bodyPart: RFoot speciesRestriction: [Vulpkanin] sprites: @@ -597,7 +597,7 @@ - type: marking id: VulpPointsCrestLegs - markingCategory: Legs + markingCategory: LeftLeg bodyPart: LLeg speciesRestriction: [Vulpkanin] sprites: @@ -606,7 +606,7 @@ - type: marking id: VulpPointsFadeLegs - markingCategory: Legs + markingCategory: LeftLeg bodyPart: LLeg speciesRestriction: [Vulpkanin] sprites: @@ -615,7 +615,7 @@ - type: marking id: VulpPointsSharpLegs - markingCategory: Legs + markingCategory: LeftLeg bodyPart: LLeg speciesRestriction: [Vulpkanin] sprites: @@ -625,7 +625,7 @@ # Arm Markings - type: marking id: VulpPointsHands - markingCategory: Overlay + markingCategory: RightHand bodyPart: RHand speciesRestriction: [Vulpkanin] sprites: @@ -634,7 +634,7 @@ - type: marking id: VulpPointsCrestArms - markingCategory: Arms + markingCategory: LeftArm bodyPart: LArm speciesRestriction: [Vulpkanin] sprites: @@ -643,7 +643,7 @@ - type: marking id: VulpPointsFadeArms - markingCategory: Arms + markingCategory: LeftArm bodyPart: LArm speciesRestriction: [Vulpkanin] sprites: @@ -652,7 +652,7 @@ - type: marking id: VulpPointsSharpArms - markingCategory: Arms + markingCategory: LeftArm bodyPart: LArm speciesRestriction: [Vulpkanin] sprites: diff --git a/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/animals.yml index dd59d74d3f..674c7f3f82 100644 --- a/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/animals.yml @@ -1,6 +1,6 @@ - type: entity name: arctic fox - parent: SimpleMobBase + parent: MobFox id: MobArcticFox description: Wears a regal coat of snowy elegance, belying its cold and resourceful nature. components: @@ -30,8 +30,8 @@ - type: Strippable - type: UserInterface interfaces: - - key: enum.StrippingUiKey.Key - type: StrippableBoundUserInterface + enum.StrippingUiKey.Key: + type: StrippableBoundUserInterface - type: DamageStateVisuals states: Alive: @@ -40,10 +40,6 @@ Base: arcticfox_crit Dead: Base: arcticfox_dead - - type: Butcherable - spawned: - - id: FoodMeat - amount: 3 - type: InteractionPopup successChance: 0.5 interactSuccessString: petting-success-soft-floofy @@ -56,25 +52,10 @@ coldDamage: types: Cold: 0.1 - - type: Grammar - attributes: - gender: epicene - - type: Bloodstream - bloodMaxVolume: 100 - - type: MobPrice - price: 400 - - type: Tag - tags: - - VimPilot - - type: LanguageKnowledge - speaks: - - Fox - understands: - - Fox - type: entity name: security dog - parent: SimpleMobBase + parent: MobCorgi # There isn't a base dog mob, surprisingly. id: MobSecDog description: Funnily enough, this pig is actually a dog. components: @@ -101,7 +82,6 @@ - type: HTN rootTask: task: SimpleHostileCompound - - type: Physics - type: Speech speechVerb: Canine speechSounds: Vulpkanin @@ -145,8 +125,8 @@ - type: Strippable - type: UserInterface interfaces: - - key: enum.StrippingUiKey.Key - type: StrippableBoundUserInterface + enum.StrippingUiKey.Key: + type: StrippableBoundUserInterface - type: DamageStateVisuals states: Alive: @@ -184,4 +164,4 @@ - Dog understands: - Dog - - GalacticCommon + - TauCetiBasic diff --git a/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/familiars.yml b/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/familiars.yml index d40a72167f..1090927cf8 100644 --- a/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/familiars.yml +++ b/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/familiars.yml @@ -63,6 +63,15 @@ - type: Damageable damageContainer: CorporealSpirit damageModifierSet: CorporealSpirit + - type: PassiveDamage # Slight passive regen. Assuming one damage type, comes out to about 4 damage a minute. + allowedStates: + - Alive + damageCap: 120 + damage: + types: + Cold: -0.07 + groups: + Brute: -0.07 - type: Speech speechSounds: Bass - type: Puller @@ -81,10 +90,15 @@ - type: ZombieImmune # No zombie servant - type: RandomMetadata nameSegments: [names_golem] - - type: PotentialPsionic - type: Psionic removable: false - # - type: PyrokinesisPower # Pending psionic rework + psychognomicDescriptors: + - p-descriptor-bound + - p-descriptor-cyclic + - type: InnatePsionicPowers + powersToAdd: + - PyrokinesisPower + - TelepathyPower - type: Grammar attributes: proper: true @@ -97,19 +111,18 @@ - NanoTrasen - type: LanguageKnowledge speaks: - - GalacticCommon + - TauCetiBasic understands: - - GalacticCommon + - TauCetiBasic - type: GhostTakeoverAvailable - type: GhostRole makeSentient: true allowMovement: true allowSpeech: true requirements: - - !type:CharacterDepartmentRequirement - departments: - - Epistemics - min: 14400 # DeltaV - 4 hours + - !type:DepartmentTimeRequirement + department: Epistemics + time: 14400 # 4 hours - type: entity parent: WelderExperimental @@ -131,10 +144,6 @@ - type: Welder fuelReagent: Phlogiston tankSafe: true - litMeleeDamageBonus: - types: # When lit, negate standard melee damage and replace with heat - Heat: 10 - Blunt: -10 # welderOnSounds: # collection: WelderIfritHandOn # welderOffSounds: diff --git a/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/fun.yml b/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/fun.yml new file mode 100644 index 0000000000..96008395b3 --- /dev/null +++ b/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/fun.yml @@ -0,0 +1,63 @@ +- type: entity + name: space shrimp + parent: [ SimpleMobBase, FlyingMobBase, MobCombat ] + id: MobSpaceShrimp + description: Shrimply cursed... + components: + - type: MeleeWeapon + soundHit: + path: /Audio/Effects/pop.ogg + - type: MovementSpeedModifier + baseWalkSpeed : 6 + baseSprintSpeed : 6 + - type: Sprite + sprite: DeltaV/Mobs/Animals/shrimp.rsi + layers: + - map: ["enum.DamageStateVisualLayers.Base"] + state: shrimp + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeCircle + radius: 0.35 + density: 100 + mask: + - FlyingMobMask + layer: + - FlyingMobLayer + - type: Physics + - type: DamageStateVisuals + states: + Alive: + Base: shrimp + Dead: + Base: dead + - type: Butcherable + spawned: + - id: FoodMeatCrab + amount: 3 + - type: Bloodstream + bloodMaxVolume: 100 + bloodReagent: BbqSauce # Australia reference + - type: InteractionPopup + successChance: 0.5 + interactSuccessString: petting-success-possum + interactFailureString: petting-failure-possum + interactSuccessSpawn: EffectHearts + interactSuccessSound: + path: /Audio/Animals/raccoon_chatter.ogg + - type: Speech + speechSounds: Slime + - type: Puller + needsHands: false + - type: MindContainer + showExamineInfo: true + - type: NpcFactionMember + factions: + - Passive + - type: Body + prototype: Animal + - type: HTN + rootTask: + task: SimpleHostileCompound diff --git a/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/glimmer_creatures.yml b/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/glimmer_creatures.yml deleted file mode 100644 index e3eb9cd6de..0000000000 --- a/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/glimmer_creatures.yml +++ /dev/null @@ -1,32 +0,0 @@ -- type: entity - name: glimmer mite - parent: MobCockroach - id: MobGlimmerMite - description: A strange pest from a world beyond the noosphere. - components: - - type: Sprite - sprite: DeltaV/Mobs/Ghosts/glimmermite.rsi - layers: - - map: ["enum.DamageStateVisualLayers.Base"] - state: mite - - type: DamageStateVisuals - states: - Alive: - Base: mite - Dead: - Base: mite_dead - baseDecayRate: 0.25 - - type: SolutionContainerManager - solutions: - food: - reagents: - - ReagentId: Ectoplasm - Quantity: 15 - - type: PotentialPsionic - - type: Psionic - - type: GlimmerSource - - type: AmbientSound - range: 6 - volume: -3 - sound: /Audio/DeltaV/Glimmer_Creatures/mite.ogg - - type: AmbientOnPowered diff --git a/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/nukiemouse.yml b/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/nukiemouse.yml index 8968f0e77a..09cafa2957 100644 --- a/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/nukiemouse.yml +++ b/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/nukiemouse.yml @@ -67,8 +67,8 @@ - type: InventorySlots - type: UserInterface interfaces: - - key: enum.StrippingUiKey.Key - type: StrippableBoundUserInterface + enum.StrippingUiKey.Key: + type: StrippableBoundUserInterface - type: DamageStateVisuals states: Alive: @@ -101,7 +101,7 @@ - Mouse understands: - Mouse - - GalacticCommon + - TauCetiBasic - type: Tag tags: - VimPilot @@ -147,7 +147,7 @@ - type: Bloodstream bloodMaxVolume: 60 - type: CanEscapeInventory - BaseResistTime: 3 + baseResistTime: 3 - type: MobPrice price: 250 # Their suits, while tiny, go for quite a bit on the market - type: IntrinsicRadioReceiver diff --git a/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/salvage.yml b/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/salvage.yml index 507aa46793..808444ed70 100644 --- a/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/salvage.yml +++ b/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/salvage.yml @@ -1,7 +1,3 @@ -#Delta-V - This file is licensed under AGPLv3 -# Copyright (c) 2024 Delta-V Contributors -# See AGPLv3.txt for details. - - type: entity name: Syndicate Guard parent: BaseMobHuman diff --git a/Resources/Prototypes/DeltaV/Entities/Mobs/Player/humanoid.yml b/Resources/Prototypes/DeltaV/Entities/Mobs/Player/humanoid.yml index 2c8b01553a..4db83e9ab1 100644 --- a/Resources/Prototypes/DeltaV/Entities/Mobs/Player/humanoid.yml +++ b/Resources/Prototypes/DeltaV/Entities/Mobs/Player/humanoid.yml @@ -10,6 +10,8 @@ - type: randomHumanoidSettings id: SyndicateListener + speciesBlacklist: + - Shadowkin components: - type: Loadout prototypes: [SyndicateListenerGear] @@ -34,6 +36,8 @@ - type: randomHumanoidSettings id: Mobster randomizeName: false + speciesBlacklist: + - Shadowkin components: - type: GhostRole name: Mobster @@ -65,6 +69,8 @@ - type: randomHumanoidSettings id: MobsterAlt randomizeName: false + speciesBlacklist: + - Shadowkin components: - type: GhostRole name: Mobster diff --git a/Resources/Prototypes/DeltaV/Entities/Mobs/Player/vulpkanin.yml b/Resources/Prototypes/DeltaV/Entities/Mobs/Player/vulpkanin.yml index 718dc3ca55..d5dbb5f7ee 100644 --- a/Resources/Prototypes/DeltaV/Entities/Mobs/Player/vulpkanin.yml +++ b/Resources/Prototypes/DeltaV/Entities/Mobs/Player/vulpkanin.yml @@ -19,7 +19,6 @@ - type: NpcFactionMember factions: - NanoTrasen - - type: PotentialPsionic - type: Respirator damage: types: diff --git a/Resources/Prototypes/DeltaV/Entities/Mobs/Species/vulpkanin.yml b/Resources/Prototypes/DeltaV/Entities/Mobs/Species/vulpkanin.yml index 07152d74ef..b3027839b1 100644 --- a/Resources/Prototypes/DeltaV/Entities/Mobs/Species/vulpkanin.yml +++ b/Resources/Prototypes/DeltaV/Entities/Mobs/Species/vulpkanin.yml @@ -106,10 +106,10 @@ - type: Wagging - type: LanguageKnowledge speaks: - - GalacticCommon + - TauCetiBasic - Canilunzt understands: - - GalacticCommon + - TauCetiBasic - Canilunzt - type: ConsumeDelayModifier foodDelayMultiplier: 0.5 diff --git a/Resources/Prototypes/DeltaV/Entities/Objects/Devices/cartridges.yml b/Resources/Prototypes/DeltaV/Entities/Objects/Devices/cartridges.yml index def215cee4..81e11d9d08 100644 --- a/Resources/Prototypes/DeltaV/Entities/Objects/Devices/cartridges.yml +++ b/Resources/Prototypes/DeltaV/Entities/Objects/Devices/cartridges.yml @@ -38,3 +38,24 @@ sprite: Objects/Weapons/Melee/stunbaton.rsi state: stunbaton_on - type: SecWatchCartridge + +- type: entity + parent: BaseItem + id: MailMetricsCartridge + name: mail metrics cartridge + description: A cartridge that tracks statistics related to mail deliveries. + components: + - type: Sprite + sprite: DeltaV/Objects/Devices/cartridge.rsi + state: cart-mail + - type: Icon + sprite: DeltaV/Objects/Devices/cartridge.rsi + state: cart-mail + - type: UIFragment + ui: !type:MailMetricUi + - type: MailMetricsCartridge + - type: Cartridge + programName: mail-metrics-program-name + icon: + sprite: Objects/Specific/Mail/mail.rsi + state: icon diff --git a/Resources/Prototypes/DeltaV/Entities/Objects/Devices/pda.yml b/Resources/Prototypes/DeltaV/Entities/Objects/Devices/pda.yml index d5f121bb0b..b640206c91 100644 --- a/Resources/Prototypes/DeltaV/Entities/Objects/Devices/pda.yml +++ b/Resources/Prototypes/DeltaV/Entities/Objects/Devices/pda.yml @@ -1,7 +1,3 @@ -#Delta-V - This file is licensed under AGPLv3 -# Copyright (c) 2024 Delta-V Contributors -# See AGPLv3.txt for details. - - type: entity parent: BasePDA id: CorpsmanPDA @@ -118,7 +114,7 @@ - NotekeeperCartridge - NewsReaderCartridge - CrimeAssistCartridge - + - type: entity parent: BasePDA id: ProsecutorPDA diff --git a/Resources/Prototypes/DeltaV/Entities/Objects/Misc/paperslips.yml b/Resources/Prototypes/DeltaV/Entities/Objects/Misc/paperslips.yml index 3c25380e65..4ab4e54863 100644 --- a/Resources/Prototypes/DeltaV/Entities/Objects/Misc/paperslips.yml +++ b/Resources/Prototypes/DeltaV/Entities/Objects/Misc/paperslips.yml @@ -125,10 +125,10 @@ - type: Paper - type: ActivatableUI key: enum.PaperUiKey.Key - closeOnHandDeselect: false + requireActiveHand: false - type: UserInterface interfaces: - - key: enum.PaperUiKey.Key + enum.PaperUiKey.Key: type: PaperBoundUserInterface - type: Item size: Tiny diff --git a/Resources/Prototypes/DeltaV/Entities/Objects/Misc/subdermal_implants.yml b/Resources/Prototypes/DeltaV/Entities/Objects/Misc/subdermal_implants.yml index 93a9641f7b..4e8392870c 100644 --- a/Resources/Prototypes/DeltaV/Entities/Objects/Misc/subdermal_implants.yml +++ b/Resources/Prototypes/DeltaV/Entities/Objects/Misc/subdermal_implants.yml @@ -9,8 +9,12 @@ implantAction: ActionSyrinxChangeVoiceMask whitelist: components: - - HarpySinger - - type: VoiceMasker + - Flight # If someone else has the flight component, or you can obtain it in some way, this needs to be changed. To be a harpy only thing. + - type: VoiceMask + - type: UserInterface + interfaces: + enum.VoiceMaskUIKey.Key: + type: VoiceMaskBoundUserInterface - type: Tag tags: - SubdermalImplant diff --git a/Resources/Prototypes/DeltaV/Entities/Objects/Specific/Mail/mail.yml b/Resources/Prototypes/DeltaV/Entities/Objects/Specific/Mail/mail.yml index 7de554f5ff..6f96930078 100644 --- a/Resources/Prototypes/DeltaV/Entities/Objects/Specific/Mail/mail.yml +++ b/Resources/Prototypes/DeltaV/Entities/Objects/Specific/Mail/mail.yml @@ -1,3 +1,4 @@ +# DeltaV Mail - type: entity noSpawn: true parent: BaseMail @@ -175,3 +176,1643 @@ - type: Mail contents: - id: FoodPiePumpkin + +- type: entity + noSpawn: true + parent: BaseMail + id: MailDVCosplayFakeWizard + suffix: cosplay-wizard, fake as fuck + components: + - type: Mail + contents: + - id: ClothingOuterWizardFake + - id: ClothingHeadHatWizardFake + - id: ClothingShoesWizardFake + - id: FoodBurgerSpell + orGroup: FakeWizard + prob: 0.3 + - id: FoodKebabSkewer + orGroup: FakeWizard + prob: 0.1 + +- type: entity + parent: BaseMail + id: MailDVScarves + suffix: scarves + components: + - type: Mail + contents: + - id: ClothingNeckScarfStripedRed + orGroup: Scarf + - id: ClothingNeckScarfStripedBlue + orGroup: Scarf + - id: ClothingNeckScarfStripedGreen + orGroup: Scarf + - id: ClothingNeckScarfStripedBlack + orGroup: Scarf + - id: ClothingNeckScarfStripedBrown + orGroup: Scarf + - id: ClothingNeckScarfStripedLightBlue + orGroup: Scarf + - id: ClothingNeckScarfStripedOrange + orGroup: Scarf + - id: ClothingNeckScarfStripedPurple + orGroup: Scarf + - id: ClothingNeckScarfStripedZebra + orGroup: Scarf + - id: ClothingNeckScarfStripedSyndieGreen + orGroup: Scarf + prob: 0.25 + - id: ClothingNeckScarfStripedSyndieRed + orGroup: Scarf + prob: 0.25 + - id: ClothingNeckScarfStripedCentcom + orGroup: Scarf + prob: 0.001 + +- type: entity + parent: BaseMailLarge + id: MailDVBoxes + suffix: boxes + components: + - type: Mail + contents: +# - id: BoxEnvelope # TODO: we do not have this on EE? +# orGroup: Box + - id: BoxCardboard + orGroup: Box + - id: BoxMRE + orGroup: Box + - id: BoxPerformer + orGroup: Box + - id: BoxFlare + orGroup: Box + - id: BoxDarts + orGroup: Box + - id: BoxMousetrap + orGroup: Box + + + +# Frontier Mail, including Extended Nyano Mail. (Pony Express?) +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFAlcohol + suffix: alcohol, extended + components: + - type: Mail + isFragile: true + contents: + # 12.5 overall weight, 8% base chance + - id: DrinkAbsintheBottleFull + orGroup: Drink + - id: DrinkBlueCuracaoBottleFull + orGroup: Drink + - id: DrinkCoffeeLiqueurBottleFull + orGroup: Drink + - id: DrinkGinBottleFull + orGroup: Drink + - id: DrinkMelonLiquorBottleFull + orGroup: Drink + - id: DrinkRumBottleFull + orGroup: Drink + - id: DrinkTequilaBottleFull + orGroup: Drink + - id: DrinkVermouthBottleFull + orGroup: Drink + - id: DrinkVodkaBottleFull + orGroup: Drink + - id: DrinkWhiskeyBottleFull + orGroup: Drink + - id: DrinkWineBottleFull + orGroup: Drink + - id: DrinkChampagneBottleFull + orGroup: Drink + prob: 0.5 + - id: DrinkGildlagerBottleFull + orGroup: Drink + prob: 0.5 + - id: DrinkPatronBottleFull + orGroup: Drink + prob: 0.5 + - id: DrinkGlass + amount: 2 + +- type: entity + noSpawn: true + parent: BaseMailLarge + id: MailNFBible + suffix: bible, extended + components: + - type: Mail + contents: + - id: Bible + - id: ClothingHeadHatWitch1 #DeltaV - Compensates for items that don't exist here + - id: ClothingOuterCoatMNKBlackTopCoat #DeltaV - Compensates for items that don't exist here + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFBikeHorn + suffix: bike horn, random + components: + - type: Mail + contents: + - id: BikeHorn + orGroup: Horn + prob: 0.95 + - id: CluwneHorn + orGroup: Horn + prob: 0.05 + +- type: entity + noSpawn: true + parent: BaseMailLarge + id: MailNFBuildABuddy + suffix: Build-a-Buddy + components: + - type: Mail + isFragile: true + contents: +# - id: BoxBuildABuddyGoblin # DeltaV - Goblins Aren't Real +# orGroup: Box + - id: BoxBuildABuddyHuman + orGroup: Box + - id: BoxBuildABuddyReptilian + orGroup: Box + - id: BoxBuildABuddySlime + orGroup: Box + - id: BoxBuildABuddyVulpkanin + orGroup: Box + - id: DrinkSpaceGlue + - id: PaperMailNFBuildABuddy + +- type: entity + noSpawn: true + parent: BaseMailLarge + id: MailNFCake + suffix: cake, extended + components: + - type: Mail + isFragile: true + isPriority: true + contents: + # 14.8 total weight, ~6.8% base chance + - id: FoodCakeApple + orGroup: Cake + - id: FoodCakeBirthday + orGroup: Cake + - id: FoodCakeBlueberry + orGroup: Cake + - id: FoodCakeCarrot + orGroup: Cake + - id: FoodCakeCheese + orGroup: Cake + - id: FoodCakeChocolate + orGroup: Cake + - id: FoodCakeChristmas + orGroup: Cake + - id: FoodCakeClown + orGroup: Cake + - id: FoodCakeLemon + orGroup: Cake + - id: FoodCakeLime + orGroup: Cake + - id: FoodCakeOrange + orGroup: Cake + - id: FoodCakePumpkin + orGroup: Cake + - id: FoodCakeVanilla + orGroup: Cake + # Uncommon + - id: FoodMothMothmallow + orGroup: Cake + prob: 0.5 + - id: FoodCakeSlime + orGroup: Cake + prob: 0.5 + # Rare + - id: FoodCakeBrain + orGroup: Cake + prob: 0.25 + - id: FoodCakeLemoon + orGroup: Cake + prob: 0.25 + - id: FoodCakeSuppermatter + orGroup: Cake + prob: 0.25 + # Ultra rare + - id: FoodCakeSpaceman + orGroup: Cake + prob: 0.05 + - id: KnifePlastic + - id: ForkPlastic + amount: 2 + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFCosplayWizard + suffix: cosplay-wizard, extended + components: + - type: Mail + contents: + - id: ClothingOuterWizard + - id: ClothingHeadHatWizard + - id: ClothingShoesWizard + - id: PonderingOrb + orGroup: Staff + prob: 0.3 + - id: RGBStaff + orGroup: Staff + prob: 0.1 + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFCosplayMaid + suffix: cosplay-maid, extended + components: + - type: Mail + contents: + - id: UniformMaid + orGroup: Uniform + - id: ClothingUniformJumpskirtJanimaid + orGroup: Uniform + - id: ClothingUniformJumpskirtJanimaidmini + orGroup: Uniform + - id: MegaSprayBottle + - id: ClothingHandsGlovesColorWhite + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFCosplayNurse + suffix: cosplay-nurse, extended + components: + - type: Mail + contents: + - id: ClothingUniformJumpskirtNurse + - id: ClothingHeadNurseHat + - id: Syringe + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFCheese + suffix: cheese, extended + components: + - type: Mail + isFragile: true + isPriority: true + contents: + - id: FoodCheese + orGroup: Cheese + prob: 0.5 + - id: FoodChevre + orGroup: Cheese + prob: 0.5 + - id: KnifePlastic + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFCigarettes + suffix: cigs, random + components: + - type: Mail + contents: + - id: CigPackBlack + orGroup: Cigs + prob: 0.19 + - id: CigPackBlue + orGroup: Cigs + prob: 0.19 + - id: CigPackGreen + orGroup: Cigs + prob: 0.19 + - id: CigPackRed + orGroup: Cigs + prob: 0.19 + - id: CigPackMixed + orGroup: Cigs + prob: 0.09 # Pool shared with CigPackMixedMedical, CigPackMixedNasty + - id: CigPackMixedMedical + orGroup: Cigs + prob: 0.05 + - id: CigPackMixedNasty + orGroup: Cigs + prob: 0.05 + # Rare + - id: CigPackSyndicate + orGroup: Cigs + prob: 0.05 + - id: CheapLighter + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFEMP + suffix: emp + components: + - type: Mail + contents: + - id: DelayedEMP + - id: PaperMailNFEMPPreparedness + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFSmoke + suffix: smoke + components: + - type: Mail + contents: + - id: DelayedSmoke + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFGoldCigars + suffix: cigars, premium + components: + - type: Mail + contents: + - id: CigarGoldCase + orGroup: Cigars + - id: FlippoLighter + orGroup: Lighter + prob: 0.95 + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFCookies + suffix: cookies, random + components: + - type: Mail + isPriority: true + contents: + # Cookie 1 + - id: FoodBakedCookie + orGroup: Cookie1 + - id: FoodBakedCookieOatmeal + orGroup: Cookie1 + - id: FoodBakedCookieRaisin + orGroup: Cookie1 + - id: FoodBakedCookieSugar + orGroup: Cookie1 + # Cookie 2 + - id: FoodBakedCookie + orGroup: Cookie2 + - id: FoodBakedCookieOatmeal + orGroup: Cookie2 + - id: FoodBakedCookieRaisin + orGroup: Cookie2 + - id: FoodBakedCookieSugar + orGroup: Cookie2 + # Cookie 3 + - id: FoodBakedCookie + orGroup: Cookie3 + - id: FoodBakedCookieOatmeal + orGroup: Cookie3 + - id: FoodBakedCookieRaisin + orGroup: Cookie3 + - id: FoodBakedCookieSugar + orGroup: Cookie3 + # Cookie 4 + - id: FoodBakedCookie + orGroup: Cookie4 + - id: FoodBakedCookieOatmeal + orGroup: Cookie4 + - id: FoodBakedCookieRaisin + orGroup: Cookie4 + - id: FoodBakedCookieSugar + orGroup: Cookie4 + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFKnife + suffix: knife, extended + components: + - type: Mail + contents: + - id: KukriKnife + orGroup: Knife + - id: Machete # A little large for an envelope but "we'll live" + orGroup: Knife + - id: CombatKnife + orGroup: Knife + - id: SurvivalKnife + orGroup: Knife + +- type: entity + noSpawn: true + parent: BaseMailLarge + id: MailNFSword + suffix: sword + components: + - type: Mail + contents: + - id: KatanaDulled + orGroup: Sword + prob: 0.95 + - id: ClaymoreDulled + orGroup: Sword + prob: 0.95 + - id: FoamCutlass + orGroup: Sword + prob: 0.95 + - id: Katana + orGroup: Sword + prob: 0.5 + - id: Claymore + orGroup: Sword + prob: 0.5 +# - id: CaneSheathFilled # TODO: don't have this yet +# orGroup: Sword +# prob: 0.3 + - id: Cutlass + orGroup: Sword + prob: 0.1 + - id: Kanabou # ah yes, swords + orGroup: Sword + prob: 0.1 + - id: ClothingBeltSheathFilled # Little dangerous between the reflect and the damage + orGroup: Sword + prob: 0.001 + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFMuffins + suffix: muffins, random + components: + - type: Mail + isPriority: true + contents: + # Muffin 1 + - id: FoodBakedMuffinBerry + orGroup: Muffin1 + - id: FoodBakedMuffinCherry + orGroup: Muffin1 + - id: FoodBakedMuffinBluecherry + orGroup: Muffin1 + - id: FoodBakedMuffin + orGroup: Muffin1 + - id: FoodMothMoffin + orGroup: Muffin1 + prob: 0.5 + # Muffin 2 + - id: FoodBakedMuffinBerry + orGroup: Muffin2 + - id: FoodBakedMuffinCherry + orGroup: Muffin2 + - id: FoodBakedMuffinBluecherry + orGroup: Muffin2 + - id: FoodBakedMuffin + orGroup: Muffin2 + - id: FoodMothMoffin + orGroup: Muffin2 + prob: 0.5 + # Muffin 3 + - id: FoodBakedMuffinBerry + orGroup: Muffin3 + - id: FoodBakedMuffinCherry + orGroup: Muffin3 + - id: FoodBakedMuffinBluecherry + orGroup: Muffin3 + - id: FoodBakedMuffin + orGroup: Muffin3 + - id: FoodMothMoffin + orGroup: Muffin3 + prob: 0.5 + # Muffin 4 + - id: FoodBakedMuffinBerry + orGroup: Muffin4 + - id: FoodBakedMuffinCherry + orGroup: Muffin4 + - id: FoodBakedMuffinBluecherry + orGroup: Muffin4 + - id: FoodBakedMuffin + orGroup: Muffin4 + - id: FoodMothMoffin + orGroup: Muffin4 + prob: 0.5 + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFPAI + suffix: PAI, extended + components: + - type: Mail + contents: + - id: PersonalAI + orGroup: PAI + prob: 0.99 + # Ultra rare + - id: SyndicatePersonalAI + orGroup: PAI + prob: 0.01 + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFPlushie + suffix: plushie, extended + components: + - type: Mail + contents: + # These are all grouped up now to guarantee at least one item received. + # The downside is you're not going to get half a dozen plushies anymore. + - id: PlushieBee + orGroup: Plushie + - id: PlushieRGBee + prob: 0.5 + orGroup: Plushie + - id: PlushieNuke + orGroup: Plushie + - id: PlushieArachind #DeltaV - Adds MORE PLUSHIE + orGroup: Plushie + - id: PlushieAtmosian #DeltaV - Adds MORE PLUSHIE + orGroup: Plushie + - id: PlushieXeno #DeltaV - Adds MORE PLUSHIE + orGroup: Plushie + - id: PlushiePenguin #DeltaV - Adds MORE PLUSHIE + orGroup: Plushie + - id: PlushieGhost #DeltaV - Adds MORE PLUSHIE + orGroup: Plushie + - id: PlushieDiona #DeltaV - Adds MORE PLUSHIE + orGroup: Plushie + - id: ToyMouse #DeltaV - Adds MORE PLUSHIE + orGroup: Plushie + - id: PlushieRouny + orGroup: Plushie + - id: PlushieLizard + orGroup: Plushie + - id: PlushieSpaceLizard + orGroup: Plushie + - id: PlushieRatvar + orGroup: Plushie + - id: PlushieNar + orGroup: Plushie + - id: PlushieCarp + orGroup: Plushie + - id: PlushieHolocarp #DeltaV - Adds MORE PLUSHIE + orGroup: Plushie + - id: PlushieRainbowCarp #DeltaV - Adds MORE PLUSHIE + orGroup: Plushie + - id: PlushieSlime + orGroup: Plushie + - id: PlushieSnake + orGroup: Plushie + - id: PlushieMothRandom + orGroup: Plushie + - id: PlushieMoth + prob: 0.5 + orGroup: Plushie + - id: PlushieMothMusician + prob: 0.5 + orGroup: Plushie + - id: PlushieMothBartender + prob: 0.5 + orGroup: Plushie + +# Random snacks, replaces MailChocolate (lousy animal organs) +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFSnacks + suffix: snacks, random + components: + - type: Mail + contents: + # Snack 1 + - id: FoodSnackChocolate + orGroup: Snack1 + - id: FoodSnackPopcorn + orGroup: Snack1 + - id: FoodSnackChips + orGroup: Snack1 + - id: FoodSnackBoritos + orGroup: Snack1 + - id: FoodSnackSus + orGroup: Snack1 + - id: FoodSnackPistachios + orGroup: Snack1 + - id: FoodSnackRaisins + orGroup: Snack1 + - id: FoodSnackCheesie + orGroup: Snack1 + - id: FoodSnackEnergy + orGroup: Snack1 + - id: FoodSnackCnDs + orGroup: Snack1 + - id: FoodSnackSemki + orGroup: Snack1 + - id: FoodSnackSyndi + orGroup: Snack1 + prob: 0.5 + # Snack 2 + - id: FoodSnackChocolate + orGroup: Snack2 + - id: FoodSnackPopcorn + orGroup: Snack2 + - id: FoodSnackChips + orGroup: Snack2 + - id: FoodSnackBoritos + orGroup: Snack2 + - id: FoodSnackSus + orGroup: Snack2 + - id: FoodSnackPistachios + orGroup: Snack2 + - id: FoodSnackRaisins + orGroup: Snack2 + - id: FoodSnackCheesie + orGroup: Snack2 + - id: FoodSnackEnergy + orGroup: Snack2 + - id: FoodSnackCnDs + orGroup: Snack2 + - id: FoodSnackSemki + orGroup: Snack2 + - id: FoodSnackSyndi + orGroup: Snack2 + prob: 0.5 + # Snack 3 + - id: FoodSnackChocolate + orGroup: Snack3 + - id: FoodSnackPopcorn + orGroup: Snack3 + - id: FoodSnackChips + orGroup: Snack3 + - id: FoodSnackBoritos + orGroup: Snack3 + - id: FoodSnackSus + orGroup: Snack3 + - id: FoodSnackPistachios + orGroup: Snack3 + - id: FoodSnackRaisins + orGroup: Snack3 + - id: FoodSnackCheesie + orGroup: Snack3 + - id: FoodSnackEnergy + orGroup: Snack3 + - id: FoodSnackCnDs + orGroup: Snack3 + - id: FoodSnackSemki + orGroup: Snack3 + - id: FoodSnackSyndi + orGroup: Snack3 + prob: 0.5 + # Snack 4 + - id: FoodSnackChocolate + orGroup: Snack4 + - id: FoodSnackPopcorn + orGroup: Snack4 + - id: FoodSnackChips + orGroup: Snack4 + - id: FoodSnackBoritos + orGroup: Snack4 + - id: FoodSnackSus + orGroup: Snack4 + - id: FoodSnackPistachios + orGroup: Snack4 + - id: FoodSnackRaisins + orGroup: Snack4 + - id: FoodSnackCheesie + orGroup: Snack4 + - id: FoodSnackEnergy + orGroup: Snack4 + - id: FoodSnackCnDs + orGroup: Snack4 + - id: FoodSnackSemki + orGroup: Snack4 + - id: FoodSnackSyndi + orGroup: Snack4 + prob: 0.5 + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFVagueThreat + suffix: vague-threat + components: + - type: Mail + contents: + - id: PaperMailNFVagueThreat1 + orGroup: Paper + - id: PaperMailNFVagueThreat2 + orGroup: Paper + - id: PaperMailNFVagueThreat3 + orGroup: Paper + - id: PaperMailNFVagueThreat4 + orGroup: Paper + - id: PaperMailNFVagueThreat5 + orGroup: Paper + - id: PaperMailNFVagueThreat6 + orGroup: Paper + - id: PaperMailNFVagueThreat7 + orGroup: Paper + - id: PaperMailNFVagueThreat8 + orGroup: Paper + - id: PaperMailNFVagueThreat9 + orGroup: Paper + - id: PaperMailNFVagueThreat10 + orGroup: Paper + - id: PaperMailNFVagueThreat11 + orGroup: Paper + - id: PaperMailNFVagueThreat12 + orGroup: Paper + - id: KitchenKnife + orGroup: ThreateningObject + - id: ButchCleaver + orGroup: ThreateningObject + - id: CombatKnife + orGroup: ThreateningObject + - id: SurvivalKnife + orGroup: ThreateningObject + - id: SoapHomemade + orGroup: ThreateningObject + - id: FoodMeat + orGroup: ThreateningObject + - id: OrganHumanHeart + orGroup: ThreateningObject + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFDonkPockets + suffix: donk pockets, random + components: + - type: Mail + contents: + - id: FoodBoxDonkpocket + orGroup: Donk + prob: 0.4 # Higher probability, useful for chefs. + - id: FoodBoxDonkpocketSpicy + orGroup: Donk + prob: 0.1 + - id: FoodBoxDonkpocketTeriyaki + orGroup: Donk + prob: 0.1 + - id: FoodBoxDonkpocketPizza + orGroup: Donk + prob: 0.1 + - id: FoodBoxDonkpocketStonk + orGroup: Donk + prob: 0.05 + - id: FoodBoxDonkpocketCarp + orGroup: Donk + prob: 0.05 + - id: FoodBoxDonkpocketBerry + orGroup: Donk + prob: 0.1 + - id: FoodBoxDonkpocketHonk + orGroup: Donk + prob: 0.05 + - id: FoodBoxDonkpocketDink + orGroup: Donk + prob: 0.05 # Bad luck. + +# Needs a buff? +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFSodaPwrGame + suffix: Pwrgame + components: + - type: Mail + contents: + - id: DrinkPwrGameCan + amount: 3 + - id: PaperMailNFPwrGameAd + +# Needs a buff? +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFSodaRedBool + suffix: Red Bool + components: + - type: Mail + contents: + - id: DrinkEnergyDrinkCan + amount: 3 + - id: PaperMailNFRedBoolAd + +# Needs a buff? +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFSodaSpaceCola + suffix: Space Cola + components: + - type: Mail + contents: + - id: DrinkColaBottleFull + - id: PaperMailNFSpaceColaAd + +# Needs a buff? +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFSodaSpaceMountainWind + suffix: Space Mountain Wind + components: + - type: Mail + contents: + - id: DrinkSpaceMountainWindBottleFull + - id: PaperMailNFSpaceMountainWindAd + +# Needs a buff? +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFSodaSpaceUp + suffix: Space Up + components: + - type: Mail + contents: + - id: DrinkSpaceUpBottleFull + - id: PaperMailNFSpaceUpAd + +#TODO: we don't have rainbow joints or blunts (yet?). Uncomment when (if?) they are added. +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFJoints + suffix: joints + components: + - type: Mail + contents: + # Smokeable 1 + - id: Joint + orGroup: Smokeable1 + prob: 0.35 +# - id: JointRainbow +# orGroup: Smokeable1 +# prob: 0.15 +# - id: Blunt +# orGroup: Smokeable1 +# prob: 0.35 +# - id: BluntRainbow +# orGroup: Smokeable1 +# prob: 0.15 + # Smokeable 2 + - id: Joint + orGroup: Smokeable2 + prob: 0.35 +# - id: JointRainbow +# orGroup: Smokeable2 +# prob: 0.15 +# - id: Blunt +# orGroup: Smokeable2 +# prob: 0.35 +# - id: BluntRainbow +# orGroup: Smokeable2 +# prob: 0.15 + # Smokeable 3 + - id: Joint + orGroup: Smokeable3 + prob: 0.35 +# - id: JointRainbow +# orGroup: Smokeable3 +# prob: 0.15 +# - id: Blunt +# orGroup: Smokeable3 +# prob: 0.35 +# - id: BluntRainbow +# orGroup: Smokeable3 +# prob: 0.15 + +# Catchalls for food that only exist in random spawners +# Mmm, mail food +# Needs a buff? +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFUnusualFood + suffix: unusual food + components: + - type: Mail + isPriority: true + isFragile: true + contents: + - id: FoodMealNachos + orGroup: Food + - id: FoodMealNachosCheesy + orGroup: Food + - id: FoodMealNachosCuban + orGroup: Food + - id: FoodMealEggplantParm + orGroup: Food + - id: FoodMealPotatoYaki + orGroup: Food + - id: FoodMealCornedbeef + orGroup: Food + - id: FoodMealBearsteak + orGroup: Food + - id: FoodMealPigblanket + orGroup: Food + - id: FoodMealEggsbenedict + orGroup: Food + - id: FoodMealOmelette + orGroup: Food + - id: FoodMealFriedegg + orGroup: Food + - id: FoodMealMilkape + orGroup: Food + - id: FoodMealMemoryleek + orGroup: Food + - id: DisgustingSweptSoup + orGroup: Food + - id: FoodBreadVolcanic + orGroup: Food + - id: FoodBakedWaffleSoylent + orGroup: Food + - id: FoodBakedWaffleRoffle + orGroup: Food + - id: FoodPieCherry + orGroup: Food + - id: FoodPieFrosty + orGroup: Food + prob: 0.05 + - id: FoodMeatGoliathCooked + amount: 3 + orGroup: Food + - id: FoodMeatRounyCooked + amount: 3 + orGroup: Food + - id: FoodMeatLizardCooked + amount: 3 + orGroup: Food + - id: FoodMeatSpiderlegCooked + amount: 3 + orGroup: Food + - id: FoodMeatMeatballCooked + amount: 4 + orGroup: Food + +# Needs a buff? +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFBakedGoods + suffix: baked goods + components: + - type: Mail + isPriority: true + isFragile: true + contents: + - id: FoodBakedBunHoney + amount: 2 + orGroup: Food + - id: FoodBakedBunHotX + amount: 2 + orGroup: Food + - id: FoodBakedBunMeat + amount: 2 + orGroup: Food + - id: FoodBakedPretzel + amount: 2 + orGroup: Food + - id: FoodBakedCannoli + amount: 2 + orGroup: Food + - id: FoodDonutPlain + amount: 2 + orGroup: Food + - id: FoodDonutJellyPlain + amount: 2 + orGroup: Food + - id: FoodDonutHomer + amount: 2 + orGroup: Food + - id: FoodDonutChaos + amount: 2 + orGroup: Food + - id: FoodDonutMeat + amount: 2 + orGroup: Food + - id: FoodDonutPink + amount: 2 + orGroup: Food + - id: FoodDonutSpaceman + amount: 2 + orGroup: Food + - id: FoodDonutApple + amount: 2 + orGroup: Food + - id: FoodDonutCaramel + amount: 2 + orGroup: Food + - id: FoodDonutChocolate + amount: 2 + orGroup: Food +# - id: FoodDonutBluePumpkin # Don't have that yet +# amount: 2 +# orGroup: Food + - id: FoodDonutBungo + amount: 2 + orGroup: Food + - id: FoodDonut + amount: 2 + orGroup: Food + - id: FoodDonutSweetpea + amount: 2 + orGroup: Food + - id: FoodDonutJellyHomer + amount: 2 + orGroup: Food + - id: FoodDonutJellyPink + amount: 2 + orGroup: Food + - id: FoodDonutJellySpaceman + amount: 2 + orGroup: Food + - id: FoodDonutJellyApple + amount: 2 + orGroup: Food + - id: FoodDonutJellyCaramel + amount: 2 + orGroup: Food + - id: FoodDonutJellyChocolate + amount: 2 + orGroup: Food +# - id: FoodDonutJellyBluePumpkin # Don't have that yet +# amount: 2 +# orGroup: Food + - id: FoodDonutJellyBungo + amount: 2 + orGroup: Food + - id: FoodDonutJelly + amount: 2 + orGroup: Food + - id: FoodDonutJellySweetpea + amount: 2 + orGroup: Food + - id: FoodDonutJellySlugcat + amount: 2 + orGroup: Food + - id: FoodFrozenSandwich # ah yes, baked goods + amount: 2 + orGroup: Food + - id: FoodFrozenFreezy + amount: 2 + orGroup: Food + - id: FoodFrozenSundae + amount: 2 + orGroup: Food + - id: FoodFrozenCornuto + amount: 2 + orGroup: Food + - id: FoodFrozenPopsicleOrange + amount: 2 + orGroup: Food + - id: FoodFrozenPopsicleBerry + amount: 2 + orGroup: Food + - id: FoodFrozenPopsicleJumbo + amount: 2 + orGroup: Food + - id: FoodFrozenSnowcone + amount: 2 + orGroup: Food + - id: FoodFrozenSnowconeBerry + amount: 2 + orGroup: Food + - id: FoodFrozenSnowconeFruit + amount: 2 + orGroup: Food + - id: FoodFrozenSnowconeClown + amount: 2 + orGroup: Food + - id: FoodFrozenSnowconeMime + amount: 2 + orGroup: Food + - id: FoodFrozenSnowconeRainbow + amount: 2 + orGroup: Food + - id: FoodFrozenSnowconeMime + amount: 2 + orGroup: Food + - id: FoodMealMint # unlucky + amount: 2 + orGroup: Food + +# Needs a buff? +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFUnusualProduce + suffix: unusual produce + components: + - type: Mail + isPriority: true + isFragile: true + contents: + - id: FoodLaughinPeaPod + orGroup: Produce + amount: 5 + - id: FoodMimana + orGroup: Produce + amount: 5 + - id: FoodLemoon + orGroup: Produce + amount: 5 + - id: FoodBlueTomato + orGroup: Produce + amount: 5 + - id: FoodBloodTomato + orGroup: Produce + amount: 5 + - id: FoodKoibean + orGroup: Produce + amount: 5 +# EE does not have those yet. Uncomment if any of those get ported. +# - id: FoodGhostPepper #DeltaV +# orGroup: Produce +# amount: 5 +# - id: FoodCosmicRevenant #DeltaV +# orGroup: Produce +# amount: 5 +# - id: FoodCrystalThistle #DeltaV +# orGroup: Produce +# amount: 5 + - id: FoodLily + orGroup: Produce + amount: 5 + prob: 0.5 + - id: FoodAmbrosiaDeus + orGroup: Produce + amount: 5 + prob: 0.5 + - id: FoodSpacemansTrumpet + orGroup: Produce + amount: 5 + prob: 0.25 + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFSoaps + suffix: soap sampler + components: + - type: Mail + contents: + - id: BoxSoapsAssorted + - id: PaperMailNTSoapAd1 + orGroup: Ad + - id: PaperMailNTSoapAd2 + orGroup: Ad + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFSoapsOmega + suffix: soap sampler, omega + components: + - type: Mail + contents: + - id: BoxSoapsAssortedOmega + - id: PaperMailNTSoapAd1 + orGroup: Ad + - id: PaperMailNTSoapAd2 + orGroup: Ad + +# Could add spessman battle rules here +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFFigurineBulk # DeltaV - No longer Bulk + suffix: figurine, bulk #DeltaV - Spams 3 boxes instead of using the bulk figurine prototype that Frontier uses + components: + - type: Mail + contents: + - id: MysteryFigureBox + - id: MysteryFigureBox + - id: MysteryFigureBox + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFPen + suffix: fancy pen + components: + - type: Mail + contents: + - id: LuxuryPen + orGroup: Pen + prob: 0.50 + # DeltaV: Commenting these two out because I dunno if crew should have nice things + # - id: PenHop + # orGroup: Pen + # prob: 0.25 + # - id: PenCap + # orGroup: Pen + # prob: 0.25 + # TODO: come up with a slightly less powerful version of these + # Ultra-rare + # - id: CyberPen + # orGroup: Pen + # prob: 0.005 + # - id: PenCentcom + # orGroup: Pen + # prob: 0.005 + - id: PaperMailNFPaperPusherAd + +- type: entity + noSpawn: true + parent: BaseMailLarge + id: MailNFThrongler + suffix: throngler + components: + - type: Mail + contents: + - id: ThronglerToy + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFInstrumentSmall + suffix: instrument, expanded + components: + - type: Mail + contents: + - id: TrumpetInstrument + orGroup: Instrument + - id: RecorderInstrument + orGroup: Instrument + - id: ClarinetInstrument + orGroup: Instrument + - id: FluteInstrument + orGroup: Instrument + - id: HarmonicaInstrument + orGroup: Instrument + - id: OcarinaInstrument + orGroup: Instrument + - id: PanFluteInstrument + orGroup: Instrument + - id: KalimbaInstrument + orGroup: Instrument + - id: WoodblockInstrument + orGroup: Instrument + - id: BikeHornInstrument + orGroup: Instrument + - id: MusicBoxInstrument + orGroup: Instrument + - id: MicrophoneInstrument + orGroup: Instrument + - id: MusicalLungInstrument + orGroup: Instrument + # Uncommon + - id: PhoneInstrument + orGroup: Instrument + prob: 0.1 + # Rare + - id: BananaPhoneInstrument + orGroup: Instrument + prob: 0.05 + # Ultra-rare + - id: PhoneInstrumentSyndicate + orGroup: Instrument + prob: 0.01 + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFUnusualClothing + suffix: unusual clothing + components: + - type: Mail + contents: + - id: ClothingKimonoPink + orGroup: Clothes + - id: ClothingKimonoBlue + orGroup: Clothes + - id: ClothingKimonoPurple + orGroup: Clothes + - id: ClothingKimonoSky + orGroup: Clothes + - id: ClothingKimonoGreen + orGroup: Clothes + - id: ClothingUniformMartialGi + orGroup: Clothes + - id: ClothingNeckBling + orGroup: Clothes + - id: ClothingShoesBling + orGroup: Clothes + - id: ClothingNeckCloakAdmin + orGroup: Clothes + - id: ClothingHeadHatFancyCrown + orGroup: Clothes + - id: ClothingHeadHatCake + orGroup: Clothes + - id: ClothingHeadHatCone + orGroup: Clothes + - id: ClothingMaskOniRed + orGroup: Clothes + - id: ClothingMaskOniBlue + orGroup: Clothes + - id: ClothingHeadHatRichard + orGroup: Clothes + - id: ClothingHeadHatAnimalHeadslime + orGroup: Clothes + - id: ClothingHeadHatDogEars + orGroup: Clothes + - id: ClothingHeadHatCatEars + orGroup: Clothes + - id: ClothingEyesGlassesOutlawGlasses + orGroup: Clothes + - id: ClothingUniformJumpsuitGalaxyBlue + orGroup: Clothes + prob: 0.25 + - id: ClothingUniformJumpsuitGalaxyRed + orGroup: Clothes + prob: 0.25 + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFCritter + suffix: critter + components: + - type: Mail + isFragile: true + contents: + # Bugs (weight: 2) + - id: MobCockroach + orGroup: Critter + prob: 0.36 + - id: MobSlug + orGroup: Critter + prob: 0.36 + - id: MobArgocyteSlurva # honorary bug? + orGroup: Critter + prob: 0.36 + - id: MobBee + orGroup: Critter + prob: 0.36 + - id: MobButterfly + orGroup: Critter + prob: 0.36 + # Uncommon + - id: MobMothroach + orGroup: Critter + prob: 0.2 + # Small reptiles (weight: 1) + - id: MobLizard + orGroup: Critter + prob: 0.34 + - id: MobSnake + orGroup: Critter + prob: 0.33 + - id: MobFrog + orGroup: Critter + prob: 0.33 + # Small mammals (weight: 1) + - id: MobMouse + orGroup: Critter + prob: 0.33 + - id: MobMouse1 + orGroup: Critter + prob: 0.33 + - id: MobMouse2 + orGroup: Critter + prob: 0.33 + - id: MobMouseCancer + orGroup: Critter + prob: 0.01 # Rare + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFTacticalMaid + suffix: tactical maid + components: + - type: Mail + contents: + - id: ClothingUniformJumpskirtTacticalMaid + - id: ClothingHeadHatTacticalMaidHeadband + - id: MegaSprayBottle + - id: ClothingHandsTacticalMaidGloves + +- type: entity + noSpawn: true + parent: BaseMailLarge + id: MailNFKendoKit + suffix: kendo kit + components: + - type: Mail + contents: # A lot of stuff here, seems spammy. + - id: ClothingUniformKendoHakama + amount: 2 + - id: ClothingOuterArmorKendoBogu + amount: 2 + - id: ClothingHeadHelmetKendoMen + amount: 2 + - id: Shinai + amount: 2 + +# Base Nyano Mail +- type: entity + noSpawn: true + parent: BaseMail + id: MailSake + suffix: osake + components: + - type: Mail + contents: + - id: DrinkSakeCup + amount: 2 + - id: DrinkTokkuri + +- type: entity + noSpawn: true + parent: BaseMail + id: MailBlockGameDIY + suffix: blockgamediy + components: + - type: Mail + contents: + - id: BlockGameArcadeComputerCircuitboard + +- type: entity + noSpawn: true + parent: BaseMail + id: MailCigars + suffix: Cigars + components: + - type: Mail + contents: + - id: CigarCase + - id: Lighter + +- type: entity + noSpawn: true + parent: BaseMail + id: MailCrayon + suffix: Crayon + components: + - type: Mail + contents: + - id: CrayonBox + +- type: entity + noSpawn: true + parent: BaseMail + id: MailCosplayArc + suffix: cosplay-arc + components: + - type: Mail + openSound: /Audio/Nyanotrasen/Voice/Felinid/cat_wilhelm.ogg + contents: + - id: ClothingCostumeArcDress + +- type: entity + noSpawn: true + parent: BaseMail + id: MailCosplayGeisha + suffix: cosplay-geisha + components: + - type: Mail + contents: + - id: UniformGeisha + - id: DrinkTeapot + - id: DrinkTeacup + amount: 3 + +- type: entity + noSpawn: true + parent: BaseMail + id: MailCosplaySchoolgirl + suffix: cosplay-schoolgirl + components: + - type: Mail + contents: + - id: UniformSchoolgirlBlack + orGroup: Color + - id: UniformSchoolgirlBlue + orGroup: Color + - id: UniformSchoolgirlCyan + orGroup: Color + - id: UniformSchoolgirlGreen + orGroup: Color + - id: UniformSchoolgirlOrange + orGroup: Color + - id: UniformSchoolgirlPink + orGroup: Color + - id: UniformSchoolgirlPurple + orGroup: Color + - id: UniformSchoolgirlRed + orGroup: Color + +- type: entity + noSpawn: true + parent: BaseMail + id: MailFlowers + suffix: flowers + components: + - type: Mail + contents: + # TODO actual flowers + - id: ClothingHeadHatFlowerWreath + orGroup: Flowers + - id: FoodPoppy + orGroup: Flowers + - id: FoodLily + +- type: entity + noSpawn: true + parent: BaseMail + id: MailSignallerKit + suffix: signallerkit + components: + - type: Mail + contents: + - id: Multitool + - id: RemoteSignaller + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNoir + suffix: noir + components: + - type: Mail + contents: + - id: ClothingUniformJumpsuitDetectiveGrey + - id: ClothingUniformJumpskirtDetectiveGrey + - id: ClothingHeadHatBowlerHat + - id: ClothingOuterCoatGentle + +- type: entity + noSpawn: true + parent: BaseMail + id: MailRestraints + suffix: restraints + components: + - type: Mail + contents: + - id: Handcuffs + - id: ClothingMaskMuzzle + - id: ClothingEyesBlindfold + +- type: entity + noSpawn: true + parent: BaseMail + id: MailFishingCap + suffix: fishingcap + components: + - type: Mail + contents: + - id: ClothingHeadFishCap + +- type: entity + noSpawn: true + parent: BaseMail + id: MailFlashlight + suffix: Flashlight + components: + - type: Mail + contents: + - id: FlashlightLantern + +- type: entity + noSpawn: true + parent: BaseMail + id: MailSpaceVillainDIY + suffix: spacevilliandiy + components: + - type: Mail + contents: + - id: SpaceVillainArcadeComputerCircuitboard + +- type: entity + noSpawn: true + parent: BaseMail + id: MailSunglasses + suffix: Sunglasses + components: + - type: Mail + contents: + - id: ClothingEyesGlassesSunglasses + +- type: entity + noSpawn: true + parent: BaseMail + id: MailWinterCoat + suffix: wintercoat + components: + - type: Mail + contents: + - id: ClothingOuterWinterCoat + orGroup: Coat + - id: ClothingOuterWinterCoatLong + orGroup: Coat + - id: ClothingOuterWinterCoatPlaid + orGroup: Coat diff --git a/Resources/Prototypes/DeltaV/Entities/Objects/Specific/Mail/mail_civilian.yml b/Resources/Prototypes/DeltaV/Entities/Objects/Specific/Mail/mail_civilian.yml new file mode 100644 index 0000000000..19a1ee3c53 --- /dev/null +++ b/Resources/Prototypes/DeltaV/Entities/Objects/Specific/Mail/mail_civilian.yml @@ -0,0 +1,229 @@ +# Frontier Mail +- type: entity + noSpawn: true + parent: BaseMailLarge + id: MailNFInstrumentLarge + suffix: instrument, large + components: + - type: Mail + contents: + - id: TromboneInstrument + orGroup: Instrument + - id: FrenchHornInstrument + orGroup: Instrument + - id: SaxophoneInstrument + orGroup: Instrument + - id: EuphoniumInstrument + orGroup: Instrument + - id: AcousticGuitarInstrument + orGroup: Instrument + - id: ElectricGuitarInstrument + orGroup: Instrument + - id: BassGuitarInstrument + orGroup: Instrument + - id: RockGuitarInstrument + orGroup: Instrument + - id: BanjoInstrument + orGroup: Instrument + - id: ViolinInstrument + orGroup: Instrument + - id: CelloInstrument + orGroup: Instrument + - id: ViolaInstrument + orGroup: Instrument + - id: BagpipeInstrument # Fury. + orGroup: Instrument + - id: SynthesizerInstrument + orGroup: Instrument + - id: AccordionInstrument + orGroup: Instrument + - id: GlockenspielInstrument + orGroup: Instrument + - id: XylophoneInstrument + orGroup: Instrument + # Uncommon + - id: Rickenbacker4003Instrument + orGroup: Instrument + prob: 0.25 + # Rare + - id: Rickenbacker4001Instrument + orGroup: Instrument + prob: 0.1 + +# Base Nyano Mail + +- type: entity + noSpawn: true + parent: BaseMail + id: MailBotanistChemicalBottles + suffix: botanist chemicals + components: + - type: Mail + contents: + - id: RobustHarvestChemistryBottle + orGroup: Chemical + prob: 0.6 + - id: WeedSpray + orGroup: Chemical + prob: 0.4 + +- type: entity + noSpawn: true + parent: BaseMail + id: MailBotanistMutagen + suffix: mutagen + components: + - type: Mail + isFragile: true + isPriority: true + contents: + - id: UnstableMutagenChemistryBottle + +- type: entity + noSpawn: true + parent: BaseMail + id: MailBotanistSeeds + suffix: seeds + components: + - type: Mail + contents: + - id: AloeSeeds + orGroup: Seeds + - id: AmbrosiaVulgarisSeeds + orGroup: Seeds + - id: AppleSeeds + orGroup: Seeds + - id: BananaSeeds + orGroup: Seeds + - id: CarrotSeeds + orGroup: Seeds + - id: ChanterelleSeeds + orGroup: Seeds + - id: ChiliSeeds + orGroup: Seeds + - id: CornSeeds + orGroup: Seeds + - id: EggplantSeeds + orGroup: Seeds + - id: GalaxythistleSeeds + orGroup: Seeds + - id: LemonSeeds + orGroup: Seeds + - id: LingzhiSeeds + orGroup: Seeds + - id: OatSeeds + orGroup: Seeds + - id: OnionSeeds + orGroup: Seeds + - id: PoppySeeds + orGroup: Seeds + - id: PotatoSeeds + orGroup: Seeds + - id: SugarcaneSeeds + orGroup: Seeds + - id: TomatoSeeds + orGroup: Seeds + - id: TowercapSeeds + orGroup: Seeds + - id: WheatSeeds + orGroup: Seeds + +- type: entity + noSpawn: true + parent: BaseMail + id: MailClownGildedBikeHorn + suffix: honk + components: + - type: Mail + isFragile: true + contents: + - id: BikeHornInstrument + +- type: entity + noSpawn: true + parent: BaseMail + id: MailClownHonkSupplement + suffix: honk + components: + - type: Mail + isFragile: true + contents: + - id: BikeHorn + - id: FoodPieBananaCream + - id: FoodBanana + +- type: entity + noSpawn: true + parent: BaseMail + id: MailHoPBureaucracy + suffix: hop paper + components: + - type: Mail + contents: + - id: Paper + maxAmount: 3 + +- type: entity + noSpawn: true + parent: BaseMail + id: MailHoPSupplement + suffix: hop supplement + components: + - type: Mail + contents: + - id: ClearPDA + - id: ClothingHeadsetGrey + - id: Paper + +- type: entity + noSpawn: true + parent: BaseMail + id: MailMimeArtsCrafts + suffix: arts and crafts + components: + - type: Mail + contents: + - id: CrayonBox + - id: Paper + maxAmount: 3 + +- type: entity + noSpawn: true + parent: BaseMail + id: MailMimeBlankBook + suffix: blank book + components: + - type: Mail + contents: + - id: BookRandom + +- type: entity + noSpawn: true + parent: BaseMail + id: MailMimeBottleOfNothing + suffix: bottle of nothing + components: + - type: Mail + contents: + - id: DrinkBottleOfNothingFull + +- type: entity + noSpawn: true + parent: BaseMail + id: MailPassengerMoney + suffix: passenger money + components: + - type: Mail + contents: + - id: SpaceCash100 + orGroup: Cash + prob: 0.1 + maxAmount: 10 + - id: SpaceCash500 + orGroup: Cash + prob: 0.3 + maxAmount: 5 + - id: SpaceCash1000 + orGroup: Cash + prob: 0.6 + maxAmount: 3 diff --git a/Resources/Prototypes/DeltaV/Entities/Objects/Specific/Mail/mail_command.yml b/Resources/Prototypes/DeltaV/Entities/Objects/Specific/Mail/mail_command.yml new file mode 100644 index 0000000000..86dec46a65 --- /dev/null +++ b/Resources/Prototypes/DeltaV/Entities/Objects/Specific/Mail/mail_command.yml @@ -0,0 +1,35 @@ +# Base Nyano Mail +- type: entity + noSpawn: true + parent: BaseMail + id: MailCommandPinpointerNuclear + suffix: pinpointer mail ops + components: + - type: Mail + contents: + - id: PinpointerNuclear + +- type: entity + noSpawn: true + parent: BaseMail + id: MailStationRepNFNukeDisk + suffix: nuke disk + components: + - type: Mail + isFragile: true + contents: + - id: NukeDiskFake + - id: PaperMailNFAntivirus + +- type: entity + noSpawn: true + parent: BaseMail + id: MailCommandNFPipebombIntern + suffix: pipe and bomb + components: + - type: Mail + isFragile: true + contents: + - id: SmokingPipeFilledTobacco + - id: DrinkAtomicBombGlass + - id: PaperMailNFPipebombIntern diff --git a/Resources/Prototypes/DeltaV/Entities/Objects/Specific/Mail/mail_engineering.yml b/Resources/Prototypes/DeltaV/Entities/Objects/Specific/Mail/mail_engineering.yml new file mode 100644 index 0000000000..af70fc621c --- /dev/null +++ b/Resources/Prototypes/DeltaV/Entities/Objects/Specific/Mail/mail_engineering.yml @@ -0,0 +1,182 @@ +# Base Nyano Mail +- type: entity + noSpawn: true + parent: BaseMail + id: MailEngineeringCables + suffix: cables + components: + - type: Mail + contents: + - id: CableHVStack + orGroup: Cables + - id: CableMVStack + orGroup: Cables + - id: CableApcStack + orGroup: Cables + +- type: entity + noSpawn: true + parent: BaseMail + id: MailEngineeringKudzuDeterrent + suffix: antikudzu + components: + - type: Mail + contents: + - id: PlantBGoneSpray + +- type: entity + noSpawn: true + parent: BaseMail + id: MailEngineeringSheetGlass + suffix: sheetglass + components: + - type: Mail + contents: + - id: SheetGlass + +- type: entity + noSpawn: true + parent: BaseMail + id: MailEngineeringWelderReplacement + suffix: welder + components: + - type: Mail + contents: + - id: Welder + +# Frontier Mail + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFCircuitboardIndustrial + suffix: industrial circuitboard + components: + - type: Mail + contents: + - id: ThermomachineFreezerMachineCircuitBoard + orGroup: Board + prob: 0.5 + - id: ThermomachineHeaterMachineCircuitBoard + orGroup: Board + prob: 0.5 + - id: HellfireFreezerMachineCircuitBoard + orGroup: Board + prob: 0.25 + - id: HellfireHeaterMachineCircuitBoard + orGroup: Board + prob: 0.25 + - id: CryoPodMachineCircuitboard # Medical as well + orGroup: Board + prob: 0.5 + - id: ChemMasterMachineCircuitboard + orGroup: Board + prob: 0.5 + - id: ChemDispenserMachineCircuitboard + orGroup: Board + prob: 0.25 + - id: StasisBedMachineCircuitboard + orGroup: Board + prob: 0.25 + - id: BiomassReclaimerMachineCircuitboard + orGroup: Board + prob: 0.25 + - id: BiofabricatorMachineCircuitboard + orGroup: Board + prob: 0.25 + - id: TurboItemRechargerCircuitboard + orGroup: Board + prob: 0.5 + - id: AutolatheHyperConvectionMachineCircuitboard + orGroup: Board + prob: 0.25 + - id: ProtolatheHyperConvectionMachineCircuitboard + orGroup: Board + prob: 0.25 + - id: HotplateMachineCircuitboard + orGroup: Board + prob: 0.5 +# - id: CircuitImprinterHyperConvectionMachineCircuitboard +# orGroup: Board +# prob: 0.25 + - id: SheetifierMachineCircuitboard + orGroup: Board + prob: 0.25 + - id: RadarConsoleCircuitboard + orGroup: Board + prob: 0.25 + - id: OreProcessorIndustrialMachineCircuitboard + orGroup: Board + prob: 0.5 + - id: GasRecyclerMachineCircuitboard + orGroup: Board + prob: 0.1 + +- type: entity + noSpawn: true + parent: BaseMail + id: MailNFCircuitboardService + suffix: service circuitboard + components: + - type: Mail + contents: + - id: ComputerTelevisionCircuitboard + orGroup: Board + - id: ReagentGrinderMachineCircuitboard + orGroup: Board + - id: ReagentGrinderIndustrialMachineCircuitboard + orGroup: Board + prob: 0.5 + - id: SurveillanceWirelessCameraMovableCircuitboard + orGroup: Board + prob: 0.5 + - id: MicrowaveMachineCircuitboard + orGroup: Board + - id: ElectricGrillMachineCircuitboard + orGroup: Board + prob: 0.5 + - id: FatExtractorMachineCircuitboard + orGroup: Board + prob: 0.25 + - id: SeedExtractorMachineCircuitboard + orGroup: Board + prob: 0.5 + - id: BoozeDispenserMachineCircuitboard + orGroup: Board + - id: SodaDispenserMachineCircuitboard + orGroup: Board + - id: JukeboxCircuitBoard + orGroup: Board + - id: TelecomServerCircuitboard + orGroup: Board + prob: 0.25 + - id: ComputerMassMediaCircuitboard + orGroup: Board + prob: 0.1 + +- type: entity + noSpawn: true + parent: BaseMailLarge + id: MailNFPowerTool + suffix: power tool + components: + - type: Mail + contents: + - id: PaperMailNFPowerTool + orGroup: Paper + - id: JawsOfLife + orGroup: Gift + prob: 0.33 + - id: PowerDrill + orGroup: Gift + prob: 0.33 + - id: WelderIndustrial + orGroup: Gift + prob: 0.20 + # Rare + - id: WelderIndustrialAdvanced + orGroup: Gift + prob: 0.10 + - id: WelderExperimental + orGroup: Gift + prob: 0.04 diff --git a/Resources/Prototypes/DeltaV/Entities/Objects/Specific/Mail/mail_epistemology.yml b/Resources/Prototypes/DeltaV/Entities/Objects/Specific/Mail/mail_epistemology.yml new file mode 100644 index 0000000000..be6f9818e2 --- /dev/null +++ b/Resources/Prototypes/DeltaV/Entities/Objects/Specific/Mail/mail_epistemology.yml @@ -0,0 +1,48 @@ +- type: entity + noSpawn: true + parent: BaseMail + id: MailEpistemologyBluespace + suffix: bluespace + components: + - type: Mail + contents: + - id: MaterialBluespace1 + +- type: entity + noSpawn: true + parent: BaseMail + id: MailEpistemologyIngotGold + suffix: ingotgold + components: + - type: Mail + contents: + - id: IngotGold1 + maxAmount: 3 + +- type: entity + noSpawn: true + parent: BaseMail + id: MailEpistemologyResearchDisk + suffix: researchdisk + components: + - type: Mail + contents: + - id: ResearchDisk + orGroup: Disk + prob: 0.6 + - id: ResearchDisk5000 + orGroup: Disk + prob: 0.3 + - id: ResearchDisk10000 + orGroup: Disk + prob: 0.1 + +- type: entity + noSpawn: true + parent: BaseMail + id: MailEpistemologyTinfoilHat + suffix: tinfoilhat + components: + - type: Mail + contents: + - id: ClothingHeadTinfoil diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/mail_medical.yml b/Resources/Prototypes/DeltaV/Entities/Objects/Specific/Mail/mail_medical.yml similarity index 84% rename from Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/mail_medical.yml rename to Resources/Prototypes/DeltaV/Entities/Objects/Specific/Mail/mail_medical.yml index 4e797272e5..735f840a20 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/mail_medical.yml +++ b/Resources/Prototypes/DeltaV/Entities/Objects/Specific/Mail/mail_medical.yml @@ -1,3 +1,4 @@ +# Base Nyano Mail - type: entity noSpawn: true parent: BaseMail @@ -91,3 +92,19 @@ maxAmount: 2 - id: SyringeTranexamicAcid maxAmount: 2 + +# Frontier Mail +- type: entity + parent: BaseMailLarge + id: MailNFMedkit + suffix: medkit + components: + - type: Mail + contents: + - id: MedkitAdvancedFilled + orGroup: Medkit + prob: 0.75 + - id: MedkitCombatFilled + orGroup: Medkit + prob: 0.25 + diff --git a/Resources/Prototypes/DeltaV/Entities/Objects/Specific/Mail/mail_security.yml b/Resources/Prototypes/DeltaV/Entities/Objects/Specific/Mail/mail_security.yml new file mode 100644 index 0000000000..eed846a090 --- /dev/null +++ b/Resources/Prototypes/DeltaV/Entities/Objects/Specific/Mail/mail_security.yml @@ -0,0 +1,95 @@ +# Base Nyano Mail +- type: entity + noSpawn: true + parent: BaseMail + id: MailSecurityDonuts + suffix: donuts + components: + - type: Mail + contents: + - id: FoodBoxDonut + +- type: entity + noSpawn: true + parent: BaseMail + id: MailSecurityFlashlight + suffix: seclite + components: + - type: Mail + contents: + - id: FlashlightSeclite + +- type: entity + noSpawn: true + parent: BaseMail + id: MailSecurityNonlethalsKit + suffix: nonlethalskit + components: + - type: Mail + contents: + - id: Flash + maxAmount: 2 + - id: GrenadeFlashBang + maxAmount: 2 + - id: Handcuffs + maxAmount: 2 + +#- type: entity +# parent: BaseMail +# id: MailSecuritySpaceLaw +# suffix: spacelaw +# components: +# - type: Mail +# contents: +# - id: HyperlinkBookSpaceLaw +# Will we ever readd hyperlinks books? Who knows! + +- type: entity + noSpawn: true + parent: BaseMail + id: MailWardenCrowdControl + suffix: crowdcontrol + components: + - type: Mail + contents: + - id: BoxBeanbag + +- type: entity + noSpawn: true + parent: BaseMail + id: MailDetectiveForensicSupplement # Deltav - Detective is in charge of investigating crimes. + suffix: detectivesupplement # Deltav - Detective is in charge of investigating crimes. + components: + - type: Mail + contents: + - id: BoxForensicPad + +# Frontier Mail + +- type: entity + noSpawn: true + parent: BaseMailLarge + id: MailSecurityNFMusket + suffix: musket + components: + - type: Mail + contents: + - id: ClothingHeadHatPwig + - id: Musket + - id: CartridgeAntiMateriel + amount: 2 + - id: PaperMailNTMusket + +# Delta Mail + +- type: entity + noSpawn: true + parent: BaseMail + id: MailSecurityDVSpaceLaw + suffix: spacelaw, extended + components: + - type: Mail + contents: + - id: BookSecurity +# - id: BookSOP #This is where I'd put my Delta SOP book... IF I HAD ONE!!! + - id: PaperMailNFSpaceLaw # Uses the NF space law paper, which is edited to mention the Delta Sector diff --git a/Resources/Prototypes/DeltaV/Entities/Objects/Weapons/Guns/Ammunition/Projectiles/special.yml b/Resources/Prototypes/DeltaV/Entities/Objects/Weapons/Guns/Ammunition/Projectiles/special.yml index 6a8114d884..df2ccf2a94 100644 --- a/Resources/Prototypes/DeltaV/Entities/Objects/Weapons/Guns/Ammunition/Projectiles/special.yml +++ b/Resources/Prototypes/DeltaV/Entities/Objects/Weapons/Guns/Ammunition/Projectiles/special.yml @@ -92,6 +92,3 @@ blockSlots: NONE #shouldn't be blocked by a mask - type: InjectableSolution solution: ammo - - type: GuideHelp - guides: - - Psionics diff --git a/Resources/Prototypes/DeltaV/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml b/Resources/Prototypes/DeltaV/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml index 9ff83bfd48..8c8ce740a5 100644 --- a/Resources/Prototypes/DeltaV/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml +++ b/Resources/Prototypes/DeltaV/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml @@ -114,7 +114,7 @@ - type: StaticPrice price: 750 - type: StealTarget - stealGroup: WeaponEnergyGunMultiphase + stealGroup: HoSAntiqueWeapon - type: entity name: miniature energy gun @@ -166,6 +166,13 @@ Lethal: { state: mode-lethal } Special: { state: mode-stun } # Unused +- type: entity + name: miniature energy gun + parent: WeaponEnergyGunMini + id: WeaponEnergyGunMiniSecurity + description: A light version of the Energy gun with a smaller capacity. + The serial number on the handguard marks this gun as belonging to an NT Security Officer. + - type: entity name: PDW-9 Energy Pistol parent: BaseWeaponBatterySmall @@ -223,6 +230,13 @@ - type: StaticPrice price: 750 +- type: entity + name: PDW-9 Energy Pistol + parent: WeaponEnergyGunPistol + id: WeaponEnergyGunPistolSecurity + description: A military grade sidearm, used by many militia forces throughout the local sector. + The serial number on the handguard marks this gun as belonging to an NT Security Officer. + - type: entity name: IK-60 laser carbine parent: BaseWeaponBattery diff --git a/Resources/Prototypes/DeltaV/Entities/Objects/Weapons/Guns/Pistols/pistols.yml b/Resources/Prototypes/DeltaV/Entities/Objects/Weapons/Guns/Pistols/pistols.yml index 6f4d5b5230..405c321316 100644 --- a/Resources/Prototypes/DeltaV/Entities/Objects/Weapons/Guns/Pistols/pistols.yml +++ b/Resources/Prototypes/DeltaV/Entities/Objects/Weapons/Guns/Pistols/pistols.yml @@ -33,6 +33,13 @@ gun_magazine: !type:ContainerSlot gun_chamber: !type:ContainerSlot +- type: entity + name: viper + parent: WeaponPistolViperWood + id: WeaponPistolViperWoodSecurity + description: A small, low-power pistol with pleasant lacquered wooden grips. Uses .35 auto ammo. + The serial number on the handguard marks this gun as belonging to an NT Security Officer. + - type: entity name: Pollock parent: BaseWeaponPistol @@ -72,6 +79,38 @@ gun_magazine: !type:ContainerSlot gun_chamber: !type:ContainerSlot +- type: entity + name: Pollock + parent: WeaponPistolPollock + id: WeaponPistolPollockSecurity + description: A compact and mass-produced combat pistol. Uses .35 auto ammo. + The serial number on the handguard marks this gun as belonging to an NT Security Officer. + +- type: entity + name: Pollock + parent: WeaponPistolPollockSecurity + id: WeaponPistolPollockNonlethalSecurity + components: + - type: ItemSlots + slots: + gun_magazine: + name: Magazine + startingItem: MagazinePistolRubber + insertSound: /Audio/Weapons/Guns/MagIn/pistol_magin.ogg + ejectSound: /Audio/Weapons/Guns/MagOut/pistol_magout.ogg + priority: 2 + whitelist: + tags: + - MagazinePistol + - MagazinePistolHighCapacity + gun_chamber: + name: Chamber + startingItem: CartridgePistolRubber + priority: 1 + whitelist: + tags: + - CartridgePistol + - type: entity name: psi-breaker parent: BaseWeaponPistol diff --git a/Resources/Prototypes/DeltaV/Entities/Objects/Weapons/Guns/Revolvers/revolvers.yml b/Resources/Prototypes/DeltaV/Entities/Objects/Weapons/Guns/Revolvers/revolvers.yml index a19a21dd92..d74949a80a 100644 --- a/Resources/Prototypes/DeltaV/Entities/Objects/Weapons/Guns/Revolvers/revolvers.yml +++ b/Resources/Prototypes/DeltaV/Entities/Objects/Weapons/Guns/Revolvers/revolvers.yml @@ -22,6 +22,28 @@ chambers: [ True, True, True, True, True, True ] ammoSlots: [ null, null, null, null, null, null ] +- type: entity + name: snubbed .45 + parent: WeaponRevolverSnub + id: WeaponRevolverSnubSecurity + description: An old and reliable revolver, modified to be more easily concealed. Uses .45 magnum ammo. + The serial number on the handguard marks this gun as belonging to an NT Security Officer. + +- type: entity + name: snubbed .45 + parent: WeaponRevolverSnubSecurity + id: WeaponRevolverSnubNonlethalSecurity + components: + - type: RevolverAmmoProvider + whitelist: + tags: + - CartridgeMagnum + - SpeedLoaderMagnum + proto: CartridgeMagnumRubber + capacity: 6 + chambers: [ True, True, True, True, True, True ] + ammoSlots: [ null, null, null, null, null, null ] + - type: entity name: k-38 masterpiece parent: BaseWeaponRevolver @@ -37,6 +59,28 @@ fireRate: 2 availableModes: - SemiAuto + - type: RevolverAmmoProvider + whitelist: + tags: + - CartridgeSpecial + - SpeedLoaderSpecial + proto: CartridgeSpecial + capacity: 6 + chambers: [ True, True, True, True, True, True ] + ammoSlots: [ null, null, null, null, null, null ] + +- type: entity + name: k-38 masterpiece + parent: WeaponRevolverK38Master + id: WeaponRevolverK38MasterSecurity + description: A classic, if not outdated, law enforcement firearm. Uses .38 special ammo. + The serial number on the handguard marks this gun as belonging to an NT Security Officer. + +- type: entity + name: k-38 masterpiece + parent: WeaponRevolverK38MasterSecurity + id: WeaponRevolverK38MasterNonlethalSecurity + components: - type: RevolverAmmoProvider whitelist: tags: @@ -62,6 +106,28 @@ fireRate: 1.75 availableModes: - SemiAuto + - type: RevolverAmmoProvider + whitelist: + tags: + - CartridgeSpecial + - SpeedLoaderSpecial + proto: CartridgeSpecial + capacity: 6 + chambers: [ True, True, True, True, True, True ] + ammoSlots: [ null, null, null, null, null, null ] + +- type: entity + name: fitz special + parent: WeaponRevolverFitz + id: WeaponRevolverFitzSecurity + description: A compact and concealable self defence snub revolver. Uses .38 special ammo. + The serial number on the handguard marks this gun as belonging to an NT Security Officer. + +- type: entity + name: fitz special + parent: WeaponRevolverFitzSecurity + id: WeaponRevolverFitzNonlethalSecurity + components: - type: RevolverAmmoProvider whitelist: tags: diff --git a/Resources/Prototypes/DeltaV/Entities/Structures/Doors/Airlocks/access.yml b/Resources/Prototypes/DeltaV/Entities/Structures/Doors/Airlocks/access.yml index f6e7bcf257..04785c042b 100644 --- a/Resources/Prototypes/DeltaV/Entities/Structures/Doors/Airlocks/access.yml +++ b/Resources/Prototypes/DeltaV/Entities/Structures/Doors/Airlocks/access.yml @@ -4,16 +4,19 @@ id: AirlockMantisLocked suffix: Mantis, Locked components: - - type: AccessReader - access: [["Mantis"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsMantis ] + - type: entity parent: AirlockScienceGlass id: AirlockMantisGlassLocked suffix: Mantis, Locked components: - - type: AccessReader - access: [["Mantis"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsMantis ] - type: entity parent: AirlockCommand @@ -102,16 +105,18 @@ id: AirlockCorpsmanLocked suffix: Corpsman, Locked components: - - type: AccessReader - access: [["Corpsman"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsCorpsman ] - type: entity parent: AirlockSecurityGlass id: AirlockCorpsmanGlassLocked suffix: Corpsman, Locked components: - - type: AccessReader - access: [["Corpsman"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsCorpsman ] - type: entity parent: AirlockGlassShuttle @@ -126,72 +131,81 @@ id: AirlockBoxerLocked suffix: Boxer, Locked components: - - type: AccessReader - access: [["Boxer"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsBoxer ] - type: entity parent: Airlock id: AirlockClownLocked suffix: Clown, Locked components: - - type: AccessReader - access: [["Clown"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsClown ] - type: entity parent: Airlock id: AirlockMimeLocked suffix: Mime, Locked components: - - type: AccessReader - access: [["Mime"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsMime ] - type: entity parent: Airlock id: AirlockMusicianLocked suffix: Musician, Locked components: - - type: AccessReader - access: [["Musician"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsMusician ] - type: entity parent: Airlock id: AirlockReporterLocked suffix: Reporter, Locked components: - - type: AccessReader - access: [["Reporter"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsReporter ] - type: entity parent: Airlock id: AirlockLibraryLocked suffix: Library, Locked components: - - type: AccessReader - access: [["Library"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsLibrary ] - type: entity parent: Airlock id: AirlockZookeeperLocked suffix: Zookeeper, Locked components: - - type: AccessReader - access: [["Zookeeper"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsZookeeper ] - type: entity parent: AirlockExternal id: AirlockExternalSalvageLocked suffix: External, Salvage, Locked components: - - type: AccessReader - access: [["Salvage"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsSalvage ] - type: entity parent: AirlockMedical id: AirlockPsychologistLocked suffix: Psychologist, Locked components: - - type: AccessReader - access: [["Psychologist"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsPsychologist ] # Glass Airlocks - type: entity @@ -199,72 +213,81 @@ id: AirlockBoxerGlassLocked suffix: Boxer, Locked components: - - type: AccessReader - access: [["Boxer"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsBoxer ] - type: entity parent: AirlockGlass id: AirlockClownGlassLocked suffix: Clown, Locked components: - - type: AccessReader - access: [["Clown"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsClown ] - type: entity parent: AirlockGlass id: AirlockMimeGlassLocked suffix: Mime, Locked components: - - type: AccessReader - access: [["Mime"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsMime ] - type: entity parent: AirlockGlass id: AirlockMusicianGlassLocked suffix: Musician, Locked components: - - type: AccessReader - access: [["Musician"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsMusician ] - type: entity parent: AirlockGlass id: AirlockReporterGlassLocked suffix: Reporter, Locked components: - - type: AccessReader - access: [["Reporter"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsReporter ] - type: entity parent: AirlockGlass id: AirlockLibraryGlassLocked suffix: Library, Locked components: - - type: AccessReader - access: [["Library"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsLibrary ] - type: entity parent: AirlockGlass id: AirlockZookeeperGlassLocked suffix: Zookeeper, Locked components: - - type: AccessReader - access: [["Zookeeper"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsZookeeper ] - type: entity parent: AirlockExternalGlass id: AirlockExternalGlassSalvageLocked suffix: External, Glass, Salvage, Locked components: - - type: AccessReader - access: [["Salvage"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsSalvage ] - type: entity parent: AirlockMedicalGlass id: AirlockPsychologistGlassLocked suffix: Psychologist, Locked components: - - type: AccessReader - access: [["Psychologist"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsPsychologist ] # Maintenance Hatches - type: entity @@ -272,69 +295,78 @@ id: AirlockMaintBoxerLocked suffix: Boxer, Locked components: - - type: AccessReader - access: [["Boxer"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsBoxer ] - type: entity parent: AirlockMaint id: AirlockMaintClownLocked suffix: Clown, Locked components: - - type: AccessReader - access: [["Clown"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsClown ] - type: entity parent: AirlockMaint id: AirlockMaintMimeLocked suffix: Mime, Locked components: - - type: AccessReader - access: [["Mime"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsMime ] - type: entity parent: AirlockMaint id: AirlockMaintMusicianLocked suffix: Musician, Locked components: - - type: AccessReader - access: [["Musician"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsMusician ] - type: entity parent: AirlockMaint id: AirlockMaintReporterLocked suffix: Reporter, Locked components: - - type: AccessReader - access: [["Reporter"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsReporter ] - type: entity parent: AirlockMaint id: AirlockMaintLibraryLocked suffix: Library, Locked components: - - type: AccessReader - access: [["Library"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsLibrary ] - type: entity parent: AirlockMaint id: AirlockMaintZookeeperLocked suffix: Zookeeper, Locked components: - - type: AccessReader - access: [["Zookeeper"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsZookeeper ] - type: entity parent: AirlockMaint id: AirlockMaintPsychologistLocked suffix: Psychologist, Locked components: - - type: AccessReader - access: [["Psychologist"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsPsychologist ] - type: entity parent: AirlockMaint id: AirlockMaintSecurityLawyerLocked suffix: Security/Lawyer, Locked components: - - type: AccessReader - access: [["Security"], ["Lawyer"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsSecurityLawyer ] diff --git a/Resources/Prototypes/DeltaV/Entities/Structures/Machines/faxmachines.yml b/Resources/Prototypes/DeltaV/Entities/Structures/Machines/faxmachines.yml index 550aaed534..c2eb95cd7c 100644 --- a/Resources/Prototypes/DeltaV/Entities/Structures/Machines/faxmachines.yml +++ b/Resources/Prototypes/DeltaV/Entities/Structures/Machines/faxmachines.yml @@ -1,7 +1,3 @@ -#Delta-V - This file is licensed under AGPLv3 -# Copyright (c) 2024 Delta-V Contributors -# See AGPLv3.txt for details. - - type: entity parent: FaxMachineBase id: FaxMachineListeningSyndie diff --git a/Resources/Prototypes/DeltaV/Entities/Structures/Machines/syndicate_monitor_server.yml b/Resources/Prototypes/DeltaV/Entities/Structures/Machines/syndicate_monitor_server.yml index b4a1f8808e..02d3d69cc6 100644 --- a/Resources/Prototypes/DeltaV/Entities/Structures/Machines/syndicate_monitor_server.yml +++ b/Resources/Prototypes/DeltaV/Entities/Structures/Machines/syndicate_monitor_server.yml @@ -1,7 +1,3 @@ -#Delta-V - This file is licensed under AGPLv3 -# Copyright (c) 2024 Delta-V Contributors -# See AGPLv3.txt for details. - - type: entity id: SyndicateMonitoringServer parent: BaseMachinePowered @@ -27,7 +23,6 @@ machine_parts: !type:Container - type: CrewMonitoringServer - type: SingletonDeviceNetServer - ServerType: CrewMonitoringServer - type: DeviceNetwork deviceNetId: Wireless transmitFrequencyId: CrewMonitor @@ -37,7 +32,6 @@ range: 10000 # Mega range for Listening Post - type: ApcPowerReceiver powerLoad: 200 - priority: Low - type: ExtensionCableReceiver - type: Destructible thresholds: diff --git a/Resources/Prototypes/DeltaV/GameRules/events.yml b/Resources/Prototypes/DeltaV/GameRules/events.yml index a3fb69cf73..db727601f8 100644 --- a/Resources/Prototypes/DeltaV/GameRules/events.yml +++ b/Resources/Prototypes/DeltaV/GameRules/events.yml @@ -62,7 +62,6 @@ noSpawn: true components: - type: StationEvent - id: VentCritters earliestStart: 15 minimumPlayers: 1 weight: 4 @@ -84,5 +83,3 @@ duration: 1 - type: PirateRadioSpawnRule debrisCount: 6 - distanceModifier: 13 - debrisDistanceModifier: 3 diff --git a/Resources/Prototypes/DeltaV/Mail/mailDeliveries.yml b/Resources/Prototypes/DeltaV/Mail/mailDeliveries.yml new file mode 100644 index 0000000000..c0d6df9ec2 --- /dev/null +++ b/Resources/Prototypes/DeltaV/Mail/mailDeliveries.yml @@ -0,0 +1,147 @@ +- type: mailDeliveryPool + id: RandomDeltaVMailDeliveryPool #This entire table is messy as fuck but the weights add up to 35. TODO: ORGANIZE THIS SHIT AAAA + everyone: + MailNFAlcohol: 1 + MailNFBakedGoods: 1 + MailNFBible: 1 + MailNFBikeHorn: 0.5 + MailBooksAll: 1 + MailBlockGameDIY: 0.5 + MailNFBuildABuddy: 0.3 + MailDVBoxes: 0.3 + MailCrayon: 1 + MailNFCake: 1 + MailNFCheese: 1 + MailNFCookies: 1.1 + MailNFCritter: 1 + #Cigarettes - Adds up to 1 in weight + MailNFCigarettes: 0.5 + MailCigars: 0.2 + MailNFJoints: 0.2 + MailNFGoldCigars: 0.1 + #Cosplay - Adds up to 3.4 in weight + MailCosplayArc: 0.5 + MailDVCosplayFakeWizard: 0.5 + MailNFCosplayWizard: 0.5 + MailNFCosplayMaid: 0.5 + MailCosplayGeisha: 0.5 + MailCosplaySchoolgirl: 0.5 + MailNFCosplayNurse: 0.4 + MailNFDonkPockets: 0.5 + MailNFEMP: 0.3 + MailNFFigurineBulk: 1 + MailFishingCap: 0.5 + MailFlashlight: 1 + MailFlowers: 1 + MailNFKendoKit: 0.3 + MailNFKnife: 0.7 + MailNFMuffins: 1 + MailNoir: 0.5 + MailNFPAI: 1.2 + MailNFPlushie: 1 + MailPumpkinPie: 0.3 + MailNFPen: 0.7 + MailRestraints: 0.8 + MailSake: 0.4 + MailDVScarves: 0.15 + MailNFSnacks: 1 + #Soda - Adds up to 1 in weight + MailNFSodaPwrGame: 0.2 + MailNFSodaRedBool: 0.2 + MailNFSodaSpaceCola: 0.2 + MailNFSodaSpaceMountainWind: 0.2 + MailNFSodaSpaceUp: 0.2 + #End Soda + MailNFSmoke: 0.4 + MailSpaceVillainDIY: 0.5 + MailSignallerKit: 0.5 + MailSunglasses: 1 + MailNFSoaps: 0.5 + MailNFSoapsOmega: 0.5 + MailNFSword: 0.5 + MailNFTacticalMaid: 0.5 + MailNFThrongler: 0.05 + MailNFUnusualClothing: 0.5 + MailNFUnusualFood: 1 + MailNFUnusualProduce: 1 + MailNFVagueThreat: 0.5 + # Mainly for Glacier + MailWinterCoat: 1.5 + + # Department and job-specific mail can have slightly higher weights, + # since they'll be merged with the everyone pool. + departments: + Medical: + MailMedicalBasicSupplies: 2 + MailMedicalChemistrySupplement: 2 + MailMedicalEmergencyPens: 3 + MailMedicalMedicinePills: 2 + MailMedicalSheetPlasma: 1 + # MailMedicalSpaceacillin: 1 + MailMedicalStabilizers: 2 + MailNFMedkit: 2 + Engineering: + MailAMEGuide: 1 + MailEngineeringCables: 2 + MailEngineeringKudzuDeterrent: 2 + MailEngineeringSheetGlass: 2 + MailEngineeringWelderReplacement: 2 + MailNFCircuitboardIndustrial: 2 + MailNFCircuitboardService: 1 + MailNFPowerTool: 1 + Security: + MailSecurityDonuts: 3 + MailSecurityFlashlight: 2 + MailSecurityNonlethalsKit: 2 + MailSecurityDVSpaceLaw: 1 + MailSecurityNFMusket: 1 + Epistemics: +# MailBooks: 1 + MailEpistemologyBluespace: 1 + MailEpistemologyIngotGold: 2 + MailEpistemologyResearchDisk: 1 + MailEpistemologyTinfoilHat: 1 + MailSignallerKit: 1 + # All heads of staff are in Command and not their departments, technically. + # So any items from the departments above that should also be sent to the + # respective department heads should be duplicated below. + Command: + MailCommandPinpointerNuclear: 0.5 + MailStationRepNFNukeDisk: 0.3 + MailCommandNFPipebombIntern: 0.1 + + jobs: + Botanist: + MailBotanistChemicalBottles: 2 + MailBotanistMutagen: 1.5 + MailBotanistSeeds: 1 + ChiefEngineer: + MailEngineeringKudzuDeterrent: 2 + ChiefMedicalOfficer: + MailMedicalEmergencyPens: 2 + MailMedicalMedicinePills: 3 + MailMedicalSheetPlasma: 2 + Clown: + MailClownGildedBikeHorn: 0.5 + MailClownHonkSupplement: 3 + Detective: # Deltav - Detective is in charge of investigating crimes. + MailDetectiveForensicSupplement: 2 # Deltav - Detective is in charge of investigating crimes. + HeadOfPersonnel: + MailHoPBureaucracy: 2 + MailHoPSupplement: 3 + HeadOfSecurity: + MailSecurityNonlethalsKit: 2 + Lawyer: + MailSecurityDVSpaceLaw: 2 + Mime: + MailMimeArtsCrafts: 3 + MailMimeBlankBook: 2 + MailMimeBottleOfNothing: 1 + ResearchDirector: # DeltaV - Epistemics Department replacing Science but keeping their IDs + MailEpistemologyIngotGold: 2 + Musician: + MailMusicianInstrumentSmall: 1 + Passenger: + MailPassengerMoney: 3 + Warden: + MailWardenCrowdControl: 2 diff --git a/Resources/Prototypes/DeltaV/NPC/roboisseur.yml b/Resources/Prototypes/DeltaV/NPC/roboisseur.yml index e7f0b5bcd0..d76c1e48fa 100644 --- a/Resources/Prototypes/DeltaV/NPC/roboisseur.yml +++ b/Resources/Prototypes/DeltaV/NPC/roboisseur.yml @@ -28,3 +28,15 @@ attributes: gender: male proper: true + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeAabb + bounds: "-0.15,-0.15,0.15,0.15" + density: 60 + mask: + - MachineMask + layer: + - MidImpassable + - LowImpassable diff --git a/Resources/Prototypes/DeltaV/Objectives/paradox_anomaly.yml b/Resources/Prototypes/DeltaV/Objectives/paradox_anomaly.yml index dd0b74c461..ec87795ab5 100644 --- a/Resources/Prototypes/DeltaV/Objectives/paradox_anomaly.yml +++ b/Resources/Prototypes/DeltaV/Objectives/paradox_anomaly.yml @@ -1,9 +1,9 @@ - type: entity abstract: true - parent: BaseTerminatorObjective # mrp terminator real id: BaseParadoxAnomalyObjective components: - type: Objective + difficulty: 1 issuer: self # not using base kill/keep alive objectives since these intentionally conflict with eachother @@ -19,7 +19,6 @@ state: icon - type: TargetObjective title: objective-paradox-anomaly-kill-title - - type: TerminatorTargetOverride - type: KillPersonCondition requireDead: true @@ -35,7 +34,6 @@ state: folder-white - type: TargetObjective title: objective-paradox-anomaly-friend-title - - type: TerminatorTargetOverride - type: KeepAliveCondition - type: entity diff --git a/Resources/Prototypes/DeltaV/Objectives/stealTargetGroups.yml b/Resources/Prototypes/DeltaV/Objectives/stealTargetGroups.yml index b311330873..53022ce236 100644 --- a/Resources/Prototypes/DeltaV/Objectives/stealTargetGroups.yml +++ b/Resources/Prototypes/DeltaV/Objectives/stealTargetGroups.yml @@ -12,13 +12,6 @@ sprite: DeltaV/Objects/Misc/bureaucracy.rsi state: folder-hop-ian -- type: stealTargetGroup - id: WeaponEnergyGunMultiphase - name: x-01 multiphase energy gun - sprite: - sprite: DeltaV/Objects/Weapons/Guns/Battery/multiphase_energygun.rsi - state: base - - type: stealTargetGroup id: RubberStampNotary name: notary stamp diff --git a/Resources/Prototypes/DeltaV/Objectives/traitor.yml b/Resources/Prototypes/DeltaV/Objectives/traitor.yml index d27ec220fa..c5c5318b5d 100644 --- a/Resources/Prototypes/DeltaV/Objectives/traitor.yml +++ b/Resources/Prototypes/DeltaV/Objectives/traitor.yml @@ -20,19 +20,6 @@ stealGroup: BookIanDossier # owner: job-name-hop -- type: entity # Head of Security steal objective. - noSpawn: true - parent: BaseTraitorStealObjective - id: HoSGunStealObjective - components: - - type: Objective - difficulty: 3 # HoS will mostly be using the gun to stop you from stealing it - - type: NotJobRequirement - job: HeadOfSecurity - - type: StealCondition - stealGroup: WeaponEnergyGunMultiphase - owner: job-name-hos - - type: entity # Clerk steal objective. noSpawn: true parent: BaseTraitorStealObjective @@ -43,5 +30,3 @@ - type: StealCondition stealGroup: RubberStampNotary owner: job-name-clerk - - diff --git a/Resources/Prototypes/DeltaV/Reagents/Consumable/Drink/drinks.yml b/Resources/Prototypes/DeltaV/Reagents/Consumable/Drink/drinks.yml index c568c1bc75..7fdc51f1de 100644 --- a/Resources/Prototypes/DeltaV/Reagents/Consumable/Drink/drinks.yml +++ b/Resources/Prototypes/DeltaV/Reagents/Consumable/Drink/drinks.yml @@ -19,7 +19,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.3 + amount: 0.336 - !type:PopupMessage conditions: - !type:ReagentThreshold @@ -55,7 +55,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.20 + amount: 0.133 Poison: effects: - !type:HealthChange @@ -83,7 +83,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.15 + amount: 0.16 - type: reagent id: GreenGrass @@ -105,7 +105,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.05 + amount: 0.083 - type: reagent id: Daiquiri @@ -128,7 +128,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.25 + amount: 0.3 - type: reagent id: ArsonistsBrew @@ -151,7 +151,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.30 + amount: 0.08 Poison: effects: - !type:HealthChange @@ -179,6 +179,14 @@ metamorphicMaxFillLevels: 4 metamorphicFillBaseName: fill- metamorphicChangeColor: false + metabolisms: + Drink: + effects: + - !type:SatiateThirst + factor: 2 + - !type:AdjustReagent + reagent: Ethanol + amount: 0.015 - type: reagent id: Mothamphetamine @@ -201,7 +209,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.25 + amount: 0.126 Poison: effects: - !type:Jitter diff --git a/Resources/Prototypes/DeltaV/Recipes/Construction/clothing.yml b/Resources/Prototypes/DeltaV/Recipes/Construction/clothing.yml index 386a0211c0..601bcd540b 100644 --- a/Resources/Prototypes/DeltaV/Recipes/Construction/clothing.yml +++ b/Resources/Prototypes/DeltaV/Recipes/Construction/clothing.yml @@ -1,9 +1,9 @@ - type: construction name: prescription medhud - id: ClothingEyesPrescriptionMedHud + id: PrescriptionMedHud graph: PrescriptionMedHud startNode: start - targetNode: prescmedhud + targetNode: prescmedhud category: construction-category-clothing description: Prescription medhud, merged glasses and medhud together by sheer luck and cables with glue. icon: { sprite: DeltaV/Clothing/Eyes/Hud/prescmedhud.rsi, state: icon } @@ -11,10 +11,10 @@ - type: construction name: prescription sechud - id: ClothingEyesPrescriptionHudSecurity + id: PrescriptionHudSecurity graph: PrescriptionSecHud startNode: start - targetNode: prescsechud + targetNode: prescsechud category: construction-category-clothing description: Prescription sechud, merged glasses and sechud together by sheer luck and cables with glue. icon: { sprite: DeltaV/Clothing/Eyes/Hud/prescsechud.rsi, state: icon } diff --git a/Resources/Prototypes/DeltaV/Roles/Jobs/Fun/misc_startinggear.yml b/Resources/Prototypes/DeltaV/Roles/Jobs/Fun/misc_startinggear.yml index 60b6e3c6e9..db5e717a01 100644 --- a/Resources/Prototypes/DeltaV/Roles/Jobs/Fun/misc_startinggear.yml +++ b/Resources/Prototypes/DeltaV/Roles/Jobs/Fun/misc_startinggear.yml @@ -1,7 +1,3 @@ -#Delta-V - This file is licensed under AGPLv3 -# Copyright (c) 2024 Delta-V Contributors -# See AGPLv3.txt for details. - #Misc outfit startingGear definitions. # Laika sec glasses @@ -66,6 +62,7 @@ gloves: ClothingHandsGlovesCombat shoes: ClothingShoesSlippers id: SyndiListeningPostPDA + pocket1: BaseUplinkRadio20TC innerClothingSkirt: ClothingUniformJumpsuitPyjamaSyndicatePink # Mobsters diff --git a/Resources/Prototypes/DeltaV/Roles/Jobs/Justice/chief_justice.yml b/Resources/Prototypes/DeltaV/Roles/Jobs/Justice/chief_justice.yml index 0c063641aa..8f76050d88 100644 --- a/Resources/Prototypes/DeltaV/Roles/Jobs/Justice/chief_justice.yml +++ b/Resources/Prototypes/DeltaV/Roles/Jobs/Justice/chief_justice.yml @@ -35,10 +35,6 @@ - !type:AddComponentSpecial components: - type: CommandStaff - - !type:AddComponentSpecial - components: - - type: PsionicBonusChance #Nyano - Summary: makes it more likely to become psionic. - flatBonus: 0.025 - type: startingGear id: CJGear diff --git a/Resources/Prototypes/DeltaV/Roles/Jobs/NPC/syndicateNPCs.yml b/Resources/Prototypes/DeltaV/Roles/Jobs/NPC/syndicateNPCs.yml index a1984279e9..b5379c5014 100644 --- a/Resources/Prototypes/DeltaV/Roles/Jobs/NPC/syndicateNPCs.yml +++ b/Resources/Prototypes/DeltaV/Roles/Jobs/NPC/syndicateNPCs.yml @@ -1,7 +1,3 @@ -#Delta-V - This file is licensed under AGPLv3 -# Copyright (c) 2024 Delta-V Contributors -# See AGPLv3.txt for details. - - type: startingGear id: RadioGuardGear equipment: @@ -22,4 +18,4 @@ description: An off-the-shelf plate carrier that has been cruelly grafted onto its wearers body noSpawn: true components: - - type: Unremoveable + - type: Unremoveable diff --git a/Resources/Prototypes/DeltaV/Roles/Jobs/Security/brigmedic.yml b/Resources/Prototypes/DeltaV/Roles/Jobs/Security/brigmedic.yml index 84de1983b9..b5d9d021e8 100644 --- a/Resources/Prototypes/DeltaV/Roles/Jobs/Security/brigmedic.yml +++ b/Resources/Prototypes/DeltaV/Roles/Jobs/Security/brigmedic.yml @@ -30,6 +30,8 @@ - !type:AddComponentSpecial components: - type: CPRTraining + - type: SurgerySpeedModifier + SpeedModifier: 2.0 - type: startingGear id: CorpsmanGear # see Prototypes/Roles/Jobs/Fun/misc_startinggear.yml for "BrigmedicGear" @@ -44,7 +46,6 @@ id: CorpsmanPDA ears: ClothingHeadsetBrigmedic belt: ClothingBeltCorpsmanWebbingFilled - pocket1: WeaponPistolMk58Nonlethal innerClothingSkirt: ClothingUniformJumpskirtBrigmedic satchel: ClothingBackpackSatchelBrigmedicFilled duffelbag: ClothingBackpackDuffelBrigmedicFilled diff --git a/Resources/Prototypes/DeltaV/Species/vulpkanin.yml b/Resources/Prototypes/DeltaV/Species/vulpkanin.yml index 92fd881b85..064fc4003d 100644 --- a/Resources/Prototypes/DeltaV/Species/vulpkanin.yml +++ b/Resources/Prototypes/DeltaV/Species/vulpkanin.yml @@ -50,11 +50,29 @@ Head: points: 1 required: false - Legs: - points: 6 + RightLeg: + points: 2 required: false - Arms: - points: 6 + RightFoot: + points: 2 + required: false + LeftLeg: + points: 2 + required: false + LeftFoot: + points: 2 + required: false + RightArm: + points: 2 + required: false + RightHand: + points: 2 + required: false + LeftArm: + points: 2 + required: false + LeftHand: + points: 2 required: false Snout: points: 1 diff --git a/Resources/Prototypes/DeltaV/Traits/altvision.yml b/Resources/Prototypes/DeltaV/Traits/altvision.yml index 390e14d4ad..31a67ad350 100644 --- a/Resources/Prototypes/DeltaV/Traits/altvision.yml +++ b/Resources/Prototypes/DeltaV/Traits/altvision.yml @@ -7,12 +7,15 @@ species: - Vulpkanin - Harpy + - Shadowkin - !type:CharacterTraitRequirement inverted: true traits: - DogVision - components: - - type: UltraVision + functions: + - !type:TraitAddComponent + components: + - type: UltraVision - type: trait id: DogVision @@ -23,9 +26,12 @@ species: - Vulpkanin - Harpy + - Shadowkin - !type:CharacterTraitRequirement inverted: true traits: - UltraVision - components: - - type: DogVision + functions: + - !type:TraitAddComponent + components: + - type: DogVision diff --git a/Resources/Prototypes/DeltaV/Traits/neutral.yml b/Resources/Prototypes/DeltaV/Traits/neutral.yml index 6168d7045a..f8c4119f04 100644 --- a/Resources/Prototypes/DeltaV/Traits/neutral.yml +++ b/Resources/Prototypes/DeltaV/Traits/neutral.yml @@ -1,6 +1,11 @@ - type: trait id: ScottishAccent - category: Speech + category: TraitsSpeechAccents points: 0 - components: - - type: ScottishAccent + requirements: + - !type:CharacterItemGroupRequirement + group: TraitsAccents + functions: + - !type:TraitAddComponent + components: + - type: ScottishAccent diff --git a/Resources/Prototypes/DeltaV/Voice/speech_emote_sounds.yml b/Resources/Prototypes/DeltaV/Voice/speech_emote_sounds.yml index 119134dcf1..ad91d9c2d2 100644 --- a/Resources/Prototypes/DeltaV/Voice/speech_emote_sounds.yml +++ b/Resources/Prototypes/DeltaV/Voice/speech_emote_sounds.yml @@ -60,6 +60,10 @@ collection: VulpkaninHowls Weh: collection: Weh + Mars: + collection: Mars + Wurble: + collection: Wurble - type: emoteSounds id: MaleVulpkanin diff --git a/Resources/Prototypes/DeltaV/Voice/speech_emotes.yml b/Resources/Prototypes/DeltaV/Voice/speech_emotes.yml index 29f75d0d50..7f3eb0313a 100644 --- a/Resources/Prototypes/DeltaV/Voice/speech_emotes.yml +++ b/Resources/Prototypes/DeltaV/Voice/speech_emotes.yml @@ -1,5 +1,6 @@ - type: emote id: HarpyRing + name: chat-emote-name-harpyring category: Vocal chatMessages: [ rings ] chatTriggers: @@ -7,6 +8,7 @@ - type: emote id: HarpyPew + name: chat-emote-name-harpypew category: Vocal chatMessages: [ pews ] chatTriggers: @@ -15,6 +17,7 @@ - type: emote id: HarpyBang + name: chat-emote-name-harpybang category: Vocal chatMessages: [ bangs ] chatTriggers: @@ -22,6 +25,7 @@ - type: emote id: HarpyRev + name: chat-emote-name-harpyrev category: Vocal chatMessages: [ revs ] chatTriggers: @@ -29,6 +33,7 @@ - type: emote id: HarpyCaw + name: chat-emote-name-harpycaw category: Vocal chatMessages: [ caws ] chatTriggers: @@ -37,6 +42,7 @@ #Vulpkanin - type: emote id: Bark + name: chat-emote-name-vulpbark category: Vocal chatMessages: [ barks ] chatTriggers: @@ -44,6 +50,7 @@ - type: emote id: Snarl + name: chat-emote-name-vulpsnarl category: Vocal chatMessages: [ snarls ] chatTriggers: @@ -51,6 +58,7 @@ - type: emote id: Whine + name: chat-emote-name-vulpwhine category: Vocal chatMessages: [ whines ] chatTriggers: @@ -58,6 +66,7 @@ - type: emote id: Howl + name: chat-emote-name-vulphowl category: Vocal chatMessages: [ howls ] chatTriggers: diff --git a/Resources/Prototypes/Device/devicenet_frequencies.yml b/Resources/Prototypes/Device/devicenet_frequencies.yml index db48d117e0..ecdbb3bb4c 100644 --- a/Resources/Prototypes/Device/devicenet_frequencies.yml +++ b/Resources/Prototypes/Device/devicenet_frequencies.yml @@ -75,6 +75,17 @@ name: device-frequency-prototype-name-crew-monitor frequency: 1261 +# Cyborgs broadcast to consoles on this frequency +- type: deviceFrequency + id: RoboticsConsole + name: device-frequency-prototype-name-robotics-console + frequency: 1291 + +# Console sends commands to cyborgs on this frequency +- type: deviceFrequency + id: CyborgControl + name: device-frequency-prototype-name-cyborg-control + frequency: 1292 # This frequency will likely have a LARGE number of listening entities. Please don't broadcast on this frequency. - type: deviceFrequency diff --git a/Resources/Prototypes/DeviceLinking/source_ports.yml b/Resources/Prototypes/DeviceLinking/source_ports.yml index 18b9b831e6..1988f29e45 100644 --- a/Resources/Prototypes/DeviceLinking/source_ports.yml +++ b/Resources/Prototypes/DeviceLinking/source_ports.yml @@ -45,6 +45,11 @@ description: signal-port-description-doorstatus defaultLinks: [ DoorBolt ] +- type: sourcePort + id: DockStatus + name: signal-port-name-dockstatus + description: signal-port-description-dockstatus + - type: sourcePort id: OrderSender name: signal-port-name-order-sender diff --git a/Resources/Prototypes/Entities/Clothing/Back/backpacks.yml b/Resources/Prototypes/Entities/Clothing/Back/backpacks.yml index 605cb61c43..4af443113a 100644 --- a/Resources/Prototypes/Entities/Clothing/Back/backpacks.yml +++ b/Resources/Prototypes/Entities/Clothing/Back/backpacks.yml @@ -23,8 +23,8 @@ ents: [] - type: UserInterface interfaces: - - key: enum.StorageUiKey.Key - type: StorageBoundUserInterface + enum.StorageUiKey.Key: + type: StorageBoundUserInterface # to prevent bag open/honk spam - type: UseDelay delay: 0.5 @@ -146,6 +146,15 @@ - type: Sprite sprite: Clothing/Back/Backpacks/science.rsi +- type: entity + parent: ClothingBackpack + id: ClothingBackpackRobotics + name: robotics backpack + description: A sturdy backpack to store all the tools and parts. + components: + - type: Sprite + sprite: Clothing/Back/Backpacks/robotics.rsi + - type: entity parent: ClothingBackpack id: ClothingBackpackVirology diff --git a/Resources/Prototypes/Entities/Clothing/Back/duffel.yml b/Resources/Prototypes/Entities/Clothing/Back/duffel.yml index 46d7ae4538..969d9a6fde 100644 --- a/Resources/Prototypes/Entities/Clothing/Back/duffel.yml +++ b/Resources/Prototypes/Entities/Clothing/Back/duffel.yml @@ -130,6 +130,15 @@ - type: Sprite sprite: Clothing/Back/Duffels/science.rsi +- type: entity + parent: ClothingBackpackDuffel + id: ClothingBackpackDuffelRobotics + name: robotics duffel bag + description: A large and sturdy duffel bag to fit everything needed to build a cyborg. + components: + - type: Sprite + sprite: Clothing/Back/Duffels/robotics.rsi + - type: entity parent: ClothingBackpackDuffel id: ClothingBackpackDuffelHydroponics diff --git a/Resources/Prototypes/Entities/Clothing/Back/satchel.yml b/Resources/Prototypes/Entities/Clothing/Back/satchel.yml index bf52477340..0aa6fc5730 100644 --- a/Resources/Prototypes/Entities/Clothing/Back/satchel.yml +++ b/Resources/Prototypes/Entities/Clothing/Back/satchel.yml @@ -105,6 +105,15 @@ - type: Sprite sprite: Clothing/Back/Satchels/science.rsi +- type: entity + parent: ClothingBackpackSatchel + id: ClothingBackpackSatchelRobotics + name: robotics satchel + description: A sturdy satchel to store the various tools and duct tape. + components: + - type: Sprite + sprite: Clothing/Back/Satchels/robotics.rsi + - type: entity parent: ClothingBackpackSatchel id: ClothingBackpackSatchelSecurity diff --git a/Resources/Prototypes/Entities/Clothing/Back/specific.yml b/Resources/Prototypes/Entities/Clothing/Back/specific.yml index 8af00039b5..e2932537e9 100644 --- a/Resources/Prototypes/Entities/Clothing/Back/specific.yml +++ b/Resources/Prototypes/Entities/Clothing/Back/specific.yml @@ -14,9 +14,9 @@ default: ClothingBackpack - type: UserInterface interfaces: - - key: enum.StorageUiKey.Key + enum.StorageUiKey.Key: type: StorageBoundUserInterface - - key: enum.ChameleonUiKey.Key + enum.ChameleonUiKey.Key: type: ChameleonBoundUserInterface - type: entity @@ -51,8 +51,8 @@ canChangeTransferAmount: true - type: UserInterface interfaces: - - key: enum.TransferAmountUiKey.Key - type: TransferAmountBoundUserInterface + enum.TransferAmountUiKey.Key: + type: TransferAmountBoundUserInterface - type: DrawableSolution solution: tank - type: RefillableSolution @@ -61,3 +61,25 @@ solution: tank - type: ExaminableSolution solution: tank + +- type: entity + parent: Clothing + id: ClothingBackpackEtherealTeleporter + name: ethereal teleporter + description: Originally created while several research facilities were experimenting on Shadowkin, this backpack allows the wearer to jump the gap between the "normal" dimension and The Dark. + components: + - type: Tag + tags: + - WhitelistChameleon + - type: Sprite + sprite: Clothing/Back/etherealteleporter.rsi + state: icon + - type: Item + size: Ginormous + - type: Clothing + slots: BACK + sprite: Clothing/Back/etherealteleporter.rsi + # TODO: Uncomment this when ClothingGrantPsionicPower is fixed and back working. + # - type: ClothingGrantPsionicPower + # power: DarkSwapPower + # - type: Psionic diff --git a/Resources/Prototypes/Entities/Clothing/Belt/base_clothingbelt.yml b/Resources/Prototypes/Entities/Clothing/Belt/base_clothingbelt.yml index 62889d0aba..1d26d71660 100644 --- a/Resources/Prototypes/Entities/Clothing/Belt/base_clothingbelt.yml +++ b/Resources/Prototypes/Entities/Clothing/Belt/base_clothingbelt.yml @@ -45,8 +45,8 @@ ents: [] - type: UserInterface interfaces: - - key: enum.StorageUiKey.Key - type: StorageBoundUserInterface + enum.StorageUiKey.Key: + type: StorageBoundUserInterface - type: entity abstract: true diff --git a/Resources/Prototypes/Entities/Clothing/Belt/belts.yml b/Resources/Prototypes/Entities/Clothing/Belt/belts.yml index 359165cfc5..f0ee2c4c66 100644 --- a/Resources/Prototypes/Entities/Clothing/Belt/belts.yml +++ b/Resources/Prototypes/Entities/Clothing/Belt/belts.yml @@ -31,6 +31,7 @@ - AppraisalTool - JawsOfLife - GPS + - WeldingMask components: - SprayPainter - NetworkConfigurator @@ -267,8 +268,8 @@ - PillCanister - Radio - DiscreteHealthAnalyzer - - SurgeryTool components: + - SurgeryTool - Hypospray - Injector - Pill @@ -473,12 +474,19 @@ - MagazinePistol - MagazineMagnum - CombatKnife + - Truncheon components: - Stunbaton - FlashOnTrigger - SmokeOnTrigger - Flash - Handcuff + - BallisticAmmoProvider + - CartridgeAmmo + - DoorRemote + - Whistle + - HolosignProjector + - BalloonPopper - type: ItemMapper mapLayers: flashbang: @@ -600,7 +608,7 @@ - CartridgeAmmo - type: entity - parent: ClothingBeltStorageBase + parent: ClothingBeltSecurity id: ClothingBeltSecurityWebbing name: security webbing description: Unique and versatile chest rig, can hold security gear. @@ -623,11 +631,10 @@ - id: Stunbaton - id: Handcuffs - id: Handcuffs - - type: entity parent: ClothingBeltStorageBase id: ClothingBeltMercWebbing - name: mercenarie webbing + name: mercenary webbing description: Ideal for storing everything from ammo to weapons and combat essentials. components: - type: Sprite diff --git a/Resources/Prototypes/Entities/Clothing/Belt/quiver.yml b/Resources/Prototypes/Entities/Clothing/Belt/quiver.yml index 6cbf5a69ca..bb4e395c4f 100644 --- a/Resources/Prototypes/Entities/Clothing/Belt/quiver.yml +++ b/Resources/Prototypes/Entities/Clothing/Belt/quiver.yml @@ -18,7 +18,11 @@ whitelist: tags: - Arrow + - Plunger - type: Appearance - type: StorageContainerVisuals maxFillLevels: 3 fillBaseName: fill- + - type: Construction + graph: Quiver + node: Quiver diff --git a/Resources/Prototypes/Entities/Clothing/Ears/specific.yml b/Resources/Prototypes/Entities/Clothing/Ears/specific.yml index 093f5e645b..83612ac4b2 100644 --- a/Resources/Prototypes/Entities/Clothing/Ears/specific.yml +++ b/Resources/Prototypes/Entities/Clothing/Ears/specific.yml @@ -16,5 +16,5 @@ default: ClothingHeadsetGrey - type: UserInterface interfaces: - - key: enum.ChameleonUiKey.Key + enum.ChameleonUiKey.Key: type: ChameleonBoundUserInterface diff --git a/Resources/Prototypes/Entities/Clothing/Eyes/glasses.yml b/Resources/Prototypes/Entities/Clothing/Eyes/glasses.yml index 4e6d9991e3..b156091af9 100644 --- a/Resources/Prototypes/Entities/Clothing/Eyes/glasses.yml +++ b/Resources/Prototypes/Entities/Clothing/Eyes/glasses.yml @@ -84,7 +84,6 @@ tags: - HamsterWearable - WhitelistChameleon - - GlassesNearsight # SimpleStation14 NearsightedTrait - type: DropOnSlip chance: 30 @@ -162,7 +161,7 @@ protectionTime: 5 - type: entity - parent: ClothingEyesBase + parent: [ClothingEyesBase, ShowSecurityIcons] id: ClothingEyesGlassesSecurity name: security glasses description: Upgraded sunglasses that provide flash immunity and a security HUD. @@ -182,7 +181,6 @@ - type: GuideHelp guides: - Security - - type: ShowSecurityIcons - type: IdentityBlocker coverage: EYES @@ -275,3 +273,15 @@ sprite: Clothing/Eyes/Glasses/meson.rsi - type: Clothing sprite: Clothing/Eyes/Glasses/meson.rsi + +- type: entity + parent: ClothingEyesBase + id: ClothingEyesGlassesEthereal + name: ethereal goggles + description: An unusual pair of goggles developed during a time of inhumane experimentation involving Shadowkin. They are designed to allow the wearer to peer into The Dark. + components: + - type: Sprite + sprite: Clothing/Eyes/Glasses/etherealgoogles.rsi + - type: Clothing + sprite: Clothing/Eyes/Glasses/etherealgoogles.rsi + - type: ShowEthereal diff --git a/Resources/Prototypes/Entities/Clothing/Eyes/hud.yml b/Resources/Prototypes/Entities/Clothing/Eyes/hud.yml index 15b85b67d8..d3c0f53b44 100644 --- a/Resources/Prototypes/Entities/Clothing/Eyes/hud.yml +++ b/Resources/Prototypes/Entities/Clothing/Eyes/hud.yml @@ -1,3 +1,13 @@ +- type: entity + id: ShowSecurityIcons + abstract: true + noSpawn: true + components: + - type: ShowJobIcons + - type: ShowMindShieldIcons + - type: ShowCriminalRecordIcons + + - type: entity parent: ClothingEyesBase id: ClothingEyesHudDiagnostic @@ -19,7 +29,7 @@ parent: ClothingEyesBase id: ClothingEyesHudMedical name: medical hud - description: A heads-up display that scans the humanoids in view and provides accurate data about their health status. + description: A heads-up display that scans the humanoids in view and provides accurate data about their health status. components: - type: Sprite sprite: Clothing/Eyes/Hud/med.rsi @@ -38,7 +48,7 @@ chance: 25 - type: entity - parent: ClothingEyesBase + parent: [ClothingEyesBase, ShowSecurityIcons] id: ClothingEyesHudSecurity name: security hud description: A heads-up display that scans the humanoids in view and provides accurate data about their ID status and security records. @@ -47,7 +57,6 @@ sprite: Clothing/Eyes/Hud/sec.rsi - type: Clothing sprite: Clothing/Eyes/Hud/sec.rsi - - type: ShowSecurityIcons - type: Tag tags: - HudSecurity @@ -144,7 +153,7 @@ - type: ShowThirstIcons - type: entity - parent: ClothingEyesBase + parent: [ClothingEyesBase, ShowSecurityIcons] id: ClothingEyesHudMedSec name: medsec hud description: An eye display that looks like a mixture of medical and security huds. @@ -156,13 +165,15 @@ - type: Construction graph: HudMedSec node: medsecHud - - type: ShowSecurityIcons - type: ShowHealthBars damageContainers: - Biological + - type: ShowHealthIcons + damageContainers: + - Biological - type: entity - parent: ClothingEyesBase + parent: [ClothingEyesBase, ShowSecurityIcons] id: ClothingEyesHudMultiversal name: multiversal hud description: Filler @@ -171,7 +182,6 @@ sprite: Clothing/Eyes/Hud/medsecengi.rsi - type: Clothing sprite: Clothing/Eyes/Hud/medsecengi.rsi - - type: ShowSecurityIcons - type: ShowHealthBars damageContainers: - Biological @@ -182,7 +192,7 @@ - type: ShowSyndicateIcons - type: entity - parent: ClothingEyesBase + parent: [ClothingEyesBase, ShowSecurityIcons] id: ClothingEyesHudOmni name: omni hud description: Filler @@ -191,7 +201,6 @@ sprite: Clothing/Eyes/Hud/omni.rsi - type: Clothing sprite: Clothing/Eyes/Hud/omni.rsi - - type: ShowSecurityIcons - type: ShowHealthBars damageContainers: - Biological @@ -204,7 +213,7 @@ - type: ShowSyndicateIcons - type: entity - parent: ClothingEyesBase + parent: [ClothingEyesBase, ShowSecurityIcons] id: ClothingEyesHudSyndicate name: syndicate visor description: The syndicate's professional head-up display, designed for better detection of humanoids and their subsequent elimination. @@ -214,14 +223,26 @@ - type: Clothing sprite: Clothing/Eyes/Hud/synd.rsi - type: ShowSyndicateIcons - - type: ShowSecurityIcons - type: entity - parent: ClothingEyesGlassesSunglasses + parent: [ClothingEyesBase, ShowSecurityIcons] + id: ClothingEyesHudSyndicateAgent + name: syndicate agent visor + description: The Syndicate Agent's professional heads-up display, designed for quick diagnosis of their team's status. + components: + - type: Sprite + sprite: Clothing/Eyes/Hud/syndagent.rsi + - type: Clothing + sprite: Clothing/Eyes/Hud/syndagent.rsi + - type: ShowSyndicateIcons + - type: ShowHealthBars + damageContainers: + - Biological + +- type: entity + parent: [ClothingEyesGlassesSunglasses, ShowSecurityIcons] id: ClothingEyesGlassesHiddenSecurity suffix: Syndicate - components: - - type: ShowSecurityIcons - type: entity parent: ClothingEyesHudMedical diff --git a/Resources/Prototypes/Entities/Clothing/Eyes/specific.yml b/Resources/Prototypes/Entities/Clothing/Eyes/specific.yml index c04f348287..fa87ed398e 100644 --- a/Resources/Prototypes/Entities/Clothing/Eyes/specific.yml +++ b/Resources/Prototypes/Entities/Clothing/Eyes/specific.yml @@ -16,6 +16,6 @@ default: ClothingEyesGlassesSunglasses - type: UserInterface interfaces: - - key: enum.ChameleonUiKey.Key + enum.ChameleonUiKey.Key: type: ChameleonBoundUserInterface diff --git a/Resources/Prototypes/Entities/Clothing/Hands/gloves.yml b/Resources/Prototypes/Entities/Clothing/Hands/gloves.yml index 4cd0c04e2b..01d9e72fef 100644 --- a/Resources/Prototypes/Entities/Clothing/Hands/gloves.yml +++ b/Resources/Prototypes/Entities/Clothing/Hands/gloves.yml @@ -20,6 +20,7 @@ soundHit: collection: BoxingHit animation: WeaponArcFist + mustBeEquippedToUse: true - type: Fiber fiberMaterial: fibers-leather fiberColor: fibers-red @@ -90,6 +91,7 @@ types: Blunt: 8 bluntStaminaDamageFactor: 0.0 # so blunt doesn't deal stamina damage at all + mustBeEquippedToUse: true - type: entity parent: ClothingHandsBase @@ -239,6 +241,12 @@ name: combat gloves description: These tactical gloves are fireproof and shock resistant. components: + - type: Sprite + sprite: Clothing/Hands/Gloves/combat.rsi + - type: Clothing + sprite: Clothing/Hands/Gloves/combat.rsi + - type: GloveHeatResistance + heatResistance: 1400 - type: Insulated - type: Fiber fiberMaterial: fibers-insulative @@ -354,12 +362,16 @@ - type: MeleeWeapon autoAttack: true attackRate: 4 + heavyStaminaCost: 2 + heavyDamageBaseModifier: 1.05 + maxTargets: 1 damage: types: Blunt: 8 soundHit: collection: Punch animation: WeaponArcFist + mustBeEquippedToUse: true - type: Fiber fiberMaterial: fibers-leather fiberColor: fibers-blue @@ -367,12 +379,11 @@ - type: MeleeSpeech - type: ActivatableUI key: enum.MeleeSpeechUiKey.Key - closeOnHandDeselect: false - rightClickOnly: true + verbOnly: true - type: Actions - type: UserInterface interfaces: - - key: enum.MeleeSpeechUiKey.Key + enum.MeleeSpeechUiKey.Key: type: MeleeSpeechBoundUserInterface - type: StaticPrice price: 0 diff --git a/Resources/Prototypes/Entities/Clothing/Hands/specific.yml b/Resources/Prototypes/Entities/Clothing/Hands/specific.yml index db34297b42..b35c3b8b50 100644 --- a/Resources/Prototypes/Entities/Clothing/Hands/specific.yml +++ b/Resources/Prototypes/Entities/Clothing/Hands/specific.yml @@ -19,9 +19,9 @@ - type: FingerprintMask - type: UserInterface interfaces: - - key: enum.ChameleonUiKey.Key + enum.ChameleonUiKey.Key: type: ChameleonBoundUserInterface - + - type: entity parent: ClothingHandsChameleon id: ClothingHandsChameleonThief diff --git a/Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml b/Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml index d13b284ff2..e19217686f 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml @@ -133,11 +133,13 @@ unequipSound: /Audio/Mecha/mechmove03.ogg - type: Tag tags: - - HidesHair - WhitelistChameleon - HelmetEVA - - HidesNose - type: IdentityBlocker + - type: HideLayerClothing + slots: + - Hair + - Snout - type: entity abstract: true @@ -161,6 +163,8 @@ lowPressureMultiplier: 1000 - type: TemperatureProtection coefficient: 0.1 + - type: FireProtection + reduction: 0.2 - type: Armor modifiers: coefficients: @@ -173,10 +177,12 @@ - type: IngestionBlocker - type: Tag tags: - - HidesHair - WhitelistChameleon - - HidesNose - type: IdentityBlocker + - type: HideLayerClothing + slots: + - Hair + - Snout - type: entity abstract: true @@ -248,6 +254,6 @@ - type: TemperatureProtection coefficient: 0.7 - type: GroupExamine - - type: Tag - tags: - - HidesHair + - type: HideLayerClothing + slots: + - Hair diff --git a/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml b/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml index 6985fe7ec3..b563fbc007 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml @@ -17,9 +17,9 @@ sprite: Clothing/Head/Hardsuits/basic.rsi - type: Clothing sprite: Clothing/Head/Hardsuits/basic.rsi - - type: Tag - tags: - - HidesNose + - type: HideLayerClothing + slots: + - Snout #Atmospherics Hardsuit - type: entity @@ -436,6 +436,8 @@ lowPressureMultiplier: 1000 - type: TemperatureProtection coefficient: 0.005 + - type: FireProtection + reduction: 0.2 - type: Armor modifiers: coefficients: diff --git a/Resources/Prototypes/Entities/Clothing/Head/hats.yml b/Resources/Prototypes/Entities/Clothing/Head/hats.yml index 4b752053a1..80a3046b93 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/hats.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/hats.yml @@ -273,7 +273,7 @@ - 0,0,0,0 - type: UserInterface interfaces: - - key: enum.StorageUiKey.Key + enum.StorageUiKey.Key: type: StorageBoundUserInterface - type: ContainerContainer containers: @@ -286,6 +286,36 @@ - type: DropOnSlip chance: 20 +- type: entity + parent: ClothingHeadHatChef + id: ClothingHeadHatChefNt + name: nanotrasen chef's hat + components: + - type: Sprite + sprite: Clothing/Head/Hats/chefhat_nt.rsi + - type: Clothing + sprite: Clothing/Head/Hats/chefhat_nt.rsi + +- type: entity + parent: ClothingHeadHatChef + id: ClothingHeadHatChefIdris + name: idris incorporated chef's hat + components: + - type: Sprite + sprite: Clothing/Head/Hats/chefhat_idris.rsi + - type: Clothing + sprite: Clothing/Head/Hats/chefhat_idris.rsi + +- type: entity + parent: ClothingHeadHatChef + id: ClothingHeadHatChefOrion + name: orion express' chef hat + components: + - type: Sprite + sprite: Clothing/Head/Hats/chefhat_orion.rsi + - type: Clothing + sprite: Clothing/Head/Hats/chefhat_orion.rsi + - type: entity parent: ClothingHeadBase id: ClothingHeadHatFedoraBrown @@ -430,14 +460,17 @@ sprite: Clothing/Head/Hats/plaguedoctor.rsi - type: Tag tags: - - HidesHair - WhitelistChameleon - ClothMade + - type: HideLayerClothing + slots: + - Hair + - Snout - type: DropOnSlip chance: 20 - type: entity - parent: ClothingHeadBase + parent: ClothingHeadHatWizardBase id: ClothingHeadHatRedwizard name: red wizard hat description: Strange-looking red hat-wear that most certainly belongs to a real magic user. @@ -570,7 +603,7 @@ chance: 20 - type: entity - parent: ClothingHeadBase + parent: ClothingHeadHatWizardBase id: ClothingHeadHatVioletwizard name: violet wizard hat description: "Strange-looking violet hat-wear that most certainly belongs to a real magic user." @@ -606,15 +639,18 @@ name: witch hat description: A witch hat. components: + - type: WizardClothes #Yes this will count - type: Sprite sprite: Clothing/Head/Hats/witch.rsi - type: Clothing sprite: Clothing/Head/Hats/witch.rsi - type: Tag tags: - - HidesHair - WhitelistChameleon - ClothMade + - type: HideLayerClothing + slots: + - Hair - type: DropOnSlip chance: 20 @@ -630,7 +666,14 @@ sprite: Clothing/Head/Hats/wizard_fake.rsi - type: entity + abstract: true parent: ClothingHeadBase + id: ClothingHeadHatWizardBase + components: + - type: WizardClothes + +- type: entity + parent: ClothingHeadHatWizardBase id: ClothingHeadHatWizard name: wizard hat description: Strange-looking blue hat-wear that most certainly belongs to a powerful magic user. @@ -941,8 +984,8 @@ - 0,0,0,0 - type: UserInterface interfaces: - - key: enum.StorageUiKey.Key - type: StorageBoundUserInterface + enum.StorageUiKey.Key: + type: StorageBoundUserInterface - type: ContainerContainer containers: storagebase: !type:Container @@ -1193,3 +1236,33 @@ sprite: Clothing/Head/Hats/beret_medic.rsi - type: Clothing sprite: Clothing/Head/Hats/beret_medic.rsi + +- type: entity + parent: ClothingHeadBase + id: ClothingHeadHatFlatcapBartenderNanotrasen + name: bartender's nanotrasen flatcap + components: + - type: Sprite + sprite: Clothing/Head/Hats/flatcap_nt.rsi + - type: Clothing + sprite: Clothing/Head/Hats/flatcap_nt.rsi + +- type: entity + parent: ClothingHeadBase + id: ClothingHeadHatFlatcapBartenderIdris + name: bartender's idris incorporated flatcap + components: + - type: Sprite + sprite: Clothing/Head/Hats/flatcap_idris.rsi + - type: Clothing + sprite: Clothing/Head/Hats/flatcap_idris.rsi + +- type: entity + parent: ClothingHeadBase + id: ClothingHeadHatFlatcapBartenderOrion + name: bartender's orion express flatcap + components: + - type: Sprite + sprite: Clothing/Head/Hats/flatcap_orion.rsi + - type: Clothing + sprite: Clothing/Head/Hats/flatcap_orion.rsi diff --git a/Resources/Prototypes/Entities/Clothing/Head/helmets.yml b/Resources/Prototypes/Entities/Clothing/Head/helmets.yml index b79b72ffb6..009c2ef784 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/helmets.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/helmets.yml @@ -110,6 +110,10 @@ Blunt: 0.95 Slash: 0.95 Piercing: 0.95 + - type: HideLayerClothing + slots: + - Hair + - Snout #Janitorial Bombsuit Helmet - type: entity @@ -157,10 +161,13 @@ sprite: Clothing/Head/Helmets/spaceninja.rsi - type: Tag tags: - - HidesHair - WhitelistChameleon - type: IngestionBlocker - type: IdentityBlocker + - type: HideLayerClothing + slots: + - Hair + - Snout #Templar Helmet - type: entity @@ -217,11 +224,16 @@ - type: IngestionBlocker - type: TemperatureProtection coefficient: 0.005 + - type: FireProtection + reduction: 0.15 # not fully sealed so less protection - type: IdentityBlocker - type: Tag tags: - - HidesHair - WhitelistChameleon + - type: HideLayerClothing + slots: + - Hair + - Snout #Atmos Fire Helmet - type: entity @@ -238,14 +250,19 @@ - type: IngestionBlocker - type: TemperatureProtection coefficient: 0.005 + - type: FireProtection + reduction: 0.2 - type: PressureProtection highPressureMultiplier: 0.25 lowPressureMultiplier: 1000 - type: IdentityBlocker - type: Tag tags: - - HidesHair - WhitelistChameleon + - type: HideLayerClothing + slots: + - Hair + - Snout #Chitinous Helmet - type: entity diff --git a/Resources/Prototypes/Entities/Clothing/Head/hoods.yml b/Resources/Prototypes/Entities/Clothing/Head/hoods.yml index 9ab528581a..0bca209d51 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/hoods.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/hoods.yml @@ -13,8 +13,11 @@ - type: IngestionBlocker - type: Tag tags: - - HidesHair - WhitelistChameleon + - type: HideLayerClothing + slots: + - Hair + - Snout - type: entity parent: ClothingHeadHatHoodBioGeneral @@ -94,9 +97,11 @@ sprite: Clothing/Head/Hoods/chaplain.rsi - type: Tag tags: - - HidesHair - HamsterWearable - WhitelistChameleon + - type: HideLayerClothing + slots: + - Hair - type: entity parent: ClothingHeadBase @@ -110,8 +115,10 @@ sprite: Clothing/Head/Hoods/cult.rsi - type: Tag tags: - - HidesHair - WhitelistChameleon + - type: HideLayerClothing + slots: + - Hair - type: entity parent: ClothingHeadBase @@ -125,9 +132,11 @@ sprite: Clothing/Head/Hoods/nun.rsi - type: Tag tags: - - HidesHair - HamsterWearable - WhitelistChameleon + - type: HideLayerClothing + slots: + - Hair - type: entity parent: ClothingHeadBase @@ -145,9 +154,10 @@ Heat: 0.95 Radiation: 0.65 - type: BreathMask - - type: Tag - tags: - - HidesHair + - type: HideLayerClothing + slots: + - Hair + - Snout - type: entity parent: ClothingHeadBase @@ -161,8 +171,10 @@ sprite: Clothing/Head/Hoods/goliathcloak.rsi - type: Tag tags: - - HidesHair - WhitelistChameleon + - type: HideLayerClothing + slots: + - Hair - type: entity parent: ClothingHeadBase @@ -175,9 +187,9 @@ sprite: Clothing/Head/Hoods/iansuit.rsi - type: Clothing sprite: Clothing/Head/Hoods/iansuit.rsi - - type: Tag - tags: - - HidesHair + - type: HideLayerClothing + slots: + - Hair - type: entity parent: ClothingHeadBase @@ -190,9 +202,9 @@ sprite: Clothing/Head/Hoods/carpsuit.rsi - type: Clothing sprite: Clothing/Head/Hoods/carpsuit.rsi - - type: Tag - tags: - - HidesHair + - type: HideLayerClothing + slots: + - Hair - type: entity parent: ClothingHeadBase @@ -207,8 +219,11 @@ - type: IdentityBlocker - type: Tag tags: - - HidesHair - WhitelistChameleon + - type: HideLayerClothing + slots: + - Hair + - Snout #Winter Coat Hoods - type: entity diff --git a/Resources/Prototypes/Entities/Clothing/Head/specific.yml b/Resources/Prototypes/Entities/Clothing/Head/specific.yml index 1dd36bff94..636f922d85 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/specific.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/specific.yml @@ -16,5 +16,5 @@ default: ClothingHeadHatBeret - type: UserInterface interfaces: - - key: enum.ChameleonUiKey.Key + enum.ChameleonUiKey.Key: type: ChameleonBoundUserInterface diff --git a/Resources/Prototypes/Entities/Clothing/Head/welding.yml b/Resources/Prototypes/Entities/Clothing/Head/welding.yml index 93d9b1e084..c0ae440a56 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/welding.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/welding.yml @@ -19,6 +19,10 @@ - type: Tag tags: - WhitelistChameleon + - WeldingMask + - type: HideLayerClothing + slots: + - Snout - type: entity parent: WeldingMaskBase @@ -34,6 +38,7 @@ tags: - HamsterWearable - WhitelistChameleon + - WeldingMask - type: entity parent: WeldingMaskBase diff --git a/Resources/Prototypes/Entities/Clothing/Masks/bandanas.yml b/Resources/Prototypes/Entities/Clothing/Masks/bandanas.yml index 246b47b800..f5ad2fb6c8 100644 --- a/Resources/Prototypes/Entities/Clothing/Masks/bandanas.yml +++ b/Resources/Prototypes/Entities/Clothing/Masks/bandanas.yml @@ -25,7 +25,10 @@ - type: Tag tags: - Bandana - - HidesNose + - type: HideLayerClothing + slots: + - Snout + hideOnToggle: true - type: entity parent: ClothingMaskBandanaBase diff --git a/Resources/Prototypes/Entities/Clothing/Masks/masks.yml b/Resources/Prototypes/Entities/Clothing/Masks/masks.yml index 1fc3725f98..19aad4d49c 100644 --- a/Resources/Prototypes/Entities/Clothing/Masks/masks.yml +++ b/Resources/Prototypes/Entities/Clothing/Masks/masks.yml @@ -15,7 +15,10 @@ tags: - HamsterWearable - WhitelistChameleon - - HidesNose + - type: HideLayerClothing + slots: + - Snout + hideOnToggle: true - type: entity parent: ClothingMaskGas @@ -197,7 +200,10 @@ tags: - ClownMask - WhitelistChameleon - - HidesNose + - IPCMaskWearable + - type: HideLayerClothing + slots: + - Snout - type: DropOnSlip chance: 5 @@ -210,7 +216,9 @@ - ClownMask - HamsterWearable - WhitelistChameleon - - HidesNose + - type: HideLayerClothing + slots: + - Snout - type: entity parent: ClothingMaskClown @@ -239,7 +247,10 @@ - type: IdentityBlocker - type: Tag tags: - - HidesNose + - IPCMaskWearable + - type: HideLayerClothing + slots: + - Snout - type: entity parent: ClothingMaskBase @@ -257,7 +268,10 @@ tags: - HamsterWearable - WhitelistChameleon - - HidesNose + - IPCMaskWearable + - type: HideLayerClothing + slots: + - Snout - type: entity parent: ClothingMaskPullableBase @@ -271,9 +285,12 @@ sprite: Clothing/Mask/sterile.rsi - type: IngestionBlocker - type: Item - storedRotation: -90 + size: Tiny - type: IdentityBlocker coverage: MOUTH + - type: PhysicalComposition + materialComposition: + Plastic: 25 - type: entity parent: ClothingMaskBase @@ -308,9 +325,10 @@ - type: BreathMask - type: IngestionBlocker - type: IdentityBlocker - - type: Tag - tags: - - HidesNose + - type: HideLayerClothing + slots: + - Snout + hideOnToggle: true - type: entity parent: ClothingMaskClownBase @@ -340,8 +358,11 @@ - type: Tag tags: - WhitelistChameleon - - HidesHair - - HidesNose + - type: HideLayerClothing + slots: + - Hair + - Snout + hideOnToggle: true - type: entity parent: ClothingMaskGasExplorer @@ -367,8 +388,11 @@ - type: Tag tags: - WhitelistChameleon - - HidesHair - - HidesNose + - type: HideLayerClothing + slots: + - Hair + - Snout + hideOnToggle: true - type: entity parent: ClothingMaskGasERT @@ -401,9 +425,12 @@ - type: BreathMask - type: Tag tags: + - IPCMaskWearable # Estacao Pirata - IPCs - HamsterWearable - WhitelistChameleon - - HidesNose + - type: HideLayerClothing + slots: + - Snout - type: IdentityBlocker - type: entity @@ -420,7 +447,10 @@ - type: IdentityBlocker - type: Tag tags: - - HidesNose + - IPCMaskWearable + - type: HideLayerClothing + slots: + - Snout - type: entity parent: ClothingMaskBase @@ -436,7 +466,10 @@ - type: IdentityBlocker - type: Tag tags: - - HidesNose + - IPCMaskWearable + - type: HideLayerClothing + slots: + - Snout - type: entity parent: ClothingMaskBase @@ -452,7 +485,10 @@ - type: IdentityBlocker - type: Tag tags: - - HidesNose + - IPCMaskWearable + - type: HideLayerClothing + slots: + - Snout - type: entity parent: ClothingMaskBase @@ -468,7 +504,10 @@ - type: IdentityBlocker - type: Tag tags: - - HidesNose + - IPCMaskWearable + - type: HideLayerClothing + slots: + - Snout - type: entity parent: ClothingMaskBase @@ -484,7 +523,10 @@ - type: IdentityBlocker - type: Tag tags: - - HidesNose + - IPCMaskWearable + - type: HideLayerClothing + slots: + - Snout - type: entity parent: ClothingMaskBase @@ -500,7 +542,10 @@ - type: IdentityBlocker - type: Tag tags: - - HidesNose + - IPCMaskWearable + - type: HideLayerClothing + slots: + - Snout - type: entity parent: ClothingMaskBase @@ -517,6 +562,7 @@ - type: Tag tags: - WhitelistChameleon + - IPCMaskWearable # Estacao Pirata - IPCs - type: entity parent: ClothingMaskNeckGaiter @@ -603,3 +649,6 @@ - type: EyeProtection - type: BreathMask - type: IdentityBlocker + - type: Tag + tags: + - IPCMaskWearable # Estacao Pirata - IPCs diff --git a/Resources/Prototypes/Entities/Clothing/Masks/specific.yml b/Resources/Prototypes/Entities/Clothing/Masks/specific.yml index d0e4e4d7e9..35b2806f6e 100644 --- a/Resources/Prototypes/Entities/Clothing/Masks/specific.yml +++ b/Resources/Prototypes/Entities/Clothing/Masks/specific.yml @@ -6,8 +6,7 @@ suffix: Chameleon components: - type: Tag - tags: # ignore "WhitelistChameleon" tag - - HidesNose + tags: [] # ignore "WhitelistChameleon" tag - type: Sprite sprite: Clothing/Mask/gas.rsi - type: Clothing @@ -19,12 +18,54 @@ - type: IdentityBlocker # need that for default ClothingMaskGas - type: UserInterface interfaces: - - key: enum.ChameleonUiKey.Key + enum.ChameleonUiKey.Key: type: ChameleonBoundUserInterface + - type: HideLayerClothing + slots: + - Snout - type: entity parent: ClothingMaskGasChameleon id: ClothingMaskGasVoiceChameleon suffix: Voice Mask, Chameleon components: - - type: VoiceMasker + - type: VoiceMask + - type: UserInterface + interfaces: + enum.VoiceMaskUIKey.Key: + type: VoiceMaskBoundUserInterface + - type: Tag + tags: + - IPCMaskWearable + - type: HideLayerClothing + slots: + - Snout + +- type: entity + parent: ClothingMaskBase + id: ClothingMaskWeldingGas + name: welding gas mask + description: A gas mask with built in welding goggles and face shield. Looks like a skull, clearly designed by a nerd. + components: + - type: Sprite + sprite: Clothing/Mask/welding-gas.rsi + state: icon + - type: Clothing + sprite: Clothing/Mask/welding-gas.rsi + - type: BreathMask + - type: IngestionBlocker + - type: IdentityBlocker + - type: FlashImmunity + - type: EyeProtection + - type: PhysicalComposition + materialComposition: + Steel: 200 + Glass: 100 + - type: StaticPrice + price: 100 + - type: Tag + tags: + - WhitelistChameleon + - type: HideLayerClothing + slots: + - Snout diff --git a/Resources/Prototypes/Entities/Clothing/Neck/medals.yml b/Resources/Prototypes/Entities/Clothing/Neck/medals.yml index e76a4b3ee3..e633c6c52d 100644 --- a/Resources/Prototypes/Entities/Clothing/Neck/medals.yml +++ b/Resources/Prototypes/Entities/Clothing/Neck/medals.yml @@ -1,102 +1,126 @@ -- type: entity - parent: ClothingNeckBase - id: ClothingNeckMedalBase - abstract: true - name: medal - description: not given to anyone - components: - - type: Tag - tags: - - WhitelistChameleon - -- type: entity - parent: ClothingNeckMedalBase - id: ClothingNeckBronzeheart - name: bronzeheart medal - description: Given to crewmates for exemplary bravery in the face of danger. - components: - - type: Sprite - sprite: Clothing/Neck/Medals/bronzeheart.rsi - - type: Clothing - sprite: Clothing/Neck/Medals/bronzeheart.rsi - -- type: entity - parent: ClothingNeckMedalBase - id: ClothingNeckGoldmedal - name: gold medal of crewmanship - description: Given to crewmates who display excellent crewmanship. - components: - - type: Sprite - sprite: Clothing/Neck/Medals/gold.rsi - - type: Clothing - sprite: Clothing/Neck/Medals/gold.rsi - - type: StealTarget - stealGroup: ClothingNeckGoldmedal - -- type: entity - parent: ClothingNeckMedalBase - id: ClothingNeckCargomedal - name: logistics medal # DeltaV - Logistics Department replacing Cargo - description: Whether it's for superior accountancy, courageous salvage work, or just being a friendly technician - this medal is to be assigned only for the best work in the logistics department. # DeltaV - Logistics Department replacing Cargo. Updated description for flavour - components: - - type: Sprite - sprite: DeltaV/Clothing/Neck/Medals/cargomedal.rsi # DeltaV - resprite - - type: Clothing - sprite: DeltaV/Clothing/Neck/Medals/cargomedal.rsi # DeltaV - resprite - -- type: entity - parent: ClothingNeckMedalBase - id: ClothingNeckEngineermedal - name: engineer medal - description: Whether it's by keeping the crew breathing, meticulous rewiring and upgrading, or just having plenty of materials on-hand - this medal is to be assigned only for the best work in the engineering department. # DeltaV - Updated description for flavour - components: - - type: Sprite - sprite: DeltaV/Clothing/Neck/Medals/engineermedal.rsi # DeltaV - resprite - - type: Clothing - sprite: DeltaV/Clothing/Neck/Medals/engineermedal.rsi # DeltaV - resprite - -- type: entity - parent: ClothingNeckMedalBase - id: ClothingNeckMedicalmedal - name: medical medal - description: Whether it's being the first on the scene of an accident, the most caring bedside manner, or just making sure the patients don't go mad - this medal is to be assigned only for the best work in the medical department. # DeltaV - Updated description for flavour - components: - - type: Sprite - sprite: DeltaV/Clothing/Neck/Medals/medicalmedal.rsi # DeltaV - resprite - - type: Clothing - sprite: DeltaV/Clothing/Neck/Medals/medicalmedal.rsi # DeltaV - resprite - -- type: entity - parent: ClothingNeckMedalBase - id: ClothingNeckSciencemedal - name: epistemics medal # DeltaV - Epistemics Department replacing Science - description: Whether it's pushing the edges of modern science and technology, foraying into the vast expanses of the noosphere, or just fetching the coffee - this medal is to be assigned only for the best work in the epistemics department. # DeltaV - Epistemics Department replacing Science. Updated description for flavour - components: - - type: Sprite - sprite: DeltaV/Clothing/Neck/Medals/sciencemedal.rsi # DeltaV - resprite - - type: Clothing - sprite: DeltaV/Clothing/Neck/Medals/sciencemedal.rsi # DeltaV - resprite - -- type: entity - parent: ClothingNeckMedalBase - id: ClothingNeckSecuritymedal - name: security medal - description: Whether it's catching armed terrorists, guarding the crew from alien threats, or just having a conversation in the corridor - this medal is to be assigned only for the best work in the security department. # DeltaV - Updated description for flavour - components: - - type: Sprite - sprite: DeltaV/Clothing/Neck/Medals/securitymedal.rsi # DeltaV - resprite - - type: Clothing - sprite: DeltaV/Clothing/Neck/Medals/securitymedal.rsi # DeltaV - resprite - -- type: entity - parent: ClothingNeckMedalBase - id: ClothingNeckClownmedal - name: bravado medal # DeltaV - Updated title so it's not just a joke medal. - description: Given to crewmates who laugh in the face of death, lift their peers' spirits, and uplift themselves and their friends to success. # DeltaV - Updated description for flavour and to reflect title. - components: - - type: Sprite - sprite: DeltaV/Clothing/Neck/Medals/clownmedal.rsi # DeltaV - resprite - - type: Clothing - sprite: DeltaV/Clothing/Neck/Medals/clownmedal.rsi # DeltaV - resprite - - type: StealTarget - stealGroup: ClothingNeckClownmedal +- type: entity + parent: ClothingNeckBase + id: ClothingNeckMedalBase + abstract: true + name: medal + description: not given to anyone + components: + - type: Tag + tags: + - WhitelistChameleon + +- type: entity + parent: ClothingNeckMedalBase + id: ClothingNeckBronzeheart + name: bronzeheart medal + description: Given to crewmates for exemplary bravery in the face of danger. + components: + - type: Sprite + sprite: Clothing/Neck/Medals/bronzeheart.rsi + - type: Clothing + sprite: Clothing/Neck/Medals/bronzeheart.rsi + - type: Tag + tags: + - Medal + +- type: entity + parent: ClothingNeckMedalBase + id: ClothingNeckGoldmedal + name: gold medal of crewmanship + description: Given to crewmates who display excellent crewmanship. + components: + - type: Sprite + sprite: Clothing/Neck/Medals/gold.rsi + - type: Clothing + sprite: Clothing/Neck/Medals/gold.rsi + - type: StealTarget + stealGroup: ClothingNeckGoldmedal + - type: Tag + tags: + - Medal + +- type: entity + parent: ClothingNeckMedalBase + id: ClothingNeckCargomedal + name: logistics medal # DeltaV - Logistics Department replacing Cargo + description: Whether it's for superior accountancy, courageous salvage work, or just being a friendly technician - this medal is to be assigned only for the best work in the logistics department. # DeltaV - Logistics Department replacing Cargo. Updated description for flavour + components: + - type: Sprite + sprite: DeltaV/Clothing/Neck/Medals/cargomedal.rsi # DeltaV - resprite + - type: Clothing + sprite: DeltaV/Clothing/Neck/Medals/cargomedal.rsi + - type: Tag + tags: + - Medal + +- type: entity + parent: ClothingNeckMedalBase + id: ClothingNeckEngineermedal + name: engineer medal + description: Whether it's by keeping the crew breathing, meticulous rewiring and upgrading, or just having plenty of materials on-hand - this medal is to be assigned only for the best work in the engineering department. # DeltaV - Updated description for flavour + components: + - type: Sprite + sprite: DeltaV/Clothing/Neck/Medals/engineermedal.rsi # DeltaV - resprite + - type: Clothing + sprite: DeltaV/Clothing/Neck/Medals/engineermedal.rsi + - type: Tag + tags: + - Medal + +- type: entity + parent: ClothingNeckMedalBase + id: ClothingNeckMedicalmedal + name: medical medal + description: Whether it's being the first on the scene of an accident, the most caring bedside manner, or just making sure the patients don't go mad - this medal is to be assigned only for the best work in the medical department. # DeltaV - Updated description for flavour + components: + - type: Sprite + sprite: DeltaV/Clothing/Neck/Medals/medicalmedal.rsi + - type: Clothing + sprite: DeltaV/Clothing/Neck/Medals/medicalmedal.rsi + - type: Tag + tags: + - Medal + +- type: entity + parent: ClothingNeckMedalBase + id: ClothingNeckSciencemedal + name: epistemics medal # DeltaV - Epistemics Department replacing Science + description: Whether it's pushing the edges of modern science and technology, foraying into the vast expanses of the noosphere, or just fetching the coffee - this medal is to be assigned only for the best work in the epistemics department. # DeltaV - Epistemics Department replacing Science. Updated description for flavour + components: + - type: Sprite + sprite: DeltaV/Clothing/Neck/Medals/sciencemedal.rsi + - type: Clothing + sprite: DeltaV/Clothing/Neck/Medals/sciencemedal.rsi + - type: Tag + tags: + - Medal + +- type: entity + parent: ClothingNeckMedalBase + id: ClothingNeckSecuritymedal + name: security medal + description: Whether it's catching armed terrorists, guarding the crew from alien threats, or just having a conversation in the corridor - this medal is to be assigned only for the best work in the security department. # DeltaV - Updated description for flavour + components: + - type: Sprite + sprite: DeltaV/Clothing/Neck/Medals/securitymedal.rsi + - type: Clothing + sprite: DeltaV/Clothing/Neck/Medals/securitymedal.rsi + - type: Tag + tags: + - Medal + +- type: entity + parent: ClothingNeckMedalBase + id: ClothingNeckClownmedal + name: bravado medal # DeltaV - Updated title so it's not just a joke medal. + description: Given to crewmates who laugh in the face of death, lift their peers' spirits, and uplift themselves and their friends to success. # DeltaV - Updated description for flavour and to reflect title. + components: + - type: Sprite + sprite: DeltaV/Clothing/Neck/Medals/clownmedal.rsi # DeltaV - resprite + - type: Clothing + sprite: DeltaV/Clothing/Neck/Medals/clownmedal.rsi # DeltaV - resprite + - type: StealTarget + stealGroup: ClothingNeckClownmedal + - type: Tag + tags: + - Medal diff --git a/Resources/Prototypes/Entities/Clothing/Neck/specific.yml b/Resources/Prototypes/Entities/Clothing/Neck/specific.yml index 1f50dc1ef1..56fddceaad 100644 --- a/Resources/Prototypes/Entities/Clothing/Neck/specific.yml +++ b/Resources/Prototypes/Entities/Clothing/Neck/specific.yml @@ -16,5 +16,5 @@ default: ClothingNeckScarfStripedRed - type: UserInterface interfaces: - - key: enum.ChameleonUiKey.Key + enum.ChameleonUiKey.Key: type: ChameleonBoundUserInterface diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/armor.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/armor.yml index eb580d6499..46a431ddff 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/armor.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/armor.yml @@ -2,7 +2,7 @@ # NOTE: Half of the kind of armor you're probably thinking of is in vests.yml. These should probably be merged some day. - type: entity - parent: ClothingOuterBaseMedium + parent: [ClothingOuterBaseMedium, AllowSuitStorageClothing] id: ClothingOuterArmorBasic name: armor vest description: A standard Type I armored vest that provides decent protection against most types of damage. @@ -35,7 +35,7 @@ sprite: Clothing/OuterClothing/Armor/security_slim.rsi - type: entity - parent: ClothingOuterBaseLarge + parent: [ClothingOuterBaseLarge, AllowSuitStorageClothing] id: ClothingOuterArmorRiot name: riot suit description: A suit of semi-flexible polycarbonate body armor with heavy padding to protect against melee attacks. Perfect for fighting delinquents around the station. @@ -95,6 +95,7 @@ Heat: 0.4 # this technically means it protects against fires pretty well? -heat is just for lasers and stuff, not atmos temperature - type: Reflect reflectProb: 1 + innate: true # armor grants a passive shield that does not require concentration to maintain reflects: - Energy @@ -118,7 +119,7 @@ - type: GroupExamine - type: entity - parent: ClothingOuterBaseLarge + parent: [ClothingOuterBaseLarge, AllowSuitStorageClothing] id: ClothingOuterArmorHeavy name: heavy armor suit description: A heavily armored suit that protects against excessive damage. @@ -137,6 +138,8 @@ Radiation: 0 Caustic: 0.75 - type: GroupExamine + - type: ClothingRequiredStepTriggerImmune + slots: WITHOUT_POCKET - type: Tag tags: - FullBodyOuter @@ -186,7 +189,7 @@ sprite: Clothing/OuterClothing/Armor/magusred.rsi - type: entity - parent: ClothingOuterBaseLarge + parent: [ClothingOuterBaseLarge, AllowSuitStorageClothing] id: ClothingOuterArmorCaptainCarapace name: "captain's carapace" description: "An armored chestpiece that provides protection whilst still offering maximum mobility and flexibility. Issued only to the station's finest." @@ -236,6 +239,8 @@ - type: ExplosionResistance damageCoefficient: 0.5 - type: GroupExamine + - type: ClothingRequiredStepTriggerImmune + slots: WITHOUT_POCKET - type: Tag tags: - FullBodyOuter @@ -265,6 +270,8 @@ - type: Construction graph: BoneArmor node: armor + - type: ClothingRequiredStepTriggerImmune + slots: WITHOUT_POCKET - type: entity parent: ClothingOuterBaseLarge @@ -284,3 +291,5 @@ Piercing: 0.6 Heat: 0.5 - type: GroupExamine + - type: ClothingRequiredStepTriggerImmune + slots: WITHOUT_POCKET diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/base_clothingouter.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/base_clothingouter.yml index 358f91d297..c18e5d6ab6 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/base_clothingouter.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/base_clothingouter.yml @@ -41,8 +41,8 @@ ents: [] - type: UserInterface interfaces: - - key: enum.StorageUiKey.Key - type: StorageBoundUserInterface + enum.StorageUiKey.Key: + type: StorageBoundUserInterface - type: StaticPrice price: 70 @@ -104,7 +104,7 @@ - type: entity abstract: true - parent: [ClothingOuterBase, GeigerCounterClothing] + parent: [ClothingOuterBase, GeigerCounterClothing, AllowSuitStorageClothing] id: ClothingOuterHardsuitBase name: base hardsuit components: @@ -112,7 +112,9 @@ highPressureMultiplier: 0.3 lowPressureMultiplier: 1000 - type: TemperatureProtection - coefficient: 0.001 # yes it needs to be this low, fires are fucking deadly apparently!!!! + coefficient: 0.01 + - type: FireProtection + reduction: 0.75 # almost perfectly sealed, atmos firesuit is better - type: ClothingSpeedModifier walkModifier: 0.4 sprintModifier: 0.6 @@ -147,10 +149,12 @@ localSoundOnly: true - type: StaminaDamageResistance coefficient: 0.75 # 25% + - type: ClothingRequiredStepTriggerImmune + slots: WITHOUT_POCKET - type: entity abstract: true - parent: ClothingOuterBase + parent: [ClothingOuterBase, AllowSuitStorageClothing] id: ClothingOuterEVASuitBase name: base EVA Suit components: @@ -171,6 +175,8 @@ - type: Clothing equipDelay: 1.25 # Softsuits are easier to put on and off unequipDelay: 1 + - type: ClothingRequiredStepTriggerImmune + slots: WITHOUT_POCKET - type: entity parent: ClothingOuterBase @@ -193,4 +199,4 @@ id: ClothingOuterBaseMedium components: - type: Item - size: Huge + size: Huge \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/coats.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/coats.yml index a47a2bbceb..dc5454d597 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/coats.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/coats.yml @@ -10,7 +10,7 @@ sprite: Clothing/OuterClothing/Coats/bomber.rsi - type: entity - parent: ClothingOuterStorageBase + parent: [ClothingOuterStorageBase, AllowSuitStorageClothing] id: ClothingOuterCoatDetective name: detective trenchcoat description: A rugged canvas trenchcoat, designed and created by TX Fabrication Corp. Wearing it makes you feel for the plight of the Tibetans. @@ -31,6 +31,15 @@ Piercing: 0.70 Heat: 0.80 +- type: entity + parent: ClothingOuterCoatDetective + id: ClothingOuterCoatDetectiveLoadout + components: + - type: StorageFill + contents: + - id: SmokingPipeFilledTobacco + - id: FlippoLighter #Not the steal objective, only difference from normal detective trenchcoat + - type: entity parent: ClothingOuterStorageBase id: ClothingOuterCoatGentle @@ -43,7 +52,38 @@ sprite: Clothing/OuterClothing/Coats/gentlecoat.rsi - type: entity - parent: ClothingOuterStorageBase + abstract: true + parent: AllowSuitStorageClothing + id: ClothingOuterArmorHoS + components: + - type: Armor + modifiers: + coefficients: + Blunt: 0.7 + Slash: 0.7 + Piercing: 0.7 + Heat: 0.7 + Caustic: 0.75 # not the full 90% from ss13 because of the head + - type: ExplosionResistance + damageCoefficient: 0.9 + +- type: entity + abstract: true + parent: AllowSuitStorageClothing + id: ClothingOuterArmorWarden + components: + - type: Armor + modifiers: + coefficients: + Blunt: 0.7 + Slash: 0.7 + Piercing: 0.7 + Heat: 0.7 + - type: ExplosionResistance + damageCoefficient: 0.9 + +- type: entity + parent: [ClothingOuterArmorHoS, ClothingOuterStorageBase] id: ClothingOuterCoatHoSTrench name: head of security's armored trenchcoat description: A greatcoat enhanced with a special alloy for some extra protection and style for those with a commanding presence. @@ -52,16 +92,6 @@ sprite: DeltaV/Clothing/OuterClothing/Coats/hos_trenchcoat.rsi # DeltaV - resprite - type: Clothing sprite: DeltaV/Clothing/OuterClothing/Coats/hos_trenchcoat.rsi # DeltaV - resprite - - type: Armor - modifiers: - coefficients: - Blunt: 0.70 - Slash: 0.70 - Piercing: 0.70 - Heat: 0.70 - Caustic: 0.75 #not the full 90% from ss13 because of the head - - type: ExplosionResistance - damageCoefficient: 0.90 - type: entity parent: ClothingOuterStorageBase @@ -197,7 +227,7 @@ parent: ClothingOuterStorageFoldableBase id: ClothingOuterCoatLabCmo name: chief medical officer's lab coat - description: Bluer than the standard model. + description: Custom made blue lab coat for the Chief Medical Officer, offers improved protection against chemical spills and minor cuts components: - type: Sprite sprite: Clothing/OuterClothing/Coats/labcoat_cmo.rsi @@ -206,7 +236,9 @@ - type: Armor modifiers: coefficients: - Caustic: 0.75 + Slash: 0.95 + Heat: 0.95 + Caustic: 0.65 - type: entity parent: [ClothingOuterStorageFoldableBaseOpened, ClothingOuterCoatLabCmo] @@ -286,7 +318,7 @@ sprite: Clothing/OuterClothing/Coats/pirate.rsi - type: entity - parent: ClothingOuterStorageBase + parent: [ClothingOuterArmorWarden, ClothingOuterStorageBase] id: ClothingOuterCoatWarden name: warden's armored jacket description: A sturdy, utilitarian jacket designed to protect a warden from any brig-bound threats. @@ -295,15 +327,6 @@ sprite: Clothing/OuterClothing/Coats/warden.rsi - type: Clothing sprite: Clothing/OuterClothing/Coats/warden.rsi - - type: Armor - modifiers: - coefficients: - Blunt: 0.70 - Slash: 0.70 - Piercing: 0.70 - Heat: 0.70 - - type: ExplosionResistance - damageCoefficient: 0.90 - type: entity parent: ClothingOuterStorageBase diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml index ba889285a1..bb4aedcb5d 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml @@ -52,20 +52,14 @@ Blunt: 0.9 Slash: 0.9 Piercing: 0.9 - Heat: 0.2 + Heat: 0.8 Radiation: 0.5 - Caustic: 0.5 - type: ClothingSpeedModifier walkModifier: 0.7 sprintModifier: 0.7 - type: HeldSpeedModifier - type: ToggleableClothing clothingPrototype: ClothingHeadHelmetHardsuitAtmos - - type: ReverseEngineering # Nyano - difficulty: 5 - newItem: ClothingOuterHardsuitJuggernautReverseEngineered - recipes: - - ClothingOuterHardsuitJuggernautReverseEngineered #Engineering Hardsuit - type: entity @@ -81,8 +75,6 @@ - type: PressureProtection highPressureMultiplier: 0.04 lowPressureMultiplier: 1000 - - type: TemperatureProtection - coefficient: 0.01 - type: ExplosionResistance damageCoefficient: 0.5 - type: Armor @@ -420,7 +412,7 @@ parent: ClothingOuterHardsuitBase id: ClothingOuterHardsuitRd name: experimental research hardsuit - description: A special suit that protects against hazardous, low pressure environments. Has an additional layer of armor. Able to be compressed to small sizes. + description: A special suit that protects against hazardous, low pressure environments. Has an additional layer of armor. components: - type: Sprite sprite: Clothing/OuterClothing/Hardsuits/rd.rsi @@ -445,7 +437,9 @@ sprintModifier: 0.75 - type: HeldSpeedModifier - type: Item - size: Normal + size: Huge + shape: + - 0,0,4,4 #5X5, can fit in a duffel bag but nothing smaller. - type: Tag tags: - WhitelistChameleon @@ -557,11 +551,6 @@ - type: HeldSpeedModifier - type: ToggleableClothing clothingPrototype: ClothingHeadHelmetHardsuitSyndie - - type: ReverseEngineering # Nyano - difficulty: 5 - newItem: ClothingOuterHardsuitSyndieReverseEngineered - recipes: - - ClothingOuterHardsuitSyndieReverseEngineered - type: Tag tags: - MonkeyWearable @@ -610,6 +599,8 @@ coefficient: 0.001 - type: ExplosionResistance damageCoefficient: 0.2 + - type: FireProtection + reduction: 0.8 # perfect protection like atmos firesuit for pyro tf2 ops - type: Armor modifiers: coefficients: diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/misc.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/misc.yml index 4b2c3c9c3f..b5169a9cf6 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/misc.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/misc.yml @@ -56,6 +56,39 @@ - type: Clothing sprite: Clothing/OuterClothing/Misc/chef.rsi +- type: entity + parent: ClothingOuterStorageBase + id: ClothingOuterJacketChefNt + name: nanotrasen chef jacket + description: An apron-jacket used by a high class Nanotrasen Corporation chef. + components: + - type: Sprite + sprite: Clothing/OuterClothing/Misc/chef_nt.rsi + - type: Clothing + sprite: Clothing/OuterClothing/Misc/chef_nt.rsi + +- type: entity + parent: ClothingOuterStorageBase + id: ClothingOuterJacketChefIdris + name: idris chef jacket + description: An apron-jacket used by a high class Idris Incorporated chef. + components: + - type: Sprite + sprite: Clothing/OuterClothing/Misc/chef_idris.rsi + - type: Clothing + sprite: Clothing/OuterClothing/Misc/chef_idris.rsi + +- type: entity + parent: ClothingOuterStorageBase + id: ClothingOuterJacketChefOrion + name: orion chef jacket + description: An apron-jacket used by a high class Orion Expresschef. + components: + - type: Sprite + sprite: Clothing/OuterClothing/Misc/chef_orion.rsi + - type: Clothing + sprite: Clothing/OuterClothing/Misc/chef_orion.rsi + - type: entity parent: ClothingOuterBase id: ClothingOuterHoodieBlack @@ -166,9 +199,16 @@ - type: Clothing sprite: Clothing/OuterClothing/Misc/santa.rsi +- type: entity + abstract: true + parent: ClothingOuterBase + id: ClothingOuterWizardBase + components: + - type: WizardClothes + # Is this wizard wearing a fanny pack??? - type: entity - parent: ClothingOuterEVASuitBase # DeltaV - Make real wizard clothes space proof + parent: ClothingOuterWizardBase id: ClothingOuterWizardViolet name: violet wizard robes description: A bizarre gem-encrusted violet robe that radiates magical energies. @@ -179,7 +219,7 @@ sprite: Clothing/OuterClothing/Misc/violetwizard.rsi - type: entity - parent: ClothingOuterEVASuitBase # DeltaV - Make real wizard clothes space proof + parent: ClothingOuterWizardBase id: ClothingOuterWizard name: wizard robes description: A bizarre gem-encrusted blue robe that radiates magical energies. @@ -190,7 +230,7 @@ sprite: Clothing/OuterClothing/Misc/wizard.rsi - type: entity - parent: ClothingOuterEVASuitBase # DeltaV - Make real wizard clothes space proof + parent: ClothingOuterWizardBase id: ClothingOuterWizardRed name: red wizard robes description: Strange-looking, red, hat-wear that most certainly belongs to a real magic user. @@ -388,3 +428,20 @@ sprite: Clothing/OuterClothing/Misc/unathirobe.rsi - type: Clothing sprite: Clothing/OuterClothing/Misc/unathirobe.rsi + +- type: entity + parent: ClothingOuterBase + id: ClothingOuterShadowkinRestraints + name: shadowkin restraints + description: One of the first creations after finding Shadowkin, these were used to contain the Shadowkin during research so they didn't teleport away. + components: + - type: Sprite + sprite: Clothing/OuterClothing/Misc/shadowkinrestraints.rsi + - type: Clothing + sprite: Clothing/OuterClothing/Misc/shadowkinrestraints.rsi + equipDelay: 0.5 + unequipDelay: 10 + - type: ShadowkinCuff + - type: GuideHelp + guides: + - Shadowkin \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/specific.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/specific.yml index 9be8c22a0a..c3d9bb1224 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/specific.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/specific.yml @@ -16,5 +16,5 @@ default: ClothingOuterVest - type: UserInterface interfaces: - - key: enum.ChameleonUiKey.Key + enum.ChameleonUiKey.Key: type: ChameleonBoundUserInterface diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml index 9f0a01cc48..6215fb3b45 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml @@ -27,6 +27,8 @@ - WhitelistChameleon - FullBodyOuter - HidesHarpyWings + - type: ClothingRequiredStepTriggerImmune + slots: WITHOUT_POCKET - type: entity parent: ClothingOuterSuitBomb @@ -52,7 +54,7 @@ - FullBodyOuter - type: entity - parent: ClothingOuterBaseLarge + parent: [ClothingOuterBaseLarge, AllowSuitStorageClothing] id: ClothingOuterSuitFire name: fire suit description: A suit that helps protect against hazardous temperatures. @@ -65,24 +67,28 @@ highPressureMultiplier: 0.04 - type: TemperatureProtection coefficient: 0.005 + - type: FireProtection + reduction: 0.65 # doesnt have a full seal so not as good - type: Armor modifiers: coefficients: Slash: 0.9 - Heat: 0.3 - Cold: 0.2 + Heat: 0.8 + Cold: 0.8 - type: ClothingSpeedModifier walkModifier: 0.8 sprintModifier: 0.7 - type: HeldSpeedModifier - type: GroupExamine + - type: ClothingRequiredStepTriggerImmune + slots: WITHOUT_POCKET - type: Tag tags: - WhitelistChameleon - HidesHarpyWings - type: entity - parent: ClothingOuterBaseLarge + parent: [ClothingOuterBaseLarge, AllowSuitStorageClothing] id: ClothingOuterSuitAtmosFire name: atmos fire suit description: An expensive firesuit that protects against even the most deadly of station fires. Designed to protect even if the wearer is set aflame. @@ -96,17 +102,21 @@ lowPressureMultiplier: 1000 - type: TemperatureProtection coefficient: 0.001 + - type: FireProtection + reduction: 0.8 # atmos firesuit offers best protection, hardsuits are a little vulnerable - type: Armor modifiers: coefficients: Slash: 0.9 - Heat: 0.3 - Cold: 0.2 + Heat: 0.8 + Cold: 0.8 - type: ClothingSpeedModifier walkModifier: 0.8 sprintModifier: 0.8 - type: HeldSpeedModifier - type: GroupExamine + - type: ClothingRequiredStepTriggerImmune + slots: WITHOUT_POCKET - type: Tag tags: - FullBodyOuter @@ -114,7 +124,7 @@ - HidesHarpyWings - type: entity - parent: [ClothingOuterBaseLarge, GeigerCounterClothing] + parent: [ClothingOuterBaseLarge, GeigerCounterClothing, AllowSuitStorageClothing] id: ClothingOuterSuitRad name: radiation suit description: "A suit that protects against radiation. The label reads, 'Made with lead. Please do not consume insulation.'" @@ -135,6 +145,8 @@ - type: ContainerContainer containers: toggleable-clothing: !type:ContainerSlot {} + - type: ClothingRequiredStepTriggerImmune + slots: WITHOUT_POCKET - type: Tag tags: - FullBodyOuter @@ -142,7 +154,7 @@ - HidesHarpyWings - type: entity - parent: ClothingOuterBaseLarge + parent: [ClothingOuterBaseLarge, AllowSuitStorageClothing] id: ClothingOuterSuitSpaceNinja name: space ninja suit description: This black technologically advanced, cybernetically-enhanced suit provides many abilities like invisibility or teleportation. @@ -191,6 +203,8 @@ sprite: Clothing/OuterClothing/Suits/chicken.rsi - type: Clothing sprite: Clothing/OuterClothing/Suits/chicken.rsi + - type: ClothingRequiredStepTriggerImmune + slots: WITHOUT_POCKET - type: Tag tags: - FullBodyOuter @@ -219,6 +233,8 @@ - type: ContainerContainer containers: toggleable-clothing: !type:ContainerSlot {} + - type: ClothingRequiredStepTriggerImmune + slots: WITHOUT_POCKET - type: Tag tags: - FullBodyOuter @@ -243,6 +259,8 @@ - type: Construction graph: ClothingOuterSuitIan node: suit + - type: ClothingRequiredStepTriggerImmune + slots: WITHOUT_POCKET - type: entity parent: ClothingOuterBase diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/vests.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/vests.yml index 784084652c..9359bb0ce3 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/vests.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/vests.yml @@ -1,6 +1,6 @@ #Web vest - type: entity - parent: ClothingOuterStorageBase + parent: [ClothingOuterStorageBase, AllowSuitStorageClothing] id: ClothingOuterVestWeb name: web vest description: A synthetic armor vest. This one has added webbing and ballistic plates. @@ -21,7 +21,7 @@ #Mercenary web vest - type: entity - parent: ClothingOuterStorageBase #web vest so it should have some pockets for ammo + parent: ClothingOuterVestWeb #web vest so it should have some pockets for ammo id: ClothingOuterVestWebMerc name: merc web vest description: A high-quality armored vest made from a hard synthetic material. It's surprisingly flexible and light, despite formidable armor plating. @@ -87,3 +87,36 @@ sprite: Clothing/OuterClothing/Vests/vest.rsi - type: Clothing sprite: Clothing/OuterClothing/Vests/vest.rsi + +- type: entity + parent: ClothingOuterBase + id: ClothingOuterVestNt + name: nanotrasen vest + description: A thick vest with a rubbery, water-resistant shell coloured in blue. + components: + - type: Sprite + sprite: Clothing/OuterClothing/Vests/vest_nt.rsi + - type: Clothing + sprite: Clothing/OuterClothing/Vests/vest_nt.rsi + +- type: entity + parent: ClothingOuterBase + id: ClothingOuterVestIdris + name: idris incorporated vest + description: A thick vest with a rubbery, water-resistant shell coloured in teal. + components: + - type: Sprite + sprite: Clothing/OuterClothing/Vests/vest_idris.rsi + - type: Clothing + sprite: Clothing/OuterClothing/Vests/vest_idris.rsi + +- type: entity + parent: ClothingOuterBase + id: ClothingOuterVestOrion + name: orion express vest + description: A thick vest with a rubbery, water-resistant shell coloured in brown. + components: + - type: Sprite + sprite: Clothing/OuterClothing/Vests/vest_orion.rsi + - type: Clothing + sprite: Clothing/OuterClothing/Vests/vest_orion.rsi \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/wintercoats.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/wintercoats.yml index 160c606cc4..73dc6c8c7f 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/wintercoats.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/wintercoats.yml @@ -231,9 +231,23 @@ clothingPrototype: ClothingHeadHatHoodWinterHOP - type: entity - parent: ClothingOuterWinterCoatToggleable + parent: [ClothingOuterArmorHoS, ClothingOuterWinterCoatToggleable] id: ClothingOuterWinterHoS + name: head of security's armored winter coat + description: A sturdy, utilitarian winter coat designed to protect a head of security from any brig-bound threats and hypothermic events. + components: + - type: Sprite + sprite: Clothing/OuterClothing/WinterCoats/coathosarmored.rsi + - type: Clothing + sprite: Clothing/OuterClothing/WinterCoats/coathosarmored.rsi + - type: ToggleableClothing + clothingPrototype: ClothingHeadHatHoodWinterHOS + +- type: entity + parent: ClothingOuterWinterCoatToggleable + id: ClothingOuterWinterHoSUnarmored name: head of security's winter coat + description: A sturdy coat, a warm coat, but not an armored coat. components: - type: Sprite sprite: Clothing/OuterClothing/WinterCoats/coathos.rsi @@ -446,22 +460,28 @@ clothingPrototype: ClothingHeadHatHoodWinterSci - type: entity - parent: ClothingOuterWinterCoatToggleable + parent: [ClothingOuterArmorWarden, ClothingOuterWinterCoatToggleable] id: ClothingOuterWinterWarden name: warden's armored winter coat description: A sturdy, utilitarian winter coat designed to protect a warden from any brig-bound threats and hypothermic events. components: + - type: Sprite + sprite: Clothing/OuterClothing/WinterCoats/coatwardenarmored.rsi + - type: Clothing + sprite: Clothing/OuterClothing/WinterCoats/coatwardenarmored.rsi + - type: ToggleableClothing + clothingPrototype: ClothingHeadHatHoodWinterWarden + +- type: entity + parent: ClothingOuterWinterCoatToggleable + id: ClothingOuterWinterWardenUnarmored + name: warden's winter coat + description: A sturdy coat, a warm coat, but not an armored coat. + components: - type: Sprite sprite: Clothing/OuterClothing/WinterCoats/coatwarden.rsi - type: Clothing sprite: Clothing/OuterClothing/WinterCoats/coatwarden.rsi - - type: Armor - modifiers: - coefficients: - Blunt: 0.70 - Slash: 0.70 - Piercing: 0.8 #slightly less bulletproof then warden's normal coat - Heat: 0.70 - type: ToggleableClothing clothingPrototype: ClothingHeadHatHoodWinterWarden diff --git a/Resources/Prototypes/Entities/Clothing/Shoes/base_clothingshoes.yml b/Resources/Prototypes/Entities/Clothing/Shoes/base_clothingshoes.yml index 1119d5cda7..4e7c966a0d 100644 --- a/Resources/Prototypes/Entities/Clothing/Shoes/base_clothingshoes.yml +++ b/Resources/Prototypes/Entities/Clothing/Shoes/base_clothingshoes.yml @@ -23,6 +23,7 @@ tags: - ClothMade - WhitelistChameleon + - type: ClothingRequiredStepTriggerImmune - type: entity abstract: true @@ -58,4 +59,4 @@ description: Fluffy boots to help survive even the coldest of winters. components: - type: TemperatureProtection - coefficient: 0.2 + coefficient: 0.8 diff --git a/Resources/Prototypes/Entities/Clothing/Shoes/magboots.yml b/Resources/Prototypes/Entities/Clothing/Shoes/magboots.yml index 880239b51e..c4bdefd3a1 100644 --- a/Resources/Prototypes/Entities/Clothing/Shoes/magboots.yml +++ b/Resources/Prototypes/Entities/Clothing/Shoes/magboots.yml @@ -28,6 +28,10 @@ - type: Tag tags: - WhitelistChameleon + - type: ReverseEngineering + difficulty: 2 + recipes: + - ClothingShoesBootsMag - type: entity parent: ClothingShoesBootsMag @@ -110,7 +114,7 @@ key: enum.SharedGasTankUiKey.Key - type: UserInterface interfaces: - - key: enum.SharedGasTankUiKey.Key + enum.SharedGasTankUiKey.Key: type: GasTankBoundUserInterface - type: Explosive explosionType: Default diff --git a/Resources/Prototypes/Entities/Clothing/Shoes/misc.yml b/Resources/Prototypes/Entities/Clothing/Shoes/misc.yml index d1f6e083f4..d5a695c7c0 100644 --- a/Resources/Prototypes/Entities/Clothing/Shoes/misc.yml +++ b/Resources/Prototypes/Entities/Clothing/Shoes/misc.yml @@ -47,6 +47,8 @@ collection: FootstepDuck params: variation: 0.07 + - type: WaddleWhenWorn + tumbleIntensity: 10 # smaller than clown shoes - type: Construction graph: ClothingShoeSlippersDuck node: shoes diff --git a/Resources/Prototypes/Entities/Clothing/Shoes/specific.yml b/Resources/Prototypes/Entities/Clothing/Shoes/specific.yml index 987eda582e..4ae752345a 100644 --- a/Resources/Prototypes/Entities/Clothing/Shoes/specific.yml +++ b/Resources/Prototypes/Entities/Clothing/Shoes/specific.yml @@ -185,7 +185,7 @@ default: ClothingShoesColorBlack - type: UserInterface interfaces: - - key: enum.ChameleonUiKey.Key + enum.ChameleonUiKey.Key: type: ChameleonBoundUserInterface - type: entity diff --git a/Resources/Prototypes/Entities/Clothing/Uniforms/base_clothinguniforms.yml b/Resources/Prototypes/Entities/Clothing/Uniforms/base_clothinguniforms.yml index 9c896786c2..0a8e440e5b 100644 --- a/Resources/Prototypes/Entities/Clothing/Uniforms/base_clothinguniforms.yml +++ b/Resources/Prototypes/Entities/Clothing/Uniforms/base_clothinguniforms.yml @@ -9,6 +9,12 @@ slots: [innerclothing] equipSound: path: /Audio/Items/jumpsuit_equip.ogg + - type: EmitSoundOnPickup # Not sure why this wasn't set to the base; as all clothes should have clothing sounds. + sound: /Audio/SimpleStation14/Items/Handling/cloth_pickup.ogg + - type: EmitSoundOnDrop + sound: /Audio/SimpleStation14/Items/Handling/cloth_drop.ogg + - type: EmitSoundOnLand + sound: /Audio/SimpleStation14/Items/Handling/cloth_drop.ogg - type: Butcherable butcheringType: Knife spawned: @@ -36,7 +42,10 @@ - type: Clothing slots: [innerclothing] femaleMask: UniformTop - + - type: Tag # Needed for species with nonhuman legs/can only wear skirts + tags: + - Skirt + - ClothMade - type: entity abstract: true @@ -50,12 +59,6 @@ - type: WirelessNetworkConnection range: 1200 - type: StationLimitedNetwork - - type: EmitSoundOnPickup - sound: /Audio/SimpleStation14/Items/Handling/cloth_pickup.ogg - - type: EmitSoundOnDrop - sound: /Audio/SimpleStation14/Items/Handling/cloth_drop.ogg - - type: EmitSoundOnLand - sound: /Audio/SimpleStation14/Items/Handling/cloth_drop.ogg - type: entity abstract: true @@ -65,7 +68,87 @@ - type: Clothing slots: [innerclothing] femaleMask: UniformTop - - type: Tag #DeltaV, needed for species with nonhuman legs/can only wear skirts + - type: Tag # Needed for species with nonhuman legs/can only wear skirts + # And for moths to be able to eat this. tags: - Skirt - - ClothMade # Delta-V - allows moths to eat this + - ClothMade + +- type: entity # For clothes that need suit sensors. + parent: [UnsensoredClothingUniformBase, BaseFoldable] + id: ClothingUniformBaseUnsensoredFlippable + abstract: true + components: + - type: Appearance + - type: Foldable + canFoldInsideContainer: true + unfoldVerbText: fold-unrollsleeves-verb + foldVerbText: fold-rollsleeves-verb + - type: FoldableClothing + foldedEquippedPrefix: flipped + foldedHeldPrefix: flipped + - type: Sprite + layers: + - state: icon + map: [ "unfoldedLayer" ] + - state: icon_flipped + map: ["foldedLayer"] + visible: false + +- type: entity # For clothes that need suit sensors. + parent: ClothingHeadHeadHatBaseFlippable + id: ClothingUniformBaseUnsensoredFlipped + suffix: flipped + abstract: true + components: + - type: Foldable + folded: true + - type: Clothing + equippedPrefix: flipped + - type: Item + heldPrefix: flipped + - type: Sprite + layers: + - state: icon + map: [ "unfoldedLayer" ] + visible: false + - state: icon_flipped + map: ["foldedLayer"] + visible: true + +- type: entity # For clothes that don't need suit sensors. + parent: ClothingUniformBaseUnsensoredFlippable + id: ClothingUniformBaseFlippable + abstract: true + components: + - type: SuitSensor + - type: DeviceNetwork + deviceNetId: Wireless + transmitFrequencyId: SuitSensor + - type: WirelessNetworkConnection + range: 1200 + - type: StationLimitedNetwork + +- type: entity # For clothes that don't need suit sensors. + parent: ClothingUniformBaseFlippable + id: ClothingUniformBaseFlipped + suffix: flipped + abstract: true + components: # I *think* I can just disregard the components; + # and inherit from ClothingUniformBaseFlippable + # and ClothingUniformBaseUnsensoredFlipped but I don't want to + # test that right now. + - type: Foldable + folded: true + - type: Clothing + equippedPrefix: flipped + - type: Item + heldPrefix: flipped + - type: Sprite + layers: + - state: icon + map: [ "unfoldedLayer" ] + visible: false + - state: icon_flipped + map: ["foldedLayer"] + visible: true diff --git a/Resources/Prototypes/Entities/Clothing/Uniforms/color_jumpskirts.yml b/Resources/Prototypes/Entities/Clothing/Uniforms/color_jumpskirts.yml index 1f77059841..b5432eee07 100644 --- a/Resources/Prototypes/Entities/Clothing/Uniforms/color_jumpskirts.yml +++ b/Resources/Prototypes/Entities/Clothing/Uniforms/color_jumpskirts.yml @@ -1,6 +1,7 @@ +# All of the jumpskirts here have had their parent changed to ClothingUniformSkirtBase, as to allow harpies to wear them. # White Jumpskirt - type: entity - parent: ClothingUniformBase + parent: ClothingUniformSkirtBase id: ClothingUniformJumpskirtColorWhite name: white jumpskirt description: A generic white jumpskirt with no rank markings. @@ -27,7 +28,7 @@ # Grey Jumpskirt - type: entity - parent: ClothingUniformBase + parent: ClothingUniformSkirtBase id: ClothingUniformJumpskirtColorGrey name: grey jumpskirt description: A tasteful grey jumpskirt that reminds you of the good old days. @@ -58,7 +59,7 @@ # Black Jumpskirt - type: entity - parent: ClothingUniformBase + parent: ClothingUniformSkirtBase id: ClothingUniformJumpskirtColorBlack name: black jumpskirt description: A generic black jumpskirt with no rank markings. @@ -89,7 +90,7 @@ # Blue Jumpskirt - type: entity - parent: ClothingUniformBase + parent: ClothingUniformSkirtBase id: ClothingUniformJumpskirtColorBlue name: blue jumpskirt description: A generic blue jumpskirt with no rank markings. @@ -120,7 +121,7 @@ # Dark Blue Jumpskirt - type: entity - parent: ClothingUniformBase + parent: ClothingUniformSkirtBase id: ClothingUniformJumpskirtColorDarkBlue name: dark blue jumpskirt description: A generic dark blue jumpskirt with no rank markings. @@ -151,7 +152,7 @@ # Teal Jumpskirt - type: entity - parent: ClothingUniformBase + parent: ClothingUniformSkirtBase id: ClothingUniformJumpskirtColorTeal name: teal jumpskirt description: A generic teal jumpskirt with no rank markings. @@ -182,7 +183,7 @@ # Green Jumpskirt - type: entity - parent: ClothingUniformBase + parent: ClothingUniformSkirtBase id: ClothingUniformJumpskirtColorGreen name: green jumpskirt description: A generic green jumpskirt with no rank markings. @@ -213,7 +214,7 @@ # Dark Green Jumpskirt - type: entity - parent: ClothingUniformBase + parent: ClothingUniformSkirtBase id: ClothingUniformJumpskirtColorDarkGreen name: dark green jumpskirt description: A generic dark green jumpskirt with no rank markings. @@ -244,7 +245,7 @@ # Orange Jumpskirt - type: entity - parent: ClothingUniformBase + parent: ClothingUniformSkirtBase id: ClothingUniformJumpskirtColorOrange name: orange jumpskirt description: Don't wear this near paranoid security officers. @@ -275,7 +276,7 @@ # Pink Jumpskirt - type: entity - parent: ClothingUniformBase + parent: ClothingUniformSkirtBase id: ClothingUniformJumpskirtColorPink name: pink jumpskirt description: Just looking at this makes you feel fabulous. @@ -306,7 +307,7 @@ # Red Jumpskirt - type: entity - parent: ClothingUniformBase + parent: ClothingUniformSkirtBase id: ClothingUniformJumpskirtColorRed name: red jumpskirt description: A generic red jumpskirt with no rank markings. @@ -337,7 +338,7 @@ # Yellow Jumpskirt - type: entity - parent: ClothingUniformBase + parent: ClothingUniformSkirtBase id: ClothingUniformJumpskirtColorYellow name: yellow jumpskirt description: A generic yellow jumpskirt with no rank markings. @@ -368,7 +369,7 @@ # Purple Jumpskirt - type: entity - parent: ClothingUniformBase + parent: ClothingUniformSkirtBase id: ClothingUniformJumpskirtColorPurple name: purple jumpskirt description: A generic light purple jumpskirt with no rank markings. @@ -399,7 +400,7 @@ # Light Brown Jumpskirt - type: entity - parent: ClothingUniformBase + parent: ClothingUniformSkirtBase id: ClothingUniformJumpskirtColorLightBrown name: light brown jumpskirt description: A generic light brown jumpskirt with no rank markings. @@ -430,7 +431,7 @@ # Brown Jumpskirt - type: entity - parent: ClothingUniformBase + parent: ClothingUniformSkirtBase id: ClothingUniformJumpskirtColorBrown name: brown jumpskirt description: A generic brown jumpskirt with no rank markings. @@ -461,7 +462,7 @@ # Maroon Jumpskirt - type: entity - parent: ClothingUniformBase + parent: ClothingUniformSkirtBase id: ClothingUniformJumpskirtColorMaroon name: maroon jumpskirt description: A generic maroon jumpskirt with no rank markings. diff --git a/Resources/Prototypes/Entities/Clothing/Uniforms/jumpskirts.yml b/Resources/Prototypes/Entities/Clothing/Uniforms/jumpskirts.yml index 7cbf5c940a..18266dc498 100644 --- a/Resources/Prototypes/Entities/Clothing/Uniforms/jumpskirts.yml +++ b/Resources/Prototypes/Entities/Clothing/Uniforms/jumpskirts.yml @@ -9,6 +9,19 @@ - type: Clothing sprite: Clothing/Uniforms/Jumpskirt/bartender.rsi +# This has been placed here, as it visually resembles a skirt. +# Likewise, the sprite file location has been moved to jumpskirts and the parent has been changed to ClothingUniformSkirtBase. +- type: entity + parent: ClothingUniformSkirtBase + id: ClothingUniformJumpsuitBartenderPurple + name: purple bartender's uniform + description: A special purple outfit to serve drinks. + components: + - type: Sprite + sprite: Clothing/Uniforms/Jumpskirt/bartender_purple.rsi + - type: Clothing + sprite: Clothing/Uniforms/Jumpskirt/bartender_purple.rsi + - type: entity parent: ClothingUniformSkirtBase id: ClothingUniformJumpskirtCaptain @@ -35,7 +48,7 @@ parent: ClothingUniformSkirtBase id: ClothingUniformJumpskirtChiefEngineer name: chief engineer's jumpskirt - description: It's a high visibility jumpskirt given to those engineers insane enough to achieve the rank of Chief Engineer. It has minor radiation shielding. + description: It's a high visibility jumpskirt given to those engineers insane enough to achieve the rank of Chief Engineer. components: - type: Sprite sprite: Clothing/Uniforms/Jumpskirt/ce.rsi @@ -167,7 +180,7 @@ parent: ClothingUniformSkirtBase id: ClothingUniformJumpskirtHoS name: head of security's jumpskirt - description: A standard Station Security uniform, adorned with gold emblems and commander's epaulettes. # DeltaV - improved description + description: A standard Station Security uniform, adorned with gold emblems and commander's epaulettes. components: - type: Sprite sprite: DeltaV/Clothing/Uniforms/Jumpskirt/hos.rsi # DeltaV - resprite @@ -178,7 +191,7 @@ parent: ClothingUniformSkirtBase id: ClothingUniformJumpskirtHoSAlt name: head of security's turtleneck - description: It's a turtleneck worn by those strong and disciplined enough to achieve the position of Head of Security. Its sturdy fabric provides minor protection from slash and pierce damage. + description: It's a turtleneck worn by those strong and disciplined enough to achieve the position of Head of Security. components: - type: Sprite sprite: Clothing/Uniforms/Jumpskirt/hos_alt.rsi @@ -218,6 +231,19 @@ - type: Clothing sprite: Clothing/Uniforms/Jumpskirt/janitor.rsi +# This has been moved here, as it visually resembles a skirt. +# Likewise, the sprite file location has been moved to jumpskirts and the parent has been changed to ClothingUniformSkirtBase. +- type: entity + parent: ClothingUniformSkirtBase + id: ClothingUniformJumpsuitKimono + name: kimono + description: traditional chinese clothing + components: + - type: Sprite + sprite: Clothing/Uniforms/Jumpskirt/kimono.rsi + - type: Clothing + sprite: Clothing/Uniforms/Jumpskirt/kimono.rsi + - type: entity parent: ClothingUniformSkirtBase id: ClothingUniformJumpskirtMedicalDoctor @@ -339,7 +365,7 @@ parent: ClothingUniformSkirtBase id: ClothingUniformJumpskirtScientist name: scientist jumpskirt - description: It's made of a special fiber that provides minor protection against explosives. It has markings that denote the wearer as a scientist. + description: It's made of a special fiber that increases perceived intelligence and decreases personal ethics. It has markings that denote the wearer as a scientist. components: - type: Sprite sprite: Clothing/Uniforms/Jumpskirt/scientist.rsi @@ -592,6 +618,30 @@ - type: Clothing sprite: Clothing/Uniforms/Jumpskirt/skirtoflife.rsi +# Both the light and dark monastic robes have been moved here. +# They have also had their parent changed to ClothingUniformSkirtBase, and their sprite has likewise been moved to the jumpskirts directory. +- type: entity + parent: ClothingUniformSkirtBase + id: ClothingUniformJumpsuitMonasticRobeDark + name: dark monastic robe + description: It's a dark robe, often worn by religious folk. + components: + - type: Sprite + sprite: Clothing/Uniforms/Jumpskirt/monastic_robe_dark.rsi + - type: Clothing + sprite: Clothing/Uniforms/Jumpskirt/monastic_robe_dark.rsi + +- type: entity + parent: ClothingUniformSkirtBase + id: ClothingUniformJumpsuitMonasticRobeLight + name: light monastic robe + description: It's a light robe, often worn by religious folk. + components: + - type: Sprite + sprite: Clothing/Uniforms/Jumpskirt/monastic_robe_light.rsi + - type: Clothing + sprite: Clothing/Uniforms/Jumpskirt/monastic_robe_light.rsi + - type: entity parent: ClothingUniformSkirtBase id: ClothingUniformJumpskirtSeniorEngineer @@ -678,6 +728,17 @@ graph: WebObjects node: jumpskirt +- type: entity + parent: ClothingUniformSkirtBase + id: ClothingUniformJumpsuitGladiator + name: gladiator uniform + description: Made for true gladiators (or Ash Walkers). + components: + - type: Sprite + sprite: Clothing/Uniforms/Jumpskirt/gladiator.rsi + - type: Clothing + sprite: Clothing/Uniforms/Jumpskirt/gladiator.rsi + - type: entity parent: ClothingUniformSkirtBase id: ClothingUniformJumpskirtCasualBlue @@ -767,3 +828,14 @@ - state: equipped-INNERCLOTHING-jumpskirt - state: equipped-INNERCLOTHING-shirt color: "#b30000" + +- type: entity + parent: ClothingUniformSkirtBase + id: ClothingUniformJumpskirtOldDress + name: old dress + description: A worn-looking dress from a very long time ago. + components: + - type: Sprite + sprite: Clothing/Uniforms/Jumpskirt/olddress.rsi + - type: Clothing + sprite: Clothing/Uniforms/Jumpskirt/olddress.rsi diff --git a/Resources/Prototypes/Entities/Clothing/Uniforms/jumpsuits.yml b/Resources/Prototypes/Entities/Clothing/Uniforms/jumpsuits.yml index 6c853374f2..d2339764f8 100644 --- a/Resources/Prototypes/Entities/Clothing/Uniforms/jumpsuits.yml +++ b/Resources/Prototypes/Entities/Clothing/Uniforms/jumpsuits.yml @@ -31,6 +31,54 @@ - type: Clothing sprite: Clothing/Uniforms/Jumpsuit/bartender.rsi +- type: entity + parent: ClothingUniformBaseFlippable + id: ClothingUniformJumpsuitBartenderNt + name: bartender's nanotrasen uniform + description: A nice and tidy uniform. Shame about the bar though. + components: + - type: Sprite + sprite: Clothing/Uniforms/Jumpsuit/bartender_nt.rsi + - type: Clothing + sprite: Clothing/Uniforms/Jumpsuit/bartender_nt.rsi + +- type: entity + parent: [ ClothingUniformBaseFlipped, ClothingUniformJumpsuitBartenderNt ] + id: ClothingUniformJumpsuitBartenderNtFlipped + name: bartender's nanotrasen uniform + +- type: entity + parent: ClothingUniformBaseFlippable + id: ClothingUniformJumpsuitBartenderIdris + name: bartender's idris incorporated uniform + description: A nice and tidy uniform. Shame about the bar though. + components: + - type: Sprite + sprite: Clothing/Uniforms/Jumpsuit/bartender_idris.rsi + - type: Clothing + sprite: Clothing/Uniforms/Jumpsuit/bartender_idris.rsi + +- type: entity + parent: [ ClothingUniformBaseFlipped, ClothingUniformJumpsuitBartenderIdris ] + id: ClothingUniformJumpsuitBartenderIdrisFlipped + name: bartender's idris incorporated uniform + +- type: entity + parent: ClothingUniformBaseFlippable + id: ClothingUniformJumpsuitBartenderOrion + name: bartender's orion express uniform + description: A nice and tidy uniform. Shame about the bar though. + components: + - type: Sprite + sprite: Clothing/Uniforms/Jumpsuit/bartender_orion.rsi + - type: Clothing + sprite: Clothing/Uniforms/Jumpsuit/bartender_orion.rsi + +- type: entity + parent: [ ClothingUniformBaseFlipped, ClothingUniformJumpsuitBartenderOrion ] + id: ClothingUniformJumpsuitBartenderOrionFlipped + name: bartender's orion express uniform + - type: entity parent: ClothingUniformBase id: ClothingUniformJumpsuitJacketMonkey @@ -46,17 +94,6 @@ randomMode: false mode: SensorCords -- type: entity - parent: ClothingUniformBase - id: ClothingUniformJumpsuitBartenderPurple - name: purple bartender's uniform - description: A special purple outfit to serve drinks. - components: - - type: Sprite - sprite: Clothing/Uniforms/Jumpsuit/bartender_purple.rsi - - type: Clothing - sprite: Clothing/Uniforms/Jumpsuit/bartender_purple.rsi - - type: entity parent: ClothingUniformBase id: ClothingUniformJumpsuitCaptain @@ -94,7 +131,7 @@ parent: ClothingUniformBase id: ClothingUniformJumpsuitChiefEngineer name: chief engineer's jumpsuit - description: It's a high visibility jumpsuit given to those engineers insane enough to achieve the rank of Chief Engineer. It has minor radiation shielding. + description: It's a high visibility jumpsuit given to those engineers insane enough to achieve the rank of Chief Engineer. components: - type: Sprite sprite: Clothing/Uniforms/Jumpsuit/ce.rsi @@ -167,6 +204,55 @@ - type: Clothing sprite: Clothing/Uniforms/Jumpsuit/chef.rsi +- type: entity + parent: ClothingUniformBaseFlippable + id: ClothingUniformJumpsuitChefNt + name: nanotrasen chef uniform + description: Can't cook without this. + components: + - type: Sprite + sprite: Clothing/Uniforms/Jumpsuit/chef_nt.rsi + - type: Clothing + sprite: Clothing/Uniforms/Jumpsuit/chef_nt.rsi + +- type: entity + parent: [ ClothingUniformBaseFlipped, ClothingUniformJumpsuitChefNt ] + id: ClothingUniformChefNtFlipped + name: nanotrasen chef uniform + +- type: entity + parent: ClothingUniformBase + id: ClothingUniformJumpsuitChefIdris + name: idris incorporated chef uniform + description: Can't cook without this. + components: + - type: Sprite + sprite: Clothing/Uniforms/Jumpsuit/chef_idris.rsi + - type: Clothing + sprite: Clothing/Uniforms/Jumpsuit/chef_idris.rsi + +- type: entity + parent: [ ClothingUniformBaseFlipped, ClothingUniformJumpsuitChefIdris ] + id: ClothingUniformChefIdrisFlipped + name: nidris incorporated notrasen chef uniform + +- type: entity + parent: ClothingUniformBase + id: ClothingUniformJumpsuitChefOrion + name: orion express chef uniform + description: Can't cook without this. + components: + - type: Sprite + sprite: Clothing/Uniforms/Jumpsuit/chef_orion.rsi + - type: Clothing + sprite: Clothing/Uniforms/Jumpsuit/chef_orion.rsi + +- type: entity + parent: [ ClothingUniformBaseFlipped, ClothingUniformJumpsuitChefOrion ] + id: ClothingUniformChefOrionFlipped + name: orion express chef uniform + + - type: entity parent: ClothingUniformBase id: ClothingUniformJumpsuitChemistry @@ -385,6 +471,54 @@ - type: Clothing sprite: Clothing/Uniforms/Jumpsuit/hydro.rsi +- type: entity + parent: ClothingUniformBaseFlippable + id: ClothingUniformJumpsuitHydroponicsNt + name: nanotrasen hydroponics jumpsuit + description: Has a strong earthy smell to it. Hopefully it's merely dirty as opposed to soiled. + components: + - type: Sprite + sprite: Clothing/Uniforms/Jumpsuit/hydro_nt.rsi + - type: Clothing + sprite: Clothing/Uniforms/Jumpsuit/hydro_nt.rsi + +- type: entity + parent: [ ClothingUniformBaseFlipped, ClothingUniformJumpsuitHydroponicsNt ] + id: ClothingUniformJumpsuitHydroponicsNtFlipped + name: nanotrasen hydroponics jumpsuit + +- type: entity + parent: ClothingUniformBaseFlippable + id: ClothingUniformJumpsuitHydroponicsIdris + name: idris incorporated hydroponics jumpsuit + description: Has a strong earthy smell to it. Hopefully it's merely dirty as opposed to soiled. + components: + - type: Sprite + sprite: Clothing/Uniforms/Jumpsuit/hydro_idris.rsi + - type: Clothing + sprite: Clothing/Uniforms/Jumpsuit/hydro_idris.rsi + +- type: entity + parent: [ ClothingUniformBaseFlipped, ClothingUniformJumpsuitHydroponicsIdris ] + id: ClothingUniformJumpsuitHydroponicsIdrisFlipped + name: idris incorporated hydroponics jumpsuit + +- type: entity + parent: ClothingUniformBaseFlippable + id: ClothingUniformJumpsuitHydroponicsOrion + name: orion express hydroponics jumpsuit + description: Has a strong earthy smell to it. Hopefully it's merely dirty as opposed to soiled. + components: + - type: Sprite + sprite: Clothing/Uniforms/Jumpsuit/hydro_orion.rsi + - type: Clothing + sprite: Clothing/Uniforms/Jumpsuit/hydro_orion.rsi + +- type: entity + parent: [ ClothingUniformBaseFlipped, ClothingUniformJumpsuitHydroponicsOrion ] + id: ClothingUniformJumpsuitHydroponicsOrionFlipped + name: orion express hydroponics jumpsuit + - type: entity parent: ClothingUniformBase id: ClothingUniformJumpsuitJanitor @@ -397,15 +531,52 @@ sprite: Clothing/Uniforms/Jumpsuit/janitor.rsi - type: entity - parent: ClothingUniformBase - id: ClothingUniformJumpsuitKimono - name: kimono - description: traditional chinese clothing + parent: ClothingUniformBaseFlippable + id: ClothingUniformJumpsuitJanitorNt + name: nanotrasen janitor jumpsuit + description: The jumpsuit for the poor sop with a mop. components: - type: Sprite - sprite: Clothing/Uniforms/Jumpsuit/kimono.rsi + sprite: Clothing/Uniforms/Jumpsuit/janitor_nt.rsi - type: Clothing - sprite: Clothing/Uniforms/Jumpsuit/kimono.rsi + sprite: Clothing/Uniforms/Jumpsuit/janitor_nt.rsi + +- type: entity + parent: [ ClothingUniformBaseFlipped, ClothingUniformJumpsuitJanitorNt ] + id: ClothingUniformJumpsuitJanitorNtFlipped + name: nanotrasen janitor jumpsuit + +- type: entity + parent: ClothingUniformBaseFlippable + id: ClothingUniformJumpsuitJanitorIdris + name: idris incorporated janitor jumpsuit + description: The jumpsuit for the poor sop with a mop. + components: + - type: Sprite + sprite: Clothing/Uniforms/Jumpsuit/janitor_idris.rsi + - type: Clothing + sprite: Clothing/Uniforms/Jumpsuit/janitor_idris.rsi + +- type: entity + parent: [ ClothingUniformBaseFlipped, ClothingUniformJumpsuitJanitorIdris ] + id: ClothingUniformJumpsuitJanitorIdrisFlipped + name: idris incorporated janitor jumpsuit + +- type: entity + parent: ClothingUniformBaseFlippable + id: ClothingUniformJumpsuitJanitorOrion + name: orion express janitor jumpsuit + description: The jumpsuit for the poor sop with a mop. + components: + - type: Sprite + sprite: Clothing/Uniforms/Jumpsuit/janitor_orion.rsi + - type: Clothing + sprite: Clothing/Uniforms/Jumpsuit/janitor_orion.rsi + +- type: entity + parent: [ ClothingUniformBaseFlipped, ClothingUniformJumpsuitJanitorOrion ] + id: ClothingUniformJumpsuitJanitorOrionFlipped + name: orion express janitor jumpsuit - type: entity parent: ClothingUniformBase @@ -540,7 +711,7 @@ parent: ClothingUniformBase id: ClothingUniformJumpsuitScientist name: scientist jumpsuit - description: It's made of a special fiber that provides minor protection against explosives. It has markings that denote the wearer as a scientist. + description: It's made of a special fiber that increases perceived intelligence and decreases personal ethics. It has markings that denote the wearer as a scientist. components: - type: Sprite sprite: Clothing/Uniforms/Jumpsuit/scientist.rsi @@ -635,6 +806,118 @@ - type: Clothing sprite: Clothing/Uniforms/Jumpsuit/librarian.rsi +- type: entity + parent: ClothingUniformBaseFlippable + id: ClothingUniformJumpsuitLibrarianNt + name: nanotrasen librarian jumpsuit + description: A cosy blue jumper fit for a curator of books. + components: + - type: Sprite + sprite: Clothing/Uniforms/Jumpsuit/librarian_nt.rsi + - type: Clothing + sprite: Clothing/Uniforms/Jumpsuit/librarian_nt.rsi + +- type: entity + parent: [ ClothingUniformBaseFlipped, ClothingUniformJumpsuitLibrarianNt ] + id: ClothingUniformJumpsuitLibrarianFlipped + name: nanotrasen librarian jumpsuit + +- type: entity + parent: ClothingUniformBaseFlippable + id: ClothingUniformJumpsuitLibrarianIdris + name: idris incroporated librarian jumpsuit + description: A cosy teal jumper fit for a curator of books. + components: + - type: Sprite + sprite: Clothing/Uniforms/Jumpsuit/librarian_idris.rsi + - type: Clothing + sprite: Clothing/Uniforms/Jumpsuit/librarian_idris.rsi + +- type: entity + parent: [ ClothingUniformBaseFlipped, ClothingUniformJumpsuitLibrarianIdris ] + id: ClothingUniformJumpsuitLibrarianIdrisFlipped + name: idris incroporated librarian jumpsuit + +- type: entity + parent: ClothingUniformBaseFlippable + id: ClothingUniformJumpsuitLibrarianOrion + name: orion express librarian jumpsuit + description: A cosy brown jumper fit for a curator of books. + components: + - type: Sprite + sprite: Clothing/Uniforms/Jumpsuit/librarian_orion.rsi + - type: Clothing + sprite: Clothing/Uniforms/Jumpsuit/librarian_orion.rsi + +- type: entity + parent: [ ClothingUniformBaseFlipped, ClothingUniformJumpsuitLibrarianOrion ] + id: ClothingUniformJumpsuitLibrarianOrionFlipped + name: orion express librarian jumpsuit + +- type: entity + parent: ClothingUniformBaseFlippable + id: ClothingUniformJumpsuitLibrarianHeph + name: hephaestus industries librarian jumpsuit + description: A cosy green jumper fit for a curator of books. + components: + - type: Sprite + sprite: Clothing/Uniforms/Jumpsuit/librarian_heph.rsi + - type: Clothing + sprite: Clothing/Uniforms/Jumpsuit/librarian_heph.rsi + +- type: entity + parent: [ ClothingUniformBaseFlipped, ClothingUniformJumpsuitLibrarianHeph ] + id: ClothingUniformJumpsuitLibrarianHephFlipped + name: hephaestus industries librarian jumpsuit + +- type: entity + parent: ClothingUniformBaseFlippable + id: ClothingUniformJumpsuitLibrarianPMCG + name: private military contracting group librarian jumpsuit + description: A cosy white jumper fit for a curator of books. + components: + - type: Sprite + sprite: Clothing/Uniforms/Jumpsuit/librarian_pmcg.rsi + - type: Clothing + sprite: Clothing/Uniforms/Jumpsuit/librarian_pmcg.rsi + +- type: entity + parent: [ ClothingUniformBaseFlipped, ClothingUniformJumpsuitLibrarianPMCG ] + id: ClothingUniformJumpsuitLibrarianPMCGFlipped + name: private military contracting group librarian jumpsuit + +- type: entity + parent: ClothingUniformBaseFlippable + id: ClothingUniformJumpsuitLibrarianZav + name: zavodskoi interstellar librarian jumpsuit + description: A blood brown jumper fit for a curator of books. + components: + - type: Sprite + sprite: Clothing/Uniforms/Jumpsuit/librarian_zav.rsi + - type: Clothing + sprite: Clothing/Uniforms/Jumpsuit/librarian_zav.rsi + +- type: entity + parent: [ ClothingUniformBaseFlipped, ClothingUniformJumpsuitLibrarianZav ] + id: ClothingUniformJumpsuitLibrarianZavFlipped + name: zavodskoi interstellar librarian jumpsuit + +- type: entity + parent: ClothingUniformBaseFlippable + id: ClothingUniformJumpsuitLibrarianZeng + name: zeng-hu pharmaceuticals librarian jumpsuit + description: A blood brown jumper fit for a curator of books. + components: + - type: Sprite + sprite: Clothing/Uniforms/Jumpsuit/librarian_zeng.rsi + - type: Clothing + sprite: Clothing/Uniforms/Jumpsuit/librarian_zeng.rsi + +- type: entity + parent: [ ClothingUniformBaseFlipped, ClothingUniformJumpsuitLibrarianZeng ] + id: ClothingUniformJumpsuitLibrarianZengFlipped + name: zeng-hu pharmaceuticals librarian jumpsuit + - type: entity parent: ClothingUniformBase id: ClothingUniformJumpsuitCurator @@ -892,28 +1175,6 @@ - type: Clothing sprite: Clothing/Uniforms/Jumpsuit/journalist.rsi -- type: entity - parent: ClothingUniformBase - id: ClothingUniformJumpsuitMonasticRobeDark - name: dark monastic robe - description: It's a dark robe, often worn by religious folk. - components: - - type: Sprite - sprite: Clothing/Uniforms/Jumpsuit/monastic_robe_dark.rsi - - type: Clothing - sprite: Clothing/Uniforms/Jumpsuit/monastic_robe_dark.rsi - -- type: entity - parent: ClothingUniformBase - id: ClothingUniformJumpsuitMonasticRobeLight - name: light monastic robe - description: It's a light robe, often worn by religious folk. - components: - - type: Sprite - sprite: Clothing/Uniforms/Jumpsuit/monastic_robe_light.rsi - - type: Clothing - sprite: Clothing/Uniforms/Jumpsuit/monastic_robe_light.rsi - - type: entity parent: ClothingUniformBase id: ClothingUniformJumpsuitMusician @@ -1065,7 +1326,8 @@ parent: ClothingUniformBase id: ClothingUniformJumpsuitHawaiRed name: red hawaiian shirt - description: Red as a juicy watermelons. +# Fixed typo, "Red as a juicy watermelons." + description: Red as a juicy watermelon. components: - type: Sprite sprite: Clothing/Uniforms/Jumpsuit/hawaired.rsi @@ -1098,7 +1360,8 @@ parent: ClothingUniformBase id: ClothingUniformJumpsuitFlannel name: flannel jumpsuit - description: Smells like someones been grillin'. +# Fixed a typo, there should be an apostrophe before the s in 'someone's." + description: Smells like someone's been grillin'. components: - type: Sprite sprite: Clothing/Uniforms/Jumpsuit/flannel.rsi @@ -1191,17 +1454,6 @@ - type: Clothing sprite: Clothing/Uniforms/Jumpsuit/loungewear.rsi -- type: entity - parent: ClothingUniformBase - id: ClothingUniformJumpsuitGladiator - name: gladiator uniform - description: Made for true gladiators (or Ash Walkers). - components: - - type: Sprite - sprite: Clothing/Uniforms/Jumpsuit/gladiator.rsi - - type: Clothing - sprite: Clothing/Uniforms/Jumpsuit/gladiator.rsi - - type: entity parent: ClothingUniformBase id: ClothingUniformJumpsuitCasualBlue diff --git a/Resources/Prototypes/Entities/Clothing/Uniforms/specific.yml b/Resources/Prototypes/Entities/Clothing/Uniforms/specific.yml index ad02f4aa67..a2c903bac3 100644 --- a/Resources/Prototypes/Entities/Clothing/Uniforms/specific.yml +++ b/Resources/Prototypes/Entities/Clothing/Uniforms/specific.yml @@ -38,5 +38,5 @@ default: ClothingUniformJumpsuitColorBlack - type: UserInterface interfaces: - - key: enum.ChameleonUiKey.Key - type: ChameleonBoundUserInterface \ No newline at end of file + enum.ChameleonUiKey.Key: + type: ChameleonBoundUserInterface diff --git a/Resources/Prototypes/Entities/Clothing/base_clothing.yml b/Resources/Prototypes/Entities/Clothing/base_clothing.yml index 16532c03e6..bc592616be 100644 --- a/Resources/Prototypes/Entities/Clothing/base_clothing.yml +++ b/Resources/Prototypes/Entities/Clothing/base_clothing.yml @@ -22,6 +22,12 @@ - type: Geiger attachedToSuit: true +- type: entity + abstract: true + id: AllowSuitStorageClothing + components: + - type: AllowSuitStorage + # for clothing that has a single item slot to insert and alt click out. # inheritors add a whitelisted slot named item - type: entity diff --git a/Resources/Prototypes/Entities/Debugging/debug_sweps.yml b/Resources/Prototypes/Entities/Debugging/debug_sweps.yml index e3289569a2..0430dbf427 100644 --- a/Resources/Prototypes/Entities/Debugging/debug_sweps.yml +++ b/Resources/Prototypes/Entities/Debugging/debug_sweps.yml @@ -129,3 +129,51 @@ damage: types: Blunt: 200 + +- type: entity + name: bang severer + parent: BaseItem + id: MeleeDebugSever + description: sever yer parts a week from now + suffix: DEBUG + components: + - type: Tag + tags: + - Debug + - type: Sprite + sprite: Objects/Weapons/Melee/debug.rsi + state: icon + - type: MeleeWeapon + damage: + types: + Slash: 20000 + clickPartDamageMultiplier: 10 + - type: Item + size: Tiny + sprite: Objects/Weapons/Melee/debug.rsi + +- type: entity + name: bang severer 100dmg + parent: MeleeDebugSever + id: MeleeDebugSever100 + components: + - type: Tag + tags: + - Debug + - type: MeleeWeapon + damage: + types: + Slash: 100 + +- type: entity + name: bang severer 200dmg + parent: MeleeDebugSever + id: MeleeDebugSever200 + components: + - type: Tag + tags: + - Debug + - type: MeleeWeapon + damage: + types: + Slash: 200 diff --git a/Resources/Prototypes/Entities/Debugging/tippy.yml b/Resources/Prototypes/Entities/Debugging/tippy.yml new file mode 100644 index 0000000000..292cbed0f1 --- /dev/null +++ b/Resources/Prototypes/Entities/Debugging/tippy.yml @@ -0,0 +1,30 @@ +- type: entity + id: Tippy + noSpawn: true + components: + - type: Sprite + netsync: false + noRot: false + scale: 4,4 + layers: + - sprite: Tips/tippy.rsi + state: left + map: [ "revealing" ] + - sprite: Tips/tippy.rsi + state: right + map: [ "hiding" ] + - sprite: Tips/tippy.rsi + state: down + visible: false + map: [ "speaking" ] + # footstep sounds wile waddling onto the screen. + - type: FootstepModifier + footstepSoundCollection: + collection: FootstepClown + # visuals for the speech bubble. + # only supports background image. + - type: PaperVisuals + backgroundImagePath: "/Textures/Interface/Paper/paper_background_default.svg.96dpi.png" + backgroundPatchMargin: 16.0, 16.0, 16.0, 16.0 + backgroundModulate: "#ffffcc" + fontAccentColor: "#000000" diff --git a/Resources/Prototypes/Entities/Effects/bluespace_flash.yml b/Resources/Prototypes/Entities/Effects/bluespace_flash.yml index 3421f0d9a6..b05681def2 100644 --- a/Resources/Prototypes/Entities/Effects/bluespace_flash.yml +++ b/Resources/Prototypes/Entities/Effects/bluespace_flash.yml @@ -9,5 +9,98 @@ - type: TimedDespawn lifetime: 1 - type: EmitSoundOnSpawn - sound: - path: /Audio/Effects/Lightning/lightningbolt.ogg \ No newline at end of file + sound: + path: /Audio/Effects/Lightning/lightningbolt.ogg + +- type: entity + id: EffectFlashTelekineticPulse + noSpawn: true + components: + - type: PointLight + radius: 10 + energy: 3.5 + color: "#18abf5" + - type: TimedDespawn + lifetime: 1 + - type: EmitSoundOnSpawn + sound: + path: /Audio/Effects/Lightning/lightningbolt.ogg + +- type: entity + id: EffectFlashShadeskip + noSpawn: true + components: + - type: PointLight + radius: 5 + energy: 3.5 + color: "#7100bd" + - type: TimedDespawn + lifetime: 1 + - type: EmitSoundOnSpawn + sound: + path: /Audio/Effects/Lightning/lightningbolt.ogg + +- type: entity + id: EffectFlashShadowkinShadeskip + noSpawn: true + components: + - type: PointLight + radius: 5 + energy: 3.5 + color: "#7100bd" + - type: TimedDespawn + lifetime: 3 + - type: EmitSoundOnSpawn + sound: + path: /Audio/Effects/Shadowkin/shadeskip.ogg + +- type: entity + id: EffectFlashShadowkinDarkSwapOn + noSpawn: true + components: + - type: PointLight + radius: 5 + energy: 3.5 + color: "#7100bd" + - type: TimedDespawn + lifetime: 3 + - type: EmitSoundOnSpawn + sound: + path: /Audio/Effects/Shadowkin/darkswapon.ogg + +- type: entity + id: EffectFlashShadowkinDarkSwapOff + noSpawn: true + components: + - type: PointLight + radius: 5 + energy: 3.5 + color: "#7100bd" + - type: TimedDespawn + lifetime: 3 + - type: EmitSoundOnSpawn + sound: + path: /Audio/Effects/Shadowkin/darkswapoff.ogg + +- type: entity + id: EffectPyrokineticFlare + noSpawn: true + components: + - type: Sprite + sprite: /Textures/Interface/Actions/psionics.rsi + visible: false + state: pyrokinetic_flare + - type: TriggerOnSpawn + - type: PointLight + radius: 5 + energy: 8 + color: "#ff4500" + - type: LightFade + duration: 0.5 + - type: TimedDespawn + lifetime: 0.5 + - type: FlashOnTrigger + range: 7 + - type: EmitSoundOnSpawn + sound: + path: "/Audio/Effects/flash_bang.ogg" diff --git a/Resources/Prototypes/Entities/Effects/chemistry_effects.yml b/Resources/Prototypes/Entities/Effects/chemistry_effects.yml index 096e88bcb6..469bab3278 100644 --- a/Resources/Prototypes/Entities/Effects/chemistry_effects.yml +++ b/Resources/Prototypes/Entities/Effects/chemistry_effects.yml @@ -77,6 +77,9 @@ animationState: foam-dissolve - type: Slippery - type: StepTrigger + triggerGroups: + types: + - SlipTile # disabled until foam reagent duplication is fixed #- type: ScoopableSolution # solution: solutionArea @@ -135,7 +138,7 @@ - type: RCDDeconstructable cost: 2 delay: 2 - fx: EffectRCDDeconstruct2 + fx: EffectRCDDeconstruct2 - type: Clickable - type: InteractionOutline - type: Sprite diff --git a/Resources/Prototypes/Entities/Effects/puddle.yml b/Resources/Prototypes/Entities/Effects/puddle.yml index 2c845e1d0f..7f6125e73d 100644 --- a/Resources/Prototypes/Entities/Effects/puddle.yml +++ b/Resources/Prototypes/Entities/Effects/puddle.yml @@ -142,13 +142,19 @@ mode: CardinalFlags - type: SolutionContainerManager solutions: - puddle: { maxVol: 1000 } + puddle: + maxVol: 1000 - type: Puddle + - type: MixableSolution + solution: puddle - type: Appearance - type: ActiveEdgeSpreader - type: EdgeSpreader id: Puddle - type: StepTrigger + triggerGroups: + types: + - SlipTile - type: Drink delay: 3 transferAmount: 1 @@ -160,3 +166,42 @@ solution: puddle - type: BadDrink - type: IgnoresFingerprints + - type: PuddleFootPrints + +- type: entity + name: Footstep + id: Footstep + save: false + description: Trace of liquid + components: + - type: Clickable + - type: FootstepModifier + footstepSoundCollection: + collection: FootstepWater + params: + volume: 3 + - type: Transform + noRot: false + - type: Sprite + drawdepth: FloorObjects + color: "#FFFFFF80" + - type: Physics + bodyType: Static + - type: Fixtures + fixtures: + slipFixture: + shape: + !type:PhysShapeAabb + bounds: "-0.4,-0.4,0.4,0.4" + mask: + - ItemMask + layer: + - SlipLayer + hard: false + - type: SolutionContainerManager + solutions: + step: { maxVol: 2 } + - type: FootPrint + - type: Puddle + solution: step + - type: Appearance diff --git a/Resources/Prototypes/Entities/Markers/Spawners/Random/Food_Drinks/drinks_glass.yml b/Resources/Prototypes/Entities/Markers/Spawners/Random/Food_Drinks/drinks_glass.yml index ff9e512432..f8fc2998d7 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/Random/Food_Drinks/drinks_glass.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/Random/Food_Drinks/drinks_glass.yml @@ -32,6 +32,7 @@ - DrinkBloodyMaryGlass - DrinkBooger - DrinkBraveBullGlass + - BudgetInsulsDrinkGlass - DrinkCarrotJuice - DrinkCoconutRum - DrinkChocolateGlass @@ -57,6 +58,7 @@ - DrinkIcedGreenTeaGlass - DrinkIcedBeerGlass - DrinkIceCreamGlass + - IrishBoolGlass - DrinkIrishCarBomb - DrinkIrishCoffeeGlass - DrinkLemonadeGlass @@ -78,6 +80,7 @@ - DrinkRewriter - DrinkRoyRogersGlass - DrinkRootBeerFloatGlass + - RubberneckGlass - DrinkRumGlass - DrinkSakeGlass - DrinkSbitenGlass @@ -91,12 +94,15 @@ - DrinkThreeMileIslandGlass - DrinkToxinsSpecialGlass - DrinkVodkaMartiniGlass + - DrinkVodkaRedBool - DrinkVodkaTonicGlass - DrinkWatermelonJuice + - DrinkWatermelonWakeup - DrinkWhiskeyColaGlass - DrinkWhiskeySodaGlass - DrinkWhiteRussianGlass - DrinkWineGlass + - XenoBasherGlass - DrinkShakeBlue - DrinkShakeWhite - DrinkTheMartinez diff --git a/Resources/Prototypes/Entities/Markers/Spawners/Random/Food_Drinks/food_produce.yml b/Resources/Prototypes/Entities/Markers/Spawners/Random/Food_Drinks/food_produce.yml index 6edf341e10..a889b939bd 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/Random/Food_Drinks/food_produce.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/Random/Food_Drinks/food_produce.yml @@ -33,8 +33,8 @@ - FoodOnion - FoodOnionRed - FoodMushroom - - FoodChili - - FoodChilly + - FoodChiliPepper + - FoodChillyPepper - FoodAloe - FoodPoppy - FoodLingzhi diff --git a/Resources/Prototypes/Entities/Markers/Spawners/Random/crates.yml b/Resources/Prototypes/Entities/Markers/Spawners/Random/crates.yml index ae7e5bcf76..883182aae8 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/Random/crates.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/Random/crates.yml @@ -44,6 +44,7 @@ - CrateMaterialPlastic - CrateMaterialWood - CrateMaterialPlasteel + - CrateFunSprayPaints - CrateFunArtSupplies - CrateEngineeringCableLV - CrateEngineeringCableMV diff --git a/Resources/Prototypes/Entities/Markers/Spawners/Random/maintenance.yml b/Resources/Prototypes/Entities/Markers/Spawners/Random/maintenance.yml index 4c820998ea..3259439258 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/Random/maintenance.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/Random/maintenance.yml @@ -176,7 +176,12 @@ - MaterialCloth10 - MaterialWoodPlank10 - ResearchDisk + - DeathPaint - Plunger + - SprayPaintBlue + - SprayPaintRed + - SprayPaintGreen + - SprayPaintOrange - TechnologyDisk - PowerCellMedium - PowerCellSmall diff --git a/Resources/Prototypes/Entities/Markers/Spawners/Random/salvage.yml b/Resources/Prototypes/Entities/Markers/Spawners/Random/salvage.yml index 34bf32d8d7..4a611fe026 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/Random/salvage.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/Random/salvage.yml @@ -63,23 +63,78 @@ offset: 0.0 - type: entity - name: salvage loot spawner - id: SalvageLootSpawner + name: Salvage T2 Machine Parts Spawner + id: SalvagePartsT2Spawner parent: MarkerBase components: - type: Sprite layers: - state: red - - sprite: Objects/Weapons/Melee/crusher.rsi - state: icon + - sprite: Objects/Misc/stock_parts.rsi + state: advanced_matter_bin - type: RandomSpawner prototypes: - - WeaponCrusher - - WeaponCrusherDagger - - WeaponCrusherGlaive - - MiningDrill + - AdvancedCapacitorStockPart + - NanoManipulatorStockPart + - AdvancedMatterBinStockPart offset: 0.0 +- type: entity + parent: MarkerBase + id: SalvagePartsT3T4Spawner + name: tier 3/4 machine part + components: + - type: Sprite + layers: + - sprite: Objects/Misc/stock_parts.rsi + state: super_matter_bin + - type: RandomSpawner + rarePrototypes: + - BluespaceCapacitorStockPart + - BluespaceManipulatorStockPart + - BluespaceMatterBinStockPart + rareChance: 0.05 + prototypes: + - SuperCapacitorStockPart + - PicoManipulatorStockPart + - SuperMatterBinStockPart + chance: 0.95 + offset: 0.0 + +- type: entity + parent: MarkerBase + id: SalvagePartsT3Spawner + name: tier 3 machine part + suffix: Spawner + components: + - type: Sprite + layers: + - sprite: Objects/Misc/stock_parts.rsi + state: super_matter_bin + - type: RandomSpawner + prototypes: + - SuperCapacitorStockPart + - PicoManipulatorStockPart + - SuperMatterBinStockPart + offset: 0.0 + +- type: entity + parent: MarkerBase + id: SalvagePartsT4Spawner + name: tier 4 machine part + suffix: Spawner + components: + - type: Sprite + layers: + - sprite: Objects/Misc/stock_parts.rsi + state: bluespace_matter_bin + - type: RandomSpawner + prototypes: + - BluespaceCapacitorStockPart + - PicoManipulatorStockPart + - BluespaceMatterBinStockPart + offset: 0.0 + - type: entity name: Salvage Mob Spawner id: SalvageMobSpawner @@ -228,3 +283,28 @@ - MobFleshLoverSalvage chance: 1 offset: 0.2 + +- type: entity + name: Salvage Loot Spawner + id: SalvageLootSpawner + parent: MarkerBase + components: + - type: Sprite + layers: + - state: red + - sprite: Structures/Storage/Crates/generic.rsi + state: icon + - type: RandomSpawner + rarePrototypes: + - SalvagePartsT2Spawner + - SalvagePartsT3Spawner + - SalvagePartsT3T4Spawner + - SalvagePartsT4Spawner + rareChance: 0.4 + prototypes: + - CrateSalvageAssortedGoodies + - WeaponCrusher + - WeaponCrusherDagger + - WeaponCrusherGlaive + chance: 0.9 + offset: 0.2 diff --git a/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml b/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml index 48b0bfbeb4..4b7b96882c 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml @@ -8,6 +8,8 @@ name: ghost-role-information-rat-king-name description: ghost-role-information-rat-king-description rules: ghost-role-information-rat-king-rules + raffle: + settings: default - type: GhostRoleMobSpawner prototype: MobRatKing - type: Sprite @@ -27,6 +29,8 @@ name: ghost-role-information-remilia-name description: ghost-role-information-remilia-description rules: ghost-role-information-remilia-rules + raffle: + settings: short - type: GhostRoleMobSpawner prototype: MobBatRemilia - type: Sprite @@ -46,6 +50,8 @@ name: ghost-role-information-cerberus-name description: ghost-role-information-cerberus-description rules: ghost-role-information-cerberus-rules + raffle: + settings: default - type: GhostRoleMobSpawner prototype: MobCorgiCerberus - type: Sprite @@ -64,6 +70,8 @@ components: - type: GhostRole rules: ghost-role-information-nukeop-rules + raffle: + settings: default - type: GhostRoleMobSpawner prototype: MobHumanNukeOp - type: Sprite @@ -84,14 +92,15 @@ name: ghost-role-information-loneop-name description: ghost-role-information-loneop-description rules: ghost-role-information-loneop-rules + raffle: + settings: default requirements: - !type:CharacterOverallTimeRequirement min: 172800 # DeltaV - 48 hours - !type:CharacterDepartmentTimeRequirement # DeltaV - Security dept time requirement department: Security - min: 36000 # DeltaV - 10 hours - - type: GhostRoleMobSpawner - prototype: MobHumanLoneNuclearOperative + time: 36000 # DeltaV - 10 hours + - type: GhostRoleAntagSpawner - type: Sprite sprite: Markers/jobs.rsi layers: @@ -110,6 +119,8 @@ name: ghost-role-information-space-dragon-name description: ghost-role-information-space-dragon-description rules: ghost-role-component-default-rules + raffle: + settings: default - type: GhostRoleMobSpawner prototype: MobDragon - type: Sprite @@ -128,6 +139,8 @@ name: ghost-role-information-space-ninja-name description: ghost-role-information-space-ninja-description rules: ghost-role-information-space-ninja-rules + raffle: + settings: default - type: GhostRoleMobSpawner prototype: MobHumanSpaceNinja - type: Sprite @@ -136,20 +149,3 @@ - state: green - sprite: Objects/Weapons/Melee/energykatana.rsi state: icon - -- type: entity - parent: MarkerBase - id: SpawnPointGhostTerminator - name: terminator spawn point - components: - - type: GhostRole - name: ghost-role-information-exterminator-name - description: ghost-role-information-exterminator-description - rules: ghost-role-information-exterminator-rules - - type: GhostRoleMobSpawner - prototype: MobHumanTerminator - - type: Sprite - layers: - - state: green - - sprite: Mobs/Species/Terminator/parts.rsi - state: full diff --git a/Resources/Prototypes/Entities/Markers/Spawners/jobs.yml b/Resources/Prototypes/Entities/Markers/Spawners/jobs.yml index 27852ff1b5..25514e3162 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/jobs.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/jobs.yml @@ -475,6 +475,18 @@ - sprite: DeltaV/Markers/jobs.rsi # DeltaV - Epistemics new labcoats state: scientist +- type: entity + id: SpawnPointRoboticist + parent: SpawnPointJobBase + name: roboticist + components: + - type: SpawnPoint + job_id: Roboticist + - type: Sprite + layers: + - state: green + - state: roboticist + # Security - type: entity diff --git a/Resources/Prototypes/Entities/Markers/Spawners/mobs.yml b/Resources/Prototypes/Entities/Markers/Spawners/mobs.yml index 168026a5a7..1295d48e71 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/mobs.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/mobs.yml @@ -849,3 +849,17 @@ - type: ConditionalSpawner prototypes: - MobLuminousEntity + +- type: entity + name: clown spider spawner + id: SpawnClownSpider + parent: MarkerBase + components: + - type: Sprite + layers: + - state: green + - state: clown + sprite: Mobs/Animals/clownspider.rsi + - type: ConditionalSpawner + prototypes: + - MobClownSpider \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Markers/Spawners/statues.yml b/Resources/Prototypes/Entities/Markers/Spawners/statues.yml new file mode 100644 index 0000000000..4a3b89e401 --- /dev/null +++ b/Resources/Prototypes/Entities/Markers/Spawners/statues.yml @@ -0,0 +1,25 @@ +- type: entity + name: Oracle Spawner + id: OracleSpawner + parent: MarkerBase + components: + - type: Sprite + sprite: Markers/jobs.rsi + layers: + - state: green + - type: ConditionalSpawner + prototypes: + - Oracle + +- type: entity + name: Sophia Spawner + id: SophicScribeSpawner + parent: MarkerBase + components: + - type: Sprite + sprite: Markers/jobs.rsi + layers: + - state: green + - type: ConditionalSpawner + prototypes: + - SophicScribe diff --git a/Resources/Prototypes/Entities/Markers/environmental.yml b/Resources/Prototypes/Entities/Markers/environmental.yml new file mode 100644 index 0000000000..0642518356 --- /dev/null +++ b/Resources/Prototypes/Entities/Markers/environmental.yml @@ -0,0 +1,100 @@ +# Radiation +- type: entity + name: Marker Radiation + id: MarkerRadiation1 + parent: MarkerBase + suffix: intensity 1 + components: + - type: Sprite + layers: + - sprite: Markers/environment.rsi + state: base-green + shader: unshaded + - sprite: Markers/environment.rsi + shader: unshaded + state: rad + - type: RadiationSource + intensity: 1 + +- type: entity + parent: MarkerRadiation1 + id: MarkerRadiation2 + suffix: intensity 2 + components: + - type: RadiationSource + intensity: 2 + +- type: entity + parent: MarkerRadiation1 + id: MarkerRadiation3 + suffix: intensity 3 + components: + - type: RadiationSource + intensity: 3 + +- type: entity + parent: MarkerRadiation1 + id: MarkerRadiation4 + suffix: intensity 4 + components: + - type: RadiationSource + intensity: 4 + +- type: entity + parent: MarkerRadiation1 + id: MarkerRadiation5 + suffix: intensity 5 + components: + - type: RadiationSource + intensity: 5 + +- type: entity + parent: MarkerRadiation1 + id: MarkerRadiation10 + suffix: intensity 10 + components: + - type: RadiationSource + intensity: 10 + +# Invisible Walls +- type: entity + name: Marker Blocker + id: MarkerBlocker + parent: MarkerBase + suffix: invisible wall + components: + - type: Sprite + layers: + - sprite: Markers/environment.rsi + state: base-blue + shader: unshaded + - sprite: Markers/environment.rsi + shader: unshaded + state: wall + - type: PlacementReplacement + key: blocker + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeAabb + bounds: "-0.5,-0.5,0.5,0.5" + mask: + - FullTileMask + layer: + - WallLayer + density: 1000 + - type: Physics + bodyType: Static + + +# Weather Blocker +- type: entity + name: Marker Weather Blocker + id: MarkerWeatherblocker + parent: MarkerBase + components: + - type: Sprite + sprite: Markers/environment.rsi + state: weather + - type: BlockWeather diff --git a/Resources/Prototypes/Entities/Mobs/Customization/Markings/arachnid.yml b/Resources/Prototypes/Entities/Mobs/Customization/Markings/arachnid.yml index b138c2bd22..3d29b811c1 100644 --- a/Resources/Prototypes/Entities/Mobs/Customization/Markings/arachnid.yml +++ b/Resources/Prototypes/Entities/Mobs/Customization/Markings/arachnid.yml @@ -291,7 +291,7 @@ - type: marking id: ArachnidRArmStripes bodyPart: RArm - markingCategory: Arms + markingCategory: RightArm speciesRestriction: [Arachnid] sprites: - sprite: Mobs/Customization/Arachnid/arms.rsi @@ -304,7 +304,7 @@ - type: marking id: ArachnidLArmStripes bodyPart: LArm - markingCategory: Arms + markingCategory: LeftArm speciesRestriction: [Arachnid] sprites: - sprite: Mobs/Customization/Arachnid/arms.rsi @@ -318,7 +318,7 @@ - type: marking id: ArachnidRLegStripes bodyPart: RLeg - markingCategory: Legs + markingCategory: RightLeg speciesRestriction: [Arachnid] sprites: - sprite: Mobs/Customization/Arachnid/legs.rsi @@ -331,7 +331,7 @@ - type: marking id: ArachnidLLegStripes bodyPart: LLeg - markingCategory: Legs + markingCategory: LeftLeg speciesRestriction: [Arachnid] sprites: - sprite: Mobs/Customization/Arachnid/legs.rsi diff --git a/Resources/Prototypes/Entities/Mobs/Customization/Markings/harpy.yml b/Resources/Prototypes/Entities/Mobs/Customization/Markings/harpy.yml index 2629d83651..72cccec952 100644 --- a/Resources/Prototypes/Entities/Mobs/Customization/Markings/harpy.yml +++ b/Resources/Prototypes/Entities/Mobs/Customization/Markings/harpy.yml @@ -4,7 +4,7 @@ - type: marking id: HarpyWingDefaultHuescale bodyPart: RArm - markingCategory: Arms + markingCategory: RightArm speciesRestriction: [Harpy] coloring: default: @@ -21,7 +21,7 @@ - type: marking id: HarpyWingDefaultWhitescale bodyPart: RArm - markingCategory: Arms + markingCategory: RightArm speciesRestriction: [Harpy] coloring: default: @@ -38,7 +38,7 @@ - type: marking id: HarpyWingClassic bodyPart: RArm - markingCategory: Arms + markingCategory: RightArm speciesRestriction: [Harpy] coloring: default: @@ -55,7 +55,7 @@ - type: marking id: HarpyWingFoldedHuescale bodyPart: RArm - markingCategory: Arms + markingCategory: RightArm speciesRestriction: [Harpy] coloring: default: @@ -72,7 +72,7 @@ - type: marking id: HarpyWingFoldedWhitescale bodyPart: RArm - markingCategory: Arms + markingCategory: RightArm speciesRestriction: [Harpy] coloring: default: @@ -89,7 +89,7 @@ - type: marking id: HarpyWingOwlHuescale bodyPart: RArm - markingCategory: Arms + markingCategory: RightArm speciesRestriction: [Harpy] coloring: default: @@ -106,7 +106,7 @@ - type: marking id: HarpyWingOwlWhitescale bodyPart: RArm - markingCategory: Arms + markingCategory: RightArm speciesRestriction: [Harpy] coloring: default: @@ -306,7 +306,7 @@ - type: marking id: HarpyWing2ToneClassic bodyPart: RArm - markingCategory: Arms + markingCategory: RightArm speciesRestriction: [Harpy] sprites: - sprite: Mobs/Customization/Harpy/harpy_wings.rsi @@ -317,7 +317,7 @@ - type: marking id: HarpyWing3ToneClassic bodyPart: RArm - markingCategory: Arms + markingCategory: RightArm speciesRestriction: [Harpy] sprites: - sprite: Mobs/Customization/Harpy/harpy_wings.rsi @@ -330,7 +330,7 @@ - type: marking id: HarpyWingSpeckledClassic bodyPart: RArm - markingCategory: Arms + markingCategory: RightArm speciesRestriction: [Harpy] sprites: - sprite: Mobs/Customization/Harpy/harpy_wings.rsi @@ -341,7 +341,7 @@ - type: marking id: HarpyWingUndertoneClassic bodyPart: RArm - markingCategory: Arms + markingCategory: RightArm speciesRestriction: [Harpy] sprites: - sprite: Mobs/Customization/Harpy/harpy_wings.rsi @@ -352,7 +352,7 @@ - type: marking id: HarpyWingTipsClassic bodyPart: RArm - markingCategory: Arms + markingCategory: RightArm speciesRestriction: [Harpy] sprites: - sprite: Mobs/Customization/Harpy/harpy_wings.rsi @@ -363,7 +363,7 @@ - type: marking id: HarpyWingBat bodyPart: RArm - markingCategory: Arms + markingCategory: RightArm speciesRestriction: [Harpy] sprites: - sprite: Mobs/Customization/Harpy/harpy_wings.rsi @@ -374,7 +374,7 @@ - type: marking id: HarpyWingBionic bodyPart: RArm - markingCategory: Arms + markingCategory: RightArm speciesRestriction: [Harpy] sprites: - sprite: Mobs/Customization/Harpy/harpy_wings.rsi @@ -405,7 +405,7 @@ - type: marking id: HarpyLegsDefault bodyPart: LLeg - markingCategory: Legs + markingCategory: RightLeg speciesRestriction: [Harpy] coloring: default: @@ -422,7 +422,7 @@ - type: marking id: HarpyFeetDefault bodyPart: RFoot - markingCategory: Legs + markingCategory: RightLeg speciesRestriction: [Harpy] coloring: default: diff --git a/Resources/Prototypes/Entities/Mobs/Customization/Markings/human_hair.yml b/Resources/Prototypes/Entities/Mobs/Customization/Markings/human_hair.yml index 449f8aa2cd..aa983583c5 100644 --- a/Resources/Prototypes/Entities/Mobs/Customization/Markings/human_hair.yml +++ b/Resources/Prototypes/Entities/Mobs/Customization/Markings/human_hair.yml @@ -1160,6 +1160,13 @@ sprites: - sprite: Mobs/Customization/human_hair.rsi state: spiky2 +- type: marking + id: HumanHairSpookyLong + bodyPart: Hair + markingCategory: Hair + sprites: + - sprite: Mobs/Customization/human_hair.rsi + state: spookylong - type: marking id: HumanHairSwept bodyPart: Hair diff --git a/Resources/Prototypes/Entities/Mobs/Customization/Markings/kemonomimi_ears.yml b/Resources/Prototypes/Entities/Mobs/Customization/Markings/kemonomimi_ears.yml new file mode 100644 index 0000000000..66b4b5242c --- /dev/null +++ b/Resources/Prototypes/Entities/Mobs/Customization/Markings/kemonomimi_ears.yml @@ -0,0 +1,77 @@ +- type: marking + id: BullishHorns + bodyPart: HeadTop + markingCategory: HeadTop + speciesRestriction: [Human] + coloring: + default: + type: + !type:CategoryColoring + category: Hair + fallbackTypes: + - !type:SkinColoring + sprites: + - sprite: Mobs/Customization/kemonomimi-ears.rsi + state: bullishhorns + +- type: marking + id: BunnyEars + bodyPart: HeadTop + markingCategory: HeadTop + speciesRestriction: [Human] + coloring: + default: + type: + !type:CategoryColoring + category: Hair + fallbackTypes: + - !type:SkinColoring + layers: + bunnyearstone2: + type: + !type:SimpleColoring + color: "#FFFFFF" + sprites: + - sprite: Mobs/Customization/kemonomimi-ears.rsi + state: bunnyearstone1 + - sprite: Mobs/Customization/kemonomimi-ears.rsi + state: bunnyearstone2 + +- type: marking + id: FoxEars + bodyPart: HeadTop + markingCategory: HeadTop + speciesRestriction: [Human] + coloring: + default: + type: + !type:CategoryColoring + category: Hair + fallbackTypes: + - !type:SkinColoring + sprites: + - sprite: Mobs/Customization/kemonomimi-ears.rsi + state: foxears + +- type: marking + id: GoatHorns + bodyPart: HeadTop + markingCategory: HeadTop + speciesRestriction: [Human] + coloring: + default: + type: + !type:CategoryColoring + category: Hair + fallbackTypes: + - !type:SkinColoring + layers: + goathornstone22: + type: + !type:SimpleColoring + color: "#FFFFFF" + sprites: + - sprite: Mobs/Customization/kemonomimi-ears.rsi + state: goathornstone1 + - sprite: Mobs/Customization/kemonomimi-ears.rsi + state: goathornstone2 \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Mobs/Customization/Markings/kemonomimi_tails.yml b/Resources/Prototypes/Entities/Mobs/Customization/Markings/kemonomimi_tails.yml new file mode 100644 index 0000000000..d45d366ec0 --- /dev/null +++ b/Resources/Prototypes/Entities/Mobs/Customization/Markings/kemonomimi_tails.yml @@ -0,0 +1,169 @@ +- type: marking + id: BunnyTail + bodyPart: Tail + markingCategory: Tail + speciesRestriction: [Human] + coloring: + default: + type: + !type:CategoryColoring + category: Hair + fallbackTypes: + - !type:CategoryColoring + category: FacialHair + - !type:SkinColoring + sprites: + - sprite: Mobs/Customization/kemonomimi-tails.rsi + state: bunnytail + +- type: marking + id: DobleFur + bodyPart: Tail + markingCategory: Tail + speciesRestriction: [Human] + coloring: + default: + type: + !type:CategoryColoring + category: Hair + fallbackTypes: + - !type:CategoryColoring + category: FacialHair + - !type:SkinColoring + sprites: + - sprite: Mobs/Customization/kemonomimi-tails.rsi + state: doblefurtailtone1 + - sprite: Mobs/Customization/kemonomimi-tails.rsi + state: doblefurtailtone2 + +- type: marking + id: FluffyTail + bodyPart: Tail + markingCategory: Tail + speciesRestriction: [Human] + coloring: + default: + type: + !type:CategoryColoring + category: Hair + fallbackTypes: + - !type:CategoryColoring + category: FacialHair + - !type:SkinColoring + sprites: + - sprite: Mobs/Customization/kemonomimi-tails.rsi + state: fluffytail + +- type: marking + id: FoxNineTails + bodyPart: Tail + markingCategory: Tail + speciesRestriction: [Human] + coloring: + default: + type: + !type:CategoryColoring + category: Hair + fallbackTypes: + - !type:CategoryColoring + category: FacialHair + - !type:SkinColoring + sprites: + - sprite: Mobs/Customization/kemonomimi-tails.rsi + state: foxninetailstone1 + - sprite: Mobs/Customization/kemonomimi-tails.rsi + state: foxninetailstone2 + +- type: marking + id: FoxThreeTails + bodyPart: Tail + markingCategory: Tail + speciesRestriction: [Human] + coloring: + default: + type: + !type:CategoryColoring + category: Hair + fallbackTypes: + - !type:CategoryColoring + category: FacialHair + - !type:SkinColoring + sprites: + - sprite: Mobs/Customization/kemonomimi-tails.rsi + state: foxthreetailstone1 + - sprite: Mobs/Customization/kemonomimi-tails.rsi + state: foxthreetailstone2 + +- type: marking + id: HorseTailCulty + bodyPart: Tail + markingCategory: Tail + speciesRestriction: [Human] + coloring: + default: + type: + !type:CategoryColoring + category: Hair + fallbackTypes: + - !type:CategoryColoring + category: FacialHair + - !type:SkinColoring + sprites: + - sprite: Mobs/Customization/kemonomimi-tails.rsi + state: horsetailculty + +- type: marking + id: HorseTailLong + bodyPart: Tail + markingCategory: Tail + speciesRestriction: [Human] + coloring: + default: + type: + !type:CategoryColoring + category: Hair + fallbackTypes: + - !type:CategoryColoring + category: FacialHair + - !type:SkinColoring + sprites: + - sprite: Mobs/Customization/kemonomimi-tails.rsi + state: horsetaillong + +- type: marking + id: SharkTail + bodyPart: Tail + markingCategory: Tail + speciesRestriction: [Human] + coloring: + default: + type: + !type:CategoryColoring + category: Hair + fallbackTypes: + - !type:CategoryColoring + category: FacialHair + - !type:SkinColoring + sprites: + - sprite: Mobs/Customization/kemonomimi-tails.rsi + state: sharktail + +- type: marking + id: TasselTail + bodyPart: Tail + markingCategory: Tail + speciesRestriction: [Human] + coloring: + default: + type: + !type:CategoryColoring + category: Hair + fallbackTypes: + - !type:CategoryColoring + category: FacialHair + - !type:SkinColoring + sprites: + - sprite: Mobs/Customization/kemonomimi-tails.rsi + state: tasseltailtone1 + - sprite: Mobs/Customization/kemonomimi-tails.rsi + state: tasseltailtone2 \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Mobs/Customization/Markings/moth.yml b/Resources/Prototypes/Entities/Mobs/Customization/Markings/moth.yml index ee0d5f0de3..205e6d69fe 100644 --- a/Resources/Prototypes/Entities/Mobs/Customization/Markings/moth.yml +++ b/Resources/Prototypes/Entities/Mobs/Customization/Markings/moth.yml @@ -417,7 +417,7 @@ - type: marking id: MothLLegCharred bodyPart: LLeg - markingCategory: Legs + markingCategory: LeftLeg speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -426,7 +426,7 @@ - type: marking id: MothRLegCharred bodyPart: RLeg - markingCategory: Legs + markingCategory: RightLeg speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -435,7 +435,7 @@ - type: marking id: MothLArmCharred bodyPart: LArm - markingCategory: Arms + markingCategory: LeftArm speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -444,7 +444,7 @@ - type: marking id: MothRArmCharred bodyPart: RArm - markingCategory: Arms + markingCategory: RightArm speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -472,7 +472,7 @@ - type: marking id: MothLLegDeathhead bodyPart: LLeg - markingCategory: Legs + markingCategory: LeftLeg speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -481,7 +481,7 @@ - type: marking id: MothRLegDeathhead bodyPart: RLeg - markingCategory: Legs + markingCategory: RightLeg speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -490,7 +490,7 @@ - type: marking id: MothLArmDeathhead bodyPart: LArm - markingCategory: Arms + markingCategory: LeftArm speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -499,7 +499,7 @@ - type: marking id: MothRArmDeathhead bodyPart: RArm - markingCategory: Arms + markingCategory: RightArm speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -527,7 +527,7 @@ - type: marking id: MothLLegFan bodyPart: LLeg - markingCategory: Legs + markingCategory: LeftLeg speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -536,7 +536,7 @@ - type: marking id: MothRLegFan bodyPart: RLeg - markingCategory: Legs + markingCategory: RightLeg speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -545,7 +545,7 @@ - type: marking id: MothLArmFan bodyPart: LArm - markingCategory: Arms + markingCategory: LeftArm speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -554,7 +554,7 @@ - type: marking id: MothRArmFan bodyPart: RArm - markingCategory: Arms + markingCategory: RightArm speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -582,7 +582,7 @@ - type: marking id: MothLLegFirewatch bodyPart: LLeg - markingCategory: Legs + markingCategory: LeftLeg speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -591,7 +591,7 @@ - type: marking id: MothRLegFirewatch bodyPart: RLeg - markingCategory: Legs + markingCategory: RightLeg speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -600,7 +600,7 @@ - type: marking id: MothLArmFirewatch bodyPart: LArm - markingCategory: Arms + markingCategory: LeftArm speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -609,7 +609,7 @@ - type: marking id: MothRArmFirewatch bodyPart: RArm - markingCategory: Arms + markingCategory: RightArm speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -637,7 +637,7 @@ - type: marking id: MothLLegGothic bodyPart: LLeg - markingCategory: Legs + markingCategory: LeftLeg speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -646,7 +646,7 @@ - type: marking id: MothRLegGothic bodyPart: RLeg - markingCategory: Legs + markingCategory: RightLeg speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -655,7 +655,7 @@ - type: marking id: MothLArmGothic bodyPart: LArm - markingCategory: Arms + markingCategory: LeftArm speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -664,7 +664,7 @@ - type: marking id: MothRArmGothic bodyPart: RArm - markingCategory: Arms + markingCategory: RightArm speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -692,7 +692,7 @@ - type: marking id: MothLLegJungle bodyPart: LLeg - markingCategory: Legs + markingCategory: LeftLeg speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -701,7 +701,7 @@ - type: marking id: MothRLegJungle bodyPart: RLeg - markingCategory: Legs + markingCategory: RightLeg speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -710,7 +710,7 @@ - type: marking id: MothLArmJungle bodyPart: LArm - markingCategory: Arms + markingCategory: LeftArm speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -719,7 +719,7 @@ - type: marking id: MothRArmJungle bodyPart: RArm - markingCategory: Arms + markingCategory: RightArm speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -747,7 +747,7 @@ - type: marking id: MothLLegMoonfly bodyPart: LLeg - markingCategory: Legs + markingCategory: LeftLeg speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -756,7 +756,7 @@ - type: marking id: MothRLegMoonfly bodyPart: RLeg - markingCategory: Legs + markingCategory: RightLeg speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -765,7 +765,7 @@ - type: marking id: MothLArmMoonfly bodyPart: LArm - markingCategory: Arms + markingCategory: LeftArm speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -774,7 +774,7 @@ - type: marking id: MothRArmMoonfly bodyPart: RArm - markingCategory: Arms + markingCategory: RightArm speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -802,7 +802,7 @@ - type: marking id: MothLLegOakworm bodyPart: LLeg - markingCategory: Legs + markingCategory: LeftLeg speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -811,7 +811,7 @@ - type: marking id: MothRLegOakworm bodyPart: RLeg - markingCategory: Legs + markingCategory: RightLeg speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -820,7 +820,7 @@ - type: marking id: MothLArmOakworm bodyPart: LArm - markingCategory: Arms + markingCategory: LeftArm speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -829,7 +829,7 @@ - type: marking id: MothRArmOakworm bodyPart: RArm - markingCategory: Arms + markingCategory: RightArm speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -857,7 +857,7 @@ - type: marking id: MothLLegPointy bodyPart: LLeg - markingCategory: Legs + markingCategory: LeftLeg speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -866,7 +866,7 @@ - type: marking id: MothRLegPointy bodyPart: RLeg - markingCategory: Legs + markingCategory: RightLeg speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -875,7 +875,7 @@ - type: marking id: MothLArmPointy bodyPart: LArm - markingCategory: Arms + markingCategory: LeftArm speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -884,7 +884,7 @@ - type: marking id: MothRArmPointy bodyPart: RArm - markingCategory: Arms + markingCategory: RightArm speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -912,7 +912,7 @@ - type: marking id: MothLLegRagged bodyPart: LLeg - markingCategory: Legs + markingCategory: LeftLeg speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -921,7 +921,7 @@ - type: marking id: MothRLegRagged bodyPart: RLeg - markingCategory: Legs + markingCategory: RightLeg speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -930,7 +930,7 @@ - type: marking id: MothLArmRagged bodyPart: LArm - markingCategory: Arms + markingCategory: LeftArm speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -939,7 +939,7 @@ - type: marking id: MothRArmRagged bodyPart: RArm - markingCategory: Arms + markingCategory: RightArm speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -967,7 +967,7 @@ - type: marking id: MothLLegRoyal bodyPart: LLeg - markingCategory: Legs + markingCategory: LeftLeg speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -976,7 +976,7 @@ - type: marking id: MothRLegRoyal bodyPart: RLeg - markingCategory: Legs + markingCategory: RightLeg speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -985,7 +985,7 @@ - type: marking id: MothLArmRoyal bodyPart: LArm - markingCategory: Arms + markingCategory: LeftArm speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -994,7 +994,7 @@ - type: marking id: MothRArmRoyal bodyPart: RArm - markingCategory: Arms + markingCategory: RightArm speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -1022,7 +1022,7 @@ - type: marking id: MothLLegWhitefly bodyPart: LLeg - markingCategory: Legs + markingCategory: LeftLeg speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -1031,7 +1031,7 @@ - type: marking id: MothRLegWhitefly bodyPart: RLeg - markingCategory: Legs + markingCategory: RightLeg speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -1040,7 +1040,7 @@ - type: marking id: MothLArmWhitefly bodyPart: LArm - markingCategory: Arms + markingCategory: LeftArm speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -1049,7 +1049,7 @@ - type: marking id: MothRArmWhitefly bodyPart: RArm - markingCategory: Arms + markingCategory: RightArm speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -1077,7 +1077,7 @@ - type: marking id: MothLLegWitchwing bodyPart: LLeg - markingCategory: Legs + markingCategory: LeftLeg speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -1086,7 +1086,7 @@ - type: marking id: MothRLegWitchwing bodyPart: RLeg - markingCategory: Legs + markingCategory: RightLeg speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -1095,7 +1095,7 @@ - type: marking id: MothLArmWitchwing bodyPart: LArm - markingCategory: Arms + markingCategory: LeftArm speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi @@ -1104,7 +1104,7 @@ - type: marking id: MothRArmWitchwing bodyPart: RArm - markingCategory: Arms + markingCategory: RightArm speciesRestriction: [Moth] sprites: - sprite: Mobs/Customization/Moth/moth_parts.rsi diff --git a/Resources/Prototypes/Entities/Mobs/Customization/Markings/oni_feet.yml b/Resources/Prototypes/Entities/Mobs/Customization/Markings/oni_feet.yml index 0ff03df1a0..552b6322cd 100644 --- a/Resources/Prototypes/Entities/Mobs/Customization/Markings/oni_feet.yml +++ b/Resources/Prototypes/Entities/Mobs/Customization/Markings/oni_feet.yml @@ -1,7 +1,7 @@ - type: marking id: OniTwoToedFeet bodyPart: RFoot # Can't be LFoot to avoid visual glitches - markingCategory: Legs + markingCategory: RightFoot speciesRestriction: [Oni] coloring: default: diff --git a/Resources/Prototypes/Entities/Mobs/Customization/Markings/reptilian.yml b/Resources/Prototypes/Entities/Mobs/Customization/Markings/reptilian.yml index 728a203489..57c2f66338 100644 --- a/Resources/Prototypes/Entities/Mobs/Customization/Markings/reptilian.yml +++ b/Resources/Prototypes/Entities/Mobs/Customization/Markings/reptilian.yml @@ -2,7 +2,7 @@ id: LizardFrillsAquatic bodyPart: HeadSide markingCategory: HeadSide - speciesRestriction: [Reptilian] + speciesRestriction: [Reptilian, Human] sprites: - sprite: Mobs/Customization/reptilian_parts.rsi state: frills_aquatic @@ -11,7 +11,7 @@ id: LizardFrillsShort bodyPart: HeadSide markingCategory: HeadSide - speciesRestriction: [Reptilian] + speciesRestriction: [Reptilian, Human] sprites: - sprite: Mobs/Customization/reptilian_parts.rsi state: frills_short @@ -20,7 +20,7 @@ id: LizardFrillsSimple bodyPart: HeadSide markingCategory: HeadSide - speciesRestriction: [Reptilian] + speciesRestriction: [Reptilian, Human] sprites: - sprite: Mobs/Customization/reptilian_parts.rsi state: frills_simple @@ -29,7 +29,7 @@ id: LizardFrillsDivinity bodyPart: HeadSide markingCategory: HeadSide - speciesRestriction: [Reptilian] + speciesRestriction: [Reptilian, Human] sprites: - sprite: Mobs/Customization/reptilian_parts.rsi state: frills_divinity @@ -38,7 +38,7 @@ id: LizardFrillsBig bodyPart: HeadSide markingCategory: HeadSide - speciesRestriction: [Reptilian] + speciesRestriction: [Reptilian, Human] sprites: - sprite: Mobs/Customization/reptilian_parts.rsi state: frills_big @@ -47,7 +47,7 @@ id: LizardFrillsAxolotl bodyPart: HeadSide markingCategory: HeadSide - speciesRestriction: [Reptilian] + speciesRestriction: [Reptilian, Human] sprites: - sprite: Mobs/Customization/reptilian_parts.rsi state: frills_axolotl @@ -56,18 +56,27 @@ id: LizardFrillsHood bodyPart: HeadSide markingCategory: HeadSide - speciesRestriction: [Reptilian] + speciesRestriction: [Reptilian, Human] sprites: - sprite: Mobs/Customization/reptilian_parts.rsi state: frills_hood_primary - sprite: Mobs/Customization/reptilian_parts.rsi state: frills_hood_secondary +- type: marking + id: LizardFrillsNeckfull + bodyPart: HeadSide + markingCategory: HeadSide + speciesRestriction: [Reptilian] + sprites: + - sprite: Mobs/Customization/reptilian_parts.rsi + state: frills_neckfull + - type: marking id: LizardHornsAngler bodyPart: HeadTop markingCategory: HeadTop - speciesRestriction: [Reptilian] + speciesRestriction: [Reptilian, Human] sprites: - sprite: Mobs/Customization/reptilian_parts.rsi state: horns_angler @@ -76,7 +85,7 @@ id: LizardHornsCurled bodyPart: HeadTop markingCategory: HeadTop - speciesRestriction: [Reptilian] + speciesRestriction: [Reptilian, Human] sprites: - sprite: Mobs/Customization/reptilian_parts.rsi state: horns_curled @@ -85,7 +94,7 @@ id: LizardHornsRam bodyPart: HeadTop markingCategory: HeadTop - speciesRestriction: [Reptilian] + speciesRestriction: [Reptilian, Human] sprites: - sprite: Mobs/Customization/reptilian_parts.rsi state: horns_ram @@ -94,7 +103,7 @@ id: LizardHornsShort bodyPart: HeadTop markingCategory: HeadTop - speciesRestriction: [Reptilian] + speciesRestriction: [Reptilian, Human] sprites: - sprite: Mobs/Customization/reptilian_parts.rsi state: horns_short @@ -103,7 +112,7 @@ id: LizardHornsSimple bodyPart: HeadTop markingCategory: HeadTop - speciesRestriction: [Reptilian] + speciesRestriction: [Reptilian, Human] sprites: - sprite: Mobs/Customization/reptilian_parts.rsi state: horns_simple @@ -112,7 +121,7 @@ id: LizardHornsDouble bodyPart: HeadTop markingCategory: HeadTop - speciesRestriction: [Reptilian] + speciesRestriction: [Reptilian, Human] sprites: - sprite: Mobs/Customization/reptilian_parts.rsi state: horns_double @@ -121,7 +130,7 @@ id: LizardTailSmooth bodyPart: Tail markingCategory: Tail - speciesRestriction: [Reptilian] + speciesRestriction: [Reptilian, Human] sprites: - sprite: Mobs/Customization/reptilian_parts.rsi state: tail_smooth_primary @@ -132,7 +141,7 @@ id: LizardTailLarge bodyPart: Tail markingCategory: Tail - speciesRestriction: [Reptilian] + speciesRestriction: [Reptilian, Human] sprites: - sprite: Mobs/Customization/reptilian_parts.rsi state: tail_large @@ -141,7 +150,7 @@ id: LizardTailSpikes bodyPart: Tail markingCategory: Tail - speciesRestriction: [Reptilian] + speciesRestriction: [Reptilian, Human] sprites: - sprite: Mobs/Customization/reptilian_parts.rsi state: tail_spikes @@ -150,7 +159,7 @@ id: LizardTailLTiger bodyPart: Tail markingCategory: Tail - speciesRestriction: [Reptilian] + speciesRestriction: [Reptilian, Human] sprites: - sprite: Mobs/Customization/reptilian_parts.rsi state: tail_ltiger @@ -159,7 +168,7 @@ id: LizardTailDTiger bodyPart: Tail markingCategory: Tail - speciesRestriction: [Reptilian] + speciesRestriction: [Reptilian, Human] sprites: - sprite: Mobs/Customization/reptilian_parts.rsi state: tail_dtiger @@ -184,11 +193,22 @@ - sprite: Mobs/Customization/reptilian_parts.rsi state: snout_sharp +- type: marking + id: LizardSnoutSplotch + bodyPart: Snout + markingCategory: Snout + speciesRestriction: [Reptilian] + sprites: + - sprite: Mobs/Customization/reptilian_parts.rsi + state: snout_splotch_primary + - sprite: Mobs/Customization/reptilian_parts.rsi + state: snout_splotch_secondary + - type: marking id: LizardChestTiger bodyPart: Chest markingCategory: Chest - speciesRestriction: [Reptilian, Shadowkin] # Parkstation-Shadowkin + speciesRestriction: [Reptilian, Shadowkin] sprites: - sprite: Mobs/Customization/reptilian_parts.rsi state: body_tiger @@ -205,7 +225,7 @@ - type: marking id: LizardLArmTiger bodyPart: LArm - markingCategory: Arms + markingCategory: LeftArm speciesRestriction: [Reptilian, Shadowkin] # Parkstation-Shadowkin sprites: - sprite: Mobs/Customization/reptilian_parts.rsi @@ -214,7 +234,7 @@ - type: marking id: LizardLLegTiger bodyPart: LLeg - markingCategory: Legs + markingCategory: LeftLeg speciesRestriction: [Reptilian, Shadowkin] # Parkstation-Shadowkin sprites: - sprite: Mobs/Customization/reptilian_parts.rsi @@ -223,7 +243,7 @@ - type: marking id: LizardRArmTiger bodyPart: RArm - markingCategory: Arms + markingCategory: RightArm speciesRestriction: [Reptilian, Shadowkin] # Parkstation-Shadowkin sprites: - sprite: Mobs/Customization/reptilian_parts.rsi @@ -232,7 +252,7 @@ - type: marking id: LizardRLegTiger bodyPart: RLeg - markingCategory: Legs + markingCategory: RightLeg speciesRestriction: [Reptilian, Shadowkin] # Parkstation-Shadowkin sprites: - sprite: Mobs/Customization/reptilian_parts.rsi @@ -242,7 +262,7 @@ id: LizardHornsArgali bodyPart: HeadTop markingCategory: HeadTop - speciesRestriction: [Reptilian] + speciesRestriction: [Reptilian, Human] sprites: - sprite: Mobs/Customization/reptilian_parts.rsi state: horns_argali @@ -251,7 +271,7 @@ id: LizardHornsAyrshire bodyPart: HeadTop markingCategory: HeadTop - speciesRestriction: [Reptilian] + speciesRestriction: [Reptilian, Human] sprites: - sprite: Mobs/Customization/reptilian_parts.rsi state: horns_ayrshire @@ -260,7 +280,7 @@ id: LizardHornsMyrsore bodyPart: HeadTop markingCategory: HeadTop - speciesRestriction: [Reptilian] + speciesRestriction: [Reptilian, Human] sprites: - sprite: Mobs/Customization/reptilian_parts.rsi state: horns_myrsore @@ -269,7 +289,7 @@ id: LizardHornsBighorn bodyPart: HeadTop markingCategory: HeadTop - speciesRestriction: [Reptilian] + speciesRestriction: [Reptilian, Human] sprites: - sprite: Mobs/Customization/reptilian_parts.rsi state: horns_bighorn @@ -278,7 +298,7 @@ id: LizardHornsKoboldEars bodyPart: HeadTop markingCategory: HeadTop - speciesRestriction: [Reptilian] + speciesRestriction: [Reptilian, Human] sprites: - sprite: Mobs/Customization/reptilian_parts.rsi state: horns_kobold_ears @@ -287,7 +307,7 @@ id: LizardHornsFloppyKoboldEars bodyPart: HeadSide markingCategory: HeadSide - speciesRestriction: [Reptilian] + speciesRestriction: [Reptilian, Human] sprites: - sprite: Mobs/Customization/reptilian_parts.rsi state: horns_floppy_kobold_ears diff --git a/Resources/Prototypes/Entities/Mobs/Customization/Markings/shadowkin.yml b/Resources/Prototypes/Entities/Mobs/Customization/Markings/shadowkin.yml new file mode 100644 index 0000000000..f86852a987 --- /dev/null +++ b/Resources/Prototypes/Entities/Mobs/Customization/Markings/shadowkin.yml @@ -0,0 +1,83 @@ +# Ears + +- type: marking + id: EarsShadowkin + bodyPart: HeadTop + markingCategory: HeadTop + speciesRestriction: [Shadowkin] + forcedColoring: true + sprites: + - sprite: Mobs/Customization/Shadowkin/ears.rsi + state: shadowkin + +- type: marking + id: EarsShadowkinStriped + bodyPart: HeadTop + markingCategory: HeadTop + speciesRestriction: [Shadowkin] + coloring: + default: + type: + !type:SkinColoring + layers: + shadowkin_stripes: + type: + !type:SimpleColoring + color: "#FFFFFF" + sprites: + - sprite: Mobs/Customization/Shadowkin/ears.rsi + state: shadowkin + - sprite: Mobs/Customization/Shadowkin/ears.rsi + state: shadowkin_stripes + +# Tails + +- type: marking + id: TailShadowkin + bodyPart: Tail + markingCategory: Tail + speciesRestriction: [Shadowkin] + forcedColoring: true + sprites: + - sprite: Mobs/Customization/Shadowkin/tails64x32.rsi + state: shadowkin + +- type: marking + id: TailShadowkinBig + bodyPart: Tail + markingCategory: Tail + speciesRestriction: [Shadowkin] + forcedColoring: true + sprites: + - sprite: Mobs/Customization/Shadowkin/tails64x32.rsi + state: shadowkin_big + +- type: marking + id: TailShadowkinBigFluff + bodyPart: Tail + markingCategory: Tail + speciesRestriction: [Shadowkin] + forcedColoring: true + sprites: + - sprite: Mobs/Customization/Shadowkin/tails64x32.rsi + state: shadowkin_big_fluff + +- type: marking + id: TailShadowkinShorter + bodyPart: Tail + markingCategory: Tail + speciesRestriction: [Shadowkin] + forcedColoring: true + sprites: + - sprite: Mobs/Customization/Shadowkin/tails32x32.rsi + state: shadowkin_shorter + +- type: marking + id: TailShadowkinMedium + bodyPart: Tail + markingCategory: Tail + speciesRestriction: [Shadowkin] + forcedColoring: true + sprites: + - sprite: Mobs/Customization/Shadowkin/tails32x32.rsi + state: shadowkin_medium \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Mobs/Customization/Markings/slime.yml b/Resources/Prototypes/Entities/Mobs/Customization/Markings/slime.yml index 77a21b11fe..92918d4f14 100644 --- a/Resources/Prototypes/Entities/Mobs/Customization/Markings/slime.yml +++ b/Resources/Prototypes/Entities/Mobs/Customization/Markings/slime.yml @@ -1,7 +1,7 @@ - type: marking id: SlimeGradientLeftArm bodyPart: LArm - markingCategory: Arms + markingCategory: LeftArm speciesRestriction: [ Reptilian, SlimePerson, Shadowkin ] # Parkstation-WideUseMarkings sprites: - sprite: Mobs/Customization/slime_parts.rsi @@ -10,7 +10,7 @@ - type: marking id: SlimeGradientRightArm bodyPart: RArm - markingCategory: Arms + markingCategory: RightArm speciesRestriction: [ Reptilian, SlimePerson, Shadowkin ] # Parkstation-WideUseMarkings sprites: - sprite: Mobs/Customization/slime_parts.rsi @@ -19,7 +19,7 @@ - type: marking id: SlimeGradientLeftLeg bodyPart: LLeg - markingCategory: Legs + markingCategory: LeftLeg speciesRestriction: [ Reptilian, SlimePerson, Shadowkin ] # Parkstation-WideUseMarkings sprites: - sprite: Mobs/Customization/slime_parts.rsi @@ -28,7 +28,7 @@ - type: marking id: SlimeGradientRightLeg bodyPart: RLeg - markingCategory: Legs + markingCategory: RightLeg speciesRestriction: [ Reptilian, SlimePerson, Shadowkin ] # Parkstation-WideUseMarkings sprites: - sprite: Mobs/Customization/slime_parts.rsi @@ -37,8 +37,8 @@ - type: marking id: SlimeGradientLeftFoot bodyPart: LFoot - markingCategory: Legs - speciesRestriction: [SlimePerson] + markingCategory: LeftFoot + speciesRestriction: [SlimePerson, Shadowkin] sprites: - sprite: Mobs/Customization/slime_parts.rsi state: gradient_l_foot @@ -46,8 +46,8 @@ - type: marking id: SlimeGradientRightFoot bodyPart: RFoot - markingCategory: Legs - speciesRestriction: [SlimePerson] + markingCategory: RightFoot + speciesRestriction: [SlimePerson, Shadowkin] sprites: - sprite: Mobs/Customization/slime_parts.rsi state: gradient_r_foot @@ -55,7 +55,7 @@ - type: marking id: SlimeGradientLeftHand bodyPart: LHand - markingCategory: Arms + markingCategory: LeftHand speciesRestriction: [ Reptilian, SlimePerson, Shadowkin ] # Parkstation-WideUseMarkings sprites: - sprite: Mobs/Customization/slime_parts.rsi @@ -64,7 +64,7 @@ - type: marking id: SlimeGradientRightHand bodyPart: RHand - markingCategory: Arms + markingCategory: RightHand speciesRestriction: [ Reptilian, SlimePerson, Shadowkin ] # Parkstation-WideUseMarkings sprites: - sprite: Mobs/Customization/slime_parts.rsi diff --git a/Resources/Prototypes/Entities/Mobs/Customization/Markings/tattoos.yml b/Resources/Prototypes/Entities/Mobs/Customization/Markings/tattoos.yml index ec37c9e985..987308d3ff 100644 --- a/Resources/Prototypes/Entities/Mobs/Customization/Markings/tattoos.yml +++ b/Resources/Prototypes/Entities/Mobs/Customization/Markings/tattoos.yml @@ -29,7 +29,7 @@ - type: marking id: TattooSilverburghLeftLeg bodyPart: LLeg - markingCategory: Legs + markingCategory: LeftLeg speciesRestriction: [ Human, Dwarf, Felinid, Oni, Harpy, Reptilian, SlimePerson, Arachne, Arachnid, Moth, Shadowkin ] # Parkstation-WideUseMarkings coloring: default: @@ -43,7 +43,7 @@ - type: marking id: TattooSilverburghRightLeg bodyPart: RLeg - markingCategory: Legs + markingCategory: RightLeg speciesRestriction: [ Human, Dwarf, Felinid, Oni, Harpy, Reptilian, SlimePerson, Arachne, Arachnid, Moth, Shadowkin ] # Parkstation-WideUseMarkings coloring: default: @@ -57,7 +57,7 @@ - type: marking id: TattooCampbellLeftArm bodyPart: LArm - markingCategory: Arms + markingCategory: LeftArm speciesRestriction: [ Human, Dwarf, Felinid, Oni, Harpy, Reptilian, SlimePerson, Arachne, Arachnid, Moth, Shadowkin ] # Parkstation-WideUseMarkings coloring: default: @@ -71,7 +71,7 @@ - type: marking id: TattooCampbellRightArm bodyPart: RArm - markingCategory: Arms + markingCategory: RightArm speciesRestriction: [ Human, Dwarf, Felinid, Oni, Harpy, Reptilian, SlimePerson, Arachne, Arachnid, Moth, Shadowkin ] # Parkstation-WideUseMarkings coloring: default: @@ -85,7 +85,7 @@ - type: marking id: TattooCampbellLeftLeg bodyPart: LLeg - markingCategory: Legs + markingCategory: LeftLeg speciesRestriction: [ Human, Dwarf, Felinid, Oni, Harpy, Reptilian, SlimePerson, Arachne, Arachnid, Moth, Shadowkin ] # Parkstation-WideUseMarkings coloring: default: @@ -99,7 +99,7 @@ - type: marking id: TattooCampbellRightLeg bodyPart: RLeg - markingCategory: Legs + markingCategory: RightLeg speciesRestriction: [ Human, Dwarf, Felinid, Oni, Harpy, Reptilian, SlimePerson, Arachne, Arachnid, Moth, Shadowkin ] # Parkstation-WideUseMarkings coloring: default: diff --git a/Resources/Prototypes/Entities/Mobs/Customization/Markings/vox_facial_hair.yml b/Resources/Prototypes/Entities/Mobs/Customization/Markings/vox_facial_hair.yml index d4cc4b00e3..6ec5f8999a 100644 --- a/Resources/Prototypes/Entities/Mobs/Customization/Markings/vox_facial_hair.yml +++ b/Resources/Prototypes/Entities/Mobs/Customization/Markings/vox_facial_hair.yml @@ -1,40 +1,44 @@ - type: marking - id: VoxFacialHairColonel + id: VoxFacialHairBeard bodyPart: FacialHair markingCategory: FacialHair speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_facial_hair.rsi - state: vox_colonel_s + state: beard_s + - type: marking - id: VoxFacialHairFu + id: VoxFacialHairColonel bodyPart: FacialHair markingCategory: FacialHair speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_facial_hair.rsi - state: vox_fu_s + state: colonel_s + - type: marking - id: VoxFacialHairNeck + id: VoxFacialHairFu bodyPart: FacialHair markingCategory: FacialHair speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_facial_hair.rsi - state: vox_neck_s + state: fu_s + - type: marking - id: VoxFacialHairBeard + id: VoxFacialHairMane bodyPart: FacialHair markingCategory: FacialHair speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_facial_hair.rsi - state: vox_beard_s + state: mane_s + - type: marking - id: VoxFacialHairRuffBeard + id: VoxFacialHairNeck bodyPart: FacialHair markingCategory: FacialHair speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_facial_hair.rsi - state: vox_ruff_beard_s + state: neck_s diff --git a/Resources/Prototypes/Entities/Mobs/Customization/Markings/vox_hair.yml b/Resources/Prototypes/Entities/Mobs/Customization/Markings/vox_hair.yml index 975de63204..9a847b40a9 100644 --- a/Resources/Prototypes/Entities/Mobs/Customization/Markings/vox_hair.yml +++ b/Resources/Prototypes/Entities/Mobs/Customization/Markings/vox_hair.yml @@ -1,91 +1,102 @@ - type: marking - id: VoxHairShortQuills + id: VoxHairAfro bodyPart: Hair markingCategory: Hair speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_hair.rsi - state: vox_shortquills_s + state: afro_s + - type: marking - id: VoxHairKingly + id: VoxHairBraids bodyPart: Hair markingCategory: Hair speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_hair.rsi - state: vox_kingly_s + state: braid_s + - type: marking - id: VoxHairAfro + id: VoxHairCrestedQuills bodyPart: Hair markingCategory: Hair speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_hair.rsi - state: vox_afro_s + state: crestedquills_s + - type: marking - id: VoxHairMohawk + id: VoxHairEmperorQuills bodyPart: Hair markingCategory: Hair speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_hair.rsi - state: vox_mohawk_s + state: emperorquills_s + - type: marking - id: VoxHairYasuhiro + id: VoxHairFlowing bodyPart: Hair markingCategory: Hair speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_hair.rsi - state: vox_yasu_s + state: flowing_s + - type: marking - id: VoxHairHorns + id: VoxHairHawk bodyPart: Hair markingCategory: Hair speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_hair.rsi - state: vox_horns_s + state: hawk_s + - type: marking - id: VoxHairNights + id: VoxHairHorns bodyPart: Hair markingCategory: Hair speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_hair.rsi - state: vox_nights_s + state: horns_s + - type: marking - id: VoxHairSurf + id: VoxHairKeelQuills bodyPart: Hair markingCategory: Hair speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_hair.rsi - state: vox_surf_s + state: keelquills_s + - type: marking - id: VoxHairCropped + id: VoxHairKeetQuills bodyPart: Hair markingCategory: Hair speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_hair.rsi - state: vox_cropped_s + state: keetquills_s + - type: marking - id: VoxHairRuffhawk + id: VoxHairKingly bodyPart: Hair markingCategory: Hair speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_hair.rsi - state: vox_ruff_hawk_s + state: kingly_s + - type: marking - id: VoxHairRows + id: VoxHairLongBraid bodyPart: Hair markingCategory: Hair speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_hair.rsi - state: vox_rows_s + state: afro_s + - type: marking id: VoxHairMange bodyPart: Hair @@ -93,7 +104,26 @@ speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_hair.rsi - state: vox_mange_s + state: mange_s + +- type: marking + id: VoxHairMohawk + bodyPart: Hair + markingCategory: Hair + speciesRestriction: [Vox] + sprites: + - sprite: Mobs/Customization/vox_hair.rsi + state: mohawk_s + +- type: marking + id: VoxHairNights + bodyPart: Hair + markingCategory: Hair + speciesRestriction: [Vox] + sprites: + - sprite: Mobs/Customization/vox_hair.rsi + state: nights_s + - type: marking id: VoxHairPony bodyPart: Hair @@ -101,4 +131,67 @@ speciesRestriction: [Vox] sprites: - sprite: Mobs/Customization/vox_hair.rsi - state: vox_pony_s + state: ponytail_s + +- type: marking + id: VoxHairRazorClipped + bodyPart: Hair + markingCategory: Hair + speciesRestriction: [Vox] + sprites: + - sprite: Mobs/Customization/vox_hair.rsi + state: razor_clipped_s + +- type: marking + id: VoxHairRazor + bodyPart: Hair + markingCategory: Hair + speciesRestriction: [Vox] + sprites: + - sprite: Mobs/Customization/vox_hair.rsi + state: razor_s + +- type: marking + id: VoxHairSortBraid + bodyPart: Hair + markingCategory: Hair + speciesRestriction: [Vox] + sprites: + - sprite: Mobs/Customization/vox_hair.rsi + state: short_braid_s + +- type: marking + id: VoxHairShortQuills + bodyPart: Hair + markingCategory: Hair + speciesRestriction: [Vox] + sprites: + - sprite: Mobs/Customization/vox_hair.rsi + state: shortquills_s + +- type: marking + id: VoxHairSurf + bodyPart: Hair + markingCategory: Hair + speciesRestriction: [Vox] + sprites: + - sprite: Mobs/Customization/vox_hair.rsi + state: surf_s + +- type: marking + id: VoxHairTielQuills + bodyPart: Hair + markingCategory: Hair + speciesRestriction: [Vox] + sprites: + - sprite: Mobs/Customization/vox_hair.rsi + state: tielquills_s + +- type: marking + id: VoxHairYasu + bodyPart: Hair + markingCategory: Hair + speciesRestriction: [Vox] + sprites: + - sprite: Mobs/Customization/vox_hair.rsi + state: yasu_s \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Mobs/Customization/Markings/vox_parts.yml b/Resources/Prototypes/Entities/Mobs/Customization/Markings/vox_parts.yml new file mode 100644 index 0000000000..96acd06882 --- /dev/null +++ b/Resources/Prototypes/Entities/Mobs/Customization/Markings/vox_parts.yml @@ -0,0 +1,145 @@ +- type: marking + id: VoxBeak + bodyPart: Snout + markingCategory: Snout + forcedColoring: true + speciesRestriction: [Vox] + sprites: + - sprite: Mobs/Customization/vox_parts.rsi + state: beak + coloring: + default: + type: + !type:SimpleColoring + color: "#937e3d" + +- type: marking + id: VoxLArmScales + bodyPart: LArm + markingCategory: LeftArm + forcedColoring: true + speciesRestriction: [Vox] + sprites: + - sprite: Mobs/Customization/vox_parts.rsi + state: l_arm + coloring: + default: + type: + !type:SimpleColoring + color: "#937e3d" + +- type: marking + id: VoxLLegScales + bodyPart: LLeg + markingCategory: LeftLeg + forcedColoring: true + speciesRestriction: [Vox] + sprites: + - sprite: Mobs/Customization/vox_parts.rsi + state: l_leg + coloring: + default: + type: + !type:SimpleColoring + color: "#937e3d" + +- type: marking + id: VoxRArmScales + bodyPart: RArm + markingCategory: RightArm + forcedColoring: true + speciesRestriction: [Vox] + sprites: + - sprite: Mobs/Customization/vox_parts.rsi + state: r_arm + coloring: + default: + type: + !type:SimpleColoring + color: "#937e3d" + +- type: marking + id: VoxRLegScales + bodyPart: RLeg + markingCategory: RightLeg + forcedColoring: true + speciesRestriction: [Vox] + sprites: + - sprite: Mobs/Customization/vox_parts.rsi + state: r_leg + coloring: + default: + type: + !type:SimpleColoring + color: "#937e3d" + +- type: marking + id: VoxRHandScales + bodyPart: RHand + markingCategory: RightHand + forcedColoring: true + speciesRestriction: [Vox] + sprites: + - sprite: Mobs/Customization/vox_parts.rsi + state: r_hand + coloring: + default: + type: + !type:SimpleColoring + color: "#937e3d" + +- type: marking + id: VoxLHandScales + bodyPart: LHand + markingCategory: LeftHand + forcedColoring: true + speciesRestriction: [Vox] + sprites: + - sprite: Mobs/Customization/vox_parts.rsi + state: l_hand + coloring: + default: + type: + !type:SimpleColoring + color: "#937e3d" + +- type: marking + id: VoxLFootScales + bodyPart: LFoot + markingCategory: LeftFoot + forcedColoring: true + speciesRestriction: [Vox] + sprites: + - sprite: Mobs/Customization/vox_parts.rsi + state: l_foot + coloring: + default: + type: + !type:SimpleColoring + color: "#937e3d" + +- type: marking + id: VoxRFootScales + bodyPart: RFoot + markingCategory: RightFoot + forcedColoring: true + speciesRestriction: [Vox] + sprites: + - sprite: Mobs/Customization/vox_parts.rsi + state: r_foot + coloring: + default: + type: + !type:SimpleColoring + color: "#937e3d" + +- type: marking + id: VoxTail + bodyPart: Tail + markingCategory: Tail + speciesRestriction: [Vox] + forcedColoring: true + sprites: + - sprite: Mobs/Customization/vox_parts.rsi + # Ideally this should use the normal tail sprite and apply an actual mask over it, not just use a butchered sprite + state: tail_stenciled diff --git a/Resources/Prototypes/Entities/Mobs/Customization/Markings/vox_tattoos.yml b/Resources/Prototypes/Entities/Mobs/Customization/Markings/vox_tattoos.yml new file mode 100644 index 0000000000..a802d284f3 --- /dev/null +++ b/Resources/Prototypes/Entities/Mobs/Customization/Markings/vox_tattoos.yml @@ -0,0 +1,55 @@ +- type: marking + id: TattooVoxHeartLeftArm + bodyPart: LArm + markingCategory: LeftArm + speciesRestriction: [Vox] + coloring: + default: + type: + !type:TattooColoring + fallbackColor: "#666666" + sprites: + - sprite: Mobs/Customization/vox_tattoos.rsi + state: heart_l_arm + +- type: marking + id: TattooVoxHeartRightArm + bodyPart: RArm + markingCategory: RightArm + speciesRestriction: [Vox] + coloring: + default: + type: + !type:TattooColoring + fallbackColor: "#666666" + sprites: + - sprite: Mobs/Customization/vox_tattoos.rsi + state: heart_r_arm + +- type: marking + id: TattooVoxHiveChest + bodyPart: Chest + markingCategory: Chest + speciesRestriction: [Vox] + coloring: + default: + type: + !type:TattooColoring + fallbackColor: "#666666" + sprites: + - sprite: Mobs/Customization/vox_tattoos.rsi + state: hive_s + +- type: marking + id: TattooVoxNightlingChest + bodyPart: Chest + markingCategory: Chest + speciesRestriction: [Vox] + coloring: + default: + type: + !type:TattooColoring + fallbackColor: "#666666" + sprites: + - sprite: Mobs/Customization/vox_tattoos.rsi + state: nightling_s diff --git a/Resources/Prototypes/Entities/Mobs/Customization/antenna.yml b/Resources/Prototypes/Entities/Mobs/Customization/antenna.yml new file mode 100644 index 0000000000..657e5406eb --- /dev/null +++ b/Resources/Prototypes/Entities/Mobs/Customization/antenna.yml @@ -0,0 +1,89 @@ +- type: marking + speciesRestriction: [IPC] + id: RobotAntennaTv + bodyPart: HeadTop + markingCategory: HeadTop + sprites: + - sprite: Mobs/Customization/ipc_antenna.rsi + state: ipc_antenna_tv + +- type: marking + speciesRestriction: [IPC] + id: RobotAntennaTesla + bodyPart: HeadTop + markingCategory: HeadTop + sprites: + - sprite: Mobs/Customization/ipc_antenna.rsi + state: ipc_antenna_tesla + +# - type: marking +# speciesRestriction: [IPC] +# id: RobotAntennaLightb +# bodyPart: HeadTop +# markingCategory: HeadTop +# sprites: +# - sprite: Mobs/Customization/ipc_antenna.rsi +# state: ipc_antenna_lightb + +# - type: marking +# speciesRestriction: [IPC] +# id: RobotAntennaLight +# bodyPart: HeadTop +# markingCategory: HeadTop +# sprites: +# - sprite: Mobs/Customization/ipc_antenna.rsi +# state: ipc_antenna_light + +- type: marking + speciesRestriction: [IPC] + id: RobotAntennaCyberhead + bodyPart: HeadTop + markingCategory: HeadTop + sprites: + - sprite: Mobs/Customization/ipc_antenna.rsi + state: ipc_antenna_cyberhead + +# - type: marking +# speciesRestriction: [IPC] +# id: RobotAntennaSidelights +# bodyPart: HeadTop +# markingCategory: HeadTop +# sprites: +# - sprite: Mobs/Customization/ipc_antenna.rsi +# state: ipc_antenna_sidelights + +- type: marking + speciesRestriction: [IPC] + id: RobotAntennaAntlers + bodyPart: HeadTop + markingCategory: HeadTop + sprites: + - sprite: Mobs/Customization/ipc_antenna.rsi + state: ipc_antenna_antlers + +- type: marking + speciesRestriction: [IPC] + id: RobotAntennaDroneeyes + bodyPart: HeadTop + markingCategory: HeadTop + sprites: + - sprite: Mobs/Customization/ipc_antenna.rsi + state: ipc_antenna_droneeyes + +- type: marking + speciesRestriction: [IPC] + id: RobotAntennaCrowned + bodyPart: HeadTop + markingCategory: HeadTop + sprites: + - sprite: Mobs/Customization/ipc_antenna.rsi + state: ipc_antenna_crowned + +- type: marking + speciesRestriction: [IPC] + id: RobotAntennaTowers + bodyPart: HeadTop + markingCategory: HeadTop + sprites: + - sprite: Mobs/Customization/ipc_antenna.rsi + state: ipc_antenna_towers diff --git a/Resources/Prototypes/Entities/Mobs/Customization/cyberlimbs/bishop.yml b/Resources/Prototypes/Entities/Mobs/Customization/cyberlimbs/bishop.yml index 11f4967616..cdbb4ecfed 100644 --- a/Resources/Prototypes/Entities/Mobs/Customization/cyberlimbs/bishop.yml +++ b/Resources/Prototypes/Entities/Mobs/Customization/cyberlimbs/bishop.yml @@ -9,6 +9,24 @@ - sprite: Mobs/Customization/cyberlimbs/bishop/bishop_monitor.rsi state: head-2 +- type: marking + id: CyberLimbsMarkingBishopHeadAlt + bodyPart: Head + markingCategory: Head + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Customization/cyberlimbs/bishop/bishop_main.rsi + state: head + +- type: marking + id: CyberLimbsMarkingBishopHeadAlt1 + bodyPart: Head + markingCategory: Head + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Customization/cyberlimbs/bishop/bishop_alt1.rsi + state: head + - type: marking id: CyberLimbsMarkingBishopChest bodyPart: Chest @@ -23,7 +41,7 @@ - type: marking id: CyberLimbsMarkingBishopLArm bodyPart: LArm - markingCategory: Arms + markingCategory: LeftArm speciesRestriction: [IPC, Moth, Dwarf, Human, Arachnid, Felinid, Oni, Vulpkanin, HumanoidFoxes, Reptilian] sprites: - sprite: Mobs/Customization/cyberlimbs/bishop/bishop_main.rsi @@ -36,7 +54,7 @@ - type: marking id: CyberLimbsMarkingBishopLHand bodyPart: LHand - markingCategory: Arms + markingCategory: LeftHand speciesRestriction: [IPC, Moth, Dwarf, Human, Arachnid, Felinid, Oni, Vulpkanin, HumanoidFoxes, Reptilian] sprites: - sprite: Mobs/Customization/cyberlimbs/bishop/bishop_main.rsi @@ -45,7 +63,7 @@ - type: marking id: CyberLimbsMarkingBishopLLeg bodyPart: LLeg - markingCategory: Legs + markingCategory: LeftLeg speciesRestriction: [IPC, Moth, Dwarf, Human, Arachnid, Felinid, Oni, Vulpkanin, HumanoidFoxes, Reptilian] sprites: - sprite: Mobs/Customization/cyberlimbs/bishop/bishop_main.rsi @@ -57,7 +75,7 @@ - type: marking id: CyberLimbsMarkingBishopLFoot bodyPart: LFoot - markingCategory: Legs + markingCategory: LeftFoot speciesRestriction: [IPC, Moth, Dwarf, Human, Arachnid, Felinid, Oni, Vulpkanin, HumanoidFoxes, Reptilian] sprites: - sprite: Mobs/Customization/cyberlimbs/bishop/bishop_main.rsi @@ -68,7 +86,7 @@ - type: marking id: CyberLimbsMarkingBishopRArm bodyPart: RArm - markingCategory: Arms + markingCategory: RightArm speciesRestriction: [IPC, Moth, Dwarf, Human, Arachnid, Felinid, Oni, Vulpkanin, HumanoidFoxes, Reptilian] sprites: - sprite: Mobs/Customization/cyberlimbs/bishop/bishop_main.rsi @@ -82,7 +100,7 @@ - type: marking id: CyberLimbsMarkingBishopRHand bodyPart: RHand - markingCategory: Arms + markingCategory: RightHand speciesRestriction: [IPC, Moth, Dwarf, Human, Arachnid, Felinid, Oni, Vulpkanin, HumanoidFoxes, Reptilian] sprites: - sprite: Mobs/Customization/cyberlimbs/bishop/bishop_main.rsi @@ -91,7 +109,7 @@ - type: marking id: CyberLimbsMarkingBishopRLeg bodyPart: RLeg - markingCategory: Legs + markingCategory: RightLeg speciesRestriction: [IPC, Moth, Dwarf, Human, Arachnid, Felinid, Oni, Vulpkanin, HumanoidFoxes, Reptilian] sprites: - sprite: Mobs/Customization/cyberlimbs/bishop/bishop_main.rsi @@ -103,8 +121,8 @@ - type: marking id: CyberLimbsMarkingBishopRFoot bodyPart: RFoot - markingCategory: Legs + markingCategory: RightFoot speciesRestriction: [IPC, Moth, Dwarf, Human, Arachnid, Felinid, Oni, Vulpkanin, HumanoidFoxes, Reptilian] sprites: - sprite: Mobs/Customization/cyberlimbs/bishop/bishop_main.rsi - state: r_foot + state: r_foot \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Mobs/Customization/cyberlimbs/hesphiastos.yml b/Resources/Prototypes/Entities/Mobs/Customization/cyberlimbs/hesphiastos.yml index 3103c64003..e3bcd07f3a 100644 --- a/Resources/Prototypes/Entities/Mobs/Customization/cyberlimbs/hesphiastos.yml +++ b/Resources/Prototypes/Entities/Mobs/Customization/cyberlimbs/hesphiastos.yml @@ -9,6 +9,19 @@ - sprite: Mobs/Customization/cyberlimbs/hesphiastos/hesphiastos_monitor.rsi state: head-2 +- type: marking + id: CyberLimbsMarkingHesphiastosHeadAlt + bodyPart: Head + markingCategory: Head + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Customization/cyberlimbs/hesphiastos/hesphiastos_alt1.rsi + state: head-1 + - sprite: Mobs/Customization/cyberlimbs/hesphiastos/hesphiastos_alt1.rsi + state: head-2 + - sprite: Mobs/Customization/cyberlimbs/hesphiastos/hesphiastos_alt1.rsi + state: head-3 + - type: marking id: CyberLimbsMarkingHesphiastosChest bodyPart: Chest @@ -23,7 +36,7 @@ - type: marking id: CyberLimbsMarkingHesphiastosLArm bodyPart: LArm - markingCategory: Arms + markingCategory: LeftArm speciesRestriction: [IPC, Moth, Dwarf, Human, Arachnid, Felinid, Oni, Vulpkanin, HumanoidFoxes, Reptilian] sprites: - sprite: Mobs/Customization/cyberlimbs/hesphiastos/hesphiastos_main.rsi @@ -34,7 +47,7 @@ - type: marking id: CyberLimbsMarkingHesphiastosLHand bodyPart: LHand - markingCategory: Arms + markingCategory: LeftHand speciesRestriction: [IPC, Moth, Dwarf, Human, Arachnid, Felinid, Oni, Vulpkanin, HumanoidFoxes, Reptilian] sprites: - sprite: Mobs/Customization/cyberlimbs/hesphiastos/hesphiastos_main.rsi @@ -45,7 +58,7 @@ - type: marking id: CyberLimbsMarkingHesphiastosLLeg bodyPart: LLeg - markingCategory: Legs + markingCategory: LeftLeg speciesRestriction: [IPC, Moth, Dwarf, Human, Arachnid, Felinid, Oni, Vulpkanin, HumanoidFoxes, Reptilian] sprites: - sprite: Mobs/Customization/cyberlimbs/hesphiastos/hesphiastos_main.rsi @@ -57,7 +70,7 @@ - type: marking id: CyberLimbsMarkingHesphiastosLFoot bodyPart: LFoot - markingCategory: Legs + markingCategory: LeftFoot speciesRestriction: [IPC, Moth, Dwarf, Human, Arachnid, Felinid, Oni, Vulpkanin, HumanoidFoxes, Reptilian] sprites: - sprite: Mobs/Customization/cyberlimbs/hesphiastos/hesphiastos_main.rsi @@ -70,7 +83,7 @@ - type: marking id: CyberLimbsMarkingHesphiastosRArm bodyPart: RArm - markingCategory: Arms + markingCategory: RightArm speciesRestriction: [IPC, Moth, Dwarf, Human, Arachnid, Felinid, Oni, Vulpkanin, HumanoidFoxes, Reptilian] sprites: - sprite: Mobs/Customization/cyberlimbs/hesphiastos/hesphiastos_main.rsi @@ -82,7 +95,7 @@ - type: marking id: CyberLimbsMarkingHesphiastosRHand bodyPart: RHand - markingCategory: Arms + markingCategory: RightHand speciesRestriction: [IPC, Moth, Dwarf, Human, Arachnid, Felinid, Oni, Vulpkanin, HumanoidFoxes, Reptilian] sprites: - sprite: Mobs/Customization/cyberlimbs/hesphiastos/hesphiastos_main.rsi @@ -94,7 +107,7 @@ - type: marking id: CyberLimbsMarkingHesphiastosRLeg bodyPart: RLeg - markingCategory: Legs + markingCategory: RightLeg speciesRestriction: [IPC, Moth, Dwarf, Human, Arachnid, Felinid, Oni, Vulpkanin, HumanoidFoxes, Reptilian] sprites: - sprite: Mobs/Customization/cyberlimbs/hesphiastos/hesphiastos_main.rsi @@ -106,10 +119,10 @@ - type: marking id: CyberLimbsMarkingHesphiastosRFoot bodyPart: RFoot - markingCategory: Legs + markingCategory: RightFoot speciesRestriction: [IPC, Moth, Dwarf, Human, Arachnid, Felinid, Oni, Vulpkanin, HumanoidFoxes, Reptilian] sprites: - sprite: Mobs/Customization/cyberlimbs/hesphiastos/hesphiastos_main.rsi state: r_foot-1 - sprite: Mobs/Customization/cyberlimbs/hesphiastos/hesphiastos_main.rsi - state: r_foot-2 + state: r_foot-2 \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Mobs/Customization/cyberlimbs/morpheus.yml b/Resources/Prototypes/Entities/Mobs/Customization/cyberlimbs/morpheus.yml new file mode 100644 index 0000000000..4456c288df --- /dev/null +++ b/Resources/Prototypes/Entities/Mobs/Customization/cyberlimbs/morpheus.yml @@ -0,0 +1,158 @@ +- type: marking + id: CyberLimbsMarkingMorpheusHead + bodyPart: Head + markingCategory: Head + speciesRestriction: [IPC] + coloring: + default: + type: + !type:SimpleColoring + color: "#FFFFFF" + sprites: + - sprite: Mobs/Customization/cyberlimbs/morpheus/morpheus_main.rsi + state: head + +- type: marking + id: CyberLimbsMarkingMorpheusHeadAlt + bodyPart: Head + markingCategory: Head + speciesRestriction: [IPC] + coloring: + default: + type: + !type:SimpleColoring + color: "#FFFFFF" + sprites: + - sprite: Mobs/Customization/cyberlimbs/morpheus/morpheus_alt1.rsi + state: head + +- type: marking + id: CyberLimbsMarkingMorpheusChest + bodyPart: Chest + markingCategory: Chest + speciesRestriction: [IPC] + coloring: + default: + type: + !type:SimpleColoring + color: "#FFFFFF" + sprites: + - sprite: Mobs/Customization/cyberlimbs/morpheus/morpheus_main.rsi + state: torso + +- type: marking + id: CyberLimbsMarkingMorpheusLArm + bodyPart: LArm + markingCategory: LeftArm + speciesRestriction: [IPC] + coloring: + default: + type: + !type:SimpleColoring + color: "#FFFFFF" + sprites: + - sprite: Mobs/Customization/cyberlimbs/morpheus/morpheus_main.rsi + state: l_arm + +- type: marking + id: CyberLimbsMarkingMorpheusLHand + bodyPart: LHand + markingCategory: LeftHand + speciesRestriction: [IPC] + coloring: + default: + type: + !type:SimpleColoring + color: "#FFFFFF" + sprites: + - sprite: Mobs/Customization/cyberlimbs/morpheus/morpheus_main.rsi + state: l_hand + +- type: marking + id: CyberLimbsMarkingMorpheusLLeg + bodyPart: LLeg + markingCategory: LeftLeg + speciesRestriction: [IPC] + coloring: + default: + type: + !type:SimpleColoring + color: "#FFFFFF" + sprites: + - sprite: Mobs/Customization/cyberlimbs/morpheus/morpheus_main.rsi + state: l_leg + + +- type: marking + id: CyberLimbsMarkingMorpheusLFoot + bodyPart: LFoot + markingCategory: LeftFoot + speciesRestriction: [IPC] + coloring: + default: + type: + !type:SimpleColoring + color: "#FFFFFF" + sprites: + - sprite: Mobs/Customization/cyberlimbs/morpheus/morpheus_main.rsi + state: l_foot + + + +- type: marking + id: CyberLimbsMarkingMorpheusRArm + bodyPart: RArm + markingCategory: RightArm + speciesRestriction: [IPC] + coloring: + default: + type: + !type:SimpleColoring + color: "#FFFFFF" + sprites: + - sprite: Mobs/Customization/cyberlimbs/morpheus/morpheus_main.rsi + state: r_arm + + +- type: marking + id: CyberLimbsMarkingMorpheusRHand + bodyPart: RHand + markingCategory: RightHand + speciesRestriction: [IPC] + coloring: + default: + type: + !type:SimpleColoring + color: "#FFFFFF" + sprites: + - sprite: Mobs/Customization/cyberlimbs/morpheus/morpheus_main.rsi + state: r_hand + +- type: marking + id: CyberLimbsMarkingMorpheusRLeg + bodyPart: RLeg + markingCategory: RightLeg + speciesRestriction: [IPC] + coloring: + default: + type: + !type:SimpleColoring + color: "#FFFFFF" + sprites: + - sprite: Mobs/Customization/cyberlimbs/morpheus/morpheus_main.rsi + state: r_leg + + +- type: marking + id: CyberLimbsMarkingMorpheusRFoot + bodyPart: RFoot + markingCategory: RightFoot + speciesRestriction: [IPC] + coloring: + default: + type: + !type:SimpleColoring + color: "#FFFFFF" + sprites: + - sprite: Mobs/Customization/cyberlimbs/morpheus/morpheus_main.rsi + state: r_foot \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Mobs/Customization/cyberlimbs/shellguard.yml b/Resources/Prototypes/Entities/Mobs/Customization/cyberlimbs/shellguard.yml new file mode 100644 index 0000000000..81f4396d06 --- /dev/null +++ b/Resources/Prototypes/Entities/Mobs/Customization/cyberlimbs/shellguard.yml @@ -0,0 +1,125 @@ +- type: marking + id: CyberLimbsMarkingShellguardHead + bodyPart: Head + markingCategory: Head + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Customization/cyberlimbs/shellguard/shellguard_monitor.rsi + state: head-1 + - sprite: Mobs/Customization/cyberlimbs/shellguard/shellguard_monitor.rsi + state: head-2 + +- type: marking + id: CyberLimbsMarkingShellguardHeadAlt + bodyPart: Head + markingCategory: Head + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Customization/cyberlimbs/shellguard/shellguard_alt1.rsi + state: head-1 + - sprite: Mobs/Customization/cyberlimbs/shellguard/shellguard_alt1.rsi + state: head-2 + +- type: marking + id: CyberLimbsMarkingShellguardChest + bodyPart: Chest + markingCategory: Chest + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi + state: torso-1 + - sprite: Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi + state: torso-2 + +- type: marking + id: CyberLimbsMarkingShellguardLArm + bodyPart: LArm + markingCategory: LeftArm + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi + state: l_arm-1 + - sprite: Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi + state: l_arm-2 + +- type: marking + id: CyberLimbsMarkingShellguardLHand + bodyPart: LHand + markingCategory: LeftHand + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi + state: l_hand-1 + - sprite: Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi + state: l_hand-2 + +- type: marking + id: CyberLimbsMarkingShellguardLLeg + bodyPart: LLeg + markingCategory: LeftLeg + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi + state: l_leg-1 + - sprite: Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi + state: l_leg-2 + + +- type: marking + id: CyberLimbsMarkingShellguardLFoot + bodyPart: LFoot + markingCategory: LeftFoot + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi + state: l_foot-1 + - sprite: Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi + state: l_foot-2 + + + +- type: marking + id: CyberLimbsMarkingShellguardRArm + bodyPart: RArm + markingCategory: RightArm + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi + state: r_arm-1 + - sprite: Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi + state: r_arm-2 + + +- type: marking + id: CyberLimbsMarkingShellguardRHand + bodyPart: RHand + markingCategory: RightHand + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi + state: r_hand-1 + - sprite: Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi + state: r_hand-2 + +- type: marking + id: CyberLimbsMarkingShellguardRLeg + bodyPart: RLeg + markingCategory: RightLeg + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi + state: r_leg-1 + - sprite: Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi + state: r_leg-2 + + +- type: marking + id: CyberLimbsMarkingShellguardRFoot + bodyPart: RFoot + markingCategory: RightFoot + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi + state: r_foot-1 + - sprite: Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi + state: r_foot-2 \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Mobs/Customization/cyberlimbs/wardtakahashi.yml b/Resources/Prototypes/Entities/Mobs/Customization/cyberlimbs/wardtakahashi.yml new file mode 100644 index 0000000000..f331309ca9 --- /dev/null +++ b/Resources/Prototypes/Entities/Mobs/Customization/cyberlimbs/wardtakahashi.yml @@ -0,0 +1,112 @@ +- type: marking + id: CyberLimbsMarkingWardtakahashiHead + bodyPart: Head + markingCategory: Head + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_monitor.rsi + state: head + +- type: marking + id: CyberLimbsMarkingWardtakahashiHeadAlt + bodyPart: Head + markingCategory: Head + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_main.rsi + state: head + +- type: marking + id: CyberLimbsMarkingWardtakahashiHeadAlt1 + bodyPart: Head + markingCategory: Head + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_alt1.rsi + state: head + +- type: marking + id: CyberLimbsMarkingWardtakahashiChest + bodyPart: Chest + markingCategory: Chest + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_main.rsi + state: torso + +- type: marking + id: CyberLimbsMarkingWardtakahashiLArm + bodyPart: LArm + markingCategory: LeftArm + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_main.rsi + state: l_arm + +- type: marking + id: CyberLimbsMarkingWardtakahashiLHand + bodyPart: LHand + markingCategory: LeftHand + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_main.rsi + state: l_hand + +- type: marking + id: CyberLimbsMarkingWardtakahashiLLeg + bodyPart: LLeg + markingCategory: LeftLeg + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_main.rsi + state: l_leg + + +- type: marking + id: CyberLimbsMarkingWardtakahashiLFoot + bodyPart: LFoot + markingCategory: LeftFoot + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_main.rsi + state: l_foot + + + +- type: marking + id: CyberLimbsMarkingWardtakahashiRArm + bodyPart: RArm + markingCategory: RightArm + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_main.rsi + state: r_arm + + +- type: marking + id: CyberLimbsMarkingWardtakahashiRHand + bodyPart: RHand + markingCategory: RightHand + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_main.rsi + state: r_hand + +- type: marking + id: CyberLimbsMarkingWardtakahashiRLeg + bodyPart: RLeg + markingCategory: RightLeg + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_main.rsi + state: r_leg + + +- type: marking + id: CyberLimbsMarkingWardtakahashiRFoot + bodyPart: RFoot + markingCategory: RightFoot + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_main.rsi + state: r_foot \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Mobs/Customization/cyberlimbs/xion.yml b/Resources/Prototypes/Entities/Mobs/Customization/cyberlimbs/xion.yml new file mode 100644 index 0000000000..84b630b788 --- /dev/null +++ b/Resources/Prototypes/Entities/Mobs/Customization/cyberlimbs/xion.yml @@ -0,0 +1,126 @@ +- type: marking + id: CyberLimbsMarkingXionHead + bodyPart: Head + markingCategory: Head + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Customization/cyberlimbs/xion/xion_monitor.rsi + state: head-1 + - sprite: Mobs/Customization/cyberlimbs/xion/xion_monitor.rsi + state: head-2 + +- type: marking + id: CyberLimbsMarkingXionHeadAlt + bodyPart: Head + markingCategory: Head + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Customization/cyberlimbs/xion/xion_alt1.rsi + state: head-1 + - sprite: Mobs/Customization/cyberlimbs/xion/xion_alt1.rsi + state: head-2 + +- type: marking + id: CyberLimbsMarkingXionChest + bodyPart: Chest + markingCategory: Chest + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Customization/cyberlimbs/xion/xion_main.rsi + state: torso-1 + - sprite: Mobs/Customization/cyberlimbs/xion/xion_main.rsi + state: torso-2 + +- type: marking + id: CyberLimbsMarkingXionLArm + bodyPart: LArm + markingCategory: LeftArm + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Customization/cyberlimbs/xion/xion_main.rsi + state: l_arm-1 + - sprite: Mobs/Customization/cyberlimbs/xion/xion_main.rsi + state: l_arm-2 + +- type: marking + id: CyberLimbsMarkingXionLHand + bodyPart: LHand + markingCategory: LeftHand + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Customization/cyberlimbs/xion/xion_main.rsi + state: l_hand-1 + - sprite: Mobs/Customization/cyberlimbs/xion/xion_main.rsi + state: l_hand-2 + +- type: marking + id: CyberLimbsMarkingXionLLeg + bodyPart: LLeg + markingCategory: LeftLeg + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Customization/cyberlimbs/xion/xion_main.rsi + state: l_leg-1 + - sprite: Mobs/Customization/cyberlimbs/xion/xion_main.rsi + state: l_leg-2 + + +- type: marking + id: CyberLimbsMarkingXionLFoot + bodyPart: LFoot + markingCategory: LeftFoot + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Customization/cyberlimbs/xion/xion_main.rsi + state: l_foot-1 + - sprite: Mobs/Customization/cyberlimbs/xion/xion_main.rsi + state: l_foot-2 + + + +- type: marking + id: CyberLimbsMarkingXionRArm + bodyPart: RArm + markingCategory: RightArm + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Customization/cyberlimbs/xion/xion_main.rsi + state: r_arm-1 + - sprite: Mobs/Customization/cyberlimbs/xion/xion_main.rsi + state: r_arm-2 + + + +- type: marking + id: CyberLimbsMarkingXionRHand + bodyPart: RHand + markingCategory: RightHand + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Customization/cyberlimbs/xion/xion_main.rsi + state: r_hand-1 + - sprite: Mobs/Customization/cyberlimbs/xion/xion_main.rsi + state: r_hand-2 + +- type: marking + id: CyberLimbsMarkingXionRLeg + bodyPart: RLeg + markingCategory: RightLeg + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Customization/cyberlimbs/xion/xion_main.rsi + state: r_leg-1 + - sprite: Mobs/Customization/cyberlimbs/xion/xion_main.rsi + state: r_leg-2 + + +- type: marking + id: CyberLimbsMarkingXionRFoot + bodyPart: RFoot + markingCategory: RightFoot + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Customization/cyberlimbs/xion/xion_main.rsi + state: r_foot-1 + - sprite: Mobs/Customization/cyberlimbs/xion/xion_main.rsi + state: r_foot-2 \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Mobs/Customization/cyberlimbs/zenghu.yml b/Resources/Prototypes/Entities/Mobs/Customization/cyberlimbs/zenghu.yml new file mode 100644 index 0000000000..9b6811b084 --- /dev/null +++ b/Resources/Prototypes/Entities/Mobs/Customization/cyberlimbs/zenghu.yml @@ -0,0 +1,115 @@ +- type: marking + id: CyberLimbsMarkingZenghuHead + bodyPart: Head + markingCategory: Head + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi + state: head-1 + - sprite: Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi + state: head-2 + +- type: marking + id: CyberLimbsMarkingZenghuChest + bodyPart: Chest + markingCategory: Chest + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi + state: torso-1 + - sprite: Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi + state: torso-2 + +- type: marking + id: CyberLimbsMarkingZenghuLArm + bodyPart: LArm + markingCategory: LeftArm + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi + state: l_arm-1 + - sprite: Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi + state: l_arm-2 + +- type: marking + id: CyberLimbsMarkingZenghuLHand + bodyPart: LHand + markingCategory: LeftHand + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi + state: l_hand-1 + - sprite: Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi + state: l_hand-2 + +- type: marking + id: CyberLimbsMarkingZenghuLLeg + bodyPart: LLeg + markingCategory: LeftLeg + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi + state: l_leg-1 + - sprite: Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi + state: l_leg-2 + + +- type: marking + id: CyberLimbsMarkingZenghuLFoot + bodyPart: LFoot + markingCategory: LeftFoot + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi + state: l_foot-1 + - sprite: Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi + state: l_foot-2 + + + +- type: marking + id: CyberLimbsMarkingZenghuRArm + bodyPart: RArm + markingCategory: RightArm + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi + state: r_arm-1 + - sprite: Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi + state: r_arm-2 + + + +- type: marking + id: CyberLimbsMarkingZenghuRHand + bodyPart: RHand + markingCategory: RightHand + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi + state: r_hand-1 + - sprite: Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi + state: r_hand-2 + +- type: marking + id: CyberLimbsMarkingZenghuRLeg + bodyPart: RLeg + markingCategory: RightLeg + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi + state: r_leg-1 + - sprite: Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi + state: r_leg-2 + + +- type: marking + id: CyberLimbsMarkingZenghuRFoot + bodyPart: RFoot + markingCategory: RightFoot + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi + state: r_foot-1 + - sprite: Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi + state: r_foot-2 \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Mobs/Customization/screens.yml b/Resources/Prototypes/Entities/Mobs/Customization/screens.yml new file mode 100644 index 0000000000..da8092f5dd --- /dev/null +++ b/Resources/Prototypes/Entities/Mobs/Customization/screens.yml @@ -0,0 +1,391 @@ +## CANT SET THESE SCREENS TO "shader: unshaded" +## RobustToolbox/Robust.Shared/Utility/SpriteSpecifier.cs + +- type: marking + speciesRestriction: [IPC] + id: ScreenStatic + bodyPart: HeadSide + markingCategory: HeadSide + sprites: + - sprite: Mobs/Customization/ipc_screens.rsi + state: ipc_screen_static + + +- type: marking + speciesRestriction: [IPC] + id: ScreenBlue + bodyPart: HeadSide + markingCategory: HeadSide + sprites: + - sprite: Mobs/Customization/ipc_screens.rsi + state: ipc_screen_blue + + +- type: marking + speciesRestriction: [IPC] + id: ScreenBreakout + bodyPart: HeadSide + markingCategory: HeadSide + sprites: + - sprite: Mobs/Customization/ipc_screens.rsi + state: ipc_screen_breakout + + +- type: marking + speciesRestriction: [IPC] + id: ScreenEight + bodyPart: HeadSide + markingCategory: HeadSide + sprites: + - sprite: Mobs/Customization/ipc_screens.rsi + state: ipc_screen_eight + + +- type: marking + speciesRestriction: [IPC] + id: ScreenGoggles + bodyPart: HeadSide + markingCategory: HeadSide + sprites: + - sprite: Mobs/Customization/ipc_screens.rsi + state: ipc_screen_goggles + + +- type: marking + speciesRestriction: [IPC] + id: ScreenExclaim + bodyPart: HeadSide + markingCategory: HeadSide + sprites: + - sprite: Mobs/Customization/ipc_screens.rsi + state: ipc_screen_exclaim + + +- type: marking + speciesRestriction: [IPC] + id: ScreenHeart + bodyPart: HeadSide + markingCategory: HeadSide + sprites: + - sprite: Mobs/Customization/ipc_screens.rsi + state: ipc_screen_heart + + +- type: marking + speciesRestriction: [IPC] + id: ScreenMonoeye + bodyPart: HeadSide + markingCategory: HeadSide + sprites: + - sprite: Mobs/Customization/ipc_screens.rsi + state: ipc_screen_monoeye + + +- type: marking + speciesRestriction: [IPC] + id: ScreenNature + bodyPart: HeadSide + markingCategory: HeadSide + sprites: + - sprite: Mobs/Customization/ipc_screens.rsi + state: ipc_screen_nature + + +- type: marking + speciesRestriction: [IPC] + id: ScreenOrange + bodyPart: HeadSide + markingCategory: HeadSide + sprites: + - sprite: Mobs/Customization/ipc_screens.rsi + state: ipc_screen_orange + + +- type: marking + speciesRestriction: [IPC] + id: ScreenPink + bodyPart: HeadSide + markingCategory: HeadSide + sprites: + - sprite: Mobs/Customization/ipc_screens.rsi + state: ipc_screen_pink + + +- type: marking + speciesRestriction: [IPC] + id: ScreenQuestion + bodyPart: HeadSide + markingCategory: HeadSide + sprites: + - sprite: Mobs/Customization/ipc_screens.rsi + state: ipc_screen_question + + +- type: marking + speciesRestriction: [IPC] + id: ScreenShower + bodyPart: HeadSide + markingCategory: HeadSide + sprites: + - sprite: Mobs/Customization/ipc_screens.rsi + state: ipc_screen_shower + + +- type: marking + speciesRestriction: [IPC] + id: ScreenYellow + bodyPart: HeadSide + markingCategory: HeadSide + sprites: + - sprite: Mobs/Customization/ipc_screens.rsi + state: ipc_screen_yellow + + +- type: marking + speciesRestriction: [IPC] + id: ScreenScroll + bodyPart: HeadSide + markingCategory: HeadSide + sprites: + - sprite: Mobs/Customization/ipc_screens.rsi + state: ipc_screen_scroll + + +- type: marking + speciesRestriction: [IPC] + id: ScreenConsole + bodyPart: HeadSide + markingCategory: HeadSide + sprites: + - sprite: Mobs/Customization/ipc_screens.rsi + state: ipc_screen_console + + +- type: marking + speciesRestriction: [IPC] + id: ScreenRgb + bodyPart: HeadSide + markingCategory: HeadSide + sprites: + - sprite: Mobs/Customization/ipc_screens.rsi + state: ipc_screen_rgb + + +- type: marking + speciesRestriction: [IPC] + id: ScreenGlider + bodyPart: HeadSide + markingCategory: HeadSide + sprites: + - sprite: Mobs/Customization/ipc_screens.rsi + state: ipc_screen_glider + + +- type: marking + speciesRestriction: [IPC] + id: ScreenRainbowhoriz + bodyPart: HeadSide + markingCategory: HeadSide + sprites: + - sprite: Mobs/Customization/ipc_screens.rsi + state: ipc_screen_rainbowhoriz + + +- type: marking + speciesRestriction: [IPC] + id: ScreenBsod + bodyPart: HeadSide + markingCategory: HeadSide + sprites: + - sprite: Mobs/Customization/ipc_screens.rsi + state: ipc_screen_bsod + + +- type: marking + speciesRestriction: [IPC] + id: ScreenRedtext + bodyPart: HeadSide + markingCategory: HeadSide + sprites: + - sprite: Mobs/Customization/ipc_screens.rsi + state: ipc_screen_redtext + + +- type: marking + speciesRestriction: [IPC] + id: ScreenSinewave + bodyPart: HeadSide + markingCategory: HeadSide + sprites: + - sprite: Mobs/Customization/ipc_screens.rsi + state: ipc_screen_sinewave + + +- type: marking + speciesRestriction: [IPC] + id: ScreenSquarewave + bodyPart: HeadSide + markingCategory: HeadSide + sprites: + - sprite: Mobs/Customization/ipc_screens.rsi + state: ipc_screen_squarewave + + +- type: marking + speciesRestriction: [IPC] + id: ScreenEcgwave + bodyPart: HeadSide + markingCategory: HeadSide + sprites: + - sprite: Mobs/Customization/ipc_screens.rsi + state: ipc_screen_ecgwave + + +- type: marking + speciesRestriction: [IPC] + id: ScreenEyes + bodyPart: HeadSide + markingCategory: HeadSide + sprites: + - sprite: Mobs/Customization/ipc_screens.rsi + state: ipc_screen_eyes + + +- type: marking + speciesRestriction: [IPC] + id: ScreenEyestall + bodyPart: HeadSide + markingCategory: HeadSide + sprites: + - sprite: Mobs/Customization/ipc_screens.rsi + state: ipc_screen_eyestall + + +- type: marking + speciesRestriction: [IPC] + id: ScreenEyesangry + bodyPart: HeadSide + markingCategory: HeadSide + sprites: + - sprite: Mobs/Customization/ipc_screens.rsi + state: ipc_screen_eyesangry + + +- type: marking + speciesRestriction: [IPC] + id: ScreenLoading + bodyPart: HeadSide + markingCategory: HeadSide + sprites: + - sprite: Mobs/Customization/ipc_screens.rsi + state: ipc_screen_loading + + +- type: marking + speciesRestriction: [IPC] + id: ScreenWindowsxp + bodyPart: HeadSide + markingCategory: HeadSide + sprites: + - sprite: Mobs/Customization/ipc_screens.rsi + state: ipc_screen_windowsxp + + +- type: marking + speciesRestriction: [IPC] + id: ScreenTetris + bodyPart: HeadSide + markingCategory: HeadSide + sprites: + - sprite: Mobs/Customization/ipc_screens.rsi + state: ipc_screen_tetris + + +- type: marking + speciesRestriction: [IPC] + id: ScreenTv + bodyPart: HeadSide + markingCategory: HeadSide + sprites: + - sprite: Mobs/Customization/ipc_screens.rsi + state: ipc_screen_tv + + +- type: marking + speciesRestriction: [IPC] + id: ScreenTextdrop + bodyPart: HeadSide + markingCategory: HeadSide + sprites: + - sprite: Mobs/Customization/ipc_screens.rsi + state: ipc_screen_textdrop + + +- type: marking + speciesRestriction: [IPC] + id: ScreenStars + bodyPart: HeadSide + markingCategory: HeadSide + sprites: + - sprite: Mobs/Customization/ipc_screens.rsi + state: ipc_screen_stars + + +- type: marking + speciesRestriction: [IPC] + id: ScreenRainbowdiag + bodyPart: HeadSide + markingCategory: HeadSide + sprites: + - sprite: Mobs/Customization/ipc_screens.rsi + state: ipc_screen_rainbowdiag + + +- type: marking + speciesRestriction: [IPC] + id: ScreenBlank + bodyPart: HeadSide + markingCategory: HeadSide + sprites: + - sprite: Mobs/Customization/ipc_screens.rsi + state: ipc_screen_blank + + +- type: marking + speciesRestriction: [IPC] + id: ScreenSmile + bodyPart: HeadSide + markingCategory: HeadSide + sprites: + - sprite: Mobs/Customization/ipc_screens.rsi + state: ipc_screen_smile + +- type: marking + speciesRestriction: [IPC] + id: ScreenFrown + bodyPart: HeadSide + markingCategory: HeadSide + sprites: + - sprite: Mobs/Customization/ipc_screens.rsi + state: ipc_screen_frown + + +- type: marking + speciesRestriction: [IPC] + id: ScreenRing + bodyPart: HeadSide + markingCategory: HeadSide + sprites: + - sprite: Mobs/Customization/ipc_screens.rsi + state: ipc_screen_ring + + +- type: marking + speciesRestriction: [IPC] + id: ScreenL + bodyPart: HeadSide + markingCategory: HeadSide + sprites: + - sprite: Mobs/Customization/ipc_screens.rsi + state: ipc_screen_l + diff --git a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml index 9ff9837a3b..7974b06870 100644 --- a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml +++ b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml @@ -63,12 +63,12 @@ locPrefix: silicon - type: UserInterface interfaces: - - key: enum.SiliconLawsUiKey.Key - type: SiliconLawBoundUserInterface - - key: enum.BorgUiKey.Key - type: BorgBoundUserInterface - - key: enum.StrippingUiKey.Key - type: StrippableBoundUserInterface + enum.SiliconLawsUiKey.Key: + type: SiliconLawBoundUserInterface + enum.BorgUiKey.Key: + type: BorgBoundUserInterface + enum.StrippingUiKey.Key: + type: StrippableBoundUserInterface - type: ActivatableUI key: enum.BorgUiKey.Key - type: SiliconLawBound @@ -129,11 +129,14 @@ proto: robot - type: Speech speechVerb: Robotic - speechSounds: Pai + speechSounds: Borg - type: Vocal sounds: Unsexed: UnisexSilicon - type: UnblockableSpeech + - type: FootstepModifier + footstepSoundCollection: + collection: FootstepBorg - type: Construction graph: Cyborg containers: @@ -205,28 +208,47 @@ - type: StandingState - type: Tag tags: - - ShoesRequiredStepTriggerImmune - DoorBumpOpener + - FootstepSound - CanPilot - type: Emoting - type: GuideHelp guides: - Cyborgs + - type: StepTriggerImmune - type: Eye visMask: - PsionicInvisibility - Normal - type: LanguageKnowledge speaks: - - GalacticCommon + - TauCetiBasic - RobotTalk understands: - - GalacticCommon + - TauCetiBasic - RobotTalk + - type: PsionicInsulation + +- type: entity + abstract: true + id: BaseBorgTransponder + components: + - type: BorgTransponder + - type: DeviceNetwork + deviceNetId: Wireless + receiveFrequencyId: CyborgControl + transmitFrequencyId: RoboticsConsole + # explosion does most of its damage in the center and less at the edges + - type: Explosive + explosionType: Minibomb + totalIntensity: 30 + intensitySlope: 20 + maxIntensity: 20 + canCreateVacuum: false # its for killing the borg not the station - type: entity id: BaseBorgChassisNT - parent: BaseBorgChassis + parent: [BaseBorgChassis, BaseBorgTransponder] abstract: true components: - type: NpcFactionMember @@ -238,30 +260,36 @@ - AllAccessBorg - type: AccessReader access: [["Command"], ["Research"]] - - type: ShowSecurityIcons + - type: ShowJobIcons + - type: ShowMindShieldIcons - type: entity id: BaseBorgChassisSyndicate parent: BaseBorgChassis abstract: true components: - - type: NpcFactionMember - factions: - - Syndicate - - type: Access - tags: - - NuclearOperative - - SyndicateAgent - - type: AccessReader - access: [["SyndicateAgent"], ["NuclearOperative"]] - - type: SiliconLawProvider - laws: SyndicateStatic - - type: IntrinsicRadioTransmitter - channels: - - Binary - - Syndicate - - type: ActiveRadio - channels: - - Syndicate - - type: ShowSyndicateIcons - - type: MovementAlwaysTouching + - type: NpcFactionMember + factions: + - Syndicate + - type: Access + tags: + - NuclearOperative + - SyndicateAgent + - type: AccessReader + access: [["SyndicateAgent"], ["NuclearOperative"]] + - type: SiliconLawProvider + laws: SyndicateStatic + - type: IntrinsicRadioTransmitter + channels: + - Binary + - Syndicate + - type: ActiveRadio + channels: + - Syndicate + - type: ShowSyndicateIcons + - type: MovementAlwaysTouching + - type: Speech + speechSounds: SyndieBorg + - type: Vocal + sounds: + Unsexed: UnisexSiliconSyndicate diff --git a/Resources/Prototypes/Entities/Mobs/Cyborgs/borg_chassis.yml b/Resources/Prototypes/Entities/Mobs/Cyborgs/borg_chassis.yml index 5056fda4c0..e0dc4a0ad4 100644 --- a/Resources/Prototypes/Entities/Mobs/Cyborgs/borg_chassis.yml +++ b/Resources/Prototypes/Entities/Mobs/Cyborgs/borg_chassis.yml @@ -20,6 +20,11 @@ - BorgModuleGeneric hasMindState: robot_e noMindState: robot_e_r + - type: BorgTransponder + sprite: + sprite: Mobs/Silicon/chassis.rsi + state: robot + name: cyborg - type: Construction node: cyborg - type: Speech @@ -57,6 +62,11 @@ - BorgModuleCargo hasMindState: miner_e noMindState: miner_e_r + - type: BorgTransponder + sprite: + sprite: Mobs/Silicon/chassis.rsi + state: miner + name: salvage cyborg - type: Construction node: mining - type: IntrinsicRadioTransmitter @@ -100,6 +110,11 @@ - BorgModuleEngineering hasMindState: engineer_e noMindState: engineer_e_r + - type: BorgTransponder + sprite: + sprite: Mobs/Silicon/chassis.rsi + state: engineer + name: engineer cyborg - type: Construction node: engineer - type: IntrinsicRadioTransmitter @@ -153,6 +168,11 @@ - BorgModuleJanitor hasMindState: janitor_e noMindState: janitor_e_r + - type: BorgTransponder + sprite: + sprite: Mobs/Silicon/chassis.rsi + state: janitor + name: janitor cyborg - type: Construction node: janitor - type: IntrinsicRadioTransmitter @@ -206,6 +226,11 @@ - BorgModuleMedical hasMindState: medical_e noMindState: medical_e_r + - type: BorgTransponder + sprite: + sprite: Mobs/Silicon/chassis.rsi + state: medical + name: medical cyborg - type: Construction node: medical - type: IntrinsicRadioTransmitter @@ -224,11 +249,14 @@ access: [["Medical"], ["Command"], ["Research"]] - type: Inventory templateId: borgDutch + - type: FootstepModifier + footstepSoundCollection: + collection: FootstepHoverBorg - type: FabricateActions actions: - ActionFabricateLollipop - ActionFabricateGumball - - type: SiliconLawProvider # Delta-V - Adds custom lawset for Medical cyborg + - type: SiliconLawProvider laws: Medical - type: entity @@ -255,6 +283,11 @@ - BorgModuleService hasMindState: service_e noMindState: service_e_r + - type: BorgTransponder + sprite: + sprite: Mobs/Silicon/chassis.rsi + state: service + name: service cyborg - type: Construction node: service - type: IntrinsicRadioTransmitter diff --git a/Resources/Prototypes/Entities/Mobs/Debugging/debug_counter.yml b/Resources/Prototypes/Entities/Mobs/Debugging/debug_counter.yml new file mode 100644 index 0000000000..05707c7151 --- /dev/null +++ b/Resources/Prototypes/Entities/Mobs/Debugging/debug_counter.yml @@ -0,0 +1,53 @@ +- type: entity + parent: MobHuman + id: MobDebugCounter + name: debug counter + description: He can count + suffix: AI, DEBUG + components: + - type: HTN + rootTask: + task: DebugCounterCompound + blackboard: + MinimumIdleTime: !type:Single + 0.5 + MaximumIdleTime: !type:Single + 0.5 + Count: !type:Single + 0 + +- type: entity + parent: MobHuman + id: MobDebugRandomCounter + name: debug random counter + description: He can randomize + suffix: AI, DEBUG + components: + - type: HTN + rootTask: + task: DebugRandomCounterCompound + blackboard: + MinimumIdleTime: !type:Single + 1 + MaximumIdleTime: !type:Single + 1 + Count: !type:Single + 0 + +- type: entity + parent: MobHuman + id: MobDebugRandomLess + name: debug random less + description: He can lessing + suffix: AI, DEBUG + components: + - type: HTN + rootTask: + task: DebugRandomLessCompound + blackboard: + MinimumIdleTime: !type:Single + 1 + MaximumIdleTime: !type:Single + 1 + Count: !type:Single + 0 diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/Rslimes.yml b/Resources/Prototypes/Entities/Mobs/NPCs/Rslimes.yml new file mode 100644 index 0000000000..3512b51815 --- /dev/null +++ b/Resources/Prototypes/Entities/Mobs/NPCs/Rslimes.yml @@ -0,0 +1,287 @@ +- type: entity + id: ReagentSlimeIchor + parent: ReagentSlime + suffix: Ichor + components: + - type: Bloodstream + bloodReagent: Ichor + - type: PointLight + color: "#f4692e" + - type: Sprite + drawdepth: Mobs + sprite: Mobs/Aliens/elemental.rsi + layers: + - map: [ "enum.DamageStateVisualLayers.Base" ] + state: alive + color: "#f4692e" + +- type: entity + id: ReagentSlimeBleach + parent: ReagentSlime + suffix: Bleach + components: + - type: Bloodstream + bloodReagent: Bleach + - type: PointLight + color: "#a1000b" + - type: Sprite + drawdepth: Mobs + sprite: Mobs/Aliens/elemental.rsi + layers: + - map: [ "enum.DamageStateVisualLayers.Base" ] + state: alive + color: "#a1000b" + +- type: entity + id: ReagentSlimeSoap + parent: ReagentSlime + suffix: Soap + components: + - type: Bloodstream + bloodReagent: SoapReagent + - type: PointLight + color: "#c8dfc9" + - type: Sprite + drawdepth: Mobs + sprite: Mobs/Aliens/elemental.rsi + layers: + - map: [ "enum.DamageStateVisualLayers.Base" ] + state: alive + color: "#c8dfc9" + +- type: entity + id: ReagentSlimeSpacelube + parent: ReagentSlime + suffix: Spacelube + components: + - type: Bloodstream + bloodReagent: SpaceLube + - type: PointLight + color: "#77b58e" + - type: Sprite + drawdepth: Mobs + sprite: Mobs/Aliens/elemental.rsi + layers: + - map: [ "enum.DamageStateVisualLayers.Base" ] + state: alive + color: "#77b58e" + +- type: entity + id: ReagentSlimeBuzzachloricbees + parent: ReagentSlime + suffix: Buzzachloricbees + components: + - type: Bloodstream + bloodReagent: BuzzochloricBees + - type: PointLight + color: "#FFD35D" + - type: Sprite + drawdepth: Mobs + sprite: Mobs/Aliens/elemental.rsi + layers: + - map: [ "enum.DamageStateVisualLayers.Base" ] + state: alive + color: "#FFD35D" + +- type: entity + id: ReagentSlimeWehjuice + parent: ReagentSlime + suffix: Wehjuice + components: + - type: Bloodstream + bloodReagent: JuiceThatMakesYouWeh + - type: PointLight + color: "#59b23a" + - type: Sprite + drawdepth: Mobs + sprite: Mobs/Aliens/elemental.rsi + layers: + - map: [ "enum.DamageStateVisualLayers.Base" ] + state: alive + color: "#59b23a" + +- type: entity + id: ReagentCognizine + parent: ReagentSlime + suffix: Cognizine + components: + - type: Bloodstream + bloodReagent: Cognizine + - type: PointLight + color: "#b50ee8" + - type: Sprite + drawdepth: Mobs + sprite: Mobs/Aliens/elemental.rsi + layers: + - map: [ "enum.DamageStateVisualLayers.Base" ] + state: alive + color: "#b50ee8" + +- type: entity + id: ReagentSlimeNecrosol + parent: ReagentSlime + suffix: Necrosol + components: + - type: Bloodstream + bloodReagent: Necrosol + - type: PointLight + color: "#86a5bd" + - type: Sprite + drawdepth: Mobs + sprite: Mobs/Aliens/elemental.rsi + layers: + - map: [ "enum.DamageStateVisualLayers.Base" ] + state: alive + color: "#86a5bd" + +- type: entity + id: ReagentSlimeSpaceDrugs + parent: ReagentSlime + suffix: SpaceDrugs + components: + - type: Bloodstream + bloodReagent: SpaceDrugs + - type: PointLight + color: "#63806e" + - type: Sprite + drawdepth: Mobs + sprite: Mobs/Aliens/elemental.rsi + layers: + - map: [ "enum.DamageStateVisualLayers.Base" ] + state: alive + color: "#63806e" + +- type: entity + id: ReagentSlimeUnstableMutagen + parent: ReagentSlime + suffix: UnstableMutagen + components: + - type: Bloodstream + bloodReagent: UnstableMutagen + - type: PointLight + color: "#00ff5f" + - type: Sprite + drawdepth: Mobs + sprite: Mobs/Aliens/elemental.rsi + layers: + - map: [ "enum.DamageStateVisualLayers.Base" ] + state: alive + color: "#00ff5f" + +- type: entity + id: ReagentSlimeLead + parent: ReagentSlime + suffix: Lead + components: + - type: Bloodstream + bloodReagent: Lead + - type: PointLight + color: "#5C6274" + - type: Sprite + drawdepth: Mobs + sprite: Mobs/Aliens/elemental.rsi + layers: + - map: [ "enum.DamageStateVisualLayers.Base" ] + state: alive + color: "#5C6274" + +- type: entity + id: ReagentSlimechlorinetriflouride + parent: ReagentSlime + suffix: chlorinetriflouride + components: + - type: Bloodstream + bloodReagent: ChlorineTrifluoride + - type: PointLight + color: "#FFC8C8" + - type: Sprite + drawdepth: Mobs + sprite: Mobs/Aliens/elemental.rsi + layers: + - map: [ "enum.DamageStateVisualLayers.Base" ] + state: alive + color: "#FFC8C8" + +- type: entity + id: ReagentSlimePotassium + parent: ReagentSlime + suffix: Potassium + components: + - type: Bloodstream + bloodReagent: Potassium + - type: PointLight + color: "#c6c8cc" + - type: Sprite + drawdepth: Mobs + sprite: Mobs/Aliens/elemental.rsi + layers: + - map: [ "enum.DamageStateVisualLayers.Base" ] + state: alive + color: "#c6c8cc" + +- type: entity + id: reagentslimeVents + parent: BaseGameRule + noSpawn: true + components: + - type: StationEvent + startAnnouncement: true + earliestStart: 20 + reoccurrenceDelay: 12 + minimumPlayers: 30 + weight: 6 # Really weak compared to other critters + duration: 60 + - type: VentCrittersRule + entries: + - id: ReagentSlimeBeer + prob: 0.003 + - id: ReagentSlimePax + prob: 0.001 + - id: ReagentSlimeNocturine + prob: 0.001 + - id: ReagentSlimeTHC + prob: 0.002 + - id: ReagentSlimeBicaridine + prob: 0.002 + - id: ReagentSlimeToxin + prob: 0.002 + - id: ReagentSlimeNapalm + prob: 0.002 + - id: ReagentSlimeOmnizine + prob: 0.003 + - id: ReagentSlimeMuteToxin + prob: 0.002 + - id: ReagentSlimeNorepinephricAcid + prob: 0.002 + - id: ReagentSlimeEphedrine + prob: 0.002 + - id: ReagentSlimeRobustHarvest + prob: 0.003 + - id: ReagentSlimeIchor + prob: 0.002 + - id: ReagentSlimeBleach + prob: 0.002 + - id: ReagentSlimeSoap + prob: 0.002 + - id: ReagentSlimeSpacelube + prob: 0.002 + - id: ReagentSlimeBuzzachloricbees + prob: 0.002 + - id: ReagentSlimeWehjuice + prob: 0.003 + - id: ReagentCognizine + prob: 0.003 + - id: ReagentSlimeNecrosol + prob: 0.002 + - id: ReagentSlimeSpaceDrugs + prob: 0.003 + - id: ReagentSlimeUnstableMutagen + prob: 0.001 + - id: ReagentSlimeLead + prob: 0.001 + - id: ReagentSlimechlorinetriflouride + prob: 0.002 + - id: ReagentSlimePotassium + prob: 0.002 + - id: ReagentSlimeLotophagoiOil + prob: 0.001 diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index 46188fe878..575bb64d0c 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -17,6 +17,7 @@ - type: Speech speechSounds: Squeak speechVerb: SmallMob + allowedEmotes: ['Squeak'] - type: Fixtures fixtures: fix1: @@ -66,6 +67,9 @@ tags: - VimPilot - type: RandomBark + barkType: mouse + minTime: 10 # Mice like to squeak, I think. You can always put your pet mouse to sleep if it gets annoying + maxTime: 160 - type: entity name: bee @@ -135,11 +139,7 @@ Quantity: 5 - type: ZombieImmune - type: RandomBark - barks: - - Bzzzzz - - Bzzz bzzz - - Bzzzzzzzzzzzz - - Bzz + barkType: bee barkMultiplier: 1.5 - type: entity @@ -254,6 +254,7 @@ factions: - Passive - type: RandomBark + barkType: chicken - type: entity parent: MobChicken @@ -396,8 +397,12 @@ damageType: Blunt damage: 10 behaviors: - - !type:GibBehavior { } + - !type:GibBehavior + gibContents: Skip - type: NonSpreaderZombie + - type: LanguageKnowledge + speaks: [Hissing] + understands: [Hissing] - type: entity name: glockroach @@ -457,7 +462,10 @@ - type: GhostTakeoverAvailable - type: Speech speechVerb: Moth - speechSounds: Chitter # Delta-V - Eep! + speechSounds: Chitter + allowedEmotes: ['Chitter', 'Squeak'] + - type: FaxableObject + insertingState: inserting_mothroach - type: MothAccent - type: Sprite sprite: Mobs/Animals/mothroach.rsi @@ -641,6 +649,7 @@ factions: - Passive - type: RandomBark + barkType: chicken # Duh barkMultiplier: 0.7 - type: entity @@ -821,13 +830,7 @@ guides: - Chef - type: RandomBark - barks: - - Mooooooo - - Moo - - Huff - - Mooooooooooo - - Moooooo - - Moooo + barkType: cow barkMultiplier: 3 @@ -867,6 +870,7 @@ - type: Speech speechVerb: Arachnid speechSounds: Arachnid + allowedEmotes: ['Click', 'Chitter'] - type: DamageStateVisuals states: Alive: @@ -901,11 +905,7 @@ - type: Body prototype: AnimalHemocyanin - type: RandomBark - barks: - - click clack - - clack - - clickity clack - - clack clack + barkType: crab - type: entity name: goat @@ -1143,8 +1143,8 @@ - id: FoodMeat - type: UserInterface interfaces: - - key: enum.StrippingUiKey.Key - type: StrippableBoundUserInterface + enum.StrippingUiKey.Key: + type: StrippableBoundUserInterface - type: DamageStateVisuals states: Alive: @@ -1212,10 +1212,15 @@ abstract: true components: - type: CombatMode + #- type: SurgeryTarget + # canOperate: false + #- type: Targeting - type: Inventory templateId: monkey speciesId: monkey - type: InventorySlots + - type: Deathgasp + prototype: MonkeyDeathgasp - type: Cuffable - type: RotationVisuals defaultRotation: 90 @@ -1235,8 +1240,10 @@ - type: Strippable - type: UserInterface interfaces: - - key: enum.StrippingUiKey.Key - type: StrippableBoundUserInterface + enum.StrippingUiKey.Key: + type: StrippableBoundUserInterface + #enum.SurgeryUIKey.Key: + # type: SurgeryBui - type: Sprite drawdepth: Mobs layers: @@ -1372,7 +1379,7 @@ understands: - Monkey - Kobold - - GalacticCommon + - TauCetiBasic - type: NpcFactionMember factions: - Syndicate @@ -1381,6 +1388,8 @@ makeSentient: true name: ghost-role-information-monkey-name description: ghost-role-information-monkey-description + raffle: + settings: default - type: GhostTakeoverAvailable - type: Loadout prototypes: [SyndicateOperativeGearMonkey] @@ -1404,9 +1413,10 @@ - type: entity name: kobold - id: MobKobold + id: MobBaseKobold parent: MobBaseAncestor description: Cousins to the sentient race of lizard people, kobolds blend in with their natural habitat and are as nasty as monkeys; ready to pull out your hair and stab you to death. + abstract: true components: - type: NameIdentifier group: Kobold @@ -1422,9 +1432,9 @@ speechVerb: Reptilian - type: Vocal sounds: - Male: UnisexReptilian - Female: UnisexReptilian - Unsexed: UnisexReptilian + Male: MaleReptilian + Female: FemaleReptilian + Unsexed: MaleReptilian - type: TypingIndicator proto: lizard - type: InteractionPopup @@ -1504,15 +1514,6 @@ spawned: - id: FoodMeat amount: 2 - - type: Clumsy - clumsyDamage: - types: - Blunt: 2 - Piercing: 7 - groups: - Burn: 3 - clumsySound: - path: /Audio/Voice/Reptilian/reptilian_scream.ogg - type: AlwaysRevolutionaryConvertible - type: GhostTakeoverAvailable - type: SentienceTarget @@ -1524,6 +1525,56 @@ description: ghost-role-information-kobold-description - type: RandomBark barkMultiplier: 0.65 + barkType: kobold + +- type: entity + name: kobold + id: MobKobold + parent: MobBaseKobold + description: Cousins to the sentient race of lizard people, kobolds blend in with their natural habitat and are as nasty as monkeys; ready to pull out your hair and stab you to death. + components: + - type: Clumsy + clumsyDamage: + types: + Blunt: 2 + Piercing: 7 + groups: + Burn: 3 + clumsySound: + path: /Audio/Voice/Reptilian/reptilian_scream.ogg + +- type: entity + id: MobBaseSyndicateKobold + parent: MobBaseKobold + suffix: syndicate base + components: + - type: MobThresholds + thresholds: + 0: Alive + 75: Critical + 200: Dead + - type: NpcFactionMember + factions: + - Syndicate + - type: Loadout + prototypes: [SyndicateOperativeGearMonkey] + +- type: entity + id: MobKoboldSyndicateAgent + parent: MobBaseSyndicateKobold + suffix: syndicate agent + components: + # make the player a traitor once its taken + - type: AutoTraitor + giveUplink: false + giveObjectives: false + +- type: entity + id: MobKoboldSyndicateAgentNukeops # Reinforcement exclusive to nukeops uplink + parent: MobBaseSyndicateKobold + suffix: NukeOps + components: + - type: NukeOperative - type: entity name: guidebook monkey @@ -1552,6 +1603,7 @@ - type: Speech speechSounds: Squeak speechVerb: SmallMob + allowedEmotes: ['Squeak'] - type: Sprite drawdepth: SmallMobs sprite: Mobs/Animals/mouse.rsi @@ -1580,6 +1632,8 @@ rootTask: task: MouseCompound - type: Physics + - type: FaxableObject + insertingState: inserting_mouse - type: Fixtures fixtures: fix1: @@ -1688,9 +1742,14 @@ - type: PreventSpiller - type: RandomBark barkMultiplier: 0.3 + barkType: mouse - type: FireVisuals sprite: Mobs/Effects/onfire.rsi normalState: Mouse_burning + - type: StepTriggerImmune + whitelist: + types: + - Landmine - type: entity parent: MobMouse @@ -1932,16 +1991,6 @@ - VimPilot - type: RandomBark barkMultiplier: 1.3 - barks: - - Croooaaaakkk - - Ribbit - - Ribbit - - Ribbit - - Crooaak - - Ribbit - - Ribbit - - Ribbit - - Bibbit # Would be cool to have some functionality for the parrot to be able to sit on stuff - type: entity @@ -2000,9 +2049,9 @@ bloodMaxVolume: 50 - type: LanguageKnowledge speaks: - - GalacticCommon + - TauCetiBasic understands: - - GalacticCommon + - TauCetiBasic - type: entity name: penguin @@ -2053,6 +2102,7 @@ factions: - Passive - type: Temperature + atmosTemperatureTransferEfficiency: 0.04 heatDamageThreshold: 335 coldDamageThreshold: 230 currentTemperature: 310.15 @@ -2064,9 +2114,11 @@ types: Heat : 0.2 #per second, scales with temperature & other constants - type: RandomBark - barks: - - Wank + barkType: penguin barkMultiplier: 0.6 + - type: LanguageKnowledge + speaks: [Penguin] + understands: [Penguin] - type: entity name: grenade penguin @@ -2184,13 +2236,12 @@ damageContainer: Biological damageModifierSet: Scale - type: RandomBark - barkMultiplier: 1.5 - barks: - - Hsssssss - - Hss - - Hsssss - - Hisss - - Hshsss + minTime: 10 + maxTime: 50 # It's a sssnake... + barkType: hissing + - type: LanguageKnowledge + speaks: [Hissing] + understands: [Hissing] # Code unique spider prototypes or combine them all into one spider and get a # random sprite state when you spawn it. @@ -2244,6 +2295,7 @@ 0: Alive 90: Dead - type: MeleeWeapon + altDisarm: false angle: 0 animation: WeaponArcBite soundHit: @@ -2285,6 +2337,7 @@ - type: Speech speechVerb: Arachnid speechSounds: Arachnid + allowedEmotes: ['Click', 'Chitter'] - type: Vocal sounds: Male: UnisexArachnid @@ -2320,6 +2373,11 @@ Brute: -0.07 Burn: -0.07 - type: RandomBark + barkType: hissing + barkMultiplier: 0.3 + - type: BloodSucker + webRequired: true + - type: Cocooner - type: entity name: tarantula @@ -2339,7 +2397,9 @@ makeSentient: true name: ghost-role-information-giant-spider-name description: ghost-role-information-giant-spider-description - rules: deltav-ghost-role-information-softantag-rules #DeltaV + rules: deltav-ghost-role-information-softantag-rules + raffle: + settings: short - type: GhostTakeoverAvailable - type: entity @@ -2371,6 +2431,7 @@ - type: Spider webPrototype: SpiderWebClown - type: MeleeWeapon + altDisarm: false angle: 0 animation: WeaponArcBite soundHit: @@ -2387,6 +2448,18 @@ bloodMaxVolume: 150 bloodReagent: Laughter +- type: entity + name: wizard spider + parent: MobGiantSpider + id: MobGiantSpiderWizard + description: This spider looks a little magical + suffix: Wizard + components: + - type: Accentless + removes: + - type: ReplacementAccent + accent: xeno #let this wizard speak + - type: entity name: possum parent: SimpleMobBase @@ -2419,8 +2492,8 @@ - type: Strippable - type: UserInterface interfaces: - - key: enum.StrippingUiKey.Key - type: StrippableBoundUserInterface + enum.StrippingUiKey.Key: + type: StrippableBoundUserInterface - type: DamageStateVisuals states: Alive: @@ -2448,7 +2521,8 @@ - Hissing understands: - Hissing - + - type: RandomBark + barkType: possum - type: entity name: possum @@ -2501,8 +2575,8 @@ - type: Strippable - type: UserInterface interfaces: - - key: enum.StrippingUiKey.Key - type: StrippableBoundUserInterface + enum.StrippingUiKey.Key: + type: StrippableBoundUserInterface - type: DamageStateVisuals states: Alive: @@ -2531,6 +2605,8 @@ - Hissing understands: - Hissing + - type: RandomBark + barkType: raccoon - type: entity name: fox @@ -2571,8 +2647,8 @@ - type: Strippable - type: UserInterface interfaces: - - key: enum.StrippingUiKey.Key - type: StrippableBoundUserInterface + enum.StrippingUiKey.Key: + type: StrippableBoundUserInterface - type: DamageStateVisuals states: Alive: @@ -2589,7 +2665,7 @@ interactFailureString: petting-failure-generic interactSuccessSpawn: EffectHearts interactSuccessSound: - path: /Audio/Animals/fox_squeak.ogg + collection: Fox - type: Grammar attributes: gender: epicene @@ -2619,6 +2695,9 @@ - Fox understands: - Fox + - type: RandomBark + barkType: fox + barkMultiplier: 0.5 # Talkative <3 - type: entity name: corgi @@ -2654,8 +2733,8 @@ - type: Strippable - type: UserInterface interfaces: - - key: enum.StrippingUiKey.Key - type: StrippableBoundUserInterface + enum.StrippingUiKey.Key: + type: StrippableBoundUserInterface - type: DamageStateVisuals states: Alive: @@ -2688,6 +2767,7 @@ tags: - VimPilot - type: RandomBark + barkType: dog - type: entity name: corrupted corgi @@ -2809,8 +2889,8 @@ - type: Strippable - type: UserInterface interfaces: - - key: enum.StrippingUiKey.Key - type: StrippableBoundUserInterface + enum.StrippingUiKey.Key: + type: StrippableBoundUserInterface - type: DamageStateVisuals states: Alive: @@ -2837,6 +2917,11 @@ interactSuccessSound: path: /Audio/Animals/cat_meow.ogg - type: MeleeWeapon + altDisarm: false + angle: 0 + animation: WeaponArcBite + soundHit: + path: /Audio/Effects/bite.ogg damage: types: Piercing: 5 @@ -2849,6 +2934,7 @@ tags: - VimPilot - type: RandomBark + barkType: cat - type: entity name: calico cat @@ -2889,19 +2975,34 @@ allowMovement: true description: ghost-role-information-SyndiCat-description rules: ghost-role-information-SyndiCat-rules + raffle: + settings: default - type: GhostTakeoverAvailable - type: AutoImplant implants: - MicroBombImplant + - type: ExplosionResistance + damageCoefficient: 0.2 - type: NpcFactionMember factions: - Syndicate + - type: MeleeWeapon + altDisarm: false + angle: 0 + animation: WeaponArcBite + soundHit: + path: /Audio/Effects/bite.ogg + damage: + types: + Slash: 6 + Piercing: 6 + Structural: 15 - type: LanguageKnowledge speaks: - - Cat + - Cat understands: - Cat - - GalacticCommon + - TauCetiBasic - type: entity name: space cat @@ -2922,7 +3023,13 @@ - type: Temperature heatDamageThreshold: 423 coldDamageThreshold: 0 + - type: Tag + tags: + - DoorBumpOpener + - type: MovementAlwaysTouching - type: PressureImmunity + - type: StepTriggerImmune + - type: Insulated - type: InteractionPopup successChance: 0.7 interactSuccessString: petting-success-space-cat @@ -3043,8 +3150,8 @@ - type: Strippable - type: UserInterface interfaces: - - key: enum.StrippingUiKey.Key - type: StrippableBoundUserInterface + enum.StrippingUiKey.Key: + type: StrippableBoundUserInterface - type: DamageStateVisuals states: Alive: @@ -3068,10 +3175,6 @@ - type: Tag tags: - VimPilot - - type: RandomBark - barkMultiplier: 10 - barks: - - Sloth - type: LanguageKnowledge # WHAT DOES THE SLOTH SAY??????? speaks: - Hissing @@ -3110,8 +3213,8 @@ - type: Strippable - type: UserInterface interfaces: - - key: enum.StrippingUiKey.Key - type: StrippableBoundUserInterface + enum.StrippingUiKey.Key: + type: StrippableBoundUserInterface - type: DamageStateVisuals states: Alive: @@ -3161,6 +3264,7 @@ - type: Speech speechVerb: SmallMob speechSounds: Squeak + allowedEmotes: ['Squeak'] - type: Sprite drawdepth: SmallMobs sprite: Mobs/Animals/hamster.rsi @@ -3177,6 +3281,8 @@ - type: Item size: Tiny - type: Physics + - type: FaxableObject + insertingState: inserting_hamster - type: Fixtures fixtures: fix1: @@ -3211,8 +3317,8 @@ - type: Strippable - type: UserInterface interfaces: - - key: enum.StrippingUiKey.Key - type: StrippableBoundUserInterface + enum.StrippingUiKey.Key: + type: StrippableBoundUserInterface - type: DamageStateVisuals states: Alive: @@ -3287,6 +3393,7 @@ - type: NonSpreaderZombie - type: RandomBark barkMultiplier: 0.45 + barkType: mouse # duh - type: FireVisuals sprite: Mobs/Effects/onfire.rsi normalState: Mouse_burning @@ -3334,8 +3441,8 @@ - type: Strippable - type: UserInterface interfaces: - - key: enum.StrippingUiKey.Key - type: StrippableBoundUserInterface + enum.StrippingUiKey.Key: + type: StrippableBoundUserInterface - type: DamageStateVisuals states: Alive: @@ -3403,8 +3510,8 @@ bloodMaxVolume: 60 - type: UserInterface interfaces: - - key: enum.StrippingUiKey.Key - type: StrippableBoundUserInterface + enum.StrippingUiKey.Key: + type: StrippableBoundUserInterface - type: DamageStateVisuals states: Alive: @@ -3452,7 +3559,7 @@ speaks: - RootSpeak understands: - - GalacticCommon + - TauCetiBasic - RootSpeak - type: entity @@ -3462,4 +3569,4 @@ components: - type: ReplacementAccent accent: nymph - - type: RandomBark + - type: RandomBark # Using the default barks since they aren't going to talk anyway diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/behonker.yml b/Resources/Prototypes/Entities/Mobs/NPCs/behonker.yml index 8a10337c94..07ee6b1536 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/behonker.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/behonker.yml @@ -10,6 +10,8 @@ makeSentient: true name: ghost-role-information-behonker-name description: ghost-role-information-behonker-description + raffle: + settings: default - type: GhostTakeoverAvailable - type: HTN rootTask: @@ -76,8 +78,8 @@ - id: BikeHorn - type: UserInterface interfaces: - - key: enum.StrippingUiKey.Key - type: StrippableBoundUserInterface + enum.StrippingUiKey.Key: + type: StrippableBoundUserInterface - type: MobThresholds thresholds: 0: Alive diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml b/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml index 787f8ae542..6138bc6e96 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml @@ -71,6 +71,7 @@ tags: - Carp - DoorBumpOpener + - NoPaint - type: ReplacementAccent accent: genericAggressive - type: Speech @@ -164,6 +165,8 @@ makeSentient: true name: ghost-role-information-sentient-carp-name description: ghost-role-information-sentient-carp-description + raffle: + settings: short - type: GhostTakeoverAvailable - type: HTN rootTask: diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/dogs.yml b/Resources/Prototypes/Entities/Mobs/NPCs/dogs.yml new file mode 100644 index 0000000000..a0c19569bb --- /dev/null +++ b/Resources/Prototypes/Entities/Mobs/NPCs/dogs.yml @@ -0,0 +1,128 @@ +- type: entity + parent: SimpleMobBase + id: MobPibble + name: pitbull + description: Nanny dog. Or a lab mix depending on who is asking. + components: + - type: MeleeWeapon + hidden: true + soundHit: + path: /Audio/Effects/bite.ogg + angle: 0 + animation: WeaponArcBite + damage: + types: + Slash: 4 + Piercing: 8 + Blunt: 1 + Structural: 4 + - type: MobState + - type: Sprite + drawdepth: Mobs + sprite: Mobs/Pets/pitbull.rsi + layers: + - map: ["enum.DamageStateVisualLayers.Base"] + state: pibble + - type: Physics + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeCircle + radius: 0.35 + density: 135 #Bigger than Laika + mask: + - MobMask + layer: + - MobLayer + - type: Appearance + - type: Inventory + speciesId: dog + templateId: pet + - type: InventorySlots + - type: Strippable + - type: UserInterface + interfaces: + enum.StrippingUiKey.Key: + type: StrippableBoundUserInterface + - type: DamageStateVisuals + states: + Alive: + Base: pibble + Critical: + Base: pibble_dead + Dead: + Base: pibble_dead + - type: Butcherable + spawned: + - id: FoodMeat + amount: 3 + - type: ReplacementAccent + accent: dog + - type: InteractionPopup + successChance: 0.7 + interactSuccessString: petting-success-dog + interactFailureString: petting-failure-pibble + interactSuccessSound: + path: /Audio/Animals/small_dog_bark_happy.ogg + - type: DogVision + - type: NpcFactionMember + factions: + - Pibble + - type: HTN + rootTask: + task: SimpleHostileCompound + blackboard: + NavSmash: !type:Bool + true + - type: Grammar + attributes: + proper: true + - type: RandomMetadata + nameSegments: [names_pitbull] + - type: Speech + speechVerb: Canine + speechSounds: Vulpkanin + - type: LanguageKnowledge + speaks: + - Dog + understands: + - Dog + - TauCetiBasic + +- type: entity + parent: MobPibble + id: MobPibbleVent + name: ventbull + description: Some kind of pitbull mix... or maybe the next stage in pibble evolution? + components: + - type: MeleeWeapon + hidden: true + soundHit: + path: /Audio/Effects/bite.ogg + angle: 0 + animation: WeaponArcBite + damage: + types: + Slash: 2 + Piercing: 5 + Blunt: 0.5 + Structural: 2 + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeCircle + radius: 0.28 + density: 135 + mask: + - SmallMobMask + layer: + - SmallMobLayer + - type: Sprite + scale: 0.8, 0.6 + drawdepth: Mobs + sprite: Mobs/Pets/ventbull.rsi + layers: + - map: ["enum.DamageStateVisualLayers.Base"] + state: pibble diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/elemental.yml b/Resources/Prototypes/Entities/Mobs/NPCs/elemental.yml index c2380c4027..94ba7ec418 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/elemental.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/elemental.yml @@ -44,7 +44,6 @@ - type: Tag tags: - DoorBumpOpener - - ShoesRequiredStepTriggerImmune - type: MobState allowedStates: - Alive @@ -73,6 +72,8 @@ - type: InputMover - type: MobMover - type: ZombieImmune + - type: ClothingRequiredStepTriggerImmune + slots: All - type: entity abstract: true @@ -101,6 +102,11 @@ - SimpleHostile - type: Damageable damageContainer: StructuralInorganic + - type: Psionic + removable: false + - type: InnatePsionicPowers + powersToAdd: + - TelepathyPower - type: entity parent: MobOreCrab @@ -231,6 +237,8 @@ - type: GhostRole prob: 0 description: ghost-role-information-angry-slimes-description + raffle: + settings: short - type: NpcFactionMember factions: - SimpleHostile @@ -252,13 +260,13 @@ - type: MobThresholds thresholds: 0: Alive - 150: Dead + 50: Dead - type: SlowOnDamage speedModifierThresholds: - 50: 0.4 + 20: 0.4 - type: Bloodstream bloodReagent: Water - chemicalMaxVolume: 100 + chemicalMaxVolume: 50 - type: StatusEffects allowed: - SlowedDown @@ -269,10 +277,10 @@ animation: WeaponArcBite damage: types: - Slash: 8 + Slash: 3 - type: MeleeChemicalInjector solution: bloodstream - transferAmount: 5 + transferAmount: 2 - type: DamageStateVisuals rotate: true states: @@ -293,6 +301,11 @@ solution: bloodstream - type: DrainableSolution solution: bloodstream + - type: Psionic + removable: false + - type: InnatePsionicPowers + powersToAdd: + - TelepathyPower - type: entity name: Reagent Slime Spawner @@ -319,6 +332,20 @@ - ReagentSlimeNorepinephricAcid - ReagentSlimeEphedrine - ReagentSlimeRobustHarvest + - ReagentSlimeIchor + - ReagentSlimeBleach + - ReagentSlimeSoap + - ReagentSlimeSpacelube + - ReagentSlimeBuzzachloricbees + - ReagentSlimeWehjuice + - ReagentCognizine + - ReagentSlimeNecrosol + - ReagentSlimeSpaceDrugs + - ReagentSlimeUnstableMutagen + - ReagentSlimeLead + - ReagentSlimechlorinetriflouride + - ReagentSlimePotassium + - ReagentSlimeLotophagoiOil chance: 1 - type: entity @@ -530,3 +557,23 @@ - map: [ "enum.DamageStateVisualLayers.Base" ] state: alive color: "#3e901c" + +- type: entity + id: ReagentSlimeLotophagoiOil + parent: ReagentSlime + suffix: Lotophagoi Oil + components: + - type: Bloodstream + bloodReagent: LotophagoiOil + - type: PointLight + color: "#FFBF00" + - type: Sprite + drawdepth: Mobs + sprite: Mobs/Aliens/elemental.rsi + layers: + - map: [ "enum.DamageStateVisualLayers.Base" ] + state: alive + color: "#3e901c" + - type: GhostRole + prob: 1 #it's significantly more psionic than the others + description: ghost-role-information-angry-slimes-description diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/glimmer_creatures.yml b/Resources/Prototypes/Entities/Mobs/NPCs/glimmer_creatures.yml new file mode 100644 index 0000000000..c2219d7fae --- /dev/null +++ b/Resources/Prototypes/Entities/Mobs/NPCs/glimmer_creatures.yml @@ -0,0 +1,166 @@ +- type: entity + name: glimmer mite + parent: MobCockroach + id: MobGlimmerMite + description: A strange pest from a world beyond the noosphere. + components: + - type: Sprite + sprite: DeltaV/Mobs/Ghosts/glimmermite.rsi + layers: + - map: ["enum.DamageStateVisualLayers.Base"] + state: mite + - type: DamageStateVisuals + states: + Alive: + Base: mite + Dead: + Base: mite_dead + baseDecayRate: 0.25 + - type: SolutionContainerManager + solutions: + food: + reagents: + - ReagentId: Ectoplasm + Quantity: 15 + - type: Psionic + - type: GlimmerSource + - type: AmbientSound + range: 6 + volume: -3 + sound: /Audio/DeltaV/Glimmer_Creatures/mite.ogg + - type: AmbientOnPowered + +- type: entity + parent: + - BaseMob + - MobCombat + - MobDamageable + id: MobGlimmerWisp + name: glimmer wisp + description: A strange orb that moves with intent. + components: + # appearance + - type: Sprite + drawDepth: Ghosts + sprite: Mobs/Demons/glimmer_wisp.rsi + layers: + - state: willowisp + shader: unshaded + - type: PointLight + color: "#419ba3" + - type: Stealth + lastVisibility: 0.66 + - type: AmbientSound + volume: -8 + range: 5 + sound: + path: /Audio/Ambience/wisp_ambience.ogg + # physical + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeCircle + radius: 0.35 + density: 13 + mask: + - Opaque + layer: + - MobLayer + - type: MovementSpeedModifier + baseSprintSpeed: 8 + baseWalkSpeed: 5 + - type: MovementIgnoreGravity + - type: Speech + # powers + - type: Psionic + removable: false + - type: InnatePsionicPowers + powersToAdd: + - NoosphericZapPower + - type: LifeDrainer + damage: + types: + Asphyxiation: 200 + whitelist: + components: + - Psionic + # damage + - type: Reactive + groups: + Acidic: [Touch] # Holy water + - type: MobState + allowedStates: + - Alive + - Dead + - type: MobThresholds + thresholds: + 0: Alive + 300: Dead + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 300 + behaviors: + - !type:PlaySoundBehavior + sound: + path: /Audio/Effects/wail.ogg + - !type:SpawnEntitiesBehavior + spawn: + Ectoplasm: + min: 2 + max: 3 + - !type:DoActsBehavior + acts: [ "Destruction" ] + - type: Damageable + damageContainer: Spirit + damageModifierSet: CorporealSpirit + - type: DamageOnDispel + damage: + types: + Heat: 100 + - type: SlowOnDamage + speedModifierThresholds: + 150: 0.8 + 200: 0.6 + 250: 0.3 + - type: StatusEffects + allowed: + - Stun + - KnockedDown #KnockedDown is inseperable from stun because... IT JUST IS OK? + - SlowedDown + - Pacified + # combat + - type: Gun + fireRate: 0.7 + soundGunshot: + collection: MagicMissile + showExamineText: false + selectedMode: SemiAuto + availableModes: + - SemiAuto + - type: HitscanBatteryAmmoProvider + proto: WispLash + fireCost: 1 + # TODO: implement upstream or make it use a proper thing, maybe copy dragon + #examinable: false + - type: Battery + maxCharge: 1000 + startingCharge: 1000 + - type: BatterySelfRecharger + autoRecharge: true + autoRechargeRate: 100 + # AI + - type: HTN + rootTask: + task: GlimmerWispCompound + - type: NpcFactionMember + factions: + - GlimmerMonster + - type: NPCRetaliation + attackMemoryLength: 10 + - type: NPCRangedCombat + # other + - type: LanguageSpeaker + - type: UniversalLanguageSpeaker # Should it speak unversal or some other language? diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/hellspawn.yml b/Resources/Prototypes/Entities/Mobs/NPCs/hellspawn.yml index 5511c40baa..74658f0a2d 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/hellspawn.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/hellspawn.yml @@ -12,6 +12,8 @@ makeSentient: true name: ghost-role-information-hellspawn-name description: ghost-role-information-hellspawn-description + raffle: + settings: default - type: RotationVisuals defaultRotation: 90 horizontalRotation: 90 @@ -53,6 +55,7 @@ - type: Perishable - type: Reflect reflectProb: 0.7 + innate: true reflects: - Energy - type: Fixtures diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/mimic.yml b/Resources/Prototypes/Entities/Mobs/NPCs/mimic.yml index 1f153ff314..c3a92d3ebc 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/mimic.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/mimic.yml @@ -1,3 +1,4 @@ +# TODO: Pending a rewrite of Mob Replacement System, this entire list should just be moved into a ComponentRegistry on the event. - type: entity name: Mimic id: MobMimic diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml b/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml index 0fd513164f..eedbb241bd 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml @@ -40,7 +40,7 @@ speaks: - Dog understands: - - GalacticCommon + - TauCetiBasic - Dog - type: entity @@ -131,7 +131,7 @@ speaks: - Cat understands: - - GalacticCommon + - TauCetiBasic - Cat - type: entity @@ -155,7 +155,7 @@ speaks: - Cat understands: - - GalacticCommon + - TauCetiBasic - Cat - type: entity @@ -229,8 +229,8 @@ - type: Strippable - type: UserInterface interfaces: - - key: enum.StrippingUiKey.Key - type: StrippableBoundUserInterface + enum.StrippingUiKey.Key: + type: StrippableBoundUserInterface - type: DamageStateVisuals states: Alive: @@ -292,8 +292,8 @@ - type: Strippable - type: UserInterface interfaces: - - key: enum.StrippingUiKey.Key - type: StrippableBoundUserInterface + enum.StrippingUiKey.Key: + type: StrippableBoundUserInterface - type: DamageStateVisuals states: Alive: @@ -310,7 +310,7 @@ speaks: - Dog understands: - - GalacticCommon + - TauCetiBasic - Dog - type: InteractionPopup successChance: 0.5 @@ -330,6 +330,7 @@ - type: StealTarget stealGroup: AnimalMcGriff - type: RandomBark + barkType: dog barkMultiplier: 1.3 - type: entity @@ -397,8 +398,8 @@ - type: Strippable - type: UserInterface interfaces: - - key: enum.StrippingUiKey.Key - type: StrippableBoundUserInterface + enum.StrippingUiKey.Key: + type: StrippableBoundUserInterface - type: DamageStateVisuals states: Alive: @@ -415,7 +416,7 @@ speaks: - Dog understands: - - GalacticCommon + - TauCetiBasic - Dog - type: InteractionPopup successChance: 0.7 @@ -435,6 +436,7 @@ - type: StealTarget stealGroup: AnimalWalter - type: RandomBark + barkType: dog barkMultiplier: 1.1 - type: entity @@ -559,7 +561,7 @@ interactFailureString: petting-failure-generic interactSuccessSpawn: EffectHearts interactSuccessSound: - path: /Audio/Animals/fox_squeak.ogg + collection: Fox - type: Butcherable spawned: - id: FoodMeat @@ -580,7 +582,7 @@ speaks: - Fox understands: - - GalacticCommon + - TauCetiBasic - Fox - type: entity @@ -633,7 +635,7 @@ speaks: - Mouse understands: - - GalacticCommon + - TauCetiBasic - Mouse - type: entity @@ -795,6 +797,9 @@ types: Blunt: 1 Caustic: 1 + - type: MultiHandedItem + - type: Item + size: Huge - type: SentienceTarget flavorKind: station-event-random-sentience-flavor-slime - type: MobPrice @@ -811,7 +816,7 @@ speaks: - Bubblish understands: - - GalacticCommon + - TauCetiBasic - Bubblish - type: entity @@ -820,13 +825,6 @@ id: MobMonkeyPunpun description: A prominent representative of monkeys with unlimited access to alcohol. components: - - type: GhostRole - prob: 1 - makeSentient: true - allowSpeech: true - allowMovement: true - name: ghost-role-information-punpun-name - description: ghost-role-information-punpun-description - type: GhostTakeoverAvailable - type: Butcherable butcheringType: Spike @@ -851,9 +849,10 @@ speaks: - Monkey understands: - - GalacticCommon + - TauCetiBasic - Monkey - Kobold + - type: Punpun - type: entity name: Tropico @@ -885,5 +884,5 @@ speaks: - Crab understands: - - GalacticCommon + - TauCetiBasic - Crab diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml b/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml index d1b3bd6a6a..f6a42dfb76 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml @@ -90,6 +90,8 @@ name: ghost-role-information-rat-king-name description: ghost-role-information-rat-king-description rules: ghost-role-information-rat-king-rules + raffle: + settings: default - type: GhostTakeoverAvailable - type: Tag tags: @@ -118,13 +120,12 @@ - type: Grammar attributes: gender: male - - type: PotentialPsionic # Nyano - type: LanguageKnowledge speaks: - - GalacticCommon + - TauCetiBasic - Mouse understands: - - GalacticCommon + - TauCetiBasic - Mouse - type: entity @@ -302,7 +303,7 @@ speaks: - Mouse understands: - - GalacticCommon + - TauCetiBasic - Mouse - type: FireVisuals sprite: Mobs/Effects/onfire.rsi diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml b/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml index 1c6bda6fd3..c3706cea2a 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml @@ -60,6 +60,8 @@ name: ghost-role-information-revenant-name description: ghost-role-information-revenant-description rules: ghost-role-information-revenant-rules + raffle: + settings: default - type: GhostTakeoverAvailable - type: Revenant malfunctionWhitelist: @@ -79,8 +81,8 @@ softness: 1 - type: UserInterface interfaces: - - key: enum.StoreUiKey.Key - type: StoreBoundUserInterface + enum.StoreUiKey.Key: + type: StoreBoundUserInterface - type: Visibility layer: 2 #ghost vis layer - type: Store @@ -97,4 +99,16 @@ - RevenantTheme - type: Speech speechVerb: Ghost + - type: Psionic + removable: false + psychognomicDescriptors: + - p-descriptor-vampiric + - type: InnatePsionicPowers + powersToAdd: + - XenoglossyPower + - TelepathyPower + - type: LanguageSpeaker - type: UniversalLanguageSpeaker + - type: Tag + tags: + - NoPaint diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml b/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml index bc9a18b021..2fea60ea1e 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml @@ -61,11 +61,11 @@ group: GenericNumber - type: Repairable doAfterDelay: 8 + fuelCost: 15 - type: Pullable - type: Tag tags: - DoorBumpOpener - - ShoesRequiredStepTriggerImmune - type: MobState allowedStates: - Alive @@ -106,13 +106,15 @@ - type: TypingIndicator proto: robot - type: ZombieImmune + - type: StepTriggerImmune - type: LanguageKnowledge speaks: - - GalacticCommon + - TauCetiBasic - RobotTalk understands: - - GalacticCommon + - TauCetiBasic - RobotTalk + - type: PsionicInsulation - type: entity parent: MobSiliconBase @@ -136,6 +138,9 @@ - type: Speech speechVerb: Cluwne - type: StepTrigger + triggerGroups: + types: + - SlipEntity intersectRatio: 0.2 - type: Fixtures fixtures: @@ -160,6 +165,8 @@ makeSentient: true name: ghost-role-information-honkbot-name description: ghost-role-information-honkbot-description + raffle: + settings: default - type: GhostTakeoverAvailable - type: InteractionPopup interactSuccessString: petting-success-honkbot @@ -185,6 +192,8 @@ makeSentient: true name: ghost-role-information-jonkbot-name description: ghost-role-information-jonkbot-description + raffle: + settings: default - type: InteractionPopup interactSuccessSound: path: /Audio/Items/brokenbikehorn.ogg @@ -306,8 +315,8 @@ - type: Strippable - type: UserInterface interfaces: - - key: enum.StrippingUiKey.Key - type: StrippableBoundUserInterface + enum.StrippingUiKey.Key: + type: StrippableBoundUserInterface - type: Sprite layers: - map: ["enum.DamageStateVisualLayers.Base"] @@ -321,9 +330,69 @@ makeSentient: true name: ghost-role-information-mimebot-name description: ghost-role-information-mimebot-description + raffle: + settings: default - type: GhostTakeoverAvailable - type: InteractionPopup interactSuccessString: petting-success-mimebot interactFailureString: petting-failure-mimebot - type: Inventory templateId: head + +- type: entity + parent: MobSiliconBase + id: MobSupplyBot + name: supplybot + description: Delivers cargo! + components: + - type: Sprite + sprite: Mobs/Silicon/Bots/supplybot.rsi + layers: + - state: supplybot + - type: GhostRole + makeSentient: true + name: ghost-role-information-supplybot-name + description: ghost-role-information-supplybot-description + raffle: + settings: default + - type: GhostTakeoverAvailable + - type: Construction + graph: SupplyBot + node: bot + - type: Access + tags: + - Cargo + - Maintenance + - Salvage + - type: Dumpable + - type: Storage + maxItemSize: Huge + grid: + - 0,0,9,3 + - type: UserInterface + interfaces: + enum.StorageUiKey.Key: + type: StorageBoundUserInterface + - type: ContainerContainer + containers: + storagebase: !type:Container + ents: [] + - type: UnpoweredFlashlight + - type: PointLight + enabled: false + radius: 3.5 + softness: 2 + mask: /Textures/Effects/LightMasks/cone.png + autoRot: true + - type: FootstepModifier + footstepSoundCollection: + collection: FootstepBorg + - type: Tag + tags: + - DoorBumpOpener + - FootstepSound + - type: ActiveRadio + channels: + - Binary + - Common + - Supply diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/slimes.yml b/Resources/Prototypes/Entities/Mobs/NPCs/slimes.yml index fbf133a0f1..74bba2dec8 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/slimes.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/slimes.yml @@ -1,16 +1,10 @@ - type: entity name: basic slime - id: MobAdultSlimes + id: BaseMobAdultSlimes parent: [ SimpleMobBase, MobCombat ] abstract: true description: It looks so much like jelly. I wonder what it tastes like? components: - - type: NpcFactionMember - factions: - - SimpleNeutral - - type: HTN - rootTask: - task: SimpleHostileCompound - type: Sprite drawdepth: Mobs sprite: Mobs/Aliens/slimes.rsi @@ -44,6 +38,7 @@ - type: Tag tags: - FootstepSound + - DoorBumpOpener - type: Butcherable butcheringType: Knife spawned: @@ -99,6 +94,7 @@ prototype: Slimes requiredLegs: 1 - type: MeleeWeapon + altDisarm: false soundHit: path: /Audio/Weapons/punch3.ogg angle: 0 @@ -112,6 +108,19 @@ successChance: 0.5 interactSuccessString: petting-success-slimes interactFailureString: petting-failure-generic + - type: Speech + speechVerb: Slime + speechSounds: Slime + - type: TypingIndicator + proto: slime + +- type: entity + name: basic slime + id: MobAdultSlimes + parent: BaseMobAdultSlimes + abstract: true + description: It looks so much like jelly. I wonder what it tastes like? + components: - type: LanguageKnowledge speaks: - Bubblish @@ -122,12 +131,54 @@ makeSentient: true name: ghost-role-information-slimes-name description: ghost-role-information-slimes-description - rules: deltav-ghost-role-information-softantag-rules #DeltaV + rules: deltav-ghost-role-information-softantag-rules - type: Speech speechVerb: Slime speechSounds: Slime + allowedEmotes: ['Squish'] - type: TypingIndicator proto: slime + - type: NpcFactionMember + factions: + - SimpleNeutral + - type: HTN + rootTask: + task: SimpleHostileCompound + +- type: entity + name: geras + description: A geras of a slime - the name is ironic, isn't it? + id: MobSlimesGeras + parent: BaseMobAdultSlimes + noSpawn: true + components: + # they portable... + - type: MovementSpeedModifier + baseWalkSpeed: 3 + baseSprintSpeed: 5 # +.5 from normal movement speed + - type: MobThresholds + thresholds: + 0: Alive + 80: Dead # weak af tho + - type: NpcFactionMember + factions: + - NanoTrasen + - type: MultiHandedItem + - type: Item + size: Huge + - type: Sprite + color: "#FFFFFF55" + - type: MeleeWeapon + attackRate: 2 + damage: + types: + Blunt: 4 + - type: DamageStateVisuals + states: + Alive: + Base: blue_adult_slime + Dead: + Base: blue_adult_slime_dead - type: entity name: blue slime @@ -150,8 +201,6 @@ - type: NpcFactionMember factions: - SimpleHostile -# - type: GhostRole #DeltaV - all slimes neutral when ghost role -# description: ghost-role-information-angry-slimes-description - type: entity name: green slime diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/space.yml b/Resources/Prototypes/Entities/Mobs/NPCs/space.yml index adf3af93ea..4ea766c314 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/space.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/space.yml @@ -174,8 +174,8 @@ - type: Strippable - type: UserInterface interfaces: - - key: enum.StrippingUiKey.Key - type: StrippableBoundUserInterface + enum.StrippingUiKey.Key: + type: StrippableBoundUserInterface - type: entity id: MobKangarooSpaceSalvage @@ -269,6 +269,7 @@ - type: Speech speechVerb: Arachnid speechSounds: Arachnid + allowedEmotes: ['Click', 'Chitter'] - type: Vocal sounds: Male: UnisexArachnid @@ -276,6 +277,9 @@ Unsexed: UnisexArachnid - type: TypingIndicator proto: spider + - type: BloodSucker + webRequired: true + - type: Cocooner - type: entity id: MobSpiderSpaceSalvage diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml index 397989643e..04b767b8ae 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml @@ -58,13 +58,18 @@ layer: - MobLayer - type: MobState - allowedStates: - - Alive - - Dead + - type: Deathgasp + - type: MobStateActions + actions: + Critical: + - ActionCritSuccumb + - ActionCritFakeDeath + - ActionCritLastWords - type: MobThresholds thresholds: 0: Alive - 50: Dead + 50: Critical + 100: Dead - type: SlowOnDamage speedModifierThresholds: 25: 0.5 @@ -92,19 +97,22 @@ Dead: Base: dead - type: Puller + needsHands: false - type: Butcherable butcheringType: Spike spawned: - id: FoodMeatXeno amount: 5 - #- type: GhostRole # Delta V - Moves ghost role from parent to child - # allowMovement: true # Delta V - Moves ghost role from parent to child - # allowSpeech: true # Delta V - Moves ghost role from parent to child - # makeSentient: true # Delta V - Moves ghost role from parent to child - # name: ghost-role-information-xeno-name # Delta V - Moves ghost role from parent to child - # description: ghost-role-information-xeno-description # Delta V - Moves ghost role from parent to child - # rules: ghost-role-information-xeno-rules # Delta V - Moves ghost role from parent to child - # - type: GhostTakeoverAvailable # Delta V - Moves ghost role from parent to child + - type: GhostRole + allowMovement: true + allowSpeech: true + makeSentient: true + name: ghost-role-information-xeno-name + description: ghost-role-information-xeno-description + rules: ghost-role-information-xeno-rules + raffle: + settings: default + - type: GhostTakeoverAvailable - type: TypingIndicator proto: alien - type: Temperature @@ -121,10 +129,11 @@ molsPerSecondPerUnitMass: 0.0005 - type: Speech speechVerb: LargeMob - - type: PotentialPsionic #Nyano - Summary: makes potentially psionic. - chance: -2 - - type: Psionic #Nyano - Summary: makes psionic by default. + - type: Psionic removable: false + - type: InnatePsionicPowers + powersToAdd: + - TelepathyPower - type: LanguageKnowledge speaks: - Xeno @@ -146,7 +155,8 @@ - type: MobThresholds thresholds: 0: Alive - 100: Dead + 100: Critical + 150: Dead - type: Stamina critThreshold: 300 - type: SlowOnDamage @@ -179,7 +189,8 @@ - type: MobThresholds thresholds: 0: Alive - 80: Dead + 80: Critical + 130: Dead - type: SlowOnDamage speedModifierThresholds: 40: 0.7 @@ -216,7 +227,8 @@ - type: MobThresholds thresholds: 0: Alive - 300: Dead + 300: Critical + 350: Dead - type: SlowOnDamage speedModifierThresholds: 150: 0.7 @@ -241,10 +253,10 @@ - CannotSuicide - type: LanguageKnowledge speaks: - - GalacticCommon + - TauCetiBasic - Xeno understands: - - GalacticCommon + - TauCetiBasic - Xeno - type: entity @@ -262,7 +274,8 @@ - type: MobThresholds thresholds: 0: Alive - 100: Dead + 100: Critical + 150: Dead - type: MovementSpeedModifier baseSprintSpeed: 4 - type: MeleeWeapon @@ -301,7 +314,7 @@ - type: MeleeWeapon damage: groups: - Brute: 5 + Brute: 10 - type: Fixtures fixtures: fix1: @@ -323,6 +336,7 @@ drawdepth: Mobs sprite: Mobs/Aliens/Xenos/rouny.rsi offset: 0,0.6 + scale: 0.7, 0.7 - type: Butcherable butcheringType: Spike spawned: diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/xenopet.yml b/Resources/Prototypes/Entities/Mobs/NPCs/xenopet.yml new file mode 100644 index 0000000000..23108e521b --- /dev/null +++ b/Resources/Prototypes/Entities/Mobs/NPCs/xenopet.yml @@ -0,0 +1,534 @@ +- type: entity + name: Neutral Rouny + id: MobXenoNeutralRouny + parent: MobXenoRounyNPC + description: They mostly come at night. Mostly. + components: + - type: NpcFactionMember + factions: + - Passive + - type: PointLight + radius: 2 + energy: 1 + color: "#B85E5E" + - type: MovementAlwaysTouching + - type: GhostRole + name: ghost-role-information-friendlyxeno-name + description: ghost-role-information-friendlyxeno-description + rules: ghost-role-information-friendlyxeno-rules + - type: GhostTakeoverAvailable + - type: LanguageKnowledge + speaks: + - Xeno + understands: + - Xeno + - TauCetiBasic + +- type: entity + name: Neutral Praetorian + id: MobXenoNeutralPraetorian + parent: MobXenoPraetorianNPC + description: They mostly come at night. Mostly. + components: + - type: NpcFactionMember + factions: + - Passive + - type: PointLight + radius: 2 + energy: 1 + color: "#62B85E" + - type: GhostRole + name: ghost-role-information-friendlyxeno-name + description: ghost-role-information-friendlyxeno-description + rules: ghost-role-information-friendlyxeno-rules + - type: GhostTakeoverAvailable + - type: LanguageKnowledge + speaks: + - Xeno + understands: + - Xeno + - TauCetiBasic + +- type: entity + name: Neutral Drone + id: MobXenoNeutralDrone + parent: MobXenoDroneNPC + description: They mostly come at night. Mostly. + components: + - type: NpcFactionMember + factions: + - Passive + - type: PointLight + radius: 2 + energy: 1 + color: "#8B5EB8" + - type: GhostRole + name: ghost-role-information-friendlyxeno-name + description: ghost-role-information-friendlyxeno-description + rules: ghost-role-information-friendlyxeno-rules + - type: GhostTakeoverAvailable + - type: LanguageKnowledge + speaks: + - Xeno + understands: + - Xeno + - TauCetiBasic + +- type: entity + name: Neutral Ravager + id: MobXenoNeutralRavager + parent: MobXenoRavagerNPC + description: They mostly come at night. Mostly. + components: + - type: NpcFactionMember + factions: + - Passive + - type: PointLight + radius: 2 + energy: 1 + color: "#E3954D" + - type: GhostRole + name: ghost-role-information-friendlyxeno-name + description: ghost-role-information-friendlyxeno-description + rules: ghost-role-information-friendlyxeno-rules + - type: GhostTakeoverAvailable + - type: LanguageKnowledge + speaks: + - Xeno + understands: + - Xeno + - TauCetiBasic + +- type: inventoryTemplate + id: friendxeno + slots: + - name: id + slotTexture: id + slotFlags: IDCARD + slotGroup: SecondHotbar + stripTime: 6 + uiWindowPos: 2,1 + strippingWindowPos: 2,4 + displayName: ID + - name: head + slotTexture: head + slotFlags: HEAD + uiWindowPos: 1,2 + strippingWindowPos: 0,0 + displayName: Head + - name: mask + slotTexture: mask + slotFlags: MASK + uiWindowPos: 0,1 + strippingWindowPos: 1,1 + displayName: Mask + +- type: entity + name: Friend-Shaped + parent: MobXenoNeutralRouny + id: MobXenoFriendShaped + description: A very clearly friend-shaped Xeno. + components: + - type: NpcFactionMember + factions: + - PetsNT + - type: Tool + speed: 3 + qualities: + - Prying + - type: Sprite + drawdepth: Mobs + sprite: Mobs/Aliens/Xenos/rouny.rsi + scale: 0.5, 0.5 + - type: GhostRole + - type: GhostTakeoverAvailable + - type: Inventory + templateId: friendxeno + - type: IdExaminable + - type: InventorySlots + - type: Stripping + - type: Strippable + - type: UserInterface + interfaces: + enum.StrippingUiKey.Key: + type: StrippableBoundUserInterface + - type: Grammar + attributes: + proper: true + gender: male + - type: Body + prototype: Friendshaped + requiredLegs: 1 # TODO: More than 1 leg + - type: InteractionPopup + successChance: 0.5 + interactSuccessString: petting-success-reptile + interactFailureString: petting-failure-generic + interactSuccessSpawn: EffectHearts + interactSuccessSound: + path: /Audio/Animals/lizard_happy.ogg + +- type: entity + name: Patriach + parent: MobXenoNeutralPraetorian + id: MobXenoPatriarch + description: A not entirely clearly friend-shaped Xeno. + components: + - type: NpcFactionMember + factions: + - PetsNT + - type: Tool + speed: 3 + qualities: + - Prying + - type: Sprite + drawdepth: Mobs + sprite: Mobs/Animals/patriarch.rsi + layers: + - map: ["enum.DamageStateVisualLayers.Base"] + state: patriarch + - type: DamageStateVisuals + states: + Alive: + Base: patriarch + Critical: + Base: patriarch_crit + Dead: + Base: patriarch_dead + - type: Inventory + templateId: friendxeno + - type: IdExaminable + - type: InventorySlots + - type: Strippable + - type: UserInterface + interfaces: + enum.StrippingUiKey.Key: + type: StrippableBoundUserInterface + - type: Grammar + attributes: + proper: true + gender: male + - type: InteractionPopup + successChance: 0.5 + interactSuccessString: petting-success-reptile + interactFailureString: petting-failure-generic + interactSuccessSpawn: EffectHearts + interactSuccessSound: + path: /Audio/Animals/lizard_happy.ogg + +- type: entity + name: FXES + parent: MobXenoNeutralDrone + id: MobXenoFXES + description: A very clearly friend-shaped Xeno. + components: + - type: NpcFactionMember + factions: + - PetsNT + - type: Tool + speed: 3 + qualities: + - Prying + - type: Inventory + templateId: friendxeno + - type: IdExaminable + - type: InventorySlots + - type: Strippable + - type: UserInterface + interfaces: + enum.StrippingUiKey.Key: + type: StrippableBoundUserInterface + - type: Grammar + attributes: + proper: true + gender: male + - type: InteractionPopup + successChance: 0.5 + interactSuccessString: petting-success-reptile + interactFailureString: petting-failure-generic + interactSuccessSpawn: EffectHearts + interactSuccessSound: + path: /Audio/Animals/lizard_happy.ogg + +- type: entity + name: Hell-Shaped + parent: MobXenoNeutralRavager + id: MobXenoHellShaped + description: A very clearly friend-shaped Xeno. + components: + - type: NpcFactionMember + factions: + - PetsNT + - type: Tool + speed: 3 + qualities: + - Prying + - type: Inventory + templateId: friendxeno + - type: IdExaminable + - type: InventorySlots + - type: Strippable + - type: UserInterface + interfaces: + enum.StrippingUiKey.Key: + type: StrippableBoundUserInterface + - type: Grammar + attributes: + proper: true + gender: male + - type: InteractionPopup + successChance: 0.5 + interactSuccessString: petting-success-reptile + interactFailureString: petting-failure-generic + interactSuccessSpawn: EffectHearts + interactSuccessSound: + path: /Audio/Animals/lizard_happy.ogg + +- type: entity + name: FXE Subject 7355 + parent: MobXenoNeutralRavager + id: MobXenoSubjectTess + description: An extremely oddly coloured xeno, with glowing stripes. An odd mutation. + components: + - type: NpcFactionMember + factions: + - PetsNT + - type: Tool + speed: 3 + qualities: + - Prying + - type: Sprite + drawdepth: Mobs + sprite: Mobs/Animals/subject7355.rsi + layers: + - map: ["enum.DamageStateVisualLayers.Base"] + state: subject7355 + - map: [ "enum.DamageStateVisualLayers.BaseUnshaded" ] + state: glow + shader: unshaded + - type: PointLight + radius: 2 + energy: 1 + color: "#639fff" + - type: DamageStateVisuals + states: + Alive: + Base: subject7355 + Critical: + Base: subject7355_crit + Dead: + Base: subject7355_dead + - type: Inventory + templateId: friendxeno + - type: IdExaminable + - type: InventorySlots + - type: Strippable + - type: UserInterface + interfaces: + enum.StrippingUiKey.Key: + type: StrippableBoundUserInterface + - type: Grammar + attributes: + proper: true + gender: male + - type: InteractionPopup + successChance: 0.5 + interactSuccessString: petting-success-reptile + interactFailureString: petting-failure-generic + interactSuccessSpawn: EffectHearts + interactSuccessSound: + path: /Audio/Animals/lizard_happy.ogg + +- type: entity + id: neutralXenoVents + parent: BaseGameRule + noSpawn: true + components: + - type: StationEvent + startAnnouncement: true + earliestStart: 20 + reoccurrenceDelay: 12 + minimumPlayers: 1 + weight: 6 # Really weak compared to other critters + duration: 60 + - type: VentCrittersRule + entries: + - id: MobXenoNeutralRouny + prob: 0.0075 + - id: MobXenoNeutralDrone + prob: 0.0075 + - id: MobXenoNeutralPraetorian + prob: 0.0075 + - id: MobXenoNeutralRavager + prob: 0.0075 + +- type: entity + id: ArgocyteVents + parent: BaseGameRule + noSpawn: true + components: + - type: StationEvent + startAnnouncement: true + earliestStart: 20 + reoccurrenceDelay: 12 + minimumPlayers: 30 + weight: 6 # Really weak compared to other critters + duration: 60 + - type: VentCrittersRule + entries: + - id: MobArgocyteSlurva + prob: 0.003 + - id: MobArgocyteBarrier + prob: 0.003 + - id: MobArgocyteSkitter + prob: 0.003 + - id: MobArgocyteSwiper + prob: 0.003 + - id: MobArgocyteMolder + prob: 0.003 + - id: MobArgocytePouncer + prob: 0.003 + - id: MobArgocyteGlider + prob: 0.003 + - id: MobArgocyteHarvester + prob: 0.003 + - id: MobArgocyteCrawler + prob: 0.003 + - id: MobArgocyteEnforcer + prob: 0.003 + - id: MobArgocyteFounder + prob: 0.003 + - id: MobArgocyteLeviathing + prob: 0.0001 + +- type: entity + id: MeatVents + parent: BaseGameRule + noSpawn: true + components: + - type: StationEvent + startAnnouncement: true + earliestStart: 20 + reoccurrenceDelay: 12 + minimumPlayers: 20 + weight: 6 # Really weak compared to other critters + duration: 60 + - type: VentCrittersRule + entries: + - id: MobFleshJared + prob: 0.005 + - id: MobFleshGolem + prob: 0.005 + - id: MobFleshClamp + prob: 0.005 + - id: MobFleshLover + prob: 0.005 + - id: MobAbomination + prob: 0.005 + +- type: entity + id: TickVents + parent: BaseGameRule + noSpawn: true + components: + - type: StationEvent + startAnnouncement: true + earliestStart: 20 + reoccurrenceDelay: 12 + minimumPlayers: 1 + weight: 6 # Really weak compared to other critters + duration: 60 + - type: VentCrittersRule + entries: + - id: MobTick + prob: 0.01 + +- type: entity + id: CarpVents + parent: BaseGameRule + noSpawn: true + components: + - type: StationEvent + startAnnouncement: true + earliestStart: 20 + reoccurrenceDelay: 12 + minimumPlayers: 20 + weight: 6 # Really weak compared to other critters + duration: 60 + - type: VentCrittersRule + entries: + - id: MobCarp + prob: 0.01 + - id: MobCarpMagic + prob: 0.01 + - id: MobCarpHolo + prob: 0.002 + - id: MobCarpRainbow + prob: 0.01 + - id: MobShark + prob: 0.002 + - id: MobCarpDragon + prob: 0.01 + +- type: entity + id: SpaceVents + parent: BaseGameRule + noSpawn: true + components: + - type: StationEvent + startAnnouncement: true + earliestStart: 20 + reoccurrenceDelay: 12 + minimumPlayers: 20 + weight: 6 # Really weak compared to other critters + duration: 60 + - type: VentCrittersRule + entries: + - id: MobBearSpace + prob: 0.01 + - id: MobKangarooSpace + prob: 0.01 + - id: MobSpiderSpace + prob: 0.01 + - id: MobCobraSpace + prob: 0.01 + +- type: entity + id: LightVents + parent: BaseGameRule + noSpawn: true + components: + - type: StationEvent + startAnnouncement: true + earliestStart: 20 + reoccurrenceDelay: 12 + minimumPlayers: 20 + weight: 6 # Really weak compared to other critters + duration: 60 + - type: VentCrittersRule + entries: + - id: MobLivingLight + prob: 0.01 + - id: MobLuminousPerson + prob: 0.01 + - id: MobLuminousObject + prob: 0.01 + - id: MobLuminousEntity + prob: 0.01 + +- type: entity + name: Friendly Xeno spawner + id: friendlyxenoSpawner + parent: MarkerBase + components: + - type: Sprite + layers: + - state: green + - sprite: Mobs/Aliens/Xenos/drone.rsi + state: sleeping + - type: RandomSpawner + prototypes: + - MobXenoNeutralRouny + - MobXenoNeutralPraetorian + - MobXenoNeutralDrone + - MobXenoNeutralRavager + chance: 1 diff --git a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml index e9ab487efd..c455615dc9 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml @@ -10,6 +10,7 @@ - PsionicInvisibility - Ghost - Normal + - Ethereal - type: ContentEye maxZoom: 8.916104, 8.916104 - type: Tag @@ -25,6 +26,8 @@ - type: GhostHearing - type: Hands - type: Puller + pushAcceleration: 1000000 # Will still be capped in max speed + maxPushRange: 20 - type: CombatMode - type: Physics ignorePaused: true @@ -41,32 +44,32 @@ - Captain # Parkstation-Oligarchy - type: UserInterface interfaces: - - key: enum.SolarControlConsoleUiKey.Key + enum.SolarControlConsoleUiKey.Key: type: SolarControlConsoleBoundUserInterface - - key: enum.CommunicationsConsoleUiKey.Key + enum.CommunicationsConsoleUiKey.Key: type: CommunicationsConsoleBoundUserInterface - - key: enum.RadarConsoleUiKey.Key + enum.RadarConsoleUiKey.Key: type: RadarConsoleBoundUserInterface - - key: enum.CargoConsoleUiKey.Orders + enum.CargoConsoleUiKey.Orders: type: CargoOrderConsoleBoundUserInterface - - key: enum.CrewMonitoringUIKey.Key + enum.CrewMonitoringUIKey.Key: type: CrewMonitoringBoundUserInterface - - key: enum.GeneralStationRecordConsoleKey.Key - # who the fuck named this bruh + enum.GeneralStationRecordConsoleKey.Key: + # who the fuck named this bruh type: GeneralStationRecordConsoleBoundUserInterface - type: IntrinsicUI uis: - - key: enum.SolarControlConsoleUiKey.Key + enum.SolarControlConsoleUiKey.Key: toggleAction: ActionAGhostShowSolar - - key: enum.CommunicationsConsoleUiKey.Key + enum.CommunicationsConsoleUiKey.Key: toggleAction: ActionAGhostShowCommunications - - key: enum.RadarConsoleUiKey.Key + enum.RadarConsoleUiKey.Key: toggleAction: ActionAGhostShowRadar - - key: enum.CargoConsoleUiKey.Orders + enum.CargoConsoleUiKey.Orders: toggleAction: ActionAGhostShowCargo - - key: enum.CrewMonitoringUIKey.Key + enum.CrewMonitoringUIKey.Key: toggleAction: ActionAGhostShowCrewMonitoring - - key: enum.GeneralStationRecordConsoleKey.Key + enum.GeneralStationRecordConsoleKey.Key: toggleAction: ActionAGhostShowStationRecords - type: SolarControlConsole # look ma i AM the computer! - type: CommunicationsConsole diff --git a/Resources/Prototypes/Entities/Mobs/Player/arachne.yml b/Resources/Prototypes/Entities/Mobs/Player/arachne.yml index cd4123fa80..209ad3e93e 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/arachne.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/arachne.yml @@ -27,4 +27,3 @@ - type: NpcFactionMember factions: - NanoTrasen - - type: PotentialPsionic diff --git a/Resources/Prototypes/Entities/Mobs/Player/arachnid.yml b/Resources/Prototypes/Entities/Mobs/Player/arachnid.yml index 5ebd43ddf4..d9dea3c18d 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/arachnid.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/arachnid.yml @@ -11,4 +11,3 @@ damageRecovery: types: Asphyxiation: -0.5 - - type: PotentialPsionic #Nyano - Summary: makes potentially psionic. diff --git a/Resources/Prototypes/Entities/Mobs/Player/diona.yml b/Resources/Prototypes/Entities/Mobs/Player/diona.yml index 28687c68bf..dfd5e9a1be 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/diona.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/diona.yml @@ -11,7 +11,6 @@ damageRecovery: types: Asphyxiation: -1.0 - - type: PotentialPsionic #Nyano - Summary: makes potentially psionic. # Reformed Diona - type: entity diff --git a/Resources/Prototypes/Entities/Mobs/Player/dragon.yml b/Resources/Prototypes/Entities/Mobs/Player/dragon.yml index 0eda8ae9d7..cb9dc1c911 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/dragon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/dragon.yml @@ -14,6 +14,8 @@ makeSentient: true name: ghost-role-information-space-dragon-name description: ghost-role-information-space-dragon-description + raffle: + settings: default - type: GhostTakeoverAvailable - type: HTN rootTask: @@ -89,6 +91,12 @@ speedModifierThresholds: 250: 0.7 400: 0.5 + # disable taking damage from fire, since its a fire breathing dragon + - type: Flammable + damage: + types: {} + - type: Temperature + heatDamageThreshold: 800 - type: Metabolizer solutionOnBody: false updateInterval: 0.25 @@ -139,10 +147,33 @@ spawnRiftAction: ActionSpawnRift - type: GenericAntag rule: Dragon + - type: ActionGun + action: ActionDragonsBreath + gunProto: DragonsBreathGun - type: GuideHelp guides: - MinorAntagonists +- type: entity + noSpawn: true + id: DragonsBreathGun + name: dragon's lung + description: For dragon's breathing + components: + - type: RechargeBasicEntityAmmo + rechargeCooldown: 5 + rechargeSound: + path: /Audio/Animals/space_dragon_roar.ogg + - type: BasicEntityAmmoProvider + proto: ProjectileDragonsBreath + capacity: 1 + count: 1 + - type: Gun + soundGunshot: + path: /Audio/Animals/space_dragon_roar.ogg + soundEmpty: null + projectileSpeed: 5 + - type: entity parent: BaseMobDragon id: MobDragonDungeon @@ -150,6 +181,8 @@ components: - type: GhostRole description: ghost-role-information-space-dragon-dungeon-description + raffle: + settings: default - type: SlowOnDamage speedModifierThresholds: 100: 0.7 @@ -179,6 +212,7 @@ state: icon event: !type:DragonSpawnRiftActionEvent useDelay: 1 + priority: 3 - type: entity id: ActionDevour @@ -190,3 +224,18 @@ icon: { sprite : Interface/Actions/devour.rsi, state: icon } iconOn: { sprite : Interface/Actions/devour.rsi, state: icon-on } event: !type:DevourActionEvent + priority: 1 + +- type: entity + noSpawn: true + id: ActionDragonsBreath + name: "[color=orange]Dragon's Breath[/color]" + description: Spew out flames at anyone foolish enough to attack you! + components: + - type: WorldTargetAction + # TODO: actual sprite + icon: { sprite : Objects/Weapons/Guns/Projectiles/magic.rsi, state: fireball } + event: !type:ActionGunShootEvent + priority: 2 + checkCanAccess: false + range: 0 diff --git a/Resources/Prototypes/Entities/Mobs/Player/dwarf.yml b/Resources/Prototypes/Entities/Mobs/Player/dwarf.yml index fb84ad3650..f8f0ddd2b9 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/dwarf.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/dwarf.yml @@ -2,6 +2,4 @@ save: false name: Urist McHands The Dwarf parent: BaseMobDwarf - id: MobDwarf - components: - - type: PotentialPsionic #Nyano - Summary: makes potentially psionic. \ No newline at end of file + id: MobDwarf \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Mobs/Player/familiars.yml b/Resources/Prototypes/Entities/Mobs/Player/familiars.yml index 6510c8af99..95c87fcb63 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/familiars.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/familiars.yml @@ -1,6 +1,75 @@ +- type: entity + parent: + - BaseMob + - MobCombat + - MobDamageable + id: BaseMobPsionicFamiliar + abstract: true + components: + - type: Sprite + drawdepth: Mobs + layers: + - map: ["enum.DamageStateVisualLayers.Base"] + state: bat + sprite: Mobs/Animals/bat.rsi + - type: GhostRole + makeSentient: true + allowMovement: true + allowSpeech: true + name: ghost-role-information-familiar-name + description: ghost-role-information-familiar-description + rules: ghost-role-information-familiar-rules + raffle: + settings: default + - type: GhostTakeoverAvailable + - type: Tag + tags: + - DoorBumpOpener + - type: MobThresholds + thresholds: + 0: Alive + 50: Dead + - type: Damageable + damageContainer: CorporealSpirit + damageModifierSet: CorporealSpirit + - type: MindContainer + showExamineInfo: false + - type: NpcFactionMember + factions: + - PsionicInterloper + - type: Alerts + - type: Familiar + - type: Psionic + removable: false + psychognomicDescriptors: + - p-descriptor-bound + - p-descriptor-cyclic + - type: InnatePsionicPowers + powersToAdd: + - TelepathyPower + - type: HTN + rootTask: + task: MeleePsionicFamiliarCompound + blackboard: + IdleRange: !type:Single + 3.5 + FollowCloseRange: !type:Single + 2.0 + FollowRange: !type:Single + 3.0 + - type: NPCRetaliation + attackMemoryLength: 10 + retaliateFriendlies: true + - type: PsionicFamiliar + - type: Dispellable + - type: DamageOnDispel + damage: + types: + Heat: 100 + - type: entity name: Remilia - parent: MobBat + parent: BaseMobPsionicFamiliar id: MobBatRemilia description: The chaplain's familiar. Likes fruit. components: @@ -23,19 +92,47 @@ - type: Access tags: - Chapel - - type: Damageable # Nyanotrasen - Corporeal Spirit allows Holy water to do damage - damageContainer: CorporealSpirit - damageModifierSet: CorporealSpirit - - type: MindContainer - showExamineInfo: true - - type: NpcFactionMember - factions: - - PetsNT - - PsionicInterloper #Nyano - Summary: makes a part of the psionic faction. - - type: Alerts - - type: Familiar - - type: Psionic #Nyano - Summary: Makes psionic on creation. - removable: false + - type: InnatePsionicPowers + powersToAdd: + - TelepathyPower + - XenoglossyPower + - type: MovementSpeedModifier + baseWalkSpeed : 3 + baseSprintSpeed : 6 + - type: Speech + speechSounds: Squeak + speechVerb: SmallMob + allowedEmotes: ['Squeak'] + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeCircle + radius: 0.25 + density: 0.8 + mask: + - FlyingMobMask + layer: + - FlyingMobLayer + - type: InteractionPopup + successChance: 0.2 + interactSuccessString: petting-success-soft-floofy + interactFailureString: petting-failure-bat + interactSuccessSpawn: EffectHearts + interactSuccessSound: + path: /Audio/Animals/fox_squeak.ogg + - type: MeleeWeapon + soundHit: + path: /Audio/Effects/bite.ogg + angle: 0 + animation: WeaponArcBite + damage: + types: + Piercing: 5 + - type: MobThresholds + thresholds: + 0: Alive + 30: Dead - type: entity name: Cerberus @@ -50,6 +147,8 @@ name: ghost-role-information-cerberus-name description: ghost-role-information-cerberus-description rules: ghost-role-information-cerberus-rules + raffle: + settings: default - type: GhostTakeoverAvailable - type: MeleeWeapon altDisarm: false @@ -66,7 +165,7 @@ - type: NpcFactionMember factions: - Syndicate - - PsionicInterloper #Nyano - Summary: makes part of the psionoic faction. + - PsionicInterloper - type: InteractionPopup successChance: 0.5 interactSuccessString: petting-success-corrupted-corgi @@ -91,10 +190,57 @@ showExamineInfo: true - type: Familiar - type: Dispellable - - type: Psionic #Nyano - Summary: makes psionic on creation. + - type: Psionic removable: false + - type: InnatePsionicPowers + powersToAdd: + - TelepathyPower - type: Vocal sounds: Male: Cerberus Female: Cerberus Unsexed: Cerberus + +- type: entity + name: imp familiar + parent: BaseMobPsionicFamiliar + id: MobPsionicFamiliarImp + description: A living mote of flame summoned from Gehenna. + components: + - type: Sprite + drawdepth: Mobs + layers: + - map: ["enum.DamageStateVisualLayers.Base"] + state: imp + sprite: Mobs/Demons/imp.rsi + - type: InnatePsionicPowers + powersToAdd: + - TelepathyPower + - PyrokineticFlare + - XenoglossyPower + - type: MeleeWeapon + damage: + types: + Heat: 9 + soundHit: + path: /Audio/Weapons/Guns/Hits/energy_meat1.ogg + params: + variation: 0.250 + volume: -10 + - type: PointLight + radius: 2 + energy: 30 + color: "#ff4500" + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeCircle + radius: 0.35 + density: 13 + mask: + - Opaque + layer: + - MobLayer + - type: RandomMetadata + nameSegments: [names_golem] diff --git a/Resources/Prototypes/Entities/Mobs/Player/guardian.yml b/Resources/Prototypes/Entities/Mobs/Player/guardian.yml index ae4a370f9d..7bb8547cfa 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/guardian.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/guardian.yml @@ -13,6 +13,8 @@ makeSentient: true name: ghost-role-information-guardian-name description: ghost-role-information-guardian-description + raffle: + settings: default - type: GhostTakeoverAvailable - type: Input context: "human" @@ -95,17 +97,14 @@ - type: MeleeSpeech - type: UserInterface interfaces: - - key: enum.MeleeSpeechUiKey.Key - type: MeleeSpeechBoundUserInterface + enum.MeleeSpeechUiKey.Key: + type: MeleeSpeechBoundUserInterface - type: Actions - type: Guardian - - type: InteractionPopup - interactSuccessString: petting-success-holo - interactFailureString: petting-failure-holo - successChance: 0.7 - type: Tag tags: - CannotSuicide + - NoPaint # From the uplink injector - type: entity @@ -120,6 +119,8 @@ makeSentient: true name: ghost-role-information-holoparasite-name description: ghost-role-information-holoparasite-description + raffle: + settings: default - type: GhostTakeoverAvailable - type: NameIdentifier group: Holoparasite @@ -150,6 +151,8 @@ makeSentient: true name: ghost-role-information-ifrit-name description: ghost-role-information-ifrit-description + raffle: + settings: default - type: GhostTakeoverAvailable - type: RandomSprite available: @@ -163,6 +166,11 @@ map: [ "enum.DamageStateVisualLayers.BaseUnshaded" ] color: "#40a7d7" shader: unshaded + - type: Psionic + removable: false + - type: InnatePsionicPowers + powersToAdd: + - TelepathyPower - type: entity name: HoloClown @@ -176,6 +184,8 @@ makeSentient: true name: ghost-role-information-holoclown-name description: ghost-role-information-holoclown-description + raffle: + settings: default - type: GhostTakeoverAvailable - type: NameIdentifier group: Holoparasite @@ -214,6 +224,7 @@ tags: - CannotSuicide - FootstepSound + - NoPaint - type: Inventory templateId: holoclown - type: Hands diff --git a/Resources/Prototypes/Entities/Mobs/Player/harpy.yml b/Resources/Prototypes/Entities/Mobs/Player/harpy.yml index fa6aa98d93..f4222301b5 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/harpy.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/harpy.yml @@ -21,4 +21,4 @@ - type: NpcFactionMember factions: - NanoTrasen - - type: PotentialPsionic + - Birb diff --git a/Resources/Prototypes/Entities/Mobs/Player/human.yml b/Resources/Prototypes/Entities/Mobs/Player/human.yml index 6197c82c02..aa87f81a83 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/human.yml @@ -3,8 +3,6 @@ name: Urist McHands parent: BaseMobHuman id: MobHuman - components: - - type: PotentialPsionic #Nyano - Summary: makes potentially psionic. #Syndie - type: entity @@ -48,9 +46,8 @@ components: - type: NukeOperative - type: RandomHumanoidAppearance - - type: PsionicBonusChance #Nyano - Summary: makes more likely to be psionic. - multiplier: 7 - warn: false + - type: Psionic + powerRollMultiplier: 7 - type: entity noSpawn: true @@ -70,9 +67,8 @@ - type: NpcFactionMember factions: - Syndicate - - type: PsionicBonusChance #Nyano - Summary: makes more likely to be psionic. - multiplier: 7 - warn: false + - type: Psionic + powerRollMultiplier: 7 # Space Ninja - type: entity diff --git a/Resources/Prototypes/Entities/Mobs/Player/humanoid.yml b/Resources/Prototypes/Entities/Mobs/Player/humanoid.yml index 9a8ca5be00..a4bfe2289b 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/humanoid.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/humanoid.yml @@ -21,11 +21,15 @@ - type: randomHumanoidSettings id: DeathSquad randomizeName: false + speciesBlacklist: + - Shadowkin components: - type: MindShield - type: GhostRole name: ghost-role-information-Death-Squad-name description: ghost-role-information-Death-Squad-description + raffle: + settings: short - type: GhostTakeoverAvailable - type: Loadout prototypes: [ DeathSquadGear ] @@ -57,11 +61,15 @@ - type: randomHumanoidSettings id: ERTLeader randomizeName: false + speciesBlacklist: + - Shadowkin components: - type: MindShield - type: GhostRole name: ghost-role-information-ert-leader-name description: ghost-role-information-ert-leader-description + raffle: + settings: short - type: GhostTakeoverAvailable - type: Loadout prototypes: [ ERTLeaderGear ] @@ -92,6 +100,8 @@ - type: GhostRole name: ghost-role-information-ert-leader-name description: ghost-role-information-ert-leader-description + raffle: + settings: short - type: GhostTakeoverAvailable - type: Loadout prototypes: [ ERTLeaderGearEVA ] @@ -114,6 +124,8 @@ - type: GhostRole name: ghost-role-information-ert-leader-name description: ghost-role-information-ert-leader-description + raffle: + settings: short - type: GhostTakeoverAvailable - type: Loadout prototypes: [ ERTLeaderGearEVALecter ] @@ -145,6 +157,8 @@ - type: GhostRole name: ghost-role-information-ert-chaplain-name description: ghost-role-information-ert-chaplain-description + raffle: + settings: short - type: GhostTakeoverAvailable - type: RandomMetadata nameSegments: @@ -174,6 +188,8 @@ - type: GhostRole name: ghost-role-information-ert-chaplain-name description: ghost-role-information-ert-chaplain-description + raffle: + settings: short - type: GhostTakeoverAvailable - type: Loadout prototypes: [ ERTChaplainGearEVA ] @@ -205,6 +221,8 @@ - type: GhostRole name: ghost-role-information-ert-janitor-name description: ghost-role-information-ert-janitor-description + raffle: + settings: short - type: GhostTakeoverAvailable - type: RandomMetadata nameSegments: @@ -234,6 +252,8 @@ - type: GhostRole name: ghost-role-information-ert-janitor-name description: ghost-role-information-ert-janitor-description + raffle: + settings: short - type: GhostTakeoverAvailable - type: Loadout prototypes: [ ERTJanitorGearEVA ] @@ -265,6 +285,8 @@ - type: GhostRole name: ghost-role-information-ert-engineer-name description: ghost-role-information-ert-engineer-description + raffle: + settings: short - type: GhostTakeoverAvailable - type: RandomMetadata nameSegments: @@ -294,6 +316,8 @@ - type: GhostRole name: ghost-role-information-ert-engineer-name description: ghost-role-information-ert-engineer-description + raffle: + settings: short - type: GhostTakeoverAvailable - type: Loadout prototypes: [ ERTEngineerGearEVA ] @@ -325,6 +349,8 @@ - type: GhostRole name: ghost-role-information-ert-security-name description: ghost-role-information-ert-security-description + raffle: + settings: short - type: GhostTakeoverAvailable - type: RandomMetadata nameSegments: @@ -354,6 +380,8 @@ - type: GhostRole name: ghost-role-information-ert-security-name description: ghost-role-information-ert-security-description + raffle: + settings: short - type: GhostTakeoverAvailable - type: Loadout prototypes: [ ERTSecurityGearEVA ] @@ -375,6 +403,8 @@ - type: GhostRole name: ghost-role-information-ert-security-name description: ghost-role-information-ert-security-description + raffle: + settings: short - type: GhostTakeoverAvailable - type: Loadout prototypes: [ ERTSecurityGearEVALecter ] @@ -406,6 +436,8 @@ - type: GhostRole name: ghost-role-information-ert-medical-name description: ghost-role-information-ert-medical-description + raffle: + settings: short - type: GhostTakeoverAvailable - type: RandomMetadata nameSegments: @@ -435,6 +467,8 @@ - type: GhostRole name: ghost-role-information-ert-medical-name description: ghost-role-information-ert-medical-description + raffle: + settings: short - type: GhostTakeoverAvailable - type: Loadout prototypes: [ ERTMedicalGearEVA ] @@ -456,6 +490,8 @@ - type: randomHumanoidSettings id: CBURNAgent + speciesBlacklist: + - Shadowkin components: - type: MindShield - type: Loadout @@ -463,6 +499,8 @@ - type: GhostRole name: ghost-role-information-cburn-agent-name description: ghost-role-information-cburn-agent-description + raffle: + settings: short - type: GhostTakeoverAvailable - type: RandomMetadata nameSegments: @@ -484,11 +522,15 @@ - type: randomHumanoidSettings id: CentcomOfficial + speciesBlacklist: + - Shadowkin components: - type: MindShield - type: GhostRole name: ghost-role-information-centcom-official-name description: ghost-role-information-centcom-official-description + raffle: + settings: default - type: GhostTakeoverAvailable - type: Loadout prototypes: [ CentcomGear ] @@ -510,6 +552,8 @@ - type: randomHumanoidSettings id: SyndicateAgent + speciesBlacklist: + - Shadowkin components: - type: Loadout prototypes: [SyndicateOperativeGearExtremelyBasic] @@ -526,11 +570,12 @@ - type: randomHumanoidSettings id: NukeOp + speciesBlacklist: + - Shadowkin components: - type: NukeOperative - - type: PsionicBonusChance #Nyano - Summary: makes more likely to be psionic. - multiplier: 7 - warn: false + - type: Psionic + powerRollMultiplier: 7 - type: entity id: RandomHumanoidSpawnerCluwne @@ -549,10 +594,318 @@ - type: randomHumanoidSettings id: Cluwne + speciesBlacklist: + - Shadowkin randomizeName: false components: - type: GhostRole name: ghost-role-information-cluwne-name description: ghost-role-information-cluwne-description + raffle: + settings: default - type: GhostTakeoverAvailable - type: Cluwne + +## Lost Cargo technician + +- type: entity + name: lost cargo technician spawner + id: LostCargoTechnicianSpawner + parent: MarkerBase + components: + - type: Sprite + layers: + - state: red + - sprite: Objects/Tools/appraisal-tool.rsi + state: icon + - type: RandomSpawner + prototypes: + - RandomHumanoidLostCargoTechnician + chance: 1 + +- type: entity + id: RandomHumanoidLostCargoTechnician + name: lost cargo technician ghost role + components: + - type: Sprite + sprite: Objects/Tools/appraisal-tool.rsi + state: icon + - type: RandomHumanoidSpawner + settings: LostCargoTechnician + +- type: randomHumanoidSettings + id: LostCargoTechnician + randomizeName: false + components: + - type: GhostRole + name: ghost-role-information-lost-cargo-technical-name + description: ghost-role-information-lost-cargo-technical-description + rules: ghost-role-information-lost-cargo-technical-rules + raffle: + settings: short + - type: GhostTakeoverAvailable + - type: Loadout + prototypes: [ CargoTechGear ] + - type: RandomMetadata + nameSegments: + - names_first + - names_last + +# Clown troupe + +- type: entity + name: clown troupe spawner + id: ClownTroupeSpawner + parent: MarkerBase + components: + - type: Sprite + layers: + - state: red + - sprite: Objects/Fun/bikehorn.rsi + state: icon + - type: RandomSpawner + prototypes: + - RandomHumanoidClownTroupe + rarePrototypes: + - RandomHumanoidClownTroupeBanana + rareChance: 0.3 + +- type: entity + id: RandomHumanoidClownTroupe + name: clown troupe ghost role + components: + - type: Sprite + sprite: Objects/Tools/appraisal-tool.rsi + state: icon + - type: RandomHumanoidSpawner + settings: ClownTroupe + +- type: entity + id: RandomHumanoidClownTroupeBanana + name: banana clown troupe + parent: RandomHumanoidClownTroupe + components: + - type: RandomHumanoidSpawner + settings: ClownTroupeBanana + +- type: randomHumanoidSettings + id: ClownTroupe + randomizeName: false + components: + - type: GhostRole + name: ghost-role-information-clown-troupe-name + description: ghost-role-information-clown-troupe-description + rules: ghost-role-information-clown-troupe-rules + raffle: + settings: short + - type: GhostTakeoverAvailable + - type: Loadout + prototypes: [ ClownGear ] + - type: RandomMetadata + nameSegments: + - names_clown + +- type: randomHumanoidSettings + id: ClownTroupeBanana + randomizeName: false + components: + - type: GhostRole + name: ghost-role-information-clown-troupe-name + description: ghost-role-information-clown-troupe-description + rules: ghost-role-information-clown-troupe-rules + - type: GhostTakeoverAvailable + - type: Loadout + prototypes: [ BananaClown ] + - type: RandomMetadata + nameSegments: + - names_clown + +# Traveling exotic chef + +- type: entity + name: traveling chef spawner + id: TravelingChefSpawner + parent: MarkerBase + components: + - type: Sprite + layers: + - state: red + - sprite: Objects/Weapons/Melee/kitchen_knife.rsi + state: icon + - type: RandomSpawner + prototypes: + - RandomHumanoidTravelingChef + +- type: entity + id: RandomHumanoidTravelingChef + name: traveling chef ghost role + components: + - type: Sprite + sprite: Objects/Tools/appraisal-tool.rsi + state: icon + - type: RandomHumanoidSpawner + settings: TravelingChef + +- type: randomHumanoidSettings + id: TravelingChef + randomizeName: false + components: + - type: GhostRole + name: ghost-role-information-traveling-chef-name + description: ghost-role-information-traveling-chef-description + rules: ghost-role-information-traveling-chef-rules + raffle: + settings: short + - type: GhostTakeoverAvailable + - type: Loadout + prototypes: [ ChefGear ] + - type: RandomMetadata + nameSegments: + - names_first + - names_last + +# Disaster victim + +- type: entity + name: disaster victim spawner + id: DisasterVictimSpawner + parent: MarkerBase + components: + - type: Sprite + layers: + - state: red + - sprite: Clothing/OuterClothing/Hardsuits/basic.rsi + state: icon + - type: RandomSpawner + prototypes: + - RandomHumanoidDisasterVictimRD + - RandomHumanoidDisasterVictimCMO + - RandomHumanoidDisasterVictimCaptain + - MobSkeletonCloset + +- type: entity + id: RandomHumanoidDisasterVictimRD + name: disaster victim RD ghost role + components: + - type: Sprite + sprite: Clothing/OuterClothing/Hardsuits/basic.rsi + state: icon + - type: RandomHumanoidSpawner + settings: DisasterVictimResearchDirector + +- type: entity + id: RandomHumanoidDisasterVictimCMO + parent: RandomHumanoidDisasterVictimRD + name: disaster victim CMO ghost role + components: + - type: RandomHumanoidSpawner + settings: DisasterVictimCMO + +- type: entity + id: RandomHumanoidDisasterVictimCaptain + parent: RandomHumanoidDisasterVictimRD + name: disaster victim Captain ghost role + components: + - type: RandomHumanoidSpawner + settings: DisasterVictimCaptain + +- type: randomHumanoidSettings + id: DisasterVictimResearchDirector + randomizeName: false + components: + - type: GhostRole + name: ghost-role-information-disaster-victim-name + description: ghost-role-information-disaster-victim-description + rules: ghost-role-information-disaster-victim-rules + raffle: + settings: default + - type: GhostTakeoverAvailable + - type: Loadout + prototypes: [ ResearchDirectorGear ] + - type: RandomMetadata + nameSegments: + - names_first + - names_last + +- type: randomHumanoidSettings + id: DisasterVictimCMO + randomizeName: false + components: + - type: GhostRole + name: ghost-role-information-disaster-victim-name + description: ghost-role-information-disaster-victim-description + rules: ghost-role-information-disaster-victim-rules + raffle: + settings: default + - type: GhostTakeoverAvailable + - type: Loadout + prototypes: [ CMOGear ] + - type: RandomMetadata + nameSegments: + - names_first + - names_last + +- type: randomHumanoidSettings + id: DisasterVictimCaptain + randomizeName: false + components: + - type: GhostRole + name: ghost-role-information-disaster-victim-name + description: ghost-role-information-disaster-victim-description + rules: ghost-role-information-disaster-victim-rules + raffle: + settings: default + - type: GhostTakeoverAvailable + - type: Loadout + prototypes: [ CaptainGear ] + - type: RandomMetadata + nameSegments: + - names_first + - names_last + +# Syndie Disaster Victim + +- type: entity + name: syndie disaster victim spawner + id: SyndieDisasterVictimSpawner + parent: MarkerBase + components: + - type: Sprite + layers: + - state: red + - sprite: Structures/Decoration/banner.rsi + state: banner_syndicate + - type: RandomSpawner + prototypes: + - RandomHumanoidSyndieDisasterVictim + +- type: entity + id: RandomHumanoidSyndieDisasterVictim + name: syndie disaster victim ghost role + components: + - type: Sprite + sprite: Structures/Decoration/banner.rsi + state: banner_syndicate + - type: RandomHumanoidSpawner + settings: SyndieDisasterVictim + +- type: randomHumanoidSettings + id: SyndieDisasterVictim + randomizeName: false + components: + - type: NpcFactionMember + factions: + - Syndicate + - type: GhostRole + name: ghost-role-information-syndie-disaster-victim-name + description: ghost-role-information-syndie-disaster-victim-description + rules: ghost-role-information-syndie-disaster-victim-rules + raffle: + settings: short + - type: GhostTakeoverAvailable + - type: Loadout + prototypes: [ SyndicateOperativeGearCivilian ] + - type: RandomMetadata + nameSegments: + - names_first + - names_last diff --git a/Resources/Prototypes/Entities/Mobs/Player/ipc.yml b/Resources/Prototypes/Entities/Mobs/Player/ipc.yml new file mode 100644 index 0000000000..85aec21ac6 --- /dev/null +++ b/Resources/Prototypes/Entities/Mobs/Player/ipc.yml @@ -0,0 +1,137 @@ +- type: entity + id: MobIPC + parent: PlayerSiliconHumanoidBase + name: Urist McPositronic + description: A positronic brain in a metal body. + components: + - type: PowerCellSlot + cellSlotId: cell_slot + fitsInCharger: true + - type: ItemSlots + slots: + cell_slot: + locked: true + name: power-cell-slot-component-slot-name-default + startingItem: PowerCellMedium + - type: BatterySlotRequiresLock + itemSlot: cell_slot + - type: EncryptionHolderRequiresLock + - type: SiliconEmitSoundOnDrained + sound: "/Audio/Weapons/Guns/EmptyAlarm/smg_empty_alarm.ogg" + minInterval: 15 + maxInterval: 30 + popUp: "silicon-power-low" + - type: Lock + locked: true + lockOnClick: false + unlockOnClick: false + # Por algum motivo esse componente é bem bugado, então o "LockTime" é quem diz o tempo de tudo enquanto o unlock só está servindo como um bool (???) + lockTime: 5 + unlockTime: 5 + - type: NpcFactionMember + factions: + - NanoTrasen + - type: StandingState + - type: MobState + allowedStates: + - Alive + - Critical + - Dead + - type: MobThresholds + thresholds: + 0: Alive + 119.999: Critical # TO make it almost impossible + 120: Dead + stateAlertDict: + Alive: BorgHealth + Critical: BorgCrit + Dead: BorgDead + - type: TypingIndicator + proto: robot + - type: Destructible + thresholds: + - trigger: + !type:DamageTypeTrigger + damageType: Blunt + damage: 400 + behaviors: + - !type:GibBehavior { } + - type: SlowOnDamage + speedModifierThresholds: + 60: 0.7 + 90: 0.5 + 120: 0.3 + - type: SiliconDownOnDead + - type: Inventory + templateId: ipc + - type: GuideHelp + guides: + - IPCs + - type: Silicon + entityType: enum.SiliconType.Player + batteryPowered: true + drainPerSecond: 1.5 + chargeThresholdMid: 0.80 + chargeThresholdLow: 0.35 + chargeThresholdCritical: 0.10 + speedModifierThresholds: + 4: 1 + 3: 1 + 2: 0.80 + 1: 0.45 + 0: 0.00 + - type: BatteryDrinker + - type: EncryptionKeyHolder + keySlots: 4 + examineWhileLocked: false + keysExtractionMethod: Cutting + keysUnlocked: false + - type: ActiveRadio + - type: IntrinsicRadioReceiver + - type: IntrinsicRadioTransmitter + - type: DeadStartupButton + sound: + path: /Audio/Effects/Silicon/startup.ogg + - type: EmitBuzzWhileDamaged + - type: CanHostGuardian + - type: LanguageKnowledge + speaks: + - TauCetiBasic + - RobotTalk + understands: + - TauCetiBasic + - RobotTalk + - type: WeldingHealable + - type: PsionicInsulation + - type: OfferItem + - type: LayingDown + - type: Carriable + - type: StepTriggerImmune + whitelist: + types: + - Shard + - type: Fixtures + fixtures: # TODO: This needs a second fixture just for mob collisions. + fix1: + shape: + !type:PhysShapeCircle + radius: 0.35 + density: 185 + restitution: 0.0 + mask: + - MobMask + layer: + - MobLayer + +- type: entity + save: false + name: Urist McPositronic + parent: MobHumanDummy + id: MobIPCDummy + noSpawn: true + description: A dummy IPC meant to be used in character setup. + components: + - type: HumanoidAppearance + species: IPC + - type: Inventory + templateId: ipc diff --git a/Resources/Prototypes/Entities/Mobs/Player/moth.yml b/Resources/Prototypes/Entities/Mobs/Player/moth.yml index ffdb36d86b..e79ba1a454 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/moth.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/moth.yml @@ -1,7 +1,5 @@ - type: entity save: false name: Urist McFluff - parent: BaseMobMoth - id: MobMoth - components: - - type: PotentialPsionic #Nyano - Summary: makes potentially psionic. \ No newline at end of file + parent: BaseMobMoth + id: MobMoth \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Mobs/Player/observer.yml b/Resources/Prototypes/Entities/Mobs/Player/observer.yml index 0086be81d9..c92595ffc9 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/observer.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/observer.yml @@ -32,6 +32,7 @@ - PsionicInvisibility - Ghost - Normal + - Ethereal - type: Input context: "ghost" - type: Examiner diff --git a/Resources/Prototypes/Entities/Mobs/Player/reptilian.yml b/Resources/Prototypes/Entities/Mobs/Player/reptilian.yml index 71d7422297..b332f573fb 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/reptilian.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/reptilian.yml @@ -1,9 +1,8 @@ - type: entity save: false - name: Urisst' Mzhand + name: Urist McScales + suffix: Urisst' Mzhand parent: BaseMobReptilian id: MobReptilian - components: - - type: PotentialPsionic #Nyano - Summary: makes potentially psionic. #Weh diff --git a/Resources/Prototypes/Entities/Mobs/Player/shadowkin.yml b/Resources/Prototypes/Entities/Mobs/Player/shadowkin.yml new file mode 100644 index 0000000000..2a58fe5c1f --- /dev/null +++ b/Resources/Prototypes/Entities/Mobs/Player/shadowkin.yml @@ -0,0 +1,5 @@ +- type: entity + save: false + name: Urist McShadow + parent: MobShadowkinBase + id: MobShadowkin \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Mobs/Player/silicon_base.yml b/Resources/Prototypes/Entities/Mobs/Player/silicon_base.yml new file mode 100644 index 0000000000..0163f6727e --- /dev/null +++ b/Resources/Prototypes/Entities/Mobs/Player/silicon_base.yml @@ -0,0 +1,309 @@ +- type: entity + save: false + id: PlayerSiliconHumanoidBase + parent: [BaseMob, MobDamageable, MobCombat, MobAtmosExposed, MobFlammable] + abstract: true + components: + - type: ContentEye + - type: CameraRecoil + - type: Reactive + groups: + Flammable: [Touch] + Extinguish: [Touch] + Acidic: [Touch] + reactions: + - reagents: [Water, SpaceCleaner] + methods: [Touch] + effects: + - !type:WashCreamPieReaction + - type: DamageOnHighSpeedImpact + damage: + types: + Blunt: 10 + soundHit: + path: /Audio/Effects/hit_kick.ogg + - type: Damageable + damageContainer: Silicon + damageModifierSet: IPC + - type: InteractionOutline + - type: MovementSpeedModifier + baseWalkSpeed: 4 + baseSprintSpeed: 3 + - type: ZombieImmune + - type: DoAfter + - type: RotationVisuals + horizontalRotation: 90 + - type: Examiner + # - type: Recyclable + # safe: false + # - type: EyeProtection # You'll want this if your robot can't wear glasses, like an IPC. + # protectionTime: 12 + - type: Silicon + entityType: enum.SiliconType.Player + batteryPowered: false # Needs to also have a battery! + chargeThresholdMid: 0.60 + chargeThresholdLow: 0.30 + chargeThresholdCritical: 0 + speedModifierThresholds: + 4: 1 + 3: 1 + 2: 0.80 + 1: 0.45 + 0: 0.00 + + - type: Temperature + heatDamageThreshold: 325 + coldDamageThreshold: 260 + currentTemperature: 310.15 + specificHeat: 42 + coldDamage: + types: + Cold: 0.1 #per second, scales with temperature & other constants + heatDamage: + types: + Heat: 3 #per second, scales with temperature & other constants + atmosTemperatureTransferEfficiency: 0.05 + - type: Deathgasp + prototype: SiliconDeathgasp + needsCritical: false + - type: MobState + allowedStates: + - Alive + - Dead + - type: MobThresholds + thresholds: + 0: Alive + 165: Dead + - type: Destructible + thresholds: + - trigger: !type:DamageTrigger + damage: 500 + behaviors: + - !type:GibBehavior { } + - type: Icon + sprite: Mobs/Species/IPC/parts.rsi + state: full + - type: Sprite + noRot: true + drawdepth: Mobs + layers: + - map: ["enum.HumanoidVisualLayers.Chest"] + - map: ["enum.HumanoidVisualLayers.Head"] + - map: ["enum.HumanoidVisualLayers.Snout"] + - map: ["enum.HumanoidVisualLayers.Eyes"] + - map: ["enum.HumanoidVisualLayers.RArm"] + - map: ["enum.HumanoidVisualLayers.LArm"] + - map: ["enum.HumanoidVisualLayers.RLeg"] + - map: ["enum.HumanoidVisualLayers.LLeg"] + - shader: StencilClear + sprite: Mobs/Species/Human/parts.rsi + state: l_leg + - shader: StencilMask + map: ["enum.HumanoidVisualLayers.StencilMask"] + sprite: Mobs/Customization/masking_helpers.rsi + state: female_full + visible: false + - map: ["enum.HumanoidVisualLayers.LFoot"] + - map: ["enum.HumanoidVisualLayers.RFoot"] + - map: ["socks"] + - map: ["underpants"] + - map: ["undershirt"] + - map: ["jumpsuit"] + - map: ["enum.HumanoidVisualLayers.LHand"] + - map: ["enum.HumanoidVisualLayers.RHand"] + - map: ["enum.HumanoidVisualLayers.Handcuffs"] + color: "#ffffff" + sprite: Objects/Misc/handcuffs.rsi + state: body-overlay-2 + visible: false + - map: ["id"] + - map: ["gloves"] + - map: ["shoes"] + - map: ["ears"] + - map: ["outerClothing"] + - map: ["eyes"] + - map: ["belt"] + - map: ["neck"] + - map: ["back"] + - map: ["enum.HumanoidVisualLayers.FacialHair"] + - map: ["enum.HumanoidVisualLayers.Hair"] + - map: ["enum.HumanoidVisualLayers.HeadSide"] + - map: ["enum.HumanoidVisualLayers.HeadTop"] + - map: ["mask"] + - map: ["head"] + - map: ["pocket1"] + - map: ["pocket2"] + - map: ["enum.HumanoidVisualLayers.Tail"] + - map: ["clownedon"] # Dynamically generated + sprite: "Effects/creampie.rsi" + state: "creampie_human" + visible: false + + #- type: Bloodstream This is left commented out because it's not necessary for a robot, but you may want it. + # damageBleedModifiers: BloodlossIPC + # bloodReagent: Oil + # bleedReductionAmount: 0 + # bloodMaxVolume: 500 + # chemicalMaxVolume: 0 + # bleedPuddleThreshold: 3 + # bleedRefreshAmount: 0 + # bloodLossThreshold: 0 + # maxBleedAmount: 14 + # bloodlossDamage: + # types: + # Burn: 1.5 + # bloodlossHealDamage: + # types: + # Burn: 0 + - type: Flashable + - type: Flammable + fireSpread: true + canResistFire: true + damage: + types: + Heat: 0.75 #per second, scales with number of fire 'stacks' + # - type: Barotrauma # Not particularly modifiable. In the future, some response to pressure changes would be nice. + # damage: + # types: + # Blunt: 0.28 #per second, scales with pressure and other constants. + - type: Identity + # soundHit: + # path: /Audio/Effects/metalbreak.ogg + - type: RangedDamageSound + soundGroups: + Brute: + collection: MetalBulletImpact + soundTypes: + Heat: + collection: MetalLaserImpact + - type: Tag + tags: + - CanPilot + - FootstepSound + - DoorBumpOpener + - type: Hands + - type: Inventory + templateId: human + - type: InventorySlots + - type: Appearance + - type: GenericVisualizer + visuals: + enum.CreamPiedVisuals.Creamed: + clownedon: # Not 'creampied' bc I can already see Skyrat complaining about conflicts. + True: { visible: true } + False: { visible: false } + - type: Cuffable + - type: Mood + - type: AnimationPlayer + - type: Buckle + - type: CreamPied + - type: Stripping + - type: Strippable + - type: SurgeryTarget + - type: Targeting + - type: UserInterface + interfaces: + enum.HumanoidMarkingModifierKey.Key: + type: HumanoidMarkingModifierBoundUserInterface + enum.StrippingUiKey.Key: + type: StrippableBoundUserInterface + enum.SurgeryUIKey.Key: + type: SurgeryBui + - type: Emoting + - type: Grammar + attributes: + proper: true + - type: Climbing + - type: StandingState + - type: MindContainer + showExamineInfo: true + - type: SSDIndicator + - type: CanEscapeInventory + - type: HumanoidAppearance + species: IPC + - type: Body + prototype: IPC + requiredLegs: 2 + - type: Ensnareable + sprite: Objects/Misc/ensnare.rsi + - type: Speech + speechSounds: Pai + - type: Vocal + wilhelm: "/Audio/Voice/IPC/wilhelm.ogg" + sounds: + Male: UnisexIPC + Female: UnisexIPC + Unsexed: UnisexIPC + - type: MeleeWeapon + hidden: true + soundHit: + collection: Punch + angle: 30 + animation: WeaponArcFist + attackRate: 1 + damage: + types: + Blunt: 6 # It's tough. + - type: MobPrice + price: 1500 # Kidnapping a living person and selling them for cred is a good move. + deathPenalty: 0.01 # However they really ought to be living and intact, otherwise they're worth 100x less. + - type: Pullable + - type: Puller + + - type: BodyEmotes + soundsId: GeneralBodyEmotes + - type: DamageVisuals + thresholds: [ 10, 20, 30, 50, 70, 100 ] + targetLayers: + - "enum.HumanoidVisualLayers.Chest" + - "enum.HumanoidVisualLayers.Head" + - "enum.HumanoidVisualLayers.LArm" + - "enum.HumanoidVisualLayers.LLeg" + - "enum.HumanoidVisualLayers.RArm" + - "enum.HumanoidVisualLayers.RLeg" + damageOverlayGroups: + Brute: + sprite: Mobs/Effects/brute_damage.rsi + color: "#DD8822" + # Organs + - type: IdExaminable + - type: HealthExaminable + examinableTypes: + - Blunt + - Slash + - Piercing + - Heat + - Shock + - type: StatusEffects + allowed: + - Stun + - KnockedDown + - SlowedDown + - Stutter + - SeeingRainbows + - Electrocution + # - Drunk + - SlurredSpeech + - PressureImmunity + - Muted + # - ForcedSleep + - TemporaryBlindness + - Pacified + # - PsionicsDisabled + # - PsionicallyInsulated + - type: Blindable + - type: FireVisuals + alternateState: Standing + - type: LightningTarget + priority: 1 + lightningExplode: false + + +- type: damageModifierSet + id: IPC + coefficients: + Poison: 0 + Cold: 0.2 + Heat: 2 + Shock: 2.5 + diff --git a/Resources/Prototypes/Entities/Mobs/Player/skeleton.yml b/Resources/Prototypes/Entities/Mobs/Player/skeleton.yml index d2a3225c07..bb77cefad5 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/skeleton.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/skeleton.yml @@ -2,8 +2,6 @@ save: false parent: BaseMobSkeletonPerson id: MobSkeletonPerson - components: - - type: PotentialPsionic #Nyano - Summary: makes potentially psionic. - type: entity name: skeleton pirate @@ -13,6 +11,8 @@ - type: GhostRole name: ghost-role-information-skeleton-pirate-name description: ghost-role-information-skeleton-pirate-description + raffle: + settings: default - type: GhostTakeoverAvailable - type: Loadout prototypes: [PirateGear] @@ -27,6 +27,8 @@ - type: GhostRole name: ghost-role-information-skeleton-biker-name description: ghost-role-information-skeleton-biker-description + raffle: + settings: default - type: GhostTakeoverAvailable - type: Loadout prototypes: [SkeletonBiker] @@ -40,6 +42,8 @@ - type: GhostRole name: ghost-role-information-closet-skeleton-name description: ghost-role-information-closet-skeleton-description + raffle: + settings: default - type: GhostTakeoverAvailable - type: Loadout prototypes: [LimitedPassengerGear] diff --git a/Resources/Prototypes/Entities/Mobs/Player/slime.yml b/Resources/Prototypes/Entities/Mobs/Player/slime.yml index 79669a8fe2..d748ff8f3c 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/slime.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/slime.yml @@ -1,6 +1,4 @@ - type: entity save: false parent: BaseMobSlimePerson - id: MobSlimePerson - components: - - type: PotentialPsionic #Nyano - Summary: makes potentially psionic. \ No newline at end of file + id: MobSlimePerson \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Mobs/Player/terminator.yml b/Resources/Prototypes/Entities/Mobs/Player/terminator.yml deleted file mode 100644 index 663838e01a..0000000000 --- a/Resources/Prototypes/Entities/Mobs/Player/terminator.yml +++ /dev/null @@ -1,203 +0,0 @@ -# stuff common to flesh and endoskeleton -- type: entity - abstract: true - id: MobTerminatorBase - components: - - type: ZombieImmune # yeah no - - type: FlashImmunity # no brain to brainwash, eyes are robotic - -- type: entity - parent: [MobHuman, MobTerminatorBase] - id: MobHumanTerminator - # uses random name generator dont worry - name: exterminator - components: - - type: Terminator - - type: GenericAntag - rule: Exterminator - # reduced barotrauma damage - - type: Barotrauma - damage: - types: - Blunt: 0.1 - # 4x stamina, faster recovery - - type: Stamina - decay: 6 - cooldown: 1 - critThreshold: 400 - # immune to space drugs, pax, temporary blindness - - type: StatusEffects - allowed: - - Stun - - KnockedDown - - SlowedDown - - Stutter - - Electrocution - - Drunk - - SlurredSpeech - - RatvarianLanguage - - PressureImmunity - - Muted - - ForcedSleep - - StaminaModifier - - type: MobState - allowedStates: - - Alive - - Dead - # endoskeleton need it - - type: TransferMindOnGib - - type: MobThresholds - thresholds: - 0: Alive - # used for health display its not possible to actually fall into crit - 200: Dead - # fire!!!! - - type: Flammable - damage: - types: - Heat: 6.0 - # slightly wider thresholds - - type: Temperature - heatDamageThreshold: 390 - coldDamageThreshold: 240 - # take terminator flesh damage - - type: Damageable - damageModifierSet: CyberneticFlesh - # only organ is an endoskeleton, which is transferred when flesh dies - - type: Body - prototype: TerminatorFlesh - # endoskeleton transformation when either you would get burned to crit or killed by any damage - # you will become an endoskeleton as your last chance to kill the target - - type: Destructible - thresholds: - # the burn trigger is first incase of a bombing or nuking, it might well do over 200 damage but 100 heat is more important - - trigger: - !type:DamageTypeTrigger - damageType: Heat - damage: 100 - behaviors: - - !type:PopupBehavior - popup: terminator-endoskeleton-burn-popup - popupType: LargeCaution - - !type:GibBehavior - - trigger: - !type:DamageTrigger - damage: 200 - behaviors: - - !type:PopupBehavior - popup: terminator-endoskeleton-gib-popup - popupType: LargeCaution - - !type:GibBehavior - # faster than humans when damaged - - type: SlowOnDamage - speedModifierThresholds: - 70: 0.8 - 90: 0.6 - # arnold is very strong - - type: MeleeWeapon - damage: - types: - Blunt: 10 - Structural: 10 - - type: RandomHumanoidAppearance - - type: Tag - tags: - - CanPilot - - FootstepSound - - DoorBumpOpener - - Unimplantable # no brain to mindshield, no organic body to implant into - -- type: entity - parent: - - BaseMob - - MobCombat - - MobDamageable - - MobSiliconBase - - MobTerminatorBase - id: MobTerminatorEndoskeleton - # you are now valid - name: nt-800 "exterminator" endoskeleton - description: The inner powerhouse of Susnet's infiltrator androids. Ridiculously hard alloy on the inside, unassuming flesh on the outside. - components: - - type: HumanoidAppearance - species: Terminator - - type: MovementSpeedModifier - baseWalkSpeed: 1.5 - baseSprintSpeed: 3.0 - - type: Sprite - sprite: Mobs/Species/Terminator/parts.rsi - - type: Fixtures - fixtures: - fix1: - shape: - !type:PhysShapeCircle - radius: 0.35 - # he heavy - density: 500 - mask: - - MobMask - layer: - - MobLayer - - type: MobThresholds - thresholds: - 0: Alive - # gibbed at 200 so cant go crit - 200: Dead - # incase some weird stuff happens and the crew adopts a terminator - - type: Repairable - doAfterDelay: 15 - allowSelfRepair: false - - type: Body - prototype: TerminatorEndoskeleton - # lets it sit in the terminator flesh's brain slot - - type: Organ - - type: Brain - - type: TypingIndicator - proto: robot # beep boop borp - - type: Speech - speechSounds: Pai - - type: Damageable - damageModifierSet: Cybernetic - - type: Destructible - thresholds: - - trigger: - !type:DamageTrigger - damage: 200 - behaviors: - - !type:PlaySoundBehavior - # endoSKELETON - sound: /Audio/Effects/bone_rattle.ogg - # a keepsake or a gift for cargo - - !type:SpawnEntitiesBehavior - spawn: - HeadTerminator: - min: 1 - max: 1 - - !type:DoActsBehavior - acts: [ "Destruction" ] - # for fire spreading around, the endoskeleton cannot burn - - type: Flammable - fireSpread: true - canResistFire: true - damage: - types: - Heat: 0 - # now his only weapon, but it is stronger - - type: MeleeWeapon - damage: - types: - Blunt: 15 - Structural: 5 - - type: Puller - needsHands: false - - type: Prying - pryPowered: true - force: true - - type: Tag - tags: - - DoorBumpOpener - - ShoesRequiredStepTriggerImmune - # let mind transfer on gib work - - MindTransferTarget - # its just metal so no implants - - Unimplantable diff --git a/Resources/Prototypes/Entities/Mobs/Player/vox.yml b/Resources/Prototypes/Entities/Mobs/Player/vox.yml index 0a6f4f4364..e7ad39d731 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/vox.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/vox.yml @@ -1,7 +1,5 @@ - type: entity save: false - name: Vox + name: Urist McVox parent: BaseMobVox - id: MobVox - components: - - type: PotentialPsionic #Nyano - Summary: makes potentially psionic. + id: MobVox \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Mobs/Species/arachne.yml b/Resources/Prototypes/Entities/Mobs/Species/arachne.yml index 2f6437dc14..ddbdc57e0a 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/arachne.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/arachne.yml @@ -104,15 +104,15 @@ - type: Tag tags: - CanPilot - - ShoesRequiredStepTriggerImmune - DoorBumpOpener + - type: StepTriggerImmune - type: Bloodstream bloodReagent: DemonsBlood + bloodRegenerationThirst: 4 # 1 unit of demon's blood satiates 4 thirst - type: BloodSucker webRequired: true - - type: Arachne + - type: Cocooner - type: DamageVisuals - thresholds: [ 20, 40, 100 ] targetLayers: - "enum.HumanoidVisualLayers.Chest" - "enum.HumanoidVisualLayers.Head" @@ -135,6 +135,9 @@ fireStackAlternateState: 3 - type: Spider - type: IgnoreSpiderWeb + - type: FootPrints + leftBarePrint: "footprint-left-bare-spider" + rightBarePrint: "footprint-right-bare-spider" - type: entity save: false diff --git a/Resources/Prototypes/Entities/Mobs/Species/arachnid.yml b/Resources/Prototypes/Entities/Mobs/Species/arachnid.yml index c135ac2b82..f512e71d32 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/arachnid.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/arachnid.yml @@ -44,6 +44,9 @@ methods: [Touch] effects: - !type:WashCreamPieReaction + - type: BloodSucker + webRequired: true + - type: Cocooner # Damage (Self) - type: Bloodstream bloodReagent: CopperBlood @@ -64,6 +67,7 @@ - type: Speech speechVerb: Arachnid speechSounds: Arachnid + allowedEmotes: ['Click', 'Chitter'] - type: Vocal sounds: Male: UnisexArachnid @@ -123,6 +127,11 @@ sprite: "Effects/creampie.rsi" state: "creampie_arachnid" visible: false + - type: Spider + - type: IgnoreSpiderWeb + - type: FootPrints + leftBarePrint: "footprint-left-bare-spider" + rightBarePrint: "footprint-right-bare-spider" - type: entity parent: BaseSpeciesDummy diff --git a/Resources/Prototypes/Entities/Mobs/Species/base.yml b/Resources/Prototypes/Entities/Mobs/Species/base.yml index d812433b44..24ee2a964a 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/base.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/base.yml @@ -104,6 +104,7 @@ layer: - MobLayer - type: FloorOcclusion + - type: Mood - type: RangedDamageSound soundGroups: Brute: @@ -140,8 +141,8 @@ - TemporaryBlindness - Pacified - StaminaModifier - - PsionicsDisabled #Nyano - Summary: PCs can have psionics disabled. - - PsionicallyInsulated #Nyano - Summary: PCs can be made insulated from psionic powers. + - PsionicsDisabled + - PsionicallyInsulated - type: Reflect enabled: false reflectProb: 0 @@ -151,7 +152,6 @@ - type: Identity - type: IdExaminable - type: Hands - - type: Internals - type: Inventory - type: InventorySlots - type: FloatingVisuals @@ -190,12 +190,15 @@ - type: Strippable - type: UserInterface interfaces: - - key: enum.VoiceMaskUIKey.Key - type: VoiceMaskBoundUserInterface - - key: enum.HumanoidMarkingModifierKey.Key + enum.HumanoidMarkingModifierKey.Key: type: HumanoidMarkingModifierBoundUserInterface - - key: enum.StrippingUiKey.Key + enum.StrippingUiKey.Key: type: StrippableBoundUserInterface + enum.InstrumentUiKey.Key: + type: InstrumentBoundUserInterface + requireInputValidation: false + enum.SurgeryUIKey.Key: + type: SurgeryBui - type: Puller - type: Speech speechSounds: Alto @@ -217,14 +220,16 @@ - type: CanEscapeInventory # Carrying system from nyanotrasen. - type: LanguageKnowledge # This is here so even if species doesn't have a defined language, they at least speak GC speaks: - - GalacticCommon + - TauCetiBasic understands: - - GalacticCommon + - TauCetiBasic - type: Tag tags: - CanPilot - FootstepSound - DoorBumpOpener + - type: Targeting + - type: SurgeryTarget - type: entity save: false @@ -269,6 +274,8 @@ - Muted - Pacified - StaminaModifier + - PsionicsDisabled + - PsionicallyInsulated - type: Blindable # Other - type: Temperature @@ -307,6 +314,9 @@ alternateState: Standing - type: OfferItem - type: LayingDown + - type: Shoving + - type: BloodstreamAffectedByMass + power: 0.6 # A minimum size felinid will have 30% blood, a minimum size vulp will have 60%, a maximum size oni will have ~200% - type: entity save: false @@ -377,5 +387,5 @@ requiredLegs: 2 - type: UserInterface interfaces: - - key: enum.HumanoidMarkingModifierKey.Key # sure, this can go here too + enum.HumanoidMarkingModifierKey.Key: # sure, this can go here too type: HumanoidMarkingModifierBoundUserInterface diff --git a/Resources/Prototypes/Entities/Mobs/Species/diona.yml b/Resources/Prototypes/Entities/Mobs/Species/diona.yml index 42383d9a42..07d621b139 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/diona.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/diona.yml @@ -33,6 +33,8 @@ amount: 5 - type: Bloodstream bloodReagent: Sap + bloodRegenerationHunger: 1 + bloodRegenerationThirst: 1 # 1 unit of sap satiates 1 hunger and thirst - type: Reactive groups: Flammable: [ Touch ] @@ -104,16 +106,23 @@ - Dead - type: LanguageKnowledge speaks: - - GalacticCommon + - TauCetiBasic - RootSpeak understands: - - GalacticCommon + - TauCetiBasic - RootSpeak - type: TraitSpeedModifier sprintModifier: 0.75 walkModifier: 0.75 - type: SpeedModifierImmunity - type: NoSlip + - type: StepTriggerImmune + whitelist: + types: + - Shard + - type: FootPrints + leftBarePrint: "footprint-left-bare-diona" + rightBarePrint: "footprint-right-bare-diona" - type: entity parent: BaseSpeciesDummy diff --git a/Resources/Prototypes/Entities/Mobs/Species/dwarf.yml b/Resources/Prototypes/Entities/Mobs/Species/dwarf.yml index 055c6522dd..7f31504035 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/dwarf.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/dwarf.yml @@ -51,15 +51,25 @@ accent: dwarf - type: Speech speechSounds: Bass + - type: HumanoidAppearance + species: Human + hideLayersOnEquip: + - Hair + - Snout - type: LanguageKnowledge speaks: - - GalacticCommon + - TauCetiBasic - SolCommon understands: - - GalacticCommon + - TauCetiBasic - SolCommon - type: LightweightDrunk boozeStrengthMultiplier: 0.5 + - type: Stamina + critThreshold: 115 + - type: FootPrints + leftBarePrint: "footprint-left-bare-dwarf" + rightBarePrint: "footprint-right-bare-dwarf" - type: entity parent: BaseSpeciesDummy diff --git a/Resources/Prototypes/Entities/Mobs/Species/golem.yml b/Resources/Prototypes/Entities/Mobs/Species/golem.yml index d3630b609a..e509660f81 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/golem.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/golem.yml @@ -191,8 +191,7 @@ # - type: Strippable # - type: UserInterface # interfaces: -# - key: enum.VoiceMaskUIKey.Key -# type: VoiceMaskBoundUserInterface + # - key: enum.HumanoidMarkingModifierKey.Key # type: HumanoidMarkingModifierBoundUserInterface # - key: enum.StrippingUiKey.Key diff --git a/Resources/Prototypes/Entities/Mobs/Species/harpy.yml b/Resources/Prototypes/Entities/Mobs/Species/harpy.yml index f4055170b4..4ad6ea03cd 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/harpy.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/harpy.yml @@ -5,6 +5,10 @@ id: MobHarpyBase abstract: true components: + - type: Flight + isLayerAnimated: true + layer: "/Textures/Mobs/Customization/Harpy/harpy_wings.rsi" + animationKey: "Flap" - type: Singer proto: HarpySinger - type: Sprite @@ -110,21 +114,28 @@ - CanPilot - FootstepSound - DoorBumpOpener - - ShoesRequiredStepTriggerImmune - type: LanguageKnowledge speaks: - - GalacticCommon - - SolCommon + - TauCetiBasic + - ValyrianStandard understands: - - GalacticCommon - - SolCommon + - TauCetiBasic + - ValyrianStandard + - type: StepTriggerImmune + whitelist: + types: + - Shard + - Landmine + - Mousetrap + - type: FootPrints + leftBarePrint: "footprint-left-bare-lizard" + rightBarePrint: "footprint-right-bare-lizard" # I was about to complain about this, then I remembered birbs have dinosaur feet. So this is valid. - type: entity save: false - name: Urist McHands + name: Urist McBirb parent: MobHumanDummy id: MobHarpyDummy - noSpawn: true description: A dummy Harpy meant to be used in character setup. components: - type: HumanoidAppearance @@ -173,6 +184,9 @@ - map: [ "enum.HumanoidVisualLayers.Hair" ] - map: [ "mask" ] - map: [ "head" ] + - map: [ "singingLayer" ] + sprite: Effects/harpysinger.rsi + state: singing_music_notes - type: entity id: ActionHarpyPlayMidi @@ -196,3 +210,15 @@ icon: DeltaV/Interface/Actions/harpy_syrinx.png itemIconStyle: BigAction event: !type:VoiceMaskSetNameEvent + +- type: entity + id: ActionToggleFlight + name: Fly + description: Make use of your wings to fly. Beat the flightless bird allegations. + noSpawn: true + components: + - type: InstantAction + checkCanInteract: false + icon: { sprite: Interface/Actions/flight.rsi, state: flight_off } + iconOn: { sprite : Interface/Actions/flight.rsi, state: flight_on } + event: !type:ToggleFlightEvent diff --git a/Resources/Prototypes/Entities/Mobs/Species/human.yml b/Resources/Prototypes/Entities/Mobs/Species/human.yml index e00e06279e..ee2886602d 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/human.yml @@ -16,14 +16,19 @@ spawned: - id: FoodMeatHuman amount: 5 - - type: PotentialPsionic #Nyano - Summary: makes potentially psionic. + - type: HumanoidAppearance + species: Human + hideLayersOnEquip: + - Hair + - Snout - type: LanguageKnowledge speaks: - - GalacticCommon + - TauCetiBasic - SolCommon understands: - - GalacticCommon + - TauCetiBasic - SolCommon + - type: FootPrints - type: entity parent: BaseSpeciesDummy diff --git a/Resources/Prototypes/Entities/Mobs/Species/moth.yml b/Resources/Prototypes/Entities/Mobs/Species/moth.yml index 1c55dcf0df..5f68422226 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/moth.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/moth.yml @@ -23,12 +23,13 @@ accent: zombieMoth - type: Speech speechVerb: Moth + allowedEmotes: ['Chitter', 'Squeak'] - type: LanguageKnowledge speaks: - - GalacticCommon + - TauCetiBasic - Moffic understands: - - GalacticCommon + - TauCetiBasic - Moffic - type: TypingIndicator proto: moth @@ -121,6 +122,7 @@ sprite: "Effects/creampie.rsi" state: "creampie_moth" visible: false + - type: FootPrints - type: entity parent: BaseSpeciesDummy diff --git a/Resources/Prototypes/Entities/Mobs/Species/reptilian.yml b/Resources/Prototypes/Entities/Mobs/Species/reptilian.yml index 35f9e9fa39..ee5d628565 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/reptilian.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/reptilian.yml @@ -33,9 +33,9 @@ proto: lizard - type: Vocal sounds: - Male: UnisexReptilian - Female: UnisexReptilian - Unsexed: UnisexReptilian + Male: MaleReptilian + Female: FemaleReptilian + Unsexed: MaleReptilian - type: Damageable damageContainer: Biological damageModifierSet: Scale @@ -61,11 +61,14 @@ - type: Wagging - type: LanguageKnowledge speaks: - - GalacticCommon + - TauCetiBasic - Draconic understands: - - GalacticCommon + - TauCetiBasic - Draconic + - type: FootPrints + leftBarePrint: "footprint-left-bare-lizard" + rightBarePrint: "footprint-right-bare-lizard" - type: entity parent: BaseSpeciesDummy diff --git a/Resources/Prototypes/Entities/Mobs/Species/shadowkin.yml b/Resources/Prototypes/Entities/Mobs/Species/shadowkin.yml new file mode 100644 index 0000000000..393cb0b871 --- /dev/null +++ b/Resources/Prototypes/Entities/Mobs/Species/shadowkin.yml @@ -0,0 +1,304 @@ +- type: entity + save: false + parent: + - MobBloodstream + - MobAtmosStandard + - MobFlammable + - BaseMobSpecies + id: MobShadowkinBase + name: Urist McShadow + abstract: true + components: + - type: Destructible + thresholds: + - trigger: + !type:DamageTypeTrigger + damageType: Blunt + damage: 400 + behaviors: + - !type:GibBehavior {} + - !type:SpawnEntitiesBehavior + spawn: + ShadowkinShadow: + min: 1 + max: 1 + EffectFlashShadowkinShadeskip: + min: 1 + max: 1 + - trigger: + !type:DamageTypeTrigger + damageType: Heat + damage: 1500 + behaviors: + - !type:SpawnEntitiesBehavior + spawnInContainer: true + spawn: + Ash: + min: 1 + max: 1 + - !type:BurnBodyBehavior {} + - !type:PlaySoundBehavior + sound: + collection: MeatLaserImpact + - type: PassiveDamage # Slight passive regen. Assuming one damage type, comes out to about 4 damage a minute. + allowedStates: + - Alive + damageCap: 20 + damage: + types: + Heat: -0.07 + groups: + Brute: -0.07 + - type: StatusEffects + allowed: + - Stun + - KnockedDown + - SlowedDown + - Stutter + - SeeingRainbows + - Electrocution + - ForcedSleep + - TemporaryBlindness + - Drunk + - SlurredSpeech + - RatvarianLanguage + - PressureImmunity + - Muted + - Pacified + - StaminaModifier + - type: Blindable + - type: ThermalRegulator + metabolismHeat: 800 + radiatedHeat: 100 + implicitHeatRegulation: 500 + sweatHeatRegulation: 2000 + shiveringHeatRegulation: 2000 + normalBodyTemperature: 310.15 + thermalRegulationTemperatureThreshold: 25 + - type: Perishable + - type: FireVisuals + alternateState: Standing + - type: OfferItem + - type: LayingDown + - type: Shoving + - type: BloodstreamAffectedByMass + power: 0.6 + - type: Hunger + - type: Thirst + - type: Carriable + - type: HumanoidAppearance + species: Shadowkin + - type: Icon + sprite: Mobs/Species/Shadowkin/parts.rsi + state: full + - type: Body + prototype: Shadowkin + - type: Flammable + damage: + types: + Heat: 1.5 # burn more + - type: MobThresholds + thresholds: # Weak + 0: Alive + 80: Critical + 180: Dead + - type: SlowOnDamage + speedModifierThresholds: + 48: 0.85 + 64: 0.65 + - type: Damageable + damageContainer: Biological # Shadowkin + damageModifierSet: Shadowkin + - type: Barotrauma + damage: + types: + Blunt: 0.35 # per second, scales with pressure and other constants. + - type: Bloodstream + bloodlossDamage: + types: + Bloodloss: 1 + bloodlossHealDamage: + types: + Bloodloss: -0.25 + - type: Temperature + heatDamageThreshold: 330 + coldDamageThreshold: 195 + currentTemperature: 310.15 + specificHeat: 46 + coldDamage: + types: + Cold: 0.05 #per second, scales with temperature & other constants + heatDamage: + types: + Heat: 0.25 #per second, scales with temperature & other constants + - type: Fixtures + fixtures: + fix1: + shape: !type:PhysShapeCircle + radius: 0.35 + density: 130 #lower density + restitution: 0.0 + mask: + - MobMask + layer: + - MobLayer + - type: Sprite + netsync: false + noRot: true + drawdepth: Mobs + scale: 0.85, 0.85 + layers: + - map: ["enum.HumanoidVisualLayers.Chest"] + - map: ["enum.HumanoidVisualLayers.Head"] + - map: ["enum.HumanoidVisualLayers.Snout"] + - map: ["enum.HumanoidVisualLayers.Eyes"] + shader: unshaded + - map: ["enum.HumanoidVisualLayers.RArm"] + - map: ["enum.HumanoidVisualLayers.LArm"] + - map: ["enum.HumanoidVisualLayers.RLeg"] + - map: ["enum.HumanoidVisualLayers.LLeg"] + - shader: StencilClear + sprite: Mobs/Species/Human/parts.rsi + state: l_leg + - shader: StencilMask + map: ["enum.HumanoidVisualLayers.StencilMask"] + sprite: Mobs/Customization/masking_helpers.rsi + state: full + visible: false + - map: ["enum.HumanoidVisualLayers.LFoot"] + - map: ["enum.HumanoidVisualLayers.RFoot"] + - map: ["socks"] + - map: ["underpants"] + - map: ["undershirt"] + - map: ["jumpsuit"] + - map: ["enum.HumanoidVisualLayers.LHand"] + - map: ["enum.HumanoidVisualLayers.RHand"] + - map: ["enum.HumanoidVisualLayers.Handcuffs"] + color: "#ffffff" + sprite: Objects/Misc/handcuffs.rsi + state: body-overlay-2 + visible: false + - map: ["id"] + - map: ["gloves"] + - map: ["shoes"] + - map: ["ears"] + - map: ["outerClothing"] + - map: ["eyes"] + - map: ["belt"] + - map: ["neck"] + - map: ["back"] + - map: ["enum.HumanoidVisualLayers.FacialHair"] + - map: ["enum.HumanoidVisualLayers.Hair"] + - map: ["enum.HumanoidVisualLayers.HeadSide"] + - map: ["enum.HumanoidVisualLayers.HeadTop"] + - map: ["mask"] + - map: ["head"] + - map: ["pocket1"] + - map: ["pocket2"] + - map: ["enum.HumanoidVisualLayers.Tail"] + - type: MeleeWeapon + soundHit: + collection: AlienClaw + angle: 30 + animation: WeaponArcClaw + damage: + types: + Slash: 5 + - type: Vocal + sounds: + Male: MaleShadowkin + Female: FemaleShadowkin + Unsexed: MaleShadowkin + - type: TypingIndicator + proto: alien + - type: MovementSpeedModifier + baseWalkSpeed: 2.7 + baseSprintSpeed: 4.5 + - type: Flashable + eyeDamageChance: 0.3 + eyeDamage: 1 + durationMultiplier: 1.5 + - type: Speech + allowedEmotes: ['Mars', 'Wurble'] + - type: Shadowkin + - type: Psionic + mindbreakingFeedback: shadowkin-blackeye + manaGain: 0.25 + mana: 150 + maxMana: 250 + bypassManaCheck: true + removable: false + - type: InnatePsionicPowers + powersToAdd: + - ShadowkinPowers + - type: LanguageKnowledge + speaks: + - TauCetiBasic + - Marish + understands: + - TauCetiBasic + - Marish + +- type: entity + save: false + parent: MobHumanDummy + id: MobShadowkinDummy + noSpawn: true + description: A dummy shadowkin meant to be used in character setup. + components: + - type: HumanoidAppearance + species: Shadowkin + - type: Sprite + netsync: false + noRot: true + drawdepth: Mobs + scale: 0.85, 0.85 # Small + layers: + - map: ["enum.HumanoidVisualLayers.Chest"] + - map: ["enum.HumanoidVisualLayers.Head"] + - map: ["enum.HumanoidVisualLayers.Snout"] + - map: ["enum.HumanoidVisualLayers.Eyes"] + shader: unshaded + - map: ["enum.HumanoidVisualLayers.RArm"] + - map: ["enum.HumanoidVisualLayers.LArm"] + - map: ["enum.HumanoidVisualLayers.RLeg"] + - map: ["enum.HumanoidVisualLayers.LLeg"] + - shader: StencilClear + sprite: Mobs/Species/Human/parts.rsi + state: l_leg + - shader: StencilMask + map: ["enum.HumanoidVisualLayers.StencilMask"] + sprite: Mobs/Customization/masking_helpers.rsi + state: full + visible: false + - map: ["enum.HumanoidVisualLayers.LFoot"] + - map: ["enum.HumanoidVisualLayers.RFoot"] + - map: ["socks"] + - map: ["underpants"] + - map: ["undershirt"] + - map: ["jumpsuit"] + - map: ["enum.HumanoidVisualLayers.LHand"] + - map: ["enum.HumanoidVisualLayers.RHand"] + - map: ["enum.HumanoidVisualLayers.Handcuffs"] + color: "#ffffff" + sprite: Objects/Misc/handcuffs.rsi + state: body-overlay-2 + visible: false + - map: ["id"] + - map: ["gloves"] + - map: ["shoes"] + - map: ["ears"] + - map: ["outerClothing"] + - map: ["eyes"] + - map: ["belt"] + - map: ["neck"] + - map: ["back"] + - map: ["enum.HumanoidVisualLayers.FacialHair"] + - map: ["enum.HumanoidVisualLayers.Hair"] + - map: ["enum.HumanoidVisualLayers.HeadSide"] + - map: ["enum.HumanoidVisualLayers.HeadTop"] + - map: ["mask"] + - map: ["head"] + - map: ["pocket1"] + - map: ["pocket2"] + - map: ["enum.HumanoidVisualLayers.Tail"] diff --git a/Resources/Prototypes/Entities/Mobs/Species/skeleton.yml b/Resources/Prototypes/Entities/Mobs/Species/skeleton.yml index 28ea5b030f..cf3aa6b1ca 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/skeleton.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/skeleton.yml @@ -9,7 +9,7 @@ components: - type: HumanoidAppearance species: Skeleton - - type: Carriable # Carrying system from nyanotrasen. + - type: Carriable # Carrying system from nyanotrasen. - type: Icon sprite: Mobs/Species/Skeleton/parts.rsi state: full @@ -40,7 +40,8 @@ !type:DamageTrigger damage: 150 behaviors: - - !type:GibBehavior { } + - !type:GibBehavior + gibContents: Skip - type: SlowOnDamage #modified speeds because they're so weak speedModifierThresholds: 60: 0.9 @@ -102,6 +103,7 @@ probability: 0.5 - type: FireVisuals alternateState: Standing + - type: FootPrints - type: entity parent: BaseSpeciesDummy diff --git a/Resources/Prototypes/Entities/Mobs/Species/slime.yml b/Resources/Prototypes/Entities/Mobs/Species/slime.yml index 0e05b0c827..81547a3fa3 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/slime.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/slime.yml @@ -13,11 +13,37 @@ - type: Body prototype: Slime requiredLegs: 2 + # they like eat it idk lol + - type: Storage + clickInsert: false + grid: + - 0,0,1,2 + maxItemSize: Large + storageInsertSound: + path: /Audio/Voice/Slime/slime_squish.ogg + - type: ContainerContainer + containers: + storagebase: !type:Container + ents: [] + - type: UserInterface + interfaces: + enum.StorageUiKey.Key: + type: StorageBoundUserInterface + enum.HumanoidMarkingModifierKey.Key: + type: HumanoidMarkingModifierBoundUserInterface + enum.StrippingUiKey.Key: + type: StrippableBoundUserInterface + enum.SurgeryUIKey.Key: + type: SurgeryBui + # to prevent bag open/honk spam + - type: UseDelay + delay: 0.5 - type: HumanoidAppearance species: SlimePerson - type: Speech speechVerb: Slime speechSounds: Slime + allowedEmotes: ['Squish'] - type: TypingIndicator proto: slime - type: Vocal @@ -28,6 +54,16 @@ - type: Damageable damageContainer: Biological damageModifierSet: Slime + - type: Geras + - type: PassiveDamage # Around 8 damage a minute healed + allowedStates: + - Alive + damageCap: 65 + damage: + types: + Heat: -0.14 + groups: + Brute: -0.14 - type: DamageVisuals damageOverlayGroups: Brute: @@ -35,6 +71,8 @@ color: "#2cf274" - type: Bloodstream bloodReagent: Slime # TODO Color slime blood based on their slime color or smth + bloodRegenerationHunger: 2 + bloodRegenerationThirst: 2 # 1 of slime satiates 2 hunger - type: Barotrauma damage: types: @@ -76,11 +114,14 @@ maxSaturation: 15 - type: LanguageKnowledge speaks: - - GalacticCommon + - TauCetiBasic - Bubblish understands: - - GalacticCommon + - TauCetiBasic - Bubblish + - type: FootPrints + leftBarePrint: "footprint-left-bare-slime" + rightBarePrint: "footprint-right-bare-slime" - type: entity parent: MobHumanDummy diff --git a/Resources/Prototypes/Entities/Mobs/Species/vox.yml b/Resources/Prototypes/Entities/Mobs/Species/vox.yml index a271e9d084..58e2b3b646 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/vox.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/vox.yml @@ -16,6 +16,16 @@ #- type: VoxAccent # Not yet coded - type: Inventory speciesId: vox + displacements: + jumpsuit: + layer: + sprite: Mobs/Species/Vox/displacement.rsi + state: jumpsuit + copyToShaderParameters: + # Value required, provide a dummy. Gets overridden when applied. + layerKey: dummy + parameterTexture: displacementMap + parameterUV: displacementUV - type: Speech speechVerb: Vox speechSounds: Vox @@ -49,6 +59,52 @@ damage: types: Slash: 5 # Reduce? + - type: Sprite # Need to redefine the whole order to draw the tail over their gas tank + layers: + - map: [ "enum.HumanoidVisualLayers.Chest" ] + - map: [ "enum.HumanoidVisualLayers.Head" ] + - map: [ "enum.HumanoidVisualLayers.Snout" ] + - map: [ "enum.HumanoidVisualLayers.Eyes" ] + - map: [ "enum.HumanoidVisualLayers.RArm" ] + - map: [ "enum.HumanoidVisualLayers.LArm" ] + - map: [ "enum.HumanoidVisualLayers.RLeg" ] + - map: [ "enum.HumanoidVisualLayers.LLeg" ] + - map: [ "jumpsuit" ] + - map: [ "enum.HumanoidVisualLayers.LFoot" ] + - map: [ "enum.HumanoidVisualLayers.RFoot" ] + - map: [ "enum.HumanoidVisualLayers.LHand" ] + - map: [ "enum.HumanoidVisualLayers.RHand" ] + - map: [ "gloves" ] + - map: [ "shoes" ] + - map: [ "ears" ] + - map: [ "outerClothing" ] + - map: [ "eyes" ] + - map: [ "belt" ] + - map: [ "id" ] + - map: [ "neck" ] + - map: [ "back" ] + - map: [ "enum.HumanoidVisualLayers.FacialHair" ] + - map: [ "enum.HumanoidVisualLayers.Hair" ] + - map: [ "enum.HumanoidVisualLayers.HeadSide" ] + - map: [ "enum.HumanoidVisualLayers.HeadTop" ] + - map: [ "suitstorage" ] # This is not in the default order + - map: [ "enum.HumanoidVisualLayers.Tail" ] + - map: [ "mask" ] + - map: [ "head" ] + - map: [ "pocket1" ] + - map: [ "pocket2" ] + - map: [ "enum.HumanoidVisualLayers.Handcuffs" ] + color: "#ffffff" + sprite: Objects/Misc/handcuffs.rsi + state: body-overlay-2 + visible: false + - map: [ "clownedon" ] + sprite: "Effects/creampie.rsi" + state: "creampie_vox" # Not default + visible: false + - type: FootPrints + leftBarePrint: "footprint-left-bare-lizard" + rightBarePrint: "footprint-right-bare-lizard" - type: entity parent: BaseSpeciesDummy diff --git a/Resources/Prototypes/Entities/Mobs/base.yml b/Resources/Prototypes/Entities/Mobs/base.yml index 5c35afd8f0..5ab790feee 100644 --- a/Resources/Prototypes/Entities/Mobs/base.yml +++ b/Resources/Prototypes/Entities/Mobs/base.yml @@ -46,6 +46,8 @@ - type: LanguageSpeaker # Einstein Engines. This component is required to support speech, although it does not define known languages. - type: RequireProjectileTarget active: False + - type: OwnInteractionVerbs + allowedVerbs: [] # TODO: define something here, or don't. # Used for mobs that have health and can take damage. - type: entity @@ -195,7 +197,7 @@ canResistFire: true damage: #per second, scales with number of fire 'stacks' types: - Heat: 3 + Heat: 1.5 - type: FireVisuals sprite: Mobs/Effects/onfire.rsi normalState: Generic_mob_burning @@ -231,3 +233,5 @@ bloodlossHealDamage: types: Bloodloss: -1 + bloodRegenerationHunger: 1 + bloodRegenerationThirst: 1.5 # 1 unit of normal blood satiates 1t, 0.3t for vampires, 0.75h, and restores 0.25 blood for non-humans... diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks-cartons.yml b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks-cartons.yml index e758022710..40684d16cf 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks-cartons.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks-cartons.yml @@ -15,6 +15,9 @@ solutions: drink: maxVol: 50 + - type: PressurizedSolution + solution: drink + - type: Shakeable - type: Sprite state: icon - type: Item diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks.yml b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks.yml index 93d4b957fe..701b8ac28e 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks.yml @@ -10,6 +10,8 @@ solutions: drink: maxVol: 30 + - type: MixableSolution + solution: drink - type: SolutionTransfer canChangeTransferAmount: true - type: Drink @@ -31,8 +33,8 @@ solution: drink - type: UserInterface interfaces: - - key: enum.TransferAmountUiKey.Key - type: TransferAmountBoundUserInterface + enum.TransferAmountUiKey.Key: + type: TransferAmountBoundUserInterface - type: EmitSoundOnPickup sound: path: /Audio/SimpleStation14/Items/Handling/drinkglass_pickup.ogg @@ -48,6 +50,31 @@ path: /Audio/SimpleStation14/Items/Handling/drinkglass_drop.ogg params: volume: -2 + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeAabb + bounds: "-0.25,-0.25,0.25,0.25" + density: 30 + mask: + - ItemMask + layer: + - BulletImpassable # Ever seen a John Woo movie? + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 5 + behaviors: + - !type:PlaySoundBehavior + sound: + collection: MetalBreak + params: + volume: -6 + - !type:SpillBehavior { } + - !type:DoActsBehavior + acts: [ "Destruction" ] - type: entity parent: DrinkBase @@ -516,6 +543,22 @@ sprite: Objects/Consumable/Drinks/bravebullglass.rsi state: icon +- type: entity + parent: DrinkGlass + id: BudgetInsulsDrinkGlass + suffix: budget insuls + components: + - type: SolutionContainerManager + solutions: + drink: + maxVol: 30 + reagents: + - ReagentId: BudgetInsulsDrink + Quantity: 30 + - type: Icon + sprite: Objects/Consumable/Drinks/budgetinsulsdrink.rsi + state: icon + - type: entity parent: DrinkGlass id: DrinkCarrotJuice @@ -548,6 +591,22 @@ sprite: Objects/Consumable/Drinks/chocolateglass.rsi state: icon +- type: entity + parent: DrinkGlass + id: RubberneckGlass + suffix: rubberneck + components: + - type: SolutionContainerManager + solutions: + drink: + maxVol: 30 + reagents: + - ReagentId: Rubberneck + Quantity: 30 + - type: Icon + sprite: Objects/Consumable/Drinks/rubberneck.rsi + state: icon + - type: entity parent: DrinkGlass id: DrinkCoconutRum @@ -1074,6 +1133,22 @@ sprite: Objects/Consumable/Drinks/icecreamglass.rsi state: icon +- type: entity + parent: DrinkGlass + id: IrishBoolGlass + suffix: irish bool + components: + - type: SolutionContainerManager + solutions: + drink: + maxVol: 30 + reagents: + - ReagentId: IrishBool + Quantity: 30 + - type: Icon + sprite: Objects/Consumable/Drinks/beerglass.rsi + state: icon + - type: entity parent: DrinkGlass id: DrinkIrishCarBomb @@ -2068,6 +2143,22 @@ sprite: Objects/Consumable/Drinks/martiniglass.rsi state: icon +- type: entity + parent: DrinkGlass + id: DrinkVodkaRedBool + suffix: vodka red bool + components: + - type: SolutionContainerManager + solutions: + drink: + maxVol: 30 + reagents: + - ReagentId: VodkaRedBool + Quantity: 30 + - type: Icon + sprite: Objects/Consumable/Drinks/ginvodkaglass.rsi + state: icon + - type: entity parent: DrinkGlass id: DrinkVodkaTonicGlass @@ -2114,6 +2205,22 @@ - ReagentId: JuiceWatermelon Quantity: 30 +- type: entity + parent: DrinkGlass + id: DrinkWatermelonWakeup + suffix: watermelon wakeup + components: + - type: SolutionContainerManager + solutions: + drink: + maxVol: 30 + reagents: + - ReagentId: WatermelonWakeup + Quantity: 30 + - type: Icon + sprite: Objects/Consumable/Drinks/champagneglass.rsi + state: icon + - type: entity parent: DrinkGlass id: DrinkWhiskeyColaGlass @@ -2194,6 +2301,22 @@ sprite: Objects/Consumable/Drinks/wineglass.rsi state: icon +- type: entity + parent: DrinkGlass + id: XenoBasherGlass + suffix: xeno basher + components: + - type: SolutionContainerManager + solutions: + drink: + maxVol: 30 + reagents: + - ReagentId: XenoBasher + Quantity: 30 + - type: Icon + sprite: Objects/Consumable/Drinks/xenobasher.rsi + state: icon + # TODO: MOVE - type: entity diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_bottles.yml b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_bottles.yml index a6752286dd..7a4983b14d 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_bottles.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_bottles.yml @@ -31,6 +31,9 @@ !type:DamageTrigger damage: 5 behaviors: + - !type:PlaySoundBehavior + sound: + collection: GlassBreak - !type:SpillBehavior { } - !type:DoActsBehavior acts: [ "Destruction" ] @@ -39,6 +42,9 @@ - type: PhysicalComposition materialComposition: Plastic: 100 + - type: PressurizedSolution + solution: drink + - type: Shakeable - type: entity parent: DrinkBottlePlasticBaseFull @@ -368,6 +374,7 @@ - type: Tag tags: - Wine + - DrinkBottle - type: entity parent: [DrinkBottleVisualsAll, DrinkBottleGlassBaseFull] @@ -517,6 +524,7 @@ - type: Tag tags: - Wine + - DrinkBottle # Small Bottles @@ -542,6 +550,7 @@ - type: Tag tags: - Beer + - DrinkBottle - type: entity parent: [DrinkBottleVisualsAll, DrinkBottleGlassBaseFull] @@ -565,7 +574,7 @@ - type: Tag tags: - Beer - + - DrinkBottle - type: entity parent: [DrinkBottleVisualsAll, DrinkBottlePlasticBaseFull] diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_cans.yml b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_cans.yml index a9ff1c784a..319c8a634e 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_cans.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_cans.yml @@ -6,7 +6,7 @@ components: - type: Drink - type: Openable - - type: PressurizedDrink + - type: Shakeable - type: SolutionContainerManager solutions: drink: @@ -14,13 +14,15 @@ - ReagentId: Cola Quantity: 30 maxVol: 30 + - type: MixableSolution + solution: drink - type: SolutionTransfer canChangeTransferAmount: true maxTransferAmount: 15 - type: UserInterface interfaces: - - key: enum.TransferAmountUiKey.Key - type: TransferAmountBoundUserInterface + enum.TransferAmountUiKey.Key: + type: TransferAmountBoundUserInterface - type: Sprite state: icon layers: @@ -34,6 +36,8 @@ solution: drink - type: DrainableSolution solution: drink + - type: PressurizedSolution + solution: drink - type: Appearance - type: GenericVisualizer visuals: diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_cups.yml b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_cups.yml index c4693c6a71..46fb4d9726 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_cups.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_cups.yml @@ -10,7 +10,8 @@ solutions: drink: maxVol: 20 - canMix: true + - type: MixableSolution + solution: drink - type: FitsInDispenser solution: drink - type: DrawableSolution @@ -24,8 +25,8 @@ maxTransferAmount: 10 - type: UserInterface interfaces: - - key: enum.TransferAmountUiKey.Key - type: TransferAmountBoundUserInterface + enum.TransferAmountUiKey.Key: + type: TransferAmountBoundUserInterface - type: Drink - type: Sprite state: icon diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_fun.yml b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_fun.yml index 2fd2331f91..ef6208b69d 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_fun.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_fun.yml @@ -146,6 +146,9 @@ - type: RandomFillSolution solution: drink weightedRandomId: RandomFillMopwata + - type: PressurizedSolution + solution: drink + - type: Shakeable - type: Appearance - type: GenericVisualizer visuals: diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_special.yml b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_special.yml index 9a523c997b..9c87995f5e 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_special.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_special.yml @@ -8,7 +8,10 @@ solutions: drink: maxVol: 100 + - type: MixableSolution + solution: drink - type: Drink + - type: Shakeable # Doesn't do anything, but I mean... - type: FitsInDispenser solution: drink - type: DrawableSolution @@ -26,8 +29,8 @@ state: icon - type: UserInterface interfaces: - - key: enum.TransferAmountUiKey.Key - type: TransferAmountBoundUserInterface + enum.TransferAmountUiKey.Key: + type: TransferAmountBoundUserInterface - type: PhysicalComposition materialComposition: Steel: 50 @@ -107,3 +110,63 @@ - type: Drink - type: Sprite sprite: Objects/Consumable/Drinks/jar_what.rsi + +- type: entity + id: BartenderMixer + abstract: true + components: + - type: DrainableSolution + solution: drink + - type: Drink + - type: DrawableSolution + solution: drink + - type: RefillableSolution + solution: drink + - type: SolutionTransfer + canChangeTransferAmount: true + - type: Spillable + solution: drink + - type: UserInterface + interfaces: + enum.TransferAmountUiKey.Key: + type: TransferAmountBoundUserInterface + +- type: entity + parent: [BaseItem, BartenderMixer] + id: DrinkJigger + name: jigger + description: Like a shaker, but smaller. Used to control the amount of ingredients. + components: + - type: SolutionContainerManager + solutions: + drink: + maxVol: 20 + - type: MixableSolution + solution: drink + - type: FitsInDispenser + solution: drink + - type: Sprite + sprite: Objects/Consumable/Drinks/jigger.rsi + state: icon + - type: PhysicalComposition + materialComposition: + Steel: 20 + +- type: entity + parent: [BaseItem, BartenderMixer] + id: DrinkIceBucket + name: ice bucket + description: A special bucket of refreshy ice. Prohibited use for challenge with the same name! + components: + - type: SolutionContainerManager + solutions: + drink: + reagents: + - ReagentId: Ice + Quantity: 200 + - type: Sprite + sprite: Objects/Consumable/Drinks/icebucket.rsi + state: icon + - type: PhysicalComposition + materialComposition: + Steel: 75 diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/trash_drinks.yml b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/trash_drinks.yml index aeaa54ccf1..b78769034b 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/trash_drinks.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/trash_drinks.yml @@ -25,6 +25,8 @@ damage: types: Blunt: 0 + - type: MixableSolution + solution: drink - type: Spillable solution: drink - type: FitsInDispenser @@ -37,8 +39,8 @@ solution: drink - type: UserInterface interfaces: - - key: enum.TransferAmountUiKey.Key - type: TransferAmountBoundUserInterface + enum.TransferAmountUiKey.Key: + type: TransferAmountBoundUserInterface - type: DamageOnLand damage: types: @@ -102,6 +104,8 @@ solutions: drink: maxVol: 50 + - type: MixableSolution + solution: drink - type: SolutionTransfer canChangeTransferAmount: true maxTransferAmount: 5 @@ -118,8 +122,8 @@ solution: drink - type: UserInterface interfaces: - - key: enum.TransferAmountUiKey.Key - type: TransferAmountBoundUserInterface + enum.TransferAmountUiKey.Key: + type: TransferAmountBoundUserInterface - type: Damageable damageContainer: Inorganic - type: Destructible @@ -214,12 +218,12 @@ - type: entity - name: goldschlager bottle + name: gildlager bottle parent: DrinkBottleBaseEmpty - id: DrinkBottleGoldschlager + id: DrinkBottleGildlager components: - type: Sprite - sprite: Objects/Consumable/TrashDrinks/goldschlagerbottle_empty.rsi + sprite: Objects/Consumable/TrashDrinks/gildlagerbottle_empty.rsi - type: entity diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/bread.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/bread.yml index 32a1815ac7..e40ac40a28 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/bread.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/bread.yml @@ -810,6 +810,8 @@ allowMovement: true description: ghost-role-information-BreadDog-description rules: ghost-role-information-BreadDog-rules + raffle: + settings: short - type: GhostTakeoverAvailable - type: BarkAccent - type: Speech @@ -827,6 +829,7 @@ - type: Puller needsHands: false - type: Examiner + - type: DoAfter - type: CombatMode - type: MeleeWeapon soundHit: diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/cake.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/cake.yml index 71b38959ce..c939dec52c 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/cake.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/cake.yml @@ -672,6 +672,8 @@ allowMovement: true description: ghost-role-information-Cak-description rules: ghost-role-information-Cak-rules + raffle: + settings: short - type: GhostTakeoverAvailable - type: OwOAccent - type: Speech @@ -689,6 +691,7 @@ - type: Puller needsHands: false - type: Examiner + - type: DoAfter - type: CombatMode - type: MeleeWeapon soundHit: diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/bowl.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/bowl.yml index 4595329635..40cb141af7 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/bowl.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/bowl.yml @@ -19,6 +19,8 @@ - map: ["enum.SolutionContainerLayers.Fill"] state: fill-1 visible: false + - type: MixableSolution + solution: food - type: DamageOnLand damage: types: @@ -35,8 +37,8 @@ canChangeTransferAmount: true - type: UserInterface interfaces: - - key: enum.TransferAmountUiKey.Key - type: TransferAmountBoundUserInterface + enum.TransferAmountUiKey.Key: + type: TransferAmountBoundUserInterface - type: Destructible thresholds: - trigger: diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/box.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/box.yml index 966ae86db1..aa9e70f3fa 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/box.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/box.yml @@ -567,7 +567,6 @@ - type: Tag tags: - Trash - - HappyHonk - MimeHappyHonk - type: Sprite sprite: Objects/Storage/Happyhonk/mime.rsi @@ -664,6 +663,10 @@ name: woeful cluwne meal description: Nothing good can come of this. components: + - type: Tag + tags: + - Trash + - CluwneHappyHonk - type: Sprite sprite: Objects/Storage/Happyhonk/cluwne.rsi state: box diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/plate.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/plate.yml index 63c47df67e..3bd66e8a48 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/plate.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/plate.yml @@ -54,6 +54,17 @@ materialComposition: Glass: 60 - type: SpaceGarbage + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeAabb + bounds: "-0.25,-0.25,0.25,0.25" + density: 25 + mask: + - ItemMask + layer: + - BulletImpassable # Ever seen a John Woo movie? - type: entity name: broken plate diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml index 3f0277e1bc..3bf253f773 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml @@ -314,6 +314,9 @@ launchForwardsMultiplier: 1.5 - type: StepTrigger intersectRatio: 0.2 + triggerGroups: + types: + - SlipEntity - type: CollisionWake enabled: false - type: Physics @@ -1133,9 +1136,9 @@ Quantity: 1 - type: entity - name: chili + name: chili pepper parent: FoodProduceBase - id: FoodChili + id: FoodChiliPepper description: Spicy, best not touch your eyes. components: - type: FlavorProfile @@ -1163,7 +1166,7 @@ - type: entity name: chilly pepper parent: FoodProduceBase - id: FoodChilly + id: FoodChillyPepper description: Icy hot. components: - type: FlavorProfile @@ -1869,3 +1872,36 @@ tags: - ClothMade - CottonBoll + +- type: entity + name: pyrotton boll + description: This will probably set you on fire. + id: PyrottonBol + parent: FoodProduceBase + components: + - type: Sprite + sprite: Objects/Specific/Hydroponics/pyrotton.rsi + - type: FlavorProfile + flavors: + - pyrotton + - type: Food + requiresSpecialDigestion: true + - type: SolutionContainerManager + solutions: + food: + reagents: + - ReagentId: Fiber + Quantity: 5 + - ReagentId: Phlogiston + Quantity: 5 + - type: Log + spawnedPrototype: MaterialPyrotton1 + spawnCount: 2 + - type: Produce + seedId: pyrotton + - type: Tag + tags: + - ClothMade + - CottonBoll + - type: Extractable + grindableSolutionName: food diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigarettes/joints.yml b/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigarettes/joints.yml index e3ae06ec9f..9a0d96e89e 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigarettes/joints.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Cigarettes/joints.yml @@ -28,6 +28,34 @@ reagents: - ReagentId: THC Quantity: 20 + +- type: entity + id: JointRainbow + parent: Joint + name: joint + suffix: Rainbow + description: A roll of dried plant matter wrapped in thin paper. Seems to be colorful inside. + components: + - type: Construction + graph: smokeableJointRainbow + node: jointRainbow + - type: SolutionContainerManager + solutions: + smokable: + maxVol: 20 + reagents: + - ReagentId: SpaceDrugs + Quantity: 4 + - ReagentId: Lipolicide + Quantity: 4 + - ReagentId: MindbreakerToxin + Quantity: 2.66 + - ReagentId: Happiness + Quantity: 2.66 +# - ReagentId: ColorfulReagent +# Quantity: 1.33 + - ReagentId: Psicodine + Quantity: 0.8 - type: entity id: Blunt @@ -59,3 +87,31 @@ reagents: - ReagentId: THC Quantity: 20 + +- type: entity + id: BluntRainbow + parent: Blunt + name: blunt + suffix: Rainbow + description: A roll of dried plant matter wrapped in a dried tobacco leaf. Seems to be colorful inside. + components: + - type: Construction + graph: smokeableBluntRainbow + node: bluntRainbow + - type: SolutionContainerManager + solutions: + smokable: + maxVol: 20 + reagents: + - ReagentId: SpaceDrugs + Quantity: 4 + - ReagentId: Lipolicide + Quantity: 4 + - ReagentId: MindbreakerToxin + Quantity: 2.66 + - ReagentId: Happiness + Quantity: 2.66 +# - ReagentId: ColorfulReagent +# Quantity: 1.33 + - ReagentId: Psicodine + Quantity: 0.8 diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Pipes/pipe.yml b/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Pipes/pipe.yml index c3f13d80b7..79c568802c 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Pipes/pipe.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Smokeables/Pipes/pipe.yml @@ -63,3 +63,26 @@ path: /Audio/Weapons/Guns/Empty/empty.ogg ejectSound: path: /Audio/Weapons/Guns/Empty/empty.ogg + +- type: entity + id: SmokingPipeFilledCannabisRainbow + parent: SmokingPipe + name: pipe + suffix: Rainbow Cannabis + description: Just like grandpappy used to smoke. + components: + - type: ContainerContainer + containers: + bowl_slot: !type:ContainerSlot + - type: ItemSlots + - type: SmokingPipe + bowl_slot: + name: smoking-pipe-slot-component-slot-name-bowl + startingItem: GroundCannabisRainbow + whitelist: + tags: + - Smokable + insertSound: + path: /Audio/Weapons/Guns/Empty/empty.ogg + ejectSound: + path: /Audio/Weapons/Guns/Empty/empty.ogg diff --git a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/base_machineboard.yml b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/base_machineboard.yml index 164777284f..360fc5ffaa 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/base_machineboard.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/base_machineboard.yml @@ -15,7 +15,7 @@ price: 100 - type: PhysicalComposition materialComposition: - Glass: 400 + Glass: 230 chemicalComposition: Silicon: 20 diff --git a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml index f60ffad847..61dbb70cd4 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml @@ -29,6 +29,10 @@ Amount: 1 DefaultPrototype: Igniter ExamineName: Igniter + - type: ReverseEngineering # Delta + difficulty: 2 + recipes: + - AutolatheHyperConvectionMachineCircuitboard - type: entity id: ProtolatheMachineCircuitboard @@ -66,6 +70,10 @@ Amount: 1 DefaultPrototype: Igniter ExamineName: Igniter + - type: ReverseEngineering # Delta + difficulty: 2 + recipes: + - ProtolatheHyperConvectionMachineCircuitboard - type: entity id: BiofabricatorMachineCircuitboard @@ -79,6 +87,10 @@ MatterBin: 4 materialRequirements: Glass: 1 + - type: ReverseEngineering # Delta + difficulty: 2 + recipes: + - BiofabricatorMachineCircuitboard - type: entity id: SecurityTechFabCircuitboard @@ -151,10 +163,28 @@ Amount: 2 DefaultPrototype: Beaker ExamineName: Glass Beaker - - type: ReverseEngineering # Nyano - difficulty: 3 - recipes: - - CircuitImprinterMachineCircuitboard + +- type: entity + parent: BaseMachineCircuitboard + id: CircuitImprinterHyperConvectionMachineCircuitboard + name: hyper convection circuit imprinter machine board + description: A machine printed circuit board for a hyper convection circuit imprinter. + components: + - type: Sprite + state: science + - type: MachineBoard + prototype: CircuitImprinterHyperConvection + requirements: + MatterBin: 2 + tagRequirements: + GlassBeaker: + Amount: 2 + DefaultPrototype: Beaker + ExamineName: Glass Beaker + Igniter: + Amount: 1 + DefaultPrototype: Igniter + ExamineName: Igniter - type: entity id: ExosuitFabricatorMachineCircuitboard @@ -171,7 +201,7 @@ materialRequirements: Glass: 5 - type: ReverseEngineering # Nyano - difficulty: 3 + difficulty: 2 recipes: - ExosuitFabricatorMachineCircuitboard - type: GuideHelp @@ -202,10 +232,6 @@ requirements: MatterBin: 1 Manipulator: 2 - - type: ReverseEngineering # Nyano - difficulty: 2 - recipes: - - UniformPrinterMachineCircuitboard - type: entity id: VaccinatorMachineCircuitboard @@ -277,27 +303,6 @@ recipes: - ArtifactAnalyzerMachineCircuitboard -- type: entity - id: TraversalDistorterMachineCircuitboard - parent: BaseMachineCircuitboard - name: traversal distorter machine board - description: A machine printed circuit board for a traversal distorter. - components: - - type: Sprite - state: science - - type: MachineBoard - prototype: MachineTraversalDistorter - requirements: - Manipulator: 1 - Capacitor: 2 - materialRequirements: - Steel: 5 - Cable: 1 - - type: ReverseEngineering # Nyano - difficulty: 2 - recipes: - - TraversalDistorterMachineCircuitboard - - type: entity id: ArtifactCrusherMachineCircuitboard parent: BaseMachineCircuitboard @@ -313,6 +318,10 @@ materialRequirements: Glass: 1 Steel: 5 + - type: ReverseEngineering # Delta + difficulty: 3 + recipes: + - ArtifactCrusherMachineCircuitboard - type: entity parent: BaseMachineCircuitboard @@ -350,6 +359,10 @@ Cable: 5 PlasmaGlass: 15 MetalRod: 4 + - type: ReverseEngineering # Nyano + difficulty: 2 + recipes: + - AnomalyVesselExperimentalCircuitboard - type: entity parent: BaseMachineCircuitboard @@ -367,6 +380,10 @@ materialRequirements: PlasmaGlass: 5 Cable: 5 + - type: ReverseEngineering # Delta + difficulty: 3 + recipes: + - AnomalySynchronizerCircuitboard - type: entity parent: BaseMachineCircuitboard @@ -451,6 +468,10 @@ deconstructionTarget: null graph: ThermomachineBoard node: hellfirefreezer + - type: ReverseEngineering # Delta + difficulty: 3 + recipes: + - HellfireFreezerMachineCircuitBoard - type: entity parent: BaseMachineCircuitboard @@ -544,6 +565,27 @@ recipes: - CloningPodMachineCircuitboard +- type: entity + id: MetempsychoticMachineCircuitboard + parent: BaseMachineCircuitboard + name: metempsychotic machine machine board + description: A machine printed circuit board for a cloning pod + components: + - type: Sprite + state: medical + - type: MachineBoard + prototype: MetempsychoticMachine + requirements: + Capacitor: 2 + Manipulator: 2 + materialRequirements: + Glass: 1 + Cable: 1 + - type: ReverseEngineering + difficulty: 3 + recipes: + - MetempsychoticMachineCircuitboard + - type: entity id: MedicalScannerMachineCircuitboard parent: BaseMachineCircuitboard @@ -575,6 +617,10 @@ materialRequirements: Steel: 1 Cable: 2 + - type: ReverseEngineering # Delta + difficulty: 3 + recipes: + - CrewMonitoringServerMachineCircuitboard - type: entity id: CryoPodMachineCircuitboard @@ -614,10 +660,6 @@ Amount: 2 DefaultPrototype: Beaker ExamineName: Glass Beaker - - type: ReverseEngineering # Nyano - difficulty: 2 - recipes: - - ChemMasterMachineCircuitboard - type: entity id: ChemDispenserMachineCircuitboard @@ -639,10 +681,6 @@ Amount: 2 DefaultPrototype: Beaker ExamineName: Glass Beaker - - type: ReverseEngineering # Nyano - difficulty: 2 - recipes: - - ChemDispenserMachineCircuitboard - type: entity id: BiomassReclaimerMachineCircuitboard @@ -729,9 +767,6 @@ PowerCell: 4 materialRequirements: CableHV: 10 - - type: ReverseEngineering # Nyano - recipes: - - SMESMachineCircuitboard - type: entity id: CellRechargerCircuitboard @@ -841,6 +876,10 @@ materialComposition: Steel: 30 Plastic: 30 + - type: ReverseEngineering # Delta + difficulty: 2 + recipes: + - TurboItemRechargerCircuitboard - type: entity id: SubstationMachineCircuitboard @@ -856,9 +895,6 @@ materialRequirements: CableMV: 5 CableHV: 5 - - type: ReverseEngineering # Nyano - recipes: - - SubstationMachineCircuitboard - type: PhysicalComposition materialComposition: Glass: 200 @@ -900,7 +936,7 @@ DefaultPrototype: SaxophoneInstrument ExamineName: Woodwind Instrument - type: ReverseEngineering # Nyano - difficulty: 3 + difficulty: 2 recipes: - DawInstrumentMachineCircuitboard @@ -917,10 +953,6 @@ Capacitor: 1 materialRequirements: CableHV: 5 - - type: ReverseEngineering # Nyano - difficulty: 2 - recipes: - - GeneratorPlasmaMachineCircuitboard - type: PhysicalComposition materialComposition: Glass: 200 @@ -928,6 +960,10 @@ Silicon: 20 - type: StaticPrice price: 40 + - type: ReverseEngineering # Nyano + difficulty: 2 + recipes: + - PortableGeneratorPacmanMachineCircuitboard - type: entity id: ThrusterMachineCircuitboard @@ -940,6 +976,10 @@ Capacitor: 4 materialRequirements: Steel: 5 + - type: ReverseEngineering # Delta + difficulty: 3 + recipes: + - ThrusterMachineCircuitboard - type: entity id: GyroscopeMachineCircuitboard @@ -953,6 +993,10 @@ Capacitor: 1 materialRequirements: Glass: 2 + - type: ReverseEngineering # Delta + difficulty: 3 + recipes: + - GyroscopeMachineCircuitboard - type: entity id: PortableGeneratorSuperPacmanMachineCircuitboard @@ -972,12 +1016,12 @@ Glass: 200 chemicalComposition: Silicon: 20 + - type: StaticPrice + price: 40 - type: ReverseEngineering # Nyano difficulty: 2 recipes: - PortableGeneratorSuperPacmanMachineCircuitboard - - type: StaticPrice - price: 40 - type: entity id: PortableGeneratorJrPacmanMachineCircuitboard @@ -999,6 +1043,10 @@ Silicon: 20 - type: StaticPrice price: 40 + - type: ReverseEngineering # Nyano + difficulty: 2 + recipes: + - PortableGeneratorJrPacmanMachineCircuitboard - type: entity id: ReagentGrinderMachineCircuitboard @@ -1033,7 +1081,7 @@ Capacitor: 2 materialRequirements: Glass: 1 - - type: ReverseEngineering # Nyano + - type: ReverseEngineering # Delta difficulty: 2 recipes: - HotplateMachineCircuitboard @@ -1051,6 +1099,10 @@ materialRequirements: Glass: 2 Cable: 5 + - type: ReverseEngineering # Delta + difficulty: 2 + recipes: + - ElectricGrillMachineCircuitboard - type: entity id: StasisBedMachineCircuitboard @@ -1116,6 +1168,10 @@ materialRequirements: Steel: 5 Plastic: 5 + - type: ReverseEngineering # Delta + difficulty: 2 + recipes: + - MaterialReclaimerMachineCircuitboard - type: entity id: OreProcessorMachineCircuitboard @@ -1131,9 +1187,6 @@ Manipulator: 3 materialRequirements: Glass: 1 - - type: ReverseEngineering # Nyano - recipes: - - OreProcessorMachineCircuitboard - type: entity parent: BaseMachineCircuitboard @@ -1149,6 +1202,10 @@ Manipulator: 3 materialRequirements: Glass: 1 + - type: ReverseEngineering # Delta + difficulty: 2 + recipes: + - OreProcessorIndustrialMachineCircuitboard - type: entity id: SheetifierMachineCircuitboard @@ -1195,6 +1252,10 @@ Amount: 1 DefaultPrototype: ForkPlastic ExamineName: Utensil + - type: ReverseEngineering # Delta + difficulty: 2 + recipes: + - FatExtractorMachineCircuitboard - type: entity parent: BaseMachineCircuitboard @@ -1208,6 +1269,10 @@ MatterBin: 1 materialRequirements: Steel: 1 + - type: ReverseEngineering # Delta + difficulty: 2 + recipes: + - FlatpackerMachineCircuitboard - type: entity id: EmitterCircuitboard @@ -1327,9 +1392,6 @@ Amount: 1 DefaultPrototype: Beaker ExamineName: Glass Beaker - - type: ReverseEngineering # Nyano - recipes: - - BoozeDispenserMachineCircuitboard - type: entity id: CargoTelepadMachineCircuitboard @@ -1346,6 +1408,10 @@ materialRequirements: Steel: 5 Bluespace: 2 #DeltaV Bluespace Exists + - type: ReverseEngineering # Delta + difficulty: 3 + recipes: + - CargoTelepadMachineCircuitboard - type: entity id: SodaDispenserMachineCircuitboard @@ -1364,9 +1430,6 @@ Amount: 1 DefaultPrototype: Beaker ExamineName: Glass Beaker - - type: ReverseEngineering # Nyano - recipes: - - SodaDispenserMachineCircuitboard - type: entity id: TelecomServerCircuitboard @@ -1416,6 +1479,10 @@ Steel: 5 CableHV: 5 Uranium: 2 + - type: ReverseEngineering # Delta + difficulty: 3 + recipes: + - MiniGravityGeneratorCircuitboard - type: entity id: ShuttleGunSvalinnMachineGunCircuitboard @@ -1433,6 +1500,10 @@ materialRequirements: Steel: 5 CableHV: 5 + - type: ReverseEngineering # Delta + difficulty: 3 + recipes: + - ShuttleGunSvalinnMachineGunCircuitboard - type: entity id: ShuttleGunPerforatorCircuitboard @@ -1450,6 +1521,10 @@ materialRequirements: Steel: 10 CableHV: 5 + - type: ReverseEngineering # Delta + difficulty: 3 + recipes: + - ShuttleGunPerforatorCircuitboard - type: entity id: ShuttleGunFriendshipCircuitboard @@ -1467,6 +1542,10 @@ materialRequirements: Steel: 7 CableHV: 5 + - type: ReverseEngineering # Delta + difficulty: 3 + recipes: + - ShuttleGunFriendshipCircuitboard - type: entity id: ShuttleGunDusterCircuitboard @@ -1485,6 +1564,10 @@ Steel: 10 CableHV: 5 Uranium: 2 + - type: ReverseEngineering # Delta + difficulty: 4 + recipes: + - ShuttleGunDusterCircuitboard - type: entity id: ShuttleGunKineticCircuitboard @@ -1502,6 +1585,10 @@ materialRequirements: Steel: 5 CableHV: 2 + - type: ReverseEngineering # Delta + difficulty: 2 + recipes: + - ShuttleGunKineticCircuitboard - type: entity parent: BaseMachineCircuitboard @@ -1514,4 +1601,61 @@ MatterBin: 1 Manipulator: 3 materialRequirements: - Glass: 1 \ No newline at end of file + Glass: 1 + - type: ReverseEngineering # Delta + difficulty: 2 + recipes: + - ReagentGrinderIndustrialMachineCircuitboard + +- type: entity + parent: BaseMachineCircuitboard + id: JukeboxCircuitBoard + name: jukebox machine board + description: A machine printed circuit board for a jukebox. + components: + - type: MachineBoard + prototype: Jukebox + materialRequirements: + WoodPlank: 5 + Steel: 2 + Glass: 5 + Cable: 2 + - type: ReverseEngineering # Delta + difficulty: 2 + recipes: + - JukeboxCircuitBoard + +- type: entity + id: TraversalDistorterMachineCircuitboard + parent: BaseMachineCircuitboard + name: traversal distorter machine board + description: A machine printed circuit board for a traversal distorter. + components: + - type: Sprite + state: science + - type: MachineBoard + prototype: MachineTraversalDistorter + requirements: + Manipulator: 1 + Capacitor: 2 + materialRequirements: + Steel: 5 + Cable: 1 + - type: ReverseEngineering # Nyano + difficulty: 2 + recipes: + - TraversalDistorterMachineCircuitboard + +- type: entity + id: MedicalBiofabMachineBoard + parent: BaseMachineCircuitboard + name: medical biofab machine board + description: A machine printed circuit board for a medical biofab. + components: + - type: Sprite + state: medical + - type: MachineBoard + prototype: MedicalBiofabricator + requirements: + MatterBin: 2 + Manipulator: 2 diff --git a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml index ffff8847ed..7681a459cb 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml @@ -14,15 +14,15 @@ price: 100 - type: PhysicalComposition materialComposition: - Glass: 400 + Glass: 230 chemicalComposition: Silicon: 20 - type: entity parent: BaseComputerCircuitboard id: AlertsComputerCircuitboard - name: alerts computer board - description: A computer printed circuit board for an alerts computer. + name: atmospheric alerts computer board + description: A computer printed circuit board for an atmospheric alerts computer. components: - type: ComputerBoard prototype: ComputerAlert @@ -122,6 +122,10 @@ prototype: ComputerSalvageExpedition - type: StealTarget stealGroup: SalvageExpeditionsComputerCircuitboard + - type: ReverseEngineering # Nyano + difficulty: 2 + recipes: + - SalvageExpeditionsComputerCircuitboard - type: entity parent: BaseComputerCircuitboard @@ -177,6 +181,10 @@ - type: Tag tags: - ComputerTelevisionCircuitboard + - type: ReverseEngineering # Nyano + difficulty: 2 + recipes: + - ComputerTelevisionCircuitboard - type: entity parent: BaseComputerCircuitboard @@ -286,6 +294,10 @@ components: - type: ComputerBoard prototype: ComputerRadar + - type: ReverseEngineering # Nyano + difficulty: 2 + recipes: + - RadarConsoleCircuitboard - type: entity parent: BaseComputerCircuitboard @@ -339,6 +351,10 @@ components: - type: ComputerBoard prototype: ComputerShuttle + - type: ReverseEngineering # Nyano + difficulty: 3 + recipes: + - ShuttleConsoleCircuitboard - type: entity parent: BaseComputerCircuitboard @@ -396,6 +412,10 @@ price: 150 - type: ComputerBoard prototype: ComputerMassMedia + - type: ReverseEngineering # Nyano + difficulty: 3 + recipes: + - ComputerMassMediaCircuitboard - type: entity parent: BaseComputerCircuitboard @@ -407,3 +427,14 @@ state: cpu_engineering - type: ComputerBoard prototype: ComputerSensorMonitoring + +- type: entity + parent: BaseComputerCircuitboard + id: RoboticsConsoleCircuitboard + name: robotics control console board + description: A computer printed circuit board for a robotics control console. + components: + - type: Sprite + state: cpu_science + - type: ComputerBoard + prototype: ComputerRoboticsControl diff --git a/Resources/Prototypes/Entities/Objects/Devices/Electronics/door.yml b/Resources/Prototypes/Entities/Objects/Devices/Electronics/door.yml index b761ef7996..4d7dfed334 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Electronics/door.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Electronics/door.yml @@ -19,10 +19,10 @@ - type: AccessReader - type: ActivatableUI key: enum.DoorElectronicsConfigurationUiKey.Key - allowedItems: + requiredItems: tags: - DoorElectronicsConfigurator - type: UserInterface interfaces: - - key: enum.DoorElectronicsConfigurationUiKey.Key - type: DoorElectronicsBoundUserInterface + enum.DoorElectronicsConfigurationUiKey.Key: + type: DoorElectronicsBoundUserInterface diff --git a/Resources/Prototypes/Entities/Objects/Devices/Electronics/door_access.yml b/Resources/Prototypes/Entities/Objects/Devices/Electronics/door_access.yml index b793416064..c8db255f7b 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Electronics/door_access.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Electronics/door_access.yml @@ -1,19 +1,52 @@ +# Command +- type: entity + parent: DoorElectronics + id: DoorElectronicsCaptain + suffix: Captain, Locked + components: + - type: AccessReader + access: [["Captain"]] - type: entity parent: DoorElectronics - id: DoorElectronicsService - suffix: Service, Locked + id: DoorElectronicsHeadOfPersonnel + suffix: HeadOfPersonnel, Locked components: - type: AccessReader - access: [["Service"]] + access: [["HeadOfPersonnel"]] - type: entity parent: DoorElectronics - id: DoorElectronicsTheatre - suffix: Theatre, Locked + id: DoorElectronicsCommand + suffix: Command, Locked components: - type: AccessReader - access: [["Theatre"]] + access: [["Command"]] + +# Service +- type: entity + parent: DoorElectronics + id: DoorElectronicsBar + suffix: Bar, Locked + components: + - type: AccessReader + access: [["Bar"]] + +- type: entity + parent: DoorElectronics + id: DoorElectronicsBarKitchen + suffix: Bar, Locked + components: + - type: AccessReader + access: [["Bar"], ["Kitchen"]] + +- type: entity + parent: DoorElectronics + id: DoorElectronicsHydroponics + suffix: Hydroponics, Locked + components: + - type: AccessReader + access: [["Hydroponics"]] - type: entity parent: DoorElectronics @@ -25,11 +58,11 @@ - type: entity parent: DoorElectronics - id: DoorElectronicsJanitor - suffix: Janitor, Locked + id: DoorElectronicsTheatre + suffix: Theatre, Locked components: - type: AccessReader - access: [["Janitor"]] + access: [["Theatre"]] - type: entity parent: DoorElectronics @@ -41,19 +74,19 @@ - type: entity parent: DoorElectronics - id: DoorElectronicsBar - suffix: Bar, Locked + id: DoorElectronicsKitchenHydroponics + suffix: Kitchen/Hydroponics, Locked components: - type: AccessReader - access: [["Bar"]] + access: [["Kitchen"], ["Hydroponics"]] - type: entity parent: DoorElectronics - id: DoorElectronicsHydroponics - suffix: Hydroponics, Locked + id: DoorElectronicsJanitor + suffix: Janitor, Locked components: - type: AccessReader - access: [["Hydroponics"]] + access: [["Janitor"]] - type: entity parent: DoorElectronics @@ -65,19 +98,28 @@ - type: entity parent: DoorElectronics - id: DoorElectronicsCaptain - suffix: Captain, Locked + id: DoorElectronicsService + suffix: Service, Locked components: - type: AccessReader - access: [["Captain"]] + access: [["Service"]] +# Cargo - type: entity parent: DoorElectronics - id: DoorElectronicsExternal - suffix: External, Locked + id: DoorElectronicsQuartermaster + suffix: Quartermaster, Locked components: - type: AccessReader - access: [["External"]] + access: [["Quartermaster"]] + +- type: entity + parent: DoorElectronics + id: DoorElectronicsSalvage + suffix: Salvage, Locked + components: + - type: AccessReader + access: [["Salvage"]] - type: entity parent: DoorElectronics @@ -87,13 +129,14 @@ - type: AccessReader access: [["Cargo"]] +# Engineering - type: entity parent: DoorElectronics - id: DoorElectronicsEngineering - suffix: Engineering, Locked + id: DoorElectronicsChiefEngineer + suffix: ChiefEngineer, Locked components: - type: AccessReader - access: [["Engineering"]] + access: [["ChiefEngineer"]] - type: entity parent: DoorElectronics @@ -105,35 +148,36 @@ - type: entity parent: DoorElectronics - id: DoorElectronicsFreezer - suffix: Freezer, Locked + id: DoorElectronicsEngineering + suffix: Engineering, Locked components: - type: AccessReader - access: [["Kitchen"], ["Hydroponics"]] + access: [["Engineering"]] +# Science - type: entity parent: DoorElectronics - id: DoorElectronicsSalvage - suffix: Salvage, Locked + id: DoorElectronicsMorgue + suffix: Morgue, Locked components: - type: AccessReader - access: [["Salvage"]] + access: [["Medical"], ["Detective"], ["Chapel"]] - type: entity parent: DoorElectronics - id: DoorElectronicsMedical - suffix: Medical, Locked + id: DoorElectronicsResearchDirector + suffix: ResearchDirector, Locked components: - type: AccessReader - access: [["Medical"]] + access: [["ResearchDirector"]] - type: entity parent: DoorElectronics - id: DoorElectronicsChemistry - suffix: Chemistry, Locked + id: DoorElectronicsMedicalResearch + suffix: Medical/Science, Locked components: - type: AccessReader - access: [["Chemistry"]] + access: [["Research"], ["Medical"]] - type: entity parent: DoorElectronics @@ -143,117 +187,121 @@ - type: AccessReader access: [["Research"]] +# Security - type: entity parent: DoorElectronics - id: DoorElectronicsScience - suffix: Science, Locked + id: DoorElectronicsHeadOfSecurity + suffix: HeadOfSecurity, Locked components: - type: AccessReader - access: [["Research"], ["Medical"]] + access: [["HeadOfSecurity"]] - type: entity parent: DoorElectronics - id: DoorElectronicsCommand - suffix: Command, Locked + id: DoorElectronicsArmory + suffix: Armory, Locked components: - type: AccessReader - access: [["Command"]] + access: [["Armory"]] - type: entity parent: DoorElectronics - id: DoorElectronicsCentralCommand - suffix: CentralCommand, Locked + id: DoorElectronicsDetective + suffix: Detective, Locked components: - type: AccessReader - access: [["CentralCommand"]] + access: [["Detective"]] - type: entity parent: DoorElectronics - id: DoorElectronicsChiefMedicalOfficer - suffix: ChiefMedicalOfficer, Locked + id: DoorElectronicsSecurity + suffix: Security, Locked components: - type: AccessReader - access: [["ChiefMedicalOfficer"]] + access: [["Security"]] - type: entity parent: DoorElectronics - id: DoorElectronicsChiefEngineer - suffix: ChiefEngineer, Locked + id: DoorElectronicsSecurityLawyer + suffix: Security/Lawyer, Locked components: - type: AccessReader - access: [["ChiefEngineer"]] + access: [["Security"], ["Lawyer"]] - type: entity parent: DoorElectronics - id: DoorElectronicsHeadOfSecurity - suffix: HeadOfSecurity, Locked + id: DoorElectronicsBrig + suffix: Brig, Locked components: - type: AccessReader - access: [["HeadOfSecurity"]] + access: [["Brig"]] +# Medical - type: entity parent: DoorElectronics - id: DoorElectronicsResearchDirector - suffix: ResearchDirector, Locked + id: DoorElectronicsChiefMedicalOfficer + suffix: ChiefMedicalOfficer, Locked components: - type: AccessReader - access: [["ResearchDirector"]] + access: [["ChiefMedicalOfficer"]] - type: entity parent: DoorElectronics - id: DoorElectronicsHeadOfPersonnel - suffix: HeadOfPersonnel, Locked + id: DoorElectronicsChemistry + suffix: Chemistry, Locked components: - type: AccessReader - access: [["HeadOfPersonnel"]] + access: [["Chemistry"]] - type: entity parent: DoorElectronics - id: DoorElectronicsQuartermaster - suffix: Quartermaster, Locked + id: DoorElectronicsMedical + suffix: Medical, Locked components: - type: AccessReader - access: [["Quartermaster"]] + access: [["Medical"]] +# Syndicate - type: entity parent: DoorElectronics - id: DoorElectronicsSecurity - suffix: Security, Locked + id: DoorElectronicsNukeop + suffix: Nukeop, Locked components: - type: AccessReader - access: [["Security"]] + access: [["NuclearOperative"]] - type: entity parent: DoorElectronics - id: DoorElectronicsSecurityLawyer - suffix: Security/Lawyer, Locked + id: DoorElectronicsSyndicateAgent + suffix: SyndicateAgent, Locked components: - type: AccessReader - access: [["Security"], ["Lawyer"]] + access: [["SyndicateAgent"]] +# Misc - type: entity parent: DoorElectronics - id: DoorElectronicsDetective - suffix: Detective, Locked + id: DoorElectronicsCentralCommand + suffix: CentralCommand, Locked components: - type: AccessReader - access: [["Detective"]] + access: [["CentralCommand"]] -#- type: entity -# parent: DoorElectronics -# id: DoorElectronicsBrig -# suffix: Brig, Locked -# components: -# - type: AccessReader -# access: [["Brig"]] +- type: entity + parent: DoorElectronics + id: DoorElectronicsExternal + suffix: External, Locked + components: + - type: AccessReader + access: [["External"]] - type: entity parent: DoorElectronics - id: DoorElectronicsArmory - suffix: Armory, Locked + id: DoorElectronicsMaintenance + suffix: Maintenance, Locked components: - type: AccessReader - access: [["Armory"]] + access: [["Maintenance"]] - type: entity parent: DoorElectronics @@ -265,56 +313,114 @@ - type: entity parent: DoorElectronics - id: DoorElectronicsMaintenance - suffix: Maintenance, Locked + id: DoorElectronicsChiefJustice + suffix: ChiefJustice, Locked components: - type: AccessReader - access: [["Maintenance"]] + access: [["ChiefJustice"]] - type: entity parent: DoorElectronics - id: DoorElectronicsSyndicateAgent - suffix: SyndicateAgent, Locked + id: DoorElectronicsJustice + suffix: Justice, Locked components: - type: AccessReader - access: [["SyndicateAgent"]] + access: [["Justice"]] - type: entity parent: DoorElectronics - id: DoorElectronicsNukeop - suffix: Nukeop, Locked + id: DoorElectronicsProsecutor + suffix: Prosecutor, Locked components: - type: AccessReader - access: [["NuclearOperative"]] + access: [["Prosecutor"]] - type: entity parent: DoorElectronics - id: DoorElectronicsRnDMed - suffix: Medical/Science, Locked + id: DoorElectronicsMantis + suffix: Mantis, Locked components: - type: AccessReader - access: [["Research"], ["Medical"]] + access: [["Mantis"]] - type: entity parent: DoorElectronics - id: DoorElectronicsChiefJustice - suffix: ChiefJustice, Locked + id: DoorElectronicsCorpsman + suffix: Corpsman, Locked components: - type: AccessReader - access: [["ChiefJustice"]] + access: [["Corpsman"]] - type: entity parent: DoorElectronics - id: DoorElectronicsJustice - suffix: Justice, Locked + id: DoorElectronicsBoxer + suffix: Boxer, Locked components: - type: AccessReader - access: [["Justice"]] + access: [["Boxer"]] - type: entity parent: DoorElectronics - id: DoorElectronicsProsecutor - suffix: Prosecutor, Locked + id: DoorElectronicsClown + suffix: Clown, Locked + components: + - type: AccessReader + access: [["Clown"]] + +- type: entity + parent: DoorElectronics + id: DoorElectronicsMime + suffix: Mime, Locked + components: + - type: AccessReader + access: [["Mime"]] + +- type: entity + parent: DoorElectronics + id: DoorElectronicsMusician + suffix: Musician, Locked + components: + - type: AccessReader + access: [["Musician"]] + + +- type: entity + parent: DoorElectronics + id: DoorElectronicsReporter + suffix: Reporter, Locked + components: + - type: AccessReader + access: [["Reporter"]] + + +- type: entity + parent: DoorElectronics + id: DoorElectronicsLibrary + suffix: Library, Locked + components: + - type: AccessReader + access: [["Library"]] + +- type: entity + parent: DoorElectronics + id: DoorElectronicsZookeeper + suffix: Zookeeper, Locked + components: + - type: AccessReader + access: [["Zookeeper"]] + +- type: entity + parent: DoorElectronics + id: DoorElectronicsPsychologist + suffix: Psychologist, Locked + components: + - type: AccessReader + access: [["Psychologist"]] + +- type: entity + parent: DoorElectronics + id: DoorElectronicsMail + suffix: Mail, Locked components: - type: AccessReader - access: [["Prosecutor"]] \ No newline at end of file + access: [["Mail"]] diff --git a/Resources/Prototypes/Entities/Objects/Devices/Electronics/triggers.yml b/Resources/Prototypes/Entities/Objects/Devices/Electronics/triggers.yml index 43ed20c694..2cb9b5233d 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Electronics/triggers.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Electronics/triggers.yml @@ -18,6 +18,8 @@ state: timer - type: Item size: Small + - type: StaticPrice + price: 40 - type: PayloadTrigger components: - type: OnUseTimerTrigger @@ -28,8 +30,6 @@ path: /Audio/Machines/Nuke/general_beep.ogg params: volume: -2 - - type: StaticPrice - price: 40 - type: entity parent: TimerTrigger @@ -40,6 +40,11 @@ - type: Sprite sprite: Objects/Devices/signaltrigger.rsi state: signaltrigger + - type: StaticPrice + price: 40 + - type: Tag + tags: + - SignalTrigger - type: PayloadTrigger components: - type: TriggerOnSignal @@ -49,8 +54,6 @@ - type: WirelessNetworkConnection range: 200 - type: DeviceLinkSink - - type: StaticPrice - price: 40 - type: entity parent: BaseItem @@ -61,11 +64,11 @@ - type: Sprite sprite: Objects/Devices/voice.rsi state: voice - - type: PayloadTrigger - components: - - type: TriggerOnVoice - type: StaticPrice price: 40 - type: Tag tags: - VoiceTrigger + - type: PayloadTrigger + components: + - type: TriggerOnVoice diff --git a/Resources/Prototypes/Entities/Objects/Devices/Syndicate_Gadgets/reinforcement_teleporter.yml b/Resources/Prototypes/Entities/Objects/Devices/Syndicate_Gadgets/reinforcement_teleporter.yml index a8489866fc..660a8c9e60 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Syndicate_Gadgets/reinforcement_teleporter.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Syndicate_Gadgets/reinforcement_teleporter.yml @@ -12,6 +12,8 @@ name: ghost-role-information-syndicate-reinforcement-name description: ghost-role-information-syndicate-reinforcement-description rules: ghost-role-information-syndicate-reinforcement-rules + raffle: + settings: default - type: GhostRoleMobSpawner prototype: MobHumanSyndicateAgent - type: EmitSoundOnUse @@ -29,24 +31,28 @@ - type: entity parent: ReinforcementRadioSyndicate - id: ReinforcementRadioSyndicateMonkey - name: syndicate monkey reinforcement radio - description: Calls in a specially trained monkey to assist you. + id: ReinforcementRadioSyndicateAncestor + name: syndicate genetic ancestor reinforcement radio + description: Calls in a specially trained ancestor of your choosing to assist you. components: - type: GhostRole name: ghost-role-information-syndicate-monkey-reinforcement-name description: ghost-role-information-syndicate-monkey-reinforcement-description rules: ghost-role-information-syndicate-monkey-reinforcement-rules + raffle: + settings: default - type: GhostRoleMobSpawner prototype: MobMonkeySyndicateAgent + selectablePrototypes: ["SyndicateMonkey", "SyndicateKobold"] - type: entity - parent: ReinforcementRadioSyndicateMonkey - id: ReinforcementRadioSyndicateMonkeyNukeops # Reinforcement radio exclusive to nukeops uplink + parent: ReinforcementRadioSyndicateAncestor + id: ReinforcementRadioSyndicateAncestorNukeops # Reinforcement radio exclusive to nukeops uplink suffix: NukeOps components: - type: GhostRoleMobSpawner prototype: MobMonkeySyndicateAgentNukeops + selectablePrototypes: ["SyndicateMonkeyNukeops", "SyndicateKoboldNukeops"] - type: entity parent: ReinforcementRadioSyndicate @@ -59,5 +65,7 @@ name: Syndicate Assault Cyborg description: Nuclear operatives needs reinforcements. You, a cold silicon killing machine, will help them. rules: Normal syndicate antagonist rules apply. Work with whoever called you in, and don't harm them. + raffle: + settings: default - type: GhostRoleMobSpawner prototype: PlayerBorgSyndicateAssaultBattery diff --git a/Resources/Prototypes/Entities/Objects/Devices/Syndicate_Gadgets/war_declarator.yml b/Resources/Prototypes/Entities/Objects/Devices/Syndicate_Gadgets/war_declarator.yml index 08e96d1de2..78009f1042 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Syndicate_Gadgets/war_declarator.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Syndicate_Gadgets/war_declarator.yml @@ -14,11 +14,11 @@ - type: ActivatableUI inHandsOnly: true singleUser: true - closeOnHandDeselect: false + requireActiveHand: false key: enum.WarDeclaratorUiKey.Key - type: UserInterface interfaces: - - key: enum.WarDeclaratorUiKey.Key + enum.WarDeclaratorUiKey.Key: type: WarDeclaratorBoundUserInterface - type: WarDeclarator message: war-declarator-default-message diff --git a/Resources/Prototypes/Entities/Objects/Devices/chameleon_projector.yml b/Resources/Prototypes/Entities/Objects/Devices/chameleon_projector.yml new file mode 100644 index 0000000000..e021281021 --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Devices/chameleon_projector.yml @@ -0,0 +1,71 @@ +- type: entity + parent: BaseItem + id: ChameleonProjector + name: chameleon projector + description: Holoparasite technology used to create a hard-light replica of any object around you. Disguise is destroyed when picked up or deactivated. + components: + - type: Sprite + sprite: /Textures/Objects/Devices/chameleon_projector.rsi + state: icon + - type: ChameleonProjector + whitelist: + components: + - Anchorable + - Item + blacklist: + components: + - ChameleonDisguise # no becoming kleiner + - InsideEntityStorage # no clark kent going in phone booth and becoming superman + - MindContainer # no + - Pda # PDAs currently make you invisible /!\ + polymorph: + entity: ChameleonDisguise + +- type: entity + noSpawn: true + parent: BaseMob + id: ChameleonDisguise + name: Urist McKleiner + components: + # this and the name/desc get replaced, this is just placeholder incase something goes wrong + - type: Sprite + sprite: /Textures/Mobs/Species/Human/parts.rsi + state: full + # so people can attempt to pick it up + - type: Item + # so it can take damage + # projector system sets health to be proportional to mass + - type: Damageable + - type: MobState + - type: MobThresholds + thresholds: + 0: Alive + 1: Critical + 200: Dead + - type: MovementSpeedModifier + baseWalkSpeed: 1 # precise movement for the perfect spot + baseSprintSpeed: 5 # the jig is up + - type: ChameleonDisguise + +# actions +- type: entity + noSpawn: true + id: ActionDisguiseNoRot + name: Toggle Rotation + description: Use this to prevent your disguise from rotating, making it easier to hide in some scenarios. + components: + - type: InstantAction + icon: Interface/VerbIcons/refresh.svg.192dpi.png + event: !type:DisguiseToggleNoRotEvent + +- type: entity + noSpawn: true + id: ActionDisguiseAnchor + name: Toggle Anchored + description: For many objects you will want to be anchored to not be completely obvious. + components: + - type: InstantAction + icon: + sprite: Objects/Tools/wrench.rsi + state: icon + event: !type:DisguiseToggleAnchoredEvent diff --git a/Resources/Prototypes/Entities/Objects/Devices/flatpack.yml b/Resources/Prototypes/Entities/Objects/Devices/flatpack.yml index 5499348bd1..d7cba59eb1 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/flatpack.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/flatpack.yml @@ -3,8 +3,7 @@ id: BaseFlatpack name: base flatpack description: A flatpack used for constructing something. - categories: - - hideSpawnMenu + categories: [ HideSpawnMenu ] components: - type: Item size: Large @@ -110,6 +109,20 @@ - type: GuideHelp guides: [ Singularity, Power ] +- type: entity + parent: BaseFlatpack + id: EmitterFlatpack + name: emitter flatpack + description: A flatpack used for constructing an emitter. + components: + - type: Flatpack + entity: Emitter + - type: Sprite + layers: + - state: emitter + - type: GuideHelp + guides: [ Singularity, Power ] + - type: entity parent: BaseFlatpack id: TeslaGeneratorFlatpack diff --git a/Resources/Prototypes/Entities/Objects/Devices/forensic_scanner.yml b/Resources/Prototypes/Entities/Objects/Devices/forensic_scanner.yml index 7436ae7c98..170751766b 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/forensic_scanner.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/forensic_scanner.yml @@ -17,11 +17,11 @@ - type: ActivatableUI key: enum.ForensicScannerUiKey.Key inHandsOnly: true - closeOnHandDeselect: false + requireActiveHand: false - type: UserInterface interfaces: - - key: enum.ForensicScannerUiKey.Key - type: ForensicScannerBoundUserInterface + enum.ForensicScannerUiKey.Key: + type: ForensicScannerBoundUserInterface - type: ForensicScanner - type: GuideHelp guides: diff --git a/Resources/Prototypes/Entities/Objects/Devices/geiger.yml b/Resources/Prototypes/Entities/Objects/Devices/geiger.yml index de4178ce7f..f8ee24c5c6 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/geiger.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/geiger.yml @@ -31,4 +31,7 @@ Med: {state: geiger_on_med} High: {state: geiger_on_high} Extreme: {state: geiger_on_ext} + - type: PhysicalComposition + materialComposition: + Plastic: 100 diff --git a/Resources/Prototypes/Entities/Objects/Devices/hand_teleporter.yml b/Resources/Prototypes/Entities/Objects/Devices/hand_teleporter.yml index 2e6c2d8c27..deac20e05e 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/hand_teleporter.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/hand_teleporter.yml @@ -9,10 +9,6 @@ layers: - state: icon - type: HandTeleporter - - type: ReverseEngineering # Nyano - difficulty: 4 - recipes: - - HandTeleporter - type: Tag tags: - HighRiskItem diff --git a/Resources/Prototypes/Entities/Objects/Devices/holoprojectors.yml b/Resources/Prototypes/Entities/Objects/Devices/holoprojectors.yml index 5cbf64c560..b7ad8ddd6a 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/holoprojectors.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/holoprojectors.yml @@ -68,6 +68,7 @@ - type: StaticPrice price: 80 - type: ReverseEngineering # Nyano + difficulty: 3 recipes: - HolofanProjector @@ -98,6 +99,10 @@ - HolofanProjector - type: StaticPrice price: 130 + - type: ReverseEngineering # Nyano + difficulty: 3 + recipes: + - HoloprojectorField - type: entity parent: HoloprojectorField diff --git a/Resources/Prototypes/Entities/Objects/Devices/instrumentFlatpacks.yml b/Resources/Prototypes/Entities/Objects/Devices/instrumentFlatpacks.yml new file mode 100644 index 0000000000..f6057d1d32 --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Devices/instrumentFlatpacks.yml @@ -0,0 +1,120 @@ +- type: entity + parent: BaseFlatpack + id: InstrumentFlatpack + name: instrument flatpack + description: A flatpack used for constructing something. + categories: [ HideSpawnMenu ] + components: + - type: Item + size: Normal + - type: Sprite + layers: + - state: service_music + +- type: entity + parent: InstrumentFlatpack + id: PianoFlatpack + name: piano flatpack + description: A flatpack used for constructing a piano. + components: + - type: Flatpack + entity: PianoInstrument + +- type: entity + parent: InstrumentFlatpack + id: UprightPianoFlatpack + name: upright piano flatpack + description: A flatpack used for constructing an upright piano. + components: + - type: Flatpack + entity: UprightPianoInstrument + +- type: entity + parent: InstrumentFlatpack + id: VibraphoneFlatpack + name: vibraphone flatpack + description: A flatpack used for constructing a vibraphone. + components: + - type: Flatpack + entity: VibraphoneInstrument + +- type: entity + parent: InstrumentFlatpack + id: MarimbaFlatpack + name: marimba flatpack + description: A flatpack used for constructing a marimba. + components: + - type: Flatpack + entity: MarimbaInstrument + +- type: entity + parent: InstrumentFlatpack + id: ChurchOrganFlatpack + name: church organ flatpack + description: A flatpack used for constructing a church organ. + components: + - type: Flatpack + entity: ChurchOrganInstrument + +- type: entity + parent: InstrumentFlatpack + id: TubaFlatpack + name: tuba flatpack + description: A flatpack used for constructing a tuba. + components: + - type: Flatpack + entity: TubaInstrument + +- type: entity + parent: InstrumentFlatpack + id: HarpFlatpack + name: harp flatpack + description: A flatpack used for constructing a harp. + components: + - type: Flatpack + entity: HarpInstrument + +- type: entity + parent: InstrumentFlatpack + id: TimpaniFlatpack + name: timpani flatpack + description: A flatpack used for constructing a timpani. + components: + - type: Flatpack + entity: TimpaniInstrument + +- type: entity + parent: InstrumentFlatpack + id: TaikoFlatpack + name: taiko flatpack + description: A flatpack used for constructing a taiko. + components: + - type: Flatpack + entity: TaikoInstrument + +- type: entity + parent: InstrumentFlatpack + id: ContrabassFlatpack + name: contrabass flatpack + description: A flatpack used for constructing a contrabass. + components: + - type: Flatpack + entity: ContrabassInstrument + +- type: entity + parent: InstrumentFlatpack + id: MinimoogFlatpack + name: minimoog flatpack + description: A flatpack used for constructing a minimoog. + components: + - type: Flatpack + entity: MinimoogInstrument + +- type: entity + parent: InstrumentFlatpack + id: TomDrumsFlatpack + name: tom drums flatpack + description: A flatpack used for constructing a tom drums set. + components: + - type: Flatpack + entity: TomDrumsInstrument diff --git a/Resources/Prototypes/Entities/Objects/Devices/mousetrap.yml b/Resources/Prototypes/Entities/Objects/Devices/mousetrap.yml index c3bd74dd75..d20f3c2fb1 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/mousetrap.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/mousetrap.yml @@ -13,9 +13,12 @@ - type: StepTrigger intersectRatio: 0.2 requiredTriggeredSpeed: 0 + triggerGroups: + types: + - Mousetrap - type: Mousetrap - type: TriggerOnStepTrigger - - type: ShoesRequiredStepTrigger + - type: ClothingRequiredStepTrigger - type: DamageUserOnTrigger damage: types: diff --git a/Resources/Prototypes/Entities/Objects/Devices/pda.yml b/Resources/Prototypes/Entities/Objects/Devices/pda.yml index 2c28f60da5..e32271f4cd 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/pda.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/pda.yml @@ -57,6 +57,7 @@ - idcard - Belt - type: UnpoweredFlashlight + - type: EtherealLight - type: PointLight enabled: false radius: 1.5 @@ -86,24 +87,27 @@ - type: ActivatableUI key: enum.PdaUiKey.Key singleUser: true - closeOnHandDeselect: false - type: UserInterface interfaces: - - key: enum.PdaUiKey.Key - type: PdaBoundUserInterface - - key: enum.StoreUiKey.Key - type: StoreBoundUserInterface - - key: enum.RingerUiKey.Key - type: RingerBoundUserInterface - - key: enum.InstrumentUiKey.Key - type: InstrumentBoundUserInterface - - key: enum.HealthAnalyzerUiKey.Key - type: HealthAnalyzerBoundUserInterface + enum.PdaUiKey.Key: + type: PdaBoundUserInterface + enum.StoreUiKey.Key: + type: StoreBoundUserInterface + enum.RingerUiKey.Key: + type: RingerBoundUserInterface + enum.InstrumentUiKey.Key: + type: InstrumentBoundUserInterface + enum.HealthAnalyzerUiKey.Key: + type: HealthAnalyzerBoundUserInterface - type: Tag tags: - DoorBumpOpener - type: Input context: "human" + - type: LanguageSpeaker + - type: LanguageKnowledge + speaks: [TauCetiBasic, RobotTalk] + understands: [TauCetiBasic, RobotTalk] - type: entity parent: BasePDA @@ -272,6 +276,9 @@ paralyzeTime: 4 launchForwardsMultiplier: 1.5 - type: StepTrigger + triggerGroups: + types: + - SlipEntity - type: CollisionWake enabled: false - type: Physics @@ -355,6 +362,33 @@ accentVColor: "#a23e3e" - type: Icon state: pda-qm + - type: CartridgeLoader # Adds the MailMetrics courier tracker + preinstalled: + - CrewManifestCartridge + - NotekeeperCartridge + - NewsReaderCartridge + - MailMetricsCartridge + +- type: entity + name: LO special PDA unit # DeltaV - Logistics Department replacing Cargo + parent: BasePDA + id: QuartermasterNTPDA + description: PDA for the guy that directs logistics.it says in gold "special unit unique to the best logistics officer in the sector". # DeltaV - Logistics Department replacing Cargo + components: + - type: Pda + id: QuartermasterIDCard + state: pda-qmnano + - type: PdaBorderColor + borderColor: "#e39751" + accentVColor: "#a23e3e" + - type: Icon + state: pda-qmnano + - type: CartridgeLoader # Adds the MailMetrics courier tracker + preinstalled: + - CrewManifestCartridge + - NotekeeperCartridge + - NewsReaderCartridge + - MailMetricsCartridge - type: entity parent: BasePDA @@ -418,6 +452,12 @@ borderColor: "#858585" - type: Icon state: pda-library + - type: CartridgeLoader + preinstalled: + - CrewManifestCartridge + - NotekeeperCartridge + - NewsReaderCartridge + - GlimmerMonitorCartridge - type: entity parent: BasePDA @@ -480,6 +520,26 @@ - type: Icon state: pda-captain +- type: entity + parent: BasePDA + id: CaptainNTPDA + name: CAP special PDA unit + description: incredibly shining with embossed details.it says in gold "special unit unique to the best captain in the sector". + components: + - type: Pda + id: CaptainIDCard + state: pda-captainnano + penSlot: + startingItem: PenCap + priority: -1 + whitelist: + tags: + - Write + - type: PdaBorderColor + borderColor: "#7C5D00" + - type: Icon + state: pda-captainnano + - type: entity parent: BasePDA id: HoPPDA @@ -501,6 +561,27 @@ - type: Icon state: pda-hop +- type: entity + parent: BasePDA + id: HoPNTPDA + name: HOP special PDA unit + description: Looks like it's been chewed on.it says in gold "special unit unique to the best head of personnel in the sector". + components: + - type: Pda + id: HoPIDCard + state: pda-hopnano + penSlot: + startingItem: PenHop + priority: -1 + whitelist: + tags: + - Write + - type: PdaBorderColor + borderColor: "#789876" + accentHColor: "#447987" + - type: Icon + state: pda-hopnano + - type: entity parent: BasePDA id: CEPDA @@ -516,6 +597,21 @@ - type: Icon state: pda-ce +- type: entity + parent: BasePDA + id: CENTPDA + name: CE special PDA unit + description: Looks like it's barely been used.it says in gold "special unit unique to the best chief engineer in the sector". + components: + - type: Pda + id: CEIDCard + state: pda-cenano + - type: PdaBorderColor + borderColor: "#949137" + accentHColor: "#447987" + - type: Icon + state: pda-cenano + - type: entity parent: BasePDA id: EngineerPDA @@ -553,6 +649,28 @@ - type: Icon state: pda-cmo +- type: entity + parent: BaseMedicalPDA + id: CMONTPDA + name: CMO special pda unit + description: Extraordinarily shiny and sterile.it says in gold "special unit unique to the best chief medical officer in the sector". Has a built-in health analyzer. + components: + - type: Pda + id: CMOIDCard + state: pda-cmonano + penSlot: # Fancy Pen Light + startingItem: CMOPenLight + priority: -1 + whitelist: + tags: + - Write + - type: PdaBorderColor + borderColor: "#d7d7d0" + accentHColor: "#447987" + accentVColor: "#447987" + - type: Icon + state: pda-cmonano + - type: entity parent: BaseMedicalPDA id: MedicalPDA @@ -641,6 +759,28 @@ - NewsReaderCartridge - GlimmerMonitorCartridge +- type: entity + parent: BasePDA + id: RnDNTPDA + name: MY special PDA unit # DeltaV - Epistemics Department replacing Science + description: It appears surprisingly ordinary.it says in gold "special unit unique to the best Mystagogue in the sector". + components: + - type: Pda + id: RDIDCard + state: pda-rdnano + - type: PdaBorderColor + borderColor: "#d7d7d0" + accentHColor: "#447987" + accentVColor: "#8900c9" + - type: Icon + state: pda-rdnano + - type: CartridgeLoader # Nyanotrasen - Glimmer Monitor + preinstalled: + - CrewManifestCartridge + - NotekeeperCartridge + - NewsReaderCartridge + - GlimmerMonitorCartridge + - type: entity parent: BasePDA id: SciencePDA @@ -662,6 +802,21 @@ - NewsReaderCartridge - GlimmerMonitorCartridge +- type: entity + parent: SciencePDA + name: robotics PDA + id: RoboticsPDA + description: It has its back panel covered in duct tape. + components: + - type: Pda + id: RoboticsIDCard + state: pda-robotics + - type: PdaBorderColor + borderColor: "#d7d7d0" + accentVColor: "#952004" + - type: Icon + state: pda-robotics + - type: entity parent: BasePDA id: HoSPDA @@ -684,6 +839,28 @@ - CrimeAssistCartridge - SecWatchCartridge +- type: entity + parent: BasePDA + id: HoSNTPDA + name: HOS special PDA unit + description: Whosoever bears this PDA is the law.it says in gold "special unit unique to the best head of security in the sector". + components: + - type: Pda + id: HoSIDCard + state: pda-hosnano + - type: PdaBorderColor + borderColor: "#A32D26" + accentHColor: "#447987" + - type: Icon + state: pda-hosnano + - type: CartridgeLoader # DeltaV - Crime Assist + SecWatch + preinstalled: + - CrewManifestCartridge + - NotekeeperCartridge + - NewsReaderCartridge + - CrimeAssistCartridge + - SecWatchCartridge + - type: entity parent: BasePDA id: WardenPDA @@ -853,6 +1030,8 @@ whitelist: components: - Cartridge + - type: ClothingAddFaction + faction: Syndicate - type: entity parent: BasePDA @@ -1199,3 +1378,5 @@ whitelist: components: - Cartridge + - type: ClothingAddFaction + faction: Syndicate diff --git a/Resources/Prototypes/Entities/Objects/Devices/pinpointer.yml b/Resources/Prototypes/Entities/Objects/Devices/pinpointer.yml index f5e46bbf54..4d36af4459 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/pinpointer.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/pinpointer.yml @@ -57,6 +57,11 @@ - type: Sprite layers: - state: pinpointer-syndicate + map: ["enum.PinpointerLayers.Base"] + - state: pinonnull + map: ["enum.PinpointerLayers.Screen"] + shader: unshaded + visible: false - type: Icon state: pinpointer-syndicate - type: Pinpointer @@ -72,6 +77,11 @@ - type: Sprite layers: - state: pinpointer-way + map: ["enum.PinpointerLayers.Base"] + - state: pinonnull + map: ["enum.PinpointerLayers.Screen"] + shader: unshaded + visible: false - type: Icon state: pinpointer-way - type: Pinpointer @@ -88,8 +98,13 @@ - type: Sprite layers: - state: pinpointer-station + map: ["enum.PinpointerLayers.Base"] + - state: pinonnull + map: ["enum.PinpointerLayers.Screen"] + shader: unshaded + visible: false - type: Icon state: pinpointer-station - type: Pinpointer - component: BecomesStation + component: ResearchServer targetName: the station diff --git a/Resources/Prototypes/Entities/Objects/Devices/radio.yml b/Resources/Prototypes/Entities/Objects/Devices/radio.yml index 74c2865d07..43f84fe404 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/radio.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/radio.yml @@ -23,3 +23,20 @@ - type: Tag tags: - Radio + +- type: entity + name: security radio + description: A handy security radio. + parent: RadioHandheld + id: RadioHandheldSecurity + components: + - type: RadioMicrophone + broadcastChannel: Security + - type: RadioSpeaker + channels: + - Security + - type: Sprite + sprite: Objects/Devices/securityhandy.rsi + - type: Item + sprite: Objects/Devices/securityhandy.rsi + heldPrefix: walkietalkie \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Objects/Devices/station_beacon.yml b/Resources/Prototypes/Entities/Objects/Devices/station_beacon.yml index 9afef7abc7..28d6ddea69 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/station_beacon.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/station_beacon.yml @@ -30,8 +30,8 @@ singleUser: true - type: UserInterface interfaces: - - key: enum.NavMapBeaconUiKey.Key - type: NavMapBeaconBoundUserInterface + enum.NavMapBeaconUiKey.Key: + type: NavMapBeaconBoundUserInterface - type: Item size: Small - type: SubFloorHide diff --git a/Resources/Prototypes/Entities/Objects/Devices/station_map.yml b/Resources/Prototypes/Entities/Objects/Devices/station_map.yml index c37d8935f3..0d2f890a1d 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/station_map.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/station_map.yml @@ -28,7 +28,7 @@ acts: [ "Destruction" ] - type: UserInterface interfaces: - - key: enum.StationMapUiKey.Key + enum.StationMapUiKey.Key: type: StationMapBoundUserInterface - type: entity diff --git a/Resources/Prototypes/Entities/Objects/Devices/swapper.yml b/Resources/Prototypes/Entities/Objects/Devices/swapper.yml index 8a743f4796..3ab014b28c 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/swapper.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/swapper.yml @@ -25,3 +25,7 @@ - type: Tag tags: - QuantumSpinInverter + - type: ReverseEngineering # Delta + difficulty: 4 + recipes: + - DeviceQuantumSpinInverter diff --git a/Resources/Prototypes/Entities/Objects/Devices/translator_implants.yml b/Resources/Prototypes/Entities/Objects/Devices/translator_implants.yml index da42b2774b..0e7fce9568 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/translator_implants.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/translator_implants.yml @@ -1,26 +1,26 @@ - type: entity parent: BaseSubdermalImplant - id: BasicGalacticCommonTranslatorImplant + id: BasicTauCetiBasicTranslatorImplant name: basic common translator implant description: Provides your illiterate friends the ability to understand the common galactic tongue. noSpawn: true components: - type: TranslatorImplant understood: - - GalacticCommon + - TauCetiBasic - type: entity parent: BaseSubdermalImplant - id: GalacticCommonTranslatorImplant + id: TauCetiBasicTranslatorImplant name: advanced common translator implant description: A more advanced version of the translator implant, teaches your illiterate friends the ability to both speak and understand the galactic tongue! noSpawn: true components: - type: TranslatorImplant understood: - - GalacticCommon + - TauCetiBasic spoken: - - GalacticCommon + - TauCetiBasic - type: entity parent: BaseSubdermalImplant @@ -35,7 +35,7 @@ spoken: - Bubblish requires: - - GalacticCommon + - TauCetiBasic - type: entity parent: BaseSubdermalImplant @@ -50,7 +50,7 @@ spoken: - Nekomimetic requires: - - GalacticCommon + - TauCetiBasic - type: entity parent: BaseSubdermalImplant @@ -65,7 +65,7 @@ spoken: - Draconic requires: - - GalacticCommon + - TauCetiBasic - type: entity parent: BaseSubdermalImplant @@ -80,7 +80,7 @@ spoken: - Canilunzt requires: - - GalacticCommon + - TauCetiBasic - type: entity parent: BaseSubdermalImplant @@ -95,7 +95,7 @@ spoken: - SolCommon requires: - - GalacticCommon + - TauCetiBasic - type: entity parent: BaseSubdermalImplant @@ -110,7 +110,7 @@ spoken: - RootSpeak requires: - - GalacticCommon + - TauCetiBasic - type: entity parent: BaseSubdermalImplant @@ -125,4 +125,34 @@ spoken: - Moffic requires: - - GalacticCommon + - TauCetiBasic + +- type: entity + parent: BaseSubdermalImplant + id: ValyrianStandardTranslatorImplant + name: valyrian standard translator implant + description: An implant giving the ability to understand and speak Valyrian Standard. Chirp! + noSpawn: true + components: + - type: TranslatorImplant + understood: + - ValyrianStandard + spoken: + - ValyrianStandard + requires: + - TauCetiBasic + +- type: entity + parent: BaseSubdermalImplant + id: AzazibaTranslatorImplant + name: azaziba translator implant + description: An implant giving the ability to understand and speak Azaziba. # Intended for Admins Only, this item is for lore reasons not obtainable. + noSpawn: true + components: + - type: TranslatorImplant + understood: + - Azaziba + spoken: + - Azaziba + requires: + - Draconic diff --git a/Resources/Prototypes/Entities/Objects/Devices/translators.yml b/Resources/Prototypes/Entities/Objects/Devices/translators.yml index 6aa7947c82..99b6d4305b 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/translators.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/translators.yml @@ -67,13 +67,13 @@ components: - type: HandheldTranslator spoken: - - GalacticCommon + - TauCetiBasic - Canilunzt understood: - - GalacticCommon + - TauCetiBasic - Canilunzt requires: - - GalacticCommon + - TauCetiBasic - Canilunzt - type: entity @@ -84,13 +84,13 @@ components: - type: HandheldTranslator spoken: - - GalacticCommon + - TauCetiBasic - Bubblish understood: - - GalacticCommon + - TauCetiBasic - Bubblish requires: - - GalacticCommon + - TauCetiBasic - Bubblish - type: entity @@ -101,30 +101,30 @@ components: - type: HandheldTranslator spoken: - - GalacticCommon + - TauCetiBasic - Nekomimetic understood: - - GalacticCommon + - TauCetiBasic - Nekomimetic requires: - - GalacticCommon + - TauCetiBasic - Nekomimetic - type: entity id: DraconicTranslator parent: [ TranslatorPoweredBase ] - name: Draconic translator - description: Translates speech between Draconic and Galactic Common, making it easier to understand your local Uniathi. + name: Sinta'Unathi translator + description: Translates speech between Sinta'Unathi and Tau-Ceti Basic, making it easier to understand your local Unathi. components: - type: HandheldTranslator spoken: - - GalacticCommon + - TauCetiBasic - Draconic understood: - - GalacticCommon + - TauCetiBasic - Draconic requires: - - GalacticCommon + - TauCetiBasic - Draconic - type: entity @@ -135,13 +135,13 @@ components: - type: HandheldTranslator spoken: - - GalacticCommon + - TauCetiBasic - SolCommon understood: - - GalacticCommon + - TauCetiBasic - SolCommon requires: - - GalacticCommon + - TauCetiBasic - SolCommon - type: entity @@ -152,13 +152,13 @@ components: - type: HandheldTranslator spoken: - - GalacticCommon + - TauCetiBasic - RootSpeak understood: - - GalacticCommon + - TauCetiBasic - RootSpeak requires: - - GalacticCommon + - TauCetiBasic - RootSpeak - type: entity @@ -169,13 +169,13 @@ components: - type: HandheldTranslator spoken: - - GalacticCommon + - TauCetiBasic - Moffic understood: - - GalacticCommon + - TauCetiBasic - Moffic requires: - - GalacticCommon + - TauCetiBasic - Moffic - type: entity @@ -186,13 +186,13 @@ components: - type: HandheldTranslator spoken: - - GalacticCommon + - TauCetiBasic - Xeno understood: - - GalacticCommon + - TauCetiBasic - Xeno requires: - - GalacticCommon + - TauCetiBasic - type: entity id: AnimalTranslator @@ -217,5 +217,39 @@ - Kobold - Hissing requires: - - GalacticCommon + - TauCetiBasic setLanguageOnInteract: false + +- type: entity + id: ValyrianStandardTranslator + parent: [ TranslatorPoweredBase ] + name: Valyrian Standard translator + description: Translates speech between Valyrian Standard and Tau-Ceti Basic. For talking to Harpies! + components: + - type: HandheldTranslator + spoken: + - TauCetiBasic + - ValyrianStandard + understood: + - TauCetiBasic + - ValyrianStandard + requires: + - TauCetiBasic + - ValyrianStandard + +- type: entity + id: AzazibaTranslator + parent: [ TranslatorPoweredBase ] + name: Azaziba translator + description: Translates speech between Unathi and Azaziba. For Unathi to speak the Archaic form of their native tongue! # Intended for Admins Only, this item is for lore reasons not obtainable. + components: + - type: HandheldTranslator + spoken: + - Draconic + - Azaziba + understood: + - Draconic + - Azaziba + requires: + - Draconic + - Azaziba \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Objects/Fun/Instruments/base_instruments.yml b/Resources/Prototypes/Entities/Objects/Fun/Instruments/base_instruments.yml index 09b41d6e5d..614af2a488 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/Instruments/base_instruments.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/Instruments/base_instruments.yml @@ -13,8 +13,8 @@ key: enum.InstrumentUiKey.Key - type: UserInterface interfaces: - - key: enum.InstrumentUiKey.Key - type: InstrumentBoundUserInterface + enum.InstrumentUiKey.Key: + type: InstrumentBoundUserInterface - type: Item size: Normal - type: StaticPrice @@ -29,7 +29,7 @@ components: - type: Instrument - type: ActivatableUI - allowSpectator: false # otherwise they can play client-side music + blockSpectators: true # otherwise they can play client-side music inHandsOnly: false singleUser: true requireHands: true @@ -51,8 +51,8 @@ acts: ["Destruction"] - type: UserInterface interfaces: - - key: enum.InstrumentUiKey.Key - type: InstrumentBoundUserInterface + enum.InstrumentUiKey.Key: + type: InstrumentBoundUserInterface - type: Fixtures fixtures: fix1: diff --git a/Resources/Prototypes/Entities/Objects/Fun/Instruments/instruments_string.yml b/Resources/Prototypes/Entities/Objects/Fun/Instruments/instruments_string.yml index 947a973bbf..730d532930 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/Instruments/instruments_string.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/Instruments/instruments_string.yml @@ -25,7 +25,7 @@ bluntStaminaDamageFactor: 1.5 heavyRateModifier: 0.75 heavyDamageBaseModifier: 1.2 - heavyStaminaCost: 10 + heavyStaminaCost: 7.5 angle: 75 - type: Item size: Normal @@ -67,7 +67,7 @@ bluntStaminaDamageFactor: 1.5 heavyRateModifier: 0.75 heavyDamageBaseModifier: 1.2 - heavyStaminaCost: 10 + heavyStaminaCost: 7.5 angle: 75 - type: Item size: Normal @@ -110,7 +110,7 @@ bluntStaminaDamageFactor: 1.5 heavyRateModifier: 0.75 heavyDamageBaseModifier: 1.2 - heavyStaminaCost: 15 + heavyStaminaCost: 10 angle: 160 - type: Wieldable - type: IncreaseDamageOnWield @@ -234,7 +234,7 @@ bluntStaminaDamageFactor: 2 heavyRateModifier: 0.75 heavyDamageBaseModifier: 1.2 - heavyStaminaCost: 10 + heavyStaminaCost: 7.5 angle: 75 - type: entity diff --git a/Resources/Prototypes/Entities/Objects/Fun/Instruments/instruments_wind.yml b/Resources/Prototypes/Entities/Objects/Fun/Instruments/instruments_wind.yml index e99f825d48..e37fbaa361 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/Instruments/instruments_wind.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/Instruments/instruments_wind.yml @@ -70,7 +70,7 @@ slots: - neck - type: ActivatableUI - inHandsOnly: false + inHandsOnly: false - type: Tag tags: - WoodwindInstrument diff --git a/Resources/Prototypes/Entities/Objects/Fun/candy_bucket.yml b/Resources/Prototypes/Entities/Objects/Fun/candy_bucket.yml index d8f11cdea2..63175dc89a 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/candy_bucket.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/candy_bucket.yml @@ -36,8 +36,8 @@ ents: [] - type: UserInterface interfaces: - - key: enum.StorageUiKey.Key - type: StorageBoundUserInterface + enum.StorageUiKey.Key: + type: StorageBoundUserInterface # to prevent bag open/honk spam - type: UseDelay delay: 0.5 diff --git a/Resources/Prototypes/Entities/Objects/Fun/crayons.yml b/Resources/Prototypes/Entities/Objects/Fun/crayons.yml index cb755b161a..4aab2efdb9 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/crayons.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/crayons.yml @@ -18,8 +18,8 @@ - type: SpaceGarbage - type: UserInterface interfaces: - - key: enum.CrayonUiKey.Key - type: CrayonBoundUserInterface + enum.CrayonUiKey.Key: + type: CrayonBoundUserInterface - type: Crayon capacity: 15 - type: Food diff --git a/Resources/Prototypes/Entities/Objects/Fun/dice.yml b/Resources/Prototypes/Entities/Objects/Fun/dice.yml index 852a1c2699..bca5ec7ab9 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/dice.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/dice.yml @@ -119,9 +119,12 @@ mask: - ItemMask - type: StepTrigger + triggerGroups: + types: + - Shard intersectRatio: 0.2 - type: TriggerOnStepTrigger - - type: ShoesRequiredStepTrigger + - type: ClothingRequiredStepTrigger - type: Slippery slipSound: path: /Audio/Effects/glass_step.ogg diff --git a/Resources/Prototypes/Entities/Objects/Fun/immovable_rod.yml b/Resources/Prototypes/Entities/Objects/Fun/immovable_rod.yml index 7ff6c43e29..b5cebbdd09 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/immovable_rod.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/immovable_rod.yml @@ -35,6 +35,7 @@ - type: entity id: ImmovableRodDespawn + suffix: Despawn parent: ImmovableRod components: - type: TimedDespawn @@ -92,3 +93,6 @@ components: - type: ImmovableRod randomizeVelocity: false + damage: + types: + Blunt: 120 diff --git a/Resources/Prototypes/Entities/Objects/Fun/pai.yml b/Resources/Prototypes/Entities/Objects/Fun/pai.yml index 537562f461..b73767fd81 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/pai.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/pai.yml @@ -13,10 +13,12 @@ program: 2 - type: UserInterface interfaces: - - key: enum.InstrumentUiKey.Key - type: InstrumentBoundUserInterface - - key: enum.StationMapUiKey.Key - type: UntrackedStationMapBoundUserInterface + enum.InstrumentUiKey.Key: + type: InstrumentBoundUserInterface + requireInputValidation: false + enum.StationMapUiKey.Key: + type: UntrackedStationMapBoundUserInterface + requireInputValidation: false - type: Sprite sprite: Objects/Fun/pai.rsi layers: @@ -70,6 +72,22 @@ Searching: { state: pai-searching-overlay } On: { state: pai-on-overlay } - type: StationMap + - type: LanguageKnowledge + speaks: + - TauCetiBasic + - SolCommon + - Tradeband + - Freespeak + - Elyran + - RobotTalk + understands: + - TauCetiBasic + - SolCommon + - Tradeband + - Freespeak + - Elyran + - RobotTalk + - Sign # It's intentional that they don't "Speak" sign language. - type: entity parent: PersonalAI diff --git a/Resources/Prototypes/Entities/Objects/Fun/prizeticket.yml b/Resources/Prototypes/Entities/Objects/Fun/prizeticket.yml index 695e45a2e6..19aa6a3015 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/prizeticket.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/prizeticket.yml @@ -282,7 +282,10 @@ - id: CrayonBox prob: 0.80 orGroup: Prize - - id: BasePetRock + - id: PetRock + prob: 0.80 + orGroup: Prize + - id: PlushieShadowkin prob: 0.80 orGroup: Prize # Uncommon @@ -392,4 +395,6 @@ - id: ThronglerToy prob: 0.30 orGroup: Prize - \ No newline at end of file + - id: BwoinkHammer + prob: 0.30 + orGroup: Prize diff --git a/Resources/Prototypes/Entities/Objects/Fun/snap_pops.yml b/Resources/Prototypes/Entities/Objects/Fun/snap_pops.yml index a39c2f881a..88f0758874 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/snap_pops.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/snap_pops.yml @@ -33,15 +33,12 @@ maxIntensity: 0.01 intensitySlope: 1 totalIntensity: 0.01 - - type: Tag - tags: - - SnapPop - type: entity parent: BaseStorageItem id: SnapPopBox name: snap pop box - description: Contains twenty snap pops for a few minutes of popping fun! + description: Contains snap pops for a few minutes of popping fun! components: - type: Item size: Normal @@ -56,6 +53,6 @@ - type: StorageFill contents: - id: SnapPop - amount: 20 + amount: 5 - type: Dumpable diff --git a/Resources/Prototypes/Entities/Objects/Fun/spray_paint.yml b/Resources/Prototypes/Entities/Objects/Fun/spray_paint.yml new file mode 100644 index 0000000000..1b417f6cde --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Fun/spray_paint.yml @@ -0,0 +1,292 @@ +# Base Paints +- type: entity + parent: BaseItem + id: PaintBase + name: spray paint + description: A tin of spray paint. + noSpawn: true + components: + - type: Appearance + - type: Sprite + sprite: Objects/Fun/spraycans.rsi + state: clown_cap + layers: + - state: clown_cap + map: ["enum.OpenableVisuals.Layer"] + - type: Paint + consumptionUnit: 10 + blacklist: + tags: + - NoPaint + - type: Item + sprite: Objects/Fun/spraycans.rsi + heldPrefix: spray + - type: SolutionContainerManager + solutions: + drink: + maxVol: 50 + reagents: + - ReagentId: SpaceGlue + Quantity: 50 + - type: TrashOnSolutionEmpty + solution: drink + - type: Sealable + - type: Openable + sound: + path: /Audio/Effects/pop_high.ogg + closeable: true + closeSound: + path: /Audio/Effects/pop_high.ogg + +# Paints + +# funnypaint +- type: entity + parent: PaintBase + id: FunnyPaint + name: funny paint + description: A tin of funny paint, manufactured by Honk! Co. + components: + - type: Paint + color: "#fa74df" + - type: Item + sprite: Objects/Fun/spraycans.rsi + heldPrefix: clown + - type: GenericVisualizer + visuals: + enum.OpenableVisuals.Opened: + enum.OpenableVisuals.Layer: + True: {state: "clown"} + False: {state: "clown_cap"} + +- type: entity + parent: PaintBase + id: FunnyPaintYellow + name: funny paint + description: A tin of funny paint, manufactured by Honk! Co. + components: + - type: Paint + color: "#d5e028" + - type: Item + sprite: Objects/Fun/spraycans.rsi + heldPrefix: clown + - type: Sprite + sprite: Objects/Fun/spraycans.rsi + state: clown2_cap + layers: + - state: clown2_cap + map: ["enum.OpenableVisuals.Layer"] + - type: GenericVisualizer + visuals: + enum.OpenableVisuals.Opened: + enum.OpenableVisuals.Layer: + True: {state: "clown2"} + False: {state: "clown2_cap"} + +#death paint +- type: entity + parent: PaintBase + id: DeathPaint + components: + - type: Paint + color: "#ff20c8" + - type: Item + sprite: Objects/Fun/spraycans.rsi + heldPrefix: spray + - type: Sprite + sprite: Objects/Fun/spraycans.rsi + state: death_cap + layers: + - state: death_cap + map: ["enum.OpenableVisuals.Layer"] + - type: GenericVisualizer + visuals: + enum.OpenableVisuals.Opened: + enum.OpenableVisuals.Layer: + True: {state: "death"} + False: {state: "death_cap"} + +- type: entity + parent: PaintBase + id: DeathPaintTwo + components: + - type: Paint + color: "#ff2020" + - type: Item + sprite: Objects/Fun/spraycans.rsi + heldPrefix: spray + - type: Sprite + sprite: Objects/Fun/spraycans.rsi + state: death2_cap + layers: + - state: death2_cap + map: ["enum.OpenableVisuals.Layer"] + - type: GenericVisualizer + visuals: + enum.OpenableVisuals.Opened: + enum.OpenableVisuals.Layer: + True: {state: "death2"} + False: {state: "death2_cap"} + +#Sprays + +#Blue +- type: entity + parent: PaintBase + id: SprayPaintBlue + suffix: Blue + components: + - type: Sprite + sprite: Objects/Fun/spraycans.rsi + layers: + - state: spray + map: ["Base"] + - state: spray_cap_colors + map: ["enum.OpenableVisuals.Layer"] + color: "#5890f7" + - type: Paint + color: "#5890f7" + - type: GenericVisualizer + visuals: + enum.OpenableVisuals.Opened: + enum.OpenableVisuals.Layer: + True: {state: "spray_colors" , color: "#5890f7"} + False: {state: "spray_cap_colors" , color: "#5890f7"} + +#Red +- type: entity + parent: PaintBase + id: SprayPaintRed + suffix: Red + components: + - type: Sprite + sprite: Objects/Fun/spraycans.rsi + layers: + - state: spray + map: ["Base"] + - state: spray_cap_colors + map: ["enum.OpenableVisuals.Layer"] + color: "#ff3b3b" + - type: Paint + color: "#ff3b3b" + - type: GenericVisualizer + visuals: + enum.OpenableVisuals.Opened: + enum.OpenableVisuals.Layer: + True: {state: "spray_colors" , color: "#ff3b3b"} + False: {state: "spray_cap_colors" , color: "#ff3b3b"} + +#Green +- type: entity + parent: PaintBase + id: SprayPaintGreen + suffix: Green + components: + - type: Sprite + sprite: Objects/Fun/spraycans.rsi + layers: + - state: spray + map: ["Base"] + - state: spray_cap_colors + map: ["enum.OpenableVisuals.Layer"] + color: "#73f170" + - type: Paint + color: "#73f170" + - type: GenericVisualizer + visuals: + enum.OpenableVisuals.Opened: + enum.OpenableVisuals.Layer: + True: {state: "spray_colors" , color: "#73f170"} + False: {state: "spray_cap_colors" , color: "#73f170"} + +#Black +- type: entity + parent: PaintBase + id: SprayPaintBlack + suffix: Black + components: + - type: Sprite + sprite: Objects/Fun/spraycans.rsi + layers: + - state: spray + map: ["Base"] + - state: spray_cap_colors + map: ["enum.OpenableVisuals.Layer"] + color: "#3a3a3a" + - type: Paint + color: "#3a3a3a" + - type: GenericVisualizer + visuals: + enum.OpenableVisuals.Opened: + enum.OpenableVisuals.Layer: + True: {state: "spray_colors" , color: "#3a3a3a"} + False: {state: "spray_cap_colors" , color: "#3a3a3a"} + +#Orange +- type: entity + parent: PaintBase + id: SprayPaintOrange + suffix: Orange + components: + - type: Sprite + sprite: Objects/Fun/spraycans.rsi + layers: + - state: spray + map: ["Base"] + - state: spray_cap_colors + map: ["enum.OpenableVisuals.Layer"] + color: "#f6a44b" + - type: Paint + color: "#f6a44b" + - type: GenericVisualizer + visuals: + enum.OpenableVisuals.Opened: + enum.OpenableVisuals.Layer: + True: {state: "spray_colors" , color: "#f6a44b"} + False: {state: "spray_cap_colors" , color: "#f6a44b"} + +#Purple +- type: entity + parent: PaintBase + id: SprayPaintPurple + suffix: Purple + components: + - type: Sprite + sprite: Objects/Fun/spraycans.rsi + layers: + - state: spray + map: ["Base"] + - state: spray_cap_colors + map: ["enum.OpenableVisuals.Layer"] + color: "#c063f5" + - type: Paint + color: "#c063f5" + - type: GenericVisualizer + visuals: + enum.OpenableVisuals.Opened: + enum.OpenableVisuals.Layer: + True: {state: "spray_colors" , color: "#c063f5"} + False: {state: "spray_cap_colors" , color: "#c063f5"} + +#White +- type: entity + parent: PaintBase + id: SprayPaintWhite + suffix: White + components: + - type: Sprite + sprite: Objects/Fun/spraycans.rsi + layers: + - state: spray + map: ["Base"] + - state: spray_cap_colors + map: ["enum.OpenableVisuals.Layer"] + color: "#f2f2f2" + - type: Paint + color: "#f2f2f2" + - type: GenericVisualizer + visuals: + enum.OpenableVisuals.Opened: + enum.OpenableVisuals.Layer: + True: {state: "spray_colors" , color: "#f2f2f2"} + False: {state: "spray_cap_colors" , color: "#f2f2f2"} diff --git a/Resources/Prototypes/Entities/Objects/Fun/toys.yml b/Resources/Prototypes/Entities/Objects/Fun/toys.yml index 1fea59a926..fc771414b4 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/toys.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/toys.yml @@ -630,12 +630,11 @@ state: icon - type: MeleeWeapon attackRate: 1.5 - range: 1.3 + range: 1.5 damage: types: Blunt: 0.1 heavyDamageBaseModifier: 2 - heavyStaminaCost: 5 maxTargets: 8 angle: 25 - type: Clothing @@ -1745,6 +1744,34 @@ types: Blunt: 0 +- type: entity + parent: ToyHammer + id: BwoinkHammer + name: bwoink hammer + description: A toy hammer which makes a wildly terrifying sound. It is missing it's old spirit of bangammer and is still unraisinable. + suffix: DO NOT MAP + components: + - type: Sprite + sprite: Objects/Fun/bwoink_hammer.rsi + state: icon + - type: Item + size: Small + sprite: Objects/Fun/bwoink_hammer.rsi + - type: MeleeWeapon + soundHit: + path: /Audio/Admin/adminhelp_old.ogg + params: + variation: 0.03 + volume: 3 + soundNoDamage: + path: /Audio/Admin/adminhelp_old.ogg + params: + variation: 0.03 + volume: 3 + damage: + types: + Blunt: 0 + - type: entity parent: BaseItem id: WhoopieCushion @@ -1927,4 +1954,14 @@ size: Ginormous sprite: Objects/Weapons/Melee/Throngler-in-hand.rsi - type: DisarmMalus - malus: 0 \ No newline at end of file + malus: 0 + +- type: entity + parent: BasePlushie + id: PlushieShadowkin + name: shadowkin plushie + description: A plushie of a Shadowkin. It's very soft. + components: + - type: Sprite + sprite: Objects/Fun/toys.rsi + state: shadowkin \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Objects/Fun/whistles.yml b/Resources/Prototypes/Entities/Objects/Fun/whistles.yml index 10c41efc54..667d5da11c 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/whistles.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/whistles.yml @@ -6,8 +6,12 @@ description: Someone forgot to turn off kettle? components: - type: Item + sprite: Objects/Fun/whistles.rsi size: Tiny + - type: Sprite + sprite: Objects/Fun/whistles.rsi - type: Clothing + sprite: Objects/Fun/whistles.rsi quickEquip: false slots: neck - type: UseDelay @@ -16,7 +20,14 @@ sound: collection: BaseWhistle - type: Whistle - distance: 5 + distance: 3 + +- type: entity + parent: BaseWhistle + id: Whistle + components: + - type: Sprite + state: whistle - type: entity parent: BaseWhistle @@ -24,10 +35,11 @@ description: Sound of it make you feel fear. components: - type: Sprite - sprite: Objects/Fun/whistle.rsi - state: securityWhistle - - type: Item - sprite: Objects/Fun/whistle.rsi + state: sec + - type: Clothing + equippedPrefix: sec + - type: Whistle + distance: 5 - type: entity parent: BaseWhistle @@ -36,13 +48,9 @@ description: A whistle used by Syndicate commanders to draw attention. Avanti! components: - type: Sprite - sprite: Clothing/Neck/Misc/whistles.rsi - state: icon + state: trench - type: Clothing - sprite: Clothing/Neck/Misc/whistles.rsi - quickEquip: False - slots: - - neck + equippedPrefix: trench - type: EmitSoundOnUse sound: collection: TrenchWhistle diff --git a/Resources/Prototypes/Entities/Objects/Magic/books.yml b/Resources/Prototypes/Entities/Objects/Magic/books.yml index 6202767ff1..7697e46b32 100644 --- a/Resources/Prototypes/Entities/Objects/Magic/books.yml +++ b/Resources/Prototypes/Entities/Objects/Magic/books.yml @@ -9,7 +9,7 @@ layers: - state: paper_blood - state: cover_strong - color: "#645a5a" + color: "#645a5a" - state: decor_wingette_flat color: "#4d0303" - state: icon_pentagramm @@ -25,13 +25,58 @@ - type: EmitSoundOnLand sound: /Audio/SimpleStation14/Items/Handling/book_drop.ogg +# For the Wizard Antag +# Do not add discounts or price inflation +- type: entity + id: WizardsGrimoire + name: wizards grimoire + suffix: Wizard + parent: BaseItem + components: + - type: Sprite + sprite: Objects/Misc/books.rsi + layers: + - state: paper_blood + - state: cover_strong + color: "#645a5a" + - state: decor_wingette_flat + color: "#4d0303" + - state: icon_pentagramm + color: "#f7e19f" + - type: UserInterface + interfaces: + enum.StoreUiKey.Key: + type: StoreBoundUserInterface + - type: ActivatableUI + key: enum.StoreUiKey.Key + - type: Store + refundAllowed: true + ownerOnly: true # get your own tome! + preset: StorePresetSpellbook + balance: + WizCoin: 10 # prices are balanced around this 10 point maximum and how strong the spells are + +# Not meant for wizard antag but meant for spawning, so people can't abuse refund if they were given a tome +- type: entity + id: WizardsGrimoireNoRefund + name: wizards grimoire + suffix: Wizard, No Refund + parent: WizardsGrimoire + components: + - type: Store + refundAllowed: false + ownerOnly: true # get your own tome! + preset: StorePresetSpellbook + balance: + WizCoin: 10 # prices are balanced around this 10 point maximum and how strong the spells are + - type: entity id: SpawnSpellbook name: spawn spellbook parent: BaseSpellbook components: - type: Spellbook - spells: + spellActions: ActionSpawnMagicarpSpell: -1 - type: entity @@ -54,7 +99,7 @@ - state: detail_rivets color: gold - type: Spellbook - spells: + spellActions: ActionForceWall: -1 - type: entity @@ -75,7 +120,7 @@ - state: detail_rivets color: gold - type: Spellbook - spells: + spellActions: ActionBlink: -1 - type: entity @@ -98,7 +143,7 @@ color: red - state: overlay_blood - type: Spellbook - spells: + spellActions: ActionSmite: -1 - type: entity @@ -120,7 +165,7 @@ - state: detail_bookmark color: "#98c495" - type: Spellbook - spells: + spellActions: ActionKnock: -1 - type: entity @@ -144,7 +189,7 @@ - state: icon_magic_fireball shader: unshaded - type: Spellbook - spells: + spellActions: ActionFireball: -1 - type: entity @@ -159,7 +204,7 @@ layers: - state: spell_default - type: Spellbook - spells: + spellActions: ActionFlashRune: -1 ActionExplosionRune: -1 ActionIgniteRune: -1 diff --git a/Resources/Prototypes/Entities/Objects/Materials/Sheets/glass.yml b/Resources/Prototypes/Entities/Objects/Materials/Sheets/glass.yml index 89613efe14..91688ed799 100644 --- a/Resources/Prototypes/Entities/Objects/Materials/Sheets/glass.yml +++ b/Resources/Prototypes/Entities/Objects/Materials/Sheets/glass.yml @@ -16,6 +16,7 @@ - type: Tag tags: - Sheet + - NoPaint - type: Material - type: Damageable damageContainer: Inorganic @@ -49,6 +50,12 @@ solutions: glass: canReact: false + # Estacao Pirata - IPC Healing + - type: BlindHealing + damageContainers: + - Silicon + - type: StackPrice + price: 2 - type: entity parent: SheetGlassBase @@ -86,6 +93,22 @@ reagents: - ReagentId: Silicon Quantity: 10 + - type: UserInterface + interfaces: + enum.RadialSelectorUiKey.Key: + type: RadialSelectorMenuBUI + - type: ActivatableUI + key: enum.RadialSelectorUiKey.Key + inHandsOnly: true + requireActiveHand: false + - type: ShortConstruction + entries: + - prototype: Window + - prototype: WindowDirectional + - prototype: WindowDiagonal + - prototype: SheetRGlass + - prototype: SheetPGlass + - prototype: SheetUGlass - type: entity parent: SheetGlass @@ -172,6 +195,21 @@ max: 1 - !type:DoActsBehavior acts: [ "Destruction" ] + - type: UserInterface + interfaces: + enum.RadialSelectorUiKey.Key: + type: RadialSelectorMenuBUI + - type: ActivatableUI + key: enum.RadialSelectorUiKey.Key + inHandsOnly: true + requireActiveHand: false + - type: ShortConstruction + entries: + - prototype: SheetRPGlass1 + - prototype: SheetRUGlass1 + - prototype: ReinforcedWindow + - prototype: ReinforcedWindowDiagonal + - prototype: WindowReinforcedDirectional - type: entity parent: SheetRGlass @@ -249,6 +287,20 @@ max: 1 - !type:DoActsBehavior acts: [ "Destruction" ] + - type: UserInterface + interfaces: + enum.RadialSelectorUiKey.Key: + type: RadialSelectorMenuBUI + - type: ActivatableUI + key: enum.RadialSelectorUiKey.Key + inHandsOnly: true + requireActiveHand: false + - type: ShortConstruction + entries: + - prototype: SheetRPGlass0 + - prototype: PlasmaWindow + - prototype: PlasmaWindowDiagonal + - prototype: PlasmaWindowDirectional - type: entity parent: SheetPGlass @@ -315,6 +367,19 @@ - ReagentId: Carbon Quantity: 0.5 canReact: false + - type: UserInterface + interfaces: + enum.RadialSelectorUiKey.Key: + type: RadialSelectorMenuBUI + - type: ActivatableUI + key: enum.RadialSelectorUiKey.Key + inHandsOnly: true + requireActiveHand: false + - type: ShortConstruction + entries: + - prototype: ReinforcedPlasmaWindow + - prototype: ReinforcedPlasmaWindowDiagonal + - prototype: PlasmaReinforcedWindowDirectional - type: entity parent: SheetRPGlass @@ -390,6 +455,20 @@ - ReagentId: Uranium Quantity: 10 canReact: false + - type: UserInterface + interfaces: + enum.RadialSelectorUiKey.Key: + type: RadialSelectorMenuBUI + - type: ActivatableUI + key: enum.RadialSelectorUiKey.Key + inHandsOnly: true + requireActiveHand: false + - type: ShortConstruction + entries: + - prototype: SheetRUGlass0 + - prototype: UraniumWindow + - prototype: UraniumWindowDiagonal + - prototype: UraniumWindowDirectional - type: entity parent: SheetUGlass @@ -444,6 +523,19 @@ - ReagentId: Carbon Quantity: 0.5 canReact: false + - type: UserInterface + interfaces: + enum.RadialSelectorUiKey.Key: + type: RadialSelectorMenuBUI + - type: ActivatableUI + key: enum.RadialSelectorUiKey.Key + inHandsOnly: true + requireActiveHand: false + - type: ShortConstruction + entries: + - prototype: ReinforcedUraniumWindow + - prototype: ReinforcedUraniumWindowDiagonal + - prototype: UraniumReinforcedWindowDirectional - type: entity parent: SheetRUGlass diff --git a/Resources/Prototypes/Entities/Objects/Materials/Sheets/metal.yml b/Resources/Prototypes/Entities/Objects/Materials/Sheets/metal.yml index 8cd965676c..d82061a738 100644 --- a/Resources/Prototypes/Entities/Objects/Materials/Sheets/metal.yml +++ b/Resources/Prototypes/Entities/Objects/Materials/Sheets/metal.yml @@ -16,6 +16,7 @@ tags: - Sheet - Metal + - NoPaint - type: Damageable damageContainer: Inorganic damageModifierSet: Metallic @@ -70,6 +71,84 @@ Quantity: 9 - ReagentId: Carbon Quantity: 1 + - type: UserInterface + interfaces: + enum.RadialSelectorUiKey.Key: + type: RadialSelectorMenuBUI + - type: ActivatableUI + key: enum.RadialSelectorUiKey.Key + inHandsOnly: true + requireActiveHand: false + - type: ShortConstruction + entries: + - prototype: Girder + - prototype: MetalRod + - category: + name: Tiles + icon: + sprite: Objects/Tiles/tile.rsi + state: steel + entries: + - prototype: TileSteel + - prototype: TileWhite + - prototype: TileDark + - category: + name: Electronics + icon: + sprite: Structures/Machines/computers.rsi + state: computer-datatheory + entries: + - prototype: MachineFrame + - prototype: Computer + - prototype: Windoor + - prototype: LightTubeFixture + - prototype: APC + - prototype: AirAlarmFixture + - prototype: AirSensor + - prototype: FireAlarm + - category: + name: Atmospherics + icon: + sprite: Structures/Piping/Atmospherics/vent.rsi + state: vent_out + entries: + - prototype: GasPort + - prototype: GasOutletInjector + - prototype: GasVentScrubber + - prototype: GasPassiveVent + - prototype: GasVentPump + - prototype: GasMixer + - prototype: GasFilter + - prototype: GasVolumePump + - prototype: GasPressurePump + - prototype: GasValve + - category: + name: Piping + icon: + sprite: Structures/Piping/Atmospherics/pipe.rsi + state: pipeFourway + entries: + - prototype: HeatExchanger + - prototype: GasPipeBend + - prototype: GasPipeFourway + - prototype: GasPipeHalf + - prototype: GasPipeStraight + - prototype: GasPipeTJunction + - category: + name: Disposals + icon: + sprite: Structures/Piping/disposal.rsi + state: disposal + entries: + - prototype: DisposalPipe + - prototype: DisposalBend + - prototype: DisposalJunction + - prototype: DisposalYJunction + - prototype: DisposalRouter + - prototype: DisposalTagger + - prototype: DisposalTrunk + - prototype: DisposalUnit + - prototype: CrateGenericSteel - type: entity parent: SheetSteel @@ -206,6 +285,17 @@ - ReagentId: Carbon Quantity: 1 canReact: false + - type: UserInterface + interfaces: + enum.RadialSelectorUiKey.Key: + type: RadialSelectorMenuBUI + - type: ActivatableUI + key: enum.RadialSelectorUiKey.Key + inHandsOnly: true + requireActiveHand: false + - type: ShortConstruction + entries: + - prototype: SecureWindoor - type: entity parent: SheetPlasteel diff --git a/Resources/Prototypes/Entities/Objects/Materials/Sheets/other.yml b/Resources/Prototypes/Entities/Objects/Materials/Sheets/other.yml index 8c9c2c4b3b..0dca313010 100644 --- a/Resources/Prototypes/Entities/Objects/Materials/Sheets/other.yml +++ b/Resources/Prototypes/Entities/Objects/Materials/Sheets/other.yml @@ -13,6 +13,7 @@ - type: Tag tags: - Sheet + - NoPaint - type: Damageable damageContainer: Inorganic - type: Destructible @@ -111,6 +112,20 @@ - type: Tag tags: - Sheet + - NoPaint + - type: UserInterface + interfaces: + enum.RadialSelectorUiKey.Key: + type: RadialSelectorMenuBUI + - type: ActivatableUI + key: enum.RadialSelectorUiKey.Key + inHandsOnly: true + requireActiveHand: false + - type: ShortConstruction + entries: + - prototype: SheetPGlass + - prototype: SheetRPGlass + - prototype: SheetRPGlass1 - type: entity parent: SheetPlasma @@ -133,6 +148,7 @@ tags: - Plastic - Sheet + - NoPaint - type: Material - type: PhysicalComposition materialComposition: @@ -163,6 +179,17 @@ - ReagentId: Phosphorus Quantity: 5 canReact: false + - type: UserInterface + interfaces: + enum.RadialSelectorUiKey.Key: + type: RadialSelectorMenuBUI + - type: ActivatableUI + key: enum.RadialSelectorUiKey.Key + inHandsOnly: true + requireActiveHand: false + - type: ShortConstruction + entries: + - prototype: CratePlastic - type: entity parent: SheetPlastic @@ -225,6 +252,19 @@ - ReagentId: Radium Quantity: 2 canReact: false + - type: UserInterface + interfaces: + enum.RadialSelectorUiKey.Key: + type: RadialSelectorMenuBUI + - type: ActivatableUI + key: enum.RadialSelectorUiKey.Key + inHandsOnly: true + requireActiveHand: false + - type: ShortConstruction + entries: + - prototype: SheetUGlass + - prototype: SheetRUGlass + - prototype: SheetRUGlass1 - type: entity parent: SheetUranium diff --git a/Resources/Prototypes/Entities/Objects/Materials/bluespace.yml b/Resources/Prototypes/Entities/Objects/Materials/bluespace.yml index d6a3b44479..c9509d31d5 100644 --- a/Resources/Prototypes/Entities/Objects/Materials/bluespace.yml +++ b/Resources/Prototypes/Entities/Objects/Materials/bluespace.yml @@ -5,7 +5,7 @@ name: bluespace crystal components: - type: Sprite - sprite: Nyanotrasen/Objects/Materials/materials.rsi + sprite: Objects/Materials/materials.rsi scale: 1, 1 layers: - state: bluespace_3 @@ -15,6 +15,10 @@ - type: PhysicalComposition materialComposition: Bluespace: 100 + - type: EmitSoundOnUse + sound: + collection: RadiationPulse + - type: EtherealStunItem - type: Tag tags: - BluespaceCrystal @@ -55,3 +59,60 @@ components: - type: Stack count: 5 + +- type: entity + parent: MaterialBase + id: MaterialNormality + suffix: Full + name: normality crystal + components: + - type: Sprite + sprite: Objects/Materials/materials.rsi + layers: + - state: normality_3 + map: ["base"] + - type: Appearance + - type: Material + - type: PhysicalComposition + materialComposition: + Normality: 100 + - type: Tag + tags: + - NormalityCrystal + - RawMaterial + - type: Stack + stackType: Normality + baseLayer: base + layerStates: + - normality + - normality_2 + - normality_3 + count: 5 + - type: Item + size: Small + +- type: entity + parent: MaterialNormality + id: MaterialNormality1 + suffix: 1 + components: + - type: Sprite + state: normality + - type: Stack + count: 1 + +- type: entity + parent: MaterialNormality1 + id: MaterialNormality3 + suffix: 3 + components: + - type: Stack + count: 3 + +- type: entity + parent: MaterialNormality1 + id: MaterialNormality5 + suffix: 5 + components: + - type: Stack + count: 5 diff --git a/Resources/Prototypes/Entities/Objects/Materials/materials.yml b/Resources/Prototypes/Entities/Objects/Materials/materials.yml index d30702e3d7..412811b15c 100644 --- a/Resources/Prototypes/Entities/Objects/Materials/materials.yml +++ b/Resources/Prototypes/Entities/Objects/Materials/materials.yml @@ -13,6 +13,7 @@ - type: Tag tags: - RawMaterial + - NoPaint - type: Damageable damageContainer: Inorganic - type: Destructible @@ -422,6 +423,58 @@ - type: Stack count: 1 +- type: entity + parent: MaterialBase + id: MaterialPyrotton + name: pyrotton + suffix: Full + components: + - type: Stack + stackType: Pyrotton + baseLayer: base + layerStates: + - pyrotton + - pyrotton_2 + - pyrotton_3 + - type: Sprite + state: pyrotton_3 + layers: + - state: pyrotton_3 + map: ["base"] + - type: Appearance + - type: Food + requiresSpecialDigestion: true + - type: SolutionContainerManager + solutions: + food: + maxVol: 10 + reagents: + - ReagentId: Fiber + Quantity: 5 + - ReagentId: Phlogiston + Quantity: 5 + - type: Extractable + juiceSolution: + reagents: + - ReagentId: Fiber + Quantity: 3 + - ReagentId: Phlogiston + Quantity: 3 + - type: Tag + tags: + - ClothMade + - RawMaterial + +- type: entity + parent: MaterialPyrotton + id: MaterialPyrotton1 + suffix: Single + components: + - type: Sprite + state: pyrotton + - type: Stack + count: 1 + - type: entity parent: MaterialBase id: MaterialBananium diff --git a/Resources/Prototypes/Entities/Objects/Materials/ore.yml b/Resources/Prototypes/Entities/Objects/Materials/ore.yml index bf7dbfad5a..521d3cf1ec 100644 --- a/Resources/Prototypes/Entities/Objects/Materials/ore.yml +++ b/Resources/Prototypes/Entities/Objects/Materials/ore.yml @@ -344,4 +344,52 @@ suffix: Single components: - type: Stack - count: 1 \ No newline at end of file + count: 1 + +- type: entity + parent: OreBase + id: BluespaceOre + name: bluespace ore + suffix: Full + components: + - type: Stack + stackType: BluespaceOre + - type: Sprite + state: bluespace + - type: Item + - type: Material + - type: PhysicalComposition + materialComposition: + RawBluespace: 100 + +- type: entity + parent: BluespaceOre + id: BluespaceOre1 + suffix: Single + components: + - type: Stack + count: 1 + +- type: entity + parent: OreBase + id: NormalityOre + name: normality ore + suffix: Full + components: + - type: Stack + stackType: NormalityOre + - type: Sprite + state: normality + - type: Item + - type: Material + - type: PhysicalComposition + materialComposition: + RawNormality: 100 + +- type: entity + parent: NormalityOre + id: NormalityOre1 + suffix: Single + components: + - type: Stack + count: 1 diff --git a/Resources/Prototypes/Entities/Objects/Materials/parts.yml b/Resources/Prototypes/Entities/Objects/Materials/parts.yml index 726048478e..2027102190 100644 --- a/Resources/Prototypes/Entities/Objects/Materials/parts.yml +++ b/Resources/Prototypes/Entities/Objects/Materials/parts.yml @@ -69,6 +69,39 @@ Quantity: 4.5 - ReagentId: Carbon Quantity: 0.5 + - type: UserInterface + interfaces: + enum.RadialSelectorUiKey.Key: + type: RadialSelectorMenuBUI + - type: ActivatableUI + key: enum.RadialSelectorUiKey.Key + inHandsOnly: true + requireActiveHand: false + - type: ShortConstruction + entries: + - prototype: Grille + - prototype: GrilleDiagonal + - category: + name: Railings + icon: + sprite: Structures/Walls/railing.rsi + state: side + entries: + - prototype: Railing + - prototype: RailingCorner + - prototype: RailingRound + - prototype: RailingCornerSmall + - category: + name: Glass + icon: + sprite: Objects/Materials/Sheets/glass.rsi + state: rglass_3 + entries: + - prototype: SheetRPGlass + - prototype: SheetRPGlass0 + - prototype: SheetRUGlass + - prototype: SheetRUGlass0 + - prototype: SheetRGlass - type: entity parent: PartRodMetal diff --git a/Resources/Prototypes/Entities/Objects/Materials/shards.yml b/Resources/Prototypes/Entities/Objects/Materials/shards.yml index b9385e5556..85b9c55a16 100644 --- a/Resources/Prototypes/Entities/Objects/Materials/shards.yml +++ b/Resources/Prototypes/Entities/Objects/Materials/shards.yml @@ -18,7 +18,7 @@ shard2: "" shard3: "" - type: MeleeWeapon - attackRate: 1.5 + attackRate: 1 damage: types: Slash: 3.5 @@ -65,8 +65,11 @@ - !type:DoActsBehavior acts: [ "Destruction" ] - type: StepTrigger + triggerGroups: + types: + - Shard intersectRatio: 0.2 - - type: ShoesRequiredStepTrigger + - type: ClothingRequiredStepTrigger - type: Slippery slipSound: path: /Audio/Effects/glass_step.ogg @@ -94,7 +97,7 @@ - type: DamageUserOnTrigger damage: types: - Piercing: 5 + Piercing: 4.5 - type: Tag tags: - GlassShard @@ -119,6 +122,10 @@ components: - type: Sprite color: "#96cdef" + - type: MeleeWeapon + damage: + types: + Slash: 4.5 - type: WelderRefinable refineResult: - id: SheetGlass1 @@ -126,7 +133,7 @@ - type: DamageUserOnTrigger damage: types: - Piercing: 5 + Piercing: 5.5 - type: Tag tags: - ReinforcedGlassShard @@ -151,6 +158,10 @@ components: - type: Sprite color: "#FF72E7" + - type: MeleeWeapon + damage: + types: + Slash: 5.5 - type: WelderRefinable refineResult: - id: SheetGlass1 @@ -158,7 +169,7 @@ - type: DamageUserOnTrigger damage: types: - Piercing: 5 + Piercing: 6.5 - type: Tag tags: - PlasmaGlassShard @@ -185,6 +196,11 @@ components: - type: Sprite color: "#8eff7a" + - type: MeleeWeapon + damage: + types: + Slash: 4.5 + Radiation: 2 - type: WelderRefinable refineResult: - id: SheetGlass1 @@ -192,8 +208,8 @@ - type: DamageUserOnTrigger damage: types: - Piercing: 3 - Radiation: 2 + Piercing: 5 + Radiation: 2.5 - type: Tag tags: - UraniumGlassShard diff --git a/Resources/Prototypes/Entities/Objects/Misc/books.yml b/Resources/Prototypes/Entities/Objects/Misc/books.yml index 043ea4b0fe..fca4b5c59d 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/books.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/books.yml @@ -22,11 +22,10 @@ contentSize: 12000 - type: ActivatableUI key: enum.PaperUiKey.Key - closeOnHandDeselect: false - type: UserInterface interfaces: - - key: enum.PaperUiKey.Key - type: PaperBoundUserInterface + enum.PaperUiKey.Key: + type: PaperBoundUserInterface - type: Tag tags: - Book @@ -55,7 +54,7 @@ color: "#0a2a6b" - state: decor_wingette color: "#082561" - - state: icon_text + - state: icon_text color: gold - state: icon_planet color: "#42b6f5" @@ -367,7 +366,7 @@ description: Each book is unique! What is hidden in this one? components: - type: RandomMetadata - nameSegments: + nameSegments: - book_hint_appearance - book_type - type: RandomSprite @@ -416,7 +415,7 @@ icon_skull: "" icon_text: "" icon_text2: "" - icon_text3: "" + icon_text3: "" overlay: overlay_blood: "" overlay_dirt: Sixteen @@ -457,7 +456,7 @@ - book_story_element_trait - "." storySeparator: "" - + - type: entity parent: BookBase id: BookAtmosDistro @@ -536,4 +535,4 @@ - state: icon_corner color: gold - type: Paper - content: book-text-atmos-vents \ No newline at end of file + content: book-text-atmos-vents diff --git a/Resources/Prototypes/Entities/Objects/Misc/broken_bottle.yml b/Resources/Prototypes/Entities/Objects/Misc/broken_bottle.yml index f8dbabd07a..a6cbe9a6e7 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/broken_bottle.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/broken_bottle.yml @@ -6,8 +6,8 @@ components: - type: Sharp - type: MeleeWeapon - attackRate: 1.5 - range: 1.3 + attackRate: 1.4 + range: 1.4 damage: types: Slash: 4 diff --git a/Resources/Prototypes/Entities/Objects/Misc/chopsticks.yml b/Resources/Prototypes/Entities/Objects/Misc/chopsticks.yml new file mode 100644 index 0000000000..ed49053f8a --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Misc/chopsticks.yml @@ -0,0 +1,30 @@ +- type: entity + parent: BaseItem + id: ChopSticks + name: chopsticks + description: A very traditional utensil. + components: + - type: Sprite + sprite: Objects/Misc/chopstick.rsi + state: icon + - type: Item + sprite: Objects/Misc/chopstick.rsi + size: Small + - type: Utensil + types: + - Fork + +- type: entity + parent: BaseItem + name: paired chopsticks + id: PairedChopsticks + description: You should probably seperate them. + components: + - type: SpawnItemsOnUse + items: + - id: ChopSticks + sound: + path: /Audio/Effects/chopstickbreak.ogg + - type: Sprite + sprite: Objects/Misc/chopstick.rsi + state: paired diff --git a/Resources/Prototypes/Entities/Objects/Misc/fire_extinguisher.yml b/Resources/Prototypes/Entities/Objects/Misc/fire_extinguisher.yml index f1802e426f..32d8ea7540 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/fire_extinguisher.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/fire_extinguisher.yml @@ -46,8 +46,8 @@ Blunt: 8 heavyRateModifier: 0.8 heavyDamageBaseModifier: 2 - heavyStaminaCost: 15 - maxTargets: 8 + heavyStaminaCost: 7.5 + maxTargets: 6 soundHit: path: /Audio/Weapons/smash.ogg - type: Tool @@ -61,6 +61,9 @@ enabled: True: { state: fire_extinguisher_closed } False: { state: fire_extinguisher_open } + - type: PhysicalComposition + materialComposition: + Steel: 100 - type: entity name: extinguisher spray diff --git a/Resources/Prototypes/Entities/Objects/Misc/fluff_lights.yml b/Resources/Prototypes/Entities/Objects/Misc/fluff_lights.yml index a42b2fa113..8fc465f4fb 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/fluff_lights.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/fluff_lights.yml @@ -40,6 +40,7 @@ sprite: Objects/Misc/Lights/lights.rsi size: Normal heldPrefix: off + - type: EtherealLight - type: PointLight enabled: false radius: 3 @@ -212,6 +213,11 @@ enabled: false radius: 8 energy: 5 + - type: ItemSlots + slots: + cell_slot: + name: power-cell-slot-component-slot-name-default + startingItem: PowerCellMedium - type: Anchorable - type: Damageable damageContainer: Inorganic diff --git a/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml b/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml index 7041be7b20..621fb4557d 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml @@ -310,6 +310,22 @@ - type: PresetIdCard job: Scientist +- type: entity + parent: IDCardStandard + id: RoboticsIDCard + name: robotics ID card + components: + - type: Sprite + layers: + - state: default + - state: department + color: "#C96DBF" + - state: subdepartment + color: "#952004" + - state: scientist + - type: PresetIdCard + job: Roboticist + - type: entity parent: IDCardStandard id: ClownIDCard @@ -886,10 +902,12 @@ default: PassengerIDCard - type: UserInterface interfaces: - - key: enum.AgentIDCardUiKey.Key + enum.AgentIDCardUiKey.Key: type: AgentIDCardBoundUserInterface - - key: enum.ChameleonUiKey.Key + enum.ChameleonUiKey.Key: type: ChameleonBoundUserInterface + - type: ClothingAddFaction + faction: Syndicate - type: entity name: passenger ID card @@ -902,6 +920,8 @@ - Maintenance - SyndicateAgent - NuclearOperative + - type: ClothingAddFaction + faction: Syndicate - type: entity parent: IDCardStandard @@ -936,6 +956,8 @@ tags: - NuclearOperative - SyndicateAgent + - type: ClothingAddFaction + faction: Syndicate - type: entity parent: IDCardStandard @@ -954,6 +976,8 @@ tags: - NuclearOperative - SyndicateAgent + - type: ClothingAddFaction + faction: Syndicate - type: entity parent: IDCardStandard diff --git a/Resources/Prototypes/Entities/Objects/Misc/kudzu.yml b/Resources/Prototypes/Entities/Objects/Misc/kudzu.yml index a100500494..838715f1da 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/kudzu.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/kudzu.yml @@ -25,8 +25,7 @@ - type: MeleeSound soundGroups: Brute: - path: - "/Audio/Weapons/slash.ogg" + path: "/Audio/Weapons/slash.ogg" - type: Sprite sprite: Objects/Misc/kudzu.rsi state: kudzu_11 @@ -38,21 +37,19 @@ fix1: hard: false density: 7 - shape: - !type:PhysShapeAabb + shape: !type:PhysShapeAabb bounds: "-0.5,-0.5,0.5,0.5" layer: - - MidImpassable + - MidImpassable - type: Damageable damageModifierSet: Wood - type: Destructible thresholds: - - trigger: - !type:DamageTrigger - damage: 10 - behaviors: - - !type:DoActsBehavior - acts: [ "Destruction" ] + - trigger: !type:DamageTrigger + damage: 10 + behaviors: + - !type:DoActsBehavior + acts: ["Destruction"] - type: Temperature heatDamage: types: @@ -69,14 +66,14 @@ Flammable: [Touch] Extinguish: [Touch] reactions: - - reagents: [WeedKiller, PlantBGone] - methods: [Touch] - effects: - - !type:HealthChange - scaleByQuantity: true - damage: - types: - Heat: 10 + - reagents: [WeedKiller, PlantBGone] + methods: [Touch] + effects: + - !type:HealthChange + scaleByQuantity: true + damage: + types: + Heat: 10 - type: AtmosExposed - type: Kudzu growthTickChance: 0.3 @@ -86,19 +83,19 @@ sprintSpeedModifier: 0.2 ignoreWhitelist: components: - - IgnoreKudzu + - IgnoreKudzu - type: Food requiredStomachs: 2 # ruminants have 4 stomachs but i dont care to give them literally 4 stomachs. 2 is good delay: 0.5 - type: FlavorProfile flavors: - - fiber + - fiber - type: SolutionContainerManager solutions: food: reagents: - - ReagentId: Nutriment - Quantity: 2 + - ReagentId: Nutriment + Quantity: 2 - type: entity id: WeakKudzu @@ -127,22 +124,22 @@ sprintSpeedModifier: 0.8 ignoreWhitelist: components: - - IgnoreKudzu + - IgnoreKudzu - type: RandomSpawner deleteSpawnerAfterSpawn: false rareChance: 0.15 offset: 0.2 chance: 0.05 prototypes: - - LightTree01 - - LightTree02 - - LightTree03 - - LightTree04 - - LightTree05 - - LightTree06 - - CrystalCyan + - LightTree01 + - LightTree02 + - LightTree03 + - LightTree04 + - LightTree05 + - LightTree06 + - CrystalCyan rarePrototypes: - - AnomalyFloraBulb + - AnomalyFloraBulb - type: entity id: KudzuFlowerAngry @@ -154,11 +151,11 @@ - type: RandomSpawner chance: 0.05 rarePrototypes: - - AnomalyFloraBulb - - AnomalyFloraBulb - - MobLuminousEntity - - MobLuminousObject - - MobLuminousPerson + - AnomalyFloraBulb + - AnomalyFloraBulb + - MobLuminousEntity + - MobLuminousObject + - MobLuminousPerson - type: entity id: FleshKudzu @@ -173,8 +170,7 @@ - type: MeleeSound soundGroups: Brute: - path: - "/Audio/Weapons/slash.ogg" + path: "/Audio/Weapons/slash.ogg" - type: Sprite sprite: Objects/Misc/fleshkudzu.rsi state: kudzu_11 @@ -186,20 +182,18 @@ fix1: hard: false density: 7 - shape: - !type:PhysShapeAabb + shape: !type:PhysShapeAabb bounds: "-0.5,-0.5,0.5,0.5" layer: - - MidImpassable + - MidImpassable - type: Damageable - type: Destructible thresholds: - - trigger: - !type:DamageTrigger - damage: 40 - behaviors: - - !type:DoActsBehavior - acts: [ "Destruction" ] + - trigger: !type:DamageTrigger + damage: 40 + behaviors: + - !type:DoActsBehavior + acts: ["Destruction"] - type: DamageContacts damage: types: @@ -207,7 +201,7 @@ Piercing: 1.5 ignoreWhitelist: tags: - - Flesh + - Flesh - type: Kudzu growthTickChance: 0.1 spreadChance: 0.4 @@ -232,23 +226,23 @@ - type: Flammable fireSpread: true damage: - types: - Heat: 3 + types: + Heat: 3 - type: AtmosExposed - type: SpeedModifierContacts walkSpeedModifier: 0.3 sprintSpeedModifier: 0.3 ignoreWhitelist: tags: - - Flesh + - Flesh - type: Food # delightfully devilish ! delay: 0.5 - type: SolutionContainerManager solutions: food: reagents: - - ReagentId: Protein - Quantity: 2 + - ReagentId: Protein + Quantity: 2 - type: Respirator damage: types: @@ -260,7 +254,7 @@ - type: entity name: dark haze id: ShadowKudzu - parent: [ BaseKudzu, BaseShadow ] + parent: [BaseKudzu, BaseShadow] components: - type: Physics canCollide: false @@ -269,9 +263,9 @@ drawdepth: Effects sprite: Effects/spookysmoke.rsi layers: - - state: spookysmoke - color: "#793a80dd" - map: [base] + - state: spookysmoke + color: "#793a80dd" + map: [base] - type: Kudzu growthTickChance: 0.2 spreadChance: 0.99 @@ -281,10 +275,10 @@ offset: 0.2 chance: 0.45 prototypes: - - ShadowBasaltRandom + - ShadowBasaltRandom rarePrototypes: - - ShadowPortal - - ShadowKudzuLootSpawner + - ShadowPortal + - ShadowKudzuLootSpawner - type: Tag tags: - HideContextMenu @@ -292,10 +286,10 @@ - type: OptionsVisualizer visuals: base: - - options: Default - data: { state: spookysmoke } - - options: ReducedMotion - data: { state: spookysmoke_static } + - options: Default + data: { state: spookysmoke } + - options: ReducedMotion + data: { state: spookysmoke_static } - type: entity name: Haze @@ -304,3 +298,18 @@ components: - type: Kudzu spreadChance: 0 #appears during pulsation. It shouldnt spreading. + +- type: entity + name: Shadowkin Haze + id: ShadowkinShadow + parent: ShadowKudzuWeak + components: + - type: RandomSpawner + deleteSpawnerAfterSpawn: false + rareChance: 0 + offset: 0.2 + chance: 0.45 + prototypes: + - ShadowBasaltRandom + - type: TimedDespawn + lifetime: 30 \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Objects/Misc/land_mine.yml b/Resources/Prototypes/Entities/Objects/Misc/land_mine.yml index a3e3485bc6..97053660a1 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/land_mine.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/land_mine.yml @@ -40,6 +40,9 @@ params: maxDistance: 10 - type: StepTrigger + triggerGroups: + types: + - Landmine requiredTriggeredSpeed: 0 stepOn: true diff --git a/Resources/Prototypes/Entities/Objects/Misc/machine_parts.yml b/Resources/Prototypes/Entities/Objects/Misc/machine_parts.yml index 341acb52f0..8538a4342d 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/machine_parts.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/machine_parts.yml @@ -16,6 +16,10 @@ sound: /Audio/SimpleStation14/Items/Handling/component_drop.ogg - type: EmitSoundOnLand sound: /Audio/SimpleStation14/Items/Handling/component_drop.ogg +# Rating 1 + - type: GuideHelp + guides: + - MachineUpgrading # Rating 1 @@ -69,3 +73,153 @@ - type: ReverseEngineering # Nyano recipes: - MatterBinStockPart + +# Rating 2 + +- type: entity + id: AdvancedCapacitorStockPart + name: advanced capacitor + parent: CapacitorStockPart + description: An advanced capacitor used in the construction of a variety of devices. + suffix: Rating 2 + components: + - type: Sprite + state: adv_capacitor + - type: MachinePart + rating: 2 + - type: ReverseEngineering # Nyano + difficulty: 2 + recipes: + - AdvancedCapacitorStockPart + +- type: entity + id: NanoManipulatorStockPart + name: advanced manipulator + parent: MicroManipulatorStockPart + description: An advanced manipulator used in the construction of a variety of devices. + suffix: Rating 2 + components: + - type: Sprite + state: nano_mani + - type: MachinePart + rating: 2 + - type: ReverseEngineering # Nyano + difficulty: 2 + recipes: + - NanoManipulatorStockPart + +- type: entity + id: AdvancedMatterBinStockPart + name: advanced matter bin + parent: MatterBinStockPart + description: An advanced matter bin used in the construction of a variety of devices. + suffix: Rating 2 + components: + - type: Sprite + state: advanced_matter_bin + - type: MachinePart + rating: 2 + - type: ReverseEngineering # Nyano + difficulty: 2 + recipes: + - AdvancedMatterBinStockPart + +# Rating 3 + +- type: entity + id: SuperCapacitorStockPart + name: super capacitor + parent: CapacitorStockPart + description: A super capacitor used in the construction of a variety of devices. + suffix: Rating 3 + components: + - type: Sprite + state: super_capacitor + - type: MachinePart + rating: 3 + - type: ReverseEngineering # Nyano + difficulty: 3 + recipes: + - SuperCapacitorStockPart + +- type: entity + id: PicoManipulatorStockPart + name: super manipulator + parent: MicroManipulatorStockPart + description: A super manipulator used in the construction of a variety of devices. + suffix: Rating 3 + components: + - type: Sprite + state: pico_mani + - type: MachinePart + rating: 3 + - type: ReverseEngineering # Nyano + difficulty: 3 + recipes: + - PicoManipulatorStockPart + +- type: entity + id: SuperMatterBinStockPart + name: super matter bin + parent: MatterBinStockPart + description: A super matter bin used in the construction of a variety of devices. + suffix: Rating 3 + components: + - type: Sprite + state: super_matter_bin + - type: MachinePart + rating: 3 + - type: ReverseEngineering # Nyano + difficulty: 3 + recipes: + - SuperMatterBinStockPart + +# Rating 4 + +- type: entity + id: BluespaceCapacitorStockPart + name: bluespace capacitor + parent: CapacitorStockPart + description: A bluespace capacitor used in the construction of a variety of devices. + suffix: Rating 4 + components: + - type: Sprite + state: quadratic_capacitor + - type: MachinePart + rating: 4 + - type: ReverseEngineering # Nyano + difficulty: 4 + recipes: + - BluespaceCapacitorStockPart + +- type: entity + id: BluespaceManipulatorStockPart + name: bluespace manipulator + parent: MicroManipulatorStockPart + description: A bluespace manipulator used in the construction of a variety of devices. + suffix: Rating 4 + components: + - type: Sprite + state: femto_mani + - type: MachinePart + rating: 4 + - type: ReverseEngineering # Nyano + difficulty: 4 + recipes: + - BluespaceManipulatorStockPart + +- type: entity + id: BluespaceMatterBinStockPart + name: bluespace matter bin + parent: MatterBinStockPart + description: A bluespace matter bin used in the construction of a variety of devices. + suffix: Rating 4 + components: + - type: Sprite + state: bluespace_matter_bin + - type: MachinePart + rating: 4 + - type: ReverseEngineering # Nyano + difficulty: 4 + recipes: + - BluespaceMatterBinStockPart diff --git a/Resources/Prototypes/Entities/Objects/Misc/mail_capsule.yml b/Resources/Prototypes/Entities/Objects/Misc/mail_capsule.yml new file mode 100644 index 0000000000..bd6c9057cd --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Misc/mail_capsule.yml @@ -0,0 +1,139 @@ +- type: entity + name: mail capsule + suffix: Primed + id: MailCapsulePrimed + parent: BaseItem + components: + - type: ThrowingAngle + angle: 180 + - type: EmbeddableProjectile + minimumSpeed: 1 + removalTime: 0.1 + - type: Tag + tags: + - MailCapsule + - Trash + - type: Sprite + sprite: Objects/Misc/mail_capsule.rsi + layers: + - state: icon-empty + - type: ItemSlots + slots: + mail_slot: + insertVerbText: Put in Mail + ejectVerbText: Take out Mail + name: Mail + startingItem: null + whitelist: + tags: + - Book + - Document + - Mail + components: + - Mail + - Paper + - HyperlinkBook + insertOnInteract: true + priority: 3 + food_slot: + insertVerbText: Put in Food + ejectVerbText: Take out Food + name: Food + startingItem: null + whitelist: + components: + - Food + insertOnInteract: true + priority: 2 + cash_slot: + insertVerbText: Put in Cash + ejectVerbText: Take out Cash + name: Cash + startingItem: null + whitelist: + components: + - Currency + insertOnInteract: true + priority: 1 + - type: ContainerContainer + containers: + storagebase: !type:Container + showEnts: False + occludes: true + ents: [] + mail_slot: !type:ContainerSlot + showEnts: False + occludes: true + ent: null + food_slot: !type:ContainerSlot + showEnts: False + occludes: true + ent: null + cash_slot: !type:ContainerSlot + showEnts: False + occludes: true + ent: null + - type: Appearance + - type: ItemMapper + mapLayers: + icon-food: + whitelist: + components: + - Food + icon-cash: + whitelist: + components: + - Currency + icon-mail: + whitelist: + tags: + - Book + - Document + - Mail + components: + - Mail + - Paper + - HyperlinkBook + sprite: Objects/Misc/mail_capsule.rsi + - type: Dumpable + - type: Damageable + damageContainer: Inorganic + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 20 #excess damage avoids cost of spawning entities. + behaviors: + - !type:DoActsBehavior + acts: [ "Destruction" ] + - trigger: + !type:DamageTrigger + damage: 10 + behaviors: + - !type:PlaySoundBehavior + sound: + collection: GlassBreak + - !type:EmptyAllContainersBehaviour + - !type:DoActsBehavior + acts: [ "Destruction" ] + - type: DamageOnLand + damage: + types: + Blunt: 9.5 + +- type: entity + name: mail capsule box + parent: BoxCardboard + id: BoxMailCapsulePrimed + description: A box of primed mail capsules. + components: + - type: Storage + grid: + - 0,0,4,3 + - type: StorageFill + contents: + - id: MailCapsulePrimed + amount: 10 + - type: Sprite + layers: + - state: box diff --git a/Resources/Prototypes/Entities/Objects/Misc/medalcase.yml b/Resources/Prototypes/Entities/Objects/Misc/medalcase.yml index a421c1e9e9..35000b3fba 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/medalcase.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/medalcase.yml @@ -17,6 +17,9 @@ - type: Storage grid: - 0,0,7,1 + whitelist: + tags: + - Medal - type: StorageFill contents: - id: ClothingNeckGoldmedal diff --git a/Resources/Prototypes/Entities/Objects/Misc/paper.yml b/Resources/Prototypes/Entities/Objects/Misc/paper.yml index d1527ceff5..c72f7a2e91 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/paper.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/paper.yml @@ -18,12 +18,11 @@ - type: PaperLabelType - type: ActivatableUI key: enum.PaperUiKey.Key - closeOnHandDeselect: false requireHands: false - type: UserInterface interfaces: - - key: enum.PaperUiKey.Key - type: PaperBoundUserInterface + enum.PaperUiKey.Key: + type: PaperBoundUserInterface - type: Item size: Tiny - type: Tag @@ -32,6 +31,7 @@ - Trash - Paper - type: Appearance + - type: FaxableObject - type: PaperVisuals - type: Flammable fireSpread: true @@ -159,6 +159,16 @@ backgroundPatchMargin: 16.0, 16.0, 16.0, 16.0 contentMargin: 32.0, 16.0, 32.0, 0.0 +- type: entity + name: note + description: A piece of white paper. + id: PaperWrittenPunpunNote + parent: PaperCaptainsThoughts + suffix: Punpun Note + components: + - type: Paper + content: I, Punpun, invoke my right to have all of my clones on the NT family vacation to the meat packaging plant one out of every 15 shifts. + - type: entity name: cargo invoice parent: Paper @@ -270,8 +280,8 @@ key: enum.PaperUiKey.Key - type: UserInterface interfaces: - - key: enum.PaperUiKey.Key - type: PaperBoundUserInterface + enum.PaperUiKey.Key: + type: PaperBoundUserInterface - type: entity parent: Paper @@ -714,10 +724,10 @@ key: enum.CargoConsoleUiKey.Orders - type: UserInterface interfaces: - - key: enum.CargoConsoleUiKey.Orders - type: CargoOrderConsoleBoundUserInterface - - key: enum.StorageUiKey.Key - type: StorageBoundUserInterface + enum.CargoConsoleUiKey.Orders: + type: CargoOrderConsoleBoundUserInterface + enum.StorageUiKey.Key: + type: StorageBoundUserInterface - type: MeleeWeapon wideAnimationRotation: 180 damage: diff --git a/Resources/Prototypes/Entities/Objects/Misc/pet_carrier.yml b/Resources/Prototypes/Entities/Objects/Misc/pet_carrier.yml index 84c9c7c752..b7f296cbac 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/pet_carrier.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/pet_carrier.yml @@ -30,7 +30,7 @@ radius: 0.45 density: 25 mask: - - SmallMobMask + - ItemMask layer: - MachineLayer - type: EntityStorage diff --git a/Resources/Prototypes/Entities/Objects/Misc/spider_web.yml b/Resources/Prototypes/Entities/Objects/Misc/spider_web.yml index 9561fa3538..68ba94b578 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/spider_web.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/spider_web.yml @@ -76,6 +76,9 @@ ignoreWhitelist: components: - IgnoreSpiderWeb + - type: Tag + tags: + - ArachneWeb - type: entity id: SpiderWebClown @@ -111,6 +114,9 @@ paralyzeTime: 2 launchForwardsMultiplier: 1.5 - type: StepTrigger + triggerGroups: + types: + - SlipTile intersectRatio: 0.2 - type: Physics - type: Fixtures diff --git a/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml b/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml index 2336945f17..a0f5e254d5 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml @@ -126,7 +126,7 @@ ents: [ ] - type: UserInterface interfaces: - - key: enum.StorageUiKey.Key + enum.StorageUiKey.Key: type: StorageBoundUserInterface - type: entity @@ -160,8 +160,8 @@ Telecrystal: 0 - type: UserInterface interfaces: - - key: enum.StoreUiKey.Key - type: StoreBoundUserInterface + enum.StoreUiKey.Key: + type: StoreBoundUserInterface - type: entity parent: BaseSubdermalImplant diff --git a/Resources/Prototypes/Entities/Objects/Misc/tiles.yml b/Resources/Prototypes/Entities/Objects/Misc/tiles.yml index 78bbd32f17..99f9ccaa87 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/tiles.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/tiles.yml @@ -17,6 +17,9 @@ sound: /Audio/Weapons/star_hit.ogg - type: Stack count: 1 + - type: Tag + tags: + - NoPaint - type: Damageable damageContainer: Inorganic - type: Destructible diff --git a/Resources/Prototypes/Entities/Objects/Misc/torch.yml b/Resources/Prototypes/Entities/Objects/Misc/torch.yml index 0efb753e0d..50e8f65890 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/torch.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/torch.yml @@ -25,6 +25,8 @@ color: "#FFFFFF" visible: false shader: unshaded + - map: [ enum.ExpendableLightVisualLayers.Overlay ] + state: torch_nocloth - type: Icon sprite: Objects/Misc/torch.rsi state: icon diff --git a/Resources/Prototypes/Entities/Objects/Misc/translator_implanters.yml b/Resources/Prototypes/Entities/Objects/Misc/translator_implanters.yml index 8b5b262ff8..53da8e72a5 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/translator_implanters.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/translator_implanters.yml @@ -10,7 +10,7 @@ name: basic common translator implanter components: - type: Implanter - implant: BasicGalacticCommonTranslatorImplant + implant: BasicTauCetiBasicTranslatorImplant - type: entity id: AdvancedGalaticCommonTranslatorImplanter @@ -18,7 +18,7 @@ name: advanced common translator implanter components: - type: Implanter - implant: GalacticCommonTranslatorImplant + implant: TauCetiBasicTranslatorImplant - type: entity id: BubblishTranslatorImplanter @@ -37,9 +37,9 @@ implant: NekomimeticTranslatorImplant - type: entity - id: DraconicTranslatorImplanter + id: DraconicTranslatorImplanter # Intended for Admins Only, this item is for lore reasons not obtainable. parent: [ BaseTranslatorImplanter ] - name: draconic translator implant + name: unathi translator implant components: - type: Implanter implant: DraconicTranslatorImplant @@ -75,3 +75,19 @@ components: - type: Implanter implant: MofficTranslatorImplant + +- type: entity + id: ValyrianStandardTranslatorImplanter + parent: [ BaseTranslatorImplanter ] + name: valyrian standard translator implant + components: + - type: Implanter + implant: ValyrianStandardTranslatorImplant + +- type: entity + id: AzazibaTranslatorImplanter + parent: [ BaseTranslatorImplanter ] + name: azaziba translator implant + components: + - type: Implanter + implant: AzazibaTranslatorImplant \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Objects/Misc/utensils.yml b/Resources/Prototypes/Entities/Objects/Misc/utensils.yml index 4250669581..86667f094f 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/utensils.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/utensils.yml @@ -120,3 +120,28 @@ - Plastic - Trash - Knife + +- type: entity + parent: UtensilBase + id: BarSpoon + name: bar spoon + description: Your personal helper to mix drinks and changes lives. + components: + - type: Tag + tags: + - Metal + - type: Sprite + state: bar_spoon + - type: Item + heldPrefix: spoon + - type: Utensil + types: + - Spoon + - type: MeleeWeapon + wideAnimationRotation: 180 + attackRate: 2 + damage: + types: + Blunt: 2 + - type: Shovel + speedModifier: 0.05 # nah diff --git a/Resources/Prototypes/Entities/Objects/Power/portable_recharger.yml b/Resources/Prototypes/Entities/Objects/Power/portable_recharger.yml new file mode 100644 index 0000000000..e3213ac8c9 --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Power/portable_recharger.yml @@ -0,0 +1,38 @@ +- type: entity + parent: Clothing + id: PortableRecharger + name: portable recharger + description: High-tech recharger adapted for portability + components: + - type: Item + size: Huge + - type: Sprite + sprite: Objects/Power/portable_recharger.rsi + state: charging + - type: Clothing + equippedPrefix: charging + quickEquip: false + slots: + - back + - type: Charger + slotId: charger_slot + portable: true + - type: PowerChargerVisuals + - type: ApcPowerReceiver + needsPower: false + powerLoad: 0 + - type: StaticPrice + price: 500 + - type: Tag + tags: [] # ignore "WhitelistChameleon" tag + - type: ContainerContainer + containers: + charger_slot: !type:ContainerSlot + - type: ItemSlots + slots: + charger_slot: + ejectOnInteract: true + whitelist: + components: + - HitscanBatteryAmmoProvider + - ProjectileBatteryAmmoProvider diff --git a/Resources/Prototypes/Entities/Objects/Shields/shields.yml b/Resources/Prototypes/Entities/Objects/Shields/shields.yml index b794e42ff7..e7ebb1b98d 100644 --- a/Resources/Prototypes/Entities/Objects/Shields/shields.yml +++ b/Resources/Prototypes/Entities/Objects/Shields/shields.yml @@ -313,7 +313,7 @@ name: mirror shield parent: BaseShield id: MirrorShield - description: Eerily glows red... you hear the geometer whispering + description: Glows an eerie red. You hear the Geometer whispering... components: - type: Sprite state: mirror-icon @@ -321,6 +321,7 @@ heldPrefix: mirror - type: Reflect reflectProb: 0.95 + innate: true reflects: - Energy - type: Blocking #Mirror shield reflects heat/laser, but is relatively weak to everything else. @@ -408,6 +409,7 @@ - type: Reflect enabled: false reflectProb: 0.95 + innate: true reflects: - Energy - type: Blocking diff --git a/Resources/Prototypes/Entities/Objects/Specific/Cargo/cargo_pallet.yml b/Resources/Prototypes/Entities/Objects/Specific/Cargo/cargo_pallet.yml index c628d199a9..07bdee63cf 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Cargo/cargo_pallet.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Cargo/cargo_pallet.yml @@ -1,7 +1,7 @@ - type: entity id: CargoPallet name: cargo pallet - description: Designates valid items to sell to CentCom when a shuttle is recalled. + description: Common fixture of logistics and cargo. Subtle reminder where crates go during transport to avoid bruised shins. parent: BaseStructure components: - type: InteractionOutline @@ -58,17 +58,20 @@ - type: entity id: CargoPalletSell name: cargo selling pallet - description: Designates valid items to sell with a selling computer, or to CentCom when a shuttle is recalled. + description: Designates valid items to sell. parent: CargoPallet components: - type: CargoPallet palletType: sell - type: Sprite drawdepth: FloorTiles - layers: - - sprite: Structures/cargo_pallets.rsi - state: cargo_pallet_sell - + sprite: Structures/cargo_pallets.rsi + - type: Icon + sprite: Structures/cargo_pallets.rsi + state: cargo_pallet_sell + - type: IconSmooth + key: cargo_pallet_sell + base: cargo_pallet_sell_ - type: entity id: CargoPalletBuy @@ -80,7 +83,10 @@ palletType: buy - type: Sprite drawdepth: FloorTiles - layers: - - sprite: Structures/cargo_pallets.rsi - state: cargo_pallet_buy - + sprite: Structures/cargo_pallets.rsi + - type: Icon + sprite: Structures/cargo_pallets.rsi + state: cargo_pallet_buy + - type: IconSmooth + key: cargo_pallet_buy + base: cargo_pallet_buy_ diff --git a/Resources/Prototypes/Entities/Objects/Specific/Chapel/bibles.yml b/Resources/Prototypes/Entities/Objects/Specific/Chapel/bibles.yml index 9ab53cebc9..23ce5a36ee 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Chapel/bibles.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Chapel/bibles.yml @@ -20,8 +20,6 @@ Burn: 10 - type: Prayable bibleUserOnly: true - - type: Summonable - specialItem: SpawnPointGhostRemilia - type: ReactionMixer mixMessage: "bible-mixing-success" reactionTypes: @@ -40,8 +38,8 @@ - 0,0,0,1 - type: UserInterface interfaces: - - key: enum.StorageUiKey.Key - type: StorageBoundUserInterface + enum.StorageUiKey.Key: + type: StorageBoundUserInterface - type: MeleeWeapon # Nyanotrasen - Bibles do Holy damage damage: types: @@ -50,7 +48,7 @@ heavyRateModifier: 0.8 heavyDamageBaseModifier: 1 heavyStaminaCost: 5 - maxTargets: 3 + maxTargets: 4 - type: Tag tags: - Book diff --git a/Resources/Prototypes/Entities/Objects/Specific/Chapel/urn.yml b/Resources/Prototypes/Entities/Objects/Specific/Chapel/urn.yml index c778860084..55a58ef18a 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Chapel/urn.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Chapel/urn.yml @@ -9,8 +9,8 @@ - 0,0,0,1 - type: UserInterface interfaces: - - key: enum.StorageUiKey.Key - type: StorageBoundUserInterface + enum.StorageUiKey.Key: + type: StorageBoundUserInterface - type: Sprite sprite: Objects/Specific/Chapel/chaplainurn.rsi state: icon diff --git a/Resources/Prototypes/Entities/Objects/Specific/Hydroponics/leaves.yml b/Resources/Prototypes/Entities/Objects/Specific/Hydroponics/leaves.yml index b847416211..c26ba925e0 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Hydroponics/leaves.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Hydroponics/leaves.yml @@ -65,7 +65,101 @@ - Smokable - type: Item size: Tiny + +- type: entity + name: rainbow cannabis leaves + parent: LeavesCannabis + id: LeavesCannabisRainbow + description: "Is it supposed to be glowing like that...?" + components: + - type: Sprite + sprite: Objects/Specific/Hydroponics/rainbow_cannabis.rsi + - type: Produce + seedId: rainbowCannabis + - type: PointLight + radius: 1.5 + energy: 2 + - type: RgbLightController + cycleRate: 0.6 + - type: SolutionContainerManager + solutions: + food: + reagents: + - ReagentId: SpaceDrugs + Quantity: 3 + - ReagentId: Lipolicide + Quantity: 3 + - ReagentId: MindbreakerToxin + Quantity: 2 + - ReagentId: Happiness + Quantity: 2 +# - ReagentId: ColorfulReagent +# Quantity: 1 + - ReagentId: Psicodine + Quantity: 0.6 +- type: entity + name: dried rainbow cannabis leaves + parent: LeavesCannabisDried + id: LeavesCannabisRainbowDried + description: "Dried rainbow cannabis leaves, ready to be ground." + components: + - type: Stack + stackType: LeavesCannabisRainbowDried + count: 1 + - type: SolutionContainerManager + solutions: + food: + maxVol: 8.5 #fuck you saveload test fail + reagents: + - ReagentId: SpaceDrugs + Quantity: 2.4 + - ReagentId: Lipolicide + Quantity: 2.4 + - ReagentId: MindbreakerToxin + Quantity: 1.6 + - ReagentId: Happiness + Quantity: 1.6 +# - ReagentId: ColorfulReagent +# Quantity: 0.8 + - ReagentId: Psicodine + Quantity: 0.48 + - type: Sprite + sprite: Objects/Specific/Hydroponics/rainbow_cannabis.rsi + state: dried + +- type: entity + name: ground rainbow cannabis + parent: GroundCannabis + id: GroundCannabisRainbow + description: "Ground rainbow cannabis, ready to take you on a trip." + components: + - type: Stack + stackType: GroundCannabisRainbow + count: 1 + - type: SolutionContainerManager + solutions: + food: + reagents: + - ReagentId: SpaceDrugs + Quantity: 4 + - ReagentId: Lipolicide + Quantity: 4 + - ReagentId: MindbreakerToxin + Quantity: 2.66 + - ReagentId: Happiness + Quantity: 2.66 +# - ReagentId: ColorfulReagent +# Quantity: 1.33 + - ReagentId: Psicodine + Quantity: 0.8 + - type: Sprite + sprite: Objects/Specific/Hydroponics/rainbow_cannabis.rsi + state: powderpile_rainbow + color: white + - type: Construction + graph: smokeableGroundCannabisRainbow + node: groundRainbow - type: entity name: tobacco leaves diff --git a/Resources/Prototypes/Entities/Objects/Specific/Hydroponics/seeds.yml b/Resources/Prototypes/Entities/Objects/Specific/Hydroponics/seeds.yml index 2b232d643d..92aa22a8ba 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Hydroponics/seeds.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Hydroponics/seeds.yml @@ -285,6 +285,17 @@ - type: Sprite sprite: Objects/Specific/Hydroponics/cannabis.rsi +- type: entity + parent: SeedBase + name: packet of rainbow cannabis seeds + description: "These seeds grow into rainbow weed. Groovy... and also highly addictive." + id: RainbowCannabisSeeds + components: + - type: Seed + seedId: rainbowCannabis + - type: Sprite + sprite: Objects/Specific/Hydroponics/rainbow_cannabis.rsi + - type: entity parent: SeedBase name: packet of nettle seeds @@ -571,3 +582,13 @@ seedId: cotton - type: Sprite sprite: Objects/Specific/Hydroponics/cotton.rsi + +- type: entity + parent: SeedBase + name: packet of pyrotton seeds + id: PyrottonSeeds + components: + - type: Seed + seedId: pyrotton + - type: Sprite + sprite: Objects/Specific/Hydroponics/pyrotton.rsi diff --git a/Resources/Prototypes/Entities/Objects/Specific/Hydroponics/tools.yml b/Resources/Prototypes/Entities/Objects/Specific/Hydroponics/tools.yml index 37b8daddc2..c43cce1f8b 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Hydroponics/tools.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Hydroponics/tools.yml @@ -21,7 +21,6 @@ heavyRateModifier: 1 heavyRangeModifier: 1 heavyDamageBaseModifier: 1.2 - heavyStaminaCost: 5 maxTargets: 5 angle: 100 - type: Item @@ -48,7 +47,6 @@ heavyRateModifier: 0.9 heavyRangeModifier: 1.25 heavyDamageBaseModifier: 1.2 - heavyStaminaCost: 5 maxTargets: 1 angle: 20 - type: Item @@ -103,7 +101,7 @@ wideAnimationRotation: 135 swingLeft: true attackRate: 1.25 - range: 1.25 + range: 1.4 damage: types: Slash: 10 diff --git a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml index de5c33671a..a3a26299bf 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml @@ -16,7 +16,7 @@ heavyRateModifier: 0.8 heavyRangeModifier: 1.25 heavyDamageBaseModifier: 1.25 - heavyStaminaCost: 10 + heavyStaminaCost: 7.5 maxTargets: 2 angle: 180 soundHit: @@ -38,6 +38,9 @@ maxVol: 100 - type: UseDelay delay: 1 + - type: PhysicalComposition + materialComposition: + Plastic: 50 - type: Tag tags: - Mop @@ -64,7 +67,7 @@ heavyRateModifier: 0.8 heavyRangeModifier: 1.25 heavyDamageBaseModifier: 1.25 - heavyStaminaCost: 10 + heavyStaminaCost: 7.5 maxTargets: 2 angle: 180 soundHit: @@ -119,6 +122,9 @@ coefficients: Blunt: 0.95 Slash: 0.95 + - type: PhysicalComposition + materialComposition: + Plastic: 50 - type: Tag tags: - WetFloorSign diff --git a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/soap.yml b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/soap.yml index 8784ab3f3f..0806fc548d 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/soap.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/soap.yml @@ -25,6 +25,9 @@ paralyzeTime: 1.5 # Parkstation-BetterSoap launchForwardsMultiplier: 0.25 # Parkstation-BetterSoap - type: StepTrigger + triggerGroups: + types: + - SlipEntity intersectRatio: 0.2 requiredTriggeredSpeed: 4.5 # Parkstation-BetterSoap - type: CollisionWake @@ -64,6 +67,7 @@ solution: soap - type: DeleteOnSolutionEmpty solution: soap + - type: PaintRemover - type: FlavorProfile flavors: - clean @@ -159,6 +163,9 @@ paralyzeTime: 5 launchForwardsMultiplier: 2.5 - type: StepTrigger + triggerGroups: + types: + - SlipEntity intersectRatio: 0.04 - type: Item heldPrefix: syndie @@ -200,6 +207,9 @@ - type: Slippery paralyzeTime: 1 # Parkstation-BetterSoap - type: StepTrigger + triggerGroups: + types: + - SlipEntity - type: Item heldPrefix: gibs - type: FlavorProfile diff --git a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/spray.yml b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/spray.yml index 0e19c03dee..cddf7f6075 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/spray.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/spray.yml @@ -5,6 +5,12 @@ suffix: Empty description: A spray bottle with an unscrewable top. components: + - type: Drink + solution: spray + ignoreEmpty: true + useSound: + path: /Audio/Effects/spray3.ogg + transferAmount: 10 - type: Tag tags: - Spray @@ -24,6 +30,8 @@ solution: spray - type: SolutionTransfer canChangeTransferAmount: true + - type: SolutionItemStatus + solution: spray - type: UseDelay - type: Spray transferAmount: 10 diff --git a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/trashbag.yml b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/trashbag.yml index f802ae1c5c..9927d836ba 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/trashbag.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/trashbag.yml @@ -22,6 +22,8 @@ tags: - Cartridge - Trash + - type: UseDelay + delay: 0.5 - type: Tag tags: - TrashBag diff --git a/Resources/Prototypes/Entities/Objects/Specific/Kitchen/foodcarts.yml b/Resources/Prototypes/Entities/Objects/Specific/Kitchen/foodcarts.yml index 0894d6a754..ca74dca8a7 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Kitchen/foodcarts.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Kitchen/foodcarts.yml @@ -44,8 +44,8 @@ - type: Appearance - type: UserInterface interfaces: - - key: enum.StorageUiKey.Key - type: StorageBoundUserInterface + enum.StorageUiKey.Key: + type: StorageBoundUserInterface - type: Storage maxItemSize: Normal grid: diff --git a/Resources/Prototypes/Entities/Objects/Specific/Mail/Items/boxes.yml b/Resources/Prototypes/Entities/Objects/Specific/Mail/Items/boxes.yml new file mode 100644 index 0000000000..536736fc90 --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Specific/Mail/Items/boxes.yml @@ -0,0 +1,212 @@ +# Mail-only boxes. If/when something uses these outside of the mail, move the entry into Catalog/Fills. + +- type: entity + name: scented soap sampler pack + parent: BoxCardboard + id: BoxSoapsAssorted + description: A box of various scented soaps. Ooh, lavender. + components: + - type: StorageFill + contents: + - id: SoapNT + amount: 1 + - id: Soap + amount: 1 + - id: SoapHomemade + amount: 1 + - id: SoapDeluxe + amount: 1 + - type: Storage + maxItemSize: Normal + grid: + - 0,0,3,1 + whitelist: + tags: + - Soap + - type: Sprite + layers: + - state: box + +- type: entity + name: scented soap sampler pack + parent: BoxCardboard + id: BoxSoapsAssortedOmega + description: A box of various scented soaps. Ooh, bluespace. + components: + - type: StorageFill + contents: + - id: SoapNT + amount: 1 + - id: Soap + amount: 1 + - id: SoapOmega + amount: 1 + - id: SoapDeluxe + amount: 1 + - type: Storage + maxItemSize: Normal + grid: + - 0,0,3,1 + whitelist: + tags: + - Soap + - type: Sprite + layers: + - state: box + +- type: entity + name: Build-a-Buddy kit + suffix: Human + parent: BoxHug + id: BoxBuildABuddyHuman + description: "\"Henry the Human\" Build-a-Buddy kit. Some assembly required." + components: + - type: StorageFill + contents: + - id: HeadHuman + amount: 1 + - id: TorsoHuman + amount: 1 + - id: LeftArmHuman + amount: 1 + - id: RightArmHuman + amount: 1 + - id: LeftHandHuman + amount: 1 + - id: RightHandHuman + amount: 1 + - id: LeftLegHuman + amount: 1 + - id: RightLegHuman + amount: 1 + - id: LeftFootHuman + amount: 1 + - id: RightFootHuman + amount: 1 + - type: Storage + grid: + - 0,0,4,3 + whitelist: + components: + - BodyPart + +# DeltaV - Goblins Aren't Real +#- type: entity +# name: Build-a-Buddy kit +# suffix: Goblin +# parent: BoxBuildABuddyHuman +# id: BoxBuildABuddyGoblin +# description: "\"Greta the Goblin\" Build-a-Buddy kit. Some assembly required." +# components: +# - type: StorageFill +# contents: +# - id: HeadGoblin +# amount: 1 +# - id: TorsoGoblin +# amount: 1 +# - id: LeftArmGoblin +# amount: 1 +# - id: RightArmGoblin +# amount: 1 +# - id: LeftHandGoblin +# amount: 1 +# - id: RightHandGoblin +# amount: 1 +# - id: LeftLegGoblin +# amount: 1 +# - id: RightLegGoblin +# amount: 1 +# - id: LeftFootGoblin +# amount: 1 +# - id: RightFootGoblin +# amount: 1 + +- type: entity + name: Build-a-Buddy kit + suffix: Reptilian + parent: BoxBuildABuddyHuman + id: BoxBuildABuddyReptilian + description: "\"Randy the Reptilian\" Build-a-Buddy kit. Some assembly required." + components: + - type: StorageFill + contents: + - id: HeadReptilian + amount: 1 + - id: TorsoReptilian + amount: 1 + - id: LeftArmReptilian + amount: 1 + - id: RightArmReptilian + amount: 1 + - id: LeftHandReptilian + amount: 1 + - id: RightHandReptilian + amount: 1 + - id: LeftLegReptilian + amount: 1 + - id: RightLegReptilian + amount: 1 + - id: LeftFootReptilian + amount: 1 + - id: RightFootReptilian + amount: 1 + +- type: entity + name: Build-a-Buddy kit + suffix: Slime + parent: BoxBuildABuddyHuman + id: BoxBuildABuddySlime + description: "\"Steven the Slime\" Build-a-Buddy kit. Some assembly required." + components: + - type: StorageFill + contents: + - id: HeadSlime + amount: 1 + - id: TorsoSlime + amount: 1 + - id: LeftArmSlime + amount: 1 + - id: RightArmSlime + amount: 1 + - id: LeftHandSlime + amount: 1 + - id: RightHandSlime + amount: 1 + - id: LeftLegSlime + amount: 1 + - id: RightLegSlime + amount: 1 + - id: LeftFootSlime + amount: 1 + - id: RightFootSlime + amount: 1 + +- type: entity + name: Build-a-Buddy kit + suffix: Vulpkanin + parent: BoxBuildABuddyHuman + id: BoxBuildABuddyVulpkanin + description: "\"Valerie the Vulpkanin\" Build-a-Buddy kit. Some assembly required." + components: + - type: StorageFill + contents: + - id: HeadVulpkanin + amount: 1 + - id: TorsoVulpkanin + amount: 1 + - id: LeftArmVulpkanin + amount: 1 + - id: RightArmVulpkanin + amount: 1 + - id: LeftHandVulpkanin + amount: 1 + - id: RightHandVulpkanin + amount: 1 + - id: LeftLegVulpkanin + amount: 1 + - id: RightLegVulpkanin + amount: 1 + - id: LeftFootVulpkanin + amount: 1 + - id: RightFootVulpkanin + amount: 1 diff --git a/Resources/Prototypes/Entities/Objects/Specific/Mail/Items/misc.yml b/Resources/Prototypes/Entities/Objects/Specific/Mail/Items/misc.yml new file mode 100644 index 0000000000..6f1c86e00b --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Specific/Mail/Items/misc.yml @@ -0,0 +1,117 @@ +# Mail-only items. If/when these get used for anything else, please move them to another folder. +# Pranks: admin items or effects put into an envelope, released when opened or damaged. +- type: entity + id: DelayedSmoke + parent: BaseItem + noSpawn: true + name: delayed smoke + suffix: "(10s)" + components: + - type: Sprite #DeltaV: Apparently these want sprites, probably because they're baseitems + sprite: /Textures/Objects/Fun/goldbikehorn.rsi + visible: false + state: icon + - type: DelayedItem + item: AdminInstantEffectSmoke10 + +- type: entity + id: AdminInstantEffectEMP7 + noSpawn: true + suffix: EMP, 7 meters + parent: AdminInstantEffectBase + components: + - type: EmpOnTrigger + range: 7 + energyConsumption: 50000 + +- type: entity + id: DelayedEMP + parent: BaseItem + noSpawn: true + name: delayed EMP (7 meters) + components: + - type: Sprite #DeltaV: Apparently these want sprites, probably because they're baseitems + sprite: /Textures/Objects/Fun/goldbikehorn.rsi + visible: false + state: icon + - type: DelayedItem + item: AdminInstantEffectEMP7 + +# Miscellaneous Items + +- type: entity + id: SyringeCognizine + parent: Syringe + name: cognizine syringe + components: + - type: SolutionContainerManager + solutions: + drink: + maxVol: 15 + reagents: + - ReagentId: Cognizine + Quantity: 15 # Surely three friends is enough. + +- type: entity + id: SyringeOpporozidone + parent: Syringe + name: opporozidone syringe + components: + - type: SolutionContainerManager + solutions: + drink: + maxVol: 15 +# reagents: # TODO: we don't have that yet. Guess the people will receive an empty syringe instead. +# - ReagentId: Opporozidone +# Quantity: 15 + +- type: entity + id: NecrosolChemistryBottle + parent: BaseChemistryBottleFilled + name: necrosol bottle + components: + - type: SolutionContainerManager + solutions: + drink: + maxVol: 30 + reagents: + - ReagentId: Necrosol + Quantity: 30 + +# Premium Alcohol: wait, it's just marketing? +# TODO: different sprites would be nice. +- type: entity + id: DrinkPremiumVodkaBottleFull + parent: DrinkVodkaBottleFull + name: Moment of Clarity vodka bottle + description: When things get a bit hectic, all you need is a Moment of Clarity. + +- type: entity + id: DrinkPremiumGinBottleFull + parent: DrinkGinBottleFull + name: Harry's gin bottle + description: An interesting set of botanicals, for sure. Is that pumpkin? + +- type: entity + id: DrinkPremiumTequilaBottleFull + parent: DrinkTequilaBottleFull + name: Casa del Eorg tequila bottle + description: Save the best for last. Casa del Eorg, 100% agave. + +- type: entity + id: DrinkPremiumWhiskeyBottleFull + parent: DrinkWhiskeyBottleFull + name: Ol' Prowler 18 whiskey bottle + description: Surprisingly smooth, it has a nasty habit of sneaking up on you. + +- type: entity + id: DrinkPremiumRumBottleFull + parent: DrinkRumBottleFull + name: Redeemer's Bounty rum bottle + description: Well, you asked for it. Navy strength. + +- type: entity + id: DrinkPremiumAbsintheBottleFull + parent: DrinkAbsintheBottleFull + name: Bureaucracy's Kiss absinthe bottle + description: A refined taste that tends to linger. diff --git a/Resources/Prototypes/Entities/Objects/Specific/Mail/Items/paper.yml b/Resources/Prototypes/Entities/Objects/Specific/Mail/Items/paper.yml new file mode 100644 index 0000000000..56c6123edd --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Specific/Mail/Items/paper.yml @@ -0,0 +1,483 @@ +# Papers (letters, ad copy) +# TODO: these should really be based on localization strings. +- type: entity + id: PaperMailNFPowerTool + name: Hazard Fraught advertisement + categories: [ HideSpawnMenu ] + suffix: "power tool ad, formatted" + parent: Paper + components: + - type: Paper + content: |2 + + [head=1]Hazard Fraught Tools[/head] + + [head=2]Discount Tools at Quality Prices![/head] + + [head=2]Fax us for a catalog at + [color=#990000]ERROR: UNEXPECTED EOF[/color][/head] + +- type: entity + id: PaperMailNFVagueThreat1 + categories: [ HideSpawnMenu ] + suffix: "vague mail threat 1, formatted" + parent: Paper + components: + - type: Paper + content: |2 + + [head=1]I know what you did.[/head] + + [head=3]You don't know what I'm going to do to you.[/head] + +- type: entity + id: PaperMailNFVagueThreat2 + categories: [ HideSpawnMenu ] + suffix: "vague mail threat 2, formatted" + parent: Paper + components: + - type: Paper + content: |2 + + [head=1]I'm coming for you.[/head] + +- type: entity + id: PaperMailNFVagueThreat3 + categories: [ HideSpawnMenu ] + suffix: "vague mail threat 3, formatted" + parent: Paper + components: + - type: Paper + content: |2 + + [head=1]You're next.[/head] + +- type: entity + id: PaperMailNFVagueThreat4 + categories: [ HideSpawnMenu ] + suffix: "vague mail threat 4, formatted" + parent: Paper + components: + - type: Paper + content: |2 + + [head=1]We see you.[/head] + +- type: entity + id: PaperMailNFVagueThreat5 + categories: [ HideSpawnMenu ] + suffix: "vague mail threat 5, formatted" + parent: Paper + components: + - type: Paper + content: |2 + + [head=2]I hope your affairs are in order.[/head] + +- type: entity + id: PaperMailNFVagueThreat6 + categories: [ HideSpawnMenu ] + suffix: "vague mail threat 6, formatted" + parent: Paper + components: + - type: Paper + content: |2 + + [head=1]It's only a matter of time.[/head] + + + [head=1]Enjoy it while it lasts.[/head] + +- type: entity + id: PaperMailNFVagueThreat7 + categories: [ HideSpawnMenu ] + suffix: "vague mail threat 7, formatted" + parent: Paper + components: + - type: Paper + content: |2 + + [head=2]Who should we mail your pieces to?[/head] + +- type: entity + id: PaperMailNFVagueThreat8 + categories: [ HideSpawnMenu ] + suffix: "vague mail threat 8, formatted" + parent: Paper + components: + - type: Paper + content: |2 + + [head=2]Would you prefer to die slowly or quickly? + [/head] + [head=1]Just kidding.[/head] + + [head=2]We don't care what you think.[/head] + +- type: entity + id: PaperMailNFVagueThreat9 + categories: [ HideSpawnMenu ] + suffix: "vague mail threat 9, formatted" + parent: Paper + components: + - type: Paper + content: |2 + + [head=3]I think your head would look nice on my mantel.[/head] + +- type: entity + id: PaperMailNFVagueThreat10 + categories: [ HideSpawnMenu ] + suffix: "vague mail threat 10, formatted" + parent: Paper + components: + - type: Paper + content: |2 + + [head=1]You should have paid up.[/head] + + + [head=1]It's too late now.[/head] + +- type: entity + id: PaperMailNFVagueThreat11 + categories: [ HideSpawnMenu ] + suffix: "vague mail threat 11, formatted" + parent: Paper + components: + - type: Paper + content: |2 + + [head=3]Your family will miss you, but don't worry.[/head] + + + [head=1]We'll take care of them too.[/head] + +- type: entity + id: PaperMailNFVagueThreat12 + categories: [ HideSpawnMenu ] + suffix: "vague mail threat 12, formatted" + parent: Paper + components: + - type: Paper + content: |2 + + [head=3]I have a bet that you're going to die today.[/head] + + + [head=1]I'm not afraid to cheat.[/head] + +- type: entity + id: PaperMailNFPwrGameAd + name: pwr game advertisement + categories: [ HideSpawnMenu ] + suffix: "pwr game ad" + parent: Paper + components: + - type: Paper + content: |2 + + [head=1]Drink Pwr Game![/head] + + [head=3]Proud sponsor of the NT Block Game Championship.[/head] + +- type: entity + id: PaperMailNFRedBoolAd + name: red bool advertisement + categories: [ HideSpawnMenu ] + suffix: "red bool ad" + parent: Paper + components: + - type: Paper + content: |2 + + [head=2]Try NEW Reformulated Red Bool![/head] + + [head=2]Over [color=#dd0000]1.5g[/color] of caffeine per can![/head] + + [head=2]Punch your heart into overdrive![/head] + +- type: entity + id: PaperMailNFSpaceColaAd + name: space cola advertisement + categories: [ HideSpawnMenu ] + suffix: "space cola ad" + parent: Paper + components: + - type: Paper + content: |2 + + [head=2]The classic taste you love, Space Cola.[/head] + + [head=2]Now certified lead-free.[/head] + +- type: entity + id: PaperMailNFSpaceMountainWindAd + name: space mountain wind advertisement + categories: [ HideSpawnMenu ] + suffix: "space mountain wind ad" + parent: Paper + components: + - type: Paper + content: |2 + + [head=3]When it's time to game, there's one choice:[/head] + + [head=1]Space Mountain Wind.[/head] + +- type: entity + id: PaperMailNFSpaceUpAd + name: space up advertisement + categories: [ HideSpawnMenu ] + suffix: "space up ad" + parent: Paper + components: + - type: Paper + content: |2 + + [head=3]The crisp, refreshing taste of lemon and lime.[/head] + + + [head=1]Space Up![/head] + + + [head=2]Ask your barkeep for a Sui Dream today![/head] + +- type: entity + id: PaperMailNTSoapAd1 + categories: [ HideSpawnMenu ] + suffix: "soap ad 1" + parent: Paper + components: + - type: Paper + stampedBy: + - stampedColor: '#333333FF' + stampedName: Christopher Cleanman +# stampType: Signature #DeltaV - Not compatible with our signatures code stuff apparently + content: |2 + [head=3]Hello Valued Customer,[/head] + You have been selected to receive a complimentary sampler of scented soaps that Nanotrasen has to offer. + + Why not enjoy a nice warm shower with our scented soaps? Tested and effective vs. vent crud and mold. + + We hope you enjoy. + + Sincerely, + Christopher Cleanman, Vice President, NT Habs - Toiletries Dept. + +- type: entity + id: PaperMailNTSoapAd2 + categories: [ HideSpawnMenu ] + suffix: "soap ad 2" #DeltaV - Edited to not be addressed to Frontier Citizens, localized + parent: Paper + components: + - type: Paper + content: |2 + [head=2]GREETINGS DELTA SECTOR CITIZEN[/head] + + OUR REPORTS INDICATE THAT: + 1. YOU HAVE FAILED YOUR QUARTERLY HYGIENE INSPECTION. + 2. THIS HAS REDUCED SECTOR EFFICIENCY BY [bold]0.02%[/bold]. + + ENCLOSED IS A SELECTION OF HYGIENE PRODUCTS SUITABLE FOR USE BY ORGANICS. WE HOPE THAT THIS SITUATION IS RESOLVED PROMPTLY. + + [italic]THIS IS AN AUTOMATED MESSAGE. DO NOT REPLY.[/italic] + +- type: entity + id: PaperMailNTConscript + categories: [ HideSpawnMenu ] + suffix: "conscript" + parent: Paper + components: + - type: Paper + content: |2 + + [head=1]NOT ONE STEP BACK.[/head] + + + [head=1]FOR THE FRONTIER.[/head] + +- type: entity + id: PaperMailNTMusket + categories: [ HideSpawnMenu ] + suffix: "musket" + parent: Paper + components: + - type: Paper + content: |2 + + [head=2]Use a musket for sector defense, + like the founding fathers intended.[/head] + +- type: entity + id: PaperMailNFPaperPusherAd + categories: [ HideSpawnMenu ] + suffix: "paper pusher" + parent: Paper + components: + - type: Paper + content: |2 + + [head=2]Here is a pen for any letters you write. + [/head] + [head=1]Keep it close, use it often.[/head] + + [head=2]May you write well, neatly, and with style.[/head] + + [head=3]Sincerely, + [italic]The Frontier Paper Pusher's Club[/italic][/head] + +- type: entity + id: PaperMailNFPetBedAssemblyManual + name: pet bed assembly manual + categories: [ HideSpawnMenu ] + suffix: "pet bed assembly manual" + parent: Paper + components: + - type: Paper + content: |2 + + [head=1]HÖGANÄS[/head] + + [italic](There is a black and white picture of a pet bed on the first page.)[/italic] + + [italic](On the next few pages, you see a list of materials and a happy stick figure assembling furniture.)[/italic] + + [italic](On the pages after that, you see a set of instructions to assemble a pet bed. You're sure you don't need them, how hard could it be?)[/italic] + +- type: entity + id: PaperMailNTBoxer + categories: [ HideSpawnMenu ] + suffix: "boxer" + parent: Paper + components: + - type: Paper + content: |2 + [head=2]You've gotta defend your belt, champ. + [/head] + [head=1]They're coming for you.[/head] + + [head=2]This should help. Knock 'em out.[/head] + +# Placeholder for an arm-on-use, flashbang fakeout pipebomb +- type: entity + id: PaperMailNFPipebombIntern + categories: [ HideSpawnMenu ] + suffix: "pipe bomb intern" + parent: Paper + components: + - type: Paper + stampedBy: + - stampedColor: '#333333FF' + stampedName: craig +# stampType: Signature #DeltaV - Not compatible with our signatures code stuff apparently + content: |2 + [bold]hey uh, they told me to send you a pipebomb i guess? + + this is all i could find around here, hope that works + + thanks[/bold] + +- type: entity + id: PaperMailNFAntivirus + name: Snortin Antivirus invoice + categories: [ HideSpawnMenu ] + suffix: "antivirus ad" + parent: Paper + components: + - type: Paper + content: |2 + + [head=1]Invoice[/head][head=3] + Snortin Antivirus Software[/head] + + [head=3]Order #41003 + [bold][bullet/][/bold] 1x Snortin Total-275 Antivirus Install Disk[/head] + + [head=3]Total: 947381 Spesos[/head] + + Thank you for making purchase from Snortin Antivirus Software. + We assuring you that our product is greatest. + Please sending payment at earliest convenience. + +- type: entity + id: PaperMailNFEMPPreparedness + categories: [ HideSpawnMenu ] + name: EMP preparedness response form + suffix: "emp preparedness" #DeltaV - Replaces mention of SR with HoS + parent: Paper + components: + - type: Paper + content: |2 + + [head=1]EMP Preparedness Response[/head] + + You have been selected to receive a NT EMP Preparedness kit as a test. Note that this is only a test. In a real emergency, follow the instructions of your vessel's command staff. + + As the recipient of this, please note [bold]any improvements[/bold] that could be made towards the EMP preparedness of the vessel you were aboard when opening and submit this form to your serving Captain or Head of Security. + + [bold]Date of test:[/bold] + [bold]Number of affected items:[/bold] + [bold]Perceived severity of incident:[/bold] + [bold]Suggested improvements:[/bold] + +- type: entity + id: PaperMailNFBuildABuddy + categories: [ HideSpawnMenu ] + name: Build-a-Buddy adoption letter + suffix: "build-a-buddy" #DeltaV- Body text changed, because Goblins Aren't Real + parent: Paper + components: + - type: Paper + stampState: paper_stamp-generic + stampedBy: + - stampedColor: '#FF6699FF' + stampedName: Chief Friendship Officer + - stampedColor: '#333333FF' + stampedName: Cuts-With-Scalpel +# stampType: Signature #DeltaV - Not compatible with our signatures code stuff apparently. + content: |2 + + [head=1]Note of Adoption[/head] + + You're now the proud owner of your very own Build-a-Buddy! + + We hope that your new friend can serve as a shoulder to lean on in the depths of space, and hopefully you won't be quite as lonely out there. Personally, I find putting them together to be rather therapeutic. + + [bold]Collect the whole set![/bold] + [bold][bullet/][/bold] Henry the Human + [bold][bullet/][/bold] Randy the Reptilian + [bold][bullet/][/bold] Steven the Slime + [bold][bullet/][/bold] Valerie the Vulpkanin + +- type: entity + id: PaperMailNFSpaceLaw + categories: [ HideSpawnMenu ] + suffix: "space-law" #DeltaV- edited contents to be from the Delta Sector instead of the Frontier + parent: Paper + components: + - type: Paper + stampState: paper_stamp-centcom + stampedBy: + - stampedColor: '#006600FF' + stampedName: Central Admiralty of the Delta Sector + content: |2 + + [head=1]Space Law is your shield.[/head] + + [head=2]With it, you guard the Delta Sector.[/head][head=3] + [/head] + [head=1]Memorize it. Grasp it firmly.[/head] + + [head=2]The SOP is your sword, don't get rusty.[/head] + + [head=2]Maintain your balance, and wield it well.[/head] + + + + + + + + + [head=3][italic]Internal Bureau of Propaganda[/italic][/head] diff --git a/Resources/Prototypes/Entities/Objects/Specific/Mail/base_mail_large.yml b/Resources/Prototypes/Entities/Objects/Specific/Mail/base_mail_large.yml new file mode 100644 index 0000000000..a5dcefacba --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Specific/Mail/base_mail_large.yml @@ -0,0 +1,79 @@ +# Large packages. +- type: entity + parent: BaseMail + abstract: true + id: BaseMailLarge + name: mail-large-item-name-unaddressed + components: + - type: Item + size: Ginormous + - type: Sprite + scale: 0.8, 0.8 + sprite: Objects/Specific/Mail/mail_large.rsi + layers: + - state: icon + map: ["enum.MailVisualLayers.Icon"] + - state: fragile + map: ["enum.MailVisualLayers.FragileStamp"] + visible: false + - map: ["enum.MailVisualLayers.JobStamp"] + scale: 0.8, 0.8 + offset: 0.235, -0.01 + - state: locked + map: ["enum.MailVisualLayers.Lock"] + - state: priority + map: ["enum.MailVisualLayers.PriorityTape"] + visible: false + shader: unshaded + - state: broken + map: ["enum.MailVisualLayers.Breakage"] + visible: false + - type: GenericVisualizer + visuals: + enum.MailVisuals.IsTrash: + enum.MailVisualLayers.Icon: + True: + state: trash + False: + state: icon + enum.MailVisuals.IsLocked: + enum.MailVisualLayers.Lock: + True: + visible: true + False: + visible: false + enum.MailVisuals.IsFragile: + enum.MailVisualLayers.FragileStamp: + True: + visible: true + False: + visible: false + enum.MailVisuals.IsPriority: + enum.MailVisualLayers.PriorityTape: + True: + visible: true + False: + visible: false + enum.MailVisuals.IsPriorityInactive: + enum.MailVisualLayers.PriorityTape: + True: + shader: shaded + state: priority_inactive + False: + shader: unshaded + state: priority + enum.MailVisuals.IsBroken: + enum.MailVisualLayers.Breakage: + True: + visible: true + False: + visible: false + - type: MultiHandedItem + - type: Mail + isLarge: true + +- type: entity + noSpawn: true + parent: BaseMailLarge + id: MailLargeAdminFun + suffix: adminfun diff --git a/Resources/Prototypes/Entities/Objects/Specific/Mech/mechs.yml b/Resources/Prototypes/Entities/Objects/Specific/Mech/mechs.yml index 6e5362d9bb..6b5c634e93 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Mech/mechs.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Mech/mechs.yml @@ -39,8 +39,8 @@ doAfterDelay: 10 - type: UserInterface interfaces: - - key: enum.MechUiKey.Key - type: MechBoundUserInterface + enum.MechUiKey.Key: + type: MechBoundUserInterface - type: MeleeWeapon hidden: true attackRate: 0.75 @@ -72,7 +72,7 @@ shape: !type:PhysShapeCircle radius: 0.45 - density: 1000 + density: 21390 # 15 Tonnes mask: - MobMask layer: diff --git a/Resources/Prototypes/Entities/Objects/Specific/Medical/handheld_crew_monitor.yml b/Resources/Prototypes/Entities/Objects/Specific/Medical/handheld_crew_monitor.yml index e2e9328342..ab338b9da9 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Medical/handheld_crew_monitor.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Medical/handheld_crew_monitor.yml @@ -16,10 +16,9 @@ - type: ActivatableUIRequiresPowerCell - type: ActivatableUI key: enum.CrewMonitoringUIKey.Key - closeOnHandDeselect: false - type: UserInterface interfaces: - - key: enum.CrewMonitoringUIKey.Key + enum.CrewMonitoringUIKey.Key: type: CrewMonitoringBoundUserInterface - type: CrewMonitoringConsole - type: DeviceNetwork diff --git a/Resources/Prototypes/Entities/Objects/Specific/Medical/healthanalyzer.yml b/Resources/Prototypes/Entities/Objects/Specific/Medical/healthanalyzer.yml index 64bd04569b..c01aaa84a9 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Medical/healthanalyzer.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Medical/healthanalyzer.yml @@ -17,10 +17,9 @@ storedRotation: -90 - type: ActivatableUI key: enum.HealthAnalyzerUiKey.Key - closeOnHandDeselect: false - type: UserInterface interfaces: - - key: enum.HealthAnalyzerUiKey.Key + enum.HealthAnalyzerUiKey.Key: type: HealthAnalyzerBoundUserInterface - type: HealthAnalyzer scanningEndSound: diff --git a/Resources/Prototypes/Entities/Objects/Specific/Medical/medkits.yml b/Resources/Prototypes/Entities/Objects/Specific/Medical/medkits.yml index 62e5ab4476..46d1efa24e 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Medical/medkits.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Medical/medkits.yml @@ -15,6 +15,9 @@ size: Large sprite: Objects/Specific/Medical/firstaidkits.rsi heldPrefix: firstaid + - type: PhysicalComposition + materialComposition: + Plastic: 150 - type: Tag tags: - Medkit diff --git a/Resources/Prototypes/Entities/Objects/Specific/Medical/surgery.yml b/Resources/Prototypes/Entities/Objects/Specific/Medical/surgery.yml index c81768da4d..5683f700f4 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Medical/surgery.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Medical/surgery.yml @@ -8,9 +8,10 @@ - type: Sprite - type: StaticPrice price: 20 - - type: Tag - tags: - - SurgeryTool +# - type: Tag +# tags: +# - SurgeryTool + - type: SurgeryTool # Cautery @@ -32,6 +33,12 @@ Heat: 5 soundHit: path: /Audio/Effects/lightburn.ogg + - type: SurgeryTool + startSound: + path: /Audio/Medical/Surgery/cautery1.ogg + endSound: + path: /Audio/Medical/Surgery/cautery2.ogg + - type: Cautery # Drill @@ -51,7 +58,7 @@ - 1,1,1,1 - type: MeleeWeapon attackRate: 0.75 - range: 1.3 + range: 1.4 damage: types: Piercing: 8 @@ -63,6 +70,10 @@ path: /Audio/Items/drill_hit.ogg - type: StaticPrice price: 40 + - type: SurgeryTool + startSound: + path: /Audio/Medical/Surgery/saw.ogg + - type: SurgicalDrill # Scalpel @@ -87,7 +98,7 @@ wideAnimationRotation: 90 swingLeft: true attackRate: 1.25 - range: 1.25 + range: 1.4 damage: types: Slash: 7.5 @@ -98,6 +109,13 @@ angle: 20 soundHit: path: /Audio/Weapons/bladeslice.ogg + - type: SurgeryTool + startSound: + path: /Audio/Medical/Surgery/scalpel1.ogg + endSound: + path: /Audio/Medical/Surgery/scalpel2.ogg + - type: Scalpel + - type: entity name: shiv @@ -155,25 +173,59 @@ - type: Item sprite: Objects/Specific/Medical/Surgery/scissors.rsi storedRotation: 90 + - type: SurgeryTool + startSound: + path: /Audio/Medical/Surgery/retractor1.ogg + endSound: + path: /Audio/Medical/Surgery/retractor2.ogg + - type: Retractor - type: entity name: hemostat id: Hemostat - parent: Retractor + parent: BaseToolSurgery description: A surgical tool used to compress blood vessels to prevent bleeding. components: - type: Sprite + sprite: Objects/Specific/Medical/Surgery/scissors.rsi state: hemostat - type: Item heldPrefix: hemostat + sprite: Objects/Specific/Medical/Surgery/scissors.rsi storedRotation: 90 + - type: SurgeryTool + startSound: + path: /Audio/Medical/Surgery/retractor1.ogg + endSound: + path: /Audio/Medical/Surgery/hemostat1.ogg + - type: Hemostat + +# Bone setter +- type: entity + parent: BaseToolSurgery + id: Bonesetter + name: bone setter + description: Used for setting bones back into place. + components: + - type: Sprite + sprite: Objects/Specific/Medical/Surgery/bonesetter.rsi + state: bonesetter + - type: Item + sprite: Objects/Specific/Medical/Surgery/bonesetter.rsi + - type: BoneSetter + +# Bone Gel +- type: entity + parent: BaseToolSurgery + id: BoneGel + name: bottle of bone gel + description: A container for bone gel that often needs to be refilled from a specialized machine. + components: + - type: Sprite + sprite: Objects/Specific/Medical/Surgery/bone_gel.rsi + state: bone-gel + - type: BoneGel - # - type: entity - # name: bone setter - # id: BoneSetter - # parent: Retractor - # description: A surgical tool used for setting bones. - # components: # Saws - type: entity @@ -208,7 +260,9 @@ heavyStaminaCost: 20 maxTargets: 8 angle: 20 -# ~~No melee for regular saw because have you ever seen someone use a band saw as a weapon? It's dumb.~~ No, I'm going to saw through your bones. + - type: BoneSaw +# --No melee for regular saw because have you ever seen someone use a band saw as a weapon? It's dumb.-- +# No, I'm going to saw through your bones. - type: entity name: choppa @@ -238,6 +292,7 @@ qualities: - Sawing speed: 0.5 + - type: BoneSaw - type: entity name: circular saw @@ -252,7 +307,7 @@ storedRotation: 90 - type: MeleeWeapon attackRate: 1.15 - range: 1.4 + range: 1.5 bluntStaminaDamageFactor: 3.0 damage: types: @@ -260,7 +315,7 @@ Slash: 5.5 heavyRateModifier: 0.5 heavyDamageBaseModifier: 1 - heavyStaminaCost: 15 + heavyStaminaCost: 10 maxTargets: 8 angle: 360 soundHit: @@ -269,6 +324,10 @@ qualities: - Sawing speed: 1.5 + - type: SurgeryTool + startSound: + path: /Audio/Medical/Surgery/saw.ogg + - type: BoneSaw - type: entity name: advanced circular saw @@ -282,8 +341,8 @@ heldPrefix: advanced storedRotation: 90 - type: MeleeWeapon - attackRate: 1.25 - range: 1.4 + attackRate: 1.15 + range: 1.5 bluntStaminaDamageFactor: 5.0 damage: types: @@ -291,7 +350,7 @@ Slash: 7.5 heavyRateModifier: 0.5 heavyDamageBaseModifier: 1 - heavyStaminaCost: 15 + heavyStaminaCost: 10 maxTargets: 8 angle: 360 soundHit: @@ -300,3 +359,113 @@ qualities: - Sawing speed: 2.0 + - type: BoneSaw + +# ORGANS + +- type: entity + parent: OrganHumanHeart + id: BioSynthHeart + name: bio-synthetic heart + description: This heart can be transplanted into any living organism and it will adapt to its recipient. + +- type: entity + parent: OrganHumanLiver + id: BioSynthLiver + name: bio-synthetic liver + description: This liver can be transplanted into any living organism and it will adapt to its recipient. + +- type: entity + parent: OrganHumanLungs + id: BioSynthLungs + name: bio-synthetic lungs + description: These lungs can be transplanted into any living organism and it will adapt to its recipient. + +- type: entity + parent: OrganHumanEyes + id: BioSynthEyes + name: bio-synthetic eyes + description: These eyes can be transplanted into any living organism and it will adapt to its recipient. + + +# PARTS + +- type: entity + parent: LeftArmHuman + id: BioSynthLeftArm + name: bio-synthetic left arm + description: This left arm can be transplanted into any living organism and it will adapt to its recipient. + +- type: entity + parent: RightArmHuman + id: BioSynthRightArm + name: bio-synthetic right arm + description: This right arm can be transplanted into any living organism and it will adapt to its recipient. + +- type: entity + parent: LeftHandHuman + id: BioSynthLeftHand + name: bio-synthetic left hand + description: This left hand can be transplanted into any living organism and it will adapt to its recipient. + +- type: entity + parent: RightHandHuman + id: BioSynthRightHand + name: bio-synthetic right hand + description: This right hand can be transplanted into any living organism and it will adapt to its recipient. + +- type: entity + parent: LeftLegHuman + id: BioSynthLeftLeg + name: bio-synthetic left leg + description: This left leg can be transplanted into any living organism and it will adapt to its recipient. + +- type: entity + parent: RightLegHuman + id: BioSynthRightLeg + name: bio-synthetic right leg + description: This right leg can be transplanted into any living organism and it will adapt to its recipient. + +- type: entity + parent: LeftFootHuman + id: BioSynthLeftFoot + name: bio-synthetic left foot + description: This left foot can be transplanted into any living organism and it will adapt to its recipient. + +- type: entity + parent: RightFootHuman + id: BioSynthRightFoot + name: bio-synthetic right foot + description: This right foot can be transplanted into any living organism and it will adapt to its recipient. + +# JOKE ITEMS + +- type: entity + parent: LeftArmHuman + id: PizzaLeftArm + name: pizza left arm + description: For when you want to turn someone into a Space John's. + components: + - type: BodyPart + partType: Arm + symmetry: Left + toolName: "a left arm" + baseLayerId: MobPizzaLArm + - type: Sprite + sprite: Mobs/Species/Misc/Pizza/parts.rsi + state: "l_arm" + +- type: entity + parent: RightArmHuman + id: PizzaRightArm + name: pizza right arm + description: For when you want to turn someone into a Space John's. + components: + - type: BodyPart + partType: Arm + symmetry: Right + toolName: "a right arm" + baseLayerId: MobPizzaRArm + - type: Sprite + sprite: Mobs/Species/Misc/Pizza/parts.rsi + state: "r_arm" diff --git a/Resources/Prototypes/Entities/Objects/Specific/Research/anomaly.yml b/Resources/Prototypes/Entities/Objects/Specific/Research/anomaly.yml index 74199c9f65..113017cc84 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Research/anomaly.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Research/anomaly.yml @@ -9,11 +9,11 @@ state: icon - type: ActivatableUI key: enum.AnomalyScannerUiKey.Key - closeOnHandDeselect: false + requireActiveHand: false inHandsOnly: true - type: UserInterface interfaces: - - key: enum.AnomalyScannerUiKey.Key + enum.AnomalyScannerUiKey.Key: type: AnomalyScannerBoundUserInterface - type: AnomalyScanner - type: GuideHelp diff --git a/Resources/Prototypes/Entities/Objects/Specific/Research/rped.yml b/Resources/Prototypes/Entities/Objects/Specific/Research/rped.yml index 84a51b4f34..fca8c8ae85 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Research/rped.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Research/rped.yml @@ -10,6 +10,9 @@ - type: Item sprite: Objects/Specific/Research/rped.rsi size: Normal + - type: GuideHelp + guides: + - MachineUpgrading - type: PartExchanger - type: Storage grid: diff --git a/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml b/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml index a7cf7ad5c8..960e37a6cc 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml @@ -346,6 +346,7 @@ - HoloprojectorBorg - SprayBottleSpaceCleaner - Dropper + - TrashBag # medical modules - type: entity @@ -579,5 +580,19 @@ - state: icon-syndicate - type: ItemBorgModule items: - - WeaponLightMachineGunL6C - - PinpointerSyndicateNuclear + - WeaponLightMachineGunL6C + - PinpointerSyndicateNuclear + +- type: entity + id: BorgModuleMartyr + parent: [ BaseBorgModule, BaseProviderBorgModule ] + name: martyr cyborg module + description: "A module that comes with an explosive you probably don't want to handle yourself." + components: + - type: Sprite + layers: + - state: syndicateborgbomb + - state: icon-bomb + - type: ItemBorgModule + items: + - SelfDestructSeq diff --git a/Resources/Prototypes/Entities/Objects/Specific/Robotics/mmi.yml b/Resources/Prototypes/Entities/Objects/Specific/Robotics/mmi.yml index 308c013da4..ac125d36bd 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Robotics/mmi.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Robotics/mmi.yml @@ -48,6 +48,10 @@ - type: GuideHelp guides: - Cyborgs + - type: LanguageSpeaker + - type: LanguageKnowledge + speaks: [TauCetiBasic, RobotTalk] + understands: [TauCetiBasic, RobotTalk] - type: entity parent: MMI @@ -123,3 +127,8 @@ - type: GuideHelp guides: - Cyborgs + - type: Organ # Estacao Pirata - IPCs + - type: LanguageSpeaker + - type: LanguageKnowledge + speaks: [RobotTalk] + understands: [RobotTalk] diff --git a/Resources/Prototypes/Entities/Objects/Specific/Service/barber.yml b/Resources/Prototypes/Entities/Objects/Specific/Service/barber.yml index 4a16603541..451230bbf1 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Service/barber.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Service/barber.yml @@ -14,11 +14,12 @@ changeSlotTime: 20 - type: ActivatableUI key: enum.MagicMirrorUiKey.Key - closeOnHandDeselect: true + inHandsOnly: true + requireActiveHand: true - type: UserInterface interfaces: - - key: enum.MagicMirrorUiKey.Key - type: MagicMirrorBoundUserInterface + enum.MagicMirrorUiKey.Key: + type: MagicMirrorBoundUserInterface - type: MeleeWeapon wideAnimationRotation: -135 attackRate: 1 diff --git a/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/artifact_equipment.yml b/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/artifact_equipment.yml index c38868b399..e1716f0843 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/artifact_equipment.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/artifact_equipment.yml @@ -36,7 +36,7 @@ radius: 0.45 density: 50 mask: - - SmallMobMask #this is so they can go under plastic flaps + - CrateMask #this is so they can go under plastic flaps layer: - MachineLayer - type: Icon diff --git a/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/item_artifacts.yml b/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/item_artifacts.yml index cb6708431a..036b876a97 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/item_artifacts.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/item_artifacts.yml @@ -45,14 +45,14 @@ ano01_on: Rainbow - type: UserInterface #needs to be here for certain effects interfaces: - - key: enum.StorageUiKey.Key - type: StorageBoundUserInterface - - key: enum.TransferAmountUiKey.Key - type: TransferAmountBoundUserInterface - - key: enum.InstrumentUiKey.Key - type: InstrumentBoundUserInterface - - key: enum.IntercomUiKey.Key - type: IntercomBoundUserInterface + enum.StorageUiKey.Key: + type: StorageBoundUserInterface + enum.TransferAmountUiKey.Key: + type: TransferAmountBoundUserInterface + enum.InstrumentUiKey.Key: + type: InstrumentBoundUserInterface + enum.IntercomUiKey.Key: + type: IntercomBoundUserInterface - type: Appearance - type: Item size: Normal diff --git a/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/structure_artifacts.yml b/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/structure_artifacts.yml index f098894ff7..b53a617981 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/structure_artifacts.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Xenoarchaeology/structure_artifacts.yml @@ -22,14 +22,14 @@ noRot: true - type: UserInterface #needs to be here for certain effects interfaces: - - key: enum.StorageUiKey.Key - type: StorageBoundUserInterface - - key: enum.TransferAmountUiKey.Key - type: TransferAmountBoundUserInterface - - key: enum.InstrumentUiKey.Key - type: InstrumentBoundUserInterface - - key: enum.IntercomUiKey.Key - type: IntercomBoundUserInterface + enum.StorageUiKey.Key: + type: StorageBoundUserInterface + enum.TransferAmountUiKey.Key: + type: TransferAmountBoundUserInterface + enum.InstrumentUiKey.Key: + type: InstrumentBoundUserInterface + enum.IntercomUiKey.Key: + type: IntercomBoundUserInterface - type: Reactive groups: Acidic: [Touch] diff --git a/Resources/Prototypes/Entities/Objects/Specific/atmos.yml b/Resources/Prototypes/Entities/Objects/Specific/atmos.yml index 0dc714aba0..c67e7ff8c3 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/atmos.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/atmos.yml @@ -13,12 +13,12 @@ - type: ActivatableUI inHandsOnly: true singleUser: true - closeOnHandDeselect: false + requireActiveHand: false key: enum.GasAnalyzerUiKey.Key - type: UserInterface interfaces: - - key: enum.GasAnalyzerUiKey.Key - type: GasAnalyzerBoundUserInterface + enum.GasAnalyzerUiKey.Key: + type: GasAnalyzerBoundUserInterface - type: Appearance - type: GenericVisualizer visuals: diff --git a/Resources/Prototypes/Entities/Objects/Specific/chemical-containers.yml b/Resources/Prototypes/Entities/Objects/Specific/chemical-containers.yml index 027ff206f8..4bd71f898d 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/chemical-containers.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/chemical-containers.yml @@ -8,7 +8,6 @@ solutions: beaker: maxVol: 200 - canMix: true - type: Sprite sprite: Objects/Specific/Chemistry/jug.rsi layers: @@ -19,6 +18,8 @@ - type: Item size: Normal sprite: Objects/Specific/Chemistry/jug.rsi + - type: MixableSolution + solution: beaker - type: RefillableSolution solution: beaker - type: DrainableSolution @@ -31,9 +32,11 @@ solution: beaker - type: SolutionTransfer canChangeTransferAmount: true + - type: SolutionItemStatus + solution: beaker - type: UserInterface interfaces: - - key: enum.TransferAmountUiKey.Key + enum.TransferAmountUiKey.Key: type: TransferAmountBoundUserInterface - type: Drink solution: beaker diff --git a/Resources/Prototypes/Entities/Objects/Specific/chemistry-bottles.yml b/Resources/Prototypes/Entities/Objects/Specific/chemistry-bottles.yml index acfb65aa54..6fdb77fe5f 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/chemistry-bottles.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/chemistry-bottles.yml @@ -29,7 +29,8 @@ solutions: drink: # This solution name and target volume is hard-coded in ChemMasterComponent maxVol: 30 - canMix: true + - type: MixableSolution + solution: drink - type: RefillableSolution solution: drink - type: DrainableSolution @@ -41,10 +42,12 @@ - type: SolutionTransfer maxTransferAmount: 30 canChangeTransferAmount: true + - type: SolutionItemStatus + solution: drink - type: UserInterface interfaces: - - key: enum.TransferAmountUiKey.Key - type: TransferAmountBoundUserInterface + enum.TransferAmountUiKey.Key: + type: TransferAmountBoundUserInterface - type: Item size: Tiny sprite: Objects/Specific/Chemistry/beaker.rsi diff --git a/Resources/Prototypes/Entities/Objects/Specific/chemistry-vials.yml b/Resources/Prototypes/Entities/Objects/Specific/chemistry-vials.yml index c5de88d690..39a35dba8c 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/chemistry-vials.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/chemistry-vials.yml @@ -35,7 +35,8 @@ solutions: beaker: maxVol: 30 - canMix: true + - type: MixableSolution + solution: beaker - type: RefillableSolution solution: beaker - type: DrainableSolution @@ -47,10 +48,12 @@ - type: SolutionTransfer maxTransferAmount: 30 canChangeTransferAmount: true + - type: SolutionItemStatus + solution: beaker - type: UserInterface interfaces: - - key: enum.TransferAmountUiKey.Key - type: TransferAmountBoundUserInterface + enum.TransferAmountUiKey.Key: + type: TransferAmountBoundUserInterface - type: Item size: Tiny sprite: Objects/Specific/Chemistry/vial.rsi diff --git a/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml b/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml index 9e68879fb4..2f86604711 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml @@ -25,7 +25,8 @@ solutions: beaker: maxVol: 50 - canMix: true + - type: MixableSolution + solution: beaker - type: FitsInDispenser solution: beaker - type: RefillableSolution @@ -40,10 +41,12 @@ solution: beaker - type: SolutionTransfer canChangeTransferAmount: true + - type: SolutionItemStatus + solution: beaker - type: UserInterface interfaces: - - key: enum.TransferAmountUiKey.Key - type: TransferAmountBoundUserInterface + enum.TransferAmountUiKey.Key: + type: TransferAmountBoundUserInterface - type: Drink solution: beaker - type: Appearance @@ -117,7 +120,8 @@ solutions: beaker: maxVol: 50 - canMix: true + - type: MixableSolution + solution: beaker - type: FitsInDispenser solution: beaker - type: RefillableSolution @@ -132,10 +136,12 @@ solution: beaker - type: SolutionTransfer canChangeTransferAmount: true + - type: SolutionItemStatus + solution: beaker - type: UserInterface interfaces: - - key: enum.TransferAmountUiKey.Key - type: TransferAmountBoundUserInterface + enum.TransferAmountUiKey.Key: + type: TransferAmountBoundUserInterface - type: Drink solution: beaker - type: Appearance @@ -158,6 +164,9 @@ solution: beaker - type: StaticPrice price: 10 + - type: PhysicalComposition + materialComposition: + Glass: 50 - type: SolutionContainerVisuals maxFillLevels: 6 fillBaseName: beaker @@ -200,13 +209,15 @@ solutions: beaker: maxVol: 100 - canMix: true - type: Appearance - type: SolutionContainerVisuals maxFillLevels: 6 fillBaseName: beakerlarge inHandsMaxFillLevels: 4 inHandsFillBaseName: -fill- + - type: PhysicalComposition + materialComposition: + Glass: 100 - type: StaticPrice price: 20 @@ -227,6 +238,10 @@ beaker: maxVol: 60 canReact: false + - type: ReverseEngineering # Delta + difficulty: 3 + recipes: + - CryostasisBeaker - type: entity name: bluespace beaker @@ -244,7 +259,10 @@ solutions: beaker: maxVol: 1000 - canMix: true + - type: ReverseEngineering + difficulty: 4 + recipes: + - BluespaceBeaker - type: entity name: dropper @@ -274,8 +292,8 @@ solution: dropper - type: UserInterface interfaces: - - key: enum.TransferAmountUiKey.Key - type: TransferAmountBoundUserInterface + enum.TransferAmountUiKey.Key: + type: TransferAmountBoundUserInterface - type: Spillable solution: injector - type: Item @@ -401,6 +419,10 @@ tags: - Syringe - Trash + - type: ReverseEngineering # Delta + difficulty: 4 + recipes: + - SyringeBluespace - type: entity id: SyringeCryostasis @@ -432,7 +454,10 @@ tags: - Syringe - Trash - + - type: ReverseEngineering # Delta + difficulty: 3 + recipes: + - SyringeCryostasis - type: entity name: pill diff --git a/Resources/Prototypes/Entities/Objects/Specific/syndicate.yml b/Resources/Prototypes/Entities/Objects/Specific/syndicate.yml index 883f66816d..4f76006684 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/syndicate.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/syndicate.yml @@ -63,8 +63,8 @@ heldPrefix: radio - type: UserInterface interfaces: - - key: enum.StoreUiKey.Key - type: StoreBoundUserInterface + enum.StoreUiKey.Key: + type: StoreBoundUserInterface - type: ActivatableUI key: enum.StoreUiKey.Key - type: Store diff --git a/Resources/Prototypes/Entities/Objects/Tools/access_configurator.yml b/Resources/Prototypes/Entities/Objects/Tools/access_configurator.yml index 9504ec144a..9c8102979a 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/access_configurator.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/access_configurator.yml @@ -75,12 +75,12 @@ doAfter: 0.5 - type: UserInterface interfaces: - - key: enum.AccessOverriderUiKey.Key + enum.AccessOverriderUiKey.Key: type: AccessOverriderBoundUserInterface - type: ActivatableUI key: enum.AccessOverriderUiKey.Key requireHands: true - closeOnHandDeselect: false + requireActiveHand: false singleUser: true - type: ItemSlots - type: ContainerContainer diff --git a/Resources/Prototypes/Entities/Objects/Tools/appraisal.yml b/Resources/Prototypes/Entities/Objects/Tools/appraisal.yml index 4ec8d28857..3edf26ea78 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/appraisal.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/appraisal.yml @@ -17,6 +17,9 @@ quickEquip: false slots: - Belt + - type: PhysicalComposition + materialComposition: + Steel: 250 - type: Tag tags: - AppraisalTool diff --git a/Resources/Prototypes/Entities/Objects/Tools/bucket.yml b/Resources/Prototypes/Entities/Objects/Tools/bucket.yml index 496fd231b8..77c5e54897 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/bucket.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/bucket.yml @@ -26,6 +26,8 @@ solutions: bucket: maxVol: 250 + - type: MixableSolution + solution: bucket - type: SolutionTransfer transferAmount: 100 maxTransferAmount: 100 @@ -33,8 +35,8 @@ canChangeTransferAmount: true - type: UserInterface interfaces: - - key: enum.TransferAmountUiKey.Key - type: TransferAmountBoundUserInterface + enum.TransferAmountUiKey.Key: + type: TransferAmountBoundUserInterface - type: MeleeWeapon soundNoDamage: path: "/Audio/Effects/Fluids/splat.ogg" @@ -49,6 +51,8 @@ solution: bucket - type: DrainableSolution solution: bucket + - type: SolutionItemStatus + solution: bucket - type: Appearance - type: SolutionContainerVisuals maxFillLevels: 3 diff --git a/Resources/Prototypes/Entities/Objects/Tools/cable_coils.yml b/Resources/Prototypes/Entities/Objects/Tools/cable_coils.yml index 8f9fb382f5..f06539aa17 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/cable_coils.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/cable_coils.yml @@ -26,6 +26,20 @@ price: 0 - type: StackPrice price: 1 + - type: PhysicalComposition + materialComposition: + Steel: 15 + #Same as Ointment but divided by 5 and 3 because StackPrice needs to be 1 - Estacao Pirata IPCs + #1 Ointment = -50 damage of those types + #1 Cable ~= -50 (-49.8) damage of those types + - type: Healing + delay: 0.6 + damageContainers: + - Silicon + damage: + types: # these are all split across multiple types + Heat: -1.66 + Shock: -1.66 - type: entity id: CableHVStack diff --git a/Resources/Prototypes/Entities/Objects/Tools/cowtools.yml b/Resources/Prototypes/Entities/Objects/Tools/cowtools.yml index 977a8a931b..87959ebef3 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/cowtools.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/cowtools.yml @@ -128,11 +128,6 @@ sprite: Objects/Tools/Cowtools/cowelder.rsi - type: Tool speed: 0.05 - - type: Welder - litMeleeDamageBonus: - types: # When lit, negate standard melee damage and replace with heat - Heat: 0.5 - Blunt: -5 - type: entity name: milkalyzer @@ -147,8 +142,8 @@ - type: GasAnalyzer - type: UserInterface interfaces: - - key: enum.GasAnalyzerUiKey.Key - type: GasAnalyzerBoundUserInterface + enum.GasAnalyzerUiKey.Key: + type: GasAnalyzerBoundUserInterface - type: Appearance - type: entity diff --git a/Resources/Prototypes/Entities/Objects/Tools/decoys.yml b/Resources/Prototypes/Entities/Objects/Tools/decoys.yml index 0074fe517d..d399b77d02 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/decoys.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/decoys.yml @@ -48,6 +48,15 @@ - type: ContainerContainer containers: cell_slot: !type:ContainerSlot + - type: HandheldLight + addPrefix: false + - type: PointLight # Just like the real thing... + autoRot: true + radius: 1.5 + energy: 2.3 + offset: 0, -1 + color: green + netsync: false - type: Tag tags: - Balloon @@ -59,8 +68,6 @@ name: operative balloon description: Upon closer inspection, this Syndicate operative is actually a balloon. components: - - type: HandheldLight - addPrefix: false - type: Sprite sprite: Objects/Tools/Decoys/operative_decoy.rsi layers: @@ -69,13 +76,6 @@ - state: folded map: ["foldedLayer"] visible: false - - type: PointLight # Just like the real thing... - autoRot: true - radius: 1.5 - energy: 2.3 - offset: 0, -1 - color: green - netsync: false - type: entity parent: BaseDecoy @@ -83,8 +83,6 @@ name: agent balloon description: Upon closer inspection, this Syndicate agent is actually a balloon. components: - - type: HandheldLight - addPrefix: false - type: Sprite sprite: Objects/Tools/Decoys/agent_decoy.rsi layers: @@ -93,12 +91,6 @@ - state: folded map: ["foldedLayer"] visible: false - - type: PointLight - radius: 1.5 - energy: 2.3 - offset: 0, -1 - color: green - netsync: false - type: entity parent: BaseDecoy @@ -106,8 +98,6 @@ name: elite operative balloon description: Upon closer inspection, this Syndicate elite operative is actually a balloon. components: - - type: HandheldLight - addPrefix: false - type: Sprite sprite: Objects/Tools/Decoys/elite_decoy.rsi layers: @@ -117,11 +107,7 @@ map: ["foldedLayer"] visible: false - type: PointLight - radius: 1.5 - energy: 2.3 - offset: 0, -1 color: red - netsync: false - type: entity parent: BaseDecoy @@ -129,8 +115,6 @@ name: juggernaut balloon description: Upon closer inspection, this Syndicate juggernaut is actually a balloon. components: - - type: HandheldLight - addPrefix: false - type: Sprite sprite: Objects/Tools/Decoys/juggernaut_decoy.rsi layers: @@ -139,11 +123,6 @@ - state: folded map: ["foldedLayer"] visible: false - - type: PointLight # Inbuilt flashlight, I guess? - radius: 1.5 - energy: 2.3 - offset: 0, -1 - netsync: false - type: entity parent: BaseDecoy @@ -151,8 +130,6 @@ name: commander balloon description: Upon closer inspection, this Syndicate commander is actually a balloon. components: - - type: HandheldLight - addPrefix: false - type: Sprite sprite: Objects/Tools/Decoys/commander_decoy.rsi layers: @@ -161,9 +138,3 @@ - state: folded map: ["foldedLayer"] visible: false - - type: PointLight - radius: 1.5 - energy: 2.3 - offset: 0, -1 - color: green - netsync: false diff --git a/Resources/Prototypes/Entities/Objects/Tools/flare.yml b/Resources/Prototypes/Entities/Objects/Tools/flare.yml index dbdb935b91..fdf5314863 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/flare.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/flare.yml @@ -20,7 +20,13 @@ fadeOutBehaviourID: fade_out litSound: path: /Audio/Items/Flare/flare_on.ogg - loopedSound: /Audio/Items/Flare/flare_burn.ogg + loopedSound: + path: /Audio/Items/Flare/flare_burn.ogg + params: + loop: true + volume: -10 + maxDistance: 5 + - type: Sprite sprite: Objects/Misc/flare.rsi layers: diff --git a/Resources/Prototypes/Entities/Objects/Tools/flashlights.yml b/Resources/Prototypes/Entities/Objects/Tools/flashlights.yml index 2b75a7e3dd..cbb5dfee0a 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/flashlights.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/flashlights.yml @@ -65,6 +65,7 @@ - type: Item sprite: Objects/Tools/flashlight.rsi storedRotation: -90 + - type: EtherealLight - type: PointLight enabled: false mask: /Textures/Effects/LightMasks/cone.png @@ -120,9 +121,8 @@ damage: types: Blunt: 6.5 - bluntStaminaDamageFactor: 1.5 + bluntStaminaDamageFactor: 2 heavyRateModifier: 0.9 - heavyStaminaCost: 5 maxTargets: 1 angle: 20 soundHit: diff --git a/Resources/Prototypes/Entities/Objects/Tools/gas_tanks.yml b/Resources/Prototypes/Entities/Objects/Tools/gas_tanks.yml index f739de251c..8590a32a6a 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/gas_tanks.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/gas_tanks.yml @@ -1,4 +1,4 @@ -- type: entity +- type: entity abstract: true parent: BaseItem id: GasTankBase @@ -19,8 +19,8 @@ key: enum.SharedGasTankUiKey.Key - type: UserInterface interfaces: - - key: enum.SharedGasTankUiKey.Key - type: GasTankBoundUserInterface + enum.SharedGasTankUiKey.Key: + type: GasTankBoundUserInterface - type: GasTank outputPressure: 21.3 air: @@ -41,9 +41,9 @@ bluntStaminaDamageFactor: 2.5 heavyRateModifier: 0.8 heavyDamageBaseModifier: 1.5 - heavyStaminaCost: 15 - maxTargets: 1 - angle: 140 + heavyStaminaCost: 10 + maxTargets: 3 + angle: 100 - type: PhysicalComposition materialComposition: Steel: 185 @@ -245,3 +245,6 @@ outputPressure: 101.3 - type: Clothing sprite: Objects/Tanks/plasma.rsi + slots: + - Belt + - suitStorage diff --git a/Resources/Prototypes/Entities/Objects/Tools/gps.yml b/Resources/Prototypes/Entities/Objects/Tools/gps.yml index 8ae34573ff..becbb638f4 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/gps.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/gps.yml @@ -12,6 +12,10 @@ - type: Item sprite: Objects/Devices/gps.rsi - type: HandheldGPS + - type: PhysicalComposition + materialComposition: + Steel: 400 + Glass: 150 - type: Tag tags: - GPS diff --git a/Resources/Prototypes/Entities/Objects/Tools/hand_labeler.yml b/Resources/Prototypes/Entities/Objects/Tools/hand_labeler.yml index 7e88395336..c97caad00f 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/hand_labeler.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/hand_labeler.yml @@ -17,7 +17,7 @@ inHandsOnly: true - type: UserInterface interfaces: - - key: enum.HandLabelerUiKey.Key + enum.HandLabelerUiKey.Key: type: HandLabelerBoundUserInterface - type: HandLabeler whitelist: diff --git a/Resources/Prototypes/Entities/Objects/Tools/handheld_mass_scanner.yml b/Resources/Prototypes/Entities/Objects/Tools/handheld_mass_scanner.yml index 5647737219..38b1f7cda9 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/handheld_mass_scanner.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/handheld_mass_scanner.yml @@ -34,10 +34,14 @@ singleUser: true - type: UserInterface interfaces: - - key: enum.RadarConsoleUiKey.Key + enum.RadarConsoleUiKey.Key: type: RadarConsoleBoundUserInterface - type: StaticPrice price: 150 + - type: ReverseEngineering # Delta + difficulty: 3 + recipes: + - HandHeldMassScanner - type: entity id: HandHeldMassScannerEmpty @@ -69,4 +73,4 @@ name: power-cell-slot-component-slot-name-default startingItem: PowerCellMicroreactor disableEject: true - swap: false \ No newline at end of file + swap: false diff --git a/Resources/Prototypes/Entities/Objects/Tools/jammer.yml b/Resources/Prototypes/Entities/Objects/Tools/jammer.yml index beb3695627..b456a23f1f 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/jammer.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/jammer.yml @@ -6,8 +6,26 @@ components: - type: Sprite sprite: Objects/Devices/jammer.rsi - state: jammer + layers: + - state: jammer + - state: jammer_high_charge + map: ["enum.RadioJammerLayers.LED"] + shader: unshaded + visible: false - type: RadioJammer + settings: + - wattage: 1 + range: 2.5 + message: radio-jammer-component-set-message-low + name: radio-jammer-component-setting-low + - wattage: 2 + range: 6 + message: radio-jammer-component-set-message-medium + name: radio-jammer-component-setting-medium + - wattage: 12 + range: 12 + message: radio-jammer-component-set-message-high + name: radio-jammer-component-setting-high - type: PowerCellSlot cellSlotId: cell_slot - type: ContainerContainer @@ -18,3 +36,15 @@ cell_slot: name: power-cell-slot-component-slot-name-default startingItem: PowerCellMedium + - type: Appearance + - type: GenericVisualizer + visuals: + enum.RadioJammerVisuals.LEDOn: + RadioJammerLayers.LED: + True: { visible: True } + False: { visible: False } + enum.RadioJammerVisuals.ChargeLevel: + RadioJammerLayers.LED: + Low: {state: jammer_low_charge} + Medium: {state: jammer_medium_charge} + High: {state: jammer_high_charge} diff --git a/Resources/Prototypes/Entities/Objects/Tools/jaws_of_life.yml b/Resources/Prototypes/Entities/Objects/Tools/jaws_of_life.yml index 36d2f1308f..936e832224 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/jaws_of_life.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/jaws_of_life.yml @@ -44,8 +44,8 @@ changeSound: /Audio/Items/change_jaws.ogg - type: MeleeWeapon wideAnimationRotation: 90 - attackRate: 0.75 - range: 1.75 + attackRate: 0.85 + range: 1.65 damage: types: Blunt: 10 @@ -53,11 +53,15 @@ bluntStaminaDamageFactor: 2.0 heavyRateModifier: 0.8 heavyDamageBaseModifier: 1.5 - heavyStaminaCost: 10 + heavyStaminaCost: 5 maxTargets: 1 angle: 20 soundHit: collection: MetalThud + - type: ReverseEngineering # Delta + difficulty: 3 + recipes: + - JawsOfLife - type: entity name: syndicate jaws of life diff --git a/Resources/Prototypes/Entities/Objects/Tools/jetpacks.yml b/Resources/Prototypes/Entities/Objects/Tools/jetpacks.yml index a4c103847f..bd05b8d0f3 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/jetpacks.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/jetpacks.yml @@ -40,7 +40,7 @@ size: Huge - type: UserInterface interfaces: - - key: enum.SharedGasTankUiKey.Key + enum.SharedGasTankUiKey.Key: type: GasTankBoundUserInterface - type: Clothing sprite: Objects/Tanks/Jetpacks/blue.rsi diff --git a/Resources/Prototypes/Entities/Objects/Tools/penlight.yml b/Resources/Prototypes/Entities/Objects/Tools/penlight.yml index 7f8a9b262c..a7fb3a3f1c 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/penlight.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/penlight.yml @@ -28,8 +28,8 @@ - type: Appearance - type: UserInterface interfaces: - - key: enum.PenLightUiKey.Key - type: PenLightBoundUserInterface + enum.PenLightUiKey.Key: + type: PenLightBoundUserInterface - type: ToggleableLightVisuals - type: ContainerContainer containers: diff --git a/Resources/Prototypes/Entities/Objects/Tools/spray_painter.yml b/Resources/Prototypes/Entities/Objects/Tools/spray_painter.yml index 903b8d3f90..ad31a6ec02 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/spray_painter.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/spray_painter.yml @@ -13,8 +13,8 @@ key: enum.SprayPainterUiKey.Key - type: UserInterface interfaces: - - key: enum.SprayPainterUiKey.Key - type: SprayPainterBoundUserInterface + enum.SprayPainterUiKey.Key: + type: SprayPainterBoundUserInterface - type: SprayPainter colorPalette: red: '#FF1212FF' diff --git a/Resources/Prototypes/Entities/Objects/Tools/t-ray.yml b/Resources/Prototypes/Entities/Objects/Tools/t-ray.yml index c467974b74..8872b98c18 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/t-ray.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/t-ray.yml @@ -19,5 +19,9 @@ base: On: { state: tray-on } Off: { state: tray-off } + - type: PhysicalComposition + materialComposition: + Steel: 400 + Glass: 150 - type: StaticPrice price: 60 diff --git a/Resources/Prototypes/Entities/Objects/Tools/toolbox.yml b/Resources/Prototypes/Entities/Objects/Tools/toolbox.yml index 6702ae39d6..bfbb0573ca 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/toolbox.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/toolbox.yml @@ -28,7 +28,7 @@ Blunt: 9 bluntStaminaDamageFactor: 2.0 heavyRateModifier: 0.8 - heavyStaminaCost: 10 + heavyStaminaCost: 7.5 angle: 80.5 soundHit: path: "/Audio/Weapons/smash.ogg" @@ -181,5 +181,5 @@ key: enum.ThiefBackpackUIKey.Key - type: UserInterface interfaces: - - key: enum.ThiefBackpackUIKey.Key + enum.ThiefBackpackUIKey.Key: type: ThiefBackpackBoundUserInterface diff --git a/Resources/Prototypes/Entities/Objects/Tools/tools.yml b/Resources/Prototypes/Entities/Objects/Tools/tools.yml index a6926f1d8c..1f1b67349f 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/tools.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/tools.yml @@ -38,9 +38,7 @@ Blunt: 6.5 heavyRateModifier: 0.9 heavyDamageBaseModifier: 1.2 - heavyStaminaCost: 5 maxTargets: 4 - angle: 60 soundHit: path: "/Audio/Items/wirecutter.ogg" - type: Tool @@ -103,7 +101,6 @@ Piercing: 6 heavyRateModifier: 0.75 heavyDamageBaseModifier: 1.5 - heavyStaminaCost: 5 maxTargets: 1 angle: 20 soundHit: @@ -165,7 +162,6 @@ bluntStaminaDamageFactor: 1.5 heavyRateModifier: 0.75 heavyDamageBaseModifier: 1.75 - heavyStaminaCost: 5 angle: 100 soundHit: collection: MetalThud @@ -279,7 +275,6 @@ Shock: 2 heavyRateModifier: 0.9 heavyDamageBaseModifier: 1.2 - heavyStaminaCost: 5 maxTargets: 1 angle: 20 - type: Item @@ -298,11 +293,11 @@ inHandsOnly: true - type: UserInterface interfaces: - - key: enum.NetworkConfiguratorUiKey.List + enum.NetworkConfiguratorUiKey.List: type: NetworkConfiguratorBoundUserInterface - - key: enum.NetworkConfiguratorUiKey.Configure + enum.NetworkConfiguratorUiKey.Configure: type: NetworkConfiguratorBoundUserInterface - - key: enum.NetworkConfiguratorUiKey.Link + enum.NetworkConfiguratorUiKey.Link: type: NetworkConfiguratorBoundUserInterface - type: Tag tags: @@ -354,11 +349,11 @@ - DoorElectronicsConfigurator - type: UserInterface interfaces: - - key: enum.NetworkConfiguratorUiKey.List + enum.NetworkConfiguratorUiKey.List: type: NetworkConfiguratorBoundUserInterface - - key: enum.NetworkConfiguratorUiKey.Configure + enum.NetworkConfiguratorUiKey.Configure: type: NetworkConfiguratorBoundUserInterface - - key: enum.NetworkConfiguratorUiKey.Link + enum.NetworkConfiguratorUiKey.Link: type: NetworkConfiguratorBoundUserInterface - type: StaticPrice price: 56 @@ -413,10 +408,6 @@ Plastic: 100 # - type: DynamicPrice # price: 100 - - type: ReverseEngineering # Nyano - difficulty: 2 - recipes: - - PowerDrill - type: StaticPrice price: 100 - type: MeleeWeapon @@ -428,11 +419,14 @@ Piercing: 8 heavyRateModifier: 0.9 heavyDamageBaseModifier: 1.2 - heavyStaminaCost: 5 maxTargets: 1 angle: 20 soundHit: path: "/Audio/Items/drill_hit.ogg" + - type: ReverseEngineering # Nyano + difficulty: 2 + recipes: + - PowerDrill - type: entity id: RCD @@ -478,15 +472,19 @@ - type: PhysicalComposition materialComposition: Steel: 600 - Plastic: 100 + Plastic: 150 - type: StaticPrice price: 100 - type: UserInterface interfaces: - - key: enum.RcdUiKey.Key - type: RCDMenuBoundUserInterface + enum.RcdUiKey.Key: + type: RCDMenuBoundUserInterface - type: ActivatableUI key: enum.RcdUiKey.Key + - type: ReverseEngineering # Nyano + difficulty: 3 + recipes: + - RCD - type: entity id: RCDEmpty @@ -548,7 +546,7 @@ heldPrefix: ammo - type: PhysicalComposition materialComposition: - Steel: 100 + Steel: 300 Plastic: 100 - type: StaticPrice price: 60 @@ -637,14 +635,14 @@ - type: MeleeWeapon wideAnimationRotation: 45 attackRate: 0.8 - range: 2.0 + range: 1.75 damage: types: Blunt: 8 bluntStaminaDamageFactor: 1.5 heavyRateModifier: 0.9 heavyDamageBaseModifier: 1.5 - heavyStaminaCost: 10 + heavyStaminaCost: 7.5 angle: 100 soundHit: collection: MetalThud @@ -692,8 +690,7 @@ bluntStaminaDamageFactor: 2.0 heavyRateModifier: 0.8 heavyDamageBaseModifier: 1.5 - heavyStaminaCost: 5 - maxTargets: 1 + maxTargets: 2 angle: 20 soundHit: collection: MetalThud diff --git a/Resources/Prototypes/Entities/Objects/Tools/welders.yml b/Resources/Prototypes/Entities/Objects/Tools/welders.yml index 8214ec56f3..c501237b4c 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/welders.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/welders.yml @@ -106,6 +106,14 @@ price: 40 - type: IgnitionSource temperature: 700 + - type: WeldingHealing # Same as Brutepack - Estacao Pirata IPCs + damageContainers: + - Silicon + damage: + types: + Blunt: -15 + Piercing: -15 + Slash: -15 - type: entity name: industrial welding tool diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Boxes/light_rifle.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Boxes/light_rifle.yml index 22eeeff859..edca311cb1 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Boxes/light_rifle.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Boxes/light_rifle.yml @@ -10,7 +10,7 @@ tags: - CartridgeLightRifle proto: CartridgeLightRifle - capacity: 50 + capacity: 60 - type: Item size: Small - type: ContainerContainer diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Boxes/magnum.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Boxes/magnum.yml index c304f0af11..3f71f93440 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Boxes/magnum.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Boxes/magnum.yml @@ -9,7 +9,7 @@ tags: - CartridgeMagnum proto: CartridgeMagnum - capacity: 60 + capacity: 12 - type: Item size: Small - type: ContainerContainer diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Boxes/rifle.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Boxes/rifle.yml index 8e6c79dfe5..03d6e33799 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Boxes/rifle.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Boxes/rifle.yml @@ -9,7 +9,7 @@ tags: - CartridgeRifle proto: CartridgeRifle - capacity: 60 + capacity: 50 - type: Item size: Small - type: ContainerContainer diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Boxes/shotgun.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Boxes/shotgun.yml index 831c3c3352..63ad52c032 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Boxes/shotgun.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Boxes/shotgun.yml @@ -21,7 +21,7 @@ whitelist: tags: - ShellShotgun - capacity: 12 + capacity: 16 # Shotgun Shells - type: entity @@ -89,6 +89,19 @@ - state: boxwide - state: shellincendiary +- type: entity + name: shotgun uranium cartridges dispenser + parent: AmmoProviderShotgunShell + id: BoxShotgunUranium + description: A dispenser box full of uranium cartridges, designed for riot shotguns. + components: + - type: BallisticAmmoProvider + proto: ShellShotgunUranium + - type: Sprite + layers: + - state: boxwide + - state: shelluranium + - type: entity name: shotgun practice cartridges dispenser parent: AmmoProviderShotgunShell @@ -113,4 +126,4 @@ - type: Sprite layers: - state: boxwide - - state: shellslug \ No newline at end of file + - state: shellslug diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/light_rifle.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/light_rifle.yml index d420a5c3d8..296af23af5 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/light_rifle.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/light_rifle.yml @@ -68,6 +68,19 @@ - state: mag-1 map: ["enum.GunVisualLayers.Mag"] +- type: entity + id: MagazineLightRifleEmpty + name: "magazine (.30 rifle any)" + suffix: empty + parent: MagazineLightRifle + components: + - type: BallisticAmmoProvider + proto: null + - type: Sprite + layers: + - state: base + map: ["enum.GunVisualLayers.Base"] + - type: entity id: MagazineLightRiflePractice name: "magazine (.30 rifle practice)" @@ -110,6 +123,14 @@ - state: mag-1 map: ["enum.GunVisualLayers.Mag"] +- type: entity + id: MagazineLightRifleIncendiary + name: "magazine (.30 rifle incendiary)" + parent: MagazineLightRifle + components: + - type: BallisticAmmoProvider + proto: CartridgeLightRifleIncendiary + - type: entity id: MagazineLightRifleMaxim name: "pan magazine (.30 rifle)" diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/magnum.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/magnum.yml index 6a1cc041de..fc506ec594 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/magnum.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/magnum.yml @@ -47,6 +47,19 @@ zeroVisible: false - type: Appearance +- type: entity + id: MagazineMagnumEmpty + name: pistol magazine (.45 magnum any) + suffix: empty + parent: BaseMagazineMagnum + components: + - type: BallisticAmmoProvider + proto: null + - type: Sprite + layers: + - state: base + map: ["enum.GunVisualLayers.Base"] + - type: entity id: MagazineMagnum name: pistol magazine (.45 magnum) @@ -117,6 +130,19 @@ - state: mag-1 map: ["enum.GunVisualLayers.Mag"] +- type: entity + id: MagazineMagnumSubMachineGunEmpty + name: "Vector magazine (.45 magnum any)" + suffix: empty + parent: BaseMagazineMagnumSubMachineGun + components: + - type: BallisticAmmoProvider + proto: null + - type: Sprite + layers: + - state: base + map: ["enum.GunVisualLayers.Base"] + - type: entity id: MagazineMagnumSubMachineGun name: "Vector magazine (.45 magnum)" diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/pistol.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/pistol.yml index 679acd299c..bde2731937 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/pistol.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/pistol.yml @@ -128,6 +128,14 @@ containers: ballistic-ammo: !type:Container +- type: entity + id: MagazinePistolSubMachineGunTopMountedEmpty + name: WT550 magazine (.35 auto top-mounted any) + parent: MagazinePistolSubMachineGunTopMounted + components: + - type: BallisticAmmoProvider + proto: null + - type: entity id: MagazinePistol name: pistol magazine (.35 auto) @@ -142,6 +150,28 @@ - state: mag-1 map: ["enum.GunVisualLayers.Mag"] +- type: entity + id: MagazinePistolEmpty + name: pistol magazine (.35 auto any) + suffix: empty + parent: MagazinePistol + components: + - type: BallisticAmmoProvider + proto: null + - type: Sprite + layers: + - state: base + map: ["enum.GunVisualLayers.Base"] + + +- type: entity + id: MagazinePistolIncendiary + name: pistol magazine (.35 auto incendiary) + parent: MagazinePistol + components: + - type: BallisticAmmoProvider + proto: CartridgePistolIncendiary + - type: entity id: MagazinePistolPractice name: pistol magazine (.35 auto practice) @@ -170,6 +200,33 @@ - state: mag-1 map: ["enum.GunVisualLayers.Mag"] +- type: entity + id: MagazinePistolUranium + name: pistol magazine (.35 auto uranium) + parent: BaseMagazinePistol + components: + - type: BallisticAmmoProvider + proto: CartridgePistolUranium + - type: Sprite + layers: + - state: uranium + map: ["enum.GunVisualLayers.Base"] + - state: mag-1 + map: ["enum.GunVisualLayers.Mag"] + +- type: entity + id: MagazinePistolHighCapacityEmpty + name: machine pistol magazine (.35 auto any) + suffix: empty + parent: BaseMagazinePistolHighCapacity + components: + - type: BallisticAmmoProvider + proto: null + - type: Sprite + layers: + - state: base + map: ["enum.GunVisualLayers.Base"] + - type: entity id: MagazinePistolHighCapacity name: machine pistol magazine (.35 auto) @@ -232,6 +289,19 @@ - state: mag-1 map: ["enum.GunVisualLayers.Mag"] +- type: entity + id: MagazinePistolSubMachineGunEmpty + name: SMG magazine (.35 auto any) + suffix: empty + parent: BaseMagazinePistolSubMachineGun + components: + - type: BallisticAmmoProvider + proto: null + - type: Sprite + layers: + - state: base + map: ["enum.GunVisualLayers.Base"] + - type: entity id: MagazinePistolSubMachineGunPractice name: SMG magazine (.35 auto practice) diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/rifle.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/rifle.yml index 5ba57dce4e..7d8aeb9346 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/rifle.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/rifle.yml @@ -47,6 +47,27 @@ - state: mag-1 map: ["enum.GunVisualLayers.Mag"] +- type: entity + id: MagazineRifleEmpty + name: "magazine (.20 rifle any)" + suffix: empty + parent: MagazineRifle + components: + - type: BallisticAmmoProvider + proto: null + - type: Sprite + layers: + - state: base + map: ["enum.GunVisualLayers.Base"] + +- type: entity + id: MagazineRifleIncendiary + name: "magazine (.20 rifle incendiary)" + parent: MagazineRifle + components: + - type: BallisticAmmoProvider + proto: CartridgeRifleIncendiary + - type: entity id: MagazineRiflePractice name: "magazine (.20 rifle practice)" diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/shotgun.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/shotgun.yml index cbe9bbfbe9..5b0b16bf4b 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/shotgun.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/shotgun.yml @@ -33,6 +33,15 @@ zeroVisible: false - type: Appearance +- type: entity + id: MagazineShotgunEmpty + name: ammo drum (.50 shells any) + suffix: empty + parent: BaseMagazineShotgun + components: + - type: BallisticAmmoProvider + proto: null + - type: entity id: MagazineShotgun name: ammo drum (.50 pellet) diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/SpeedLoaders/magnum.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/SpeedLoaders/magnum.yml index b06ca64d8f..d8e2c908b6 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/SpeedLoaders/magnum.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/SpeedLoaders/magnum.yml @@ -39,6 +39,27 @@ zeroVisible: false - type: Appearance +- type: entity + id: SpeedLoaderMagnumEmpty + name: "speed loader (.45 magnum any)" + parent: SpeedLoaderMagnum + components: + - type: BallisticAmmoProvider + proto: null + - type: Sprite + sprite: Objects/Weapons/Guns/Ammunition/SpeedLoaders/Magnum/magnum_speed_loader.rsi + layers: + - state: base + map: [ "enum.GunVisualLayers.Base" ] + +- type: entity + id: SpeedLoaderMagnumIncendiary + name: "speed loader (.45 magnum incendiary)" + parent: SpeedLoaderMagnum + components: + - type: BallisticAmmoProvider + proto: CartridgeMagnumIncendiary + - type: entity id: SpeedLoaderMagnumPractice name: "speed loader (.45 magnum practice)" diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Basic/base_wieldable.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Basic/base_wieldable.yml new file mode 100644 index 0000000000..690658d0c7 --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Basic/base_wieldable.yml @@ -0,0 +1,13 @@ +# A basic inheritable template for a gun that is wieldable and has the standard inaccuracy. +- type: entity + id: BaseGunWieldable + abstract: true + components: + - type: Wieldable + unwieldOnUse: false + - type: GunWieldBonus + minAngle: -20 + maxAngle: -30 + - type: Gun + minAngle: 21 + maxAngle: 32 diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Basic/watergun.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Basic/watergun.yml index e925bdab87..c96a1522d2 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Basic/watergun.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Basic/watergun.yml @@ -34,8 +34,8 @@ canChangeTransferAmount: true - type: UserInterface interfaces: - - key: enum.TransferAmountUiKey.Key - type: TransferAmountBoundUserInterface + enum.TransferAmountUiKey.Key: + type: TransferAmountBoundUserInterface - type: DrawableSolution solution: chamber - type: RefillableSolution diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml index ec0a0a148b..8f062a8620 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml @@ -203,7 +203,7 @@ - type: entity name: laser rifle - parent: BaseWeaponBattery + parent: [BaseWeaponBattery, BaseGunWieldable] id: WeaponLaserCarbine description: Favoured by Nanotrasen Security for being cheap and easy to use. components: @@ -268,9 +268,18 @@ maxCharge: 2000 startingCharge: 2000 +- type: entity + name: antique pulse pistol + parent: WeaponPulsePistol + id: WeaponPulsePistolHoS + description: One of the many weapons belonging to the Head of Security's private collection. This pistol is engraved with the words, "Forgive Us, Mother Sol'" + components: + - type: StealTarget + stealGroup: HoSAntiqueWeapon + - type: entity name: pulse carbine - parent: BaseWeaponBattery + parent: [BaseWeaponBattery, BaseGunWieldable] id: WeaponPulseCarbine description: A high tech energy carbine favoured by the NT-ERT operatives. components: @@ -301,7 +310,7 @@ - type: entity name: pulse rifle - parent: BaseWeaponBattery + parent: [BaseWeaponBattery, BaseGunWieldable] id: WeaponPulseRifle description: A weapon that is almost as infamous as its users. components: @@ -328,7 +337,7 @@ - type: entity name: laser cannon - parent: BaseWeaponBattery + parent: [BaseWeaponBattery, BaseGunWieldable] id: WeaponLaserCannon description: A heavy duty, high powered laser weapon. components: @@ -383,7 +392,7 @@ - type: entity name: x-ray cannon - parent: BaseWeaponBattery + parent: [BaseWeaponBattery, BaseGunWieldable] id: WeaponXrayCannon description: An experimental weapon that uses concentrated x-ray energy against its target. components: diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Bow/bow.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Bow/bow.yml index db98c6eabe..32b4fc6075 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Bow/bow.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Bow/bow.yml @@ -12,7 +12,8 @@ - type: Clothing quickEquip: false slots: - - Back + - Back + - suitStorage - type: Wieldable wieldSound: path: /Audio/Items/bow_pull.ogg diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/LMGs/lmgs.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/LMGs/lmgs.yml index 49b2eeaada..f90cbb6e60 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/LMGs/lmgs.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/LMGs/lmgs.yml @@ -14,6 +14,8 @@ slots: - Back - type: Wieldable + unwieldOnUse: false + - type: GunRequiresWield - type: GunWieldBonus minAngle: -20 maxAngle: -20 diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Launchers/launchers.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Launchers/launchers.yml index 594ffb4d4d..be4ea534d7 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Launchers/launchers.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Launchers/launchers.yml @@ -22,7 +22,7 @@ - type: entity name: china lake - parent: BaseWeaponLauncher + parent: [BaseWeaponLauncher, BaseGunWieldable] id: WeaponLauncherChinaLake description: PLOOP components: @@ -375,3 +375,29 @@ tags: - CartridgeRocket proto: ImmovableRodSlow + +# Frontier mail RPDS +- type: entity + name: mail RPDS + parent: WeaponLauncherChinaLake + id: WeaponMailLake + description: Rap(b?)id Parcel Delivery System + components: + - type: Sprite + sprite: Objects/Weapons/Guns/Launchers/mail.rsi + layers: + - state: icon + map: ["enum.GunVisualLayers.Base"] + - type: Clothing + sprite: Objects/Weapons/Guns/Launchers/mail.rsi + quickEquip: false + slots: + - Back + - Belt + - suitStorage + - type: BallisticAmmoProvider + proto: null + whitelist: + tags: + - MailCapsule + capacity: 4 diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Pistols/pistols.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Pistols/pistols.yml index 410664e46e..c4b2082f38 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Pistols/pistols.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Pistols/pistols.yml @@ -65,17 +65,16 @@ - type: Appearance - type: StaticPrice price: 500 + - type: AmmoCounter - type: entity name: viper parent: BaseWeaponPistol id: WeaponPistolViper - description: A small, easily concealable, but somewhat underpowered gun, produced by a bulk arms manufacturer now defunct for over a century. Uses .35 auto ammo. # DeltaV - change description to reflect forced semi + description: A small, easily concealable, but somewhat underpowered gun, produced by a bulk arms manufacturer now defunct for over a century. Uses .35 auto ammo. components: - type: Sprite sprite: Objects/Weapons/Guns/Pistols/viper.rsi - availableModes: # DeltaV - force semi for balance - - SemiAuto - type: ItemSlots slots: gun_magazine: @@ -109,6 +108,48 @@ path: /Audio/Weapons/Guns/Gunshots/pistol.ogg fireOnDropChance: 0.3 +- type: entity + name: viper + parent: WeaponPistolViper + id: WeaponPistolViperSecurity + description: A small, easily concealable, but somewhat underpowered gun, produced by a bulk arms manufacturer now defunct for over a century. + Uses .35 auto ammo. The serial number on the handguard marks this gun as belonging to an NT Security Officer. + suffix: Security Loadouts + +- type: entity + id: WeaponPistolViperNonLethal + parent: WeaponPistolViper + suffix: Non-lethal + components: + - type: Sprite + sprite: Objects/Weapons/Guns/Pistols/vipernl.rsi + - type: ItemSlots + slots: + gun_magazine: + name: Magazine + startingItem: MagazinePistolRubber + insertSound: /Audio/Weapons/Guns/MagIn/pistol_magin.ogg + ejectSound: /Audio/Weapons/Guns/MagOut/pistol_magout.ogg + priority: 2 + whitelist: + tags: + - MagazinePistol + - MagazinePistolHighCapacity + gun_chamber: + name: Chamber + startingItem: CartridgePistolRubber + priority: 1 + whitelist: + tags: + - CartridgePistol + +- type: entity + id: WeaponPistolViperSecurityNonLethal + parent: WeaponPistolViperNonLethal + suffix: Non-lethal, SecurityLoadouts + description: A small, easily concealable, but somewhat underpowered gun, produced by a bulk arms manufacturer now defunct for over a century. + Uses .35 auto ammo. The serial number on the handguard marks this gun as belonging to an NT Security Officer. + - type: entity name: cobra parent: BaseWeaponPistol @@ -181,11 +222,21 @@ path: /Audio/Weapons/Guns/Gunshots/mk58.ogg fireOnDropChance: 0.5 +- type: entity + name: mk 58 + parent: WeaponPistolMk58 + id: WeaponPistolMk58Security + description: A cheap, ubiquitous sidearm, produced by a NanoTrasen subsidiary. Uses .35 auto ammo. + The serial number on the handguard marks this gun as belonging to an NT Security Officer. + suffix: Security Loadouts + - type: entity id: WeaponPistolMk58Nonlethal parent: WeaponPistolMk58 suffix: Non-lethal components: + - type: Sprite + sprite: Objects/Weapons/Guns/Pistols/mk58nl.rsi - type: ItemSlots slots: gun_magazine: @@ -205,6 +256,13 @@ tags: - CartridgePistol +- type: entity + id: WeaponPistolMk58SecurityNonlethal + parent: WeaponPistolMk58Nonlethal + suffix: Non-lethal, Security Loadouts + description: A cheap, ubiquitous sidearm, produced by a NanoTrasen subsidiary. Uses .35 auto ammo. + The serial number on the handguard marks this gun as belonging to an NT Security Officer. + - type: entity name: N1984 parent: BaseWeaponPistol @@ -253,9 +311,19 @@ - type: entity name: N1984 parent: WeaponPistolN1984 - id: WeaponPistolN1984Nonlethal + id: WeaponPistolN1984Security # the spaces in description are for formatting. + description: The sidearm of any self respecting officer. Comes in .45 magnum, the lord's caliber. + The serial number on the handguard marks this gun as belonging to an NT Security Officer. + suffix: Security Loadouts + +- type: entity + name: N1984 + parent: WeaponPistolN1984 + id: WeaponPistolN1984NonLethal suffix: Non-lethal components: + - type: Sprite + sprite: Objects/Weapons/Guns/Pistols/N1984nl.rsi - type: ItemSlots slots: gun_magazine: @@ -274,3 +342,11 @@ whitelist: tags: - CartridgeMagnum + +- type: entity + name: N1984 + parent: WeaponPistolN1984NonLethal + id: WeaponPistolN1984SecurityNonLethal + description: The sidearm of any self respecting officer. Comes in .45 magnum, the lord's caliber. + The serial number on the handguard marks this gun as belonging to an NT Security Officer. + suffix: Security Loadouts diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/arrows.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/arrows.yml index 52c5dc8a9d..6f925139fb 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/arrows.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/arrows.yml @@ -1,4 +1,4 @@ -- type: entity +- type: entity parent: BaseItem id: BaseArrow abstract: true @@ -35,7 +35,6 @@ - type: Tag tags: - Arrow - - CannonRestrict - type: Projectile deleteOnCollide: false onlyCollideWhenShot: true diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/hitscan.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/hitscan.yml index 3f37d308db..94cac9bcec 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/hitscan.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/hitscan.yml @@ -138,3 +138,20 @@ impactFlash: sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi state: impact_beam_heavy2 + +# Glimmer wisp laser +- type: hitscan + id: WispLash + damage: + types: + Cold: 8 + Shock: 8 + muzzleFlash: + sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi + state: muzzle_omni + travelFlash: + sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi + state: beam_omni + impactFlash: + sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi + state: impact_omni diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/magic.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/magic.yml index 3556d1c8f8..b3abbfdfd3 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/magic.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/magic.yml @@ -30,6 +30,39 @@ - type: IgniteOnCollide fireStacks: 0.35 +- type: entity + noSpawn: true + parent: BaseBulletTrigger + id: ProjectileDragonsBreath + name: dragon's breath + description: Try not to get toasted. + components: + - type: PointLight + color: "#E25822" + radius: 3.0 + energy: 5.0 + - type: Sprite + sprite: Objects/Weapons/Guns/Projectiles/magic.rsi + layers: + - state: fireball + shader: unshaded + - type: IgnitionSource + temperature: 1000 + ignited: true + - type: RepeatingTrigger + delay: 0.5 # line of fire as well as if it hits something + - type: ExplodeOnTrigger + - type: Explosive + explosionType: Default + totalIntensity: 5 # low intensity, the point is to burn attackers not to break open walls, dragons can just eat them + intensitySlope: 1 + maxIntensity: 3 + canCreateVacuum: false + deleteAfterExplosion: false + repeatable: true + - type: TimedDespawn + lifetime: 5 + - type: entity id: ProjectileAnomalyFireball name: fireball diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Revolvers/revolvers.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Revolvers/revolvers.yml index c5237cdad9..6c59898741 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Revolvers/revolvers.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Revolvers/revolvers.yml @@ -79,6 +79,27 @@ - type: StaticPrice price: 1500 +- type: entity + name: Deckard + parent: WeaponRevolverDeckard + id: WeaponRevolverDeckardSecurity + description: A rare, custom-built revolver. Use when there is no time for Voight-Kampff test. Uses .45 magnum ammo. + The serial number on the handguard marks this gun as belonging to an NT Security Officer. + +- type: entity + name: Deckard + parent: WeaponRevolverDeckardSecurity + id: WeaponRevolverDeckardNonLethalSecurity + description: A rare, custom-built revolver. Use when you need to spare the Replicants. Comes with .45 magnum rubber. + The serial number on the handguard marks this gun as belonging to an NT Security Officer. + components: + - type: RevolverAmmoProvider + whitelist: + tags: + - CartridgeMagnum + - SpeedLoaderMagnum + proto: CartridgeMagnumRubber + - type: entity name: Inspector parent: BaseWeaponRevolver @@ -94,11 +115,32 @@ chambers: [ True, True, True, True, True, True ] ammoSlots: [ null, null, null, null, null, null ] +- type: entity + name: Inspector + parent: WeaponRevolverInspector + id: WeaponRevolverInspectorSecurity + description: A detective's best friend. Uses .45 magnum ammo. + The serial number on the handguard marks this gun as belonging to an NT Security Officer. + +- type: entity + name: Inspector + parent: WeaponRevolverInspectorSecurity + id: WeaponRevolverInspectorNonLethalSecurity + description: A detective's best friend. Comes loaded with .45 magnum rubber. + The serial number on the handguard marks this gun as belonging to an NT Security Officer. + components: + - type: RevolverAmmoProvider + whitelist: + tags: + - CartridgeMagnum + - SpeedLoaderMagnum + proto: CartridgeMagnumRubber + - type: entity name: Mateba parent: BaseWeaponRevolver id: WeaponRevolverMateba - description: The iconic sidearm of the dreaded death squads. Uses .45 magnum ammo. + description: A distinctive revolver of an ancient Sol' design that was once seen as a rare collectible. It is in common manufacture by many companies in the Biesel Republic. Uses .45 magnum ammo. components: - type: Sprite sprite: Objects/Weapons/Guns/Revolvers/mateba.rsi @@ -113,7 +155,7 @@ name: Python parent: BaseWeaponRevolver id: WeaponRevolverPython - description: A robust revolver favoured by Syndicate agents. Uses .45 magnum ammo. + description: An iconic large-framed revolver of ancient Sol' design. It is commonly manufactured by many companies all over the colonies. Uses .45 magnum ammo. components: - type: Sprite sprite: Objects/Weapons/Guns/Revolvers/python.rsi @@ -130,12 +172,38 @@ volume: 2.25 fireOnDropChance: 0.3 +- type: entity + name: Python + parent: WeaponRevolverPython + id: WeaponRevolverPythonSecurity + description: An iconic large-framed revolver of ancient Sol' design. It is commonly manufactured by many companies all over the colonies. Uses .45 magnum ammo. + The serial number on the handguard marks this gun as belonging to an NT Security Officer. + +- type: entity + name: Python + parent: WeaponRevolverPythonSecurity + id: WeaponRevolverPythonNonlethalSecurity + components: + - type: RevolverAmmoProvider + whitelist: + tags: + - CartridgeMagnum + - SpeedLoaderMagnum + proto: CartridgeMagnumRubber + capacity: 6 + chambers: [ True, True, True, True, True, True ] + ammoSlots: [ null, null, null, null, null, null ] + soundEject: + path: /Audio/Weapons/Guns/MagOut/revolver_magout.ogg + soundInsert: + path: /Audio/Weapons/Guns/MagIn/revolver_magin.ogg + - type: entity name: Python parent: WeaponRevolverPython id: WeaponRevolverPythonAP # For the uplink. suffix: armor-piercing - description: A robust revolver favoured by Syndicate agents. Uses .45 magnum ammo. + description: An iconic large-framed revolver of ancient Sol' design. It is commonly manufactured by many companies all over the colonies. Uses .45 magnum ammo. components: - type: RevolverAmmoProvider whitelist: diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Rifles/rifles.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Rifles/rifles.yml index c55b2b6b09..61df2b857e 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Rifles/rifles.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Rifles/rifles.yml @@ -1,6 +1,6 @@ - type: entity name: BaseWeaponRifle - parent: BaseItem + parent: [BaseItem, BaseGunWieldable] id: BaseWeaponRifle description: A rooty tooty point and shooty. abstract: true @@ -14,6 +14,8 @@ slots: - Back - suitStorage + - type: Wieldable + - type: GunRequiresWield - type: AmmoCounter - type: Gun fireRate: 5 diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/SMGs/smgs.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/SMGs/smgs.yml index 3962f6e1f5..b448ddea3e 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/SMGs/smgs.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/SMGs/smgs.yml @@ -20,7 +20,7 @@ maxAngle: 16 fireRate: 8 angleIncrease: 3 - angleDecay: 16 + angleDecay: 16 selectedMode: FullAuto availableModes: - SemiAuto @@ -81,9 +81,9 @@ - type: entity name: C-20r sub machine gun - parent: BaseWeaponSubMachineGun + parent: [BaseWeaponSubMachineGun, BaseGunWieldable] id: WeaponSubMachineGunC20r - description: A firearm that is often used by the infamous nuclear operatives. Uses .35 auto ammo. + description: Manufactured by Cybersun-Armaments, the C-20r is one of the most popular personal small-arms in the Coalition of Colonies. Uses .35 auto ammo. components: - type: Sprite sprite: Objects/Weapons/Guns/SMGs/c20r.rsi @@ -94,7 +94,13 @@ map: ["enum.GunVisualLayers.Mag"] - type: Clothing sprite: Objects/Weapons/Guns/SMGs/c20r.rsi + - type: Wieldable + - type: GunWieldBonus + minAngle: -19 + maxAngle: -16 - type: Gun + minAngle: 21 + maxAngle: 32 shotsPerBurst: 5 availableModes: - SemiAuto @@ -111,9 +117,18 @@ zeroVisible: true - type: Appearance +- type: entity + name: antique C-20r submachine gun + parent: WeaponSubMachineGunC20r + id: WeaponSubMachineGunC20rHoS + description: This heavily worn submachine gun is engraved with the words, "Remember Mars". + components: + - type: StealTarget + stealGroup: HoSAntiqueWeapon + - type: entity name: Drozd - parent: BaseWeaponSubMachineGun + parent: [BaseWeaponSubMachineGun, BaseGunWieldable] id: WeaponSubMachineGunDrozd description: An excellent fully automatic Heavy SMG. components: @@ -126,7 +141,13 @@ map: ["enum.GunVisualLayers.Mag"] - type: Clothing sprite: Objects/Weapons/Guns/SMGs/drozd.rsi + - type: Wieldable + - type: GunWieldBonus + minAngle: -19 + maxAngle: -16 - type: Gun + minAngle: 21 + maxAngle: 32 fireRate: 6 selectedMode: FullAuto soundGunshot: @@ -234,7 +255,7 @@ minAngle: 1 maxAngle: 6 angleIncrease: 1.5 - angleDecay: 6 + angleDecay: 6 selectedMode: FullAuto shotsPerBurst: 5 availableModes: @@ -266,6 +287,15 @@ zeroVisible: true - type: Appearance +- type: entity + name: antique Wt550 + id: WeaponSubMachineGunWt550HoS + parent: WeaponSubMachineGunWt550 + description: A prized possession of the station's Head of Security. The smell of dried blood lingers on this weapon's muzzle. A small torch with 24 stars surrounding it has been engraved on the grip. + components: + - type: StealTarget + stealGroup: HoSAntiqueWeapon + # Rubber - type: entity name: Drozd diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Shotguns/shotguns.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Shotguns/shotguns.yml index 52b05b6d60..44ee4a08c1 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Shotguns/shotguns.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Shotguns/shotguns.yml @@ -47,7 +47,7 @@ - type: entity name: Bulldog # Don't parent to BaseWeaponShotgun because it differs significantly - parent: BaseItem + parent: [BaseItem, BaseGunWieldable] id: WeaponShotgunBulldog description: It's a magazine-fed shotgun designed for close quarters combat. Uses .50 shotgun shells. components: @@ -67,6 +67,7 @@ - Back - suitStorage - type: AmmoCounter + - type: GunRequiresWield #remove when inaccuracy on spreads is fixed - type: Gun fireRate: 2 selectedMode: FullAuto @@ -77,6 +78,7 @@ soundEmpty: path: /Audio/Weapons/Guns/Empty/empty.ogg fireOnDropChance: 0.3 + - type: Wieldable - type: ItemSlots slots: gun_magazine: @@ -101,9 +103,18 @@ - type: StaticPrice price: 500 +- type: entity + name: antique Bulldog + parent: WeaponShotgunBulldog + id: WeaponShotgunBulldogHoS + description: This is a seemingly ordinary shotgun, no different from those issued as standard in the Republic of Biesel Navy. A close inspection reveals that this weapon's serial number is 000000013. + components: + - type: StealTarget + stealGroup: HoSAntiqueWeapon + - type: entity name: double-barreled shotgun - parent: BaseWeaponShotgun + parent: [BaseWeaponShotgun, BaseGunWieldable] id: WeaponShotgunDoubleBarreled description: An immortal classic. Uses .50 shotgun shells. components: @@ -113,11 +124,12 @@ size: Normal shape: - 0,0,4,0 - sprite: Objects/Weapons/Guns/Shotguns/inhands_64x.rsi - heldPrefix: db + sprite: Objects/Weapons/Guns/Shotguns/db_shotgun_inhands_64x.rsi + - type: GunRequiresWield #remove when inaccuracy on spreads is fixed - type: Gun fireRate: 2 fireOnDropChance: 0.5 + - type: Wieldable - type: BallisticAmmoProvider capacity: 2 - type: Construction @@ -137,15 +149,19 @@ - type: entity name: Enforcer - parent: BaseWeaponShotgun + parent: [BaseWeaponShotgun, BaseGunWieldable] id: WeaponShotgunEnforcer description: A premium combat shotgun based on the Kammerer design, featuring an upgraded clip capacity. .50 shotgun shells. components: - type: Sprite - sprite: DeltaV/Objects/Weapons/Guns/Shotguns/enforcer.rsi # Delta-V + sprite: DeltaV/Objects/Weapons/Guns/Shotguns/enforcer.rsi - type: Clothing - sprite: DeltaV/Objects/Weapons/Guns/Shotguns/enforcer.rsi # Delta-V + sprite: DeltaV/Objects/Weapons/Guns/Shotguns/enforcer.rsi + - type: Item + sprite: Objects/Weapons/Guns/Shotguns/enforcer_inhands_64x.rsi - type: BallisticAmmoProvider + - type: Wieldable + - type: GunRequiresWield - type: entity parent: WeaponShotgunEnforcer @@ -157,7 +173,7 @@ - type: entity name: Kammerer - parent: BaseWeaponShotgun + parent: [BaseWeaponShotgun, BaseGunWieldable] id: WeaponShotgunKammerer description: When an old Remington design meets modern materials, this is the result. A favourite weapon of militia forces throughout many worlds. Uses .50 shotgun shells. components: @@ -165,17 +181,18 @@ size: Normal shape: - 0,0,4,0 - sprite: Objects/Weapons/Guns/Shotguns/inhands_64x.rsi - heldPrefix: pump + sprite: Objects/Weapons/Guns/Shotguns/pump_inhands_64x.rsi - type: Sprite - sprite: DeltaV/Objects/Weapons/Guns/Shotguns/pump.rsi # Delta-V + sprite: DeltaV/Objects/Weapons/Guns/Shotguns/pump.rsi - type: Clothing - sprite: DeltaV/Objects/Weapons/Guns/Shotguns/pump.rsi # Delta-V + sprite: Objects/Weapons/Guns/Shotguns/pump.rsi + - type: GunRequiresWield #remove when inaccuracy on spreads is fixed - type: BallisticAmmoProvider capacity: 4 - type: Tag tags: - WeaponShotgunKammerer + - type: Wieldable - type: entity name: sawn-off shotgun @@ -189,8 +206,7 @@ sprite: DeltaV/Objects/Weapons/Guns/Shotguns/sawn.rsi # Delta-V - type: Item size: Small - sprite: Objects/Weapons/Guns/Shotguns/inhands_64x.rsi - heldPrefix: sawn + sprite: Objects/Weapons/Guns/Shotguns/sawn_inhands_64x.rsi - type: Gun fireRate: 4 fireOnDropChance: 0.5 @@ -242,7 +258,7 @@ - type: entity name: blunderbuss - parent: BaseWeaponShotgun + parent: [BaseWeaponShotgun, BaseGunWieldable] id: WeaponShotgunBlunderbuss suffix: Pirate description: Deadly at close range. @@ -253,6 +269,7 @@ - 0,0,4,0 - type: Sprite sprite: Objects/Weapons/Guns/Shotguns/blunderbuss.rsi + - type: GunRequiresWield #remove when inaccuracy on spreads is fixed - type: Gun fireRate: 2 fireOnDropChance: 1 @@ -260,10 +277,11 @@ capacity: 1 - type: StaticPrice price: 0 + - type: Wieldable - type: entity name: improvised shotgun - parent: BaseWeaponShotgun + parent: [BaseWeaponShotgun, BaseGunWieldable] id: WeaponShotgunImprovised description: A shitty, hand-made shotgun that uses .50 shotgun shells. It can only hold one round in the chamber. components: @@ -275,8 +293,8 @@ size: Normal shape: - 0,0,4,0 - sprite: Objects/Weapons/Guns/Shotguns/inhands_64x.rsi - heldPrefix: improvised + sprite: Objects/Weapons/Guns/Shotguns/improvised_shotgun_inhands_64x.rsi + - type: GunRequiresWield #remove when inaccuracy on spreads is fixed - type: Gun fireRate: 4 #No reason to stifle the firerate since you have to manually reload every time anyways. fireOnDropChance: 1 diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Snipers/snipers.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Snipers/snipers.yml index adb8e323f4..88c00bedbd 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Snipers/snipers.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Snipers/snipers.yml @@ -18,6 +18,8 @@ - Back - suitStorage - type: AmmoCounter + - type: Wieldable + - type: GunRequiresWield - type: Gun fireRate: 0.75 selectedMode: SemiAuto @@ -40,7 +42,7 @@ - type: entity name: Kardashev-Mosin - parent: BaseWeaponSniper + parent: [BaseWeaponSniper, BaseGunWieldable] id: WeaponSniperMosin description: A weapon for hunting, or endless trench warfare. Uses .30 rifle ammo. components: @@ -55,9 +57,19 @@ path: /Audio/Weapons/Guns/Gunshots/sniper.ogg fireOnDropChance: 1 +- type: entity + name: Kardashev-Mosin + parent: WeaponSniperMosin + id: WeaponSniperMosinRubber + description: A weapon for hunting, or endless trench warfare. Uses .30 rifle rubber ammo. + suffix: Non-Lethal + components: + - type: BallisticAmmoProvider + proto: CartridgeLightRifleRubber + - type: entity name: Hristov - parent: BaseWeaponSniper + parent: [BaseWeaponSniper, BaseGunWieldable] id: WeaponSniperHristov description: A portable anti-materiel rifle. Fires armor piercing 14.5mm shells. Uses .60 anti-materiel ammo. components: @@ -71,6 +83,7 @@ - CartridgeAntiMateriel capacity: 5 proto: CartridgeAntiMateriel + - type: Telescope - type: entity name: musket diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/pneumatic_cannon.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/pneumatic_cannon.yml index ae1f5df3c1..1251172946 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/pneumatic_cannon.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/pneumatic_cannon.yml @@ -22,7 +22,6 @@ selectedMode: SemiAuto availableModes: - SemiAuto - - FullAuto soundGunshot: path: /Audio/Effects/thunk.ogg soundEmpty: @@ -34,10 +33,7 @@ - type: Storage maxItemSize: Normal grid: - - 0,0,3,3 - blacklist: - tags: - - CannonRestrict + - 0,0,1,1 - type: Appearance - type: ItemMapper containerWhitelist: [gas_tank] diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/baseball_bat.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/baseball_bat.yml index 8780d377e0..60f599af93 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/baseball_bat.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/baseball_bat.yml @@ -17,7 +17,7 @@ bluntStaminaDamageFactor: 2.0 heavyRateModifier: 0.5 heavyDamageBaseModifier: 1.75 - heavyStaminaCost: 15 + heavyStaminaCost: 10 maxTargets: 2 angle: 120 soundHit: @@ -43,6 +43,9 @@ node: bat - type: UseDelay delay: 1 + - type: PhysicalComposition + materialComposition: + Wood: 250 - type: Tag tags: - BaseballBat diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/cane.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/cane.yml new file mode 100644 index 0000000000..5c26020d72 --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/cane.yml @@ -0,0 +1,98 @@ +- type: entity + parent: BaseItem + id: Cane + name: cane + description: A wooden cane. + components: + - type: Sprite + sprite: Objects/Weapons/Melee/cane.rsi + state: cane + - type: Item + size: Normal + sprite: Objects/Weapons/Melee/cane.rsi + - type: Appearance + - type: MeleeWeapon + wideAnimationRotation: 45 + damage: + types: + Blunt: 5 + - type: StaminaDamageOnHit + damage: 5 + - type: Wieldable + - type: IncreaseDamageOnWield + damage: + types: + Blunt: 3 + - type: UseDelay + delay: 1 + +- type: entity + name: cane blade + parent: BaseItem + id: CaneBlade + description: A sharp blade with a cane shaped hilt. + components: + - type: Sharp + - type: Sprite + sprite: Objects/Weapons/Melee/cane_blade.rsi + state: icon + - type: MeleeWeapon + wideAnimationRotation: 65 + attackRate: 1.5 + damage: + types: + Slash: 14 + soundHit: + path: /Audio/Weapons/bladeslice.ogg + - type: Item + size: Normal + sprite: Objects/Weapons/Melee/cane_blade.rsi + - type: Tag + tags: + - CaneBlade + - type: DisarmMalus + +- type: entity + parent: Cane + id: CaneSheath + suffix: Empty + components: + - type: Sprite + sprite: Objects/Weapons/Melee/cane.rsi + state: cane-empty + - type: ContainerContainer + containers: + storagebase: !type:Container + ents: [] + item: !type:ContainerSlot + - type: UserInterface + interfaces: + enum.StorageUiKey.Key: + type: StorageBoundUserInterface + - type: ItemSlots + slots: + item: + name: CaneBlade + insertVerbText: sheath-insert-verb + ejectVerbText: sheath-eject-verb + whitelist: + tags: + - CaneBlade + insertSound: /Audio/Items/sheath.ogg + ejectSound: /Audio/Items/unsheath.ogg + - type: ItemMapper + mapLayers: + cane: + whitelist: + tags: + - CaneBlade + +- type: entity + id: CaneSheathFilled + parent: CaneSheath + suffix: Filled + components: + - type: ContainerFill + containers: + item: + - CaneBlade diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/cult.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/cult.yml index 5e9d789b65..a681ef52eb 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/cult.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/cult.yml @@ -11,7 +11,7 @@ - type: MeleeWeapon wideAnimationRotation: -135 attackRate: 1.25 - range: 1.4 + range: 1.5 damage: types: Slash: 8 @@ -39,12 +39,12 @@ - type: MeleeWeapon wideAnimationRotation: -135 attackRate: 0.75 - range: 1.75 + range: 1.65 damage: types: Slash: 12 heavyDamageBaseModifier: 1.2 - heavyStaminaCost: 10 + heavyStaminaCost: 7.5 maxTargets: 6 angle: 90 - type: Item @@ -70,7 +70,7 @@ state: icon - type: MeleeWeapon wideAnimationRotation: -135 - attackRate: 0.75 + attackRate: 0.85 range: 1.75 damage: types: @@ -79,7 +79,7 @@ Structural: 5 heavyRateModifier: 0.9 heavyDamageBaseModifier: 1.2 - heavyStaminaCost: 10 + heavyStaminaCost: 7.5 angle: 100 soundHit: collection: MetalThud diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/e_sword.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/e_sword.yml index 0cbc824365..72b43296af 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/e_sword.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/e_sword.yml @@ -78,9 +78,27 @@ malus: 0 - type: Reflect enabled: false + reflectProb: 0.5 + minReflectProb: 0.25 + - type: Tag + tags: + - NoPaint - type: IgnitionSource temperature: 700 +- type: entity + name: antique energy sword + parent: EnergySword + id: EnergySwordHoS + description: An elegant weapon fit for a prince, this otherwise plain silver hilt is engraved with, "My Love". + components: + - type: EnergySword + activatedColor: "#00CCFF" + colorOptions: + - "#00CCFF" + - type: StealTarget + stealGroup: HoSAntiqueWeapon + - type: entity name: pen parent: EnergySword @@ -156,6 +174,7 @@ - type: Tag tags: - Write + - NoPaint - type: DisarmMalus malus: 0 @@ -225,7 +244,7 @@ name: double-bladed energy sword parent: EnergySword id: EnergySwordDouble - description: Syndicate Command Interns thought that having one blade on the energy sword was not enough. This can be stored in pockets. + description: Syndicate Command's intern thought that having only one blade on energy swords was not cool enough. This can be stored in pockets. components: - type: EnergySword - type: ItemToggle @@ -276,7 +295,8 @@ size: Small sprite: Objects/Weapons/Melee/e_sword_double-inhands.rsi - type: Reflect - reflectProb: .80 #DeltaV: 80% Energy Reflection but no ballistics. + reflectProb: .80 + minReflectProb: .65 spread: 75 reflects: - Energy #DeltaV: 80% Energy Reflection but no ballistics. diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/fireaxe.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/fireaxe.yml index b30a285579..6630a22ea7 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/fireaxe.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/fireaxe.yml @@ -43,6 +43,7 @@ - type: Tool qualities: - Prying + - Axing - type: ToolTileCompatible - type: Prying - type: UseDelay diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/home_run_bat.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/home_run_bat.yml index 5cb5795c8a..43a4fb6c59 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/home_run_bat.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/home_run_bat.yml @@ -14,8 +14,7 @@ bluntStaminaDamageFactor: 2.0 heavyRateModifier: 0.5 heavyDamageBaseModifier: 1.75 - heavyStaminaCost: 25 - maxTargets: 2 + heavyStaminaCost: 15 angle: 120 soundHit: collection: ExplosionSmall diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/knife.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/knife.yml index 68f8863d11..5502f75289 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/knife.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/knife.yml @@ -13,13 +13,12 @@ - type: MeleeWeapon wideAnimationRotation: -135 attackRate: 1.25 - range: 1.4 + range: 1.5 damage: types: Slash: 8 heavyRateModifier: 0.8 heavyDamageBaseModifier: 1.2 - heavyStaminaCost: 5 maxTargets: 3 angle: 40 soundHit: @@ -95,7 +94,6 @@ - type: MeleeWeapon wideAnimationRotation: -135 attackRate: 1.5 - range: 1.4 damage: types: Slash: 9 @@ -122,7 +120,6 @@ - type: MeleeWeapon wideAnimationRotation: -135 attackRate: 1.25 - range: 1.5 damage: types: Slash: 8 @@ -204,7 +201,7 @@ state: icon - type: MeleeWeapon attackRate: 1.75 - range: 0.75 + range: 1.4 damage: types: Slash: 5.5 @@ -278,7 +275,6 @@ tags: - CombatKnife - Knife - - CannonRestrict - type: Sprite sprite: Objects/Weapons/Melee/throwing_knife.rsi state: icon diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/mining.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/mining.yml index a1addba262..38a203ce90 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/mining.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/mining.yml @@ -43,8 +43,8 @@ capacity: 1 count: 1 - type: MeleeWeapon - attackRate: 0.75 - range: 1.75 + attackRate: 0.85 + range: 1.65 wideAnimationRotation: -135 damage: types: @@ -53,7 +53,7 @@ bluntStaminaDamageFactor: 2.0 heavyRateModifier: 0.75 heavyDamageBaseModifier: 1.2 - heavyStaminaCost: 10 + heavyStaminaCost: 7.5 angle: 120 soundHit: collection: MetalThud @@ -92,7 +92,6 @@ Slash: 9 heavyRateModifier: 0.9 heavyDamageBaseModifier: 1.2 - heavyStaminaCost: 5 maxTargets: 2 angle: 20 - type: Tag diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/pickaxe.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/pickaxe.yml index 6ba659ccb4..fb1eb3d7fe 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/pickaxe.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/pickaxe.yml @@ -11,8 +11,8 @@ sprite: Objects/Weapons/Melee/pickaxe.rsi state: pickaxe - type: MeleeWeapon - attackRate: 0.75 - range: 1.75 + attackRate: 0.85 + range: 1.5 wideAnimationRotation: -135 soundHit: path: "/Audio/Weapons/smash.ogg" @@ -24,9 +24,8 @@ Pierce: 3 bluntStaminaDamageFactor: 2.0 heavyDamageBaseModifier: 1.75 - heavyStaminaCost: 5 - maxTargets: 2 - angle: 60 + maxTargets: 5 + angle: 80 - type: Wieldable - type: IncreaseDamageOnWield damage: @@ -61,19 +60,17 @@ wideAnimationRotation: -90 soundHit: path: "/Audio/Items/drill_hit.ogg" - attackRate: 0.5 - range: 1.4 + attackRate: 1.2 + range: 1.5 damage: types: - Blunt: 9 + Blunt: 6 Slash: 3 Structural: 12 bluntStaminaDamageFactor: 4.0 heavyRateModifier: 1 heavyRangeModifier: 2 heavyDamageBaseModifier: 1 - heavyStaminaCost: 10 - maxTargets: 3 angle: 20 - type: ReverseEngineering # Nyano diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/sledgehammer.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/sledgehammer.yml index ecc84e5007..ffe791ce0c 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/sledgehammer.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/sledgehammer.yml @@ -9,7 +9,7 @@ state: icon - type: MeleeWeapon wideAnimationRotation: -135 - attackRate: 0.8 + attackRate: 0.75 range: 1.75 damage: types: @@ -18,7 +18,7 @@ bluntStaminaDamageFactor: 2.0 heavyRateModifier: 0.75 heavyDamageBaseModifier: 1.75 - heavyStaminaCost: 15 + heavyStaminaCost: 7.5 maxTargets: 10 angle: 120 soundHit: diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/stunprod.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/stunprod.yml index d8955b4def..5214358ff9 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/stunprod.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/stunprod.yml @@ -25,22 +25,19 @@ - type: ItemToggleMeleeWeapon activatedDamage: types: - Blunt: 0 + Shock: 5 - type: Stunbaton - energyPerUse: 70 + energyPerUse: 120 - type: MeleeWeapon wideAnimationRotation: -135 - attackRate: 0.8 - range: 1.4 + attackRate: 1 + range: 1.5 damage: types: Blunt: 7.5 bluntStaminaDamageFactor: 2.0 heavyRateModifier: 0.8 heavyDamageBaseModifier: 1.2 - heavyStaminaCost: 5 - maxTargets: 3 - angle: 60 animation: WeaponArcThrust - type: StaminaDamageOnHit damage: 22 diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/sword.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/sword.yml index 82b99ce37e..046e9f76f7 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/sword.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/sword.yml @@ -16,7 +16,7 @@ path: /Audio/SimpleStation14/Weapons/Melee/rapierhit.ogg damage: types: - Slash: 17 #cmon, it has to be at least BETTER than the rest. + Slash: 15 heavyRateModifier: 0.8 heavyRangeModifier: 1 heavyDamageBaseModifier: 1 @@ -25,7 +25,12 @@ angle: 80 - type: Reflect enabled: true - reflectProb: .5 + # Design intent: a robust captain or tot can sacrifice movement to make the most of this weapon, but they have to + # really restrict themselves to walking speed or less. + reflectProb: 0.5 + velocityBeforeNotMaxProb: 1.0 + velocityBeforeMinProb: 3.0 + minReflectProb: 0.1 spread: 90 - type: Item size: Normal @@ -57,9 +62,9 @@ types: Slash: 12 heavyRateModifier: 0.5 - heavyRangeModifier: 3 #Superior Japanese folded steel + heavyRangeModifier: 2.75 #Superior Japanese folded steel heavyDamageBaseModifier: 1.25 - heavyStaminaCost: 10 + heavyStaminaCost: 15 maxTargets: 1 angle: 20 - type: Item @@ -97,6 +102,9 @@ - Back - Belt - type: Reflect + reflectProb: 0.3 + velocityBeforeNotMaxProb: 6.0 # don't punish ninjas for being ninjas + velocityBeforeMinProb: 10.0 - type: entity name: machete @@ -120,7 +128,7 @@ heavyRateModifier: 0.8 heavyRangeModifier: 1.25 heavyDamageBaseModifier: 1.2 - heavyStaminaCost: 10 + heavyStaminaCost: 7.5 angle: 80 soundHit: path: /Audio/Weapons/bladeslice.ogg @@ -151,7 +159,7 @@ heavyRateModifier: 0.5 heavyRangeModifier: 1 heavyDamageBaseModifier: 1 - heavyStaminaCost: 20 + heavyStaminaCost: 15 maxTargets: 10 angle: 200 soundHit: @@ -186,7 +194,7 @@ heavyRateModifier: 0.8 heavyRangeModifier: 1.2 heavyDamageBaseModifier: 1.2 - heavyStaminaCost: 10 + heavyStaminaCost: 7.5 maxTargets: 3 angle: 40 soundHit: @@ -200,7 +208,7 @@ name: The Throngler parent: BaseItem id: Throngler - description: Why would you make this? + description: Why would someone make this? components: - type: Sharp - type: Sprite @@ -221,7 +229,10 @@ path: /Audio/Effects/explosion_small1.ogg - type: Reflect enabled: true - reflectProb: .25 + reflectProb: 0.5 # In robust hands, deflects as well as an e-sword + velocityBeforeNotMaxProb: 1.0 + velocityBeforeMinProb: 3.0 + minReflectProb: 0.1 spread: 90 - type: Item size: Ginormous diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/telescopic_baton.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/telescopic_baton.yml new file mode 100644 index 0000000000..5e797c8536 --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/telescopic_baton.yml @@ -0,0 +1,71 @@ +- type: entity + id: TelescopicBaton + parent: BaseItem + name: telescopic baton + description: A compact and harmless personal defense weapon. Sturdy enough to knock the feet out from under attackers. + components: + - type: Sprite + sprite: Objects/Weapons/Melee/telebaton.rsi + layers: + - state: icon + map: [ "enum.TelescopicBatonVisuals.Layer" ] + - type: Item + size: Small + - type: ItemToggle + soundActivate: + path: /Audio/Weapons/telescopicon.ogg + params: + volume: -2 + soundDeactivate: + path: /Audio/Weapons/telescopicoff.ogg + params: + volume: -2 + - type: ItemToggleDisarmMalus + activatedDisarmMalus: 0.6 + - type: ItemToggleMeleeWeapon + activatedDamage: + types: + Blunt: 12 + - type: ItemToggleSize + activatedSize: Normal + - type: UseDelay + delay: 2 + - type: TelescopicBaton + - type: KnockdownOnHit + duration: 1.5 + dropHeldItemsBehavior: NoDrop + - type: MeleeWeapon + attackRate: 0.8 + bluntStaminaDamageFactor: 1.5 + heavyDamageBaseModifier: 1.2 + heavyStaminaCost: 5 + maxTargets: 1 + angle: 40 + damage: + types: + Blunt: 1 + - type: Appearance + - type: GenericVisualizer + visuals: + enum.TelescopicBatonVisuals.State: + enum.TelescopicBatonVisuals.Layer: + True: { state: icon } + False: { state: icon-off } + +- type: entity + parent: TelescopicBaton + id: TelescopicBatonAdmeme + name: robust telescopic baton + description: A compact and HARMFUL personal defense weapon. Sturdy enough to break legs of the attackers, making them unable to walk again. + suffix: admeme, DO NOT MAP + components: + - type: TelescopicBaton + attackTimeframe: 300 # one minute after activation + - type: KnockdownOnHit + duration: 60 # + - type: MeleeWeapon + attackRate: 1.2 + - type: ItemToggleMeleeWeapon + activatedDamage: + types: + Blunt: 20 diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/bola.yml b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/bola.yml index ca3edf68c0..7bbd0fe893 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/bola.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/bola.yml @@ -1,8 +1,9 @@ - type: entity name: bola + abstract: true + id: BaseBola parent: BaseItem - id: Bola - description: Linked together with some spare cuffs and metal. + description: High velocity entanglement tool. components: - type: Item size: Normal @@ -13,6 +14,25 @@ sound: /Audio/Weapons/bolathrow.ogg - type: EmitSoundOnLand sound: /Audio/Effects/snap.ogg + - type: DamageOnLand + damage: + types: + Blunt: 5 + - type: Ensnaring + freeTime: 2.0 + breakoutTime: 3.5 + walkSpeed: 0.7 + sprintSpeed: 0.7 + staminaDamage: 55 + canThrowTrigger: true + canMoveBreakout: true + +- type: entity + name: bola + parent: [BaseBola] + id: Bola + description: Linked together with some spare cuffs and metal. + components: - type: Construction graph: Bola node: bola @@ -35,16 +55,18 @@ collection: MetalBreak - !type:DoActsBehavior acts: [ "Destruction" ] + +- type: entity + name: energy bola + id: BolaEnergy + parent: BaseBola + description: An advanced hardlight criminal entangling tool. Otherwise known as an expensive piece of string. + components: + - type: Sprite + sprite: Objects/Weapons/Throwable/energy_bola.rsi - type: DamageOnLand damage: types: - Blunt: 5 + Heat: 5 - type: Ensnaring - freeTime: 2.0 - breakoutTime: 3.5 #all bola should generally be fast to remove - walkSpeed: 0.7 #makeshift bola shouldn't slow too much - sprintSpeed: 0.7 - staminaDamage: 55 # Sudden weight increase sapping stamina - canThrowTrigger: true - canMoveBreakout: true - + destroyOnRemove: true diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml index f25023b454..acbaac2922 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml @@ -120,6 +120,30 @@ params: volume: 12 +- type: entity + name: Self Destruct + description: Go out on your own terms! + parent: GrenadeBase + id: SelfDestructSeq + noSpawn: true + components: + - type: ExplodeOnTrigger + - type: Explosive + explosionType: Minibomb + totalIntensity: 400 + intensitySlope: 30 + maxIntensity: 125 + canCreateVacuum: true + - type: OnUseTimerTrigger + delay: 4.5 + beepSound: + path: /Audio/Effects/Grenades/SelfDestruct/SDS_Charge2.ogg + params: + volume: 30 + initialBeepDelay: 0 + beepInterval: 16 + + - type: entity name: supermatter grenade description: Grenade that simulates delamination of the supermatter engine, pulling things in a heap and exploding after some time. diff --git a/Resources/Prototypes/Entities/Objects/Weapons/security.yml b/Resources/Prototypes/Entities/Objects/Weapons/security.yml index a952713dd5..1b07eab9fa 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/security.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/security.yml @@ -34,12 +34,10 @@ damage: types: Blunt: 7 - bluntStaminaDamageFactor: 2.0 + bluntStaminaDamageFactor: 2.5 heavyRateModifier: 0.75 heavyDamageBaseModifier: 1.75 - heavyStaminaCost: 5 - maxTargets: 3 - angle: 60 + heavyStaminaCost: 1 animation: WeaponArcSlash - type: StaminaDamageOnHit damage: 35 @@ -82,6 +80,10 @@ explosionType: Default intensitySlope: 1.5 maxIntensity: 200 + - type: PhysicalComposition + materialComposition: + Steel: 100 + Plastic: 100 - type: GuideHelp guides: - Security @@ -106,9 +108,12 @@ bluntStaminaDamageFactor: 2 heavyRateModifier: 1 heavyDamageBaseModifier: 1.2 - heavyStaminaCost: 10 + heavyStaminaCost: 7.5 - type: Item size: Normal + - type: Tag + tags: + - Truncheon - type: Clothing sprite: Objects/Weapons/Melee/truncheon.rsi quickEquip: false diff --git a/Resources/Prototypes/Entities/Objects/base_item.yml b/Resources/Prototypes/Entities/Objects/base_item.yml index 84b5477f50..9c9437f46a 100644 --- a/Resources/Prototypes/Entities/Objects/base_item.yml +++ b/Resources/Prototypes/Entities/Objects/base_item.yml @@ -50,8 +50,8 @@ - type: Storage - type: UserInterface interfaces: - - key: enum.StorageUiKey.Key - type: StorageBoundUserInterface + enum.StorageUiKey.Key: + type: StorageBoundUserInterface - type: ContainerContainer containers: storagebase: !type:Container diff --git a/Resources/Prototypes/Entities/Stations/base.yml b/Resources/Prototypes/Entities/Stations/base.yml index 0bf0f7f915..66ea8dadea 100644 --- a/Resources/Prototypes/Entities/Stations/base.yml +++ b/Resources/Prototypes/Entities/Stations/base.yml @@ -61,7 +61,7 @@ - /Maps/Shuttles/trading_outpost.yml mining: paths: - - /Maps/Shuttles/mining.yml + - /Maps/Shuttles/pathfinder.yml # Spawn last ruins: hide: true @@ -70,13 +70,39 @@ maxCount: 2 stationGrid: false paths: - - /Maps/Ruins/DeltaV/biodome_satellite.yml #Delta V - Move to DV folder - - /Maps/Ruins/DeltaV/derelict.yml #Delta V - Move to DV folder - - /Maps/Ruins/DeltaV/djstation.yml #Delta V - Move to DV folder - - /Maps/Ruins/DeltaV/old_ai_sat.yml #Delta V - Move to DV folder - - /Maps/Ruins/DeltaV/relaystation.yml #Delta V - Move to DV folder - - /Maps/Ruins/DeltaV/whiteship_ancient.yml #Delta V - Move to DV folder - - /Maps/Ruins/DeltaV/whiteship_bluespacejumper.yml #Delta V - Move to DV folder + - /Maps/Ruins/DeltaV/biodome_satellite.yml + - /Maps/Ruins/DeltaV/derelict.yml + - /Maps/Ruins/DeltaV/djstation.yml + - /Maps/Ruins/DeltaV/old_ai_sat.yml + - /Maps/Ruins/DeltaV/relaystation.yml + - /Maps/Ruins/DeltaV/whiteship_ancient.yml + - /Maps/Ruins/DeltaV/whiteship_bluespacejumper.yml + +- type: entity + id: BaseStationShuttlesSalvageOnly + abstract: true + components: + - type: GridSpawn + groups: + mining: + addComponents: + - type: ProtectedGrid + paths: + - /Maps/Shuttles/pathfinder.yml + ruins: + hide: true + nameGrid: true + minCount: 2 + maxCount: 2 + stationGrid: false + paths: + - /Maps/Ruins/DeltaV/biodome_satellite.yml + - /Maps/Ruins/DeltaV/derelict.yml + - /Maps/Ruins/DeltaV/djstation.yml + - /Maps/Ruins/DeltaV/old_ai_sat.yml + - /Maps/Ruins/DeltaV/relaystation.yml + - /Maps/Ruins/DeltaV/whiteship_ancient.yml + - /Maps/Ruins/DeltaV/whiteship_bluespacejumper.yml - type: entity id: BaseStationShuttlesCore @@ -89,7 +115,7 @@ - /Maps/Shuttles/cargo_core.yml mining: paths: - - /Maps/Shuttles/mining.yml + - /Maps/Shuttles/pathfinder.yml ruins: hide: true nameGrid: true @@ -153,4 +179,4 @@ id: BaseStationAllEventsEligible abstract: true components: - - type: StationEventEligible # For when someone makes this more granular in the future. \ No newline at end of file + - type: StationEventEligible # For when someone makes this more granular in the future. diff --git a/Resources/Prototypes/Entities/Stations/nanotrasen.yml b/Resources/Prototypes/Entities/Stations/nanotrasen.yml index 5ecfbe037f..329542a267 100644 --- a/Resources/Prototypes/Entities/Stations/nanotrasen.yml +++ b/Resources/Prototypes/Entities/Stations/nanotrasen.yml @@ -51,3 +51,28 @@ noSpawn: true components: - type: Transform + +- type: entity + id: StandardNanotrasenStationNoATS + parent: + - BaseStation + - BaseStationNews + - BaseStationCargo + - BaseStationJobsSpawning + - BaseStationRecords + - BaseStationArrivals + - BaseStationGateway + - BaseStationShuttlesSalvageOnly + - BaseStationCentcomm + - BaseStationEvacuation + - BaseStationAlertLevels + - BaseStationMagnet + - BaseStationExpeditions + - BaseStationSiliconLawCrewsimov + - BaseStationAllEventsEligible + - BaseStationNanotrasen + - BaseRandomStation + - BaseStationMail + noSpawn: true + components: + - type: Transform diff --git a/Resources/Prototypes/Entities/Structures/Decoration/bonfire.yml b/Resources/Prototypes/Entities/Structures/Decoration/bonfire.yml index cc69a6304d..0876502e67 100644 --- a/Resources/Prototypes/Entities/Structures/Decoration/bonfire.yml +++ b/Resources/Prototypes/Entities/Structures/Decoration/bonfire.yml @@ -31,6 +31,9 @@ sound: path: /Audio/Ambience/Objects/fireplace.ogg - type: AlwaysHot + - type: Tag + tags: + - NoPaint - type: entity id: LegionnaireBonfire diff --git a/Resources/Prototypes/Entities/Structures/Dispensers/base_structuredispensers.yml b/Resources/Prototypes/Entities/Structures/Dispensers/base_structuredispensers.yml index 1b660c0cf0..213ad47d88 100644 --- a/Resources/Prototypes/Entities/Structures/Dispensers/base_structuredispensers.yml +++ b/Resources/Prototypes/Entities/Structures/Dispensers/base_structuredispensers.yml @@ -30,8 +30,8 @@ - type: ActivatableUIRequiresPower - type: UserInterface interfaces: - - key: enum.ReagentDispenserUiKey.Key - type: ReagentDispenserBoundUserInterface + enum.ReagentDispenserUiKey.Key: + type: ReagentDispenserBoundUserInterface - type: Anchorable - type: Pullable - type: Damageable diff --git a/Resources/Prototypes/Entities/Structures/Dispensers/chem.yml b/Resources/Prototypes/Entities/Structures/Dispensers/chem.yml index 681f0a390c..6e331a13a8 100644 --- a/Resources/Prototypes/Entities/Structures/Dispensers/chem.yml +++ b/Resources/Prototypes/Entities/Structures/Dispensers/chem.yml @@ -31,6 +31,9 @@ acts: ["Destruction"] - type: Machine board: ChemDispenserMachineCircuitboard + - type: UpgradePowerDraw + powerDrawMultiplier: 0.75 + scaling: Exponential - type: GuideHelp guides: - Chemicals diff --git a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/access.yml b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/access.yml index f12bd2b553..c55b9d9554 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/access.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/access.yml @@ -152,7 +152,7 @@ components: - type: ContainerFill containers: - board: [ DoorElectronicsFreezer ] + board: [ DoorElectronicsKitchenHydroponics ] - type: entity parent: AirlockFreezer @@ -243,6 +243,16 @@ containers: board: [ DoorElectronicsChemistry ] +- type: entity + parent: AirlockMedical + id: AirlockMedicalMorgueLocked + suffix: Morgue, Locked + components: + - type: ContainerFill + containers: + board: [ DoorElectronicsMorgue ] + + - type: entity parent: AirlockScience id: AirlockScienceLocked @@ -259,7 +269,7 @@ components: - type: ContainerFill containers: - board: [ DoorElectronicsScience ] + board: [ DoorElectronicsMedicalResearch ] - type: entity parent: AirlockCentralCommand @@ -633,7 +643,7 @@ components: - type: ContainerFill containers: - board: [ DoorElectronicsScience ] + board: [ DoorElectronicsMedicalResearch ] - type: entity parent: AirlockCentralCommandGlass @@ -982,7 +992,7 @@ components: - type: ContainerFill containers: - board: [ DoorElectronicsRnDMed ] + board: [ DoorElectronicsMedicalResearch ] - type: entity parent: AirlockMaint diff --git a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml index 81db550edc..586902bd77 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml @@ -64,6 +64,7 @@ containers: board: !type:Container - type: Weldable + fuel: 5 time: 3 - type: Airlock - type: NavMapDoor @@ -103,12 +104,12 @@ - type: SpawnOnOverload - type: UserInterface interfaces: - - key: enum.WiresUiKey.Key - type: WiresBoundUserInterface + enum.WiresUiKey.Key: + type: WiresBoundUserInterface - type: Airtight noAirWhenFullyAirBlocked: false - type: RadiationBlocker - resistance: 3 + resistance: 5 - type: Occluder - type: Damageable damageContainer: StructuralInorganic @@ -116,7 +117,7 @@ - type: RCDDeconstructable cost: 6 delay: 8 - fx: EffectRCDDeconstruct8 + fx: EffectRCDDeconstruct8 - type: Destructible thresholds: - trigger: @@ -149,9 +150,12 @@ # This tag is used to nagivate the Airlock construction graph. It's needed because the construction graph is shared between Airlock, AirlockGlass, and HighSecDoor - type: PryUnpowered - type: BlockWeather + - type: InteractionVerbs + allowedVerbs: + - KnockOn placement: mode: SnapgridCenter - + - type: entity id: AirlockRCDResistant parent: Airlock @@ -196,8 +200,8 @@ - type: PaintableAirlock group: Glass - type: RadiationBlocker - resistance: 2 + resistance: 1 - type: Tag tags: - GlassAirlock - # This tag is used to nagivate the Airlock construction graph. It's needed because the construction graph is shared between Airlock, AirlockGlass, and HighSecDoor \ No newline at end of file + # This tag is used to nagivate the Airlock construction graph. It's needed because the construction graph is shared between Airlock, AirlockGlass, and HighSecDoor diff --git a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/highsec.yml b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/highsec.yml index e9ea05a1c3..559dca704c 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/highsec.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/highsec.yml @@ -55,6 +55,7 @@ denySound: path: /Audio/Machines/airlock_deny.ogg - type: Weldable + fuel: 10 time: 10 - type: Airlock - type: NavMapDoor @@ -78,8 +79,8 @@ alwaysRandomize: true - type: UserInterface interfaces: - - key: enum.WiresUiKey.Key - type: WiresBoundUserInterface + enum.WiresUiKey.Key: + type: WiresBoundUserInterface - type: Airtight - type: Occluder - type: Damageable diff --git a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/shuttle.yml b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/shuttle.yml index 5d6b1088f1..43d1228a40 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/shuttle.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/shuttle.yml @@ -6,6 +6,11 @@ description: Necessary for connecting two space craft together. components: - type: Docking + - type: DockingSignalControl + - type: DeviceLinkSource + ports: + - DoorStatus + - DockStatus - type: Fixtures fixtures: fix1: @@ -75,7 +80,6 @@ suffix: Glass, Docking description: Necessary for connecting two space craft together. components: - - type: Docking - type: Sprite sprite: Structures/Doors/Airlocks/Glass/shuttle.rsi snapCardinals: false @@ -127,7 +131,6 @@ suffix: Glass, Docking description: Necessary for connecting two space craft together. components: - - type: Docking - type: Sprite sprite: Structures/Doors/Airlocks/Glass/shuttle_syndicate.rsi snapCardinals: false diff --git a/Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml b/Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml index 1ba867773b..074c981da2 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml @@ -82,6 +82,7 @@ openingAnimationTime: 0.6 closingAnimationTime: 0.6 - type: Weldable + fuel: 5 time: 3 - type: Firelock - type: Appearance @@ -89,7 +90,7 @@ - type: WiresPanel - type: UserInterface interfaces: - - key: enum.WiresUiKey.Key + enum.WiresUiKey.Key: type: WiresBoundUserInterface - type: Physics canCollide: false @@ -98,6 +99,7 @@ noAirWhenFullyAirBlocked: true - type: RadiationBlocker enabled: false + resistance: 1 - type: Occluder enabled: false - type: WallMount diff --git a/Resources/Prototypes/Entities/Structures/Doors/MaterialDoors/material_doors.yml b/Resources/Prototypes/Entities/Structures/Doors/MaterialDoors/material_doors.yml index 89ba86751a..a653f26e16 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/MaterialDoors/material_doors.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/MaterialDoors/material_doors.yml @@ -57,6 +57,9 @@ acts: ["Destruction"] - type: Occluder - type: BlockWeather + - type: InteractionVerbs + allowedVerbs: + - KnockOn - type: entity parent: BaseMaterialDoor diff --git a/Resources/Prototypes/Entities/Structures/Doors/Shutter/blast_door.yml b/Resources/Prototypes/Entities/Structures/Doors/Shutter/blast_door.yml index 0700a5a3e7..df582e9825 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Shutter/blast_door.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Shutter/blast_door.yml @@ -24,7 +24,7 @@ - type: Occluder - type: Appearance - type: RadiationBlocker - resistance: 8 + resistance: 10 - type: Damageable damageContainer: StructuralInorganic damageModifierSet: StrongMetallic diff --git a/Resources/Prototypes/Entities/Structures/Doors/Shutter/shutters.yml b/Resources/Prototypes/Entities/Structures/Doors/Shutter/shutters.yml index 55010eea51..3465b646b4 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Shutter/shutters.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Shutter/shutters.yml @@ -56,11 +56,11 @@ - type: Appearance - type: UserInterface interfaces: - - key: enum.WiresUiKey.Key - type: WiresBoundUserInterface + enum.WiresUiKey.Key: + type: WiresBoundUserInterface - type: Airtight - type: RadiationBlocker - resistance: 2 + resistance: 1 - type: Damageable damageContainer: StructuralInorganic damageModifierSet: StrongMetallic @@ -88,12 +88,11 @@ - DoorStatus lastSignals: DoorStatus: false - - type: InteractionPopup - interactSuccessString: comp-window-knock - messagePerceivedByOthers: comp-window-knock - interactSuccessSound: - path: /Audio/Effects/glass_knock.ogg - type: BlockWeather + - type: InteractionVerbs + allowedVerbs: + - KnockOn + - type: entity id: ShuttersNormal @@ -126,7 +125,7 @@ id: ShuttersRadiation parent: BaseShutter name: radiation shutters - description: Why did they make these shutters radioactive? + description: A relatively fragile set of shutters, made of radiation blocking lead plates. components: - type: Sprite sprite: Structures/Doors/Shutters/shutters_radiation.rsi @@ -140,7 +139,15 @@ containers: - board - type: RadiationBlocker - resistance: 4 + resistance: 10 + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 100 + behaviors: + - !type:DoActsBehavior + acts: ["Destruction"] - type: entity id: ShuttersRadiationOpen diff --git a/Resources/Prototypes/Entities/Structures/Doors/Windoors/base_structurewindoors.yml b/Resources/Prototypes/Entities/Structures/Doors/Windoors/base_structurewindoors.yml index d58273edcc..462632dc07 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Windoors/base_structurewindoors.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Windoors/base_structurewindoors.yml @@ -66,7 +66,7 @@ damageContainer: Inorganic damageModifierSet: Glass - type: ExaminableDamage - messages: WindowMessages + messages: WindowMessages - type: RCDDeconstructable cost: 8 delay: 8 @@ -93,10 +93,11 @@ max: 4 - !type:DoActsBehavior acts: [ "Destruction" ] - - type: AccessReader - type: ContainerFill containers: board: [ DoorElectronics ] + - type: AccessReader + containerAccessProvider: board - type: ContainerContainer containers: board: !type:Container @@ -129,8 +130,8 @@ layoutId: Airlock - type: UserInterface interfaces: - - key: enum.WiresUiKey.Key - type: WiresBoundUserInterface + enum.WiresUiKey.Key: + type: WiresBoundUserInterface - type: Appearance - type: WiresVisuals - type: Airtight @@ -145,6 +146,9 @@ - type: StaticPrice price: 100 - type: PryUnpowered + - type: InteractionVerbs + allowedVerbs: + - KnockOn - type: entity id: BaseSecureWindoor @@ -252,7 +256,7 @@ - type: StaticPrice price: 170 - type: RadiationBlocker - resistance: 2 + resistance: 4 - type: entity id: BaseSecurePlasmaWindoor @@ -307,7 +311,7 @@ - type: StaticPrice price: 312 - type: RadiationBlocker - resistance: 4 + resistance: 6 #Uranium Windoors - type: entity @@ -362,7 +366,7 @@ - type: StaticPrice price: 180 - type: RadiationBlocker - resistance: 3 + resistance: 5 - type: entity id: BaseSecureUraniumWindoor @@ -417,4 +421,4 @@ - type: StaticPrice price: 462 - type: RadiationBlocker - resistance: 5 + resistance: 8 diff --git a/Resources/Prototypes/Entities/Structures/Doors/Windoors/windoor.yml b/Resources/Prototypes/Entities/Structures/Doors/Windoors/windoor.yml index 2b4b2c5fe0..801bd091d0 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Windoors/windoor.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Windoors/windoor.yml @@ -48,88 +48,99 @@ id: WindoorBarLocked suffix: Bar, Locked components: - - type: AccessReader - access: [["Bar"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsBar ] - type: entity parent: Windoor id: WindoorBarKitchenLocked suffix: Bar&Kitchen, Locked components: - - type: AccessReader - access: [["Bar"], ["Kitchen"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsBarKitchen ] - type: entity parent: Windoor id: WindoorCargoLocked suffix: Logistics, Locked # DeltaV - Logistics Department replacing Cargo components: - - type: AccessReader - access: [["Cargo"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsCargo ] - type: entity parent: Windoor id: WindoorChapelLocked suffix: Chapel, Locked components: - - type: AccessReader - access: [["Chapel"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsChapel ] - type: entity parent: Windoor id: WindoorHydroponicsLocked suffix: Hydroponics, Locked components: - - type: AccessReader - access: [["Hydroponics"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsHydroponics ] - type: entity parent: Windoor id: WindoorJanitorLocked suffix: Janitor, Locked components: - - type: AccessReader - access: [["Janitor"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsJanitor ] - type: entity parent: WindoorPlasma id: PlasmaWindoorJanitorLocked suffix: Janitor, Locked, Plasma components: - - type: AccessReader - access: [["Janitor"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsJanitor ] - type: entity parent: Windoor id: WindoorKitchenLocked suffix: Kitchen, Locked components: - - type: AccessReader - access: [["Kitchen"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsKitchen ] - type: entity parent: Windoor id: WindoorKitchenHydroponicsLocked suffix: Kitchen&Hydroponics, Locked components: - - type: AccessReader - access: [["Kitchen"], ["Hydroponics"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsKitchenHydroponics ] - type: entity parent: Windoor id: WindoorServiceLocked suffix: Service, Locked components: - - type: AccessReader - access: [["Service"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsService ] - type: entity parent: Windoor id: WindoorTheatreLocked suffix: Theatre, Locked components: - - type: AccessReader - access: [["Theatre"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsTheatre ] # Secure @@ -138,222 +149,285 @@ id: WindoorSecureArmoryLocked suffix: Armory, Locked components: - - type: AccessReader - access: [["Armory"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsArmory ] + +- type: entity + parent: WindoorSecurePlasma + id: PlasmaWindoorSecureArmoryLocked + suffix: Armory, Locked, Plasma + components: + - type: ContainerFill + containers: + board: [ DoorElectronicsArmory ] - type: entity parent: WindoorSecure id: WindoorSecureAtmosphericsLocked suffix: Atmospherics, Locked components: - - type: AccessReader - access: [["Atmospherics"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsAtmospherics ] + +- type: entity + parent: WindoorSecurePlasma + id: PlasmaWindoorSecureAtmosphericsLocked + suffix: Atmospherics, Locked, Plasma + components: + - type: ContainerFill + containers: + board: [ DoorElectronicsAtmospherics ] - type: entity parent: WindoorSecure id: WindoorSecureBarLocked suffix: Bar, Locked components: - - type: AccessReader - access: [["Bar"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsBar ] -#Delta V: Removed Brig Access -#- type: entity -# parent: WindoorSecureSecurityLocked -# id: WindoorSecureBrigLocked -# suffix: Brig, Locked -# components: -# - type: AccessReader -# access: [["Brig"]] +- type: entity + parent: WindoorSecureSecurityLocked + id: WindoorSecureBrigLocked + suffix: Brig, Locked + components: + - type: ContainerFill + containers: + board: [ DoorElectronicsBrig ] - type: entity parent: WindoorSecure id: WindoorSecureCargoLocked suffix: Logistics, Locked # DeltaV - Logistics Department replacing Cargo components: - - type: AccessReader - access: [["Cargo"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsCargo ] - type: entity parent: WindoorSecure id: WindoorSecureChapelLocked suffix: Chapel, Locked components: - - type: AccessReader - access: [["Chapel"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsChapel ] - type: entity parent: WindoorSecure id: WindoorSecureChemistryLocked suffix: Chemistry, Locked components: - - type: AccessReader - access: [["Chemistry"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsChemistry ] - type: entity parent: WindoorSecurePlasma id: PlasmaWindoorSecureChemistryLocked suffix: Chemistry, Locked, Plasma components: - - type: AccessReader - access: [["Chemistry"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsChemistry ] - type: entity parent: WindoorSecure id: WindoorSecureCentralCommandLocked suffix: Central Command, Locked components: - - type: AccessReader - access: [["CentralCommand"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsCentralCommand ] - type: entity parent: WindoorSecurePlasma id: PlasmaWindoorSecureCentralCommandLocked suffix: Central Command, Locked, Plasma components: - - type: AccessReader - access: [["CentralCommand"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsCentralCommand ] - type: entity parent: WindoorSecureUranium id: UraniumWindoorSecureCentralCommandLocked suffix: Central Command, Locked, Uranium components: - - type: AccessReader - access: [["CentralCommand"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsCentralCommand ] - type: entity parent: WindoorSecure id: WindoorSecureCommandLocked suffix: Command, Locked components: - - type: AccessReader - access: [["Command"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsCommand ] + +- type: entity + parent: WindoorSecurePlasma + id: PlasmaWindoorSecureCommandLocked + suffix: Command, Locked, Plasma + components: + - type: ContainerFill + containers: + board: [ DoorElectronicsCommand ] - type: entity parent: WindoorSecure id: WindoorSecureDetectiveLocked suffix: Detective, Locked components: - - type: AccessReader - access: [["Detective"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsDetective ] - type: entity parent: WindoorSecure id: WindoorSecureEngineeringLocked suffix: Engineering, Locked components: - - type: AccessReader - access: [["Engineering"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsEngineering ] - type: entity parent: WindoorSecurePlasma id: PlasmaWindoorSecureEngineeringLocked suffix: Engineering, Locked, Plasma components: - - type: AccessReader - access: [["Engineering"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsEngineering ] - type: entity parent: WindoorSecureUranium id: UraniumWindoorSecureEngineeringLocked suffix: Engineering, Locked, Uranium components: - - type: AccessReader - access: [["Engineering"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsEngineering ] - type: entity parent: WindoorSecure id: WindoorSecureExternalLocked suffix: External, Locked components: - - type: AccessReader - access: [["External"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsExternal ] - type: entity parent: WindoorSecure id: WindoorSecureJanitorLocked suffix: Janitor, Locked components: - - type: AccessReader - access: [["Janitor"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsJanitor ] - type: entity parent: WindoorSecurePlasma id: PlasmaWindoorSecureJanitorLocked suffix: Janitor, Locked, Plasma components: - - type: AccessReader - access: [["Janitor"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsJanitor ] - type: entity parent: WindoorSecure id: WindoorSecureKitchenLocked suffix: Kitchen, Locked components: - - type: AccessReader - access: [["Kitchen"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsKitchen ] - type: entity parent: WindoorSecureSecurityLocked id: WindoorSecureSecurityLawyerLocked suffix: Security/Lawyer, Locked components: - - type: AccessReader - access: [["Security"], ["Lawyer"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsSecurityLawyer ] - type: entity parent: WindoorSecure id: WindoorSecureMedicalLocked suffix: Medical, Locked components: - - type: AccessReader - access: [["Medical"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsMedical ] - type: entity parent: WindoorSecure id: WindoorSecureSalvageLocked suffix: Salvage, Locked components: - - type: AccessReader - access: [["Salvage"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsSalvage ] - type: entity parent: WindoorSecure id: WindoorSecureSecurityLocked suffix: Security, Locked components: - - type: AccessReader - access: [["Security"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsSecurity ] + +- type: entity + parent: WindoorSecurePlasma + id: PlasmaWindoorSecureSecurityLocked + suffix: Security, Locked, Plasma + components: + - type: ContainerFill + containers: + board: [ DoorElectronicsSecurity ] - type: entity parent: WindoorSecure id: WindoorSecureScienceLocked suffix: Epistemics, Locked # DeltaV - Epistemics Department replacing Science components: - - type: AccessReader - access: [["Research"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsResearch ] - type: entity parent: WindoorSecurePlasma id: PlasmaWindoorSecureScienceLocked suffix: Science, Locked, Plasma components: - - type: AccessReader - access: [["Research"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsResearch ] - type: entity parent: WindoorSecure id: WindoorSecureServiceLocked suffix: Service, Locked components: - - type: AccessReader - access: [["Service"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsService ] - type: entity parent: WindoorSecure id: WindoorSecureHeadOfPersonnelLocked suffix: HeadOfPersonnel, Locked components: - - type: AccessReader - access: [["HeadOfPersonnel"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsHeadOfPersonnel ] diff --git a/Resources/Prototypes/Entities/Structures/Furniture/Tables/base_structuretables.yml b/Resources/Prototypes/Entities/Structures/Furniture/Tables/base_structuretables.yml index c1b0cf0423..dbef5a7504 100644 --- a/Resources/Prototypes/Entities/Structures/Furniture/Tables/base_structuretables.yml +++ b/Resources/Prototypes/Entities/Structures/Furniture/Tables/base_structuretables.yml @@ -20,6 +20,7 @@ - TableMask layer: - TableLayer + - BulletImpassable - type: SpriteFade - type: Sprite - type: Icon @@ -38,7 +39,8 @@ - type: FootstepModifier footstepSoundCollection: collection: FootstepHull - + - type: RequireProjectileTarget + - type: entity id: CounterBase parent: TableBase diff --git a/Resources/Prototypes/Entities/Structures/Furniture/Tables/operating_table.yml b/Resources/Prototypes/Entities/Structures/Furniture/Tables/operating_table.yml index 75cffd91f5..45423cc2d4 100644 --- a/Resources/Prototypes/Entities/Structures/Furniture/Tables/operating_table.yml +++ b/Resources/Prototypes/Entities/Structures/Furniture/Tables/operating_table.yml @@ -2,7 +2,7 @@ id: OperatingTable parent: Bed name: operating table - description: Special medical table for surgery. This one just seems to be a useless prop, though. + description: Used for advanced medical procedures. components: - type: Sprite sprite: Structures/Furniture/Tables/optable.rsi @@ -11,3 +11,4 @@ - type: Icon sprite: Structures/Furniture/Tables/optable.rsi state: operating_table + - type: OperatingTable \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Structures/Furniture/Tables/tables.yml b/Resources/Prototypes/Entities/Structures/Furniture/Tables/tables.yml index e424b1b40b..2a546bc5cf 100644 --- a/Resources/Prototypes/Entities/Structures/Furniture/Tables/tables.yml +++ b/Resources/Prototypes/Entities/Structures/Furniture/Tables/tables.yml @@ -137,7 +137,7 @@ acts: [ "Destruction" ] - trigger: !type:DamageTrigger - damage: 25 + damage: 15 behaviors: - !type:PlaySoundBehavior sound: @@ -178,7 +178,7 @@ acts: [ "Destruction" ] - trigger: !type:DamageTrigger - damage: 25 + damage: 15 behaviors: - !type:PlaySoundBehavior sound: @@ -216,7 +216,7 @@ acts: [ "Destruction" ] - trigger: !type:DamageTrigger - damage: 75 + damage: 30 behaviors: - !type:PlaySoundBehavior sound: @@ -263,7 +263,7 @@ thresholds: - trigger: !type:DamageTrigger - damage: 25 + damage: 15 behaviors: - !type:DoActsBehavior acts: [ "Destruction" ] @@ -379,7 +379,7 @@ collection: GlassBreak - trigger: !type:DamageTrigger - damage: 50 + damage: 30 behaviors: - !type:PlaySoundBehavior sound: @@ -426,7 +426,7 @@ acts: [ "Destruction" ] - trigger: !type:DamageTrigger - damage: 25 + damage: 40 behaviors: - !type:PlaySoundBehavior sound: @@ -443,7 +443,7 @@ - type: Construction graph: Table node: TableBrass - + - type: entity id: TableWood parent: TableBase @@ -546,7 +546,7 @@ thresholds: - trigger: !type:DamageTrigger - damage: 50 + damage: 40 behaviors: - !type:PlaySoundBehavior sound: @@ -573,7 +573,7 @@ thresholds: - trigger: !type:DamageTrigger - damage: 50 + damage: 20 behaviors: - !type:DoActsBehavior acts: [ "Destruction" ] @@ -604,7 +604,7 @@ thresholds: - trigger: !type:DamageTrigger - damage: 25 + damage: 25 behaviors: - !type:PlaySoundBehavior sound: @@ -677,7 +677,7 @@ - type: Construction graph: Table node: TableFancyRed - + - type: entity id: TableFancyPurple parent: TableFancyBase @@ -690,7 +690,7 @@ - type: Construction graph: Table node: TableFancyPurple - + - type: entity id: TableFancyPink parent: TableFancyBase @@ -703,7 +703,7 @@ - type: Construction graph: Table node: TableFancyPink - + - type: entity id: TableFancyGreen parent: TableFancyBase @@ -712,11 +712,11 @@ - type: Sprite sprite: Structures/Furniture/Tables/Fancy/green.rsi - type: Icon - sprite: Structures/Furniture/Tables/Fancy/green.rsi + sprite: Structures/Furniture/Tables/Fancy/green.rsi - type: Construction graph: Table node: TableFancyGreen - + - type: entity id: TableFancyOrange parent: TableFancyBase @@ -729,7 +729,7 @@ - type: Construction graph: Table node: TableFancyOrange - + - type: entity id: TableFancyWhite parent: TableFancyBase diff --git a/Resources/Prototypes/Entities/Structures/Furniture/altar.yml b/Resources/Prototypes/Entities/Structures/Furniture/altar.yml index 117967ef30..1153d5b7cc 100644 --- a/Resources/Prototypes/Entities/Structures/Furniture/altar.yml +++ b/Resources/Prototypes/Entities/Structures/Furniture/altar.yml @@ -25,8 +25,14 @@ - TableLayer - type: Sprite snapCardinals: true - - type: Climbable - type: Clickable + - type: SacrificialAltar + - type: Strap + position: Down + rotation: -90 + - type: GuideHelp + guides: + - AltarsGolemancy - type: entity id: AltarNanotrasen diff --git a/Resources/Prototypes/Entities/Structures/Furniture/bookshelf.yml b/Resources/Prototypes/Entities/Structures/Furniture/bookshelf.yml index 792f076679..30aa793767 100644 --- a/Resources/Prototypes/Entities/Structures/Furniture/bookshelf.yml +++ b/Resources/Prototypes/Entities/Structures/Furniture/bookshelf.yml @@ -59,8 +59,8 @@ - Spellbook - type: UserInterface interfaces: - - key: enum.StorageUiKey.Key - type: StorageBoundUserInterface + enum.StorageUiKey.Key: + type: StorageBoundUserInterface - type: InteractionOutline - type: ContainerContainer containers: diff --git a/Resources/Prototypes/Entities/Structures/Furniture/chairs.yml b/Resources/Prototypes/Entities/Structures/Furniture/chairs.yml index 0fb69b4fdb..14b3270ba8 100644 --- a/Resources/Prototypes/Entities/Structures/Furniture/chairs.yml +++ b/Resources/Prototypes/Entities/Structures/Furniture/chairs.yml @@ -33,7 +33,7 @@ thresholds: - trigger: !type:DamageTrigger - damage: 100 + damage: 25 behaviors: - !type:DoActsBehavior acts: [ "Destruction" ] @@ -48,6 +48,7 @@ collection: MetalBreak - type: StaticPrice price: 10 + - type: RequireProjectileTarget #Starts unanchored, cannot be rotated while anchored - type: entity diff --git a/Resources/Prototypes/Entities/Structures/Furniture/dresser.yml b/Resources/Prototypes/Entities/Structures/Furniture/dresser.yml index 1d0a25ed85..6af67b239e 100644 --- a/Resources/Prototypes/Entities/Structures/Furniture/dresser.yml +++ b/Resources/Prototypes/Entities/Structures/Furniture/dresser.yml @@ -33,8 +33,8 @@ storagebase: !type:Container - type: UserInterface interfaces: - - key: enum.StorageUiKey.Key - type: StorageBoundUserInterface + enum.StorageUiKey.Key: + type: StorageBoundUserInterface - type: InteractionOutline - type: Clickable - type: Tag diff --git a/Resources/Prototypes/Entities/Structures/Furniture/shower.yml b/Resources/Prototypes/Entities/Structures/Furniture/shower.yml index 694202efdc..a8ba4f25a9 100644 --- a/Resources/Prototypes/Entities/Structures/Furniture/shower.yml +++ b/Resources/Prototypes/Entities/Structures/Furniture/shower.yml @@ -24,11 +24,11 @@ canCollide: false - type: InteractionOutline - type: Damageable + damageContainer: Inorganic + damageModifierSet: Metallic - type: Construction graph: Shower node: shower - damageContainer: Inorganic - damageModifierSet: Metallic - type: Destructible thresholds: - trigger: diff --git a/Resources/Prototypes/Entities/Structures/Furniture/toilet.yml b/Resources/Prototypes/Entities/Structures/Furniture/toilet.yml index f02b126248..a3124c09f6 100644 --- a/Resources/Prototypes/Entities/Structures/Furniture/toilet.yml +++ b/Resources/Prototypes/Entities/Structures/Furniture/toilet.yml @@ -51,6 +51,10 @@ disposals: !type:Container - type: Physics bodyType: Static + - type: Fixtures + fixtures: + fix1: + hard: false - type: Construction graph: Toilet node: toilet @@ -66,8 +70,8 @@ price: 25 - type: UserInterface interfaces: - - key: enum.DisposalUnitUiKey.Key - type: DisposalUnitBoundUserInterface + enum.DisposalUnitUiKey.Key: + type: DisposalUnitBoundUserInterface - type: RatKingRummageable - type: SolutionContainerManager solutions: @@ -104,7 +108,10 @@ components: - type: SolutionContainerManager solutions: - toilet: + drainBuffer: + maxVol: 100 + tank: + maxVol: 500 reagents: - ReagentId: Water Quantity: 180 diff --git a/Resources/Prototypes/Entities/Structures/Holographic/projections.yml b/Resources/Prototypes/Entities/Structures/Holographic/projections.yml index f9bf81695e..7d5870bb01 100644 --- a/Resources/Prototypes/Entities/Structures/Holographic/projections.yml +++ b/Resources/Prototypes/Entities/Structures/Holographic/projections.yml @@ -25,6 +25,9 @@ behaviors: - !type:DoActsBehavior acts: [ "Destruction" ] + - type: Tag + tags: + - NoPaint - type: entity id: HoloFan diff --git a/Resources/Prototypes/Entities/Structures/Lighting/base_lighting.yml b/Resources/Prototypes/Entities/Structures/Lighting/base_lighting.yml index 22bbdb7b9f..c6c55a916c 100644 --- a/Resources/Prototypes/Entities/Structures/Lighting/base_lighting.yml +++ b/Resources/Prototypes/Entities/Structures/Lighting/base_lighting.yml @@ -31,6 +31,7 @@ state: glow shader: unshaded state: base + - type: EtherealLight - type: PointLight color: "#FFE4CE" # 5000K color temp energy: 0.8 @@ -43,7 +44,7 @@ - type: RCDDeconstructable cost: 4 delay: 2 - fx: EffectRCDDeconstruct2 + fx: EffectRCDDeconstruct2 - type: Destructible thresholds: - trigger: @@ -74,7 +75,7 @@ mode: SnapgridCenter snap: - Wallmount - + - type: entity name: light description: "A light fixture. Draws power and produces light when equipped with a light tube." @@ -91,7 +92,7 @@ bulb: Tube damage: types: - Heat: 5 + Heat: 2 - type: ContainerContainer containers: light_bulb: !type:ContainerSlot @@ -132,7 +133,7 @@ hasLampOnSpawn: LightTube damage: types: - Heat: 5 + Heat: 2 - type: AmbientOnPowered - type: AmbientSound volume: -15 @@ -151,7 +152,7 @@ hasLampOnSpawn: LedLightTube damage: types: - Heat: 2.5 + Heat: 1 #LEDs don't get as hot - type: PointLight radius: 15 energy: 1 @@ -173,20 +174,19 @@ - type: entity id: PoweredlightExterior description: "A light fixture. Draws power and produces light when equipped with a light tube." - suffix: Blue - noSpawn: true # DeltaV - Don't map these + suffix: Exterior parent: Poweredlight components: - type: PoweredLight hasLampOnSpawn: ExteriorLightTube damage: types: - Heat: 5 + Heat: 4 #brighter light gets hotter - type: entity parent: AlwaysPoweredWallLight id: AlwaysPoweredLightExterior - suffix: Always Powered, Blue + suffix: Always Powered, Exterior components: - type: PointLight radius: 12 @@ -205,7 +205,7 @@ hasLampOnSpawn: SodiumLightTube damage: types: - Heat: 5 + Heat: 2 - type: PointLight radius: 10 energy: 2.5 @@ -292,7 +292,7 @@ bulb: Bulb damage: types: - Heat: 5 + Heat: 2 - type: ApcPowerReceiver - type: ExtensionCableReceiver - type: DeviceNetwork @@ -331,7 +331,7 @@ hasLampOnSpawn: LedLightBulb damage: types: - Heat: 2.5 + Heat: 1 - type: entity id: PoweredSmallLight @@ -346,7 +346,7 @@ hasLampOnSpawn: LightBulb damage: types: - Heat: 5 + Heat: 2 #Emergency Lights - type: entity @@ -361,7 +361,7 @@ radius: 5 energy: 0.6 offset: "0, 0.4" - color: "#FF4020" + color: "#7CFC00" mask: /Textures/Effects/LightMasks/double_cone.png - type: ApcPowerReceiver - type: ExtensionCableReceiver @@ -378,10 +378,10 @@ map: [ "enum.EmergencyLightVisualLayers.Base" ] - state: emergency_light_off map: [ "enum.EmergencyLightVisualLayers.LightOff" ] - color: "#FF4020" + color: "#7CFC00" - state: emergency_light_on map: [ "enum.EmergencyLightVisualLayers.LightOn" ] - color: "#FF4020" + color: "#7CFC00" shader: "unshaded" visible: false - type: Appearance @@ -401,7 +401,7 @@ hasLampOnSpawn: LightTubeCrystalCyan damage: types: - Heat: 5 + Heat: 2 - type: PointLight radius: 8 energy: 3 @@ -428,7 +428,7 @@ hasLampOnSpawn: LightTubeCrystalBlue damage: types: - Heat: 5 + Heat: 2 - type: PointLight radius: 8 energy: 3 @@ -455,7 +455,7 @@ hasLampOnSpawn: LightTubeCrystalPink damage: types: - Heat: 5 + Heat: 2 - type: PointLight radius: 8 energy: 3 @@ -482,7 +482,7 @@ hasLampOnSpawn: LightTubeCrystalOrange damage: types: - Heat: 5 + Heat: 2 - type: PointLight radius: 8 energy: 3 @@ -509,7 +509,7 @@ hasLampOnSpawn: LightTubeCrystalRed damage: types: - Heat: 5 + Heat: 2 - type: PointLight radius: 8 energy: 3 @@ -536,7 +536,7 @@ hasLampOnSpawn: LightTubeCrystalGreen damage: types: - Heat: 5 + Heat: 2 - type: PointLight radius: 8 energy: 3 diff --git a/Resources/Prototypes/Entities/Structures/Lighting/ground_lighting.yml b/Resources/Prototypes/Entities/Structures/Lighting/ground_lighting.yml index 34a6f08294..2afde4ef3f 100644 --- a/Resources/Prototypes/Entities/Structures/Lighting/ground_lighting.yml +++ b/Resources/Prototypes/Entities/Structures/Lighting/ground_lighting.yml @@ -79,7 +79,7 @@ bulb: Tube damage: types: - Heat: 20 + Heat: 2 - type: ContainerContainer containers: light_bulb: !type:ContainerSlot @@ -124,7 +124,7 @@ hasLampOnSpawn: LightTube damage: types: - Heat: 20 + Heat: 2 - type: StaticPrice price: 25 - type: AmbientOnPowered @@ -153,7 +153,7 @@ hasLampOnSpawn: LedLightTube damage: types: - Heat: 20 + Heat: 1 - type: StaticPrice price: 25 - type: AmbientOnPowered diff --git a/Resources/Prototypes/Entities/Structures/Lighting/strobe_lighting.yml b/Resources/Prototypes/Entities/Structures/Lighting/strobe_lighting.yml index 8eceb76b63..72f5439646 100644 --- a/Resources/Prototypes/Entities/Structures/Lighting/strobe_lighting.yml +++ b/Resources/Prototypes/Entities/Structures/Lighting/strobe_lighting.yml @@ -89,7 +89,7 @@ on: false damage: types: - Heat: 5 + Heat: 2 - type: ApcPowerReceiver - type: ExtensionCableReceiver - type: DeviceNetwork @@ -145,4 +145,4 @@ volume: 0 range: 10 sound: - path: "/Audio/Effects/Lightning/strobeepsilon.ogg" \ No newline at end of file + path: "/Audio/Effects/Lightning/strobeepsilon.ogg" diff --git a/Resources/Prototypes/Entities/Structures/Machines/Computers/arcades.yml b/Resources/Prototypes/Entities/Structures/Machines/Computers/arcades.yml index 59690e56eb..98c0f229ed 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/Computers/arcades.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/Computers/arcades.yml @@ -90,9 +90,9 @@ - type: ActivatableUIRequiresPower - type: UserInterface interfaces: - - key: enum.SpaceVillainArcadeUiKey.Key + enum.SpaceVillainArcadeUiKey.Key: type: SpaceVillainArcadeBoundUserInterface - - key: enum.WiresUiKey.Key + enum.WiresUiKey.Key: type: WiresBoundUserInterface - type: Computer board: SpaceVillainArcadeComputerCircuitboard @@ -134,9 +134,9 @@ - type: ActivatableUIRequiresPower - type: UserInterface interfaces: - - key: enum.BlockGameUiKey.Key + enum.BlockGameUiKey.Key: type: BlockGameBoundUserInterface - - key: enum.WiresUiKey.Key + enum.WiresUiKey.Key: type: WiresBoundUserInterface - type: Computer board: BlockGameArcadeComputerCircuitboard diff --git a/Resources/Prototypes/Entities/Structures/Machines/Computers/base_structurecomputers.yml b/Resources/Prototypes/Entities/Structures/Machines/Computers/base_structurecomputers.yml index b7ea7c6f6c..9468ecd452 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/Computers/base_structurecomputers.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/Computers/base_structurecomputers.yml @@ -62,11 +62,11 @@ priority: 1 - type: RequireProjectileTarget - type: LanguageSpeaker - currentLanguage: GalacticCommon + currentLanguage: TauCetiBasic - type: LanguageKnowledge speaks: - - GalacticCommon + - TauCetiBasic - RobotTalk understands: - - GalacticCommon + - TauCetiBasic - RobotTalk diff --git a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml index 4fd2129f7a..df1a56866b 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml @@ -1,8 +1,8 @@ - type: entity parent: BaseComputer id: ComputerAlert - name: alerts computer - description: Used to access the station's automated alert system. + name: atmospheric alerts computer + description: Used to access the station's automated atmospheric alert system. components: - type: Computer board: AlertsComputerCircuitboard @@ -13,9 +13,33 @@ - map: ["computerLayerKeyboard"] state: generic_keyboard - map: ["computerLayerScreen"] - state: alert-2 + state: alert-0 - map: ["computerLayerKeys"] state: atmos_key + - type: GenericVisualizer + visuals: + enum.ComputerVisuals.Powered: + computerLayerScreen: + True: { visible: true, shader: unshaded } + False: { visible: false } + computerLayerKeys: + True: { visible: true, shader: unshaded } + False: { visible: true, shader: shaded } + enum.AtmosAlertsComputerVisuals.ComputerLayerScreen: + computerLayerScreen: + 0: { state: alert-0 } + 1: { state: alert-0 } + 2: { state: alert-1 } + 3: { state: alert-2 } + 4: { state: alert-2 } + - type: AtmosAlertsComputer + - type: ActivatableUI + singleUser: true + key: enum.AtmosAlertsComputerUiKey.Key + - type: UserInterface + interfaces: + enum.AtmosAlertsComputerUiKey.Key: + type: AtmosAlertsComputerBoundUserInterface - type: entity parent: BaseComputer @@ -31,8 +55,8 @@ key: enum.EmergencyConsoleUiKey.Key - type: UserInterface interfaces: - - key: enum.EmergencyConsoleUiKey.Key - type: EmergencyConsoleBoundUserInterface + enum.EmergencyConsoleUiKey.Key: + type: EmergencyConsoleBoundUserInterface - type: PointLight radius: 1.5 energy: 1.6 @@ -52,8 +76,8 @@ key: enum.ShuttleConsoleUiKey.Key - type: UserInterface interfaces: - - key: enum.ShuttleConsoleUiKey.Key - type: ShuttleConsoleBoundUserInterface + enum.ShuttleConsoleUiKey.Key: + type: ShuttleConsoleBoundUserInterface - type: RadarConsole - type: WorldLoader radius: 256 @@ -211,8 +235,8 @@ key: enum.IFFConsoleUiKey.Key - type: UserInterface interfaces: - - key: enum.IFFConsoleUiKey.Key - type: IFFConsoleBoundUserInterface + enum.IFFConsoleUiKey.Key: + type: IFFConsoleBoundUserInterface - type: Computer board: ComputerIFFCircuitboard @@ -231,8 +255,8 @@ key: enum.IFFConsoleUiKey.Key - type: UserInterface interfaces: - - key: enum.IFFConsoleUiKey.Key - type: IFFConsoleBoundUserInterface + enum.IFFConsoleUiKey.Key: + type: IFFConsoleBoundUserInterface - type: Computer board: ComputerIFFSyndicateCircuitboard @@ -271,8 +295,8 @@ key: enum.PowerMonitoringConsoleUiKey.Key - type: UserInterface interfaces: - - key: enum.PowerMonitoringConsoleUiKey.Key - type: PowerMonitoringConsoleBoundUserInterface + enum.PowerMonitoringConsoleUiKey.Key: + type: PowerMonitoringConsoleBoundUserInterface - type: entity parent: BaseComputer @@ -306,8 +330,8 @@ - type: CriminalRecordsConsole - type: UserInterface interfaces: - - key: enum.CriminalRecordsConsoleKey.Key - type: CriminalRecordsConsoleBoundUserInterface + enum.CriminalRecordsConsoleKey.Key: + type: CriminalRecordsConsoleBoundUserInterface - type: ActivatableUI key: enum.CriminalRecordsConsoleKey.Key - type: Sprite @@ -341,8 +365,8 @@ - type: GeneralStationRecordConsole - type: UserInterface interfaces: - - key: enum.GeneralStationRecordConsoleKey.Key - type: GeneralStationRecordConsoleBoundUserInterface + enum.GeneralStationRecordConsoleKey.Key: + type: GeneralStationRecordConsoleBoundUserInterface - type: ActivatableUI key: enum.GeneralStationRecordConsoleKey.Key - type: PointLight @@ -381,7 +405,7 @@ key: enum.CrewMonitoringUIKey.Key - type: UserInterface interfaces: - - key: enum.CrewMonitoringUIKey.Key + enum.CrewMonitoringUIKey.Key: type: CrewMonitoringBoundUserInterface - type: CrewMonitoringConsole - type: DeviceNetwork @@ -416,10 +440,10 @@ key: enum.ResearchConsoleUiKey.Key - type: UserInterface interfaces: - - key: enum.ResearchConsoleUiKey.Key - type: ResearchConsoleBoundUserInterface - - key: enum.ResearchClientUiKey.Key - type: ResearchClientBoundUserInterface + enum.ResearchConsoleUiKey.Key: + type: ResearchConsoleBoundUserInterface + enum.ResearchClientUiKey.Key: + type: ResearchClientBoundUserInterface - type: ApcPowerReceiver powerLoad: 1000 - type: Computer @@ -464,10 +488,10 @@ key: enum.ArtifactAnalzyerUiKey.Key - type: UserInterface interfaces: - - key: enum.ArtifactAnalzyerUiKey.Key - type: AnalysisConsoleBoundUserInterface - - key: enum.ResearchClientUiKey.Key - type: ResearchClientBoundUserInterface + enum.ArtifactAnalzyerUiKey.Key: + type: AnalysisConsoleBoundUserInterface + enum.ResearchClientUiKey.Key: + type: ResearchClientBoundUserInterface - type: ApcPowerReceiver powerLoad: 1000 - type: Computer @@ -509,8 +533,8 @@ key: enum.IdCardConsoleUiKey.Key - type: UserInterface interfaces: - - key: enum.IdCardConsoleUiKey.Key - type: IdCardConsoleBoundUserInterface + enum.IdCardConsoleUiKey.Key: + type: IdCardConsoleBoundUserInterface - type: CrewManifestViewer ownerKey: enum.IdCardConsoleUiKey.Key - type: Sprite @@ -580,8 +604,8 @@ key: enum.CommunicationsConsoleUiKey.Key - type: UserInterface interfaces: - - key: enum.CommunicationsConsoleUiKey.Key - type: CommunicationsConsoleBoundUserInterface + enum.CommunicationsConsoleUiKey.Key: + type: CommunicationsConsoleBoundUserInterface - type: Computer board: CommsComputerCircuitboard - type: PointLight @@ -644,8 +668,8 @@ key: enum.SolarControlConsoleUiKey.Key - type: UserInterface interfaces: - - key: enum.SolarControlConsoleUiKey.Key - type: SolarControlConsoleBoundUserInterface + enum.SolarControlConsoleUiKey.Key: + type: SolarControlConsoleBoundUserInterface - type: Computer board: SolarControlComputerCircuitboard - type: PointLight @@ -674,8 +698,8 @@ key: enum.RadarConsoleUiKey.Key - type: UserInterface interfaces: - - key: enum.RadarConsoleUiKey.Key - type: RadarConsoleBoundUserInterface + enum.RadarConsoleUiKey.Key: + type: RadarConsoleBoundUserInterface - type: Computer board: RadarConsoleCircuitboard - type: PointLight @@ -704,8 +728,8 @@ key: enum.CargoConsoleUiKey.Shuttle - type: UserInterface interfaces: - - key: enum.CargoConsoleUiKey.Shuttle - type: CargoShuttleConsoleBoundUserInterface + enum.CargoConsoleUiKey.Shuttle: + type: CargoShuttleConsoleBoundUserInterface - type: Computer board: CargoShuttleComputerCircuitboard - type: PointLight @@ -735,12 +759,15 @@ - map: ["computerLayerKeys"] state: tech_key - type: CargoOrderConsole + - type: ActiveRadio + channels: + - Supply - type: ActivatableUI key: enum.CargoConsoleUiKey.Orders - type: UserInterface interfaces: - - key: enum.CargoConsoleUiKey.Orders - type: CargoOrderConsoleBoundUserInterface + enum.CargoConsoleUiKey.Orders: + type: CargoOrderConsoleBoundUserInterface - type: Computer board: CargoRequestComputerCircuitboard - type: PointLight @@ -748,7 +775,7 @@ energy: 1.6 color: "#b89f25" - type: AccessReader - access: [["Orders"]] # DeltaV - see Resources/Prototypes/DeltaV/Access/cargo.yml + access: [["Cargo"]] - type: DeviceNetwork deviceNetId: Wireless receiveFrequencyId: BasicDevice @@ -783,8 +810,8 @@ key: enum.CargoConsoleUiKey.Bounty - type: UserInterface interfaces: - - key: enum.CargoConsoleUiKey.Bounty - type: CargoBountyConsoleBoundUserInterface + enum.CargoConsoleUiKey.Bounty: + type: CargoBountyConsoleBoundUserInterface - type: Computer board: CargoBountyComputerCircuitboard - type: PointLight @@ -834,8 +861,8 @@ key: enum.CloningConsoleUiKey.Key - type: UserInterface interfaces: - - key: enum.CloningConsoleUiKey.Key - type: CloningConsoleBoundUserInterface + enum.CloningConsoleUiKey.Key: + type: CloningConsoleBoundUserInterface - type: Speech speechVerb: Robotic speechSounds: Pai @@ -878,7 +905,7 @@ - type: ActivatableUIRequiresPower - type: UserInterface interfaces: - - key: enum.SalvageConsoleUiKey.Expedition + enum.SalvageConsoleUiKey.Expedition: type: SalvageExpeditionConsoleBoundUserInterface - type: Computer board: SalvageExpeditionsComputerCircuitboard @@ -923,8 +950,8 @@ anchored: true - type: UserInterface interfaces: - - key: enum.SurveillanceCameraMonitorUiKey.Key - type: SurveillanceCameraMonitorBoundUserInterface + enum.SurveillanceCameraMonitorUiKey.Key: + type: SurveillanceCameraMonitorBoundUserInterface - type: entity parent: BaseComputer @@ -960,8 +987,8 @@ - type: ActivatableUIRequiresVision - type: UserInterface interfaces: - - key: enum.SurveillanceCameraMonitorUiKey.Key - type: SurveillanceCameraMonitorBoundUserInterface + enum.SurveillanceCameraMonitorUiKey.Key: + type: SurveillanceCameraMonitorBoundUserInterface - type: entity id: ComputerPalletConsole @@ -987,8 +1014,8 @@ key: enum.CargoPalletConsoleUiKey.Sale - type: UserInterface interfaces: - - key: enum.CargoPalletConsoleUiKey.Sale - type: CargoPalletConsoleBoundUserInterface + enum.CargoPalletConsoleUiKey.Sale: + type: CargoPalletConsoleBoundUserInterface - type: Computer board: CargoRequestComputerCircuitboard - type: PointLight @@ -1028,8 +1055,8 @@ anchored: true - type: UserInterface interfaces: - - key: enum.NewsWriterUiKey.Key - type: NewsWriterBoundUserInterface + enum.NewsWriterUiKey.Key: + type: NewsWriterBoundUserInterface - type: entity parent: BaseComputer @@ -1061,7 +1088,7 @@ key: enum.SensorMonitoringConsoleUiKey.Key - type: UserInterface interfaces: - - key: enum.SensorMonitoringConsoleUiKey.Key + enum.SensorMonitoringConsoleUiKey.Key: type: SensorMonitoringConsoleBoundUserInterface - type: DeviceNetwork deviceNetId: AtmosDevices @@ -1073,3 +1100,42 @@ - type: WiredNetworkConnection - type: DeviceList - type: AtmosDevice + +- type: entity + parent: BaseComputer + id: ComputerRoboticsControl + name: robotics control console + description: Used to remotely monitor, disable and destroy the station's cyborgs. + components: + - type: Sprite + layers: + - map: ["computerLayerBody"] + state: computer + - map: ["computerLayerKeyboard"] + state: generic_keyboard + - map: ["computerLayerScreen"] + state: robot + - map: ["computerLayerKeys"] + state: rd_key + - type: RoboticsConsole + - type: ActiveRadio + channels: + - Science + - type: ActivatableUI + key: enum.RoboticsConsoleUiKey.Key + - type: UserInterface + interfaces: + enum.RoboticsConsoleUiKey.Key: + type: RoboticsConsoleBoundUserInterface + - type: ApcPowerReceiver + powerLoad: 1000 + - type: DeviceNetwork + deviceNetId: Wireless + receiveFrequencyId: RoboticsConsole + transmitFrequencyId: CyborgControl + - type: Computer + board: RoboticsConsoleCircuitboard + - type: AccessReader # only used for dangerous things + access: [["ResearchDirector"]] + - type: Lock + unlockOnClick: false diff --git a/Resources/Prototypes/Entities/Structures/Machines/Computers/frame.yml b/Resources/Prototypes/Entities/Structures/Machines/Computers/frame.yml index 08e3173334..11d691887a 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/Computers/frame.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/Computers/frame.yml @@ -40,6 +40,9 @@ node: monitorBroken - !type:DoActsBehavior acts: ["Destruction"] + - type: InteractionVerbs + allowedVerbs: + - KnockOn - type: entity id: ComputerFrame diff --git a/Resources/Prototypes/Entities/Structures/Machines/Computers/techdiskterminal.yml b/Resources/Prototypes/Entities/Structures/Machines/Computers/techdiskterminal.yml index df989a7795..dd58b9709d 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/Computers/techdiskterminal.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/Computers/techdiskterminal.yml @@ -19,10 +19,10 @@ - type: ActivatableUIRequiresPower - type: UserInterface interfaces: - - key: enum.DiskConsoleUiKey.Key - type: DiskConsoleBoundUserInterface - - key: enum.ResearchClientUiKey.Key - type: ResearchClientBoundUserInterface + enum.DiskConsoleUiKey.Key: + type: DiskConsoleBoundUserInterface + enum.ResearchClientUiKey.Key: + type: ResearchClientBoundUserInterface - type: ExtensionCableReceiver - type: Computer board: TechDiskComputerCircuitboard diff --git a/Resources/Prototypes/Entities/Structures/Machines/Medical/cryo_pod.yml b/Resources/Prototypes/Entities/Structures/Machines/Medical/cryo_pod.yml index 6ac969171e..4cb76ea4b9 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/Medical/cryo_pod.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/Medical/cryo_pod.yml @@ -89,9 +89,9 @@ scanDelay: 0 - type: UserInterface interfaces: - - key: enum.HealthAnalyzerUiKey.Key + enum.HealthAnalyzerUiKey.Key: type: HealthAnalyzerBoundUserInterface - - key: enum.WiresUiKey.Key + enum.WiresUiKey.Key: type: WiresBoundUserInterface - type: ActivatableUI key: enum.HealthAnalyzerUiKey.Key diff --git a/Resources/Prototypes/Entities/Structures/Machines/anomaly_equipment.yml b/Resources/Prototypes/Entities/Structures/Machines/anomaly_equipment.yml index ab09a03fef..09ee110054 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/anomaly_equipment.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/anomaly_equipment.yml @@ -27,8 +27,8 @@ - type: ActivatableUIRequiresPower - type: UserInterface interfaces: - - key: enum.ResearchClientUiKey.Key - type: ResearchClientBoundUserInterface + enum.ResearchClientUiKey.Key: + type: ResearchClientBoundUserInterface - type: Machine board: AnomalyVesselCircuitboard - type: PointLight @@ -176,8 +176,8 @@ SetParticleZeta: AnomalousParticleZeta SetParticleSigma: AnomalousParticleSigma fireBurstSize: 1 - fireBurstDelayMin: 2 - fireBurstDelayMax: 6 + baseFireBurstDelayMin: 2 + baseFireBurstDelayMax: 6 - type: ApcPowerReceiver powerLoad: 100 - type: GuideHelp @@ -298,8 +298,8 @@ - type: ActivatableUIRequiresPower - type: UserInterface interfaces: - - key: enum.AnomalyGeneratorUiKey.Key - type: AnomalyGeneratorBoundUserInterface + enum.AnomalyGeneratorUiKey.Key: + type: AnomalyGeneratorBoundUserInterface - type: Appearance - type: ActiveRadio channels: diff --git a/Resources/Prototypes/Entities/Structures/Machines/anomaly_sync.yml b/Resources/Prototypes/Entities/Structures/Machines/anomaly_sync.yml index 3019b8adf8..eeaae611c3 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/anomaly_sync.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/anomaly_sync.yml @@ -41,7 +41,7 @@ bounds: "-0.35,-0.35,0.35,0.35" density: 100 mask: - - ItemMask + - ItemMask hard: True - type: Transform anchored: true @@ -49,6 +49,9 @@ - type: ApcPowerReceiver powerLoad: 2500 needsPower: true + - type: UpgradePowerDraw + powerDrawMultiplier: 0.80 + scaling: Exponential - type: ItemPlacer whitelist: components: diff --git a/Resources/Prototypes/Entities/Structures/Machines/artifact_analyzer.yml b/Resources/Prototypes/Entities/Structures/Machines/artifact_analyzer.yml index 18999a6ab2..747b927e2f 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/artifact_analyzer.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/artifact_analyzer.yml @@ -44,6 +44,7 @@ powerLoad: 12000 needsPower: false #only turns on when scanning - type: ArtifactAnalyzer + - type: TraversalDistorter - type: ItemPlacer whitelist: components: @@ -107,6 +108,9 @@ hard: False - type: Transform noRot: false + - type: UpgradePowerDraw + powerDrawMultiplier: 0.80 + scaling: Exponential - type: TraversalDistorter - type: ItemPlacer # don't limit the number of artifacts that can be biased diff --git a/Resources/Prototypes/Entities/Structures/Machines/base_structuremachines.yml b/Resources/Prototypes/Entities/Structures/Machines/base_structuremachines.yml index fb5ed4440a..95f40117e5 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/base_structuremachines.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/base_structuremachines.yml @@ -41,6 +41,9 @@ - !type:PlaySoundBehavior sound: collection: MetalBreak + - type: InteractionVerbs + allowedVerbs: + - KnockOn - type: entity abstract: true diff --git a/Resources/Prototypes/Entities/Structures/Machines/bombs.yml b/Resources/Prototypes/Entities/Structures/Machines/bombs.yml index 89cadab6b1..88e650068e 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/bombs.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/bombs.yml @@ -10,7 +10,7 @@ - type: InteractionOutline - type: UserInterface interfaces: - - key: enum.WiresUiKey.Key + enum.WiresUiKey.Key: type: WiresBoundUserInterface - type: Wires layoutId: Defusable diff --git a/Resources/Prototypes/Entities/Structures/Machines/chem_master.yml b/Resources/Prototypes/Entities/Structures/Machines/chem_master.yml index aee1246021..4e565054b4 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/chem_master.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/chem_master.yml @@ -50,8 +50,8 @@ - type: ActivatableUIRequiresPower - type: UserInterface interfaces: - - key: enum.ChemMasterUiKey.Key - type: ChemMasterBoundUserInterface + enum.ChemMasterUiKey.Key: + type: ChemMasterBoundUserInterface - type: ApcPowerReceiver powerLoad: 250 - type: Appearance diff --git a/Resources/Prototypes/Entities/Structures/Machines/fax_machine.yml b/Resources/Prototypes/Entities/Structures/Machines/fax_machine.yml index a852580c1e..fdb6c1c4c5 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/fax_machine.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/fax_machine.yml @@ -1,4 +1,4 @@ -- type: entity +- type: entity parent: BaseMachinePowered id: FaxMachineBase name: long range fax machine @@ -9,7 +9,7 @@ drawdepth: SmallObjects layers: - state: icon - map: ["base"] + map: [ "enum.FaxMachineVisuals.VisualState" ] - type: Icon sprite: Structures/Machines/fax_machine.rsi state: icon @@ -32,30 +32,34 @@ - type: ActivatableUIRequiresPower - type: UserInterface interfaces: - - key: enum.FaxUiKey.Key - type: FaxBoundUi + enum.FaxUiKey.Key: + type: FaxBoundUi - type: ApcPowerReceiver powerLoad: 250 + - type: Faxecute + damage: + types: + Blunt: 100 - type: FaxMachine paperSlot: insertSound: /Audio/Machines/scanning.ogg ejectSound: /Audio/Machines/tray_eject.ogg whitelist: components: - - Paper + - FaxableObject blacklist: tags: - - PaperSlip # DeltaV - Prevent the faxing of paper slips. + - PaperSlip - type: GenericVisualizer visuals: enum.PowerDeviceVisuals.Powered: - base: + enum.FaxMachineVisuals.VisualState: True: { state: idle } False: { state: icon } enum.FaxMachineVisuals.VisualState: - base: - Inserting: { state: inserting } + enum.FaxMachineVisuals.VisualState: Printing: { state: printing } + Normal: {state: idle} - type: ItemSlots - type: ContainerContainer containers: diff --git a/Resources/Prototypes/Entities/Structures/Machines/flatpacker.yml b/Resources/Prototypes/Entities/Structures/Machines/flatpacker.yml index 3244789a02..e83cbfe873 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/flatpacker.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/flatpacker.yml @@ -58,8 +58,8 @@ - type: ActivatableUIRequiresPower - type: UserInterface interfaces: - - key: enum.FlatpackCreatorUIKey.Key - type: FlatpackCreatorBoundUserInterface + enum.FlatpackCreatorUIKey.Key: + type: FlatpackCreatorBoundUserInterface - type: ItemSlots slots: board_slot: @@ -83,8 +83,7 @@ - type: entity id: FlatpackerNoBoardEffect - categories: - - hideSpawnMenu + categories: [ HideSpawnMenu ] components: - type: Sprite sprite: Structures/Machines/autolathe.rsi diff --git a/Resources/Prototypes/Entities/Structures/Machines/gateway.yml b/Resources/Prototypes/Entities/Structures/Machines/gateway.yml index b6ad9db356..dd01adf6d5 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/gateway.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/gateway.yml @@ -52,6 +52,6 @@ key: enum.GatewayUiKey.Key - type: UserInterface interfaces: - - key: enum.GatewayUiKey.Key - type: GatewayBoundUserInterface + enum.GatewayUiKey.Key: + type: GatewayBoundUserInterface - type: Gateway diff --git a/Resources/Prototypes/Entities/Structures/Machines/gravity_generator.yml b/Resources/Prototypes/Entities/Structures/Machines/gravity_generator.yml index 618538dccb..6ee454c6a9 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/gravity_generator.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/gravity_generator.yml @@ -62,8 +62,8 @@ on: "on" - type: UserInterface interfaces: - - key: enum.GravityGeneratorUiKey.Key - type: GravityGeneratorBoundUserInterface + enum.GravityGeneratorUiKey.Key: + type: GravityGeneratorBoundUserInterface - type: Appearance - type: PointLight radius: 2.5 diff --git a/Resources/Prototypes/Entities/Structures/Machines/hotplate.yml b/Resources/Prototypes/Entities/Structures/Machines/hotplate.yml index 3764f13591..003916ce94 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/hotplate.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/hotplate.yml @@ -43,7 +43,7 @@ map: ["enum.SolutionHeaterVisuals.IsOn"] shader: unshaded - type: SolutionHeater - heatPerSecond: 160 + baseHeatPerSecond: 160 - type: ItemPlacer whitelist: components: diff --git a/Resources/Prototypes/Entities/Structures/Machines/jukebox.yml b/Resources/Prototypes/Entities/Structures/Machines/jukebox.yml new file mode 100644 index 0000000000..acd8527dbb --- /dev/null +++ b/Resources/Prototypes/Entities/Structures/Machines/jukebox.yml @@ -0,0 +1,59 @@ +- type: entity + id: Jukebox + name: jukebox + parent: [ BaseMachinePowered, ConstructibleMachine ] + description: A machine capable of playing a wide variety of tunes. Enjoyment not guaranteed. + components: + - type: Sprite + sprite: Structures/Machines/jukebox.rsi + layers: + - state: "off" + map: ["enum.JukeboxVisualLayers.Base"] + - type: Transform + anchored: true + - type: Jukebox + onState: on + offState: off + selectState: select + - type: Machine + board: JukeboxCircuitBoard + - type: Appearance + - type: ApcPowerReceiver + powerLoad: 100 + - type: ExtensionCableReceiver + - type: ActivatableUI + key: enum.JukeboxUiKey.Key + - type: ActivatableUIRequiresPower + - type: UserInterface + interfaces: + enum.JukeboxUiKey.Key: + type: JukeboxBoundUserInterface + - type: Damageable + damageContainer: Inorganic + damageModifierSet: Metallic + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 75 + behaviors: + - !type:DoActsBehavior + acts: ["Destruction"] + - !type:SpawnEntitiesBehavior + spawn: + SheetSteel1: + min: 1 + max: 2 + - type: Physics + bodyType: Static + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeAabb + bounds: "-0.25,-0.45,0.25,0.45" + mask: + - MachineMask + layer: + - MachineLayer + density: 200 diff --git a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml index de72dfcd5e..38a6185d58 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml @@ -38,10 +38,10 @@ - type: ActivatableUIRequiresPower - type: UserInterface interfaces: - - key: enum.LatheUiKey.Key - type: LatheBoundUserInterface - - key: enum.ResearchClientUiKey.Key - type: ResearchClientBoundUserInterface + enum.LatheUiKey.Key: + type: LatheBoundUserInterface + enum.ResearchClientUiKey.Key: + type: ResearchClientBoundUserInterface - type: Transform anchored: true - type: Pullable @@ -50,11 +50,46 @@ - type: ResearchClient - type: TechnologyDatabase +# a lathe that can be sped up with space lube / slowed down with glue - type: entity - id: Autolathe + abstract: true parent: BaseLathe + id: BaseLatheLube + components: + - type: ReagentSpeed + solution: lube + modifiers: + SpaceLube: 0.25 + SpaceGlue: 5 + - type: SolutionContainerManager + solutions: + lube: + maxVol: 250 + - type: Spillable + solution: lube + - type: RefillableSolution + solution: lube + - type: ExaminableSolution + solution: lube + +- type: entity + abstract: true + id: BaseHyperlathe + components: + - type: Lathe + materialUseMultiplier: 0.5 + timeMultiplier: 1.5 + - type: LatheHeatProducing + - type: ReagentSpeed + modifiers: + SpaceLube: 0.8 # being faster means less heat so lube needs to be nerfed + SpaceGlue: 5 # no change from normal lathe, overheat!!! + +- type: entity + id: Autolathe + parent: BaseLatheLube name: autolathe - description: It produces items using metal and glass. + description: It produces basic items using metal and glass. components: - type: Sprite sprite: Structures/Machines/autolathe.rsi @@ -121,6 +156,7 @@ - DrinkGlass - DrinkShotGlass - DrinkGlassCoupeShaped + - CustomDrinkJug - FoodPlate - FoodPlateSmall - FoodPlatePlastic @@ -162,88 +198,46 @@ - ClothingHeadHatWelding - type: EmagLatheRecipes emagStaticRecipes: - - CartridgePistol - - CartridgeMagnum - - ShellShotgun - - ShellShotgunFlare - - ShellTranquilizer - - CartridgeLightRifle - - CartridgeRifle - - CombatKnife - - MagazineBoxPistol - - MagazineBoxMagnum - - MagazineBoxRifle - - MagazineBoxLightRifle - - GrenadeBlast - # DeltaV - .38 special ammo - Add .38 special lethals to emagged autolathe - - CartridgeSpecial - - MagazineBoxSpecial - # End of modified code - emagDynamicRecipes: - - CartridgePistolRubber - - CartridgeMagnumRubber - - ShellShotgunBeanbag - - CartridgeRifleRubber - - CartridgeLightRifleRubber - - MagazineBoxPistolRubber - - MagazineBoxMagnumRubber - - MagazineBoxRifleRubber - - MagazineBoxLightRifleRubber - - ShellShotgunIncendiary - - CartridgePistolIncendiary - - CartridgeMagnumIncendiary - - CartridgeLightRifleIncendiary - - CartridgeRifleIncendiary - - MagazineBoxPistolIncendiary - - MagazineBoxMagnumIncendiary - - MagazineBoxLightRifleIncendiary - - MagazineBoxRifleIncendiary - - ShellShotgunUranium - - CartridgePistolUranium - - CartridgeMagnumUranium - - CartridgeLightRifleUranium - - CartridgeRifleUranium - - MagazineBoxPistolUranium - - MagazineBoxMagnumUranium - - MagazineBoxLightRifleUranium - - MagazineBoxRifleUranium - - PowerCageSmall - - PowerCageMedium - - PowerCageHigh - - MagazineGrenadeEmpty - - GrenadeEMP - - GrenadeFlash - # DeltaV - .38 special ammo - Add various .38 special ammo to emagged autolathe - - CartridgeSpecialRubber - - CartridgeSpecialIncendiary - - CartridgeSpecialUranium - - CartridgeSpecialMindbreaker - - MagazineBoxSpecialRubber - - MagazineBoxSpecialIncendiary - - MagazineBoxSpecialUranium - - MagazineBoxSpecialMindbreaker - # End of modified code + - BoxLethalshot + - BoxShotgunFlare + - BoxShotgunSlug + - MagazineBoxLightRifle + - MagazineBoxMagnum + - MagazineBoxPistol + - MagazineBoxRifle + - MagazineLightRifle + - MagazineLightRifleEmpty + - MagazinePistol + - MagazinePistolEmpty + - MagazinePistolSubMachineGun + - MagazinePistolSubMachineGunEmpty + - MagazinePistolSubMachineGunTopMounted + - MagazinePistolSubMachineGunTopMountedEmpty + - MagazineRifle + - MagazineRifleEmpty + - MagazineShotgun + - MagazineShotgunEmpty + - MagazineShotgunSlug + - RiotShield + - SpeedLoaderMagnum + - SpeedLoaderMagnumEmpty - type: entity id: AutolatheHyperConvection - parent: Autolathe + parent: [Autolathe, BaseHyperlathe] name: hyper convection autolathe description: A highly-experimental autolathe that harnesses the power of extreme heat to slowly create objects more cost-effectively. components: - type: Sprite sprite: Structures/Machines/autolathe_hypercon.rsi - - type: Lathe - materialUseMultiplier: 0.5 - timeMultiplier: 1.5 - - type: LatheHeatProducing - type: Machine board: AutolatheHyperConvectionMachineCircuitboard - type: entity id: Protolathe - parent: BaseLathe + parent: BaseLatheLube name: protolathe - description: Converts raw materials into useful objects. + description: Converts raw materials into advanced items. components: - type: Sprite sprite: Structures/Machines/protolathe.rsi @@ -294,6 +288,15 @@ - Implanter - PillCanister - ChemistryEmptyBottle01 + - AdvancedCapacitorStockPart + - AdvancedMatterBinStockPart + - NanoManipulatorStockPart + - SuperCapacitorStockPart + - SuperMatterBinStockPart + - PicoManipulatorStockPart + - BluespaceCapacitorStockPart + - BluespaceManipulatorStockPart + - BluespaceMatterBinStockPart - AdvMopItem - WeaponSprayNozzle - ClothingBackpackWaterTank @@ -310,6 +313,7 @@ - PowerCellMicroreactor - PowerCellHigh - WeaponPistolCHIMP + - ClothingMaskWeldingGas - WeaponGauntletGorilla - SynthesizerInstrument - RPED @@ -319,11 +323,11 @@ - HolofanProjector - BluespaceBeaker - SyringeBluespace - #- WeaponForceGun + - WeaponForceGun - WeaponLaserSvalinn - WeaponProtoKineticAccelerator - #- WeaponTetherGun - #- WeaponGrapplingGun + - WeaponTetherGun + - WeaponGrapplingGun - ClothingBackpackHolding - ClothingBackpackSatchelHolding - ClothingBackpackDuffelHolding @@ -355,12 +359,48 @@ - AnimalTranslator - MofficTranslatorImplanter - MofficTranslator + - RCDAmmo #DeltaV + - RCD #EE - type: EmagLatheRecipes emagDynamicRecipes: + - BoxBeanbag + - BoxShotgunIncendiary + - BoxShotgunUranium - ExplosivePayload - - WeaponLaserCarbine + - GrenadeBlast + - GrenadeEMP + - GrenadeFlash + - HoloprojectorSecurity + - MagazineBoxLightRifleIncendiary + - MagazineBoxLightRifleUranium + - MagazineBoxMagnumIncendiary + - MagazineBoxMagnumUranium + - MagazineBoxPistolIncendiary + - MagazineBoxPistolUranium + - MagazineBoxRifleIncendiary + - MagazineBoxRifleUranium + - MagazineGrenadeEmpty + - MagazineLightRifleIncendiary + - MagazineLightRifleUranium + - MagazinePistolIncendiary + - MagazinePistolUranium + - MagazineRifleIncendiary + - MagazineRifleUranium + - MagazineShotgunBeanbag + - MagazineShotgunIncendiary + - MagazineShotgunIncendiary + - PortableRecharger + - PowerCageHigh + - PowerCageMedium + - PowerCageSmall + - ShellTranquilizer + - SpeedLoaderMagnumIncendiary + - SpeedLoaderMagnumUranium + - TelescopicShield + - Truncheon - WeaponAdvancedLaser - WeaponLaserCannon + - WeaponLaserCarbine - WeaponXrayCannon - WeaponEnergyGun # DeltaV - Energy Gun - WeaponEnergyGunMini # DeltaV - Miniature Energy Gun @@ -369,22 +409,18 @@ - type: entity id: ProtolatheHyperConvection - parent: Protolathe + parent: [Protolathe, BaseHyperlathe] name: hyper convection protolathe description: A highly-experimental protolathe that harnesses the power of extreme heat to slowly create objects more cost-effectively. components: - type: Sprite sprite: Structures/Machines/protolathe_hypercon.rsi - - type: Lathe - materialUseMultiplier: 0.5 - timeMultiplier: 1.5 - - type: LatheHeatProducing - type: Machine board: ProtolatheHyperConvectionMachineCircuitboard - type: entity id: CircuitImprinter - parent: BaseLathe + parent: BaseLatheLube name: circuit imprinter description: Prints circuit boards for machines. components: @@ -443,6 +479,7 @@ - SurveillanceWirelessCameraMonitorCircuitboard - SurveillanceCameraWirelessRouterCircuitboard - ComputerTelevisionCircuitboard + - JukeboxCircuitBoard - SurveillanceWirelessCameraMovableCircuitboard - SurveillanceWirelessCameraAnchoredCircuitboard - HydroponicsTrayMachineCircuitboard @@ -452,6 +489,7 @@ - PowerComputerCircuitboard - AutolatheHyperConvectionMachineCircuitboard - ProtolatheHyperConvectionMachineCircuitboard + - CircuitImprinterHyperConvectionMachineCircuitboard - FatExtractorMachineCircuitboard - FlatpackerMachineCircuitboard - SheetifierMachineCircuitboard @@ -491,12 +529,10 @@ - AnomalySynchronizerCircuitboard - APECircuitboard - ArtifactAnalyzerMachineCircuitboard - - TraversalDistorterMachineCircuitboard - ArtifactCrusherMachineCircuitboard - TelecomServerCircuitboard - MassMediaCircuitboard - ReagentGrinderIndustrialMachineCircuitboard - # Begin Nyano additions - ReverseEngineeringMachineCircuitboard - CrewMonitoringComputerCircuitboard - DoorElectronics @@ -507,8 +543,14 @@ - SalvageMagnetMachineCircuitboard - StationMapElectronics - MetempsychoticMachineCircuitboard - # End Nyano additions - - SalvageExpeditionsComputerCircuitboard # DeltaV + - SalvageExpeditionsComputerCircuitboard + - JukeboxCircuitBoard + - type: EmagLatheRecipes + emagDynamicRecipes: + - ShuttleGunDusterCircuitboard + - ShuttleGunFriendshipCircuitboard + - ShuttleGunPerforatorCircuitboard + - ShuttleGunSvalinnMachineGunCircuitboard - type: MaterialStorage whitelist: tags: @@ -517,9 +559,20 @@ - Ingot - type: RequireProjectileTarget +- type: entity + id: CircuitImprinterHyperConvection + parent: [CircuitImprinter, BaseHyperlathe] + name: hyper convection circuit imprinter + description: A highly-experimental circuit imprinter that harnesses the power of extreme heat to slowly create objects more cost-effectively. + components: + - type: Sprite + sprite: Structures/Machines/circuit_imprinter_hypercon.rsi + - type: Machine + board: CircuitImprinterHyperConvectionMachineCircuitboard + - type: entity id: ExosuitFabricator - parent: BaseLathe + parent: BaseLatheLube name: exosuit fabricator description: Creates parts for robotics and other mechanical needs components: @@ -590,7 +643,7 @@ - BorgModuleLightReplacer - BorgModuleAdvancedCleaning - BorgModuleMining - #- BorgModuleGrapplingGun + - BorgModuleGrapplingGun - BorgModuleAdvancedTool - BorgModuleGPS - BorgModuleRCD @@ -625,8 +678,6 @@ - HamtrRLeg - VimHarness # Begin Nyano additions - - ClothingOuterHardsuitJuggernautReverseEngineered - - ClothingOuterHardsuitSyndieReverseEngineered - JetpackBlue - JetpackMini # End Nyano additions @@ -685,7 +736,7 @@ - type: entity id: SecurityTechFab - parent: BaseLathe + parent: BaseLatheLube name: security techfab description: Prints equipment for use by security crew. components: @@ -708,49 +759,65 @@ idleState: icon runningState: icon staticRecipes: + - BoxLethalshot + - BoxShotgunFlare + - BoxShotgunPractice + - BoxShotgunSlug - ClothingEyesHudSecurity - CombatKnife - Flash - - Handcuffs - - Zipties - - Stunbaton - ForensicPad - - RiotShield + - Handcuffs - ShellShotgun - ShellShotgunSlug - ShellShotgunFlare - ShellTranquilizer + - MagazineBoxLightRifle + - MagazineBoxLightRiflePractice + - MagazineBoxMagnum + - MagazineBoxMagnumPractice + - MagazineBoxPistol + - MagazineBoxPistolPractice + - MagazineBoxRifle + - MagazineBoxRiflePractice + - MagazineLightRifle + - MagazineLightRifleEmpty - MagazinePistol + - MagazinePistolEmpty - MagazinePistolSubMachineGun + - MagazinePistolSubMachineGunEmpty - MagazinePistolSubMachineGunTopMounted + - MagazinePistolSubMachineGunTopMountedEmpty - MagazineRifle - - MagazineLightRifle - - MagazineBoxPistol - - MagazineBoxMagnum - - MagazineBoxRifle - - MagazineBoxLightRifle + - MagazineRifleEmpty + - MagazineShotgun + - MagazineShotgunEmpty + - MagazineShotgunSlug + - RiotShield - SpeedLoaderMagnum - - ClothingOuterArmorPlateCarrier # DeltaV - plate carrier body armour - - ClothingOuterArmorDuraVest # DeltaV - stabproof vest body armour + - SpeedLoaderMagnumEmpty + - Stunbaton + - TargetClown + - ClothingOuterArmorPlateCarrier + - ClothingOuterArmorDuraVest - TargetHuman - TargetSyndicate - - TargetClown - - MagazineBoxLightRiflePractice - - MagazineBoxMagnumPractice - - MagazineBoxPistolPractice - - MagazineBoxRiflePractice - - ShellShotgunPractice - - WeaponLaserCarbinePractice - WeaponDisablerPractice + - WeaponLaserCarbinePractice + - Zipties - ShockCollar + - ShadowkinRestraints # DeltaV - .38 special ammo - Add various .38 special ammo to security techfab - MagazineBoxSpecial - MagazineBoxSpecialPractice - SpeedLoaderSpecial - MagazinePistolSpecial - # End of modified code dynamicRecipes: - - EncryptionKeySyndie # Nyano + - BolaEnergy + - BoxBeanbag + - BoxShotgunIncendiary + - BoxShotgunUranium + - EncryptionKeySyndie - CartridgeLightRifleIncendiary - CartridgeMagnumIncendiary - CartridgePistolIncendiary @@ -766,34 +833,51 @@ - ClothingEyesGlassesSecurity - ExplosivePayload - FlashPayload + - GrenadeBlast + - GrenadeEMP + - GrenadeFlash - HoloprojectorSecurity - MagazineBoxLightRifleIncendiary - - MagazineBoxMagnumIncendiary - - MagazineBoxPistolIncendiary - - MagazineBoxRifleIncendiary - MagazineBoxLightRifleUranium + - MagazineBoxMagnumIncendiary - MagazineBoxMagnumUranium + - MagazineBoxPistolIncendiary - MagazineBoxPistolUranium + - MagazineBoxRifleIncendiary - MagazineBoxRifleUranium - - ShellSoulbreaker # Nyanotrasen - Shotgun shell to get rid of psionics + - ShellSoulbreaker - MagazineBoxLightRifleRubber - MagazineBoxMagnumRubber - MagazineBoxPistolRubber - MagazineBoxRifleRubber - MagazineGrenadeEmpty - - GrenadeEMP - - GrenadeFlash - - ShellShotgunBeanbag - - ShellShotgunIncendiary - - ShellShotgunUranium + - MagazineLightRifleIncendiary + - MagazineLightRifleUranium + - MagazinePistolIncendiary + - MagazinePistolUranium + - MagazineRifleIncendiary + - MagazineRifleUranium + - MagazineShotgunBeanbag + - MagazineShotgunIncendiary + - PortableRecharger + - PowerCageHigh + - PowerCageMedium + - PowerCageSmall + - ShellTranquilizer + - ShuttleGunDusterCircuitboard + - ShuttleGunFriendshipCircuitboard + - ShuttleGunPerforatorCircuitboard + - ShuttleGunSvalinnMachineGunCircuitboard - Signaller - SignalTrigger + - SpeedLoaderMagnumIncendiary + - SpeedLoaderMagnumUranium - TelescopicShield - TimerTrigger - Truncheon - VoiceTrigger - - WeaponDisablerPractice - WeaponAdvancedLaser + - WeaponDisabler - WeaponDisablerSMG - WeaponLaserCannon - WeaponLaserCarbine @@ -801,18 +885,10 @@ - ClothingHeadCage # Nyanotrasen - Insulative headgear - ShockCollar # Nyanotrasen - Shock Collar - WeaponXrayCannon - - PowerCageSmall - - PowerCageMedium - - PowerCageHigh - - ShuttleGunSvalinnMachineGunCircuitboard - - ShuttleGunPerforatorCircuitboard - - ShuttleGunFriendshipCircuitboard - - ShuttleGunDusterCircuitboard - - WeaponEnergyGun #DeltaV - Energy Gun - - WeaponEnergyGunMini #DeltaV - Energy Gun but miniature - - WeaponEnergyGunPistol #DeltaV - Energy Gun Mini but with the power of a normal energy gun - - WeaponGunLaserCarbineAutomatic #DeltaV - New Laser Projectiles with a 20 round carbine - # DeltaV - .38 special ammo - Add various .38 special ammo to security techfab + - WeaponEnergyGun + - WeaponEnergyGunMini + - WeaponEnergyGunPistol + - WeaponGunLaserCarbineAutomatic - CartridgeSpecialRubber - CartridgeSpecialIncendiary - CartridgeSpecialUranium @@ -822,7 +898,6 @@ - MagazineBoxSpecialIncendiary - MagazineBoxSpecialUranium - MagazineBoxSpecialMindbreaker - # End of modified code - type: MaterialStorage whitelist: tags: @@ -832,7 +907,7 @@ - type: entity id: AmmoTechFab - parent: BaseLathe + parent: BaseLatheLube name: ammo techfab description: Prints the bare minimum of bullets that any budget military or armory could need. Nothing fancy. components: @@ -855,22 +930,27 @@ idleState: icon runningState: icon staticRecipes: - - CartridgePistol - - CartridgeMagnum - - ShellShotgun - - ShellShotgunSlug - - ShellShotgunFlare - - ShellTranquilizer - - CartridgeLightRifle - - CartridgeRifle - - MagazineBoxPistol + - BoxLethalshot + - BoxShotgunFlare + - BoxShotgunSlug + - MagazineBoxLightRifle - MagazineBoxMagnum + - MagazineBoxPistol - MagazineBoxRifle - - MagazineBoxLightRifle - # DeltaV - .38 special ammo - Add lethal .38 special ammo to ammo techfab + - MagazineLightRifle + - MagazineLightRifleEmpty + - MagazinePistol + - MagazinePistolEmpty + - MagazineRifle + - MagazineRifleEmpty + - MagazineShotgun + - MagazineShotgunEmpty + - MagazineShotgunSlug + - ShellTranquilizer + - SpeedLoaderMagnum + - SpeedLoaderMagnumEmpty - CartridgeSpecial - MagazineBoxSpecial - # End of modified code - type: MaterialStorage whitelist: tags: @@ -880,7 +960,7 @@ - type: entity id: MedicalTechFab - parent: BaseLathe + parent: BaseLatheLube name: medical techfab description: Prints equipment for use by the medbay. components: @@ -932,6 +1012,7 @@ - MedkitRadiation - MedkitCombat - Scalpel + - BoneGel - Retractor - Cautery - Drill @@ -1088,7 +1169,8 @@ - ClothingOuterWinterCE - ClothingOuterWinterCMO - ClothingOuterWinterHoP - - ClothingOuterWinterHoS + - ClothingOuterWinterHoSUnarmored + - ClothingOuterWinterWardenUnarmored - ClothingOuterWinterQM - ClothingOuterWinterRD - ClothingNeckMantleCap @@ -1202,7 +1284,8 @@ idleState: icon runningState: building staticRecipes: - - BluespaceCrystal #Nyano - Summary: Bluespace Crystals can be created here. + - BluespaceCrystal + - NormalityCrystal - SheetSteel30 - SheetGlass30 - SheetRGlass30 @@ -1231,6 +1314,8 @@ materialUseMultiplier: 0.75 timeMultiplier: 0.5 staticRecipes: + - BluespaceCrystal + - NormalityCrystal - SheetSteel30 - SheetGlass30 - SheetRGlass30 @@ -1292,7 +1377,6 @@ - type: LitOnPowered - type: ApcPowerReceiver powerLoad: 200 - priority: Low # - type: Advertise # pack: CuddlyCritterAds # TODO Change this - type: PointLight @@ -1318,6 +1402,7 @@ staticRecipes: - PrizeBall - PlushieMothRandom + - PlushieShadowkin - PlushieMothMusician - PlushieMothBartender - PlushieBee @@ -1408,8 +1493,56 @@ - SingularityToy - TeslaToy - ToySword + - BwoinkHammer - ThronglerToy - type: MaterialStorage whitelist: tags: - - PrizeTicket \ No newline at end of file + - PrizeTicket + +- type: entity + id: MedicalBiofabricator + parent: BaseLathe + name: medical biofabricator + description: Produces organs and other organic matter that can be surgically grafted onto patients with biomass. + components: + - type: Sprite + sprite: Structures/Machines/limbgrower.rsi + snapCardinals: true + layers: + - state: limbgrower_idleoff + map: ["enum.LatheVisualLayers.IsRunning"] +# - state: limbgrower_idleoff +# shader: unshaded +# map: ["enum.PowerDeviceVisualLayers.Powered"] +# - state: inserting +# map: ["enum.MaterialStorageVisualLayers.Inserting"] +# - state: panel +# map: ["enum.WiresVisualLayers.MaintenancePanel"] + - type: Machine + board: MedicalBiofabMachineBoard + - type: MaterialStorage + whitelist: + tags: + - Sheet + - RawMaterial + - type: Lathe + idleState: limbgrower_idleoff + runningState: limbgrower_idleon + staticRecipes: + - SynthLiver + - SynthHeart + - SynthLungs + - SynthEyes + - SynthLeftLeg + - SynthRightLeg + - SynthLeftFoot + - SynthRightFoot + - SynthLeftArm + - SynthRightArm + - SynthLeftHand + - SynthRightHand + - type: EmagLatheRecipes + emagStaticRecipes: + - PizzaLeftArm + - PizzaRightArm diff --git a/Resources/Prototypes/Entities/Structures/Machines/metempsychoticMachine.yml b/Resources/Prototypes/Entities/Structures/Machines/metempsychoticMachine.yml new file mode 100644 index 0000000000..9667ed12d4 --- /dev/null +++ b/Resources/Prototypes/Entities/Structures/Machines/metempsychoticMachine.yml @@ -0,0 +1,26 @@ +- type: entity + parent: CloningPod + id: MetempsychoticMachine + name: metempsychotic machine + description: Speeds along the transmigration of a soul to its next vessel. + components: + - type: CloningPod + doMetempsychosis: true + biomassCostMultiplier: 0.5 + - type: Machine + board: MetempsychoticMachineCircuitboard + - type: Sprite + sprite: Structures/Machines/metempsychotic.rsi + snapCardinals: true + layers: + - state: cloning_idle + - type: Appearance + - type: GenericVisualizer + visuals: + enum.CloningPodVisuals.Status: + base: + Cloning: { state: cloning_active } + NoMind: { state: cloning_active } + Gore: { state: cloning_failed } + Idle: { state: cloning_idle } + - type: Psionic diff --git a/Resources/Prototypes/Entities/Structures/Machines/microwave.yml b/Resources/Prototypes/Entities/Structures/Machines/microwave.yml index 55dfe296a6..994269f71b 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/microwave.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/microwave.yml @@ -44,8 +44,8 @@ range: 200 - type: UserInterface interfaces: - - key: enum.MicrowaveUiKey.Key - type: MicrowaveBoundUserInterface + enum.MicrowaveUiKey.Key: + type: MicrowaveBoundUserInterface - type: Physics - type: Fixtures fixtures: diff --git a/Resources/Prototypes/Entities/Structures/Machines/nuke.yml b/Resources/Prototypes/Entities/Structures/Machines/nuke.yml index f37c42e474..bc4581d7b6 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/nuke.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/nuke.yml @@ -88,8 +88,8 @@ key: enum.NukeUiKey.Key - type: UserInterface interfaces: - - key: enum.NukeUiKey.Key - type: NukeBoundUserInterface + enum.NukeUiKey.Key: + type: NukeBoundUserInterface - type: StaticPrice price: 50000 # YOU STOLE A NUCLEAR FISSION EXPLOSIVE?! - type: CargoSellBlacklist diff --git a/Resources/Prototypes/Entities/Structures/Machines/reagent_grinder.yml b/Resources/Prototypes/Entities/Structures/Machines/reagent_grinder.yml index 80bfb465ff..28aa464d21 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/reagent_grinder.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/reagent_grinder.yml @@ -12,8 +12,8 @@ key: enum.ReagentGrinderUiKey.Key - type: UserInterface interfaces: - - key: enum.ReagentGrinderUiKey.Key - type: ReagentGrinderBoundUserInterface + enum.ReagentGrinderUiKey.Key: + type: ReagentGrinderBoundUserInterface - type: Appearance - type: GenericVisualizer visuals: @@ -101,4 +101,4 @@ - type: DrainableSolution solution: output - type: ExaminableSolution - solution: output \ No newline at end of file + solution: output diff --git a/Resources/Prototypes/Entities/Structures/Machines/recycler.yml b/Resources/Prototypes/Entities/Structures/Machines/recycler.yml index 138795cbf1..91eb0335e6 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/recycler.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/recycler.yml @@ -116,4 +116,5 @@ interactSuccessString: petting-success-recycler interactFailureString: petting-failure-generic interactSuccessSound: - path: /Audio/Items/drill_hit.ogg \ No newline at end of file + path: /Audio/Items/drill_hit.ogg + - type: ApcPowerReceiver diff --git a/Resources/Prototypes/Entities/Structures/Machines/research.yml b/Resources/Prototypes/Entities/Structures/Machines/research.yml index 948b3f84b2..498759df3c 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/research.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/research.yml @@ -83,8 +83,8 @@ - type: ActivatableUIRequiresPower - type: UserInterface interfaces: - - key: enum.ResearchClientUiKey.Key - type: ResearchClientBoundUserInterface + enum.ResearchClientUiKey.Key: + type: ResearchClientBoundUserInterface - type: Appearance - type: GenericVisualizer visuals: diff --git a/Resources/Prototypes/Entities/Structures/Machines/salvage.yml b/Resources/Prototypes/Entities/Structures/Machines/salvage.yml index 859dc85ff1..6a2f6c810c 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/salvage.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/salvage.yml @@ -28,8 +28,8 @@ - type: ActivatableUIRequiresPower - type: UserInterface interfaces: - - key: enum.SalvageMagnetUiKey.Key - type: SalvageMagnetBoundUserInterface + enum.SalvageMagnetUiKey.Key: + type: SalvageMagnetBoundUserInterface - type: Transform noRot: false - type: Appearance diff --git a/Resources/Prototypes/Entities/Structures/Machines/seed_extractor.yml b/Resources/Prototypes/Entities/Structures/Machines/seed_extractor.yml index ec07294bfb..4ecd1c29e2 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/seed_extractor.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/seed_extractor.yml @@ -35,3 +35,6 @@ - type: SeedExtractor - type: Machine board: SeedExtractorMachineCircuitboard + - type: UpgradePowerDraw + powerDrawMultiplier: 0.75 + scaling: Exponential diff --git a/Resources/Prototypes/Entities/Structures/Machines/surveillance_camera_routers.yml b/Resources/Prototypes/Entities/Structures/Machines/surveillance_camera_routers.yml index e359db813f..7b342def54 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/surveillance_camera_routers.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/surveillance_camera_routers.yml @@ -13,7 +13,7 @@ - type: DeviceNetworkRequiresPower - type: UserInterface interfaces: - - key: enum.SurveillanceCameraSetupUiKey.Router + enum.SurveillanceCameraSetupUiKey.Router: type: SurveillanceCameraSetupBoundUi - type: Machine board: SurveillanceCameraRouterCircuitboard @@ -118,7 +118,7 @@ - type: DeviceNetworkRequiresPower - type: UserInterface interfaces: - - key: enum.SurveillanceCameraSetupUiKey.Router + enum.SurveillanceCameraSetupUiKey.Router: type: SurveillanceCameraSetupBoundUi - type: Machine board: SurveillanceCameraWirelessRouterCircuitboard diff --git a/Resources/Prototypes/Entities/Structures/Machines/telecomms.yml b/Resources/Prototypes/Entities/Structures/Machines/telecomms.yml index 2a7d827522..13fdb7e80d 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/telecomms.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/telecomms.yml @@ -67,7 +67,7 @@ - type: entity parent: TelecomServer id: TelecomServerFilled - suffix: Filled + suffix: Filled All components: - type: ContainerFill containers: diff --git a/Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml b/Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml index 02cdd80af3..c79cfa2641 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml @@ -60,10 +60,10 @@ - type: ActivatableUIRequiresPower - type: UserInterface interfaces: - - key: enum.VendingMachineUiKey.Key - type: VendingMachineBoundUserInterface - - key: enum.WiresUiKey.Key - type: WiresBoundUserInterface + enum.VendingMachineUiKey.Key: + type: VendingMachineBoundUserInterface + enum.WiresUiKey.Key: + type: WiresBoundUserInterface - type: WiresPanel - type: Wires boardName: wires-board-name-vendingmachine @@ -86,6 +86,7 @@ - type: Electrified enabled: false usesApcPower: true + - type: EtherealLight - type: PointLight enabled: false castShadows: false @@ -105,10 +106,10 @@ - type: WiresVisuals - type: LanguageKnowledge speaks: - - GalacticCommon + - TauCetiBasic - RobotTalk understands: - - GalacticCommon + - TauCetiBasic - RobotTalk - type: VendingMachine soundVend: /Audio/SimpleStation14/Machines/machine_vend.ogg @@ -1905,7 +1906,7 @@ - state: panel map: ["enum.WiresVisualLayers.MaintenancePanel"] - type: AccessReader - access: [["Service"]] + access: [["Library"]] - type: entity parent: VendingMachine diff --git a/Resources/Prototypes/Entities/Structures/Machines/wireless_surveillance_camera.yml b/Resources/Prototypes/Entities/Structures/Machines/wireless_surveillance_camera.yml index 0a14517771..95079b5c85 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/wireless_surveillance_camera.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/wireless_surveillance_camera.yml @@ -35,7 +35,7 @@ range: 10 - type: UserInterface interfaces: - - key: enum.SurveillanceCameraSetupUiKey.Camera + enum.SurveillanceCameraSetupUiKey.Camera: type: SurveillanceCameraSetupBoundUi placement: mode: SnapgridCenter diff --git a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/binary.yml b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/binary.yml index 4bea87f817..fa5804c645 100644 --- a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/binary.yml +++ b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/binary.yml @@ -51,8 +51,8 @@ enabled: false - type: UserInterface interfaces: - - key: enum.GasPressurePumpUiKey.Key - type: GasPressurePumpBoundUserInterface + enum.GasPressurePumpUiKey.Key: + type: GasPressurePumpBoundUserInterface - type: Construction graph: GasBinary node: pressurepump @@ -92,7 +92,7 @@ enabled: false - type: UserInterface interfaces: - - key: enum.GasVolumePumpUiKey.Key + enum.GasVolumePumpUiKey.Key: type: GasVolumePumpBoundUserInterface - type: Construction graph: GasBinary diff --git a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/pipes.yml b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/pipes.yml index e5b77095b0..0025fc5ae1 100644 --- a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/pipes.yml +++ b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/pipes.yml @@ -1,4 +1,4 @@ -- type: entity +- type: entity abstract: true id: GasPipeBase parent: BaseItem @@ -101,6 +101,20 @@ - type: Construction graph: GasPipe node: straight + - type: Item + size: Normal + storedSprite: + sprite: Structures/Piping/Atmospherics/pipe.rsi + state: storageStraight + shape: + - 0,0,0,2 + - type: MeleeWeapon + attackRate: 1 + damage: + types: + Blunt: 8 + soundHit: + collection: MetalThud # this NEEDS to changed to the metal pipe falling sound effect on april first every year - type: entity parent: GasPipeBase @@ -120,6 +134,27 @@ - type: Construction graph: GasPipe node: bend + - type: Item + size: Small + shape: + - 0,0,1,0 + - 1,1,1,1 + heldPrefix: Bend + storedSprite: + sprite: Structures/Piping/Atmospherics/pipe.rsi + state: storageBend + - type: MeleeWeapon + wideAnimationRotation: 180 + attackRate: 1 + damage: + types: + Blunt: 6 + soundHit: + collection: MetalThud + - type: DamageOtherOnHit + damage: + types: + Blunt: 3 #This should be 6 but throwing damage is doubled at the moment for some reason so for now it's 3 - type: entity parent: GasPipeBase @@ -139,6 +174,23 @@ - type: Construction graph: GasPipe node: tjunction + - type: Item + size: Normal + shape: + - 0,0,2,0 + - 1,1,1,1 + heldPrefix: TJunction + storedSprite: + sprite: Structures/Piping/Atmospherics/pipe.rsi + state: storageTJunction + - type: MeleeWeapon + wideAnimationRotation: 90 + attackRate: 0.75 + damage: + types: + Blunt: 10 + soundHit: + collection: MetalThud - type: entity parent: GasPipeBase @@ -160,6 +212,20 @@ - type: Construction graph: GasPipe node: fourway + - type: Item + size: Normal + shape: + - 1,0,1,2 + - 0,1,2,1 + heldPrefix: Fourway + - type: MeleeWeapon + wideAnimationRotation: 90 + attackRate: 0.75 + damage: + types: + Blunt: 10 + soundHit: + collection: MetalThud - type: entity id: GasPipeBroken diff --git a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/portable.yml b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/portable.yml index 6758c41328..87e71400f7 100644 --- a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/portable.yml +++ b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/portable.yml @@ -144,8 +144,8 @@ - type: WiresVisuals - type: UserInterface interfaces: - - key: enum.SpaceHeaterUiKey.Key - type: SpaceHeaterBoundUserInterface + enum.SpaceHeaterUiKey.Key: + type: SpaceHeaterBoundUserInterface - type: ActivatableUI inHandsOnly: false key: enum.SpaceHeaterUiKey.Key diff --git a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/trinary.yml b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/trinary.yml index d0f239b338..e8025556aa 100644 --- a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/trinary.yml +++ b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/trinary.yml @@ -53,7 +53,7 @@ - type: PipeColorVisuals - type: UserInterface interfaces: - - key: enum.GasFilterUiKey.Key + enum.GasFilterUiKey.Key: type: GasFilterBoundUserInterface - type: GasFilter enabled: false @@ -96,6 +96,9 @@ enabled: True: { state: gasFilterFOn } False: { state: gasFilterF } + - type: Construction + node: filterflipped + - type: PipeColorVisuals - type: NodeContainer nodes: @@ -138,7 +141,7 @@ - type: PipeColorVisuals - type: UserInterface interfaces: - - key: enum.GasMixerUiKey.Key + enum.GasMixerUiKey.Key: type: GasMixerBoundUserInterface - type: GasMixer enabled: false @@ -196,6 +199,8 @@ !type:PipeNode nodeGroupID: Pipe pipeDirection: North + - type: Construction + node: mixerflipped - type: entity parent: GasPipeBase diff --git a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml index bf475d5e06..7a69f4759c 100644 --- a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml +++ b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml @@ -242,8 +242,8 @@ - type: AtmosDevice - type: UserInterface interfaces: - - key: enum.ThermomachineUiKey.Key - type: GasThermomachineBoundUserInterface + enum.ThermomachineUiKey.Key: + type: GasThermomachineBoundUserInterface - type: ActivatableUI inHandsOnly: false key: enum.ThermomachineUiKey.Key @@ -460,7 +460,8 @@ solutions: tank: maxVol: 400 - canMix: true + - type: MixableSolution + solution: tank - type: DrainableSolution solution: tank - type: ExaminableSolution diff --git a/Resources/Prototypes/Entities/Structures/Piping/Disposal/pipes.yml b/Resources/Prototypes/Entities/Structures/Piping/Disposal/pipes.yml index 218b532efc..7aee589647 100644 --- a/Resources/Prototypes/Entities/Structures/Piping/Disposal/pipes.yml +++ b/Resources/Prototypes/Entities/Structures/Piping/Disposal/pipes.yml @@ -143,8 +143,8 @@ key: enum.DisposalTaggerUiKey.Key - type: UserInterface interfaces: - - key: enum.DisposalTaggerUiKey.Key - type: DisposalTaggerBoundUserInterface + enum.DisposalTaggerUiKey.Key: + type: DisposalTaggerBoundUserInterface - type: Construction graph: DisposalPipe node: tagger @@ -219,8 +219,8 @@ key: enum.DisposalRouterUiKey.Key - type: UserInterface interfaces: - - key: enum.DisposalRouterUiKey.Key - type: DisposalRouterBoundUserInterface + enum.DisposalRouterUiKey.Key: + type: DisposalRouterBoundUserInterface - type: Fixtures fixtures: fix1: @@ -271,6 +271,8 @@ bounds: "-0.25,-0.5,0.5,0.5" mask: - SubfloorMask + - type: Construction + node: routerflipped - type: entity id: DisposalJunction @@ -353,6 +355,8 @@ bounds: "-0.25,-0.5,0.5,0.5" mask: - SubfloorMask + - type: Construction + node: junctionflipped - type: entity id: DisposalYJunction @@ -489,3 +493,5 @@ pipe: Free: { state: signal-router-flipped-free } Anchored: { state: signal-router-flipped } + - type: Construction + node: signal_router_flipped diff --git a/Resources/Prototypes/Entities/Structures/Piping/Disposal/units.yml b/Resources/Prototypes/Entities/Structures/Piping/Disposal/units.yml index 75ec98c402..e7d3d3c997 100644 --- a/Resources/Prototypes/Entities/Structures/Piping/Disposal/units.yml +++ b/Resources/Prototypes/Entities/Structures/Piping/Disposal/units.yml @@ -56,8 +56,8 @@ - type: Appearance - type: UserInterface interfaces: - - key: enum.DisposalUnitUiKey.Key - type: DisposalUnitBoundUserInterface + enum.DisposalUnitUiKey.Key: + type: DisposalUnitBoundUserInterface - type: ContainerContainer containers: disposals: !type:Container @@ -77,10 +77,12 @@ graph: DisposalMachine node: disposal_unit - type: DisposalUnit + - type: ThrowInsertContainer + containerId: disposals - type: UserInterface interfaces: - - key: enum.DisposalUnitUiKey.Key - type: DisposalUnitBoundUserInterface + enum.DisposalUnitUiKey.Key: + type: DisposalUnitBoundUserInterface - type: RatKingRummageable - type: RequireProjectileTarget @@ -130,7 +132,7 @@ - type: Appearance - type: UserInterface interfaces: - - key: enum.MailingUnitUiKey.Key - type: DisposalUnitBoundUserInterface - - key: enum.ConfigurationUiKey.Key - type: ConfigurationBoundUserInterface + enum.MailingUnitUiKey.Key: + type: DisposalUnitBoundUserInterface + enum.ConfigurationUiKey.Key: + type: ConfigurationBoundUserInterface diff --git a/Resources/Prototypes/Entities/Structures/Power/Generation/PA/control_box.yml b/Resources/Prototypes/Entities/Structures/Power/Generation/PA/control_box.yml index ca0cebffec..96392ecd00 100644 --- a/Resources/Prototypes/Entities/Structures/Power/Generation/PA/control_box.yml +++ b/Resources/Prototypes/Entities/Structures/Power/Generation/PA/control_box.yml @@ -17,10 +17,10 @@ - type: ActivatableUIRequiresPower - type: UserInterface interfaces: - - key: enum.ParticleAcceleratorControlBoxUiKey.Key - type: ParticleAcceleratorBoundUserInterface - - key: enum.WiresUiKey.Key - type: WiresBoundUserInterface + enum.ParticleAcceleratorControlBoxUiKey.Key: + type: ParticleAcceleratorBoundUserInterface + enum.WiresUiKey.Key: + type: WiresBoundUserInterface - type: WiresPanel - type: Wires boardName: wires-board-name-pa diff --git a/Resources/Prototypes/Entities/Structures/Power/Generation/ame.yml b/Resources/Prototypes/Entities/Structures/Power/Generation/ame.yml index c88adca76d..4e4ef8bdbc 100644 --- a/Resources/Prototypes/Entities/Structures/Power/Generation/ame.yml +++ b/Resources/Prototypes/Entities/Structures/Power/Generation/ame.yml @@ -65,8 +65,8 @@ key: enum.AmeControllerUiKey.Key - type: UserInterface interfaces: - - key: enum.AmeControllerUiKey.Key - type: AmeControllerBoundUserInterface + enum.AmeControllerUiKey.Key: + type: AmeControllerBoundUserInterface - type: Appearance - type: GenericVisualizer visuals: diff --git a/Resources/Prototypes/Entities/Structures/Power/Generation/portable_generator.yml b/Resources/Prototypes/Entities/Structures/Power/Generation/portable_generator.yml index 4fa1bcbd91..41f336d405 100644 --- a/Resources/Prototypes/Entities/Structures/Power/Generation/portable_generator.yml +++ b/Resources/Prototypes/Entities/Structures/Power/Generation/portable_generator.yml @@ -44,7 +44,7 @@ - type: WiresPanel - type: UserInterface interfaces: - - key: enum.GeneratorComponentUiKey.Key + enum.GeneratorComponentUiKey.Key: type: PortableGeneratorBoundUserInterface - type: ActivatableUI key: enum.GeneratorComponentUiKey.Key @@ -171,6 +171,9 @@ materialWhiteList: [Plasma] - type: PortableGenerator startChance: 0.8 + - type: UpgradePowerSupplier + powerSupplyMultiplier: 1.25 + scaling: Exponential - type: GeneratorExhaustGas gasType: CarbonDioxide # 2 moles of gas for every sheet of plasma. @@ -228,6 +231,9 @@ storageLimit: 3000 materialWhiteList: [Uranium] - type: PortableGenerator + - type: UpgradePowerSupplier + powerSupplyMultiplier: 1.25 + scaling: Exponential - type: PowerMonitoringDevice group: Generator loadNodes: diff --git a/Resources/Prototypes/Entities/Structures/Power/Generation/solar.yml b/Resources/Prototypes/Entities/Structures/Power/Generation/solar.yml index c512266e97..601c7c360a 100644 --- a/Resources/Prototypes/Entities/Structures/Power/Generation/solar.yml +++ b/Resources/Prototypes/Entities/Structures/Power/Generation/solar.yml @@ -49,7 +49,7 @@ onBump: false requirePower: true highVoltageNode: output - - type: RequireProjectileTarget + - type: RequireProjectileTarget - type: entity id: SolarPanel @@ -58,8 +58,8 @@ description: A solar panel that generates power. components: - type: PowerSupplier - supplyRampTolerance: 500 - supplyRampRate: 500 + supplyRampTolerance: 1000 + supplyRampRate: 1000 - type: SolarPanel - type: Damageable damageContainer: Inorganic @@ -158,7 +158,7 @@ graph: SolarPanel node: solarassembly defaultTarget: solarpanel - - type: RequireProjectileTarget + - type: RequireProjectileTarget - type: entity id: SolarTracker @@ -203,4 +203,4 @@ - type: Construction graph: SolarPanel node: solartracker - - type: RequireProjectileTarget + - type: RequireProjectileTarget diff --git a/Resources/Prototypes/Entities/Structures/Power/apc.yml b/Resources/Prototypes/Entities/Structures/Power/apc.yml index 4df502791f..01147f439f 100644 --- a/Resources/Prototypes/Entities/Structures/Power/apc.yml +++ b/Resources/Prototypes/Entities/Structures/Power/apc.yml @@ -81,8 +81,8 @@ - type: ExtensionCableProvider - type: UserInterface interfaces: - - key: enum.ApcUiKey.Key - type: ApcBoundUserInterface + enum.ApcUiKey.Key: + type: ApcBoundUserInterface - type: ActivatableUI inHandsOnly: false singleUser: true @@ -138,6 +138,8 @@ priority: 1 - type: StaticPrice price: 500 + - type: BatteryDrinkerSource # Parkstation IPCs + maxAmount: 10000 # APC under construction - type: entity @@ -204,6 +206,8 @@ - type: Battery maxCharge: 50000 startingCharge: 50000 + - type: BatteryDrinkerSource # Parkstation IPCs + maxAmount: 5000 - type: entity parent: BaseAPC @@ -213,6 +217,8 @@ - type: Battery maxCharge: 100000 startingCharge: 100000 + - type: BatteryDrinkerSource # Parkstation IPCs + maxAmount: 12000 - type: entity parent: BaseAPC @@ -222,6 +228,8 @@ - type: Battery maxCharge: 150000 startingCharge: 150000 + - type: BatteryDrinkerSource # Parkstation IPCs + maxAmount: 18000 - type: entity parent: BaseAPC @@ -231,3 +239,5 @@ - type: Battery maxCharge: 200000 startingCharge: 200000 + - type: BatteryDrinkerSource # Parkstation IPCs # Parkstation IPCs + maxAmount: 26000 diff --git a/Resources/Prototypes/Entities/Structures/Power/chargers.yml b/Resources/Prototypes/Entities/Structures/Power/chargers.yml index f5f0748b81..ae8689bead 100644 --- a/Resources/Prototypes/Entities/Structures/Power/chargers.yml +++ b/Resources/Prototypes/Entities/Structures/Power/chargers.yml @@ -245,6 +245,7 @@ whitelist: components: - BorgChassis + - Silicon # Parkstation IPCs - type: Construction containers: - machine_parts diff --git a/Resources/Prototypes/Entities/Structures/Power/smes.yml b/Resources/Prototypes/Entities/Structures/Power/smes.yml index 1e3559e5a9..762e8d375f 100644 --- a/Resources/Prototypes/Entities/Structures/Power/smes.yml +++ b/Resources/Prototypes/Entities/Structures/Power/smes.yml @@ -29,10 +29,15 @@ state: "smes-op1" shader: unshaded - type: Smes + - type: UpgradeBattery + maxChargeMultiplier: 2 + baseMaxCharge: 8000000 + - type: UpgradePowerSupplyRamping + scaling: Linear + supplyRampingMultiplier: 1 - type: Appearance - type: Battery startingCharge: 0 - maxCharge: 8000000 - type: ExaminableBattery - type: NodeContainer examinable: true diff --git a/Resources/Prototypes/Entities/Structures/Power/substation.yml b/Resources/Prototypes/Entities/Structures/Power/substation.yml index 347b18ecae..489cfff659 100644 --- a/Resources/Prototypes/Entities/Structures/Power/substation.yml +++ b/Resources/Prototypes/Entities/Structures/Power/substation.yml @@ -17,8 +17,13 @@ shader: unshaded - state: full shader: unshaded + - type: UpgradeBattery + maxChargeMultiplier: 2 + baseMaxCharge: 2500000 + - type: UpgradePowerSupplyRamping + scaling: Linear + supplyRampingMultiplier: 1 - type: Battery - maxCharge: 2500000 startingCharge: 0 - type: ExaminableBattery - type: PointLight diff --git a/Resources/Prototypes/Entities/Structures/Shuttles/thrusters.yml b/Resources/Prototypes/Entities/Structures/Shuttles/thrusters.yml index f87be42659..eb299e3f3a 100644 --- a/Resources/Prototypes/Entities/Structures/Shuttles/thrusters.yml +++ b/Resources/Prototypes/Entities/Structures/Shuttles/thrusters.yml @@ -143,6 +143,7 @@ - type: Thruster thrusterType: Angular requireSpace: false + baseThrust: 2000 thrust: 2000 machinePartThrust: Manipulator - type: Sprite @@ -192,6 +193,9 @@ collection: MetalBreak - !type:ChangeConstructionNodeBehavior node: machineFrame + - type: UpgradePowerDraw + powerDrawMultiplier: 0.75 + scaling: Exponential - type: Damageable damageContainer: Inorganic damageModifierSet: Electronic @@ -216,6 +220,7 @@ - type: Thruster thrusterType: Angular requireSpace: false + baseThrust: 100 thrust: 100 - type: ApcPowerReceiver needsPower: false diff --git a/Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomalies.yml b/Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomalies.yml index 4f474765ba..8294586048 100644 --- a/Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomalies.yml +++ b/Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomalies.yml @@ -48,8 +48,12 @@ - type: EmitSoundOnSpawn sound: path: /Audio/Effects/teleport_arrival.ogg - - type: Psionic #Nyano - Summary: makes psionic on creation. - - type: GlimmerSource #Nyano - Summary: makes this a potential source of Glimmer. + - type: Psionic + removable: false + - type: InnatePsionicPowers + powersToAdd: + - TelepathyPower + - type: GlimmerSource active: false - type: SecretDataAnomaly randomStartSecretMin: 0 @@ -778,7 +782,6 @@ - Desoxyephedrine - Ephedrine - THC - - THCOil - SpaceDrugs - Nocturine - MuteToxin diff --git a/Resources/Prototypes/Entities/Structures/Specific/Janitor/janicart.yml b/Resources/Prototypes/Entities/Structures/Specific/Janitor/janicart.yml index 2591b3f340..9fd05beaee 100644 --- a/Resources/Prototypes/Entities/Structures/Specific/Janitor/janicart.yml +++ b/Resources/Prototypes/Entities/Structures/Specific/Janitor/janicart.yml @@ -313,8 +313,8 @@ fillBaseName: cart_water- - type: UserInterface interfaces: - - key: enum.StorageUiKey.Key - type: StorageBoundUserInterface + enum.StorageUiKey.Key: + type: StorageBoundUserInterface - type: Drink solution: bucket - type: ContainerContainer diff --git a/Resources/Prototypes/Entities/Structures/Specific/bay12barbershop.yml b/Resources/Prototypes/Entities/Structures/Specific/bay12barbershop.yml index e66ae347d9..2b0f2a788a 100644 --- a/Resources/Prototypes/Entities/Structures/Specific/bay12barbershop.yml +++ b/Resources/Prototypes/Entities/Structures/Specific/bay12barbershop.yml @@ -10,7 +10,7 @@ - type: Anchorable - type: Rotatable - type: Sprite - sprite: Structures/Specific/barbershop.rsi + sprite: Structures/Specific/barberchair.rsi state: barberchair - type: entity diff --git a/Resources/Prototypes/Entities/Structures/Specific/oracle.yml b/Resources/Prototypes/Entities/Structures/Specific/oracle.yml index 51a25bffcd..3e2ed9508e 100644 --- a/Resources/Prototypes/Entities/Structures/Specific/oracle.yml +++ b/Resources/Prototypes/Entities/Structures/Specific/oracle.yml @@ -2,7 +2,7 @@ parent: BaseStructure id: Oracle name: Oracle - description: It asks for items in exchange for knowledge. No one knows how it works. + description: She asks for items in exchange for knowledge. No one knows how she works. components: - type: Sprite noRot: true @@ -15,13 +15,24 @@ state: oracle-0 - type: Speech speechSounds: Tenor + - type: Actions - type: Psionic + removable: false + psychognomicDescriptors: + - p-descriptor-old + - p-descriptor-demiurgic + - p-descriptor-mysterious + - p-descriptor-emanative + - type: InnatePsionicPowers + powersToAdd: + - XenoglossyPower + - TelepathyPower + - NoosphericZapPower - type: SolutionContainerManager solutions: fountain: maxVol: 200 - type: Drink - isOpen: true solution: fountain - type: DrawableSolution solution: fountain @@ -47,14 +58,13 @@ rewardEntities: - OracleRewardDisks - OracleRewardCrystals - demandBlacklist: - tags: - - Bluespace - components: - - MobState - demandWhitelist: - components: - - Item + - type: LanguageSpeaker + currentLanguage: TauCetiBasic + - type: LanguageKnowledge + speaks: + - TauCetiBasic + understands: + - TauCetiBasic - type: weightedRandomEntity diff --git a/Resources/Prototypes/Entities/Structures/Storage/Canisters/gas_canisters.yml b/Resources/Prototypes/Entities/Structures/Storage/Canisters/gas_canisters.yml index 52b008c7f2..1d184ad45e 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/Canisters/gas_canisters.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/Canisters/gas_canisters.yml @@ -36,7 +36,7 @@ 3: { state: can-o3, shader: "unshaded" } - type: UserInterface interfaces: - - key: enum.GasCanisterUiKey.Key + enum.GasCanisterUiKey.Key: type: GasCanisterBoundUserInterface - type: Destructible thresholds: diff --git a/Resources/Prototypes/Entities/Structures/Storage/Closets/Lockers/base_structurelockers.yml b/Resources/Prototypes/Entities/Structures/Storage/Closets/Lockers/base_structurelockers.yml index 783bec3ba0..7d7bc94bb3 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/Closets/Lockers/base_structurelockers.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/Closets/Lockers/base_structurelockers.yml @@ -45,7 +45,7 @@ min: 1 max: 2 - type: Construction - graph: ClosetSteel + graph: ClosetSteelSecure node: done containers: - entity_storage @@ -66,8 +66,3 @@ behaviors: - !type:DoActsBehavior acts: ["Destruction"] - - type: Construction - graph: ClosetSteelSecure - node: done - containers: - - entity_storage diff --git a/Resources/Prototypes/Entities/Structures/Storage/Closets/Lockers/lockers.yml b/Resources/Prototypes/Entities/Structures/Storage/Closets/Lockers/lockers.yml index b38a77b82b..c1efc5a63f 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/Closets/Lockers/lockers.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/Closets/Lockers/lockers.yml @@ -18,6 +18,20 @@ openSound: path: /Audio/Effects/woodenclosetopen.ogg +# Bartender +- type: entity + id: LockerBartender + parent: LockerBase + name: bartender's locker + components: + - type: Appearance + - type: EntityStorageVisuals + stateBaseClosed: bartender + stateDoorOpen: bartender_open + stateDoorClosed: bartender_door + - type: AccessReader + access: [ [ "Bar" ] ] + # Basic - type: entity id: LockerSteel @@ -194,6 +208,20 @@ - type: AccessReader access: [ [ "Hydroponics" ] ] +# Janitor +- type: entity + id: LockerJanitor + parent: LockerBase + name: janitor locker + components: + - type: Appearance + - type: EntityStorageVisuals + stateBaseClosed: jan + stateDoorOpen: jan_open + stateDoorClosed: jan_door + - type: AccessReader + access: [ [ "Janitor" ] ] + # Medicine - type: entity id: LockerMedicine @@ -394,6 +422,20 @@ stateDoorOpen: syndicate_open stateDoorClosed: syndicate_door +- type: entity + id: LockerSyndicateAgentLocker + name: syndicate agent locker + parent: LockerBase + description: It's a personal storage unit for agent gear. + components: + - type: Appearance + - type: EntityStorageVisuals + stateBaseClosed: syndicate1 + stateDoorOpen: syndicate1_open + stateDoorClosed: syndicate1_door + - type: AccessReader + access: [ [ "SyndicateAgent" ] ] + # Bluespace - type: entity id: LockerBluespaceStation diff --git a/Resources/Prototypes/Entities/Structures/Storage/Closets/wall_lockers.yml b/Resources/Prototypes/Entities/Structures/Storage/Closets/wall_lockers.yml index 4280b960b4..e16df3bfb9 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/Closets/wall_lockers.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/Closets/wall_lockers.yml @@ -22,6 +22,18 @@ stateDoorOpen: emergency_open stateDoorClosed: emergency_door +- type: entity + id: ClosetWallRadiation + name: radiation wall closet + parent: BaseWallCloset + description: It's a storage unit for radiation equipment + components: + - type: Appearance + - type: EntityStorageVisuals + stateBaseClosed: rad + stateDoorOpen: rad_open + stateDoorClosed: rad_door + - type: entity id: ClosetWallFire name: fire-safety wall closet diff --git a/Resources/Prototypes/Entities/Structures/Storage/Crates/base_structurecrates.yml b/Resources/Prototypes/Entities/Structures/Storage/Crates/base_structurecrates.yml index 78c8c84d4c..01c226cb0f 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/Crates/base_structurecrates.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/Crates/base_structurecrates.yml @@ -34,7 +34,7 @@ bounds: "-0.4,-0.4,0.4,0.29" density: 50 mask: - - SmallMobMask #this is so they can go under plastic flaps + - CrateMask #this is so they can go under plastic flaps layer: - MachineLayer - type: EntityStorage diff --git a/Resources/Prototypes/Entities/Structures/Storage/Crates/crates.yml b/Resources/Prototypes/Entities/Structures/Storage/Crates/crates.yml index 1cf8083659..bd76a87f55 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/Crates/crates.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/Crates/crates.yml @@ -252,6 +252,18 @@ - type: AccessReader access: [["Engineering"], ["Research"], ["Chemistry"]] +- type: entity + parent: CrateBaseSecure + id: CrateUranium + name: uranium crate + components: + - type: Icon + sprite: Structures/Storage/Crates/uranium.rsi + - type: Sprite + sprite: Structures/Storage/Crates/uranium.rsi + - type: AccessReader + access: [["Engineering"], ["Research"]] + - type: entity parent: CrateBaseSecure id: CrateSecure @@ -356,7 +368,7 @@ bounds: "-0.4,-0.4,0.4,0.29" density: 135 mask: - - SmallMobMask #this is so they can go under plastic flaps + - CrateMask #this is so they can go under plastic flaps layer: - LargeMobLayer - type: Construction @@ -413,7 +425,7 @@ bounds: "-0.4,-0.4,0.4,0.29" density: 80 mask: - - LargeMobMask + - CrateMask layer: - LargeMobLayer - type: StaticPrice diff --git a/Resources/Prototypes/Entities/Structures/Storage/filing_cabinets.yml b/Resources/Prototypes/Entities/Structures/Storage/filing_cabinets.yml index d6becda9cc..cfda95fc2f 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/filing_cabinets.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/filing_cabinets.yml @@ -24,8 +24,8 @@ map: ["openLayer"] - type: UserInterface interfaces: - - key: enum.StorageUiKey.Key - type: StorageBoundUserInterface + enum.StorageUiKey.Key: + type: StorageBoundUserInterface - type: Transform noRot: true - type: Fixtures @@ -113,8 +113,8 @@ - type: Appearance - type: UserInterface interfaces: - - key: enum.StorageUiKey.Key - type: StorageBoundUserInterface + enum.StorageUiKey.Key: + type: StorageBoundUserInterface - type: Transform noRot: true - type: Fixtures diff --git a/Resources/Prototypes/Entities/Structures/Storage/glass_box.yml b/Resources/Prototypes/Entities/Structures/Storage/glass_box.yml index bdb02d2bc3..8177b6b6f0 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/glass_box.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/glass_box.yml @@ -1,97 +1,148 @@ - type: entity - id: GlassBoxLaser - name: glass box - description: A sturdy showcase for an expensive exhibit. + id: BaseGlassBox parent: BaseStructureDynamic + abstract: true placement: mode: SnapgridCenter components: - - type: Anchorable - delay: 4 - type: Transform anchored: true - - type: Damageable - damageContainer: Inorganic - damageModifierSet: Glass - - type: MeleeSound - soundGroups: - Brute: - collection: GlassSmash - type: Physics bodyType: Static - type: Clickable - type: InteractionOutline + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeAabb + bounds: "-0.45,-0.45,0.45,0.45" + density: 1000 + mask: + - MachineMask + layer: + - MidImpassable + - LowImpassable + - type: ItemSlots + - type: ContainerContainer + containers: + ItemCabinet: !type:ContainerSlot + - type: Anchorable + delay: 4 + - type: Appearance + +- type: entity + id: GlassBox + name: glass box + description: A sturdy showcase for an expensive exhibit. + parent: BaseGlassBox + abstract: true # TODO: Temporarily abstract. Remove it after item scaling in cabinets is implemented. + components: - type: Sprite + noRot: true sprite: Structures/Storage/glassbox.rsi layers: - - state: glassbox - - state: caplaser + - state: base + - state: caplaser # TODO: Remove it after item scaling in cabinets is implemented. map: ["enum.ItemCabinetVisualLayers.ContainsItem"] visible: true - state: glass map: ["enum.ItemCabinetVisualLayers.Door"] - - type: ItemCabinet - cabinetSlot: - ejectOnInteract: true - whitelist: - tags: - - WeaponAntiqueLaser - doorSound: - path: /Audio/Machines/machine_switch.ogg - openState: glass-up - closedState: glass - - type: Lock + - state: locked + shader: unshaded + map: ["enum.LockVisualLayers.Lock"] + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeAabb + bounds: "-0.45,-0.45,0.45,0.45" + density: 1000 + mask: + - MachineMask + layer: + - LowImpassable + - MidImpassable + - BulletImpassable - type: AccessReader - access: [["Captain"]] - - type: ItemSlots - - type: ContainerContainer - containers: - ItemCabinet: !type:ContainerSlot - type: Repairable - - type: Appearance + fuelCost: 15 + doAfterDelay: 5 + - type: Lock + - type: LockVisuals - type: DamageVisuals - thresholds: [4, 8, 12] + thresholds: [4, 8, 12] # TODO: Fix damage visuals on open state. damageDivisor: 7.555 trackAllDamage: true damageOverlay: sprite: Structures/Storage/glassbox.rsi + - type: Damageable + damageContainer: Inorganic + damageModifierSet: Glass + - type: MeleeSound + soundGroups: + Brute: + collection: GlassSmash - type: Destructible thresholds: - trigger: !type:DamageTrigger damage: 150 behaviors: - - !type:EmptyAllContainersBehaviour - - !type:PlaySoundBehavior - sound: - collection: WindowShatter - - !type:SpawnEntitiesBehavior - spawn: - ShardGlassReinforced: - min: 1 - max: 1 - GlassBoxLaserBroken: - min: 1 - max: 1 - - !type:DoActsBehavior - acts: [ "Destruction" ] + - !type:EmptyAllContainersBehaviour + - !type:PlaySoundBehavior + sound: + collection: WindowShatter + - !type:PlaySoundBehavior + sound: + path: /Audio/Machines/warning_buzzer.ogg + params: + volume: 10 + - !type:SpawnEntitiesBehavior + spawn: + ShardGlassReinforced: + min: 1 + max: 2 + - !type:ChangeConstructionNodeBehavior + node: brokenGlassBox + - !type:DoActsBehavior + acts: [ "Destruction" ] - type: entity - id: GlassBoxLaserOpen - parent: GlassBoxLaser - suffix: Open + id: GlassBoxLaser + parent: GlassBox + suffix: AntiqueLaser components: + - type: AccessReader + access: [["Captain"]] + - type: Construction + graph: GlassBox + node: glassBox - type: ItemCabinet - opened: true + cabinetSlot: + ejectOnInteract: true + whitelist: + tags: + - WeaponAntiqueLaser doorSound: path: /Audio/Machines/machine_switch.ogg openState: glass-up closedState: glass +- type: entity + id: GlassBoxLaserOpen + parent: GlassBoxLaser + suffix: AntiqueLaser, Open + components: + - type: Lock + locked: false + - type: ItemCabinet + opened: true + - type: entity id: GlassBoxLaserFilled parent: GlassBoxLaser - suffix: Filled + suffix: AntiqueLaser, Filled components: - type: ItemCabinet cabinetSlot: @@ -100,40 +151,83 @@ whitelist: tags: - WeaponAntiqueLaser - doorSound: - path: /Audio/Machines/machine_switch.ogg - openState: glass-up - closedState: glass - type: entity id: GlassBoxLaserFilledOpen parent: GlassBoxLaserFilled - suffix: Filled, Open + suffix: AntiqueLaser, Filled, Open components: + - type: Lock + locked: false - type: ItemCabinet opened: true - doorSound: - path: /Audio/Machines/machine_switch.ogg - openState: glass-up - closedState: glass - type: entity - id: GlassBoxLaserBroken + id: GlassBoxFrame + name: glass box frame + description: A glassless sturdy showcase for an expensive exhibit. + parent: BaseGlassBox + suffix: Frame + components: + - type: Sprite + noRot: true + sprite: Structures/Storage/glassbox.rsi + layers: + - state: base + - type: Construction + graph: GlassBox + node: boxMissingWires + - type: Climbable + - type: Damageable + damageModifierSet: Wood + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 100 + behaviors: + - !type:PlaySoundBehavior + sound: + collection: WoodDestroy + - !type:SpawnEntitiesBehavior + spawn: + MaterialWoodPlank1: + min: 2 + max: 5 + - !type:DoActsBehavior + acts: ["Destruction"] + +- type: entity + id: GlassBoxBroken name: broken glass box description: A broken showcase for a stolen expensive exhibit. - parent: BaseStructureDynamic + parent: GlassBoxFrame suffix: Broken - placement: - mode: SnapgridCenter components: - - type: Transform - anchored: true - - type: Physics - bodyType: Static - type: Sprite sprite: Structures/Storage/glassbox.rsi layers: - - state: glassbox - - state: glass-4 - - type: Clickable - - type: InteractionOutline + - state: base + - state: glass-broken + - type: Construction + graph: GlassBox + node: brokenGlassBox + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 100 + behaviors: + - !type:PlaySoundBehavior + sound: + collection: WoodDestroy + - !type:SpawnEntitiesBehavior + spawn: + ShardGlassReinforced: + min: 1 + max: 1 + MaterialWoodPlank1: + min: 2 + max: 5 + - !type:DoActsBehavior + acts: ["Destruction"] diff --git a/Resources/Prototypes/Entities/Structures/Storage/morgue.yml b/Resources/Prototypes/Entities/Structures/Storage/morgue.yml index 725f4c9b0f..496ea41611 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/morgue.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/morgue.yml @@ -5,6 +5,8 @@ placement: mode: SnapgridCenter components: + - type: Pullable + - type: Anchorable - type: Sprite sprite: Structures/Storage/morgue.rsi layers: @@ -28,11 +30,11 @@ shape: !type:PhysShapeAabb bounds: "-0.5,-0.5,0.5,0.5" - density: 190 + density: 1000 mask: - MachineMask layer: - - WallLayer + - HalfWallLayer - type: EntityStorage isCollidableWhenOpen: true showContents: false diff --git a/Resources/Prototypes/Entities/Structures/Storage/ore_box.yml b/Resources/Prototypes/Entities/Structures/Storage/ore_box.yml index 961d7854c0..b26bb2c954 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/ore_box.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/ore_box.yml @@ -60,8 +60,8 @@ - Ore - type: UserInterface interfaces: - - key: enum.StorageUiKey.Key - type: StorageBoundUserInterface + enum.StorageUiKey.Key: + type: StorageBoundUserInterface - type: ContainerContainer containers: storagebase: !type:Container diff --git a/Resources/Prototypes/Entities/Structures/Storage/storage.yml b/Resources/Prototypes/Entities/Structures/Storage/storage.yml index 79a1e35799..d0b468ca53 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/storage.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/storage.yml @@ -27,9 +27,9 @@ bounds: "-0.3,-0.3,0.3,0.3" density: 140 mask: - - MachineMask + - TableMask layer: - - MachineLayer + - TableLayer - type: Damageable damageContainer: Inorganic damageModifierSet: Metallic diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/air_alarm.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/air_alarm.yml index 62f2000593..6090aae882 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/air_alarm.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/air_alarm.yml @@ -53,14 +53,16 @@ - AirAlarm - type: AtmosDevice - type: AirAlarm + - type: AtmosAlertsDevice + group: AirAlarm - type: Clickable - type: InteractionOutline - type: UserInterface interfaces: - - key: enum.SharedAirAlarmInterfaceKey.Key - type: AirAlarmBoundUserInterface - - key: enum.WiresUiKey.Key - type: WiresBoundUserInterface + enum.SharedAirAlarmInterfaceKey.Key: + type: AirAlarmBoundUserInterface + enum.WiresUiKey.Key: + type: WiresBoundUserInterface - type: WiresPanel - type: Wires boardName: wires-board-name-airalarm diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/fire_alarm.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/fire_alarm.yml index 05988fbc21..c27b0ff27e 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/fire_alarm.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/fire_alarm.yml @@ -42,6 +42,8 @@ - type: Clickable - type: InteractionOutline - type: FireAlarm + - type: AtmosAlertsDevice + group: FireAlarm - type: ContainerFill containers: board: [ FireAlarmElectronics ] @@ -60,8 +62,8 @@ delta: fire_3 - type: UserInterface interfaces: - - key: enum.WiresUiKey.Key - type: WiresBoundUserInterface + enum.WiresUiKey.Key: + type: WiresBoundUserInterface - type: WiresPanel - type: Wires boardName: wires-board-name-firealarm diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/intercom.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/intercom.yml index 6bccf14e13..6c6b8bf57e 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/intercom.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/intercom.yml @@ -19,6 +19,9 @@ - type: Intercom - type: Speech speechVerb: Robotic + - type: VoiceOverride # This is for the wire that makes an electricity zapping noise. + speechVerbOverride: Electricity + enabled: false - type: ExtensionCableReceiver - type: Clickable - type: InteractionOutline @@ -63,9 +66,9 @@ key: enum.IntercomUiKey.Key - type: UserInterface interfaces: - - key: enum.IntercomUiKey.Key + enum.IntercomUiKey.Key: type: IntercomBoundUserInterface - - key: enum.WiresUiKey.Key + enum.WiresUiKey.Key: type: WiresBoundUserInterface - type: Construction graph: Intercom diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/mirror.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/mirror.yml index b524f099bf..d02bce020d 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/mirror.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/mirror.yml @@ -16,5 +16,5 @@ key: enum.MagicMirrorUiKey.Key - type: UserInterface interfaces: - - key: enum.MagicMirrorUiKey.Key - type: MagicMirrorBoundUserInterface + enum.MagicMirrorUiKey.Key: + type: MagicMirrorBoundUserInterface diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/monitors_televisions.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/monitors_televisions.yml index 451495f58d..408ab6b67c 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/monitors_televisions.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/monitors_televisions.yml @@ -110,8 +110,8 @@ - type: ActivatableUIRequiresPower - type: UserInterface interfaces: - - key: enum.SurveillanceCameraMonitorUiKey.Key - type: SurveillanceCameraMonitorBoundUserInterface + enum.SurveillanceCameraMonitorUiKey.Key: + type: SurveillanceCameraMonitorBoundUserInterface # Wall Televisions @@ -169,8 +169,8 @@ - type: ActivatableUIRequiresPower - type: UserInterface interfaces: - - key: enum.SurveillanceCameraMonitorUiKey.Key - type: SurveillanceCameraMonitorBoundUserInterface + enum.SurveillanceCameraMonitorUiKey.Key: + type: SurveillanceCameraMonitorBoundUserInterface - type: PointLight radius: 1.5 energy: 1.6 diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/noticeboard.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/noticeboard.yml index 421ab93be9..f2315583e3 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/noticeboard.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/noticeboard.yml @@ -50,8 +50,8 @@ - Write - type: UserInterface interfaces: - - key: enum.StorageUiKey.Key - type: StorageBoundUserInterface + enum.StorageUiKey.Key: + type: StorageBoundUserInterface - type: ContainerContainer containers: storagebase: !type:Container diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/station_map.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/station_map.yml index d1df619b7a..2ae5040910 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/station_map.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/station_map.yml @@ -86,7 +86,7 @@ acts: [ "Destruction" ] - type: UserInterface interfaces: - - key: enum.StationMapUiKey.Key + enum.StationMapUiKey.Key: type: StationMapBoundUserInterface - type: entity diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/surveillance_camera.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/surveillance_camera.yml index 6e56bb855f..3347b0e4b1 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/surveillance_camera.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/surveillance_camera.yml @@ -42,9 +42,9 @@ InUse: camera_in_use - type: UserInterface interfaces: - - key: enum.SurveillanceCameraSetupUiKey.Camera + enum.SurveillanceCameraSetupUiKey.Camera: type: SurveillanceCameraSetupBoundUi - - key: enum.WiresUiKey.Key + enum.WiresUiKey.Key: type: WiresBoundUserInterface - type: StaticPrice price: 200 diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/timer.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/timer.yml index 0c284eec7a..dd7eb5bea8 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/timer.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/timer.yml @@ -32,8 +32,8 @@ key: enum.SignalTimerUiKey.Key - type: UserInterface interfaces: - - key: enum.SignalTimerUiKey.Key - type: SignalTimerBoundUserInterface + enum.SignalTimerUiKey.Key: + type: SignalTimerBoundUserInterface - type: ApcPowerReceiver powerLoad: 100 - type: Electrified diff --git a/Resources/Prototypes/Entities/Structures/Walls/asteroid.yml b/Resources/Prototypes/Entities/Structures/Walls/asteroid.yml index a8cb0b1cb8..0728d2a911 100644 --- a/Resources/Prototypes/Entities/Structures/Walls/asteroid.yml +++ b/Resources/Prototypes/Entities/Structures/Walls/asteroid.yml @@ -295,6 +295,50 @@ state: rock_asteroid_west - state: rock_artifact_fragment +- type: entity + id: AsteroidRockBluespace + parent: AsteroidRock + description: A rock wall. What's that sticking out of it? + suffix: Bluespace + components: + - type: OreVein + oreChance: 1.0 + currentOre: OreBluespace + - type: Sprite + layers: + - state: rock_asteroid + - map: [ "enum.EdgeLayer.South" ] + state: rock_asteroid_south + - map: [ "enum.EdgeLayer.East" ] + state: rock_asteroid_east + - map: [ "enum.EdgeLayer.North" ] + state: rock_asteroid_north + - map: [ "enum.EdgeLayer.West" ] + state: rock_asteroid_west + - state: rock_bluespace + +- type: entity + id: AsteroidRockNormality + parent: AsteroidRock + description: A rock wall. What's that sticking out of it? + suffix: Normality + components: + - type: OreVein + oreChance: 1.0 + currentOre: OreNormality + - type: Sprite + layers: + - state: rock_asteroid + - map: [ "enum.EdgeLayer.South" ] + state: rock_asteroid_south + - map: [ "enum.EdgeLayer.East" ] + state: rock_asteroid_east + - map: [ "enum.EdgeLayer.North" ] + state: rock_asteroid_north + - map: [ "enum.EdgeLayer.West" ] + state: rock_asteroid_west + - state: rock_bluespace + - type: entity id: AsteroidRockMining parent: AsteroidRock @@ -632,6 +676,50 @@ state: rock_west - state: rock_salt +- type: entity + id: WallRockBluespace + parent: WallRock + description: A rock wall. What's that sticking out of it? + suffix: Bluespace + components: + - type: OreVein + oreChance: 1.0 + currentOre: OreBluespace + - type: Sprite + layers: + - state: rock + - map: [ "enum.EdgeLayer.South" ] + state: rock_south + - map: [ "enum.EdgeLayer.East" ] + state: rock_east + - map: [ "enum.EdgeLayer.North" ] + state: rock_north + - map: [ "enum.EdgeLayer.West" ] + state: rock_west + - state: rock_bluespace + +- type: entity + id: WallRockNormality + parent: WallRock + description: A rock wall. What's that sticking out of it? + suffix: Normality + components: + - type: OreVein + oreChance: 1.0 + currentOre: OreNormality + - type: Sprite + layers: + - state: rock + - map: [ "enum.EdgeLayer.South" ] + state: rock_south + - map: [ "enum.EdgeLayer.East" ] + state: rock_east + - map: [ "enum.EdgeLayer.North" ] + state: rock_north + - map: [ "enum.EdgeLayer.West" ] + state: rock_west + - state: rock_bluespace + # Basalt variants - type: entity id: WallRockBasalt @@ -875,6 +963,50 @@ state: rock_wall_west - state: rock_salt +- type: entity + id: WallRockBasaltBluespace + parent: WallRockBluespace + description: A rock wall. What's that sticking out of it? + suffix: Bluespace + components: + - type: OreVein + oreChance: 1.0 + currentOre: OreBluespace + - type: Sprite + layers: + - state: rock_wall + - map: [ "enum.EdgeLayer.South" ] + state: rock_wall_south + - map: [ "enum.EdgeLayer.East" ] + state: rock_wall_east + - map: [ "enum.EdgeLayer.North" ] + state: rock_wall_north + - map: [ "enum.EdgeLayer.West" ] + state: rock_wall_west + - state: rock_bluespace + +- type: entity + id: WallRockBasaltNormality + parent: WallRockNormality + description: A rock wall. What's that sticking out of it? + suffix: Normality + components: + - type: OreVein + oreChance: 1.0 + currentOre: OreNormality + - type: Sprite + layers: + - state: rock_wall + - map: [ "enum.EdgeLayer.South" ] + state: rock_wall_south + - map: [ "enum.EdgeLayer.East" ] + state: rock_wall_east + - map: [ "enum.EdgeLayer.North" ] + state: rock_wall_north + - map: [ "enum.EdgeLayer.West" ] + state: rock_wall_west + - state: rock_bluespace + # Snow variants - type: entity id: WallRockSnow @@ -1118,6 +1250,50 @@ state: rock_snow_west - state: rock_salt +- type: entity + id: WallRockSnowBluespace + parent: WallRockSnow + description: A rock wall. What's that sticking out of it? + suffix: Bluespace + components: + - type: OreVein + oreChance: 1.0 + currentOre: OreBluespace + - type: Sprite + layers: + - state: rock_snow + - map: [ "enum.EdgeLayer.South" ] + state: rock_snow_south + - map: [ "enum.EdgeLayer.East" ] + state: rock_snow_east + - map: [ "enum.EdgeLayer.North" ] + state: rock_snow_north + - map: [ "enum.EdgeLayer.West" ] + state: rock_snow_west + - state: rock_bluespace + +- type: entity + id: WallRockSnowNormality + parent: WallRockSnow + description: A rock wall. What's that sticking out of it? + suffix: Normality + components: + - type: OreVein + oreChance: 1.0 + currentOre: OreNormality + - type: Sprite + layers: + - state: rock_snow + - map: [ "enum.EdgeLayer.South" ] + state: rock_snow_south + - map: [ "enum.EdgeLayer.East" ] + state: rock_snow_east + - map: [ "enum.EdgeLayer.North" ] + state: rock_snow_north + - map: [ "enum.EdgeLayer.West" ] + state: rock_snow_west + - state: rock_bluespace + # Sand variants - type: entity id: WallRockSand @@ -1361,6 +1537,50 @@ state: rock_sand_west - state: rock_salt +- type: entity + id: WallRockSandBluespace + parent: WallRockSand + description: A rock wall. What's that sticking out of it? + suffix: Bluespace + components: + - type: OreVein + oreChance: 1.0 + currentOre: OreBluespace + - type: Sprite + layers: + - state: rock_sand + - map: [ "enum.EdgeLayer.South" ] + state: rock_sand_south + - map: [ "enum.EdgeLayer.East" ] + state: rock_sand_east + - map: [ "enum.EdgeLayer.North" ] + state: rock_sand_north + - map: [ "enum.EdgeLayer.West" ] + state: rock_sand_west + - state: rock_bluespace + +- type: entity + id: WallRockSandNormality + parent: WallRockSand + description: A rock wall. What's that sticking out of it? + suffix: Normality + components: + - type: OreVein + oreChance: 1.0 + currentOre: OreNormality + - type: Sprite + layers: + - state: rock_sand + - map: [ "enum.EdgeLayer.South" ] + state: rock_sand_south + - map: [ "enum.EdgeLayer.East" ] + state: rock_sand_east + - map: [ "enum.EdgeLayer.North" ] + state: rock_sand_north + - map: [ "enum.EdgeLayer.West" ] + state: rock_sand_west + - state: rock_bluespace + # Chromite variants - type: entity id: WallRockChromite @@ -1604,6 +1824,50 @@ state: rock_chromite_west - state: rock_salt +- type: entity + id: WallRockChromiteBluespace + parent: WallRockChromite + description: A rock wall. What's that sticking out of it? + suffix: Bluespace + components: + - type: OreVein + oreChance: 1.0 + currentOre: OreBluespace + - type: Sprite + layers: + - state: rock_chromite + - map: [ "enum.EdgeLayer.South" ] + state: rock_chromite_south + - map: [ "enum.EdgeLayer.East" ] + state: rock_chromite_east + - map: [ "enum.EdgeLayer.North" ] + state: rock_chromite_north + - map: [ "enum.EdgeLayer.West" ] + state: rock_chromite_west + - state: rock_bluespace + +- type: entity + id: WallRockChromiteNormality + parent: WallRockChromite + description: A rock wall. What's that sticking out of it? + suffix: Normality + components: + - type: OreVein + oreChance: 1.0 + currentOre: OreNormality + - type: Sprite + layers: + - state: rock_chromite + - map: [ "enum.EdgeLayer.South" ] + state: rock_chromite_south + - map: [ "enum.EdgeLayer.East" ] + state: rock_chromite_east + - map: [ "enum.EdgeLayer.North" ] + state: rock_chromite_north + - map: [ "enum.EdgeLayer.West" ] + state: rock_chromite_west + - state: rock_bluespace + # Andesite variants - type: entity id: WallRockAndesite @@ -1846,3 +2110,47 @@ - map: [ "enum.EdgeLayer.West" ] state: rock_andesite_west - state: rock_salt + +- type: entity + id: WallRockAndesiteBluespace + parent: WallRockAndesite + description: A rock wall. What's that sticking out of it? + suffix: Bluespace + components: + - type: OreVein + oreChance: 1.0 + currentOre: OreBluespace + - type: Sprite + layers: + - state: rock_andesite + - map: [ "enum.EdgeLayer.South" ] + state: rock_andesite_south + - map: [ "enum.EdgeLayer.East" ] + state: rock_andesite_east + - map: [ "enum.EdgeLayer.North" ] + state: rock_andesite_north + - map: [ "enum.EdgeLayer.West" ] + state: rock_andesite_west + - state: rock_bluespace + +- type: entity + id: WallRockAndesiteNormality + parent: WallRockAndesite + description: A rock wall. What's that sticking out of it? + suffix: Normality + components: + - type: OreVein + oreChance: 1.0 + currentOre: OreNormality + - type: Sprite + layers: + - state: rock_andesite + - map: [ "enum.EdgeLayer.South" ] + state: rock_andesite_south + - map: [ "enum.EdgeLayer.East" ] + state: rock_andesite_east + - map: [ "enum.EdgeLayer.North" ] + state: rock_andesite_north + - map: [ "enum.EdgeLayer.West" ] + state: rock_andesite_west + - state: rock_bluespace diff --git a/Resources/Prototypes/Entities/Structures/Walls/fence_metal.yml b/Resources/Prototypes/Entities/Structures/Walls/fence_metal.yml index 1dca59225c..78e28d27a5 100644 --- a/Resources/Prototypes/Entities/Structures/Walls/fence_metal.yml +++ b/Resources/Prototypes/Entities/Structures/Walls/fence_metal.yml @@ -79,6 +79,9 @@ cost: 2 delay: 2 fx: EffectRCDDeconstruct2 + - type: InteractionVerbs + allowedVerbs: + - Rattle - type: entity parent: BaseFenceMetal @@ -107,11 +110,6 @@ - FullTileMask layer: - TableLayer - - type: InteractionPopup - interactSuccessString: fence-rattle-success - messagePerceivedByOthers: fence-rattle-success - interactSuccessSound: - collection: FenceRattle - type: Destructible thresholds: - trigger: @@ -166,11 +164,6 @@ layer: - MidImpassable - LowImpassable - - type: InteractionPopup - interactSuccessString: fence-rattle-success - messagePerceivedByOthers: fence-rattle-success - interactSuccessSound: - collection: FenceRattle - type: Destructible thresholds: - trigger: @@ -234,11 +227,6 @@ layer: - MidImpassable - LowImpassable - - type: InteractionPopup - interactSuccessString: fence-rattle-success - messagePerceivedByOthers: fence-rattle-success - interactSuccessSound: - collection: FenceRattle - type: Construction graph: FenceMetal node: corner @@ -271,11 +259,6 @@ layer: - MidImpassable - LowImpassable - - type: InteractionPopup - interactSuccessString: fence-rattle-success - messagePerceivedByOthers: fence-rattle-success - interactSuccessSound: - collection: FenceRattle - type: Construction graph: FenceMetal node: end diff --git a/Resources/Prototypes/Entities/Structures/Walls/grille.yml b/Resources/Prototypes/Entities/Structures/Walls/grille.yml index 11ada142fa..7be721b6f9 100644 --- a/Resources/Prototypes/Entities/Structures/Walls/grille.yml +++ b/Resources/Prototypes/Entities/Structures/Walls/grille.yml @@ -32,7 +32,7 @@ node: grille deconstructionTarget: start - type: Damageable - damageContainer: Inorganic + damageContainer: StructuralInorganic damageModifierSet: PerforatedMetallic - type: PowerConsumer showInMonitor: false diff --git a/Resources/Prototypes/Entities/Structures/Walls/walls.yml b/Resources/Prototypes/Entities/Structures/Walls/walls.yml index b5b55dbd54..675de3dab5 100644 --- a/Resources/Prototypes/Entities/Structures/Walls/walls.yml +++ b/Resources/Prototypes/Entities/Structures/Walls/walls.yml @@ -48,7 +48,7 @@ - type: StaticPrice #was DynamicPrice price: 75 - type: RadiationBlocker - resistance: 2 + resistance: 1 - type: BlockWeather - type: entity @@ -399,7 +399,7 @@ key: walls base: plasma - type: RadiationBlocker - resistance: 5 + resistance: 7 - type: entity parent: BaseWall @@ -549,8 +549,6 @@ node: girder - !type:DoActsBehavior acts: ["Destruction"] - destroySound: - collection: MetalBreak - type: IconSmooth key: walls base: reinf_over @@ -570,7 +568,7 @@ - type: StaticPrice price: 150 - type: RadiationBlocker - resistance: 5 + resistance: 8 - type: entity parent: WallReinforced @@ -806,8 +804,6 @@ node: girder - !type:DoActsBehavior acts: ["Destruction"] - destroySound: - collection: MetalBreak - type: Construction graph: Girder node: diagonalshuttleWall @@ -977,7 +973,7 @@ key: walls base: uranium - type: RadiationBlocker - resistance: 6 + resistance: 10 - type: entity parent: BaseWall @@ -1212,7 +1208,7 @@ name: force wall components: - type: TimedDespawn - lifetime: 20 + lifetime: 12 - type: Tag tags: - Wall diff --git a/Resources/Prototypes/Entities/Structures/Windows/clockwork.yml b/Resources/Prototypes/Entities/Structures/Windows/clockwork.yml index d6b024bf36..a27f9fa0d2 100644 --- a/Resources/Prototypes/Entities/Structures/Windows/clockwork.yml +++ b/Resources/Prototypes/Entities/Structures/Windows/clockwork.yml @@ -115,7 +115,7 @@ state: state0 - type: IconSmooth mode: Diagonal - key: windows + key: walls base: state - type: Icon sprite: Structures/Windows/clockwork_diagonal.rsi diff --git a/Resources/Prototypes/Entities/Structures/Windows/mining.yml b/Resources/Prototypes/Entities/Structures/Windows/mining.yml index 82d11b732b..1aa5d46bdf 100644 --- a/Resources/Prototypes/Entities/Structures/Windows/mining.yml +++ b/Resources/Prototypes/Entities/Structures/Windows/mining.yml @@ -66,7 +66,7 @@ state: state0 - type: IconSmooth mode: Diagonal - key: windows + key: walls base: state - type: Icon sprite: Structures/Windows/mining_diagonal.rsi diff --git a/Resources/Prototypes/Entities/Structures/Windows/plasma.yml b/Resources/Prototypes/Entities/Structures/Windows/plasma.yml index 36a12f2d84..1bd0356e95 100644 --- a/Resources/Prototypes/Entities/Structures/Windows/plasma.yml +++ b/Resources/Prototypes/Entities/Structures/Windows/plasma.yml @@ -51,7 +51,7 @@ - type: StaticPrice price: 60 - type: RadiationBlocker - resistance: 2 + resistance: 4 - type: entity id: PlasmaWindowDirectional @@ -113,7 +113,7 @@ state: state0 - type: IconSmooth mode: Diagonal - key: windows + key: walls base: state - type: Icon sprite: Structures/Windows/plasma_diagonal.rsi diff --git a/Resources/Prototypes/Entities/Structures/Windows/plastitanium.yml b/Resources/Prototypes/Entities/Structures/Windows/plastitanium.yml index cb05f80cd1..af8b6ee1b0 100644 --- a/Resources/Prototypes/Entities/Structures/Windows/plastitanium.yml +++ b/Resources/Prototypes/Entities/Structures/Windows/plastitanium.yml @@ -66,7 +66,7 @@ state: state0 - type: IconSmooth mode: Diagonal - key: windows + key: walls base: state - type: Icon sprite: Structures/Windows/plastitanium_window_diagonal.rsi diff --git a/Resources/Prototypes/Entities/Structures/Windows/reinforced.yml b/Resources/Prototypes/Entities/Structures/Windows/reinforced.yml index d8b6c7d11d..9914ad8399 100644 --- a/Resources/Prototypes/Entities/Structures/Windows/reinforced.yml +++ b/Resources/Prototypes/Entities/Structures/Windows/reinforced.yml @@ -106,7 +106,7 @@ - type: RCDDeconstructable cost: 4 delay: 4 - fx: EffectRCDDeconstruct4 + fx: EffectRCDDeconstruct4 - type: Destructible thresholds: - trigger: @@ -150,7 +150,7 @@ state: state0 - type: IconSmooth mode: Diagonal - key: windows + key: walls base: state - type: Icon sprite: Structures/Windows/reinforced_window_diagonal.rsi diff --git a/Resources/Prototypes/Entities/Structures/Windows/rplasma.yml b/Resources/Prototypes/Entities/Structures/Windows/rplasma.yml index 93859b1db2..3c35d03a43 100644 --- a/Resources/Prototypes/Entities/Structures/Windows/rplasma.yml +++ b/Resources/Prototypes/Entities/Structures/Windows/rplasma.yml @@ -12,7 +12,7 @@ damageContainer: StructuralInorganic damageModifierSet: RGlass - type: RadiationBlocker - resistance: 4 + resistance: 8 - type: Destructible thresholds: - trigger: @@ -125,7 +125,7 @@ state: state0 - type: IconSmooth mode: Diagonal - key: windows + key: walls base: state - type: Icon sprite: Structures/Windows/reinforced_plasma_diagonal.rsi diff --git a/Resources/Prototypes/Entities/Structures/Windows/ruranium.yml b/Resources/Prototypes/Entities/Structures/Windows/ruranium.yml index e26fec65b7..2796c89c1a 100644 --- a/Resources/Prototypes/Entities/Structures/Windows/ruranium.yml +++ b/Resources/Prototypes/Entities/Structures/Windows/ruranium.yml @@ -51,7 +51,7 @@ - type: StaticPrice price: 140 - type: RadiationBlocker - resistance: 5 + resistance: 10 - type: entity id: UraniumReinforcedWindowDirectional @@ -116,7 +116,7 @@ state: state0 - type: IconSmooth mode: Diagonal - key: windows + key: walls base: state - type: Icon sprite: Structures/Windows/reinforced_uranium_diagonal.rsi diff --git a/Resources/Prototypes/Entities/Structures/Windows/shuttle.yml b/Resources/Prototypes/Entities/Structures/Windows/shuttle.yml index 6af5467d94..b1850d6e8f 100644 --- a/Resources/Prototypes/Entities/Structures/Windows/shuttle.yml +++ b/Resources/Prototypes/Entities/Structures/Windows/shuttle.yml @@ -63,7 +63,7 @@ state: state0 - type: IconSmooth mode: Diagonal - key: windows + key: walls base: state - type: Icon sprite: Structures/Windows/shuttle_window_diagonal.rsi diff --git a/Resources/Prototypes/Entities/Structures/Windows/uranium.yml b/Resources/Prototypes/Entities/Structures/Windows/uranium.yml index e5228bc593..9bc361212e 100644 --- a/Resources/Prototypes/Entities/Structures/Windows/uranium.yml +++ b/Resources/Prototypes/Entities/Structures/Windows/uranium.yml @@ -49,7 +49,7 @@ - type: StaticPrice price: 80 - type: RadiationBlocker - resistance: 3 + resistance: 5 - type: entity id: UraniumWindowDirectional diff --git a/Resources/Prototypes/Entities/Structures/Windows/window.yml b/Resources/Prototypes/Entities/Structures/Windows/window.yml index 443df0ea53..c8cc53106b 100644 --- a/Resources/Prototypes/Entities/Structures/Windows/window.yml +++ b/Resources/Prototypes/Entities/Structures/Windows/window.yml @@ -74,11 +74,6 @@ - type: IconSmooth key: walls base: window - - type: InteractionPopup - interactSuccessString: comp-window-knock - messagePerceivedByOthers: comp-window-knock - interactSuccessSound: - path: /Audio/Effects/glass_knock.ogg - type: Construction graph: Window node: window @@ -92,7 +87,10 @@ - type: StaticPrice price: 100 - type: BlockWeather - + - type: InteractionVerbs + allowedVerbs: + - KnockOn + - type: entity id: WindowRCDResistant parent: Window @@ -128,11 +126,6 @@ - type: Icon sprite: Structures/Windows/directional.rsi state: window - - type: InteractionPopup - interactSuccessString: comp-window-knock - messagePerceivedByOthers: comp-window-knock - interactSuccessSound: - path: /Audio/Effects/glass_knock.ogg - type: Physics - type: Fixtures fixtures: @@ -196,9 +189,12 @@ sprite: Structures/Windows/cracks_directional.rsi - type: StaticPrice price: 10 + - type: InteractionVerbs + allowedVerbs: + - KnockOn - type: entity - id: WindowDirectionalRCDResistant + id: WindowDirectionalRCDResistant parent: WindowDirectional abstract: true components: @@ -237,7 +233,7 @@ state: state0 - type: IconSmooth mode: Diagonal - key: windows + key: walls base: state - type: Icon sprite: Structures/Windows/window_diagonal.rsi diff --git a/Resources/Prototypes/Entities/Structures/cargo_telepad.yml b/Resources/Prototypes/Entities/Structures/cargo_telepad.yml index d395235a53..9dc9f77cff 100644 --- a/Resources/Prototypes/Entities/Structures/cargo_telepad.yml +++ b/Resources/Prototypes/Entities/Structures/cargo_telepad.yml @@ -47,3 +47,5 @@ board: CargoTelepadMachineCircuitboard - type: Appearance - type: CollideOnAnchor + - type: NameIdentifier + group: CargoTelepads diff --git a/Resources/Prototypes/Entities/Structures/cryopod.yml b/Resources/Prototypes/Entities/Structures/cryopod.yml index 9063e8b138..bdb8862a82 100644 --- a/Resources/Prototypes/Entities/Structures/cryopod.yml +++ b/Resources/Prototypes/Entities/Structures/cryopod.yml @@ -12,8 +12,8 @@ map: ["base"] - type: UserInterface interfaces: - - key: enum.CryostorageUIKey.Key - type: CryostorageBoundUserInterface + enum.CryostorageUIKey.Key: + type: CryostorageBoundUserInterface - type: ActivatableUI key: enum.CryostorageUIKey.Key - type: AccessReader diff --git a/Resources/Prototypes/Entities/Structures/hydro_tray.yml b/Resources/Prototypes/Entities/Structures/hydro_tray.yml index 68a0cbd38e..82a19211fc 100644 --- a/Resources/Prototypes/Entities/Structures/hydro_tray.yml +++ b/Resources/Prototypes/Entities/Structures/hydro_tray.yml @@ -91,6 +91,9 @@ - type: GuideHelp guides: - Botany + - type: Tag + tags: + - NoPaint - type: entity parent: hydroponicsTray diff --git a/Resources/Prototypes/Entities/Structures/plastic_flaps.yml b/Resources/Prototypes/Entities/Structures/plastic_flaps.yml index c4ee507395..5d5ff390ba 100644 --- a/Resources/Prototypes/Entities/Structures/plastic_flaps.yml +++ b/Resources/Prototypes/Entities/Structures/plastic_flaps.yml @@ -1,9 +1,7 @@ - type: entity - id: PlasticFlapsClear + id: PlasticFlapsBase parent: BaseStructureDynamic - name: plastic flaps - suffix: Clear - description: Heavy duty, plastic flaps. Definitely can't get past those. No way. + abstract: true placement: mode: SnapgridCenter components: @@ -23,9 +21,10 @@ bounds: "-0.49,-0.49,0.49,0.49" density: 100 mask: - - TabletopMachineMask + - Impassable layer: - MidImpassable + - BulletImpassable - type: Damageable damageContainer: StructuralInorganic damageModifierSet: Metallic @@ -33,22 +32,31 @@ thresholds: - trigger: !type:DamageTrigger - damage: 100 + damage: 50 behaviors: - !type:DoActsBehavior acts: ["Destruction"] - type: IconSmooth key: walls mode: NoSprite + - type: StaticPrice + price: 83 + - type: RequireProjectileTarget + +- type: entity + id: PlasticFlapsClear + parent: PlasticFlapsBase + name: plastic flaps + suffix: Clear + description: Heavy duty, plastic flaps. Definitely can't get past those. No way. + components: - type: Construction graph: PlasticFlapsGraph node: plasticFlaps - - type: StaticPrice - price: 83 - type: entity id: PlasticFlapsOpaque - parent: PlasticFlapsClear + parent: PlasticFlapsBase name: plastic flaps suffix: Opaque description: Heavy duty, plastic flaps. Definitely can't get past those. No way. @@ -61,10 +69,11 @@ bounds: "-0.49,-0.49,0.49,0.49" density: 100 mask: - - TabletopMachineMask + - Impassable layer: - Opaque - MidImpassable + - BulletImpassable - type: Occluder - type: Construction graph: PlasticFlapsGraph @@ -72,7 +81,7 @@ - type: entity id: PlasticFlapsAirtightClear - parent: PlasticFlapsClear + parent: PlasticFlapsBase name: airtight plastic flaps suffix: Airtight, Clear description: Heavy duty, slightly stronger, airtight plastic flaps. Definitely can't get past those. No way. @@ -81,20 +90,17 @@ thresholds: - trigger: !type:DamageTrigger - damage: 150 + damage: 75 behaviors: - !type:DoActsBehavior acts: ["Destruction"] - type: Airtight - - type: Construction - graph: PlasticFlapsGraph - node: airtightFlaps - type: StaticPrice price: 100 - type: entity id: PlasticFlapsAirtightOpaque - parent: PlasticFlapsOpaque + parent: PlasticFlapsBase name: airtight plastic flaps suffix: Airtight, Opaque description: Heavy duty, slightly stronger, airtight plastic flaps. Definitely can't get past those. No way. @@ -103,13 +109,10 @@ thresholds: - trigger: !type:DamageTrigger - damage: 150 + damage: 75 behaviors: - !type:DoActsBehavior acts: ["Destruction"] - type: Airtight - - type: Construction - graph: PlasticFlapsGraph - node: airtightopaqueFlaps - type: StaticPrice price: 100 diff --git a/Resources/Prototypes/Entities/Surgery/surgeries.yml b/Resources/Prototypes/Entities/Surgery/surgeries.yml new file mode 100644 index 0000000000..9f033333e4 --- /dev/null +++ b/Resources/Prototypes/Entities/Surgery/surgeries.yml @@ -0,0 +1,539 @@ +- type: entity + id: SurgeryBase + categories: [ HideSpawnMenu ] + +- type: entity + parent: SurgeryBase + id: SurgeryOpenIncision + name: Open Incision + categories: [ HideSpawnMenu ] + components: + - type: Surgery + steps: + - SurgeryStepOpenIncisionScalpel + - SurgeryStepRetractSkin + - SurgeryStepClampBleeders + - type: SurgeryPartPresentCondition + +- type: entity + parent: SurgeryBase + id: SurgeryCloseIncision + name: Close Incision + categories: [ HideSpawnMenu ] + components: + - type: Surgery + priority: 1 + steps: + - SurgeryStepCloseBones + - SurgeryStepMendRibcage + - SurgeryStepCloseIncision + - type: SurgeryPartPresentCondition + +- type: entity + parent: SurgeryBase + id: SurgeryOpenRibcage + name: Open Ribcage + categories: [ HideSpawnMenu ] + components: + - type: Surgery + requirement: SurgeryOpenIncision + steps: + - SurgeryStepSawBones + - SurgeryStepPriseOpenBones + - type: SurgeryPartCondition + part: Torso + +- type: entity + parent: SurgeryBase + id: SurgeryRemovePart + name: Remove Part + categories: [ HideSpawnMenu ] + components: + - type: Surgery + requirement: SurgeryOpenIncision + steps: + - SurgeryStepSawFeature + - SurgeryStepClampInternalBleeders + - SurgeryStepRemoveFeature + - type: SurgeryPartCondition + part: Torso + inverse: true + +# I fucking hate hardcoding all of this shit to accomodate for surgery BUI. +# If anyone can give me pointers on how to make it better I'd be incredibly grateful. + +- type: entity + parent: SurgeryBase + id: SurgeryAttachHead + name: Attach Head + categories: [ HideSpawnMenu ] + components: + - type: Surgery + #requirement: SurgeryOpenIncision + steps: + - SurgeryStepInsertFeature + - SurgeryStepSealWounds + - type: SurgeryPartCondition + part: Torso + - type: SurgeryPartRemovedCondition + part: Head + +- type: entity + parent: SurgeryBase + id: SurgeryAttachLeftArm + name: Attach Left Arm + categories: [ HideSpawnMenu ] + components: + - type: Surgery + requirement: SurgeryOpenIncision + steps: + - SurgeryStepInsertFeature + - SurgeryStepSealWounds + - type: SurgeryPartCondition + part: Torso + - type: SurgeryPartRemovedCondition + part: Arm + symmetry: Left + +- type: entity + parent: SurgeryBase + id: SurgeryAttachRightArm + name: Attach Right Arm + categories: [ HideSpawnMenu ] + components: + - type: Surgery + requirement: SurgeryOpenIncision + steps: + - SurgeryStepInsertFeature + - SurgeryStepSealWounds + - type: SurgeryPartCondition + part: Torso + - type: SurgeryPartRemovedCondition + part: Arm + symmetry: Right + +- type: entity + parent: SurgeryBase + id: SurgeryAttachLeftLeg + name: Attach Left Leg + categories: [ HideSpawnMenu ] + components: + - type: Surgery + requirement: SurgeryOpenIncision + steps: + - SurgeryStepInsertFeature + - SurgeryStepSealWounds + - type: SurgeryPartCondition + part: Torso + - type: SurgeryPartRemovedCondition + part: Leg + symmetry: Left + +- type: entity + parent: SurgeryBase + id: SurgeryAttachRightLeg + name: Attach Right Leg + categories: [ HideSpawnMenu ] + components: + - type: Surgery + requirement: SurgeryOpenIncision + steps: + - SurgeryStepInsertFeature + - SurgeryStepSealWounds + - type: SurgeryPartCondition + part: Torso + - type: SurgeryPartRemovedCondition + part: Leg + symmetry: Right + +- type: entity + parent: SurgeryBase + id: SurgeryAttachLeftHand + name: Attach Left Hand + categories: [ HideSpawnMenu ] + components: + - type: Surgery + requirement: SurgeryOpenIncision + steps: + - SurgeryStepInsertFeature + - SurgeryStepSealWounds + - type: SurgeryPartCondition + part: Arm + symmetry: Left + - type: SurgeryPartRemovedCondition + part: Hand + symmetry: Left + +- type: entity + parent: SurgeryBase + id: SurgeryAttachRightHand + name: Attach Right Hand + categories: [ HideSpawnMenu ] + components: + - type: Surgery + requirement: SurgeryOpenIncision + steps: + - SurgeryStepInsertFeature + - SurgeryStepSealWounds + - type: SurgeryPartCondition + part: Arm + symmetry: Right + - type: SurgeryPartRemovedCondition + part: Hand + symmetry: Right + +- type: entity + parent: SurgeryBase + id: SurgeryAttachLeftFoot + name: Attach Left Foot + categories: [ HideSpawnMenu ] + components: + - type: Surgery + requirement: SurgeryOpenIncision + steps: + - SurgeryStepInsertFeature + - SurgeryStepSealWounds + - type: SurgeryPartCondition + part: Leg + symmetry: Left + - type: SurgeryPartRemovedCondition + part: Foot + symmetry: Left + +- type: entity + parent: SurgeryBase + id: SurgeryAttachRightFoot + name: Attach Right Foot + categories: [ HideSpawnMenu ] + components: + - type: Surgery + requirement: SurgeryOpenIncision + steps: + - SurgeryStepInsertFeature + - SurgeryStepSealWounds + - type: SurgeryPartCondition + part: Leg + symmetry: Right + - type: SurgeryPartRemovedCondition + part: Foot + symmetry: Right + +#- type: entity +# parent: SurgeryBase +# id: SurgeryAlienEmbryoRemoval +# name: Alien Embryo Removal +# description: Removal of an alien embryo from the body. +# categories: [ HideSpawnMenu ] +# components: +# - type: Surgery +# priority: -1 +# requirement: SurgeryOpenRibcage +# steps: +# - SurgeryStepCutLarvaRoots +# - SurgeryStepRemoveLarva +# - type: SurgeryLarvaCondition +# - type: SurgeryPartCondition +# part: Torso + +- type: entity + parent: SurgeryBase + id: SurgeryTendWoundsBrute + name: Tend Bruise Wounds + categories: [ HideSpawnMenu ] + components: + - type: Surgery + steps: + - SurgeryStepCarefulIncisionScalpel + - SurgeryStepRepairBruteTissue + - SurgeryStepSealTendWound + - type: SurgeryWoundedCondition + +- type: entity + parent: SurgeryBase + id: SurgeryTendWoundsBurn + name: Tend Burn Wounds + categories: [ HideSpawnMenu ] + components: + - type: Surgery + steps: + - SurgeryStepCarefulIncisionScalpel + - SurgeryStepRepairBurnTissue + - SurgeryStepSealTendWound + - type: SurgeryWoundedCondition + +- type: entity + parent: SurgeryBase + id: SurgeryInsertItem + name: Cavity Implant + categories: [ HideSpawnMenu ] + components: + - type: Surgery + requirement: SurgeryOpenRibcage + steps: + - SurgeryStepInsertItem + - SurgeryStepRemoveItem + - type: SurgeryPartCondition + part: Torso + +# Note for any Organ manipulation surgeries. Most of the organs are only defined on the server. +# I added some of them to the client too, but we should probably move them to a shared +# prototype at some point. + +- type: entity + parent: SurgeryBase + id: SurgeryRemoveBrain + name: Remove Brain + categories: [ HideSpawnMenu ] + components: + - type: Surgery + requirement: SurgeryOpenIncision + steps: + - SurgeryStepSawBones + - SurgeryStepClampInternalBleeders + - SurgeryStepRemoveOrgan + - type: SurgeryPartCondition + part: Head + - type: SurgeryOrganCondition + organ: + - type: Brain + +- type: entity + parent: SurgeryBase + id: SurgeryInsertBrain + name: Insert Brain + categories: [ HideSpawnMenu ] + components: + - type: Surgery + requirement: SurgeryOpenIncision + steps: + - SurgeryStepSawBones + - SurgeryStepInsertOrgan + - SurgeryStepSealOrganWound + - type: SurgeryPartCondition + part: Head + - type: SurgeryOrganCondition + organ: + - type: Brain + inverse: true + reattaching: true + + +- type: entity + parent: SurgeryBase + id: SurgeryRemoveHeart + name: Remove Heart + categories: [ HideSpawnMenu ] + components: + - type: Surgery + requirement: SurgeryOpenRibcage + steps: + - SurgeryStepSawBones + - SurgeryStepClampInternalBleeders + - SurgeryStepRemoveOrgan + - type: SurgeryPartCondition + part: Torso + - type: SurgeryOrganCondition + organ: + - type: Heart + +- type: entity + parent: SurgeryBase + id: SurgeryInsertHeart + name: Insert Heart + categories: [ HideSpawnMenu ] + components: + - type: Surgery + requirement: SurgeryOpenRibcage + steps: + - SurgeryStepSawBones + - SurgeryStepInsertHeart + - SurgeryStepSealOrganWound + - type: SurgeryPartCondition + part: Torso + - type: SurgeryOrganCondition + organ: + - type: Heart + inverse: true + reattaching: true + +- type: entity + parent: SurgeryBase + id: SurgeryRemoveLiver + name: Remove Liver + categories: [ HideSpawnMenu ] + components: + - type: Surgery + requirement: SurgeryOpenRibcage + steps: + - SurgeryStepSawBones + - SurgeryStepClampInternalBleeders + - SurgeryStepRemoveOrgan + - type: SurgeryPartCondition + part: Torso + - type: SurgeryOrganCondition + organ: + - type: Liver + +- type: entity + parent: SurgeryBase + id: SurgeryInsertLiver + name: Insert Liver + categories: [ HideSpawnMenu ] + components: + - type: Surgery + requirement: SurgeryOpenRibcage + steps: + - SurgeryStepSawBones + - SurgeryStepInsertLiver + - SurgeryStepSealOrganWound + - type: SurgeryPartCondition + part: Torso + - type: SurgeryOrganCondition + organ: + - type: Liver + inverse: true + reattaching: true + +- type: entity + parent: SurgeryBase + id: SurgeryRemoveLungs + name: Remove Lungs + categories: [ HideSpawnMenu ] + components: + - type: Surgery + requirement: SurgeryOpenRibcage + steps: + - SurgeryStepSawBones + - SurgeryStepClampInternalBleeders + - SurgeryStepRemoveOrgan + - type: SurgeryPartCondition + part: Torso + - type: SurgeryOrganCondition + organ: + - type: Lung + +- type: entity + parent: SurgeryBase + id: SurgeryInsertLungs + name: Insert Lungs + categories: [ HideSpawnMenu ] + components: + - type: Surgery + requirement: SurgeryOpenRibcage + steps: + - SurgeryStepSawBones + - SurgeryStepInsertLungs + - SurgeryStepSealOrganWound + - type: SurgeryPartCondition + part: Torso + - type: SurgeryOrganCondition + organ: + - type: Lung + inverse: true + reattaching: true + +- type: entity + parent: SurgeryBase + id: SurgeryRemoveEyes + name: Remove Eyes + categories: [ HideSpawnMenu ] + components: + - type: Surgery + requirement: SurgeryOpenIncision + steps: + - SurgeryStepSawBones + - SurgeryStepClampInternalBleeders + - SurgeryStepRemoveOrgan + - type: SurgeryPartCondition + part: Head + - type: SurgeryOrganCondition + organ: + - type: Eyes + +- type: entity + parent: SurgeryBase + id: SurgeryInsertEyes + name: Insert Eyes + categories: [ HideSpawnMenu ] + components: + - type: Surgery + requirement: SurgeryOpenIncision + steps: + - SurgeryStepSawBones + - SurgeryStepInsertEyes + - SurgeryStepSealOrganWound + - type: SurgeryPartCondition + part: Head + - type: SurgeryOrganCondition + organ: + - type: Eyes + inverse: true + reattaching: true + +# Fluff/Joke Surgeries + +#- type: entity +# parent: SurgeryBase +# id: SurgeryAddFelinidEars +# name: Add cat ears +# categories: [ HideSpawnMenu ] +# components: +# - type: Surgery +# #requirement: SurgeryOpenIncision +# steps: +# - SurgeryStepAddFelinidEars +# - type: SurgeryPartCondition +# part: Head +# - type: SurgeryMarkingCondition +# markingCategory: HeadTop +# matchString: FelinidEars +# inverse: true + +#- type: entity +# parent: SurgeryBase +# id: SurgeryRemoveFelinidEars +# name: Remove cat ears +# categories: [ HideSpawnMenu ] +# components: +# - type: Surgery +# requirement: SurgeryOpenIncision +# steps: +# - SurgeryStepRemoveFelinidEars +# - type: SurgeryPartCondition +# part: Head +# - type: SurgeryMarkingCondition +# markingCategory: HeadTop +# matchString: FelinidEars + +#- type: entity +# parent: SurgeryBase +# id: SurgeryAddFelinidTail +# name: Add cat tail +# categories: [ HideSpawnMenu ] +# components: +# - type: Surgery +# requirement: SurgeryOpenIncision +# steps: +# - SurgeryStepAddFelinidTail +# - type: SurgeryPartCondition +# part: Torso +# - type: SurgeryMarkingCondition +# markingCategory: Tail +# matchString: FelinidTail +# inverse: true + +#- type: entity +# parent: SurgeryBase +# id: SurgeryRemoveFelinidTail +# name: Remove cat tail +# categories: [ HideSpawnMenu ] +# components: +# - type: Surgery +# requirement: SurgeryOpenIncision +# steps: +# - SurgeryStepRemoveFelinidTail +# - type: SurgeryPartCondition +# part: Torso +# - type: SurgeryMarkingCondition +# markingCategory: Tail +# matchString: FelinidTail diff --git a/Resources/Prototypes/Entities/Surgery/surgery_steps.yml b/Resources/Prototypes/Entities/Surgery/surgery_steps.yml new file mode 100644 index 0000000000..434c06f35b --- /dev/null +++ b/Resources/Prototypes/Entities/Surgery/surgery_steps.yml @@ -0,0 +1,563 @@ +- type: entity + id: SurgeryStepBase + categories: [ HideSpawnMenu ] + components: + - type: SurgeryStep + +- type: entity + parent: SurgeryStepBase + id: SurgeryStepOpenIncisionScalpel + name: Cut with a scalpel + categories: [ HideSpawnMenu ] + components: + - type: SurgeryStep + tool: + - type: Scalpel + add: + - type: IncisionOpen + - type: Sprite + sprite: Objects/Specific/Medical/Surgery/scalpel.rsi + state: scalpel + - type: SurgeryDamageChangeEffect + damage: + types: + Bloodloss: 10 + sleepModifier: 0.5 + - type: SurgeryStepEmoteEffect + +- type: entity + parent: SurgeryStepBase + id: SurgeryStepClampBleeders + name: Clamp the bleeders + categories: [ HideSpawnMenu ] + components: + - type: SurgeryStep + tool: + - type: Hemostat + add: + - type: BleedersClamped + - type: Sprite + sprite: Objects/Specific/Medical/Surgery/scissors.rsi + state: hemostat + - type: SurgeryDamageChangeEffect + damage: + types: + Bloodloss: -5 + sleepModifier: 2 + +- type: entity + parent: SurgeryStepBase + id: SurgeryStepRetractSkin + name: Retract the skin + categories: [ HideSpawnMenu ] + components: + - type: SurgeryStep + tool: + - type: Retractor + add: + - type: SkinRetracted + - type: Sprite + sprite: Objects/Specific/Medical/Surgery/scissors.rsi + state: retractor + +- type: entity + parent: SurgeryStepBase + id: SurgeryStepSawBones + name: Saw through bones + categories: [ HideSpawnMenu ] + components: + - type: SurgeryStep + tool: + - type: BoneSaw + add: + - type: RibcageSawed + - type: Sprite + sprite: Objects/Specific/Medical/Surgery/saw.rsi + state: saw + - type: SurgeryStepEmoteEffect + +- type: entity + parent: SurgeryStepBase + id: SurgeryStepPriseOpenBones + name: Prise the bones open + categories: [ HideSpawnMenu ] + components: + - type: SurgeryStep + tool: + - type: Retractor + add: + - type: RibcageOpen + - type: Sprite + sprite: Objects/Specific/Medical/Surgery/scissors.rsi + state: retractor + +#- type: entity +# parent: SurgeryStepBase +# id: SurgeryStepCutLarvaRoots +# name: Cut larva roots +# categories: [ HideSpawnMenu ] +# components: +# - type: SurgeryStep +# skill: 2 +# tool: +# - type: Scalpel +# - type: SurgeryCutLarvaRootsStep +# - type: Sprite +# sprite: Objects/Specific/Medical/Surgery/scalpel.rsi +# state: scalpel +# - type: SurgeryOperatingTableCondition + +#- type: entity +# parent: SurgeryStepBase +# id: SurgeryStepRemoveLarva +# name: Remove larva +# categories: [ HideSpawnMenu ] +# components: +# - type: SurgeryStep +# skill: 2 +# tool: +# - type: Hemostat +# bodyRemove: +# - type: VictimInfected +# - type: Sprite +# sprite: Objects/Specific/Medical/Surgery/scissors.rsi +# state: hemostat +# - type: SurgeryOperatingTableCondition +# - type: SurgeryStepSpawnEffect +# entity: XenoEmbryo + +- type: entity + parent: SurgeryStepBase + id: SurgeryStepCloseBones + name: Close bones + categories: [ HideSpawnMenu ] + components: + - type: SurgeryStep + tool: + - type: Retractor + remove: + - type: RibcageOpen + - type: Sprite + sprite: Objects/Specific/Medical/Surgery/scissors.rsi + state: retractor + +- type: entity + parent: SurgeryStepBase + id: SurgeryStepMendRibcage + name: Mend ribcage + categories: [ HideSpawnMenu ] + components: + - type: SurgeryStep + tool: + - type: BoneGel + remove: + - type: RibcageSawed + - type: Sprite + sprite: Objects/Specific/Medical/Surgery/bone_gel.rsi + state: bone-gel + +- type: entity + parent: SurgeryStepBase + id: SurgeryStepCloseIncision + name: Close incision + categories: [ HideSpawnMenu ] + components: + - type: SurgeryStep + tool: + - type: Cautery + remove: + # This surgery removes a bunch of components that might be leftover from other surgeries in unintended cases. + # Essentially a bit of a fallback for endusers :) + - type: SkinRetracted + - type: BleedersClamped + - type: IncisionOpen + - type: BodyPartReattached + - type: InternalBleedersClamped + - type: Sprite + sprite: Objects/Specific/Medical/Surgery/cautery.rsi + state: cautery + - type: SurgeryDamageChangeEffect + damage: + types: + Heat: -5 + sleepModifier: 2 + - type: SurgeryStepEmoteEffect + +# Feature Insertion + +- type: entity + parent: SurgeryStepBase + id: SurgeryStepInsertFeature + name: Insert part + categories: [ HideSpawnMenu ] + components: + - type: SurgeryStep + tool: + - type: BodyPart + - type: Sprite + sprite: Objects/Specific/Medical/Surgery/manipulation.rsi + state: insertion + - type: SurgeryAddPartStep + +- type: entity + parent: SurgeryStepBase + id: SurgeryStepSealWounds + name: Seal wounds + categories: [ HideSpawnMenu ] + components: + - type: SurgeryStep + tool: + - type: Cautery + remove: + - type: SkinRetracted + - type: BleedersClamped + - type: IncisionOpen + - type: InternalBleedersClamped + - type: SurgeryAffixPartStep + - type: Sprite + sprite: Objects/Specific/Medical/Surgery/cautery.rsi + state: cautery + - type: SurgeryStepEmoteEffect + - type: SurgeryDamageChangeEffect + damage: + types: + Heat: -5 + sleepModifier: 2 + +# Feature Removal + +- type: entity + parent: SurgeryStepBase + id: SurgeryStepSawFeature + name: Saw through bones + categories: [ HideSpawnMenu ] + components: + - type: SurgeryStep + tool: + - type: BoneSaw + add: + - type: BodyPartSawed + - type: Sprite + sprite: Objects/Specific/Medical/Surgery/saw.rsi + state: saw + - type: SurgeryStepEmoteEffect + +- type: entity + parent: SurgeryStepBase + id: SurgeryStepClampInternalBleeders + name: Clamp internal bleeders + categories: [ HideSpawnMenu ] + components: + - type: SurgeryStep + tool: + - type: Hemostat + add: + - type: InternalBleedersClamped + - type: Sprite + sprite: Objects/Specific/Medical/Surgery/scissors.rsi + state: hemostat + - type: SurgeryDamageChangeEffect + damage: + types: + Bloodloss: -5 + sleepModifier: 2 + +- type: entity + parent: SurgeryStepBase + id: SurgeryStepRemoveFeature + name: Amputate part + categories: [ HideSpawnMenu ] + components: + - type: SurgeryStep + tool: + - type: BoneSaw + remove: + # We remove these components to force people to go through all the steps again lol, otherwise you can just keep chopping. + - type: SkinRetracted + - type: BleedersClamped + - type: InternalBleedersClamped + - type: IncisionOpen + - type: Sprite + sprite: Objects/Specific/Medical/Surgery/saw.rsi + state: saw + - type: SurgeryRemovePartStep + - type: SurgeryStepEmoteEffect + +# Tend Wounds + +- type: entity + parent: SurgeryStepBase + id: SurgeryStepCarefulIncisionScalpel + name: Make a careful incision + categories: [ HideSpawnMenu ] + components: + - type: SurgeryStep + tool: + - type: Scalpel + add: + - type: IncisionOpen + - type: Sprite + sprite: Objects/Specific/Medical/Surgery/scalpel.rsi + state: scalpel + - type: SurgeryStepEmoteEffect + +- type: entity + parent: SurgeryStepBase + id: SurgeryStepRepairBruteTissue + name: Repair damaged tissue + categories: [ HideSpawnMenu ] + components: + - type: SurgeryStep + tool: + - type: Hemostat + - type: Sprite + sprite: Objects/Specific/Medical/Surgery/scissors.rsi + state: hemostat + - type: SurgeryTendWoundsEffect + damage: + groups: + Brute: -5 + - type: SurgeryRepeatableStep + +- type: entity + parent: SurgeryStepBase + id: SurgeryStepRepairBurnTissue + name: Repair burnt tissue + categories: [ HideSpawnMenu ] + components: + - type: SurgeryStep + tool: + - type: Hemostat + - type: Sprite + sprite: Objects/Specific/Medical/Surgery/scissors.rsi + state: hemostat + - type: SurgeryTendWoundsEffect + mainGroup: Burn + damage: + groups: + Burn: -5 + - type: SurgeryRepeatableStep + +- type: entity + parent: SurgeryStepBase + id: SurgeryStepSealTendWound + name: Seal the wound + categories: [ HideSpawnMenu ] + components: + - type: SurgeryStep + tool: + - type: Cautery + remove: + - type: IncisionOpen + - type: Sprite + sprite: Objects/Specific/Medical/Surgery/cautery.rsi + state: cautery + - type: SurgeryDamageChangeEffect + damage: + types: + Heat: -5 + sleepModifier: 2 + - type: SurgeryStepEmoteEffect + +# Cavity Implanting + +- type: entity + parent: SurgeryStepBase + id: SurgeryStepInsertItem + name: Insert item into cavity + categories: [ HideSpawnMenu ] + components: + - type: SurgeryStep + - type: Sprite + sprite: Objects/Specific/Medical/Surgery/manipulation.rsi + state: insertion + - type: SurgeryStepCavityEffect + action: Insert + - type: SurgeryStepEmoteEffect + +- type: entity + parent: SurgeryStepBase + id: SurgeryStepRemoveItem + name: Remove item from cavity + categories: [ HideSpawnMenu ] + components: + - type: SurgeryStep + - type: Sprite + sprite: Objects/Specific/Medical/Surgery/manipulation.rsi + state: insertion + - type: SurgeryStepCavityEffect + action: Remove + - type: SurgeryStepEmoteEffect + +# Organ Manipulation + +- type: entity + parent: SurgeryStepBase + id: SurgeryStepRemoveOrgan + name: Remove organ + categories: [ HideSpawnMenu ] + components: + - type: SurgeryStep + tool: + - type: Hemostat + - type: Sprite + sprite: Objects/Specific/Medical/Surgery/scissors.rsi + state: hemostat + - type: SurgeryRemoveOrganStep + - type: SurgeryStepEmoteEffect + +- type: entity + parent: SurgeryStepBase + id: SurgeryStepInsertOrgan + name: Add organ + categories: [ HideSpawnMenu ] + components: + - type: SurgeryStep + tool: + - type: Organ + - type: Sprite + sprite: Objects/Specific/Medical/Surgery/manipulation.rsi + state: insertion + - type: SurgeryAddOrganStep + - type: SurgeryStepEmoteEffect + +- type: entity + parent: SurgeryStepInsertOrgan + id: SurgeryStepInsertLungs + name: Add lungs + categories: [ HideSpawnMenu ] + components: + - type: SurgeryDamageChangeEffect + damage: + types: + Asphyxiation: -2147483648 # Literally the max 32 bit value, if your patient has gone higher than this, maybe it's time to restart the round. + sleepModifier: 1 + isConsumable: true + +- type: entity + parent: SurgeryStepInsertOrgan + id: SurgeryStepInsertLiver + name: Add liver + categories: [ HideSpawnMenu ] + components: + - type: SurgeryDamageChangeEffect + damage: + types: + Poison: -2147483648 # Literally the max 32 bit value, if your patient has gone higher than this, maybe it's time to restart the round. + sleepModifier: 1 + isConsumable: true + +- type: entity + parent: SurgeryStepInsertOrgan + id: SurgeryStepInsertEyes + name: Add eyes + categories: [ HideSpawnMenu ] + components: + - type: SurgerySpecialDamageChangeEffect + damageType: Eye + isConsumable: true + +- type: entity + parent: SurgeryStepInsertOrgan + id: SurgeryStepInsertHeart + name: Add heart + categories: [ HideSpawnMenu ] + components: + - type: SurgerySpecialDamageChangeEffect + damageType: Rot + isConsumable: true + +- type: entity + parent: SurgeryStepBase + id: SurgeryStepSealOrganWound + name: Seal wounds + categories: [ HideSpawnMenu ] + components: + - type: SurgeryStep + tool: + - type: Cautery + - type: SurgeryAffixOrganStep + - type: Sprite + sprite: Objects/Specific/Medical/Surgery/cautery.rsi + state: cautery + - type: SurgeryStepEmoteEffect + - type: SurgeryDamageChangeEffect + damage: + types: + Heat: -5 + sleepModifier: 2 + +# The lengths I go to just for a joke... I HATE HARDCODING AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +# Maybe I should modify species prototypes to include tails and ears properly... + +#- type: entity +# parent: SurgeryStepBase +# id: SurgeryStepAddFelinidEars +# name: Add cat ears +# categories: [ HideSpawnMenu ] +# components: +# - type: SurgeryStep +# tool: +# - type: Organ +# - type: SurgeryAddMarkingStep +# marking: FelinidEarsBasic +# markingCategory: HeadTop +# matchString: FelinidEars +# organ: +# - type: Ears +# accent: +# - type: OwOAccent +# - type: Sprite +# sprite: Objects/Specific/Medical/Surgery/manipulation.rsi +# state: insertion + +#- type: entity +# parent: SurgeryStepBase +# id: SurgeryStepAddFelinidTail +# name: Add cat tail +# categories: [ HideSpawnMenu ] +# components: +# - type: SurgeryStep +# tool: +# - type: Organ +# - type: SurgeryAddMarkingStep +# marking: FelinidTailBasic +# markingCategory: Tail +# matchString: FelinidTail +# organ: +# - type: Tail +# - type: Sprite +# sprite: Objects/Specific/Medical/Surgery/manipulation.rsi +# state: insertion + +#- type: entity +# parent: SurgeryStepBase +# id: SurgeryStepRemoveFelinidEars +# name: Remove cat ears +# categories: [ HideSpawnMenu ] +# components: +# - type: SurgeryStep +# tool: +# - type: Organ +# - type: SurgeryRemoveMarkingStep +# markingCategory: HeadTop +# matchString: FelinidEars +# - type: Sprite +# sprite: Objects/Specific/Medical/Surgery/manipulation.rsi +# state: insertion + +#- type: entity +# parent: SurgeryStepBase +# id: SurgeryStepRemoveFelinidTail +# name: Remove cat tail +# categories: [ HideSpawnMenu ] +# components: +# - type: SurgeryStep +# tool: +# - type: Organ +# - type: SurgeryRemoveMarkingStep +# markingCategory: Tail +# matchString: FelinidTail +# - type: Sprite +# sprite: Objects/Specific/Medical/Surgery/manipulation.rsi +# state: insertion diff --git a/Resources/Prototypes/Entities/Tiles/bananium.yml b/Resources/Prototypes/Entities/Tiles/bananium.yml index c9a6ec2844..9e8a46b2c3 100644 --- a/Resources/Prototypes/Entities/Tiles/bananium.yml +++ b/Resources/Prototypes/Entities/Tiles/bananium.yml @@ -47,6 +47,9 @@ paralyzeTime: 2 launchForwardsMultiplier: 1.5 - type: StepTrigger + triggerGroups: + types: + - SlipTile intersectRatio: 0.2 - type: Physics bodyType: Static diff --git a/Resources/Prototypes/Entities/Tiles/chasm.yml b/Resources/Prototypes/Entities/Tiles/chasm.yml index 23f3ad8395..85bc7b5ab3 100644 --- a/Resources/Prototypes/Entities/Tiles/chasm.yml +++ b/Resources/Prototypes/Entities/Tiles/chasm.yml @@ -14,6 +14,9 @@ blacklist: tags: - Catwalk + triggerGroups: + types: + - Chasm - type: Transform anchored: true - type: Clickable @@ -55,7 +58,7 @@ sprite: Tiles/Planet/Chasms/chromite_chasm.rsi - type: Icon sprite: Tiles/Planet/Chasms/chromite_chasm.rsi - + - type: entity parent: FloorChasmEntity id: FloorDesertChasm @@ -65,7 +68,7 @@ sprite: Tiles/Planet/Chasms/desert_chasm.rsi - type: Icon sprite: Tiles/Planet/Chasms/desert_chasm.rsi - + - type: entity parent: FloorChasmEntity id: FloorSnowChasm @@ -74,4 +77,4 @@ - type: Sprite sprite: Tiles/Planet/Chasms/snow_chasm.rsi - type: Icon - sprite: Tiles/Planet/Chasms/snow_chasm.rsi \ No newline at end of file + sprite: Tiles/Planet/Chasms/snow_chasm.rsi diff --git a/Resources/Prototypes/Entities/Tiles/lava.yml b/Resources/Prototypes/Entities/Tiles/lava.yml index 72641309b3..9d61304af9 100644 --- a/Resources/Prototypes/Entities/Tiles/lava.yml +++ b/Resources/Prototypes/Entities/Tiles/lava.yml @@ -13,6 +13,9 @@ blacklist: tags: - Catwalk + triggerGroups: + types: + - Lava - type: Lava fireStacks: 0.75 - type: Transform diff --git a/Resources/Prototypes/Entities/Tiles/liquid_plasma.yml b/Resources/Prototypes/Entities/Tiles/liquid_plasma.yml index 08f4653863..500286ead3 100644 --- a/Resources/Prototypes/Entities/Tiles/liquid_plasma.yml +++ b/Resources/Prototypes/Entities/Tiles/liquid_plasma.yml @@ -13,6 +13,9 @@ blacklist: tags: - Catwalk + triggerGroups: + types: + - Lava - type: Lava fireStacks: 0.75 - type: Transform diff --git a/Resources/Prototypes/Entities/Tiles/shadow_basalt.yml b/Resources/Prototypes/Entities/Tiles/shadow_basalt.yml index d93351c7e2..974a0ac3b7 100644 --- a/Resources/Prototypes/Entities/Tiles/shadow_basalt.yml +++ b/Resources/Prototypes/Entities/Tiles/shadow_basalt.yml @@ -1,7 +1,7 @@ - type: entity id: ShadowBasaltOne name: shadowstone - description: Cold rock + description: Glowing cracks in reality. It's probably fine. placement: mode: SnapgridCenter components: diff --git a/Resources/Prototypes/Entities/categories.yml b/Resources/Prototypes/Entities/categories.yml new file mode 100644 index 0000000000..2fb56818f9 --- /dev/null +++ b/Resources/Prototypes/Entities/categories.yml @@ -0,0 +1,4 @@ +- type: entityCategory + id: Actions + name: entity-category-name-actions + hideSpawnMenu: true diff --git a/Resources/Prototypes/EntityLists/Tools/surgery.yml b/Resources/Prototypes/EntityLists/Tools/surgery.yml index 20f689d272..072a754c50 100644 --- a/Resources/Prototypes/EntityLists/Tools/surgery.yml +++ b/Resources/Prototypes/EntityLists/Tools/surgery.yml @@ -1,10 +1,14 @@ - type: entityList id: surgerytools entities: + - Scalpel - Cautery - Drill - ScalpelLaser - Retractor - Hemostat - - SawAdvanced -# - Drapes + - SawElectric + - Bonesetter + - BoneGel + - Saw +# - Drapes \ No newline at end of file diff --git a/Resources/Prototypes/Flavors/flavors.yml b/Resources/Prototypes/Flavors/flavors.yml index 9f0f6ad678..26190d8791 100644 --- a/Resources/Prototypes/Flavors/flavors.yml +++ b/Resources/Prototypes/Flavors/flavors.yml @@ -589,6 +589,36 @@ flavorType: Complex description: flavor-complex-irish-car-bomb +- type: flavor + id: vodkaredbool + flavorType: Complex + description: flavor-complex-vodka-red-bool + +- type: flavor + id: xenobasher + flavorType: Complex + description: flavor-complex-xeno-basher + +- type: flavor + id: irishbool + flavorType: Complex + description: flavor-complex-irish-bool + +- type: flavor + id: budgetinsulsdrink + flavorType: Complex + description: flavor-complex-budget-insuls-drink + +- type: flavor + id: watermelonwakeup + flavorType: Complex + description: flavor-complex-watermelon-wakeup + +- type: flavor + id: rubberneck + flavorType: Complex + description: flavor-complex-rubberneck + - type: flavor id: blackrussian flavorType: Complex @@ -1053,3 +1083,18 @@ id: violets flavorType: Complex description: flavor-complex-violets + +- type: flavor + id: pyrotton + flavorType: Complex + description: flavor-complex-pyrotton + +- type: flavor + id: mothballs + flavorType: Complex + description: flavor-complex-mothballs + +- type: flavor + id: paintthinner + flavorType: Complex + description: flavor-complex-paint-thinner \ No newline at end of file diff --git a/Resources/Prototypes/GameRules/cargo_gifts.yml b/Resources/Prototypes/GameRules/cargo_gifts.yml index 799805272d..3787f4e603 100644 --- a/Resources/Prototypes/GameRules/cargo_gifts.yml +++ b/Resources/Prototypes/GameRules/cargo_gifts.yml @@ -4,7 +4,7 @@ noSpawn: true components: - type: StationEvent - weight: 10 + weight: 5 startDelay: 10 duration: 120 earliestStart: 20 @@ -24,11 +24,10 @@ noSpawn: true components: - type: StationEvent - weight: 6 + weight: 2 startDelay: 10 duration: 240 - minimumPlayers: 50 - earliestStart: 40 + earliestStart: 20 - type: CargoGiftsRule description: cargo-gift-pizza-large sender: cargo-gift-default-sender @@ -44,7 +43,7 @@ noSpawn: true components: - type: StationEvent - weight: 4 + weight: 5 startDelay: 10 duration: 240 earliestStart: 30 @@ -90,7 +89,7 @@ noSpawn: true components: - type: StationEvent - weight: 8 + weight: 6 startDelay: 10 duration: 120 minimumPlayers: 30 @@ -132,7 +131,7 @@ noSpawn: true components: - type: StationEvent - weight: 6 + weight: 4 startDelay: 10 duration: 120 earliestStart: 10 @@ -154,7 +153,7 @@ noSpawn: true components: - type: StationEvent - weight: 3 + weight: 4 startDelay: 10 duration: 120 earliestStart: 20 @@ -174,7 +173,7 @@ noSpawn: true components: - type: StationEvent - weight: 2 + weight: 3 startDelay: 10 duration: 120 earliestStart: 20 diff --git a/Resources/Prototypes/GameRules/events.yml b/Resources/Prototypes/GameRules/events.yml index ca88511744..4923e399dd 100644 --- a/Resources/Prototypes/GameRules/events.yml +++ b/Resources/Prototypes/GameRules/events.yml @@ -4,9 +4,8 @@ noSpawn: true components: - type: StationEvent - weight: 12 + weight: 8 startDelay: 30 - reoccurrenceDelay: 8 duration: 35 - type: AnomalySpawnRule @@ -16,9 +15,8 @@ noSpawn: true components: - type: StationEvent - weight: 5 + weight: 8 startDelay: 30 - reoccurrenceDelay: 5 duration: 35 - type: BluespaceArtifactRule @@ -28,10 +26,9 @@ noSpawn: true components: - type: StationEvent - weight: 1 - maxOccurrences: 4 # Very annoying, makes containers unusable + weight: 2 + reoccurrenceDelay: 5 earliestStart: 1 - reoccurrenceDelay: 15 duration: 1 - type: BluespaceLockerRule @@ -41,10 +38,9 @@ noSpawn: true components: - type: StationEvent - weight: 10 + weight: 7 duration: 1 - minimumPlayers: 7 - reoccurrenceDelay: 5 + minimumPlayers: 15 - type: BreakerFlipRule - type: entity @@ -55,9 +51,8 @@ - type: StationEvent startAnnouncement: true minimumPlayers: 25 - weight: 5 + weight: 3 duration: 1 - reoccurrenceDelay: 5 - type: BureaucraticErrorRule - type: entity @@ -70,7 +65,6 @@ minimumPlayers: 15 weight: 5 duration: 1 - reoccurrenceDelay: 5 - type: ClericalErrorRule - type: entity @@ -79,10 +73,9 @@ noSpawn: true components: - type: StationEvent - weight: 10 + weight: 5 duration: 1 - minimumPlayers: 15 - reoccurrenceDelay: 25 + minimumPlayers: 10 - type: RandomEntityStorageSpawnRule prototype: MobSkeletonCloset @@ -92,10 +85,10 @@ noSpawn: true components: - type: StationEvent - weight: 2 + weight: 6.5 duration: 1 - earliestStart: 45 - reoccurrenceDelay: 60 + earliestStart: 40 + reoccurrenceDelay: 20 minimumPlayers: 20 - type: RandomSpawnRule prototype: SpawnPointGhostDragon @@ -106,14 +99,13 @@ noSpawn: true components: - type: StationEvent - weight: 3 + weight: 6 duration: 1 - earliestStart: 45 - reoccurrenceDelay: 45 - minimumPlayers: 20 + earliestStart: 30 + reoccurrenceDelay: 20 + minimumPlayers: 30 - type: NinjaSpawnRule -# TODO there's already a glimmer revenant rule. One of them might be broken. - type: entity parent: BaseGameRule id: RevenantSpawn @@ -127,16 +119,16 @@ - type: RandomSpawnRule prototype: MobRevenant -- type: entity - id: FalseAlarm - parent: BaseGameRule - noSpawn: true - components: - - type: StationEvent - weight: 10 - duration: 1 - reoccurrenceDelay: 4 # Please no 10 false alarms in a row. - - type: FalseAlarmRule +# disabled until event is rewritten to be more interesting +#- type: entity +# id: FalseAlarm +# parent: BaseGameRule +# noSpawn: true +# components: +# - type: StationEvent +# weight: 15 +# duration: 1 +# - type: FalseAlarmRule - type: entity id: GasLeak @@ -146,10 +138,7 @@ - type: StationEvent startAnnouncement: true endAnnouncement: true - earliestStart: 10 - reoccurrenceDelay: 7 - minimumPlayers: 5 - weight: 10 + weight: 8 startDelay: 20 - type: GasLeakRule @@ -160,9 +149,8 @@ components: - type: StationEvent earliestStart: 15 - reoccurrenceDelay: 10 minimumPlayers: 15 - weight: 5 + weight: 7 startDelay: 50 duration: 240 - type: KudzuGrowthRule @@ -174,9 +162,8 @@ components: - type: StationEvent earliestStart: 30 - reoccurrenceDelay: 5 weight: 7.5 - minimumPlayers: 7 #Enough to hopefully have at least one engineering guy + minimumPlayers: 10 #Enough to hopefully have at least one engineering guy startAnnouncement: true endAnnouncement: true duration: null #ending is handled by MeteorSwarmRule @@ -192,8 +179,7 @@ startAnnouncement: true startDelay: 10 earliestStart: 15 - reoccurrenceDelay: 3 - weight: 5 + weight: 6 duration: 50 - type: VentCrittersRule entries: @@ -203,6 +189,11 @@ prob: 0.02 - id: MobMouse2 prob: 0.02 + - id: MobMouseCancer + prob: 0.001 + specialEntries: + - id: SpawnPointGhostRatKing + prob: 0.001 - type: entity id: CockroachMigration @@ -212,9 +203,8 @@ - type: StationEvent startAnnouncement: true startDelay: 10 - weight: 5 + weight: 6 duration: 50 - reoccurrenceDelay: 15 # Cockroaches en masse are utmost annoying to deal with. - type: VentCrittersRule entries: - id: MobCockroach @@ -222,41 +212,18 @@ - id: MobMothroach prob: 0.008 -# TODO this is the same as mouse migration, but with different announcer. -- type: entity - id: VentCritters - parent: BaseGameRule - noSpawn: true - components: - - type: StationEvent - startAnnouncement: true - startDelay: 10 - earliestStart: 15 - reoccurrenceDelay: 3 - weight: 5 - duration: 60 - - type: VentCrittersRule - entries: - - id: MobMouse - prob: 0.02 - - id: MobMouse1 - prob: 0.02 - - id: MobMouse2 - prob: 0.02 - - type: entity id: PowerGridCheck parent: BaseGameRule noSpawn: true components: - type: StationEvent - weight: 10 + weight: 5 startAnnouncement: true endAnnouncement: true startDelay: 24 duration: 60 maxDuration: 120 - reoccurrenceDelay: 2 # Gives a chance for multiple checks in a row, but not in parallel - type: PowerGridCheckRule - type: entity @@ -265,10 +232,9 @@ noSpawn: true components: - type: StationEvent - weight: 5 + weight: 6 duration: 1 - reoccurrenceDelay: 10 - maxOccurrences: 3 # Annoying and rarely if ever interesting + maxOccurrences: 1 # this event has diminishing returns on interesting-ness, so we cap it - type: RandomSentienceRule - type: entity @@ -277,12 +243,11 @@ noSpawn: true components: - type: StationEvent - weight: 10 + weight: 8 startAnnouncement: true endAnnouncement: true duration: 120 maxDuration: 240 - reoccurrenceDelay: 5 - type: SolarFlareRule onlyJamHeadsets: true affectedChannels: @@ -299,19 +264,6 @@ lightBreakChancePerSecond: 0.0003 doorToggleChancePerSecond: 0.001 -# - type: entity # DeltaV - replaced terminator with paradox anomaly in midroundantag rule -# parent: BaseGameRule -# id: TerminatorSpawn -# noSpawn: true -# components: -# - type: StationEvent -# weight: 8 -# duration: 1 -# earliestStart: 30 -# minimumPlayers: 20 -# - type: RandomSpawnRule -# prototype: SpawnPointGhostTerminator - - type: entity id: VentClog parent: BaseGameRule @@ -320,7 +272,6 @@ - type: StationEvent startAnnouncement: true earliestStart: 15 - reoccurrenceDelay: 5 minimumPlayers: 15 weight: 5 startDelay: 50 @@ -336,7 +287,6 @@ startAnnouncement: true startDelay: 10 earliestStart: 20 - reoccurrenceDelay: 12 minimumPlayers: 15 weight: 5 duration: 60 @@ -358,7 +308,6 @@ startAnnouncement: true startDelay: 10 earliestStart: 20 - reoccurrenceDelay: 15 minimumPlayers: 15 weight: 5 duration: 60 @@ -367,67 +316,22 @@ - id: MobGiantSpiderAngry prob: 0.05 -# Weaker versions of the above -- type: entity - id: SlimesSpawnWeak - parent: SlimesSpawn - noSpawn: true - components: - - type: StationEvent - minimumPlayers: 1 - - type: VentCrittersRule - entries: - - id: MobAdultSlimesBlueAngry - prob: 0.005 - - id: MobAdultSlimesGreenAngry - prob: 0.005 - - id: MobAdultSlimesYellowAngry - prob: 0.005 - -- type: entity - id: SpiderSpawnWeak - parent: SpiderSpawn - noSpawn: true - components: - - type: StationEvent - minimumPlayers: 1 - - type: VentCrittersRule - entries: - - id: MobGiantSpiderAngry - prob: 0.01 - -# - type: entity # DeltaV - Prevent normal spawning of MobClownSpider -# id: SpiderClownSpawn -# parent: BaseGameRule -# noSpawn: true -# components: -# - type: StationEvent -# startAnnouncement: true -# startDelay: 10 -# earliestStart: 20 -# minimumPlayers: 15 -# weight: 1 -# duration: 60 -# - type: VentCrittersRule -# entries: -# - id: MobClownSpider -# prob: 0.05 - - type: entity - id: OneirophageSpawn + id: SpiderClownSpawn parent: BaseGameRule noSpawn: true components: - type: StationEvent - id: VentCritters - earliestStart: 15 - minimumPlayers: 15 - weight: 4 + startAnnouncement: false + startDelay: 10 + earliestStart: 20 + minimumPlayers: 20 + weight: 1.5 duration: 60 - type: VentCrittersRule entries: - - id: MobGiantSpiderVampireAngry - prob: 0.01 + - id: MobClownSpider + prob: 0.05 - type: entity id: ZombieOutbreak @@ -435,17 +339,33 @@ noSpawn: true components: - type: StationEvent - earliestStart: 60 - reoccurrenceDelay: 60 - minimumPlayers: 15 + earliestStart: 50 + minimumPlayers: 30 weight: 2 duration: 1 - type: ZombieRule - minStartDelay: 0 #let them know immediately - maxStartDelay: 10 - maxInitialInfected: 2 - minInitialInfectedGrace: 300 - maxInitialInfectedGrace: 450 + - type: AntagSelection + definitions: + - prefRoles: [ InitialInfected ] + max: 3 + playerRatio: 10 + blacklist: + components: + - ZombieImmune + - InitialInfectedExempt + briefing: + text: zombie-patientzero-role-greeting + color: Plum + sound: "/Audio/Ambience/Antag/zombie_start.ogg" + components: + - type: PendingZombie #less time to prepare than normal + minInitialInfectedGrace: 300 + maxInitialInfectedGrace: 450 + - type: ZombifyOnDeath + - type: IncurableZombie + mindComponents: + - type: InitialInfectedRole + prototype: InitialInfected - type: entity id: LoneOpsSpawn @@ -453,12 +373,56 @@ noSpawn: true components: - type: StationEvent - earliestStart: 60 - weight: 3 - minimumPlayers: 15 - reoccurrenceDelay: 45 + earliestStart: 35 + weight: 5.5 + minimumPlayers: 20 duration: 1 - - type: LoneOpsSpawnRule + - type: LoadMapRule + preloadedGrid: ShuttleStriker + - type: NukeopsRule + roundEndBehavior: Nothing + - type: AntagSelection + definitions: + - spawnerPrototype: SpawnPointLoneNukeOperative + min: 1 + max: 1 + pickPlayer: false + startingGear: SyndicateLoneOperativeGearFull + components: + - type: NukeOperative + - type: RandomMetadata + nameSegments: + - SyndicateNamesPrefix + - SyndicateNamesNormal + - type: NpcFactionMember + factions: + - Syndicate + mindComponents: + - type: NukeopsRole + prototype: Nukeops + +- type: entity + id: SleeperAgentsRule + parent: BaseGameRule + noSpawn: true + components: + - type: StationEvent + earliestStart: 25 + weight: 8 + minimumPlayers: 15 + reoccurrenceDelay: 30 + startAnnouncement: false + - type: AlertLevelInterceptionRule + - type: TraitorRule + - type: AntagSelection + definitions: + - prefRoles: [ Traitor ] + min: 1 + max: 2 + playerRatio: 10 + mindComponents: + - type: TraitorRole + prototype: Traitor - type: entity id: MassHallucinations @@ -466,9 +430,10 @@ noSpawn: true components: - type: StationEvent - weight: 10 + weight: 7 duration: 150 maxDuration: 300 + reoccurrenceDelay: 30 - type: MassHallucinationsRule minTimeBetweenIncidents: 0.1 maxTimeBetweenIncidents: 300 @@ -476,18 +441,18 @@ sounds: collection: Paracusia -#- type: entity # DeltaV - Why does this exist?? -# id: ImmovableRodSpawn -# parent: BaseGameRule -# noSpawn: true -# components: -# - type: StationEvent -# startAnnouncement: true -# weight: 5 -# duration: 1 -# earliestStart: 45 -# minimumPlayers: 20 -# - type: ImmovableRodRule +- type: entity + id: ImmovableRodSpawn + parent: BaseGameRule + noSpawn: true + components: + - type: StationEvent + startAnnouncement: false + weight: 2 + duration: 1 + earliestStart: 45 + minimumPlayers: 20 + - type: ImmovableRodRule - type: entity noSpawn: true @@ -495,7 +460,7 @@ id: IonStorm components: - type: StationEvent - weight: 10 + weight: 8 reoccurrenceDelay: 20 duration: 1 - type: IonStormRule @@ -507,8 +472,7 @@ components: - type: StationEvent earliestStart: 0 - reoccurrenceDelay: 5 minimumPlayers: 20 + maxOccurrences: 1 # this event has diminishing returns on interesting-ness, so we cap it weight: 5 - type: MobReplacementRule - numberToReplace: 1 diff --git a/Resources/Prototypes/GameRules/midround.yml b/Resources/Prototypes/GameRules/midround.yml index 37fc4b44cd..db1a76adc0 100644 --- a/Resources/Prototypes/GameRules/midround.yml +++ b/Resources/Prototypes/GameRules/midround.yml @@ -12,6 +12,7 @@ - DoorjackObjective - SpiderChargeObjective - TerrorObjective + - MassArrestObjective - NinjaSurviveObjective - type: NinjaRule threats: NinjaThreats @@ -34,14 +35,32 @@ id: Thief components: - type: ThiefRule + - type: AntagSelection + definitions: + - prefRoles: [ Thief ] + maxRange: + min: 1 + max: 3 + playerRatio: 1 + lateJoinAdditional: true + allowNonHumans: true + multiAntagSetting: All + startingGear: ThiefGear + components: + - type: Pacified + mindComponents: + - type: ThiefRole + prototype: Thief + briefing: + sound: "/Audio/Misc/thief_greeting.ogg" -- type: entity - noSpawn: true - parent: BaseGameRule - id: Exterminator - components: - - type: GenericAntagRule - agentName: terminator-round-end-agent-name - objectives: - - TerminateObjective - - ShutDownObjective +#- type: entity +# noSpawn: true +# parent: BaseGameRule +# id: Exterminator +# components: +# - type: GenericAntagRule +# agentName: terminator-round-end-agent-name +# objectives: +# - TerminateObjective +# - ShutDownObjective diff --git a/Resources/Prototypes/GameRules/roundstart.yml b/Resources/Prototypes/GameRules/roundstart.yml index 0af55a7f9d..39bea004d0 100644 --- a/Resources/Prototypes/GameRules/roundstart.yml +++ b/Resources/Prototypes/GameRules/roundstart.yml @@ -70,31 +70,114 @@ components: - type: GameRule minPlayers: 35 + - type: RandomMetadata #this generates the random operation name cuz it's cool. + nameSegments: + - operationPrefix + - operationSuffix - type: NukeopsRule - faction: Syndicate - -- type: entity - id: Pirates - parent: BaseGameRule - noSpawn: true - components: - - type: PiratesRule + - type: LoadMapRule + gameMap: NukieOutpost + - type: AntagSelection + selectionTime: PrePlayerSpawn + definitions: + - prefRoles: [ NukeopsCommander ] + fallbackRoles: [ Nukeops, NukeopsMedic ] + max: 1 + playerRatio: 10 + startingGear: SyndicateCommanderGearFull + components: + - type: NukeOperative + - type: RandomMetadata + nameSegments: + - nukeops-role-commander + - SyndicateNamesElite + - type: NpcFactionMember + factions: + - Syndicate + mindComponents: + - type: NukeopsRole + prototype: NukeopsCommander + - prefRoles: [ NukeopsMedic ] + fallbackRoles: [ Nukeops, NukeopsCommander ] + max: 1 + playerRatio: 10 + startingGear: SyndicateOperativeMedicFull + components: + - type: NukeOperative + - type: RandomMetadata + nameSegments: + - nukeops-role-agent + - SyndicateNamesNormal + - type: NpcFactionMember + factions: + - Syndicate + mindComponents: + - type: NukeopsRole + prototype: NukeopsMedic + - prefRoles: [ Nukeops ] + fallbackRoles: [ NukeopsCommander, NukeopsMedic ] + min: 0 + max: 3 + playerRatio: 10 + startingGear: SyndicateOperativeGearFull + components: + - type: NukeOperative + - type: RandomMetadata + nameSegments: + - nukeops-role-operator + - SyndicateNamesNormal + - type: NpcFactionMember + factions: + - Syndicate + mindComponents: + - type: NukeopsRole + prototype: Nukeops - type: entity id: Traitor parent: BaseGameRule noSpawn: true components: + - type: GameRule + minPlayers: 5 + delay: + min: 240 + max: 420 - type: TraitorRule + - type: AntagSelection + definitions: + - prefRoles: [ Traitor ] + max: 8 + playerRatio: 10 + lateJoinAdditional: true + mindComponents: + - type: TraitorRole + prototype: Traitor - type: entity id: Revolutionary parent: BaseGameRule noSpawn: true components: + - type: GameRule + minPlayers: 15 - type: RevolutionaryRule - maxHeadRevs: 2 # DeltaV - playersPerHeadRev: 30 # DeltaV - need highpop readied up for multiple headrevs + - type: AntagSelection + definitions: + - prefRoles: [ HeadRev ] + max: 2 + playerRatio: 20 # WD + briefing: + text: head-rev-role-greeting + color: CornflowerBlue + sound: "/Audio/Ambience/Antag/headrev_start.ogg" + startingGear: HeadRevGear + components: + - type: Revolutionary + - type: HeadRevolutionary + mindComponents: + - type: RevolutionaryRole + prototype: HeadRev - type: entity id: Sandbox @@ -115,7 +198,32 @@ parent: BaseGameRule noSpawn: true components: + - type: GameRule + minPlayers: 20 + delay: + min: 600 + max: 900 - type: ZombieRule + - type: AntagSelection + definitions: + - prefRoles: [ InitialInfected ] + max: 6 + playerRatio: 10 + blacklist: + components: + - ZombieImmune + - InitialInfectedExempt + briefing: + text: zombie-patientzero-role-greeting + color: Plum + sound: "/Audio/Ambience/Antag/zombie_start.ogg" + components: + - type: PendingZombie + - type: ZombifyOnDeath + - type: IncurableZombie + mindComponents: + - type: InitialInfectedRole + prototype: InitialInfected # event schedulers - type: entity @@ -142,6 +250,33 @@ startingChaosRatio: 0.025 # Starts as slow as survival, but quickly ramps up shiftLengthModifier: 2.5 +- type: entity + id: IrregularStationEventScheduler + parent: BaseGameRule + noSpawn: true + components: + - type: OscillatingStationEventScheduler + minChaos: 0.8 + maxChaos: 14 + startingSlope: 0.2 + downwardsLimit: -0.35 + upwardsLimit: 0.4 + +# More likely to go down than up, so calmness prevails +- type: entity + id: IrregularExtendedStationEventScheduler + parent: BaseGameRule + noSpawn: true + components: + - type: OscillatingStationEventScheduler + minChaos: 0.8 + maxChaos: 8 + startingSlope: -1 + downwardsLimit: -0.4 + upwardsLimit: 0.3 + downwardsBias: -1.1 + upwardsBias: 0.9 + # variation passes - type: entity id: BasicRoundstartVariation @@ -154,7 +289,6 @@ - id: BasicTrashVariationPass - id: SolidWallRustingVariationPass - id: ReinforcedWallRustingVariationPass - - id: CutWireVariationPass - id: BasicPuddleMessVariationPass prob: 0.99 orGroup: puddleMess diff --git a/Resources/Prototypes/GameRules/unknown_shuttles.yml b/Resources/Prototypes/GameRules/unknown_shuttles.yml new file mode 100644 index 0000000000..f44bbdcaaa --- /dev/null +++ b/Resources/Prototypes/GameRules/unknown_shuttles.yml @@ -0,0 +1,64 @@ +- type: entity + id: UnknownShuttleCargoLost + parent: BaseGameRule + noSpawn: true + components: + - type: StationEvent + startAnnouncement: false + weight: 5 + reoccurrenceDelay: 30 + duration: 1 + - type: LoadMapRule + preloadedGrid: ShuttleCargoLost + +- type: entity + id: UnknownShuttleTravelingCuisine + parent: BaseGameRule + noSpawn: true + components: + - type: StationEvent + startAnnouncement: false + weight: 5 + reoccurrenceDelay: 30 + duration: 1 + - type: LoadMapRule + preloadedGrid: TravelingCuisine + +- type: entity + id: UnknownShuttleDisasterEvacPod + parent: BaseGameRule + noSpawn: true + components: + - type: StationEvent + startAnnouncement: false + weight: 5 + reoccurrenceDelay: 30 + duration: 1 + - type: LoadMapRule + preloadedGrid: DisasterEvacPod + +- type: entity + id: UnknownShuttleHonki + parent: BaseGameRule + noSpawn: true + components: + - type: StationEvent + startAnnouncement: false + weight: 2 + reoccurrenceDelay: 30 + duration: 1 + - type: LoadMapRule + preloadedGrid: Honki + +- type: entity + id: UnknownShuttleSyndieEvacPod + parent: BaseGameRule + noSpawn: true + components: + - type: StationEvent + startAnnouncement: false + weight: 2 + reoccurrenceDelay: 30 + duration: 1 + - type: LoadMapRule + preloadedGrid: SyndieEvacPod diff --git a/Resources/Prototypes/GameRules/variation.yml b/Resources/Prototypes/GameRules/variation.yml index 7424fc2854..2884d5f9d6 100644 --- a/Resources/Prototypes/GameRules/variation.yml +++ b/Resources/Prototypes/GameRules/variation.yml @@ -24,8 +24,8 @@ components: - type: WallReplaceVariationPass - type: EntityReplaceVariationPass - entitiesPerReplacementAverage: 10 - entitiesPerReplacementStdDev: 2 + entitiesPerReplacementAverage: 50 + entitiesPerReplacementStdDev: 10 replacements: - id: WallSolidRust @@ -36,8 +36,8 @@ components: - type: ReinforcedWallReplaceVariationPass - type: EntityReplaceVariationPass - entitiesPerReplacementAverage: 12 - entitiesPerReplacementStdDev: 2 + entitiesPerReplacementAverage: 50 + entitiesPerReplacementStdDev: 10 replacements: - id: WallReinforcedRust diff --git a/Resources/Prototypes/GhostRoleRaffles/deciders.yml b/Resources/Prototypes/GhostRoleRaffles/deciders.yml new file mode 100644 index 0000000000..b23464cf70 --- /dev/null +++ b/Resources/Prototypes/GhostRoleRaffles/deciders.yml @@ -0,0 +1,3 @@ +- type: ghostRoleRaffleDecider + id: default + decider: !type:RngGhostRoleRaffleDecider {} diff --git a/Resources/Prototypes/GhostRoleRaffles/settings.yml b/Resources/Prototypes/GhostRoleRaffles/settings.yml new file mode 100644 index 0000000000..7ed9326ee0 --- /dev/null +++ b/Resources/Prototypes/GhostRoleRaffles/settings.yml @@ -0,0 +1,15 @@ +# for important antag roles (nukie reinforcements, ninja, etc.) +- type: ghostRoleRaffleSettings + id: default + settings: + initialDuration: 30 + joinExtendsDurationBy: 10 + maxDuration: 90 + +# for roles that don't matter too much or are available plentifully (e.g. space carp) +- type: ghostRoleRaffleSettings + id: short + settings: + initialDuration: 10 + joinExtendsDurationBy: 5 + maxDuration: 30 diff --git a/Resources/Prototypes/Guidebook/medical.yml b/Resources/Prototypes/Guidebook/medical.yml index 95a4f1ca75..8a6a02a69c 100644 --- a/Resources/Prototypes/Guidebook/medical.yml +++ b/Resources/Prototypes/Guidebook/medical.yml @@ -7,6 +7,7 @@ - Chemist - Cloning - Cryogenics + - Surgery - type: guideEntry id: Medical Doctor @@ -48,3 +49,31 @@ id: AdvancedBrute name: guide-entry-brute text: "/ServerInfo/Guidebook/Medical/AdvancedBrute.xml" + +- type: guideEntry + id: Surgery + name: guide-entry-surgery + text: "/ServerInfo/Guidebook/Medical/Surgery.xml" + children: + - Part Manipulation + - Organ Manipulation + - Utility Surgeries + +- type: guideEntry + id: Part Manipulation + name: guide-entry-partmanipulation + text: "/ServerInfo/Guidebook/Medical/PartManipulation.xml" + filterEnabled: true + +- type: guideEntry + id: Organ Manipulation + name: guide-entry-organmanipulation + text: "/ServerInfo/Guidebook/Medical/OrganManipulation.xml" + filterEnabled: true + +- type: guideEntry + id: Utility Surgeries + name: guide-entry-utilitysurgeries + text: "/ServerInfo/Guidebook/Medical/UtilitySurgeries.xml" + filterEnabled: true + diff --git a/Resources/Prototypes/Guidebook/science.yml b/Resources/Prototypes/Guidebook/science.yml index a21be1678c..56363a6a92 100644 --- a/Resources/Prototypes/Guidebook/science.yml +++ b/Resources/Prototypes/Guidebook/science.yml @@ -7,9 +7,10 @@ - AnomalousResearch - Xenoarchaeology - Robotics - - Psionics # Nyanotrasen - Psionics guidebook - # - AltarsGolemancy # When it's added # Nyanotrasen - Golemancy guidebook - - ReverseEngineering # Nyanotrasen - Reverse Engineering guidebook + - MachineUpgrading + - AltarsGolemancy + - ReverseEngineering + - GlimmerCreatures - type: guideEntry id: Technologies @@ -41,7 +42,6 @@ text: "/ServerInfo/Guidebook/Science/Xenoarchaeology.xml" children: - ArtifactReports - - TraversalDistorter - type: guideEntry id: Robotics @@ -60,7 +60,17 @@ name: guide-entry-traversal-distorter text: "/ServerInfo/Guidebook/Science/TraversalDistorter.xml" +- type: guideEntry + id: MachineUpgrading + name: guide-entry-machine-upgrading + text: "/ServerInfo/Guidebook/Science/MachineUpgrading.xml" + - type: guideEntry id: Cyborgs name: guide-entry-cyborgs text: "/ServerInfo/Guidebook/Science/Cyborgs.xml" + +- type: guideEntry + id: GlimmerCreatures + name: guide-entry-glimmer-creatures + text: /ServerInfo/Guidebook/DeltaV/Epistemics/GlimmerCreatures.xml diff --git a/Resources/Prototypes/Guidebook/shiftandcrew.yml b/Resources/Prototypes/Guidebook/shiftandcrew.yml index 3c4618902e..66f9e7316d 100644 --- a/Resources/Prototypes/Guidebook/shiftandcrew.yml +++ b/Resources/Prototypes/Guidebook/shiftandcrew.yml @@ -41,3 +41,4 @@ id: Food Recipes name: guide-entry-foodrecipes text: "/ServerInfo/Guidebook/Service/FoodRecipes.xml" + filterEnabled: true diff --git a/Resources/Prototypes/Guidebook/species.yml b/Resources/Prototypes/Guidebook/species.yml index 5b9efd0366..f7b77b7ec6 100644 --- a/Resources/Prototypes/Guidebook/species.yml +++ b/Resources/Prototypes/Guidebook/species.yml @@ -10,6 +10,9 @@ - Moth - Reptilian - SlimePerson + - IPCs + - Harpy + - Shadowkin - type: guideEntry id: Arachnid @@ -45,3 +48,18 @@ id: SlimePerson name: species-name-slime text: "/ServerInfo/Guidebook/Mobs/SlimePerson.xml" + +- type: guideEntry + id: IPCs + name: species-name-ipc + text: "/ServerInfo/Guidebook/Mobs/IPCs.xml" + +- type: guideEntry + id: Harpy + name: species-name-harpy + text: "/ServerInfo/Guidebook/Mobs/Harpy.xml" + +- type: guideEntry + id: Shadowkin + name: species-name-shadowkin + text: "/ServerInfo/Guidebook/Mobs/Shadowkin.xml" diff --git a/Resources/Prototypes/Hydroponics/mutations.yml b/Resources/Prototypes/Hydroponics/mutations.yml index 17617f5ee3..6108278a4a 100644 --- a/Resources/Prototypes/Hydroponics/mutations.yml +++ b/Resources/Prototypes/Hydroponics/mutations.yml @@ -6,7 +6,6 @@ reagents: - Omnizine - Nocturine - - Barozine - Lexorin - Honk - BuzzochloricBees diff --git a/Resources/Prototypes/Hydroponics/seeds.yml b/Resources/Prototypes/Hydroponics/seeds.yml index 030c89c8cf..e134d24c26 100644 --- a/Resources/Prototypes/Hydroponics/seeds.yml +++ b/Resources/Prototypes/Hydroponics/seeds.yml @@ -18,11 +18,11 @@ Nutriment: Min: 1 Max: 20 - PotencyDivisor: 20 + potencyDivisor: 20 Flour: Min: 5 Max: 20 - PotencyDivisor: 20 + potencyDivisor: 20 - type: seed id: oat @@ -44,11 +44,11 @@ Nutriment: Min: 1 Max: 20 - PotencyDivisor: 20 + potencyDivisor: 20 Oats: Min: 5 Max: 20 - PotencyDivisor: 20 + potencyDivisor: 20 - type: seed id: banana @@ -73,11 +73,11 @@ Vitamin: Min: 1 Max: 4 - PotencyDivisor: 25 + potencyDivisor: 25 Nutriment: Min: 1 Max: 2 - PotencyDivisor: 50 + potencyDivisor: 50 - type: seed id: mimana @@ -100,11 +100,11 @@ MuteToxin: Min: 1 Max: 5 - PotencyDivisor: 20 + potencyDivisor: 20 Nutriment: Min: 1 Max: 2 - PotencyDivisor: 50 + potencyDivisor: 50 - type: seed id: carrots @@ -126,15 +126,15 @@ JuiceCarrot: Min: 1 Max: 5 - PotencyDivisor: 20 + potencyDivisor: 20 Oculine: Min: 2 Max: 6 - PotencyDivisor: 20 + potencyDivisor: 20 Vitamin: Min: 1 Max: 4 - PotencyDivisor: 25 + potencyDivisor: 25 - type: seed id: laughinPea @@ -159,15 +159,15 @@ Nutriment: Min: 1 Max: 3 - PotencyDivisor: 7 + potencyDivisor: 7 Sugar: Min: 1 Max: 10 - PotencyDivisor: 5 + potencyDivisor: 5 Laughter: Min: 1 Max: 10 - PotencyDivisor: 5 + potencyDivisor: 5 - type: seed id: lemon @@ -191,11 +191,11 @@ Nutriment: Min: 1 Max: 5 - PotencyDivisor: 20 + potencyDivisor: 20 Vitamin: Min: 1 Max: 4 - PotencyDivisor: 25 + potencyDivisor: 25 - type: seed id: lemoon @@ -217,11 +217,11 @@ Vitamin: Min: 1 Max: 4 - PotencyDivisor: 25 + potencyDivisor: 25 Milk: Min: 8 Max: 20 - PotencyDivisor: 5 + potencyDivisor: 5 - type: seed id: lime @@ -243,11 +243,11 @@ Nutriment: Min: 1 Max: 5 - PotencyDivisor: 20 + potencyDivisor: 20 Vitamin: Min: 1 Max: 4 - PotencyDivisor: 25 + potencyDivisor: 25 - type: seed id: orange @@ -269,11 +269,11 @@ Nutriment: Min: 1 Max: 5 - PotencyDivisor: 20 + potencyDivisor: 20 Vitamin: Min: 1 Max: 4 - PotencyDivisor: 25 + potencyDivisor: 25 - type: seed id: pineapple @@ -296,15 +296,15 @@ Nutriment: Min: 1 Max: 20 - PotencyDivisor: 20 + potencyDivisor: 20 Water: Min: 1 Max: 4 - PotencyDivisor: 25 + potencyDivisor: 25 Vitamin: Min: 1 Max: 2 - PotencyDivisor: 50 + potencyDivisor: 50 - type: seed id: potato @@ -326,11 +326,11 @@ Nutriment: Min: 1 Max: 10 - PotencyDivisor: 10 + potencyDivisor: 10 Vitamin: Min: 1 Max: 4 - PotencyDivisor: 25 + potencyDivisor: 25 - type: seed id: sugarcane @@ -353,7 +353,7 @@ Sugar: Min: 4 Max: 5 - PotencyDivisor: 5 + potencyDivisor: 5 - type: seed id: towercap @@ -426,15 +426,15 @@ Nutriment: Min: 1 Max: 7 - PotencyDivisor: 14 + potencyDivisor: 14 Vitamin: Min: 1 Max: 3 - PotencyDivisor: 33 + potencyDivisor: 33 Water: Min: 1 Max: 4 - PotencyDivisor: 25 + potencyDivisor: 25 - type: seed id: blueTomato @@ -460,15 +460,15 @@ Nutriment: Min: 1 Max: 5 - PotencyDivisor: 20 + potencyDivisor: 20 SpaceLube: Min: 5 Max: 15 - PotencyDivisor: 10 + potencyDivisor: 10 Vitamin: Min: 1 Max: 4 - PotencyDivisor: 25 + potencyDivisor: 25 - type: seed id: bloodTomato @@ -494,11 +494,11 @@ Blood: Min: 1 Max: 10 - PotencyDivisor: 10 + potencyDivisor: 10 Vitamin: Min: 1 Max: 4 - PotencyDivisor: 25 + potencyDivisor: 25 - type: seed id: eggplant @@ -523,11 +523,11 @@ Nutriment: Min: 1 Max: 10 - PotencyDivisor: 10 + potencyDivisor: 10 Vitamin: Min: 1 Max: 4 - PotencyDivisor: 25 + potencyDivisor: 25 - type: seed id: cabbage @@ -548,11 +548,11 @@ Nutriment: Min: 1 Max: 10 - PotencyDivisor: 10 + potencyDivisor: 10 Vitamin: Min: 1 Max: 4 - PotencyDivisor: 25 + potencyDivisor: 25 - type: seed id: garlic @@ -573,15 +573,15 @@ Nutriment: Min: 1 Max: 10 - PotencyDivisor: 10 + potencyDivisor: 10 Vitamin: Min: 1 Max: 4 - PotencyDivisor: 25 + potencyDivisor: 25 Allicin: Min: 1 Max: 8 - PotencyDivisor: 25 + potencyDivisor: 25 - type: seed id: apple @@ -603,11 +603,11 @@ Nutriment: Min: 1 Max: 10 - PotencyDivisor: 10 + potencyDivisor: 10 Vitamin: Min: 1 Max: 4 - PotencyDivisor: 25 + potencyDivisor: 25 - type: seed id: corn @@ -631,11 +631,11 @@ Nutriment: Min: 1 Max: 10 - PotencyDivisor: 20 + potencyDivisor: 20 Cornmeal: Min: 5 Max: 15 - PotencyDivisor: 10 + potencyDivisor: 10 - type: seed id: onion @@ -661,15 +661,15 @@ Nutriment: Min: 1 Max: 10 - PotencyDivisor: 10 + potencyDivisor: 10 Vitamin: Min: 1 Max: 4 - PotencyDivisor: 25 + potencyDivisor: 25 Allicin: Min: 1 Max: 4 - PotencyDivisor: 25 + potencyDivisor: 25 - type: seed id: onionred @@ -693,15 +693,15 @@ Nutriment: Min: 1 Max: 10 - PotencyDivisor: 10 + potencyDivisor: 10 Vitamin: Min: 1 Max: 4 - PotencyDivisor: 25 + potencyDivisor: 25 Allicin: Min: 1 Max: 4 - PotencyDivisor: 25 + potencyDivisor: 25 - type: seed id: chanterelle @@ -726,7 +726,7 @@ Nutriment: Min: 1 Max: 25 - PotencyDivisor: 25 + potencyDivisor: 25 - type: seed id: eggy @@ -750,7 +750,7 @@ Egg: Min: 1 Max: 10 - PotencyDivisor: 10 + potencyDivisor: 10 - type: seed id: cannabis @@ -761,6 +761,8 @@ packetPrototype: CannabisSeeds productPrototypes: - LeavesCannabis + mutationPrototypes: + - rainbowCannabis harvestRepeat: Repeat lifespan: 75 maturation: 8 @@ -775,7 +777,52 @@ THC: Min: 1 Max: 10 - PotencyDivisor: 10 + potencyDivisor: 10 + +- type: seed + id: rainbowCannabis + name: seeds-rainbow-cannabis-name + noun: seeds-noun-seeds + displayName: seeds-rainbow-cannabis-display-name + plantRsi: Objects/Specific/Hydroponics/rainbow_cannabis.rsi + packetPrototype: RainbowCannabisSeeds + productPrototypes: + - LeavesCannabisRainbow + harvestRepeat: Repeat + lifespan: 75 + maturation: 8 + production: 12 + yield: 2 + potency: 20 + growthStages: 3 + waterConsumption: 0.40 + idealLight: 9 + idealHeat: 298 + chemicals: + SpaceDrugs: + Min: 1 + Max: 15 + potencyDivisor: 10 + Lipolicide: + Min: 1 + Max: 15 + potencyDivisor: 10 + MindbreakerToxin: + Min: 1 + Max: 5 + potencyDivisor: 20 + Happiness: + Min: 1 + Max: 5 +# potencyDivisor: 20 +# ColorfulReagent: +# Min: 0 +# Max: 5 +# potencyDivisor: 20 + Psicodine: + Min: 0 + Max: 5 + potencyDivisor: 33 - type: seed id: tobacco @@ -800,7 +847,7 @@ Nicotine: Min: 1 Max: 10 - PotencyDivisor: 10 + potencyDivisor: 10 - type: seed id: nettle @@ -826,7 +873,7 @@ Histamine: Min: 1 Max: 25 - PotencyDivisor: 4 + potencyDivisor: 4 - type: seed id: deathNettle @@ -851,11 +898,11 @@ SulfuricAcid: Min: 1 Max: 15 - PotencyDivisor: 6 + potencyDivisor: 6 FluorosulfuricAcid: Min: 1 Max: 15 - PotencyDivisor: 6 + potencyDivisor: 6 - type: seed id: chili @@ -865,7 +912,7 @@ plantRsi: Objects/Specific/Hydroponics/chili.rsi packetPrototype: ChiliSeeds productPrototypes: - - FoodChili + - FoodChiliPepper mutationPrototypes: - chilly harvestRepeat: Repeat @@ -880,15 +927,15 @@ CapsaicinOil: Min: 1 Max: 10 - PotencyDivisor: 10 + potencyDivisor: 10 Nutriment: Min: 1 Max: 4 - PotencyDivisor: 25 + potencyDivisor: 25 Vitamin: Min: 1 Max: 4 - PotencyDivisor: 25 + potencyDivisor: 25 - type: seed id: chilly @@ -898,7 +945,7 @@ plantRsi: Objects/Specific/Hydroponics/chilly.rsi packetPrototype: ChillySeeds productPrototypes: - - FoodChilly + - FoodChillyPepper harvestRepeat: Repeat lifespan: 25 maturation: 6 @@ -911,15 +958,15 @@ Frostoil: Min: 1 Max: 10 - PotencyDivisor: 10 + potencyDivisor: 10 Nutriment: Min: 1 Max: 4 - PotencyDivisor: 25 + potencyDivisor: 25 Vitamin: Min: 1 Max: 4 - PotencyDivisor: 25 + potencyDivisor: 25 - type: seed id: poppy @@ -943,11 +990,11 @@ Nutriment: Min: 1 Max: 2 - Potencydivisor: 50 + potencyDivisor: 50 Bicaridine: Min: 1 Max: 20 - PotencyDivisor: 5 + potencyDivisor: 5 - type: seed id: aloe @@ -969,11 +1016,11 @@ Aloe: Min: 1 Max: 10 - PotencyDivisor: 10 + potencyDivisor: 10 Dermaline: Min: 1 Max: 10 - PotencyDivisor: 10 + potencyDivisor: 10 - type: seed id: lily @@ -997,11 +1044,11 @@ Nutriment: Min: 1 Max: 2 - Potencydivisor: 50 + potencyDivisor: 50 Bicaridine: Min: 1 Max: 20 - PotencyDivisor: 5 + potencyDivisor: 5 - type: seed id: lingzhi @@ -1023,11 +1070,11 @@ Ultravasculine: Min: 1 Max: 20 - PotencyDivisor: 5 + potencyDivisor: 5 Epinephrine: Min: 1 Max: 20 - PotencyDivisor: 5 + potencyDivisor: 5 - type: seed id: ambrosiaVulgaris @@ -1051,23 +1098,23 @@ Nutriment: Min: 1 Max: 2 - PotencyDivisor: 10 + potencyDivisor: 10 Bicaridine: Min: 1 Max: 5 - PotencyDivisor: 20 + potencyDivisor: 20 Kelotane: Min: 1 Max: 5 - PotencyDivisor: 20 + potencyDivisor: 20 Desoxyephedrine: Min: 1 Max: 10 - PotencyDivisor: 10 + potencyDivisor: 10 Vitamin: Min: 1 Max: 2 - Potencydivisor: 50 + potencyDivisor: 50 - type: seed id: ambrosiaDeus @@ -1089,19 +1136,19 @@ Nutriment: Min: 1 Max: 2 - PotencyDivisor: 10 + potencyDivisor: 10 Omnizine: # Don't kill me Min: 1 Max: 3 - PotencyDivisor: 35 + potencyDivisor: 35 SpaceDrugs: Min: 1 Max: 5 - PotencyDivisor: 20 + potencyDivisor: 20 Desoxyephedrine: Min: 1 Max: 10 - PotencyDivisor: 10 + potencyDivisor: 10 - type: seed id: galaxythistle @@ -1123,7 +1170,7 @@ Stellibinin: Min: 1 Max: 25 - PotencyDivisor: 4 + potencyDivisor: 4 - type: seed id: flyAmanita @@ -1146,11 +1193,11 @@ Amatoxin: Min: 1 Max: 25 - PotencyDivisor: 4 + potencyDivisor: 4 Nutriment: ## yumby :) Min: 1 Max: 5 - PotencyDivisor: 20 + potencyDivisor: 20 - type: seed id: gatfruit @@ -1172,11 +1219,11 @@ Nutriment: Min: 1 Max: 5 - PotencyDivisor: 20 + potencyDivisor: 20 Sulfur: Min: 1 Max: 5 - PotencyDivisor: 20 + potencyDivisor: 20 - type: seed id: rice @@ -1200,11 +1247,11 @@ Nutriment: Min: 1 Max: 20 - PotencyDivisor: 20 + potencyDivisor: 20 Rice: Min: 5 Max: 20 - PotencyDivisor: 20 + potencyDivisor: 20 - type: seed id: soybeans @@ -1229,7 +1276,7 @@ Nutriment: Min: 1 Max: 2 - PotencyDivisor: 50 + potencyDivisor: 50 - type: seed id: spacemansTrumpet @@ -1251,11 +1298,11 @@ Nutriment: Min: 1 Max: 5 - Potencydivisor: 50 + potencyDivisor: 50 PolypyryliumOligomers: Min: 1 Max: 15 - PotencyDivisor: 5 + potencyDivisor: 5 - type: seed id: koibean @@ -1278,11 +1325,11 @@ Nutriment: Min: 1 Max: 5 - PotencyDivisor: 20 + potencyDivisor: 20 CarpoToxin: Min: 1 Max: 4 - PotencyDivisor: 30 + potencyDivisor: 30 - type: seed id: grape @@ -1303,11 +1350,11 @@ Nutriment: Min: 1 Max: 5 - PotencyDivisor: 20 + potencyDivisor: 20 Vitamin: Min: 1 Max: 4 - PotencyDivisor: 25 + potencyDivisor: 25 - type: seed id: watermelon @@ -1328,15 +1375,15 @@ Nutriment: Min: 1 Max: 10 - PotencyDivisor: 10 + potencyDivisor: 10 Water: Min: 1 Max: 10 - PotencyDivisor: 10 + potencyDivisor: 10 Vitamin: Min: 1 Max: 5 - PotencyDivisor: 20 + potencyDivisor: 20 - type: seed id: cocoa @@ -1360,11 +1407,11 @@ Vitamin: Min: 1 Max: 4 - PotencyDivisor: 25 + potencyDivisor: 25 Nutriment: Min: 1 Max: 2 - PotencyDivisor: 50 + potencyDivisor: 50 - type: seed id: berries @@ -1386,11 +1433,11 @@ Nutriment: Min: 2 Max: 5 - PotencyDivisor: 30 + potencyDivisor: 30 Vitamin: Min: 1 Max: 4 - PotencyDivisor: 40 + potencyDivisor: 40 - type: seed id: bungo @@ -1415,11 +1462,11 @@ Nutriment: Min: 5 Max: 10 - PotencyDivisor: 20 + potencyDivisor: 20 Enzyme: Min: 5 Max: 10 - PotencyDivisor: 20 + potencyDivisor: 20 - type: seed id: pea @@ -1446,11 +1493,11 @@ Nutriment: Min: 1 Max: 3 - PotencyDivisor: 33 + potencyDivisor: 33 Vitamin: Min: 1 Max: 2 - PotencyDivisor: 50 + potencyDivisor: 50 - type: seed id: pumpkin @@ -1472,11 +1519,11 @@ PumpkinFlesh: Min: 1 Max: 20 - PotencyDivisor: 5 + potencyDivisor: 5 Vitamin: Min: 1 Max: 5 - PotencyDivisor: 20 + potencyDivisor: 20 - type: seed id: cotton @@ -1487,6 +1534,8 @@ packetPrototype: CottonSeeds productPrototypes: - CottonBol + mutationPrototypes: + - pyrotton lifespan: 25 maturation: 8 production: 3 @@ -1499,5 +1548,31 @@ Fiber: Min: 5 Max: 10 - PotencyDivisor: 20 + potencyDivisor: 20 +- type: seed + id: pyrotton + name: seeds-pyrotton-name + noun: seeds-noun-seeds + displayName: seeds-pyrotton-display-name + plantRsi: Objects/Specific/Hydroponics/pyrotton.rsi + packetPrototype: PyrottonSeeds + productPrototypes: + - PyrottonBol + lifespan: 25 + maturation: 8 + production: 3 + yield: 2 + potency: 5 + idealLight: 8 + growthStages: 3 + waterConsumption: 0.80 + chemicals: + Fiber: + Min: 5 + Max: 10 + potencyDivisor: 20 + Phlogiston: + Min: 4 + Max: 8 + potencyDivisor: 30 diff --git a/Resources/Prototypes/Interactions/Popups/interaction_popups.yml b/Resources/Prototypes/Interactions/Popups/interaction_popups.yml new file mode 100644 index 0000000000..cd51648e70 --- /dev/null +++ b/Resources/Prototypes/Interactions/Popups/interaction_popups.yml @@ -0,0 +1,63 @@ +# Small, invisible to others +- type: InteractionPopup + id: Subtle + popupType: Small + logChannel: Emotes + others: null + +# Small, visible to others +- type: InteractionPopup + id: Visible + popupType: Small + logChannel: Emotes + +# Small, visible to others but not logged into chat +- type: InteractionPopup + id: VisibleNoChat + popupType: Small + logPopup: false + +# Medium, visible to others +- type: InteractionPopup + id: Obvious + popupType: Medium + logChannel: Emotes + +# MediumCaution, visible to others +- type: InteractionPopup + id: Dangerous + popupType: MediumCaution + logChannel: Emotes + +# Delayed popups - not logged into chat +- type: InteractionPopup + id: SubtleDelayed + popupType: Small + logPopup: false + logChannel: Emotes + others: null + +- type: InteractionPopup + id: ObviousDelayed + popupType: Small + logPopup: false + logChannel: Emotes + +- type: InteractionPopup + id: DangerousDelayed + popupType: SmallCaution + logPopup: false + logChannel: Emotes + +# Visible only to self +- type: InteractionPopup + id: SubtleFail + popupType: SmallCaution + target: null + others: null + +# Visible to self and target, but not others +- type: InteractionPopup + id: Fail + popupType: SmallCaution + others: null diff --git a/Resources/Prototypes/Interactions/base.yml b/Resources/Prototypes/Interactions/base.yml new file mode 100644 index 0000000000..82d8574c1e --- /dev/null +++ b/Resources/Prototypes/Interactions/base.yml @@ -0,0 +1,42 @@ +- type: Interaction + id: Base + abstract: true + effectSuccess: + popup: Small + effectFailure: + popup: SubtleFail + +# Base global interaction +- type: Interaction + id: BaseGlobal + abstract: true + global: true + +# Base interaction that involves hands +- type: Interaction + id: BaseHands + abstract: true + requiresHands: true + requiresCanInteract: true + contactInteraction: true + range: + max: 1.2 + effectSuccess: + popup: Obvious + sound: {path: /Audio/Effects/thudswoosh.ogg} + effectFailure: + popup: Fail + sound: {path: /Audio/Effects/thudswoosh.ogg} + +# Base interaction using dangerous popups +- type: Interaction + id: BaseDangerous + abstract: true + effectSuccess: + popup: Dangerous + sound: {path: /Audio/Effects/thudswoosh.ogg} + effectFailure: + popup: Fail + sound: {path: /Audio/Effects/thudswoosh.ogg} + effectDelayed: + popup: DangerousDelayed diff --git a/Resources/Prototypes/Interactions/help_interactions.yml b/Resources/Prototypes/Interactions/help_interactions.yml new file mode 100644 index 0000000000..0e383389d9 --- /dev/null +++ b/Resources/Prototypes/Interactions/help_interactions.yml @@ -0,0 +1,93 @@ +- type: Interaction + id: BaseHelp + parent: [BaseDangerous, BaseHands] + abstract: true + priority: -5 + cooldown: 4 + range: {max: 1.2} + allowedContests: [Mass] + contestAdvantageRange: + min: 0.4 # Only lower bound; you can be as much stronger than your target as you like + contestAdvantageLimit: + min: 0.5 + max: 2 + contestDelay: true + +# Combines waking up, helping from stun, and forcing to stand. +- type: Interaction + id: HelpUp + parent: [BaseHelp, BaseGlobal] + delay: 1.5 + cooldown: 0.5 + hideByRequirement: true + requirement: + !type:StandingStateRequirement + allowLaying: true + allowKnockedDown: true + action: + !type:ComplexAction + requireAll: false + actions: + - !type:ToggleSleepingAction + wakeUp: true + - !type:ChangeStandingStateAction + makeStanding: true + - !type:ModifyStatusEffectAction + effect: KnockedDown + timeAdded: -2.5 # TODO: probably unnecessary but some systems like slippery sometimes mention it in TODO manner + - !type:ModifyStatusEffectAction + effect: Stun + timeAdded: -2.5 # 2 seconds delay to remove 2.5 seconds of stun - should be good enough. + +- type: Interaction + id: ForceDown + parent: [BaseHelp, BaseGlobal] + delay: 4.5 + hideByRequirement: true + requirement: + !type:StandingStateRequirement + allowStanding: true + action: + !type:ChangeStandingStateAction + makeLaying: true + +- type: Interaction + id: MakeSleepOther + parent: [BaseHelp, BaseGlobal] + priority: -6 + delay: 10 # Should be long enough to be non-abusable, right? + hideByRequirement: true + requirement: + !type:ComplexRequirement + requirements: + - !type:StandingStateRequirement + allowLaying: true + allowKnockedDown: true + - !type:MobStateRequirement + allowedStates: [Alive, Critical] + action: + !type:ToggleSleepingAction + sleep: true + +# Shake the target to wake them up and sober them up a little bit +- type: Interaction + id: ShakeOther + parent: [BaseHelp, BaseGlobal] + priority: -5 + delay: 0.8 + cooldown: 10 # Slightly abusable + effectDelayed: null + hideByRequirement: true + requirement: + !type:MobStateRequirement + allowedStates: [Alive, Critical] + action: + !type:ComplexAction + actions: + - !type:ModifyStatusEffectAction + effect: Drunk + timeAdded: -20 # Only removes 20s of visual effects, not affecting the amount of ethanol in the target's blood + - !type:JitterAction + time: 0.8 + - !type:ToggleSleepingAction + wakeUp: true diff --git a/Resources/Prototypes/Interactions/mood_interactions.yml b/Resources/Prototypes/Interactions/mood_interactions.yml new file mode 100644 index 0000000000..6a50704b03 --- /dev/null +++ b/Resources/Prototypes/Interactions/mood_interactions.yml @@ -0,0 +1,60 @@ +# Hugging - improves the mood of the user +- type: Interaction + id: Hug + parent: [BaseGlobal, BaseHands] + priority: 2 + #icon: /Textures/Interface/Actions/hug.png + delay: 0.7 + range: {max: 1} + hideByRequirement: true + requirement: + !type:MobStateRequirement + inverted: true + action: + # TODO: this should pull the target closer or sumth, but I need to code that action first + !type:MoodAction + effect: BeingHugged + +# Petting someone (people) - improves the mood of the target +- type: Interaction + id: Pet + parent: [BaseGlobal, BaseHands] + priority: 1 + #icon: /Textures/Interface/Actions/hug.png + delay: 0.4 + range: {max: 1} + hideByRequirement: true + requirement: + !type:ComplexRequirement + requirements: + - !type:MobStateRequirement + inverted: true + - !type:EntityWhitelistRequirement + whitelist: + components: [HumanoidAppearance] + action: + !type:MoodAction + effect: BeingPet + +# Petting someone (animals) - improves the mood of the user and the target +- type: Interaction + id: PetAnimal + parent: Pet + requirement: + !type:ComplexRequirement + requirements: + - !type:MobStateRequirement + allowedStates: [Alive] + - !type:EntityWhitelistRequirement + blacklist: + components: [HumanoidAppearance] + action: + !type:ComplexAction # TODO might wanna make a multiplexer action for situations like this + actions: + - !type:MoodAction + effect: BeingPet + - !type:OnUserAction + action: + !type:MoodAction + effect: PetAnimal + diff --git a/Resources/Prototypes/Interactions/noop_interactions.yml b/Resources/Prototypes/Interactions/noop_interactions.yml new file mode 100644 index 0000000000..6729b36e75 --- /dev/null +++ b/Resources/Prototypes/Interactions/noop_interactions.yml @@ -0,0 +1,56 @@ +- type: Interaction + id: LookAt + parent: BaseGlobal + priority: 4 + requiresHands: false + requiresCanInteract: false + contactInteraction: false + allowSelfInteract: true + icon: /Textures/Interface/Actions/eyeopen.png + range: {max: 20} + effectSuccess: + popup: Obvious + sound: {path: /Audio/Effects/ominous.ogg} + soundPerceivedByOthers: false # Can be used to attract attention, but not to spam sounds or chat + action: + !type:NoOpAction + +- type: Interaction + id: WaveAt + parent: [BaseHands, BaseGlobal] + priority: 3 + requiresCanInteract: false + contactInteraction: false + range: {max: 20} + effectSuccess: + popup: Obvious + sound: {path: /Audio/Effects/ominous.ogg} + soundPerceivedByOthers: false + hideByRequirement: true + requirement: + !type:MobStateRequirement # Don't wave your hands at inanimate objects smh + inverted: true + action: + !type:NoOpAction + +# Knocking on the target - windows, doors, etc. +- type: Interaction + id: KnockOn + parent: BaseHands + priority: 20 + effectSuccess: + popup: Visible + sound: {path: /Audio/Effects/glass_knock.ogg} + action: + !type:NoOpAction + +# Rattling a fence +- type: Interaction + id: Rattle + parent: BaseHands + priority: 20 + effectSuccess: + popup: VisibleNoChat + sound: {collection: FenceRattle} + action: + !type:NoOpAction diff --git a/Resources/Prototypes/Interactions/self_interactions.yml b/Resources/Prototypes/Interactions/self_interactions.yml new file mode 100644 index 0000000000..0c3aad0118 --- /dev/null +++ b/Resources/Prototypes/Interactions/self_interactions.yml @@ -0,0 +1,46 @@ +- type: Interaction + id: SelfInteractionBase + parent: [BaseHands, BaseGlobal] + abstract: true + allowSelfInteract: true + hideByRequirement: true + effectFailure: + popup: SubtleFail + effectDelayed: + popup: Subtle + requirement: + !type:SelfTargetRequirement + failPopup: + others: null + +- type: Interaction + id: PinchSelf + parent: SelfInteractionBase + delay: 1 + action: + !type:ComplexAction + actions: + - !type:ModifyHealthAction + damage: + types: {Blunt: 5} + # 45% chance to cause yelp + - !type:ConditionalAction + condition: + !type:ChanceRequirement + chance: 0.45 + true: + !type:ChatMessageAction + numMessages: 3 + +# Sleeping on the floor is real +- type: Interaction + id: MakeSleepSelf + parent: [SelfInteractionBase, MakeSleepOther] + delay: 4.5 + requirement: + !type:ComplexRequirement + requirements: + - !type:SelfTargetRequirement + - !type:StandingStateRequirement + allowLaying: true + allowKnockedDown: true diff --git a/Resources/Prototypes/InventoryTemplates/aghost_inventory_template.yml b/Resources/Prototypes/InventoryTemplates/aghost_inventory_template.yml index 6252ad8e99..84806a051a 100644 --- a/Resources/Prototypes/InventoryTemplates/aghost_inventory_template.yml +++ b/Resources/Prototypes/InventoryTemplates/aghost_inventory_template.yml @@ -11,6 +11,7 @@ displayName: ID - name: id slotTexture: id + fullTextureName: template_small slotFlags: IDCARD slotGroup: SecondHotbar stripTime: 6 diff --git a/Resources/Prototypes/InventoryTemplates/arachnid_inventory_template.yml b/Resources/Prototypes/InventoryTemplates/arachnid_inventory_template.yml index a0c62b04cb..863d5758ff 100644 --- a/Resources/Prototypes/InventoryTemplates/arachnid_inventory_template.yml +++ b/Resources/Prototypes/InventoryTemplates/arachnid_inventory_template.yml @@ -63,6 +63,7 @@ displayName: Suit Storage - name: id slotTexture: id + fullTextureName: template_small slotFlags: IDCARD slotGroup: SecondHotbar stripTime: 6 @@ -72,6 +73,7 @@ displayName: ID - name: belt slotTexture: belt + fullTextureName: template_small slotFlags: BELT slotGroup: SecondHotbar stripTime: 6 @@ -80,6 +82,7 @@ displayName: Belt - name: back slotTexture: back + fullTextureName: template_small slotFlags: BACK slotGroup: SecondHotbar stripTime: 6 @@ -89,6 +92,7 @@ - name: pocket4 slotTexture: web + fullTextureName: template_small slotFlags: POCKET slotGroup: MainHotbar stripTime: 3 @@ -97,6 +101,7 @@ displayName: Pocket 4 - name: pocket3 slotTexture: web + fullTextureName: template_small slotFlags: POCKET slotGroup: MainHotbar stripTime: 3 @@ -112,6 +117,7 @@ displayName: Suit - name: pocket1 slotTexture: pocket + fullTextureName: template_small slotFlags: POCKET slotGroup: MainHotbar stripTime: 3 @@ -122,6 +128,7 @@ stripHidden: true - name: pocket2 slotTexture: pocket + fullTextureName: template_small slotFlags: POCKET slotGroup: MainHotbar stripTime: 3 @@ -138,4 +145,6 @@ uiWindowPos: 2,0 strippingWindowPos: 2,5 dependsOn: outerClothing + dependsOnComponents: + - type: AllowSuitStorage displayName: Suit Storage diff --git a/Resources/Prototypes/InventoryTemplates/corpse_inventory_template.yml b/Resources/Prototypes/InventoryTemplates/corpse_inventory_template.yml index 41fa7dc375..878ccb3d6b 100644 --- a/Resources/Prototypes/InventoryTemplates/corpse_inventory_template.yml +++ b/Resources/Prototypes/InventoryTemplates/corpse_inventory_template.yml @@ -55,6 +55,7 @@ displayName: Head - name: pocket1 slotTexture: pocket + fullTextureName: template_small slotFlags: POCKET slotGroup: MainHotbar stripTime: 3 @@ -65,6 +66,7 @@ stripHidden: true - name: pocket2 slotTexture: pocket + fullTextureName: template_small slotFlags: POCKET slotGroup: MainHotbar stripTime: 3 @@ -81,9 +83,12 @@ uiWindowPos: 2,0 strippingWindowPos: 2,5 dependsOn: outerClothing + dependsOnComponents: + - type: AllowSuitStorage displayName: Suit Storage - name: belt slotTexture: belt + fullTextureName: template_small slotFlags: BELT slotGroup: SecondHotbar stripTime: 6 diff --git a/Resources/Prototypes/InventoryTemplates/diona_inventory_template.yml b/Resources/Prototypes/InventoryTemplates/diona_inventory_template.yml index 4bbd18b136..619aefddc3 100644 --- a/Resources/Prototypes/InventoryTemplates/diona_inventory_template.yml +++ b/Resources/Prototypes/InventoryTemplates/diona_inventory_template.yml @@ -56,6 +56,7 @@ displayName: Head - name: pocket1 slotTexture: pocket + fullTextureName: template_small slotFlags: POCKET slotGroup: MainHotbar stripTime: 3 @@ -66,6 +67,7 @@ stripHidden: true - name: pocket2 slotTexture: pocket + fullTextureName: template_small slotFlags: POCKET slotGroup: MainHotbar stripTime: 3 @@ -82,9 +84,12 @@ uiWindowPos: 2,0 strippingWindowPos: 2,5 dependsOn: outerClothing + dependsOnComponents: + - type: AllowSuitStorage displayName: Suit Storage - name: id slotTexture: id + fullTextureName: template_small slotFlags: IDCARD slotGroup: SecondHotbar stripTime: 6 @@ -94,6 +99,7 @@ displayName: ID - name: belt slotTexture: belt + fullTextureName: template_small slotFlags: BELT slotGroup: SecondHotbar stripTime: 6 @@ -102,6 +108,7 @@ displayName: Belt - name: back slotTexture: back + fullTextureName: template_small slotFlags: BACK slotGroup: SecondHotbar stripTime: 6 diff --git a/Resources/Prototypes/InventoryTemplates/holoclown_inventory_template.yml b/Resources/Prototypes/InventoryTemplates/holoclown_inventory_template.yml index 57dce506ea..7be9c75015 100644 --- a/Resources/Prototypes/InventoryTemplates/holoclown_inventory_template.yml +++ b/Resources/Prototypes/InventoryTemplates/holoclown_inventory_template.yml @@ -3,6 +3,7 @@ slots: - name: pocket1 slotTexture: pocket + fullTextureName: template_small slotFlags: POCKET slotGroup: MainHotbar stripTime: 3 @@ -12,6 +13,7 @@ stripHidden: true - name: pocket2 slotTexture: pocket + fullTextureName: template_small slotFlags: POCKET slotGroup: MainHotbar stripTime: 3 diff --git a/Resources/Prototypes/InventoryTemplates/human_inventory_template.yml b/Resources/Prototypes/InventoryTemplates/human_inventory_template.yml index 574ecca35f..ff1447931f 100644 --- a/Resources/Prototypes/InventoryTemplates/human_inventory_template.yml +++ b/Resources/Prototypes/InventoryTemplates/human_inventory_template.yml @@ -62,6 +62,7 @@ displayName: Head - name: pocket1 slotTexture: pocket + fullTextureName: template_small slotFlags: POCKET slotGroup: MainHotbar stripTime: 3 @@ -72,6 +73,7 @@ stripHidden: true - name: pocket2 slotTexture: pocket + fullTextureName: template_small slotFlags: POCKET slotGroup: MainHotbar stripTime: 3 @@ -88,9 +90,12 @@ uiWindowPos: 2,0 strippingWindowPos: 2,5 dependsOn: outerClothing + dependsOnComponents: + - type: AllowSuitStorage displayName: Suit Storage - name: id slotTexture: id + fullTextureName: template_small slotFlags: IDCARD slotGroup: SecondHotbar stripTime: 6 @@ -100,6 +105,7 @@ displayName: ID - name: belt slotTexture: belt + fullTextureName: template_small slotFlags: BELT slotGroup: SecondHotbar stripTime: 6 @@ -108,6 +114,7 @@ displayName: Belt - name: back slotTexture: back + fullTextureName: template_small slotFlags: BACK slotGroup: SecondHotbar stripTime: 6 diff --git a/Resources/Prototypes/InventoryTemplates/ipc_inventory_template.yml b/Resources/Prototypes/InventoryTemplates/ipc_inventory_template.yml new file mode 100644 index 0000000000..14a62510f2 --- /dev/null +++ b/Resources/Prototypes/InventoryTemplates/ipc_inventory_template.yml @@ -0,0 +1,143 @@ +- type: inventoryTemplate + id: ipc + slots: + - name: shoes + slotTexture: shoes + slotFlags: FEET + stripTime: 3 + uiWindowPos: 1,0 + strippingWindowPos: 1,3 + displayName: Shoes + - name: jumpsuit + slotTexture: uniform + slotFlags: INNERCLOTHING + stripTime: 6 + uiWindowPos: 0,1 + strippingWindowPos: 0,2 + displayName: Jumpsuit + - name: outerClothing + slotTexture: suit + slotFlags: OUTERCLOTHING + stripTime: 6 + uiWindowPos: 1,1 + strippingWindowPos: 1,2 + displayName: Suit + # Underwear + # - name: undershirt + # slotTexture: undershirt + # slotFlags: UNDERSHIRT + # stripTime: 8 + # uiWindowPos: 4,1 + # strippingWindowPos: 3,1 + # displayName: Undershirt + # - name: underpants + # slotTexture: underpants + # slotFlags: UNDERPANTS + # stripTime: 12 + # uiWindowPos: 4,0 + # strippingWindowPos: 3,2 + # displayName: Underpants + # - name: socks + # slotTexture: socks + # slotFlags: SOCKS + # stripTime: 8 + # uiWindowPos: 4,2 + # strippingWindowPos: 3,3 + # displayName: Socks + - name: gloves + slotTexture: gloves + slotFlags: GLOVES + uiWindowPos: 2,1 + strippingWindowPos: 2,0 + displayName: Gloves + - name: neck + slotTexture: neck + slotFlags: NECK + uiWindowPos: 0,2 + strippingWindowPos: 0,1 + displayName: Neck + - name: mask + uiWindowPos: 1,2 + slotTexture: mask + slotFlags: MASK + strippingWindowPos: 1,1 + displayName: Mask + whitelist: + components: + - IdentityBlocker + tags: + - IPCMaskWearable + - name: eyes + slotTexture: glasses + slotFlags: EYES + stripTime: 3 + uiWindowPos: 0,0 + strippingWindowPos: 0,0 + displayName: Eyes + # - name: ears + # slotTexture: ears + # slotFlags: EARS + # stripTime: 3 + # uiWindowPos: 2,0 + # strippingWindowPos: 2,0 + # displayName: Ears + - name: head + slotTexture: head + slotFlags: HEAD + uiWindowPos: 1,3 + strippingWindowPos: 1,0 + displayName: Head + - name: pocket1 + slotTexture: pocket + slotFlags: POCKET + slotGroup: MainHotbar + stripTime: 3 + uiWindowPos: 0,3 + strippingWindowPos: 0,4 + dependsOn: jumpsuit + displayName: Pocket 1 + stripHidden: true + - name: pocket2 + slotTexture: pocket + slotFlags: POCKET + slotGroup: MainHotbar + stripTime: 3 + uiWindowPos: 2,3 + strippingWindowPos: 1,4 + dependsOn: jumpsuit + displayName: Pocket 2 + stripHidden: true + - name: suitstorage + slotTexture: suit_storage + slotFlags: SUITSTORAGE + slotGroup: MainHotbar + stripTime: 3 + uiWindowPos: 2,0 + strippingWindowPos: 2,5 + dependsOn: outerClothing + displayName: Suit Storage + - name: id + slotTexture: id + slotFlags: IDCARD + slotGroup: SecondHotbar + stripTime: 6 + uiWindowPos: 2,1 + strippingWindowPos: 2,4 + dependsOn: jumpsuit + displayName: ID + - name: belt + slotTexture: belt + slotFlags: BELT + slotGroup: SecondHotbar + stripTime: 6 + uiWindowPos: 3,1 + strippingWindowPos: 1,5 + displayName: Belt + - name: back + slotTexture: back + slotFlags: BACK + slotGroup: SecondHotbar + stripTime: 6 + uiWindowPos: 3,0 + strippingWindowPos: 0,5 + displayName: Back diff --git a/Resources/Prototypes/InventoryTemplates/kangaroo_inventory_template.yml b/Resources/Prototypes/InventoryTemplates/kangaroo_inventory_template.yml index fb7dee1ec2..5f81cdebc6 100644 --- a/Resources/Prototypes/InventoryTemplates/kangaroo_inventory_template.yml +++ b/Resources/Prototypes/InventoryTemplates/kangaroo_inventory_template.yml @@ -35,6 +35,7 @@ - name: belt slotTexture: belt + fullTextureName: template_small slotFlags: BELT slotGroup: SecondHotbar stripTime: 6 @@ -47,6 +48,7 @@ - name: pocket1 slotTexture: pocket + fullTextureName: template_small slotFlags: POCKET slotGroup: MainHotbar stripTime: 3 @@ -60,6 +62,7 @@ slots: - name: pocket1 slotTexture: pocket + fullTextureName: template_small slotFlags: POCKET slotGroup: MainHotbar stripTime: 3 diff --git a/Resources/Prototypes/InventoryTemplates/monkey_inventory_template.yml b/Resources/Prototypes/InventoryTemplates/monkey_inventory_template.yml index 5af23dabac..19875f7e1b 100644 --- a/Resources/Prototypes/InventoryTemplates/monkey_inventory_template.yml +++ b/Resources/Prototypes/InventoryTemplates/monkey_inventory_template.yml @@ -29,6 +29,7 @@ displayName: Jumpsuit - name: id slotTexture: id + fullTextureName: template_small slotFlags: IDCARD slotGroup: SecondHotbar stripTime: 6 @@ -44,6 +45,8 @@ uiWindowPos: 2,0 strippingWindowPos: 2,5 dependsOn: outerClothing + dependsOnComponents: + - type: AllowSuitStorage displayName: Suit Storage - name: outerClothing slotTexture: suit diff --git a/Resources/Prototypes/Language/Species-Specific/diona.yml b/Resources/Prototypes/Language/Species-Specific/diona.yml new file mode 100644 index 0000000000..57c928dc46 --- /dev/null +++ b/Resources/Prototypes/Language/Species-Specific/diona.yml @@ -0,0 +1,18 @@ +# Spoken by dionas. +# TODO: Replace this with a much better language. +- type: language + id: RootSpeak + isVisibleLanguage: true + speech: + color: "#ce5e14dd" + fontId: Noganas + obfuscation: + !type:SyllableObfuscation + minSyllables: 1 + maxSyllables: 5 + replacement: + - hs + - zt + - kr + - st + - sh diff --git a/Resources/Prototypes/Language/Species-Specific/harpy.yml b/Resources/Prototypes/Language/Species-Specific/harpy.yml new file mode 100644 index 0000000000..531b92e325 --- /dev/null +++ b/Resources/Prototypes/Language/Species-Specific/harpy.yml @@ -0,0 +1,117 @@ +- type: language + id: ValyrianStandard + isVisibleLanguage: true + speech: + fontId: Cambria + color: "#008ecc" + obfuscation: + !type:SyllableObfuscation + minSyllables: 2 + maxSyllables: 5 + replacement: + - š + - ž + - ź + - ż + - ö + - ü + - çi + - bü + - ca + - çu + - dü + - do + - fa + - fe + - fi + - gü + - ha + - je + - jü + - la + - le + - li + - me + - mi + - mo + - çe + - nj + - ba + - ce + - da + - fi + - go + - ha + - ji + - ka + - la + - ma + - na + - pa + - ra + - sa + - ta + - wa + - za + - ab + - ed + - if + - og + - up + - yk + - za + - ta + - va + - čk + - žd + - sak + - anz + - emn + - okl + - upn + - bab + - cic + - ded + - fig + - gog + - hib + - juj + - kik + - lal + - mam + - nan + - pap + - rar + - sas + - tat + - wab + - zab + - stras + - bram + - klam + - tram + - stry + - zrak + - deck + - mist + - ale + - ogn + - emt + - brat + - skar + - plik + - klat + - ae + - ai + - au + - ei + - ou + - bl + - dr + - fl + - gr + - pr + - sz + - cz + - zk + - jn diff --git a/Resources/Prototypes/Language/Species-Specific/marish.yml b/Resources/Prototypes/Language/Species-Specific/marish.yml new file mode 100644 index 0000000000..20b42a80d1 --- /dev/null +++ b/Resources/Prototypes/Language/Species-Specific/marish.yml @@ -0,0 +1,21 @@ +# Spoken by shadowkins. +- type: language + id: Marish + isVisibleLanguage: true + speech: + color: "#be3cc5" + fontId: Lymphatic + empathySpeech: true + speechVerbOverrides: + - chat-speech-verb-marish + obfuscation: + !type:SyllableObfuscation + minSyllables: 1 # Replacements are really short + maxSyllables: 2 + replacement: + - mar + - mwrrr + - maaAr + - aarrr + - wrurrl + - mmar diff --git a/Resources/Prototypes/Language/Species-Specific/moth.yml b/Resources/Prototypes/Language/Species-Specific/moth.yml new file mode 100644 index 0000000000..e44b3bdd7c --- /dev/null +++ b/Resources/Prototypes/Language/Species-Specific/moth.yml @@ -0,0 +1,70 @@ +# Spoken by moths. +# TODO: Replace this with a much better language. +- type: language + id: Moffic + isVisibleLanguage: true + speech: + color: "#c7df2edd" + fontId: Copperplate + obfuscation: + !type:SyllableObfuscation + minSyllables: 2 # Replacements are really short + maxSyllables: 4 + replacement: + - år + - i + - går + - sek + - mo + - ff + - ok + - gj + - ø + - gå + - la + - le + - lit + - ygg + - van + - dår + - næ + - møt + - idd + - hvo + - ja + - på + - han + - så + - ån + - det + - att + - nå + - gö + - bra + - int + - tyc + - om + - när + - två + - må + - dag + - sjä + - vii + - vuo + - eil + - tun + - käyt + - teh + - vä + - hei + - huo + - suo + - ää + - ten + - ja + - heu + - stu + - uhr + - kön + - we + - hön diff --git a/Resources/Prototypes/Language/Species-Specific/nekomimetic.yml b/Resources/Prototypes/Language/Species-Specific/nekomimetic.yml new file mode 100644 index 0000000000..a9dae1650d --- /dev/null +++ b/Resources/Prototypes/Language/Species-Specific/nekomimetic.yml @@ -0,0 +1,61 @@ +# A mess of broken Japanese, spoken by Felinds and Oni +# TODO: Replace this with a much better language. +- type: language + id: Nekomimetic + isVisibleLanguage: true + speech: + color: "#df57aaee" + fontId: Manga + obfuscation: + !type:SyllableObfuscation + minSyllables: 1 + maxSyllables: 3 # May be too long even, we'll see. + replacement: + - neko + - nyan + - mimi + - moe + - mofu + - fuwa + - kyaa + - kawaii + - poka + - munya + - puni + - munyu + - ufufu + - icha + - doki + - kyun + - kusu + - nya + - nyaa + - desu + - kis + - ama + - chuu + - baka + - hewo + - boop + - gato + - kit + - sune + - yori + - sou + - baka + - chan + - san + - kun + - mahou + - yatta + - suki + - usagi + - domo + - ori + - uwa + - zaazaa + - shiku + - puru + - ira + - heto + - etto diff --git a/Resources/Prototypes/Language/Species-Specific/reptilian.yml b/Resources/Prototypes/Language/Species-Specific/reptilian.yml new file mode 100644 index 0000000000..b5ba3f6dda --- /dev/null +++ b/Resources/Prototypes/Language/Species-Specific/reptilian.yml @@ -0,0 +1,118 @@ +# Spoken by Unathi. +- type: language + id: Draconic + isVisibleLanguage: true + speech: + color: "#2aca2add" + obfuscation: + !type:SyllableObfuscation + minSyllables: 2 + maxSyllables: 4 + replacement: + - za + - az + - ze + - ez + - zi + - iz + - zo + - oz + - zu + - uz + - zs + - sz + - ha + - ah + - he + - eh + - hi + - ih + - ho + - oh + - hu + - uh + - hs + - sh + - la + - al + - le + - el + - li + - il + - lo + - ol + - lu + - ul + - ls + - sl + - ka + - ak + - ke + - ek + - ki + - ik + - ko + - ok + - ku + - uk + - ks + - sk + - sa + - as + - se + - es + - si + - is + - so + - os + - su + - us + - ss + - ss + - ra + - ar + - re + - er + - ri + - ir + - ro + - or + - ru + - ur + - rs + - sr + - a + - a + - e + - e + - i + - i + - o + - o + - u + - u + - s + - s + +# Reptilian-Specific "Rare" Language +# !Do Not Make A Translator For This Language. +- type: language + id: Azaziba + speech: + color: "#2aca2add" + obfuscation: + !type:SyllableObfuscation + minSyllables: 2 + maxSyllables: 4 + replacement: + - azs + - zis + - zau + - azua + - skiu + - zuakz + - izo + - aei + - ki + - kut + - zo diff --git a/Resources/Prototypes/Language/Species-Specific/slimeperson.yml b/Resources/Prototypes/Language/Species-Specific/slimeperson.yml new file mode 100644 index 0000000000..a4d8fc1345 --- /dev/null +++ b/Resources/Prototypes/Language/Species-Specific/slimeperson.yml @@ -0,0 +1,18 @@ +# Spoken by slimes. +# TODO: Replace this with a much better language. +- type: language + id: Bubblish + isVisibleLanguage: true + speech: + color: "#00a3e2dd" + fontId: RubikBubbles + obfuscation: + !type:SyllableObfuscation + minSyllables: 1 + maxSyllables: 3 + replacement: + - blob + - plop + - pop + - bop + - boop diff --git a/Resources/Prototypes/Language/Species-Specific/vulpkanin.yml b/Resources/Prototypes/Language/Species-Specific/vulpkanin.yml new file mode 100644 index 0000000000..2b53f79a55 --- /dev/null +++ b/Resources/Prototypes/Language/Species-Specific/vulpkanin.yml @@ -0,0 +1,69 @@ +# Spoken by the Vulpkanin race. +# TODO: Replace this with a much better language. +- type: language + id: Canilunzt + isVisibleLanguage: true + speech: + color: "#d69b3dcc" + obfuscation: + !type:SyllableObfuscation + minSyllables: 1 + maxSyllables: 4 + replacement: + - rur + - ya + - cen + - rawr + - bar + - kuk + - tek + - qat + - uk + - wu + - vuh + - tah + - tch + - schz + - auch + - ist + - ein + - entch + - zwichs + - tut + - mir + - wo + - bis + - es + - vor + - nic + - gro + - enem + - zandt + - tzch + - noch + - hel + - ischt + - far + - wa + - baram + - iereng + - tech + - lach + - sam + - mak + - lich + - gen + - or + - ag + - eck + - gec + - stag + - onn + - bin + - ket + - jarl + - vulf + - einech + - cresthz + - azunein + - ghzth diff --git a/Resources/Prototypes/Language/Standard/elyran.yml b/Resources/Prototypes/Language/Standard/elyran.yml new file mode 100644 index 0000000000..b97a0698f2 --- /dev/null +++ b/Resources/Prototypes/Language/Standard/elyran.yml @@ -0,0 +1,87 @@ +- type: language + id: Elyran + isVisibleLanguage: true + speech: + color: "#8282fbaa" + obfuscation: + !type:SyllableObfuscation + minSyllables: 1 + maxSyllables: 4 + replacement: + - af + - if + - ba + - ta + - tha + - id + - jem + - ha + - kha + - dal + - dhl + - ra + - zay + - sen + - um + - shn + - sid + - ad + - ta + - za + - ayn + - gha + - zir + - yn + - fa + - qaf + - iam + - mim + - al + - ja + - non + - ha + - waw + - ya + - hem + - zah + - hml + - ks + - ini + - da + - ks + - iga + - ih + - la + - ulf + - xe + - ayw + - sit + - ah + - aarah + - jalaa + - sirt + - kurt + - turhk + - ust + - irk + - kir + - mir + - ach + - oglu + - bolu + - shek + - she + - ghoz + - miya + - ejdan + - haaz + - quq + - taab + - shanha + - an + - saa + - seh + - an' + - e' + - a' + - em' diff --git a/Resources/Prototypes/Language/Standard/freespeak.yml b/Resources/Prototypes/Language/Standard/freespeak.yml new file mode 100644 index 0000000000..9fe252e5b2 --- /dev/null +++ b/Resources/Prototypes/Language/Standard/freespeak.yml @@ -0,0 +1,260 @@ +- type: language + id: Freespeak + isVisibleLanguage: true + speech: + color: "#597d35" + obfuscation: + !type:SyllableObfuscation + minSyllables: 1 + maxSyllables: 3 + replacement: + - a + - aan + - aas + - ab + - aba + - ad + - aee + - aft + - ag + - ai + - aise + - ak + - akee + - aq + - ar + - ata + - aur + - aus + - ba + - baat + - bach + - bad + - bahe + - band + - be + - ben + - ber + - bhaa + - bhu + - bra + - burt + - cap + - cer + - ch + - cha + - chaar + - chale + - chalo + - chil + - com + - da + - daa + - daaj + - dat + - de + - dee + - dhaa + - di + - die + - dik + - din + - diz + - do + - dos + - dosh + - durch + - eer + - ek + - er + - es + - fal + - fang + - fra + - fun + - ga + - gan + - gao + - gee + - geet + - gern + - gir + - gon + - gren + - gri + - gu + - guda + - ha + - haa + - hai + - hain + - har + - hat + - he + - hee + - heer + - hekt + - heu + - hit + - hn + - ho + - hua + - huk + - hul + - ich + - ig + - in + - isch + - ja + - jaa + - jad + - jan + - jao + - jar + - jas + - jee + - jiao + - jin + - jing + - un + - ka + - kaha + - kana + - kar + - kara + - karo + - ke + - kee + - keln + - kha + - khada + - khe + - khi + - ko + - koo + - ky + - la + - laa + - laat + - lad + - lada + - lana + - lane + - le + - lee + - leiden + - leis + - len + - lie + - lo + - maa + - maan + - mod + - most + - muj + - mujhe + - mukt + - na + - naya + - ne + - nee + - net + - neta + - nir + - nka + - oon + - oop + - pa + - paa + - pet + - phen + - phot + - pi + - plo + - pra + - que + - ra + - raa + - rahe + - raho + - ran + - rana + - rar + - re + - ri + - rie + - rin + - ro + - rona + - rosh + - rtiv + - saa + - saal + - saath + - san + - santu + - sch + - se + - sen + - sh + - sha + - shee + - shi + - shn + - sht + - shuo + - soch + - sol + - soo + - ssa + - ster + - suk + - sur + - ta + - taan + - tak + - taka + - tal + - tan + - tar + - ten + - tend + - th + - tho + - tili + - to + - ton + - tr + - tu + - tum + - tung + - udaa + - ugr + - unge + - ut + - va + - vaa + - vaad + - vaib + - ve + - ven + - ver + - vi + - vis + - vol + - wic + - wu + - wut + - xi + - xiao + - ya + - yah + - yon + - you + - zas + - ze + - zhu + - zi + - zo + - zorn + - zt diff --git a/Resources/Prototypes/Language/Standard/solcommon.yml b/Resources/Prototypes/Language/Standard/solcommon.yml new file mode 100644 index 0000000000..4bd1563f74 --- /dev/null +++ b/Resources/Prototypes/Language/Standard/solcommon.yml @@ -0,0 +1,259 @@ +- type: language + id: SolCommon + isVisibleLanguage: true + speech: + color: "#8282fbaa" + obfuscation: + !type:SyllableObfuscation + minSyllables: 1 + maxSyllables: 4 + replacement: + - a + - abe + - ade + - ai + - an + - ana + - ba + - bae + - bai + - bang + - bao + - bei + - ben + - beo + - bi + - bian + - bing + - bo + - bu + - bugu + - bun + - cai + - can + - cao + - cau + - chan + - chen + - cheong + - chiu + - chong + - chyo + - da + - dan + - dao + - de + - deun + - duo + - eon + - eun + - eusi + - feng + - fu + - ga + - gak + - gan + - gang + - gao + - ge + - gei + - gen + - geo + - gil + - go + - gou + - gu + - gua + - gui + - gul + - gun + - guo + - gwi + - ha + - hai + - hal + - han + - hap + - hara + - he + - hego + - hen + - hon + - hoo + - hu + - hua + - hun + - hyeong + - i + - jae + - jeo + - jeon + - ji + - jia + - jian + - jiang + - jie + - jong + - ju + - jue + - juede + - jung + - juzi + - ka + - kang + - kawa + - ke + - keun + - ki + - kin + - ko + - kore + - kou + - ku + - kuda + - kun + - kyu + - lang + - lao + - leng + - leung + - li + - lian + - liang + - lie + - ling + - lizi + - lleo + - long + - lu + - ma + - mah + - me + - mei + - meinu + - men + - meng + - meog + - meoni + - mi + - mian + - min + - mo + - mot + - mu + - mun + - na + - nae + - nai + - nari + - ne + - ni + - nii + - nim + - nin + - nop + - nu + - o + - oba + - oga + - oji + - oka + - ong + - op + - oto + - pa + - pai + - pang + - pin + - ping + - pong + - pu + - pum + - pye + - qi + - qie + - qing + - ra + - rei + - ren + - ri + - ru + - ruan + - sa + - sai + - sama + - san + - sang + - se + - sei + - sen + - seo + - seon + - seong + - shang + - shen + - sheng + - shi + - sho + - shui + - si + - su + - sui + - sum + - sun + - swi + - ta + - tae + - tai + - tame + - tamen + - tan + - te + - tei + - ti + - tian + - to + - ton + - tsu + - ul + - wa + - wan + - wang + - wei + - wo + - xi + - xian + - xiao + - xing + - xiong + - xiu + - xu + - xuan + - xue + - ya + - yan + - yang + - yeong + - yi + - yige + - yin + - ying + - yiqi + - yong + - you + - yu + - yuli + - yuyi + - zai + - zao + - zhan + - zhang + - zhe + - zhen + - zheng + - zhuo + - zi + - zo + - zu + - zun + - zuo diff --git a/Resources/Prototypes/Language/Standard/taucetibasic.yml b/Resources/Prototypes/Language/Standard/taucetibasic.yml new file mode 100644 index 0000000000..f21834f04e --- /dev/null +++ b/Resources/Prototypes/Language/Standard/taucetibasic.yml @@ -0,0 +1,256 @@ +# The "Default Language" other than Universal. +- type: language + id: TauCetiBasic + obfuscation: + !type:SyllableObfuscation + minSyllables: 1 + maxSyllables: 3 + replacement: + - a + - ado + - ago + - aj + - ajn + - al + - alt + - am + - amas + - an + - ang + - ante + - ap + - ard + - arma + - aro + - as + - aur + - aut + - aw + - ba + - bal + - bao + - be + - beau + - bel + - bi + - bit + - blu + - bo + - bod + - boj + - bojn + - bu + - but + - ca + - caj + - ce + - cer + - chun + - ci + - cion + - coj + - cor + - da + - daj + - dan + - de + - den + - dis + - do + - dor + - dorm + - eco + - ego + - ek + - eks + - en + - ero + - es + - est + - et + - eve + - fa + - fe + - fel + - fla + - foj + - fra + - fraz + - fros + - ful + - fut + - ga + - gan + - gar + - gi + - gis + - go + - gran + - ha + - han + - hav + - hom + - hong + - hu + - hum + - hushi + - ia + - iaj + - ica + - id + - idon + - il + - in + - ing + - io + - is + - iton + - iza + - ja + - ji + - jirou + - joj + - ka + - kaj + - kajo + - kan + - ke + - ket + - ki + - kna + - krio + - ku + - kui + - kuk + - kun + - kur + - la + - laca + - leng + - les + - li + - liao + - lib + - ling + - lis + - lo + - lon + - long + - lu + - lud + - ma + - mal + - man + - me + - mego + - mero + - mi + - mia + - min + - mo + - moj + - mola + - mon + - mul + - ne + - ner + - ni + - nio + - nu + - of + - oj + - om + - ou + - pe + - pi + - plan + - pli + - po + - por + - post + - pre + - prin + - pru + - pu + - pur + - qiu + - que + - ra + - ras + - re + - ri + - rig + - ril + - ro + - roj + - ron + - roso + - rou + - ru + - sa + - san + - sci + - sek + - shi + - shiia + - shiue + - shiwu + - shu + - shui + - si + - siaj + - sku + - so + - som + - sti + - str + - stre + - su + - suno + - ta + - tan + - tas + - te + - tel + - tem + - the + - ti + - tian + - tita + - tiu + - to + - toj + - ton + - tran + - tre + - tri + - trin + - tro + - trus + - un + - undo + - uno + - uz + - va + - var + - varm + - vas + - ve + - vek + - ven + - ves + - vi + - via + - vin + - vino + - vint + - vir + - von + - vu + - whe + - wu + - yong + - zem + - zo + - zoj + - zon diff --git a/Resources/Prototypes/Language/Standard/tradeband.yml b/Resources/Prototypes/Language/Standard/tradeband.yml new file mode 100644 index 0000000000..53613b46b2 --- /dev/null +++ b/Resources/Prototypes/Language/Standard/tradeband.yml @@ -0,0 +1,259 @@ +- type: language + id: Tradeband + isVisibleLanguage: true + speech: + color: "#597d35" + obfuscation: + !type:SyllableObfuscation + minSyllables: 1 + maxSyllables: 3 + replacement: + - a + - acc + - ai + - al + - ali + - am + - ama + - ami + - amo + - an + - ang + - arme + - ave + - ba + - bai + - bar + - bat + - bi + - blie + - bris + - ca + - can + - cant + - car + - care + - ce + - ci + - cis + - cit + - cla + - co + - cul + - cur + - curt + - da + - dam + - dans + - de + - di + - dier + - dim + - dins + - dorm + - du + - duro + - e + - eaux + - ec + - ecto + - ees + - ego + - el + - en + - ent + - er + - ere + - eres + - eri + - ero + - es + - et + - ex + - far + - fi + - fic + - fine + - fol + - foll + - fri + - fro + - gen + - gil + - go + - gran + - hab + - ho + - huc + - ia + - iam + - ibus + - idor + - ie + - iens + - ier + - ieur + - iis + - il + - in + - ine + - int + - ir + - is + - ise + - it + - itt + - jar + - je + - jo + - jor + - la + - lar + - lav + - le + - lees + - ler + - les + - li + - lib + - lie + - lo + - lu + - ma + - man + - manu + - mar + - mari + - mas + - me + - mea + - mee + - mejo + - men + - mes + - meum + - meus + - mi + - mier + - min + - mine + - mit + - mo + - moi + - mon + - mons + - mors + - mou + - mul + - na + - nam + - ne + - nee + - nent + - nes + - ni + - nit + - nom + - nu + - num + - o + - oc + - occ + - oja + - om + - omni + - or + - ori + - oro + - os + - ou + - oub + - pa + - par + - pars + - pas + - plu + - pluv + - po + - pol + - pos + - pou + - pous + - pre + - pu + - pug + - pus + - que + - qui + - re + - ri + - ric + - riga + - rito + - ro + - rom + - sa + - sal + - se + - ser + - sers + - ses + - sim + - sion + - so + - sol + - som + - sou + - sper + - sse + - ste + - su + - suis + - sul + - sur + - ta + - tar + - te + - teau + - tem + - temp + - ten + - tene + - tes + - ti + - tibus + - tien + - tion + - to + - tol + - ton + - tons + - tout + - tra + - trai + - tre + - trou + - tuo + - tus + - tut + - ues + - ui + - ul + - um + - un + - upa + - us + - ut + - ux + - va + - vail + - ve + - ven + - veni + - vi + - viam + - vie + - vo + - xus + - za + - zio diff --git a/Resources/Prototypes/Language/animal.yml b/Resources/Prototypes/Language/animal.yml new file mode 100644 index 0000000000..82f4d62749 --- /dev/null +++ b/Resources/Prototypes/Language/animal.yml @@ -0,0 +1,199 @@ +# Languages spoken by various critters. +- type: language + id: Cat + obfuscation: + !type:SyllableObfuscation + minSyllables: 1 + maxSyllables: 2 + replacement: + - murr + - meow + - purr + - mrow + +- type: language + id: Dog + obfuscation: + !type:SyllableObfuscation + minSyllables: 1 + maxSyllables: 2 + replacement: + - woof + - bark + - ruff + - bork + - raff + - garr + +- type: language + id: Fox + obfuscation: + !type:SyllableObfuscation + minSyllables: 1 + maxSyllables: 2 + replacement: + - ruff + - raff + - garr + - yip + - yap + - myah + +- type: language + id: Xeno + obfuscation: + !type:SyllableObfuscation + minSyllables: 1 + maxSyllables: 8 # I was crazy once + replacement: + - s + - S + +- type: language + id: Monkey + obfuscation: + !type:SyllableObfuscation + minSyllables: 1 + maxSyllables: 8 # They locked me in a room... + replacement: + - o + - k + +- type: language + id: Mouse + obfuscation: + !type:SyllableObfuscation + minSyllables: 2 + maxSyllables: 3 + replacement: + - squ + - eak + - pi + - ep + - chuu + - ee + - fwi + - he + +- type: language + id: Chicken + obfuscation: + !type:SyllableObfuscation + minSyllables: 1 + maxSyllables: 3 + replacement: + - co + - coo + - ot + +- type: language + id: Duck + obfuscation: + !type:SyllableObfuscation + minSyllables: 1 + maxSyllables: 3 + replacement: + - qu + - ack + - quack + +- type: language + id: Cow + obfuscation: + !type:SyllableObfuscation + minSyllables: 1 + maxSyllables: 3 + replacement: + - moo + - mooo + +- type: language + id: Sheep + obfuscation: + !type:SyllableObfuscation + minSyllables: 1 + maxSyllables: 3 + replacement: + - ba + - baa + - aa + +- type: language + id: Kangaroo + obfuscation: + !type:SyllableObfuscation + minSyllables: 1 + maxSyllables: 3 + replacement: + - shre + - ack + - chuu + - choo + +- type: language + id: Pig + obfuscation: + !type:SyllableObfuscation + minSyllables: 1 + maxSyllables: 3 + replacement: + - oink # Please someone come up with something better + +- type: language + id: Crab + obfuscation: + !type:SyllableObfuscation + minSyllables: 1 + maxSyllables: 3 + replacement: + - click + - clack + - ti + - pi + - tap + - cli + - ick + +- type: language + id: Kobold + obfuscation: + !type:SyllableObfuscation + minSyllables: 2 + maxSyllables: 4 + replacement: + - yip + - yap + - gar + - grr + - ar + - scre + - et + - gronk + - hiss + - ss + - ee + +- type: language + id: Hissing + obfuscation: + !type:SyllableObfuscation + minSyllables: 2 + maxSyllables: 4 + replacement: + - hss + - iss + - ss + - is + +- type: language + id: Penguin + obfuscation: + !type:SyllableObfuscation + minSyllables: 2 + maxSyllables: 3 + replacement: # I'm out of ideas + - pen + - peng + - won + - wonk + - wong diff --git a/Resources/Prototypes/Language/genericlanguages.yml b/Resources/Prototypes/Language/genericlanguages.yml new file mode 100644 index 0000000000..90cb874ee6 --- /dev/null +++ b/Resources/Prototypes/Language/genericlanguages.yml @@ -0,0 +1,46 @@ +# The universal language. This is technically used as a fallback for simulating the pre-languages +# style of Chat, and is not normally accessible by players. It is however used by characters +# with the Xenoglossy psionic power, as well as Ghosts, and AGhosts. +- type: language + id: Universal + obfuscation: + !type:ReplacementObfuscation + replacement: + - "*incomprehensible*" # Never actually used + +# Used by Robots. +# TODO: Replace this with much better languages. Yes, robots can have languages. +- type: language + id: RobotTalk + isVisibleLanguage: true + speech: + fontId: Monospace + obfuscation: + !type:SyllableObfuscation + minSyllables: 1 + maxSyllables: 10 # Crazy + replacement: + - 0 + - 1 + +# Example of a sign language. Used by the Sign Language trait. +- type: language + id: Sign + speech: + allowRadio: false + requireSpeech: false + color: "#dddddd" + messageWrapOverrides: + Speak: chat-sign-language-message-wrap + Whisper: chat-sign-language-whisper-wrap + speechVerbOverrides: + - chat-speech-verb-sign-1 + - chat-speech-verb-sign-2 + - chat-speech-verb-sign-3 + obfuscation: + !type:ReplacementObfuscation + replacement: + - something + - a cryptic message + - a signal + - a message diff --git a/Resources/Prototypes/Language/languages.yml b/Resources/Prototypes/Language/languages.yml deleted file mode 100644 index 65af93e02d..0000000000 --- a/Resources/Prototypes/Language/languages.yml +++ /dev/null @@ -1,614 +0,0 @@ -# The universal language, assumed if the entity has a UniversalLanguageSpeakerComponent. -# Do not use otherwise. Making an entity explicitly understand/speak this language will NOT have the desired effect. -- type: language - id: Universal - obfuscation: - !type:ReplacementObfuscation - replacement: - - "*incomprehensible*" # Never actually used - -# The common galactic tongue. -- type: language - id: GalacticCommon - obfuscation: - !type:SyllableObfuscation - minSyllables: 1 - maxSyllables: 3 - replacement: - - blah - - blah - - blah - - dingle-doingle - - dingle - - dangle - - jibber-jabber - - jubber - - bleh - - zippity - - zoop - - wibble - - wobble - - wiggle - - yada - - meh - - neh - - nah - - wah - -# Spoken by slimes. -- type: language - id: Bubblish - speech: - color: "#00a3e2dd" - fontId: RubikBubbles - obfuscation: - !type:SyllableObfuscation - minSyllables: 1 - maxSyllables: 3 - replacement: - - blob - - plop - - pop - - bop - - boop - -# Spoken by moths. -- type: language - id: Moffic - speech: - color: "#c7df2edd" - fontId: Copperplate - obfuscation: - !type:SyllableObfuscation - minSyllables: 2 # Replacements are really short - maxSyllables: 4 - replacement: - - år - - i - - går - - sek - - mo - - ff - - ok - - gj - - ø - - gå - - la - - le - - lit - - ygg - - van - - dår - - næ - - møt - - idd - - hvo - - ja - - på - - han - - så - - ån - - det - - att - - nå - - gö - - bra - - int - - tyc - - om - - när - - två - - må - - dag - - sjä - - vii - - vuo - - eil - - tun - - käyt - - teh - - vä - - hei - - huo - - suo - - ää - - ten - - ja - - heu - - stu - - uhr - - kön - - we - - hön - -# Spoken by dionas. -- type: language - id: RootSpeak - speech: - color: "#ce5e14dd" - fontId: Noganas - obfuscation: - !type:SyllableObfuscation - minSyllables: 1 - maxSyllables: 5 - replacement: - - hs - - zt - - kr - - st - - sh - -# A mess of broken Japanese, spoken by Felinds and Oni -- type: language - id: Nekomimetic - speech: - color: "#df57aaee" - fontId: Manga - obfuscation: - !type:SyllableObfuscation - minSyllables: 1 - maxSyllables: 3 # May be too long even, we'll see. - replacement: - - neko - - nyan - - mimi - - moe - - mofu - - fuwa - - kyaa - - kawaii - - poka - - munya - - puni - - munyu - - ufufu - - icha - - doki - - kyun - - kusu - - nya - - nyaa - - desu - - kis - - ama - - chuu - - baka - - hewo - - boop - - gato - - kit - - sune - - yori - - sou - - baka - - chan - - san - - kun - - mahou - - yatta - - suki - - usagi - - domo - - ori - - uwa - - zaazaa - - shiku - - puru - - ira - - heto - - etto - -# Spoken by the Lizard race. -- type: language - id: Draconic - speech: - color: "#2aca2add" - obfuscation: - !type:SyllableObfuscation - minSyllables: 2 - maxSyllables: 4 - replacement: - - za - - az - - ze - - ez - - zi - - iz - - zo - - oz - - zu - - uz - - zs - - sz - - ha - - ah - - he - - eh - - hi - - ih - - ho - - oh - - hu - - uh - - hs - - sh - - la - - al - - le - - el - - li - - il - - lo - - ol - - lu - - ul - - ls - - sl - - ka - - ak - - ke - - ek - - ki - - ik - - ko - - ok - - ku - - uk - - ks - - sk - - sa - - as - - se - - es - - si - - is - - so - - os - - su - - us - - ss - - ss - - ra - - ar - - re - - er - - ri - - ir - - ro - - or - - ru - - ur - - rs - - sr - - a - - a - - e - - e - - i - - i - - o - - o - - u - - u - - s - - s - -# Spoken by the Vulpkanin race. -- type: language - id: Canilunzt - speech: - color: "#d69b3dcc" - obfuscation: - !type:SyllableObfuscation - minSyllables: 1 - maxSyllables: 4 - replacement: - - rur - - ya - - cen - - rawr - - bar - - kuk - - tek - - qat - - uk - - wu - - vuh - - tah - - tch - - schz - - auch - - ist - - ein - - entch - - zwichs - - tut - - mir - - wo - - bis - - es - - vor - - nic - - gro - - enem - - zandt - - tzch - - noch - - hel - - ischt - - far - - wa - - baram - - iereng - - tech - - lach - - sam - - mak - - lich - - gen - - or - - ag - - eck - - gec - - stag - - onn - - bin - - ket - - jarl - - vulf - - einech - - cresthz - - azunein - - ghzth - -# The common language of the Sol system. -- type: language - id: SolCommon - speech: - color: "#8282fbaa" - obfuscation: - !type:SyllableObfuscation - minSyllables: 1 - maxSyllables: 4 - replacement: - - tao - - shi - - tzu - - yi - - com - - be - - is - - i - - op - - vi - - ed - - lec - - mo - - cle - - te - - dis - - e - -- type: language - id: RobotTalk - speech: - fontId: Monospace - obfuscation: - !type:SyllableObfuscation - minSyllables: 1 - maxSyllables: 10 # Crazy - replacement: - - 0 - - 1 - -# Languages spoken by various critters. -- type: language - id: Cat - obfuscation: - !type:SyllableObfuscation - minSyllables: 1 - maxSyllables: 2 - replacement: - - murr - - meow - - purr - - mrow - -- type: language - id: Dog - obfuscation: - !type:SyllableObfuscation - minSyllables: 1 - maxSyllables: 2 - replacement: - - woof - - bark - - ruff - - bork - - raff - - garr - -- type: language - id: Fox - obfuscation: - !type:SyllableObfuscation - minSyllables: 1 - maxSyllables: 2 - replacement: - - ruff - - raff - - garr - - yip - - yap - - myah - -- type: language - id: Xeno - obfuscation: - !type:SyllableObfuscation - minSyllables: 1 - maxSyllables: 8 # I was crazy once - replacement: - - s - - S - -- type: language - id: Monkey - obfuscation: - !type:SyllableObfuscation - minSyllables: 1 - maxSyllables: 8 # They locked me in a room... - replacement: - - o - - k - -- type: language - id: Mouse - obfuscation: - !type:SyllableObfuscation - minSyllables: 2 - maxSyllables: 3 - replacement: - - squ - - eak - - pi - - ep - - chuu - - ee - - fwi - - he - -- type: language - id: Chicken - obfuscation: - !type:SyllableObfuscation - minSyllables: 1 - maxSyllables: 3 - replacement: - - co - - coo - - ot - -- type: language - id: Duck - obfuscation: - !type:SyllableObfuscation - minSyllables: 1 - maxSyllables: 3 - replacement: - - qu - - ack - - quack - -- type: language - id: Cow - obfuscation: - !type:SyllableObfuscation - minSyllables: 1 - maxSyllables: 3 - replacement: - - moo - - mooo - -- type: language - id: Sheep - obfuscation: - !type:SyllableObfuscation - minSyllables: 1 - maxSyllables: 3 - replacement: - - ba - - baa - - aa - -- type: language - id: Kangaroo - obfuscation: - !type:SyllableObfuscation - minSyllables: 1 - maxSyllables: 3 - replacement: - - shre - - ack - - chuu - - choo - -- type: language - id: Pig - obfuscation: - !type:SyllableObfuscation - minSyllables: 1 - maxSyllables: 3 - replacement: - - oink # Please someone come up with something better - -- type: language - id: Crab - obfuscation: - !type:SyllableObfuscation - minSyllables: 1 - maxSyllables: 3 - replacement: - - click - - clack - - ti - - pi - - tap - - cli - - ick - -- type: language - id: Kobold - obfuscation: - !type:SyllableObfuscation - minSyllables: 2 - maxSyllables: 4 - replacement: - - yip - - yap - - gar - - grr - - ar - - scre - - et - - gronk - - hiss - - ss - - ee - -- type: language - id: Hissing - obfuscation: - !type:SyllableObfuscation - minSyllables: 2 - maxSyllables: 4 - replacement: - - hss - - iss - - ss - - is - -# Example of a sign language. Not currently used anyhow. -- type: language - id: Sign - speech: - allowRadio: false - requireSpeech: false - color: "#dddddd" - messageWrapOverrides: - Speak: chat-sign-language-message-wrap - Whisper: chat-sign-language-whisper-wrap - speechVerbOverrides: - - chat-speech-verb-sign-1 - - chat-speech-verb-sign-2 - - chat-speech-verb-sign-3 - obfuscation: - !type:ReplacementObfuscation - replacement: - - something - - a cryptic message to you - - a signal to you - - a message - - a rude expression to you - - a sad expression to you - - a happy expression to you diff --git a/Resources/Prototypes/Loadouts/Categories/categories.yml b/Resources/Prototypes/Loadouts/Categories/categories.yml new file mode 100644 index 0000000000..bfda095b19 --- /dev/null +++ b/Resources/Prototypes/Loadouts/Categories/categories.yml @@ -0,0 +1,113 @@ +# Alphabetically ordered, except for Uncategorized since it is always first +# AUncategorized is always first in subcategories to stay consistent with the root Uncategorized +# AUncategorized is not a spelling mistake, and might have more As added as needed + +- type: loadoutCategory + id: Uncategorized + root: true + +- type: loadoutCategory + id: Backpacks + root: true + +- type: loadoutCategory + id: Eyes + root: true + +- type: loadoutCategory + id: Hands + root: true + +- type: loadoutCategory + id: Head + root: true + +- type: loadoutCategory + id: Items + root: true + +- type: loadoutCategory + id: Jobs + root: true + subCategories: + - JobsAUncategorized + - JobsCargo + - JobsCommand + - JobsEngineering + - JobsMedical + - JobsScience + - JobsSecurity + - JobsService + +- type: loadoutCategory + id: JobsAUncategorized + +- type: loadoutCategory + id: JobsCargo + +- type: loadoutCategory + id: JobsCommand + +- type: loadoutCategory + id: JobsEngineering + +- type: loadoutCategory + id: JobsMedical + +- type: loadoutCategory + id: JobsScience + +- type: loadoutCategory + id: JobsSecurity + +- type: loadoutCategory + id: JobsService + subCategories: + - JobsServiceUncategorized + - JobsServiceBartender + - JobsServiceBotanist + - JobsServiceChef + - JobsServiceJanitor + - JobsServiceMusician + +- type: loadoutCategory + id: JobsServiceUncategorized + +- type: loadoutCategory + id: JobsServiceBartender + +- type: loadoutCategory + id: JobsServiceBotanist + +- type: loadoutCategory + id: JobsServiceChef + +- type: loadoutCategory + id: JobsServiceJanitor + +- type: loadoutCategory + id: JobsServiceMusician + +- type: loadoutCategory + id: Mask + root: true + +- type: loadoutCategory + id: Neck + root: true + +- type: loadoutCategory + id: Outer + root: true + +- type: loadoutCategory + id: Shoes + root: true + +- type: loadoutCategory + id: Species + root: true + +- type: loadoutCategory + id: Uniform + root: true diff --git a/Resources/Prototypes/Loadouts/Jobs/Heads/captain.yml b/Resources/Prototypes/Loadouts/Jobs/Heads/captain.yml index d77dabf557..47e4310fdf 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Heads/captain.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Heads/captain.yml @@ -1,7 +1,7 @@ - type: loadout id: LoadoutCommandCapNeckMantle - category: Jobs - cost: 2 + category: JobsCommand + cost: 0 exclusive: true requirements: - !type:CharacterJobRequirement @@ -12,8 +12,8 @@ - type: loadout id: LoadoutCommandCapNeckCloak - category: Jobs - cost: 2 + category: JobsCommand + cost: 0 exclusive: true requirements: - !type:CharacterJobRequirement @@ -24,8 +24,8 @@ - type: loadout id: LoadoutCommandCapNeckCloakFormal - category: Jobs - cost: 2 + category: JobsCommand + cost: 0 exclusive: true requirements: - !type:CharacterJobRequirement @@ -36,8 +36,8 @@ - type: loadout id: LoadoutCommandCapJumpsuitFormal - category: Jobs - cost: 3 + category: JobsCommand + cost: 0 exclusive: true requirements: - !type:CharacterJobRequirement @@ -48,8 +48,8 @@ - type: loadout id: LoadoutCommandCapJumpskirtFormal - category: Jobs - cost: 3 + category: JobsCommand + cost: 0 exclusive: true requirements: - !type:CharacterJobRequirement @@ -60,8 +60,8 @@ - type: loadout id: LoadoutCommandCapOuterWinter - category: Jobs - cost: 2 + category: JobsCommand + cost: 1 requirements: - !type:CharacterJobRequirement jobs: @@ -71,8 +71,8 @@ - type: loadout id: LoadoutCommandCapGloves - category: Jobs - cost: 1 + category: JobsCommand + cost: 0 requirements: - !type:CharacterJobRequirement jobs: @@ -82,8 +82,8 @@ - type: loadout id: LoadoutCommandCapHat - category: Jobs - cost: 1 + category: JobsCommand + cost: 0 requirements: - !type:CharacterJobRequirement jobs: @@ -93,8 +93,8 @@ - type: loadout id: LoadoutCommandCapHatCapcap - category: Jobs - cost: 1 + category: JobsCommand + cost: 0 requirements: - !type:CharacterJobRequirement jobs: @@ -104,8 +104,8 @@ - type: loadout id: LoadoutCommandCapHatBeret - category: Jobs - cost: 1 + category: JobsCommand + cost: 0 requirements: - !type:CharacterJobRequirement jobs: @@ -115,8 +115,8 @@ - type: loadout id: LoadoutCommandCapMaskGas - category: Jobs - cost: 1 + category: JobsCommand + cost: 0 requirements: - !type:CharacterJobRequirement jobs: @@ -126,15 +126,10 @@ - type: loadout id: LoadoutCommandCapShoesBootsWinter - category: Jobs - cost: 1 + category: JobsCommand + cost: 0 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Diona - - Harpy - !type:CharacterJobRequirement jobs: - Captain @@ -143,7 +138,7 @@ - type: loadout id: LoadoutCommandCapItemDrinkFlask - category: Jobs + category: JobsCommand cost: 1 requirements: - !type:CharacterJobRequirement diff --git a/Resources/Prototypes/Loadouts/Jobs/Heads/chiefEngineer.yml b/Resources/Prototypes/Loadouts/Jobs/Heads/chiefEngineer.yml index 4de22bc959..bfddc6e383 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Heads/chiefEngineer.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Heads/chiefEngineer.yml @@ -1,7 +1,7 @@ - type: loadout id: LoadoutCommandCENeckMantle - category: Jobs - cost: 2 + category: JobsCommand + cost: 0 exclusive: true requirements: - !type:CharacterJobRequirement @@ -12,8 +12,8 @@ - type: loadout id: LoadoutCommandCENeckCloak - category: Jobs - cost: 2 + category: JobsCommand + cost: 0 exclusive: true requirements: - !type:CharacterJobRequirement @@ -24,8 +24,8 @@ - type: loadout id: LoadoutCommandCEOuterWinter - category: Jobs - cost: 2 + category: JobsCommand + cost: 1 requirements: - !type:CharacterJobRequirement jobs: @@ -35,15 +35,10 @@ - type: loadout id: LoadoutCommandCEShoesBootsWinter - category: Jobs - cost: 1 + category: JobsCommand + cost: 0 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Diona - - Harpy - !type:CharacterJobRequirement jobs: - ChiefEngineer diff --git a/Resources/Prototypes/Loadouts/Jobs/Heads/chiefMedicalOfficer.yml b/Resources/Prototypes/Loadouts/Jobs/Heads/chiefMedicalOfficer.yml index 163bad2926..0c46af7013 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Heads/chiefMedicalOfficer.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Heads/chiefMedicalOfficer.yml @@ -1,7 +1,7 @@ - type: loadout id: LoadoutCommandCMONeckMantle - category: Jobs - cost: 2 + category: JobsCommand + cost: 0 exclusive: true requirements: - !type:CharacterJobRequirement @@ -12,8 +12,8 @@ - type: loadout id: LoadoutCommandCMONeckCloak - category: Jobs - cost: 2 + category: JobsCommand + cost: 0 exclusive: true requirements: - !type:CharacterJobRequirement @@ -24,8 +24,8 @@ - type: loadout id: LoadoutCommandCMOOuterWinter - category: Jobs - cost: 2 + category: JobsCommand + cost: 1 requirements: - !type:CharacterJobRequirement jobs: @@ -35,8 +35,8 @@ - type: loadout id: LoadoutCommandCMOOuterLab - category: Jobs - cost: 1 + category: JobsCommand + cost: 0 requirements: - !type:CharacterJobRequirement jobs: @@ -46,8 +46,8 @@ - type: loadout id: LoadoutCommandCMOHatBeret - category: Jobs - cost: 1 + category: JobsCommand + cost: 0 requirements: - !type:CharacterJobRequirement jobs: @@ -57,15 +57,10 @@ - type: loadout id: LoadoutCommandCMOShoesBootsWinter - category: Jobs - cost: 1 + category: JobsCommand + cost: 0 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Diona - - Harpy - !type:CharacterJobRequirement jobs: - ChiefMedicalOfficer diff --git a/Resources/Prototypes/Loadouts/Jobs/Heads/command.yml b/Resources/Prototypes/Loadouts/Jobs/Heads/command.yml index c8c98b5eb4..ccc791460b 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Heads/command.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Heads/command.yml @@ -1,6 +1,6 @@ - type: loadout id: LoadoutCommandGlovesInspection - category: Jobs + category: JobsCommand cost: 1 exclusive: true requirements: @@ -10,3 +10,15 @@ - Captain items: - ClothingHandsGlovesInspection + +- type: loadout + id: LoadoutCommandTelescopicBaton + category: JobsCommand + cost: 3 + exclusive: true + requirements: + - !type:CharacterDepartmentRequirement + departments: + - Command + items: + - TelescopicBaton diff --git a/Resources/Prototypes/Loadouts/Jobs/Heads/headOfPersonnel.yml b/Resources/Prototypes/Loadouts/Jobs/Heads/headOfPersonnel.yml index e6bc6ada04..7bb265dd7b 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Heads/headOfPersonnel.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Heads/headOfPersonnel.yml @@ -1,7 +1,7 @@ - type: loadout id: LoadoutCommandHOPNeckMantle - category: Jobs - cost: 2 + category: JobsCommand + cost: 0 exclusive: true requirements: - !type:CharacterJobRequirement @@ -12,8 +12,8 @@ - type: loadout id: LoadoutCommandHOPNeckCloak - category: Jobs - cost: 2 + category: JobsCommand + cost: 0 exclusive: true requirements: - !type:CharacterJobRequirement @@ -24,8 +24,8 @@ - type: loadout id: LoadoutCommandHOPJumpsuitTurtleneckBoatswain - category: Jobs - cost: 2 + category: JobsCommand + cost: 0 exclusive: true requirements: - !type:CharacterJobRequirement @@ -36,8 +36,8 @@ - type: loadout id: LoadoutCommandHOPJumpsuitMess - category: Jobs - cost: 2 + category: JobsCommand + cost: 0 exclusive: true requirements: - !type:CharacterJobRequirement @@ -48,8 +48,8 @@ - type: loadout id: LoadoutCommandHOPJumpskirtMess - category: Jobs - cost: 2 + category: JobsCommand + cost: 0 exclusive: true requirements: - !type:CharacterJobRequirement @@ -60,8 +60,8 @@ - type: loadout id: LoadoutcommandHOPOuterCoatFormal - category: Jobs - cost: 2 + category: JobsCommand + cost: 0 requirements: - !type:CharacterJobRequirement jobs: @@ -71,8 +71,8 @@ - type: loadout id: LoadoutCommandHOPBackIan - category: Jobs - cost: 4 + category: JobsCommand + cost: 2 requirements: - !type:CharacterJobRequirement jobs: @@ -82,8 +82,8 @@ - type: loadout id: LoadoutCommandHOPHatCap - category: Jobs - cost: 1 + category: JobsCommand + cost: 0 requirements: - !type:CharacterJobRequirement jobs: @@ -93,15 +93,10 @@ - type: loadout id: LoadoutCommandHOPShoesBootsWinter - category: Jobs - cost: 1 + category: JobsCommand + cost: 0 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Diona - - Harpy - !type:CharacterJobRequirement jobs: - HeadOfPersonnel @@ -110,8 +105,8 @@ - type: loadout id: LoadoutCommandHOPBedsheetIan - category: Jobs - cost: 2 + category: JobsCommand + cost: 0 exclusive: true requirements: - !type:CharacterJobRequirement diff --git a/Resources/Prototypes/Loadouts/Jobs/Heads/headOfSecurity.yml b/Resources/Prototypes/Loadouts/Jobs/Heads/headOfSecurity.yml index 7be380d747..bd2a03f214 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Heads/headOfSecurity.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Heads/headOfSecurity.yml @@ -1,7 +1,7 @@ - type: loadout id: LoadoutCommandHOSNeckMantle - category: Jobs - cost: 2 + category: JobsCommand + cost: 0 exclusive: true requirements: - !type:CharacterJobRequirement @@ -12,8 +12,8 @@ - type: loadout id: LoadoutCommandHOSNeckCloak - category: Jobs - cost: 2 + category: JobsCommand + cost: 0 exclusive: true requirements: - !type:CharacterJobRequirement @@ -24,8 +24,8 @@ - type: loadout id: LoadoutCommandHOSJumpsuitAlt - category: Jobs - cost: 2 + category: JobsCommand + cost: 0 exclusive: true requirements: - !type:CharacterJobRequirement @@ -36,8 +36,8 @@ - type: loadout id: LoadoutCommandHOSJumpsuitBlue - category: Jobs - cost: 2 + category: JobsCommand + cost: 0 exclusive: true requirements: - !type:CharacterJobRequirement @@ -48,8 +48,8 @@ - type: loadout id: LoadoutCommandHOSJumpsuitGrey - category: Jobs - cost: 2 + category: JobsCommand + cost: 0 exclusive: true requirements: - !type:CharacterJobRequirement @@ -60,8 +60,8 @@ - type: loadout id: LoadoutCommandHOSJumpsuitParade - category: Jobs - cost: 3 + category: JobsCommand + cost: 0 exclusive: true requirements: - !type:CharacterJobRequirement @@ -72,8 +72,8 @@ - type: loadout id: LoadoutCommandHOSJumpsuitFormal - category: Jobs - cost: 3 + category: JobsCommand + cost: 0 exclusive: true requirements: - !type:CharacterJobRequirement @@ -84,8 +84,8 @@ - type: loadout id: LoadoutCommandHOSJumpskirtAlt - category: Jobs - cost: 2 + category: JobsCommand + cost: 0 exclusive: true requirements: - !type:CharacterJobRequirement @@ -96,8 +96,8 @@ - type: loadout id: LoadoutCommandHOSJumpskirtParade - category: Jobs - cost: 3 + category: JobsCommand + cost: 0 exclusive: true requirements: - !type:CharacterJobRequirement @@ -108,8 +108,8 @@ - type: loadout id: LoadoutCommandHOSJumpskirtFormal - category: Jobs - cost: 3 + category: JobsCommand + cost: 0 exclusive: true requirements: - !type:CharacterJobRequirement @@ -120,8 +120,8 @@ - type: loadout id: LoadoutCommandHOSOuterWinter - category: Jobs - cost: 2 + category: JobsCommand + cost: 1 requirements: - !type:CharacterJobRequirement jobs: @@ -131,8 +131,8 @@ - type: loadout id: LoadoutCommandHOSOuterTrench - category: Jobs - cost: 2 + category: JobsCommand + cost: 1 requirements: - !type:CharacterJobRequirement jobs: @@ -142,8 +142,8 @@ - type: loadout id: LoadoutCommandHOSHatBeret - category: Jobs - cost: 1 + category: JobsCommand + cost: 0 requirements: - !type:CharacterJobRequirement jobs: @@ -153,8 +153,8 @@ - type: loadout id: LoadoutCommandHOSHatHoshat - category: Jobs - cost: 1 + category: JobsCommand + cost: 0 requirements: - !type:CharacterJobRequirement jobs: @@ -164,17 +164,106 @@ - type: loadout id: LoadoutCommandHOSShoesBootsWinter - category: Jobs - cost: 1 + category: JobsCommand + cost: 0 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Diona - - Harpy - !type:CharacterJobRequirement jobs: - HeadOfSecurity items: - ClothingShoesBootsWinterHeadOfSecurity + +# Head of Security Weapon Selection +# Most of these mirror the unique weapons that were previously map-specific items placed in the HoS Office. +# Or are weapons that fit a similar theme of "Rare weapons not normally seen by Security" +- type: loadout + id: LoadoutCommandHoSPulsePistol + category: JobsCommand + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHoSWeapon + - !type:CharacterJobRequirement + jobs: + - HeadOfSecurity + items: + - WeaponPulsePistolHoS + +- type: loadout + id: LoadoutCommandHoSWt550 + category: JobsCommand + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHoSWeapon + - !type:CharacterJobRequirement + jobs: + - HeadOfSecurity + items: + - WeaponSubMachineGunWt550HoS + +- type: loadout + id: LoadoutCommandHoSKatanaSheath + category: JobsCommand + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHoSWeapon + - !type:CharacterJobRequirement + jobs: + - HeadOfSecurity + items: + - ClothingBeltKatanaSheathFilledHoS + +- type: loadout + id: LoadoutCommandHoSC20r + category: JobsCommand + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHoSWeapon + - !type:CharacterJobRequirement + jobs: + - HeadOfSecurity + items: + - WeaponSubMachineGunC20rHoS + +- type: loadout + id: LoadoutCommandHoSBulldog + category: JobsCommand + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHoSWeapon + - !type:CharacterJobRequirement + jobs: + - HeadOfSecurity + items: + - WeaponShotgunBulldogHoS + +- type: loadout + id: LoadoutCommandHoSEnergySword + category: JobsCommand + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHoSWeapon + - !type:CharacterJobRequirement + jobs: + - HeadOfSecurity + items: + - EnergySwordHoS + +- type: loadout + id: LoadoutCommandHoSEnergyGun + category: JobsCommand + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHoSWeapon + - !type:CharacterJobRequirement + jobs: + - HeadOfSecurity + items: + - WeaponEnergyGunMultiphase diff --git a/Resources/Prototypes/Loadouts/Jobs/Heads/quarterMaster.yml b/Resources/Prototypes/Loadouts/Jobs/Heads/quarterMaster.yml index 15adc7d44e..0dd45eb3f5 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Heads/quarterMaster.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Heads/quarterMaster.yml @@ -1,7 +1,7 @@ # What? This isn't a thing?? :( # - type: loadout # id: LoadoutCommandQMNeckMantle -# category: Jobs +# category: JobsCommand # cost: 2 # exclusive: true # requirements: @@ -13,8 +13,8 @@ - type: loadout id: LoadoutCommandQMNeckCloak - category: Jobs - cost: 2 + category: JobsCommand + cost: 0 exclusive: true requirements: - !type:CharacterJobRequirement @@ -25,8 +25,8 @@ - type: loadout id: LoadoutCommandQMUniformTurtleneck - category: Jobs - cost: 2 + category: JobsCommand + cost: 0 exclusive: true requirements: - !type:CharacterJobRequirement @@ -37,8 +37,8 @@ - type: loadout id: LoadoutCommandQMUniformTurtleneckSkirt - category: Jobs - cost: 2 + category: JobsCommand + cost: 0 exclusive: true requirements: - !type:CharacterJobRequirement @@ -49,8 +49,8 @@ - type: loadout id: LoadoutCommandQMHeadSoft - category: Jobs - cost: 1 + category: JobsCommand + cost: 0 requirements: - !type:CharacterJobRequirement jobs: @@ -60,15 +60,10 @@ - type: loadout id: LoadoutCommandQMShoesBootsWinter - category: Jobs - cost: 1 + category: JobsCommand + cost: 0 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Diona - - Harpy - !type:CharacterJobRequirement jobs: - Quartermaster diff --git a/Resources/Prototypes/Loadouts/Jobs/Heads/researchDirector.yml b/Resources/Prototypes/Loadouts/Jobs/Heads/researchDirector.yml index 5d06e54efa..0ec43fc3a7 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Heads/researchDirector.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Heads/researchDirector.yml @@ -1,60 +1,99 @@ +# Outer + - type: loadout - id: LoadoutCommandRDNeckMantle - category: Jobs - cost: 2 + id: LoadoutCommandRDOuterWinter + category: JobsCommand + cost: 1 + requirements: + - !type:CharacterJobRequirement + jobs: + - ResearchDirector + items: + - ClothingOuterWinterRD + +- type: loadout + id: LoadoutCommandRDOuterMysta + category: JobsCommand + cost: 0 + requirements: + - !type:CharacterJobRequirement + jobs: + - ResearchDirector + items: + - ClothingOuterCoatRndMysta + +# Head + +- type: loadout + id: LoadoutCommandRDHeadHatBeretMysta + category: JobsCommand + cost: 0 exclusive: true requirements: - !type:CharacterJobRequirement jobs: - ResearchDirector items: - - ClothingNeckMantleRD + - ClothingHeadHatBeretMysta - type: loadout - id: LoadoutCommandRDNeckCloak - category: Jobs - cost: 2 + id: LoadoutCommandRDHeadHoodMysta + category: JobsCommand + cost: 0 exclusive: true requirements: - !type:CharacterJobRequirement jobs: - ResearchDirector items: - - ClothingNeckCloakRd + - ClothingHeadHoodMysta + +# Neck - type: loadout - id: LoadoutCommandRDOuterWinter - category: Jobs - cost: 2 + id: LoadoutCommandRDNeckMantle + category: JobsCommand + cost: 0 + exclusive: true requirements: - !type:CharacterJobRequirement jobs: - ResearchDirector items: - - ClothingOuterWinterRD + - ClothingNeckMantleRD - type: loadout - id: LoadoutCommandRDOuterMysta - category: Jobs - cost: 2 + id: LoadoutCommandRDNeckCloak + category: JobsCommand + cost: 0 + exclusive: true requirements: - !type:CharacterJobRequirement jobs: - ResearchDirector items: - - ClothingOuterCoatRndMysta + - ClothingNeckCloakRd + +- type: loadout + id: LoadoutCommandRDNeckCloakMystagogue + category: JobsCommand + cost: 0 + exclusive: true + requirements: + - !type:CharacterJobRequirement + jobs: + - ResearchDirector + items: + - ClothingNeckCloakMystagogue + +# Shoes - type: loadout id: LoadoutCommandRDShoesBootsWinter - category: Jobs - cost: 1 + category: JobsCommand + cost: 0 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Diona - - Harpy - !type:CharacterJobRequirement jobs: - ResearchDirector diff --git a/Resources/Prototypes/Loadouts/Jobs/Service/musician.yml b/Resources/Prototypes/Loadouts/Jobs/Service/musician.yml new file mode 100644 index 0000000000..5bf5471697 --- /dev/null +++ b/Resources/Prototypes/Loadouts/Jobs/Service/musician.yml @@ -0,0 +1,554 @@ +# Musician +# Musician Instruments +# Brass Instruments +- type: loadout + id: LoadoutItemTrumpetInstrumentMusician + category: JobsServiceMusician + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMusicianInstruments + - !type:CharacterJobRequirement + jobs: + - Musician + items: + - TrumpetInstrument + +- type: loadout + id: LoadoutItemTromboneInstrumentMusician + category: JobsServiceMusician + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMusicianInstruments + - !type:CharacterJobRequirement + jobs: + - Musician + items: + - TromboneInstrument + +- type: loadout + id: LoadoutItemFrenchHornInstrumentMusician + category: JobsServiceMusician + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMusicianInstruments + - !type:CharacterJobRequirement + jobs: + - Musician + items: + - FrenchHornInstrument + +- type: loadout + id: LoadoutItemEuphoniumInstrumentMusician + category: JobsServiceMusician + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMusicianInstruments + - !type:CharacterJobRequirement + jobs: + - Musician + items: + - EuphoniumInstrument + +# Misc Instruments +- type: loadout + id: LoadoutItemSeashellInstrumentMusician + category: JobsServiceMusician + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMusicianInstruments + - !type:CharacterJobRequirement + jobs: + - Musician + items: + - SeashellInstrument + +- type: loadout + id: LoadoutItemBirdToyInstrumentMusician + category: JobsServiceMusician + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMusicianInstruments + - !type:CharacterJobRequirement + jobs: + - Musician + items: + - BirdToyInstrument + # After this are some instruments like "Phone, Helicopter, and Canned Aplause. I leave those to the Clown + +# Percussion +- type: loadout + id: LoadoutItemGlockenspielInstrumentMusician + category: JobsServiceMusician + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMusicianInstruments + - !type:CharacterJobRequirement + jobs: + - Musician + items: + - GlockenspielInstrument + +- type: loadout + id: LoadoutItemMusicBoxInstrumentMusician + category: JobsServiceMusician + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMusicianInstruments + - !type:CharacterJobRequirement + jobs: + - Musician + items: + - MusicBoxInstrument + +- type: loadout + id: LoadoutItemXylophoneInstrumentMusician + category: JobsServiceMusician + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMusicianInstruments + - !type:CharacterJobRequirement + jobs: + - Musician + items: + - XylophoneInstrument + +- type: loadout + id: LoadoutItemMicrophoneInstrumentMusician + category: JobsServiceMusician + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMusicianInstruments + - !type:CharacterJobRequirement + jobs: + - Musician + items: + - MicrophoneInstrument + +- type: loadout + id: LoadoutItemSynthesizerInstrumentMusician + category: JobsServiceMusician + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMusicianInstruments + - !type:CharacterJobRequirement + jobs: + - Musician + items: + - SynthesizerInstrument + +- type: loadout + id: LoadoutItemKalimbaInstrumentMusician + category: JobsServiceMusician + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMusicianInstruments + - !type:CharacterJobRequirement + jobs: + - Musician + items: + - KalimbaInstrument + +- type: loadout + id: LoadoutItemWoodblockInstrumentMusician + category: JobsServiceMusician + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMusicianInstruments + - !type:CharacterJobRequirement + jobs: + - Musician + items: + - WoodblockInstrument + +# Stringed Instruments +- type: loadout + id: LoadoutItemElectricGuitarInstrumentMusician + category: JobsServiceMusician + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMusicianInstruments + - !type:CharacterJobRequirement + jobs: + - Musician + items: + - ElectricGuitarInstrument + +- type: loadout + id: LoadoutItemBassGuitarInstrumentMusician + category: JobsServiceMusician + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMusicianInstruments + - !type:CharacterJobRequirement + jobs: + - Musician + items: + - BassGuitarInstrument + +- type: loadout + id: LoadoutItemRockGuitarInstrumentMusician + category: JobsServiceMusician + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMusicianInstruments + - !type:CharacterJobRequirement + jobs: + - Musician + items: + - RockGuitarInstrument + +- type: loadout + id: LoadoutItemAcousticGuitarInstrumentMusician + category: JobsServiceMusician + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMusicianInstruments + - !type:CharacterJobRequirement + jobs: + - Musician + items: + - AcousticGuitarInstrument + +- type: loadout + id: LoadoutItemBanjoInstrumentMusician + category: JobsServiceMusician + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMusicianInstruments + - !type:CharacterJobRequirement + jobs: + - Musician + items: + - BanjoInstrument + +- type: loadout + id: LoadoutItemViolinInstrumentMusician + category: JobsServiceMusician + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMusicianInstruments + - !type:CharacterJobRequirement + jobs: + - Musician + items: + - ViolinInstrument + +- type: loadout + id: LoadoutItemViolaInstrumentMusician + category: JobsServiceMusician + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMusicianInstruments + - !type:CharacterJobRequirement + jobs: + - Musician + items: + - ViolaInstrument + +- type: loadout + id: LoadoutItemCelloInstrumentMusician + category: JobsServiceMusician + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMusicianInstruments + - !type:CharacterJobRequirement + jobs: + - Musician + items: + - CelloInstrument + +# Structure Instruments +- type: loadout + id: LoadoutItemPianoInstrumentMusician + category: JobsServiceMusician + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMusicianInstruments + - !type:CharacterJobRequirement + jobs: + - Musician + items: + - PianoFlatpack + +- type: loadout + id: LoadoutItemUprightPianoInstrumentMusician + category: JobsServiceMusician + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMusicianInstruments + - !type:CharacterJobRequirement + jobs: + - Musician + items: + - UprightPianoFlatpack + +- type: loadout + id: LoadoutItemVibraphoneInstrumentMusician + category: JobsServiceMusician + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMusicianInstruments + - !type:CharacterJobRequirement + jobs: + - Musician + items: + - VibraphoneFlatpack + +- type: loadout + id: LoadoutItemMarimbaInstrumentMusician + category: JobsServiceMusician + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMusicianInstruments + - !type:CharacterJobRequirement + jobs: + - Musician + items: + - MarimbaFlatpack + +- type: loadout + id: LoadoutItemChurchOrganInstrumentMusician + category: JobsServiceMusician + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMusicianInstruments + - !type:CharacterJobRequirement + jobs: + - Musician + items: + - ChurchOrganFlatpack + +- type: loadout + id: LoadoutItemTubaInstrumentMusician + category: JobsServiceMusician + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMusicianInstruments + - !type:CharacterJobRequirement + jobs: + - Musician + items: + - TubaFlatpack + +- type: loadout + id: LoadoutItemHarpInstrumentMusician + category: JobsServiceMusician + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMusicianInstruments + - !type:CharacterJobRequirement + jobs: + - Musician + items: + - HarpFlatpack + +- type: loadout + id: LoadoutItemTimpaniInstrumentMusician + category: JobsServiceMusician + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMusicianInstruments + - !type:CharacterJobRequirement + jobs: + - Musician + items: + - TimpaniFlatpack + +- type: loadout + id: LoadoutItemTaikoInstrumentMusician + category: JobsServiceMusician + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMusicianInstruments + - !type:CharacterJobRequirement + jobs: + - Musician + items: + - TaikoFlatpack + +- type: loadout + id: LoadoutItemContrabassInstrumentMusician + category: JobsServiceMusician + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMusicianInstruments + - !type:CharacterJobRequirement + jobs: + - Musician + items: + - ContrabassFlatpack + +- type: loadout + id: LoadoutItemMinimoogInstrumentMusician + category: JobsServiceMusician + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMusicianInstruments + - !type:CharacterJobRequirement + jobs: + - Musician + items: + - MinimoogFlatpack + +- type: loadout + id: LoadoutItemTomDrumsInstrumentMusician + category: JobsServiceMusician + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMusicianInstruments + - !type:CharacterJobRequirement + jobs: + - Musician + items: + - TomDrumsFlatpack + +# Wind Instruments +- type: loadout + id: LoadoutItemSaxophoneInstrumentMusician + category: JobsServiceMusician + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMusicianInstruments + - !type:CharacterJobRequirement + jobs: + - Musician + items: + - SaxophoneInstrument + +- type: loadout + id: LoadoutItemAccordionInstrumentMusician + category: JobsServiceMusician + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMusicianInstruments + - !type:CharacterJobRequirement + jobs: + - Musician + items: + - AccordionInstrument + +- type: loadout + id: LoadoutItemHarmonicaInstrumentMusician + category: JobsServiceMusician + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMusicianInstruments + - !type:CharacterJobRequirement + jobs: + - Musician + items: + - HarmonicaInstrument + +- type: loadout + id: LoadoutItemClarinetInstrumentMusician + category: JobsServiceMusician + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMusicianInstruments + - !type:CharacterJobRequirement + jobs: + - Musician + items: + - ClarinetInstrument + +- type: loadout + id: LoadoutItemFluteInstrumentMusician + category: JobsServiceMusician + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMusicianInstruments + - !type:CharacterJobRequirement + jobs: + - Musician + items: + - FluteInstrument + +- type: loadout + id: LoadoutItemRecorderInstrumentMusician + category: JobsServiceMusician + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMusicianInstruments + - !type:CharacterJobRequirement + jobs: + - Musician + items: + - RecorderInstrument + +- type: loadout + id: LoadoutItemPanFluteInstrumentMusician + category: JobsServiceMusician + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMusicianInstruments + - !type:CharacterJobRequirement + jobs: + - Musician + items: + - PanFluteInstrument + +- type: loadout + id: LoadoutItemOcarinaInstrumentMusician + category: JobsServiceMusician + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMusicianInstruments + - !type:CharacterJobRequirement + jobs: + - Musician + items: + - OcarinaInstrument + +- type: loadout + id: LoadoutItemBagpipeInstrumentMusician + category: JobsServiceMusician + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMusicianInstruments + - !type:CharacterJobRequirement + jobs: + - Musician + items: + - BagpipeInstrument diff --git a/Resources/Prototypes/Loadouts/Jobs/cargo.yml b/Resources/Prototypes/Loadouts/Jobs/cargo.yml index cdc83a3c0f..7e9c525e40 100644 --- a/Resources/Prototypes/Loadouts/Jobs/cargo.yml +++ b/Resources/Prototypes/Loadouts/Jobs/cargo.yml @@ -1,10 +1,12 @@ # Cargo technician - type: loadout id: LoadoutCargoOuterWinterCargo - category: Jobs - cost: 2 + category: JobsCargo + cost: 1 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuterCargo - !type:CharacterJobRequirement jobs: - CargoTechnician @@ -13,18 +15,15 @@ - type: loadout id: LoadoutCargoShoesBootsWinterCargo - category: Jobs - cost: 1 + category: JobsCargo + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutShoesCargo - !type:CharacterJobRequirement jobs: - CargoTechnician - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Diona - - Harpy items: - ClothingShoesBootsWinterCargo @@ -32,10 +31,12 @@ - type: loadout id: LoadoutCargoOuterWinterMiner - category: Jobs - cost: 2 + category: JobsCargo + cost: 1 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuterCargo - !type:CharacterJobRequirement jobs: - SalvageSpecialist @@ -44,10 +45,12 @@ - type: loadout id: LoadoutCargoNeckGoliathCloak - category: Jobs - cost: 2 + category: JobsCargo + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeckCargo - !type:CharacterJobRequirement jobs: - SalvageSpecialist @@ -56,3 +59,14 @@ min: 36000 # 10 hours items: - ClothingNeckCloakGoliathCloak + +- type: loadout + id: LoadoutCargoWeaponsCrusherDagger + category: JobsCargo + cost: 2 + requirements: + - !type:CharacterJobRequirement + jobs: + - SalvageSpecialist + items: + - WeaponCrusherDagger diff --git a/Resources/Prototypes/Loadouts/Jobs/engineering.yml b/Resources/Prototypes/Loadouts/Jobs/engineering.yml index 06ce4cbf8c..2bf857c85f 100644 --- a/Resources/Prototypes/Loadouts/Jobs/engineering.yml +++ b/Resources/Prototypes/Loadouts/Jobs/engineering.yml @@ -1,13 +1,11 @@ - type: loadout id: LoadoutEngineeringUniformHazard - category: Jobs - cost: 2 + category: JobsEngineering + cost: 0 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsEngineering - !type:CharacterJobRequirement jobs: - StationEngineer @@ -16,10 +14,12 @@ - type: loadout id: LoadoutEngineeringOuterHazard - category: Jobs - cost: 2 + category: JobsEngineering + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuterEngineering - !type:CharacterJobRequirement jobs: - StationEngineer @@ -28,10 +28,12 @@ - type: loadout id: LoadoutEngineeringUniformJumpskirtSenior - category: Jobs - cost: 2 + category: JobsEngineering + cost: 1 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsEngineering - !type:CharacterJobRequirement jobs: - StationEngineer @@ -49,14 +51,12 @@ - type: loadout id: LoadoutEngineeringUniformJumpsuitSenior - category: Jobs - cost: 2 + category: JobsEngineering + cost: 1 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsEngineering - !type:CharacterJobRequirement jobs: - StationEngineer @@ -74,10 +74,12 @@ - type: loadout id: LoadoutEngineeringChickenSuit # :) - category: Jobs - cost: 3 + category: JobsEngineering + cost: 1 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuterEngineering - !type:CharacterJobRequirement jobs: - AtmosphericTechnician @@ -87,10 +89,12 @@ - type: loadout id: LoadoutEngineeringEyesMeson - category: Jobs - cost: 2 + category: JobsEngineering + cost: 1 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutEyesEngineering - !type:CharacterJobRequirement jobs: - StationEngineer @@ -100,10 +104,12 @@ - type: loadout id: LoadoutEngineeringHeadBeret - category: Jobs - cost: 1 + category: JobsEngineering + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHeadEngineering - !type:CharacterJobRequirement jobs: - StationEngineer @@ -114,10 +120,12 @@ - type: loadout id: LoadoutEngineeringHeadHardhatBlue - category: Jobs - cost: 2 + category: JobsEngineering + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHeadEngineering - !type:CharacterJobRequirement jobs: - StationEngineer @@ -127,10 +135,12 @@ - type: loadout id: LoadoutEngineeringHeadHardhatOrange - category: Jobs - cost: 2 + category: JobsEngineering + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHeadEngineering - !type:CharacterJobRequirement jobs: - StationEngineer @@ -140,10 +150,12 @@ - type: loadout id: LoadoutEngineeringHeadHardhatYellow - category: Jobs - cost: 2 + category: JobsEngineering + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHeadEngineering - !type:CharacterJobRequirement jobs: - StationEngineer @@ -153,10 +165,12 @@ - type: loadout id: LoadoutEngineeringHeadHardhatWhite - category: Jobs - cost: 2 + category: JobsEngineering + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHeadEngineering - !type:CharacterJobRequirement jobs: - StationEngineer @@ -166,10 +180,12 @@ - type: loadout id: LoadoutEngineeringHeadHardhatRed - category: Jobs - cost: 2 + category: JobsEngineering + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHeadEngineering - !type:CharacterJobRequirement jobs: - StationEngineer diff --git a/Resources/Prototypes/Loadouts/Jobs/medical.yml b/Resources/Prototypes/Loadouts/Jobs/medical.yml index f193dfaea2..6a2d5fb9d2 100644 --- a/Resources/Prototypes/Loadouts/Jobs/medical.yml +++ b/Resources/Prototypes/Loadouts/Jobs/medical.yml @@ -1,9 +1,11 @@ - type: loadout id: LoadoutMedicalGlovesNitrile - category: Jobs - cost: 1 + category: JobsMedical + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutGlovesMedical - !type:CharacterJobRequirement jobs: - MedicalDoctor @@ -16,10 +18,12 @@ - type: loadout id: LoadoutMedicalOuterLabcoat - category: Jobs - cost: 2 + category: JobsMedical + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuterMedical - !type:CharacterJobRequirement jobs: - MedicalDoctor @@ -29,10 +33,12 @@ - type: loadout id: LoadoutMedicalNeckStethoscope - category: Jobs - cost: 1 + category: JobsMedical + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeckMedical - !type:CharacterJobRequirement jobs: - MedicalDoctor @@ -43,14 +49,12 @@ - type: loadout id: LoadoutMedicalUniformScrubsBlue - category: Jobs - cost: 2 + category: JobsMedical + cost: 0 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsMedical - !type:CharacterJobRequirement jobs: - MedicalDoctor @@ -62,14 +66,12 @@ - type: loadout id: LoadoutMedicalUniformScrubsGreen - category: Jobs - cost: 2 + category: JobsMedical + cost: 0 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsMedical - !type:CharacterJobRequirement jobs: - MedicalDoctor @@ -81,14 +83,12 @@ - type: loadout id: LoadoutMedicalUniformScrubsPurple - category: Jobs - cost: 2 + category: JobsMedical + cost: 0 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsMedical - !type:CharacterJobRequirement jobs: - MedicalDoctor @@ -100,14 +100,12 @@ - type: loadout id: LoadoutMedicalUniformScrubsCyan - category: Jobs - cost: 2 + category: JobsMedical + cost: 0 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsMedical - !type:CharacterJobRequirement jobs: - MedicalDoctor @@ -119,14 +117,12 @@ - type: loadout id: LoadoutMedicalUniformScrubsBlack - category: Jobs - cost: 2 + category: JobsMedical + cost: 0 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsMedical - !type:CharacterJobRequirement jobs: - MedicalDoctor @@ -138,14 +134,12 @@ - type: loadout id: LoadoutMedicalUniformScrubsPink - category: Jobs - cost: 2 + category: JobsMedical + cost: 0 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsMedical - !type:CharacterJobRequirement jobs: - MedicalDoctor @@ -157,14 +151,12 @@ - type: loadout id: LoadoutMedicalUniformScrubsCybersun - category: Jobs - cost: 3 + category: JobsMedical + cost: 1 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsMedical - !type:CharacterJobRequirement jobs: - MedicalDoctor @@ -175,10 +167,12 @@ - type: loadout id: LoadoutMedicalOuterCybersunWindbreaker - category: Jobs - cost: 5 + category: JobsMedical + cost: 3 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuterMedical - !type:CharacterJobRequirement jobs: - MedicalDoctor @@ -189,10 +183,12 @@ - type: loadout id: LoadoutMedicalOuterLabcoatChem - category: Jobs - cost: 2 + category: JobsMedical + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuterMedical - !type:CharacterJobRequirement jobs: - Chemist @@ -201,7 +197,7 @@ - type: loadout id: LoadoutMedicalItemHandLabeler - category: Jobs + category: JobsMedical exclusive: true requirements: - !type:CharacterJobRequirement @@ -212,14 +208,12 @@ - type: loadout id: LoadoutMedicalUniformParamedicJumpsuit - category: Jobs - cost: 2 + category: JobsMedical + cost: 0 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsMedical - !type:CharacterJobRequirement jobs: - Paramedic @@ -228,10 +222,12 @@ - type: loadout id: LoadoutMedicalUniformParamedicJumpskirt - category: Jobs - cost: 2 + category: JobsMedical + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsMedical - !type:CharacterJobRequirement jobs: - Paramedic @@ -240,10 +236,12 @@ - type: loadout id: LoadoutMedicalUniformJumpskirtSenior - category: Jobs - cost: 2 + category: JobsMedical + cost: 1 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsMedical - !type:CharacterJobRequirement jobs: - MedicalDoctor @@ -264,14 +262,12 @@ - type: loadout id: LoadoutMedicalUniformJumpsuitSenior - category: Jobs - cost: 2 + category: JobsMedical + cost: 1 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsMedical - !type:CharacterJobRequirement jobs: - MedicalDoctor @@ -292,10 +288,12 @@ - type: loadout id: LoadoutMedicalHeadNurse - category: Jobs - cost: 2 + category: JobsMedical + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHeadMedical - !type:CharacterJobRequirement jobs: - MedicalDoctor @@ -304,10 +302,12 @@ - type: loadout id: LoadoutMedicalHeadBeretSeniorPhysician - category: Jobs - cost: 2 + category: JobsMedical + cost: 1 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHeadMedical - !type:CharacterJobRequirement jobs: - MedicalDoctor @@ -328,10 +328,12 @@ - type: loadout id: LoadoutMedicalHeadSurgcapBlue - category: Jobs - cost: 2 + category: JobsMedical + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHeadMedical - !type:CharacterJobRequirement jobs: - MedicalDoctor @@ -343,10 +345,12 @@ - type: loadout id: LoadoutMedicalHeadSurgcapPurple - category: Jobs - cost: 2 + category: JobsMedical + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHeadMedical - !type:CharacterJobRequirement jobs: - MedicalDoctor @@ -358,10 +362,12 @@ - type: loadout id: LoadoutMedicalHeadSurgcapGreen - category: Jobs - cost: 2 + category: JobsMedical + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHeadMedical - !type:CharacterJobRequirement jobs: - MedicalDoctor @@ -373,10 +379,12 @@ - type: loadout id: LoadoutMedicalHeadSurgcapCyan - category: Jobs - cost: 2 + category: JobsMedical + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHeadMedical - !type:CharacterJobRequirement jobs: - MedicalDoctor @@ -388,10 +396,12 @@ - type: loadout id: LoadoutMedicalHeadSurgcapBlack - category: Jobs - cost: 2 + category: JobsMedical + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHeadMedical - !type:CharacterJobRequirement jobs: - MedicalDoctor @@ -403,10 +413,12 @@ - type: loadout id: LoadoutMedicalHeadSurgcapPink - category: Jobs - cost: 2 + category: JobsMedical + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHeadMedical - !type:CharacterJobRequirement jobs: - MedicalDoctor @@ -418,10 +430,12 @@ - type: loadout id: LoadoutMedicalHeadSurgcapWhite - category: Jobs - cost: 2 + category: JobsMedical + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHeadMedical - !type:CharacterJobRequirement jobs: - MedicalDoctor @@ -433,10 +447,12 @@ - type: loadout id: LoadoutMedicalHeadSurgcapCybersun - category: Jobs - cost: 3 + category: JobsMedical + cost: 1 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHeadMedical - !type:CharacterJobRequirement jobs: - MedicalDoctor @@ -447,10 +463,12 @@ - type: loadout id: LoadoutMedicalEyesHudMedical - category: Jobs - cost: 2 + category: JobsMedical + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutEyesMedical - !type:CharacterJobRequirement jobs: - MedicalDoctor @@ -463,10 +481,12 @@ - type: loadout id: LoadoutMedicalEyesEyepatchHudMedical - category: Jobs - cost: 2 + category: JobsMedical + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutEyesMedical - !type:CharacterJobRequirement jobs: - MedicalDoctor @@ -479,10 +499,12 @@ - type: loadout id: LoadoutMedicalEyesHudMedicalPrescription - category: Jobs - cost: 2 + category: JobsMedical + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutEyesMedical - !type:CharacterJobRequirement jobs: - MedicalDoctor @@ -498,10 +520,12 @@ - type: loadout id: LoadoutMedicalEyesGlassesChemical - category: Jobs - cost: 2 + category: JobsMedical + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutEyesMedical - !type:CharacterJobRequirement jobs: - Chemist @@ -510,10 +534,12 @@ - type: loadout id: LoadoutMedicalBedsheetMedical - category: Jobs - cost: 2 + category: JobsMedical + cost: 1 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeckMedical - !type:CharacterDepartmentRequirement departments: - Medical @@ -523,26 +549,26 @@ # Chemist PPE gear - type: loadout id: LoadoutMedicalUniformJumpsuitChemShirt - category: Jobs - cost: 2 + category: JobsMedical + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsMedical - !type:CharacterJobRequirement jobs: - Chemist - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy items: - ClothingUniformJumpsuitChemShirt - type: loadout id: LoadoutMedicalNeckTieChem - category: Jobs - cost: 1 + category: JobsMedical + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeckMedical - !type:CharacterJobRequirement jobs: - Chemist @@ -551,27 +577,26 @@ - type: loadout id: LoadoutMedicalShoesEnclosedChem - category: Jobs - cost: 1 + category: JobsMedical + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutShoesMedical - !type:CharacterJobRequirement jobs: - Chemist - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Diona - - Harpy items: - ClothingShoesEnclosedChem - type: loadout id: LoadoutMedicalOuterApronChemist - category: Jobs - cost: 2 + category: JobsMedical + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuterMedical - !type:CharacterJobRequirement jobs: - Chemist @@ -580,10 +605,12 @@ - type: loadout id: LoadoutMedicalEyesGlassesChemist - category: Jobs - cost: 2 + category: JobsMedical + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutEyesMedical - !type:CharacterJobRequirement jobs: - Chemist @@ -592,10 +619,12 @@ - type: loadout id: LoadoutMedicalHandsGlovesChemist - category: Jobs - cost: 1 + category: JobsMedical + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutGlovesMedical - !type:CharacterJobRequirement jobs: - Chemist diff --git a/Resources/Prototypes/Loadouts/Jobs/science.yml b/Resources/Prototypes/Loadouts/Jobs/science.yml index 04b226e2ba..3f376ec872 100644 --- a/Resources/Prototypes/Loadouts/Jobs/science.yml +++ b/Resources/Prototypes/Loadouts/Jobs/science.yml @@ -1,12 +1,16 @@ +# Uniforms + - type: loadout id: LoadoutScienceUniformJumpskirtSenior - category: Jobs - cost: 2 + category: JobsScience + cost: 0 exclusive: true requirements: - - !type:CharacterJobRequirement - jobs: - - Scientist + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsScience + - !type:CharacterDepartmentRequirement + departments: + - Epistemics - !type:CharacterDepartmentTimeRequirement department: Epistemics min: 216000 # 60 hours @@ -15,17 +19,15 @@ - type: loadout id: LoadoutScienceUniformJumpsuitSenior - category: Jobs - cost: 2 + category: JobsScience + cost: 0 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy - - !type:CharacterJobRequirement - jobs: - - Scientist + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsScience + - !type:CharacterDepartmentRequirement + departments: + - Epistemics - !type:CharacterDepartmentTimeRequirement department: Epistemics min: 216000 # 60 hours @@ -33,42 +35,130 @@ - ClothingUniformJumpsuitSeniorResearcher - type: loadout - id: LoadoutScienceOuterCoat - category: Jobs - cost: 2 + id: LoadoutScienceUniformJumpskirtRoboticist + category: JobsScience + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsScience + - !type:CharacterDepartmentRequirement + departments: + - Epistemics + items: + - ClothingUniformJumpskirtRoboticist + +- type: loadout + id: LoadoutScienceUniformJumpsuitRoboticist + category: JobsScience + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsScience + - !type:CharacterDepartmentRequirement + departments: + - Epistemics + items: + - ClothingUniformJumpsuitRoboticist + +- type: loadout + id: LoadoutScienceUniformJumpsuitMonasticRobeDark + category: JobsScience + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsScience - !type:CharacterJobRequirement jobs: - - Scientist - - ResearchAssistant - - ResearchDirector + - Chaplain items: - - ClothingOuterCoatRnd + - ClothingUniformJumpsuitMonasticRobeDark - type: loadout - id: LoadoutScienceOuterLabcoat - category: Jobs - cost: 2 + id: LoadoutScienceUniformJumpsuitMonasticRobeLight + category: JobsScience + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsScience - !type:CharacterJobRequirement jobs: - - Scientist - - ResearchAssistant - - ResearchDirector + - Chaplain + items: + - ClothingUniformJumpsuitMonasticRobeLight + +# Outer + +- type: loadout + id: LoadoutScienceOuterCoat + category: JobsScience + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuterScience + - !type:CharacterDepartmentRequirement + departments: + - Epistemics + items: + - ClothingOuterCoatRnd + +- type: loadout + id: LoadoutScienceOuterLabcoat + category: JobsScience + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuterScience + - !type:CharacterDepartmentRequirement + departments: + - Epistemics items: - ClothingOuterCoatLab +- type: loadout + id: LoadoutSciencegOuterCoatRobo + category: JobsScience + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuterScience + - !type:CharacterDepartmentRequirement + departments: + - Epistemics + items: + - ClothingOuterCoatRobo + +- type: loadout + id: LoadoutScienceOuterWinterSci + category: JobsScience + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuterScience + - !type:CharacterDepartmentRequirement + departments: + - Epistemics + items: + - ClothingOuterWinterSci + - type: loadout id: LoadoutScienceOuterLabcoatSeniorResearcher - category: Jobs - cost: 2 + category: JobsScience + cost: 0 exclusive: true requirements: - - !type:CharacterJobRequirement - jobs: - - Scientist + - !type:CharacterItemGroupRequirement + group: LoadoutOuterScience + - !type:CharacterDepartmentRequirement + departments: + - Epistemics - !type:CharacterDepartmentTimeRequirement department: Epistemics min: 216000 # 60 hours @@ -77,84 +167,547 @@ - type: loadout id: LoadoutScienceOuterExplorerLabcoat - category: Jobs - cost: 2 + category: JobsScience + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuterScience + - !type:CharacterDepartmentRequirement + departments: + - Epistemics + items: + - ClothingOuterExplorerCoat + +- type: loadout + id: LoadoutScienceOuterPlagueSuit + category: JobsScience + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuterScience - !type:CharacterJobRequirement jobs: - - Scientist - - ResearchAssistant - - ResearchDirector + - Chaplain items: - - ClothingOuterExplorerCoat + - ClothingOuterPlagueSuit + +- type: loadout + id: LoadoutScienceOuterNunRobe + category: JobsScience + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuterScience + - !type:CharacterJobRequirement + jobs: + - Chaplain + items: + - ClothingOuterNunRobe - type: loadout - id: LoadoutScienceHatBeret - category: Jobs + id: LoadoutScienceOuterHoodieBlack + category: JobsScience + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuterScience + - !type:CharacterJobRequirement + jobs: + - Chaplain + items: + - ClothingOuterHoodieBlack + +- type: loadout + id: LoadoutScienceOuterHoodieChaplain + category: JobsScience + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuterScience + - !type:CharacterJobRequirement + jobs: + - Chaplain + items: + - ClothingOuterHoodieChaplain + +- type: loadout + id: LoadoutScienceOuterWinterCoatMantis + category: JobsScience cost: 1 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuterScience - !type:CharacterJobRequirement jobs: - - Scientist - - ResearchAssistant - - ResearchDirector + - ForensicMantis + items: + - ClothingOuterWinterCoatMantis + +# Gloves + +- type: loadout + id: LoadoutScienceHandsGlovesColorPurple + category: JobsScience + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutGlovesScience + - !type:CharacterDepartmentRequirement + departments: + - Epistemics + items: + - ClothingHandsGlovesColorPurple + +- type: loadout + id: LoadoutScienceHandsGlovesLatex + category: JobsScience + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutGlovesScience + - !type:CharacterDepartmentRequirement + departments: + - Epistemics + items: + - ClothingHandsGlovesLatex + +- type: loadout + id: LoadoutScienceHandsGlovesRobohands + category: JobsScience + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutGlovesScience + - !type:CharacterDepartmentRequirement + departments: + - Epistemics + items: + - ClothingHandsGlovesRobohands + +# Neck + +- type: loadout + id: LoadoutScienceNeckTieSci + category: JobsScience + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeckScience + - !type:CharacterDepartmentRequirement + departments: + - Epistemics + items: + - ClothingNeckTieSci + +- type: loadout + id: LoadoutScienceNeckScarfStripedPurple + category: JobsScience + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeckScience + - !type:CharacterDepartmentRequirement + departments: + - Epistemics + items: + - ClothingNeckScarfStripedPurple + +- type: loadout + id: LoadoutScienceNeckStoleChaplain + category: JobsScience + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeckScience + - !type:CharacterJobRequirement + jobs: + - Chaplain + items: + - ClothingNeckStoleChaplain + +- type: loadout + id: LoadoutScienceNeckScarfStripedBlack + category: JobsScience + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeckScience + - !type:CharacterDepartmentRequirement + departments: + - Epistemics + items: + - ClothingNeckScarfStripedBlack + +# Mask + +- type: loadout + id: LoadoutScienceMaskPlague + category: JobsScience + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMaskScience + - !type:CharacterJobRequirement + jobs: + - Chaplain + items: + - ClothingMaskPlague + +# Head + +- type: loadout + id: LoadoutScienceHeadHatBeret + category: JobsScience + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHeadScience + - !type:CharacterDepartmentRequirement + departments: + - Epistemics items: - ClothingHeadHatBeretRND - type: loadout - id: LoadoutScienceEyesHudDiagnostic - category: Jobs - cost: 3 + id: LoadoutScienceHeadHatFez + category: JobsScience + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHeadScience + - !type:CharacterDepartmentRequirement + departments: + - Epistemics + items: + - ClothingHeadHatFez + +- type: loadout + id: LoadoutScienceHeadHatHoodNunHood + category: JobsScience + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHeadScience - !type:CharacterJobRequirement jobs: - - Scientist - - ResearchAssistant - - ResearchDirector + - Chaplain items: - - ClothingEyesHudDiagnostic + - ClothingHeadHatHoodNunHood - type: loadout - id: LoadoutScienceEyesEyepatchHudDiag - category: Jobs - cost: 3 + id: LoadoutScienceHeadHatPlaguedoctor + category: JobsScience + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHeadScience + - !type:CharacterJobRequirement + jobs: + - Chaplain + items: + - ClothingHeadHatPlaguedoctor + +- type: loadout + id: LoadoutScienceHeadHatWitch + category: JobsScience + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHeadScience - !type:CharacterJobRequirement jobs: - - Scientist - - ResearchAssistant - - ResearchDirector + - Chaplain + items: + - ClothingHeadHatWitch + +- type: loadout + id: LoadoutScienceHeadHatWitch1 + category: JobsScience + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHeadScience + - !type:CharacterJobRequirement + jobs: + - Chaplain + items: + - ClothingHeadHatWitch1 + +# Eyes + +- type: loadout + id: LoadoutScienceEyesHudDiagnostic + category: JobsScience + cost: 1 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutEyesScience + - !type:CharacterDepartmentRequirement + departments: + - Epistemics + items: + - ClothingEyesHudDiagnostic + +- type: loadout + id: LoadoutScienceEyesEyepatchHudDiag + category: JobsScience + cost: 1 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutEyesScience + - !type:CharacterDepartmentRequirement + departments: + - Epistemics items: - ClothingEyesEyepatchHudDiag +# Shoes + +- type: loadout + id: LoadoutScienceShoesBootsWinterSci + category: JobsScience + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutShoesScience + - !type:CharacterDepartmentRequirement + departments: + - Epistemics + items: + - ClothingShoesBootsWinterSci + # Robes + - type: loadout id: LoadoutOuterRobeTechPriest category: Outer - cost: 2 + cost: 0 items: - ClothingOuterRobeTechPriest requirements: - - !type:CharacterJobRequirement - jobs: - - Scientist - - ResearchAssistant - - ResearchDirector + - !type:CharacterItemGroupRequirement + group: LoadoutOuterScience + - !type:CharacterDepartmentRequirement + departments: + - Epistemics - type: loadout id: LoadoutHeadHoodTechPriest category: Head - cost: 1 + cost: 0 exclusive: true items: - ClothingHeadTechPriest requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHeadScience + - !type:CharacterDepartmentRequirement + departments: + - Epistemics + +# Cataloguer +- type: loadout + id: LoadoutScienceJumpsuitLibrarianNt + category: JobsScience + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutCataloguerUniforms - !type:CharacterJobRequirement jobs: - - Scientist - - ResearchAssistant - - ResearchDirector + - Librarian + items: + - ClothingUniformJumpsuitLibrarianNt + +- type: loadout + id: LoadoutScienceJumpsuitLibrarianIdris + category: JobsScience + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutCataloguerUniforms + - !type:CharacterJobRequirement + jobs: + - Librarian + items: + - ClothingUniformJumpsuitLibrarianIdris + +- type: loadout + id: LoadoutScienceJumpsuitLibrarianOrion + category: JobsScience + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutCataloguerUniforms + - !type:CharacterJobRequirement + jobs: + - Librarian + items: + - ClothingUniformJumpsuitLibrarianOrion + +- type: loadout + id: LoadoutScienceJumpsuitLibrarianHeph + category: JobsScience + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutCataloguerUniforms + - !type:CharacterJobRequirement + jobs: + - Librarian + items: + - ClothingUniformJumpsuitLibrarianHeph + +- type: loadout + id: LoadoutScienceJumpsuitLibrarianPMCG + category: JobsScience + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutCataloguerUniforms + - !type:CharacterJobRequirement + jobs: + - Librarian + items: + - ClothingUniformJumpsuitLibrarianPMCG + +- type: loadout + id: LoadoutScienceJumpsuitLibrarianZav + category: JobsScience + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutCataloguerUniforms + - !type:CharacterJobRequirement + jobs: + - Librarian + items: + - ClothingUniformJumpsuitLibrarianZav + +- type: loadout + id: LoadoutScienceJumpsuitLibrarianZeng + category: JobsScience + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutCataloguerUniforms + - !type:CharacterJobRequirement + jobs: + - Librarian + items: + - ClothingUniformJumpsuitLibrarianZeng + +- type: loadout + id: LoadoutScienceJumpsuitLibrarian + category: JobsScience + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutCataloguerUniforms + - !type:CharacterJobRequirement + jobs: + - Librarian + items: + - ClothingUniformJumpsuitLibrarian + +- type: loadout + id: LoadoutScienceJumpskirtLibrarian + category: JobsScience + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutCataloguerUniforms + - !type:CharacterJobRequirement + jobs: + - Librarian + items: + - ClothingUniformJumpskirtLibrarian + +# Chaplain +- type: loadout + id: LoadoutChaplainJumpsuit + category: JobsScience + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutChaplainUniforms + - !type:CharacterJobRequirement + jobs: + - Chaplain + items: + - ClothingUniformJumpsuitChaplain + +- type: loadout + id: LoadoutChaplainJumpskirt + category: JobsScience + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutChaplainUniforms + - !type:CharacterJobRequirement + jobs: + - Chaplain + items: + - ClothingUniformJumpskirtChaplain + +- type: loadout + id: LoadoutChaplainBible + category: JobsScience + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutChaplainEquipment + - !type:CharacterJobRequirement + jobs: + - Chaplain + items: + - Bible + +- type: loadout + id: LoadoutChaplainStamp + category: JobsScience + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutChaplainEquipment + - !type:CharacterJobRequirement + jobs: + - Chaplain + items: + - RubberStampChaplain diff --git a/Resources/Prototypes/Loadouts/Jobs/security.yml b/Resources/Prototypes/Loadouts/Jobs/security.yml index 527c9391c0..f2587b4d14 100644 --- a/Resources/Prototypes/Loadouts/Jobs/security.yml +++ b/Resources/Prototypes/Loadouts/Jobs/security.yml @@ -1,14 +1,12 @@ # Uniforms - type: loadout id: LoadoutSecurityUniformJumpsuitBlue - category: Jobs - cost: 1 + category: JobsSecurity + cost: 0 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsSecurity - !type:CharacterDepartmentRequirement departments: - Security @@ -17,14 +15,12 @@ - type: loadout id: LoadoutSecurityUniformJumpsuitGrey - category: Jobs - cost: 1 + category: JobsSecurity + cost: 0 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsSecurity - !type:CharacterDepartmentRequirement departments: - Security @@ -33,10 +29,12 @@ - type: loadout id: LoadoutSecurityUniformJumpskirtGrey - category: Jobs - cost: 1 + category: JobsSecurity + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsSecurity - !type:CharacterDepartmentRequirement departments: - Security @@ -45,10 +43,12 @@ - type: loadout id: LoadoutSecurityUniformJumpskirtBlue - category: Jobs - cost: 1 + category: JobsSecurity + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsSecurity - !type:CharacterDepartmentRequirement departments: - Security @@ -57,10 +57,12 @@ - type: loadout id: LoadoutSecurityUniformJumpskirtSenior - category: Jobs - cost: 2 + category: JobsSecurity + cost: 1 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsSecurity - !type:CharacterJobRequirement jobs: - SecurityOfficer @@ -81,14 +83,12 @@ - type: loadout id: LoadoutSecurityUniformJumpsuitSenior - category: Jobs - cost: 2 + category: JobsSecurity + cost: 1 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsSecurity - !type:CharacterJobRequirement jobs: - SecurityOfficer @@ -109,14 +109,12 @@ - type: loadout id: LoadoutUniformJumpsuitWardenBlue - category: Jobs - cost: 2 + category: JobsSecurity + cost: 0 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsSecurity - !type:CharacterJobRequirement jobs: - Warden @@ -125,14 +123,12 @@ - type: loadout id: LoadoutUniformJumpsuitWardenGrey - category: Jobs - cost: 2 + category: JobsSecurity + cost: 0 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsSecurity - !type:CharacterJobRequirement jobs: - Warden @@ -141,10 +137,12 @@ - type: loadout id: LoadoutUniformJumpskirtWardenBlue - category: Jobs - cost: 2 + category: JobsSecurity + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsSecurity - !type:CharacterJobRequirement jobs: - Warden @@ -153,10 +151,12 @@ - type: loadout id: LoadoutUniformJumpskirtWardenGrey - category: Jobs - cost: 2 + category: JobsSecurity + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsSecurity - !type:CharacterJobRequirement jobs: - Warden @@ -165,10 +165,12 @@ - type: loadout id: LoadoutUniformJumpskirtHoSBlue - category: Jobs - cost: 2 + category: JobsSecurity + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsSecurity - !type:CharacterJobRequirement jobs: - HeadOfSecurity @@ -177,10 +179,12 @@ - type: loadout id: LoadoutUniformJumpskirtHoSGrey - category: Jobs - cost: 2 + category: JobsSecurity + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsSecurity - !type:CharacterJobRequirement jobs: - HeadOfSecurity @@ -189,14 +193,12 @@ - type: loadout id: LoadoutUniformJumpsuitSecFormal - category: Jobs - cost: 1 + category: JobsSecurity + cost: 0 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsSecurity - !type:CharacterDepartmentRequirement departments: - Security @@ -205,27 +207,27 @@ - type: loadout id: LoadoutUniformJumpsuitSecSummer - category: Jobs - cost: 1 + category: JobsSecurity + cost: 0 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsSecurity - !type:CharacterDepartmentRequirement departments: - Security items: - ClothingUniformJumpsuitSecSummer - + # Mask - type: loadout id: LoadoutSecurityMaskGasSwat - category: Jobs - cost: 2 + category: JobsSecurity + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMaskSecurity - !type:CharacterJobRequirement jobs: - Warden @@ -236,14 +238,12 @@ # Shoes - type: loadout id: LoadoutSecurityShoesJackboots - category: Jobs - cost: 1 + category: JobsSecurity + cost: 0 + exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Diona - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutShoesSecurity - !type:CharacterDepartmentRequirement departments: - Security @@ -252,27 +252,27 @@ - type: loadout id: LoadoutClothingShoesBootsCombat - category: Jobs - cost: 1 + category: JobsSecurity + cost: 0 + exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Diona - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutShoesSecurity - !type:CharacterDepartmentRequirement departments: - Security items: - - ClothingShoesBootsCombat + - ClothingShoesBootsCombatFilled # Eyes - type: loadout id: LoadoutSecurityEyesHudSecurity - category: Jobs - cost: 1 + category: JobsSecurity + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutEyesSecurity - !type:CharacterDepartmentRequirement departments: - Security @@ -281,10 +281,12 @@ - type: loadout id: ClothingEyesGlassesSunglasses - category: Jobs - cost: 1 + category: JobsSecurity + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutEyesSecurity - !type:CharacterDepartmentRequirement departments: - Security @@ -293,10 +295,12 @@ - type: loadout id: LoadoutSecurityEyesEyepatchHudSecurity - category: Jobs - cost: 1 + category: JobsSecurity + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutEyesSecurity - !type:CharacterDepartmentRequirement departments: - Security @@ -305,10 +309,12 @@ - type: loadout id: LoadoutSecurityEyesHudSecurityPrescription - category: Jobs - cost: 1 + category: JobsSecurity + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutEyesSecurity - !type:CharacterDepartmentRequirement departments: - Security @@ -320,10 +326,12 @@ - type: loadout id: LoadoutClothingEyesGlassesSecurity - category: Jobs - cost: 4 + category: JobsSecurity + cost: 2 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutEyesSecurity - !type:CharacterDepartmentRequirement departments: - Security @@ -331,15 +339,57 @@ - ClothingEyesGlassesSecurity #Backpack -#Will need to add a backpack category later on, that will be part of an starting inventory loadout refactor. +- type: loadout + id: LoadoutClothingBackSecurity + category: JobsSecurity + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackSecurity + - !type:CharacterDepartmentRequirement + departments: + - Security + items: + - ClothingBackpackSecurity + +- type: loadout + id: LoadoutClothingBackSecuritySatchel + category: JobsSecurity + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackSecurity + - !type:CharacterDepartmentRequirement + departments: + - Security + items: + - ClothingBackpackSatchelSecurity + +- type: loadout + id: LoadoutClothingBackSecurityDuffel + category: JobsSecurity + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackSecurity + - !type:CharacterDepartmentRequirement + departments: + - Security + items: + - ClothingBackpackDuffelSecurity # Head - type: loadout id: LoadoutSecurityHeadHatBeret - category: Jobs - cost: 1 + category: JobsSecurity + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHeadSecurity - !type:CharacterDepartmentRequirement departments: - Security @@ -348,10 +398,12 @@ - type: loadout id: LoadoutClothingHeadHelmetBasic - category: Jobs - cost: 2 + category: JobsSecurity + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHeadSecurity - !type:CharacterDepartmentRequirement departments: - Security @@ -360,10 +412,12 @@ - type: loadout id: LoadoutClothingHeadHatBeretBrigmedic - category: Jobs - cost: 1 + category: JobsSecurity + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHeadSecurity - !type:CharacterJobRequirement jobs: - Brigmedic @@ -372,10 +426,12 @@ - type: loadout id: LoadoutClothingHeadHatBeretCorpsman - category: Jobs - cost: 1 + category: JobsSecurity + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHeadSecurity - !type:CharacterJobRequirement jobs: - Brigmedic @@ -384,10 +440,12 @@ - type: loadout id: LoadoutClothingHeadHatBeretWarden - category: Jobs - cost: 1 + category: JobsSecurity + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHeadSecurity - !type:CharacterJobRequirement jobs: - Warden @@ -396,10 +454,12 @@ - type: loadout id: LoadoutClothingHeadHatBeretHoS - category: Jobs - cost: 1 + category: JobsSecurity + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHeadSecurity - !type:CharacterJobRequirement jobs: - HeadOfSecurity @@ -408,9 +468,11 @@ - type: loadout id: LoadoutSecurityHeadHelmetInsulated - category: Jobs - cost: 2 + category: JobsSecurity + cost: 1 requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHeadSecurity - !type:CharacterDepartmentRequirement departments: - Security @@ -420,10 +482,12 @@ # Belt - type: loadout id: LoadoutSecurityBeltWebbing - category: Jobs - cost: 1 + category: JobsSecurity + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBeltSecurity - !type:CharacterDepartmentRequirement departments: - Security @@ -432,10 +496,12 @@ - type: loadout id: LoadoutClothingBeltCorpsmanWebbing - category: Jobs - cost: 1 + category: JobsSecurity + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBeltSecurity - !type:CharacterJobRequirement jobs: - Brigmedic @@ -444,36 +510,58 @@ - type: loadout id: LoadoutClothingBeltSecurity - category: Jobs - cost: 1 + category: JobsSecurity + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBeltSecurity + - !type:CharacterDepartmentRequirement + departments: + - Security + items: + - ClothingBeltSecurityFilled + +- type: loadout + id: LoadoutClothingBeltHolster + category: JobsSecurity + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBeltSecurity - !type:CharacterDepartmentRequirement departments: - Security items: - - ClothingBeltSecurity + - ClothingBeltHolster #Gloves - type: loadout id: LoadoutClothingHandsGlovesNitrile - category: Jobs - cost: 1 + category: JobsSecurity + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutGlovesSecurity - !type:CharacterJobRequirement jobs: - Brigmedic items: - ClothingHandsGlovesNitrile +# Outerwear + - type: loadout id: LoadoutClothingOuterArmorPlateCarrier - category: Jobs - cost: 2 + category: JobsSecurity + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuterSecurity - !type:CharacterDepartmentRequirement departments: - Security @@ -482,22 +570,54 @@ - type: loadout id: LoadoutClothingOuterArmorDuraVest - category: Jobs - cost: 2 + category: JobsSecurity + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuterSecurity - !type:CharacterDepartmentRequirement departments: - Security items: - ClothingOuterArmorDuraVest +- type: loadout + id: LoadoutClothingOuterArmorBasic + category: JobsSecurity + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuterSecurity + - !type:CharacterDepartmentRequirement + departments: + - Security + items: + - ClothingOuterArmorBasic + +- type: loadout + id: LoadoutClothingOuterArmorSlim + category: JobsSecurity + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuterSecurity + - !type:CharacterDepartmentRequirement + departments: + - Security + items: + - ClothingOuterArmorBasicSlim + - type: loadout id: LoadoutClothingOuterCoatDetective - category: Jobs - cost: 2 + category: JobsSecurity + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuterSecurity - !type:CharacterJobRequirement jobs: - Detective @@ -506,10 +626,12 @@ - type: loadout id: LoadoutOuterVestDetective - category: Jobs - cost: 2 + category: JobsSecurity + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuterSecurity - !type:CharacterJobRequirement jobs: - Detective @@ -518,10 +640,12 @@ - type: loadout id: LoadoutClothingOuterCoatWarden - category: Jobs - cost: 2 + category: JobsSecurity + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuterSecurity - !type:CharacterJobRequirement jobs: - Warden @@ -530,10 +654,12 @@ - type: loadout id: LoadoutClothingOuterCoatHoSTrench - category: Jobs - cost: 2 + category: JobsSecurity + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuterSecurity - !type:CharacterJobRequirement jobs: - HeadOfSecurity @@ -542,10 +668,12 @@ - type: loadout id: LoadoutClothingOuterWinterHoS - category: Jobs - cost: 2 + category: JobsSecurity + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuterSecurity - !type:CharacterJobRequirement jobs: - HeadOfSecurity @@ -555,10 +683,12 @@ # Neck - type: loadout id: LoadoutClothingNeckCloakHos - category: Jobs - cost: 1 + category: JobsSecurity + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeckSecurity - !type:CharacterJobRequirement jobs: - HeadOfSecurity @@ -567,10 +697,12 @@ - type: loadout id: LoadoutClothingNeckMantleHOS - category: Jobs - cost: 1 + category: JobsSecurity + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeckSecurity - !type:CharacterJobRequirement jobs: - HeadOfSecurity @@ -579,23 +711,28 @@ - type: loadout id: LoadoutBedsheetBrigmedic - category: Jobs - cost: 1 + category: JobsSecurity + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeckSecurity - !type:CharacterJobRequirement jobs: - Brigmedic items: - BedsheetBrigmedic - - -# Equipment + +# Equipment, limit 3 selections +# Duplicate "Spare" equipment exists and shares the ItemGroup, for those officers who like to pack a spare magazine in their pocket, outside of what was issued to them. +# I knew a lot of people in my time working IRL Armed security that did this. - type: loadout id: LoadoutSecurityCombatKnife - category: Jobs - cost: 2 + category: JobsSecurity + cost: 0 requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutEquipmentSecurity - !type:CharacterDepartmentRequirement departments: - Security @@ -604,8 +741,8 @@ - type: loadout id: LoadoutSecurityFlash - category: Jobs - cost: 1 + category: JobsSecurity + cost: 0 requirements: - !type:CharacterDepartmentRequirement departments: @@ -614,57 +751,579 @@ - Flash - type: loadout - id: LoadoutSecurityDisabler - category: Jobs + id: LoadoutMagazinePistol + category: JobsSecurity + cost: 0 + requirements: + - !type:CharacterDepartmentTimeRequirement + department: Security + min: 3600 # 1 hours + - !type:CharacterItemGroupRequirement + group: LoadoutEquipmentSecurity + - !type:CharacterDepartmentRequirement + departments: + - Security + items: + - MagazinePistol + +- type: loadout + id: LoadoutMagazinePistolSpare + category: JobsSecurity cost: 2 requirements: + - !type:CharacterDepartmentTimeRequirement + department: Security + min: 3600 # 1 hours + - !type:CharacterItemGroupRequirement + group: LoadoutEquipmentSecurity - !type:CharacterDepartmentRequirement departments: - Security items: - - WeaponDisabler + - MagazinePistol - type: loadout id: LoadoutMagazinePistolRubber - category: Jobs + category: JobsSecurity + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutEquipmentSecurity + - !type:CharacterDepartmentRequirement + departments: + - Security + items: + - MagazinePistolRubber + +- type: loadout + id: LoadoutMagazinePistolRubberSpare + category: JobsSecurity cost: 2 requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutEquipmentSecurity - !type:CharacterDepartmentRequirement departments: - Security items: - MagazinePistolRubber +- type: loadout + id: LoadoutSpeedLoaderMagnum + category: JobsSecurity + cost: 0 + exclusive: true + requirements: + - !type:CharacterDepartmentTimeRequirement + department: Security + min: 3600 # 1 hours + - !type:CharacterItemGroupRequirement + group: LoadoutEquipmentSecurity + - !type:CharacterDepartmentRequirement + departments: + - Security + items: + - SpeedLoaderMagnum + +- type: loadout + id: LoadoutSpeedLoaderMagnumSpare + category: JobsSecurity + cost: 2 + exclusive: true + requirements: + - !type:CharacterDepartmentTimeRequirement + department: Security + min: 3600 # 1 hours + - !type:CharacterItemGroupRequirement + group: LoadoutEquipmentSecurity + - !type:CharacterDepartmentRequirement + departments: + - Security + items: + - SpeedLoaderMagnum + - type: loadout id: LoadoutSpeedLoaderMagnumRubber - category: Jobs + category: JobsSecurity + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutEquipmentSecurity + - !type:CharacterDepartmentRequirement + departments: + - Security + items: + - SpeedLoaderMagnumRubber + +- type: loadout + id: LoadoutSpeedLoaderMagnumRubberSpare + category: JobsSecurity cost: 2 exclusive: true requirements: - - !type:CharacterJobRequirement - jobs: - - Detective + - !type:CharacterItemGroupRequirement + group: LoadoutEquipmentSecurity + - !type:CharacterDepartmentRequirement + departments: + - Security items: - SpeedLoaderMagnumRubber -# TODO: Make this replace the secoff handgun and make it cheaper -# # Species -# - type: loadout -# id: LoadoutSecurityEquipmentTruncheon -# category: Jobs -# cost: 8 -# requirements: -# - !type:CharacterJobRequirement -# jobs: -# - SecurityOfficer -# - Warden -# - HeadOfSecurity -# - Brigmedic -# - !type:CharacterPlaytimeRequirement -# tracker: JobSecurityOfficer -# min: 36000 # 10 hours -# - !type:CharacterSpeciesRequirement -# species: -# - Oni -# items: -# - Truncheon +- type: loadout + id: LoadoutMagazineMagnum + category: JobsSecurity + cost: 2 + exclusive: true + requirements: + - !type:CharacterDepartmentTimeRequirement + department: Security + min: 3600 # 1 hours + - !type:CharacterItemGroupRequirement + group: LoadoutEquipmentSecurity + - !type:CharacterDepartmentRequirement + departments: + - Security + items: + - MagazineMagnum + +- type: loadout + id: LoadoutMagazineMagnumRubber + category: JobsSecurity + cost: 2 + exclusive: true + requirements: + - !type:CharacterDepartmentTimeRequirement + department: Security + min: 3600 # 1 hours + - !type:CharacterItemGroupRequirement + group: LoadoutEquipmentSecurity + - !type:CharacterDepartmentRequirement + departments: + - Security + items: + - MagazineMagnumRubber + +- type: loadout + id: LoadoutMagazineMagnumSpare + category: JobsSecurity + cost: 2 + exclusive: true + requirements: + - !type:CharacterDepartmentTimeRequirement + department: Security + min: 3600 # 1 hours + - !type:CharacterItemGroupRequirement + group: LoadoutEquipmentSecurity + - !type:CharacterDepartmentRequirement + departments: + - Security + items: + - MagazineMagnum + +- type: loadout + id: LoadoutMagazineMagnumRubberSpare + category: JobsSecurity + cost: 2 + exclusive: true + requirements: + - !type:CharacterDepartmentTimeRequirement + department: Security + min: 3600 # 1 hours + - !type:CharacterItemGroupRequirement + group: LoadoutEquipmentSecurity + - !type:CharacterDepartmentRequirement + departments: + - Security + items: + - MagazineMagnumRubber + +# Service Weapon, limit 1 selection. +# Security no longer spawns with a weapon automatically, instead they have a free choice of security appropriate Duty Pistol in their loadouts. +# This category is universal to the entire security department by special request, so that players can choose their preferred Duty Pistol even if they aren't playing a security role. +# All lethal options come with a 1 hour security department playtime, as a basic shitter protection. +- type: loadout + id: LoadoutSecurityDisabler + category: JobsSecurity + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutWeaponSecurity + - !type:CharacterDepartmentRequirement + departments: + - Security + items: + - WeaponDisabler + +- type: loadout + id: LoadoutSecurityMk58 + category: JobsSecurity + cost: 0 + requirements: + - !type:CharacterDepartmentTimeRequirement + department: Security + min: 3600 # 1 hours + - !type:CharacterItemGroupRequirement + group: LoadoutWeaponSecurity + - !type:CharacterDepartmentRequirement + departments: + - Security + items: + - WeaponPistolMk58Security + +- type: loadout + id: LoadoutSecurityMk58NonLethal + category: JobsSecurity + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutWeaponSecurity + - !type:CharacterDepartmentRequirement + departments: + - Security + items: + - WeaponPistolMk58SecurityNonlethal + +- type: loadout + id: LoadoutSecurityRevolver + category: JobsSecurity + cost: 0 + requirements: + - !type:CharacterDepartmentTimeRequirement + department: Security + min: 3600 # 1 hours + - !type:CharacterItemGroupRequirement + group: LoadoutWeaponSecurity + - !type:CharacterDepartmentRequirement + departments: + - Security + items: + - WeaponRevolverInspectorSecurity + +- type: loadout + id: LoadoutSecurityRevolverNonLethal + category: JobsSecurity + cost: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutWeaponSecurity + - !type:CharacterDepartmentRequirement + departments: + - Security + items: + - WeaponRevolverInspectorNonLethalSecurity + +- type: loadout + id: LoadoutSecurityRevolverDeckard + category: JobsSecurity + cost: 1 + requirements: + - !type:CharacterDepartmentTimeRequirement + department: Security + min: 3600 # 1 hours + - !type:CharacterItemGroupRequirement + group: LoadoutWeaponSecurity + - !type:CharacterDepartmentRequirement + departments: + - Security + items: + - WeaponRevolverDeckardSecurity + +- type: loadout + id: LoadoutSecurityRevolverDeckardNonLethal + category: JobsSecurity + cost: 1 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutWeaponSecurity + - !type:CharacterDepartmentRequirement + departments: + - Security + items: + - WeaponRevolverDeckardNonLethalSecurity + +- type: loadout + id: LoadoutSecurityPistolN1984 + category: JobsSecurity + cost: 2 + requirements: + - !type:CharacterDepartmentTimeRequirement + department: Security + min: 3600 # 1 hours + - !type:CharacterItemGroupRequirement + group: LoadoutWeaponSecurity + - !type:CharacterDepartmentRequirement + departments: + - Security + items: + - WeaponPistolN1984Security + +- type: loadout + id: LoadoutSecurityPistolN1984NonLethal + category: JobsSecurity + cost: 2 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutWeaponSecurity + - !type:CharacterDepartmentRequirement + departments: + - Security + items: + - WeaponPistolN1984SecurityNonLethal + +- type: loadout + id: LoadoutSecurityPistolViper + category: JobsSecurity + cost: 2 + requirements: + - !type:CharacterDepartmentTimeRequirement + department: Security + min: 3600 # 1 hours + - !type:CharacterItemGroupRequirement + group: LoadoutWeaponSecurity + - !type:CharacterDepartmentRequirement + departments: + - Security + items: + - WeaponPistolViperSecurity + +- type: loadout + id: LoadoutSecurityPistolViperNonLethal + category: JobsSecurity + cost: 2 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutWeaponSecurity + - !type:CharacterDepartmentRequirement + departments: + - Security + items: + - WeaponPistolViperSecurityNonLethal + +- type: loadout + id: LoadoutSecurityPistolViperWood + category: JobsSecurity + cost: 2 + requirements: + - !type:CharacterDepartmentTimeRequirement + department: Security + min: 108000 # 30 hours + - !type:CharacterItemGroupRequirement + group: LoadoutWeaponSecurity + - !type:CharacterDepartmentRequirement + departments: + - Security + items: + - WeaponPistolViperWoodSecurity + +- type: loadout + id: LoadoutSecurityEquipmentTruncheon + category: JobsSecurity + cost: 3 + requirements: + - !type:CharacterDepartmentTimeRequirement + department: Security + min: 3600 # 1 hours + - !type:CharacterItemGroupRequirement + group: LoadoutWeaponSecurity + - !type:CharacterDepartmentRequirement + departments: + - Security + - !type:CharacterSpeciesRequirement + species: + - Oni + items: + - Truncheon + +- type: loadout + id: LoadoutSecurityPistolSvalin + category: JobsSecurity + cost: 1 + requirements: + - !type:CharacterDepartmentTimeRequirement + department: Security + min: 3600 # 1 hours + - !type:CharacterItemGroupRequirement + group: LoadoutWeaponSecurity + - !type:CharacterDepartmentRequirement + departments: + - Security + items: + - WeaponLaserSvalinn + +- type: loadout + id: LoadoutSecurityEnergyGunMini + category: JobsSecurity + cost: 2 + requirements: + - !type:CharacterDepartmentTimeRequirement + department: Security + min: 3600 # 1 hours + - !type:CharacterItemGroupRequirement + group: LoadoutWeaponSecurity + - !type:CharacterDepartmentRequirement + departments: + - Security + items: + - WeaponEnergyGunMiniSecurity + +- type: loadout + id: LoadoutSecurityEnergyGunPistol + category: JobsSecurity + cost: 2 + requirements: + - !type:CharacterDepartmentTimeRequirement + department: Security + min: 3600 # 1 hours + - !type:CharacterItemGroupRequirement + group: LoadoutWeaponSecurity + - !type:CharacterDepartmentRequirement + departments: + - Security + items: + - WeaponEnergyGunPistolSecurity + +- type: loadout + id: LoadoutSecurityPistolPollock + category: JobsSecurity + cost: 1 + requirements: + - !type:CharacterDepartmentTimeRequirement + department: Security + min: 3600 # 1 hours + - !type:CharacterItemGroupRequirement + group: LoadoutWeaponSecurity + - !type:CharacterDepartmentRequirement + departments: + - Security + items: + - WeaponPistolPollockSecurity + +- type: loadout + id: LoadoutSecurityPistolPollockNonlethal + category: JobsSecurity + cost: 1 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutWeaponSecurity + - !type:CharacterDepartmentRequirement + departments: + - Security + items: + - WeaponPistolPollockNonlethalSecurity + +- type: loadout + id: LoadoutSecurityRevolverSnub + category: JobsSecurity + cost: 2 + requirements: + - !type:CharacterDepartmentTimeRequirement + department: Security + min: 3600 # 1 hours + - !type:CharacterItemGroupRequirement + group: LoadoutWeaponSecurity + - !type:CharacterDepartmentRequirement + departments: + - Security + items: + - WeaponRevolverSnubSecurity + +- type: loadout + id: LoadoutSecurityRevolverSnubNonlethal + category: JobsSecurity + cost: 2 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutWeaponSecurity + - !type:CharacterDepartmentRequirement + departments: + - Security + items: + - WeaponRevolverSnubNonlethalSecurity + +- type: loadout + id: LoadoutSecurityRevolverK38Master + category: JobsSecurity + cost: 1 + requirements: + - !type:CharacterDepartmentTimeRequirement + department: Security + min: 3600 # 1 hours + - !type:CharacterItemGroupRequirement + group: LoadoutWeaponSecurity + - !type:CharacterDepartmentRequirement + departments: + - Security + items: + - WeaponRevolverK38MasterSecurity + +- type: loadout + id: LoadoutSecurityRevolverK38MasterNonlethal + category: JobsSecurity + cost: 1 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutWeaponSecurity + - !type:CharacterDepartmentRequirement + departments: + - Security + items: + - WeaponRevolverK38MasterNonlethalSecurity + +- type: loadout + id: LoadoutSecurityRevolverFitz + category: JobsSecurity + cost: 1 + requirements: + - !type:CharacterDepartmentTimeRequirement + department: Security + min: 3600 # 1 hours + - !type:CharacterItemGroupRequirement + group: LoadoutWeaponSecurity + - !type:CharacterDepartmentRequirement + departments: + - Security + items: + - WeaponRevolverFitzSecurity + +- type: loadout + id: LoadoutSecurityRevolverFitzNonlethal + category: JobsSecurity + cost: 1 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutWeaponSecurity + - !type:CharacterDepartmentRequirement + departments: + - Security + items: + - WeaponRevolverFitzNonlethalSecurity + +- type: loadout + id: LoadoutSecurityRevolverPython + category: JobsSecurity + cost: 3 + requirements: + - !type:CharacterDepartmentTimeRequirement + department: Security + min: 3600 # 1 hours + - !type:CharacterItemGroupRequirement + group: LoadoutWeaponSecurity + - !type:CharacterDepartmentRequirement + departments: + - Security + items: + - WeaponRevolverPythonSecurity + +- type: loadout + id: LoadoutSecurityRevolverPythonNonlethal + category: JobsSecurity + cost: 3 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutWeaponSecurity + - !type:CharacterDepartmentRequirement + departments: + - Security + items: + - WeaponRevolverPythonNonlethalSecurity diff --git a/Resources/Prototypes/Loadouts/Jobs/service.yml b/Resources/Prototypes/Loadouts/Jobs/service.yml index 4e41f035d7..c0f04ef151 100644 --- a/Resources/Prototypes/Loadouts/Jobs/service.yml +++ b/Resources/Prototypes/Loadouts/Jobs/service.yml @@ -1,10 +1,12 @@ # Clown - type: loadout id: LoadoutServiceClownOutfitJester - category: Jobs - cost: 3 + category: JobsServiceUncategorized + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsService - !type:CharacterJobRequirement jobs: - Clown @@ -15,10 +17,12 @@ - type: loadout id: LoadoutServiceClownOutfitJesterAlt - category: Jobs - cost: 3 + category: JobsServiceUncategorized + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsService - !type:CharacterJobRequirement jobs: - Clown @@ -29,10 +33,12 @@ - type: loadout id: LoadoutServiceClownOuterWinter - category: Jobs - cost: 2 + category: JobsServiceUncategorized + cost: 1 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuterService - !type:CharacterJobRequirement jobs: - Clown @@ -41,10 +47,12 @@ - type: loadout id: LoadoutServiceClownOuterClownPriest - category: Jobs - cost: 2 + category: JobsServiceUncategorized + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuterService - !type:CharacterJobRequirement jobs: - Clown @@ -53,27 +61,26 @@ - type: loadout id: LoadoutServiceClownBootsWinter - category: Jobs - cost: 1 + category: JobsServiceUncategorized + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutShoesService - !type:CharacterJobRequirement jobs: - Clown - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Diona - - Harpy items: - ClothingShoesBootsWinterClown - type: loadout id: LoadoutServiceClownMaskSexy - category: Jobs - cost: 1 + category: JobsServiceUncategorized + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMaskService - !type:CharacterJobRequirement jobs: - Clown @@ -82,23 +89,75 @@ - type: loadout id: LoadoutServiceClownBedsheetClown - category: Jobs - cost: 2 + category: JobsServiceUncategorized + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeckService - !type:CharacterJobRequirement jobs: - Clown items: - BedsheetClown +# For the most part we dont want people to take this item, so its used as an example of all the things +# you can do with requirements. Point someone to this thing if they ask "how tf do loadout requirements work?" + +- type: loadout + id: LoadoutServiceClownCowToolboxFilled + category: JobsServiceUncategorized + cost: 2 + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutEquipmentService + - !type:CharacterLogicXorRequirement + requirements: + - !type:CharacterLogicAndRequirement + requirements: + - !type:CharacterSexRequirement + sex: Male + - !type:CharacterSpeciesRequirement + species: + - Felinid + - !type:CharacterHeightRequirement + min: 110 + max: 122 + - !type:CharacterGenderRequirement + gender: Female + - !type:CharacterTraitRequirement + traits: + - HeavyweightDrunk + - !type:CharacterLogicAndRequirement + requirements: + - !type:CharacterSpeciesRequirement + species: + - Harpy + - !type:CharacterHeightRequirement + min: 170 + - !type:CharacterWeightRequirement + min: 20 + - !type:CharacterSexRequirement + sex: Male + - !type:CharacterJobRequirement + inverted: true # This is the equivalent of !(condition) + jobs: + - Clown + - !type:CharacterJobRequirement + jobs: + - Clown + items: + - CowToolboxFilled + # Mime - type: loadout id: LoadoutServiceMimeOuterWinter - category: Jobs - cost: 2 + category: JobsServiceUncategorized + cost: 1 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuterService - !type:CharacterJobRequirement jobs: - Mime @@ -107,10 +166,12 @@ - type: loadout id: LoadoutServiceMimeMaskSad - category: Jobs - cost: 1 + category: JobsServiceUncategorized + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMaskService - !type:CharacterJobRequirement jobs: - Mime @@ -119,10 +180,12 @@ - type: loadout id: LoadoutServiceMimeMaskScared - category: Jobs - cost: 1 + category: JobsServiceUncategorized + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMaskService - !type:CharacterJobRequirement jobs: - Mime @@ -131,10 +194,12 @@ - type: loadout id: LoadoutServiceMimeMaskSexy - category: Jobs - cost: 1 + category: JobsServiceUncategorized + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMaskService - !type:CharacterJobRequirement jobs: - Mime @@ -143,27 +208,26 @@ - type: loadout id: LoadoutServiceMimeShoesBootsWinter - category: Jobs - cost: 1 + category: JobsServiceUncategorized + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutShoesService - !type:CharacterJobRequirement jobs: - Mime - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Diona - - Harpy items: - ClothingShoesBootsWinterMime - type: loadout id: LoadoutServiceMimeBedsheetMime - category: Jobs - cost: 2 + category: JobsServiceUncategorized + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeckService - !type:CharacterJobRequirement jobs: - Mime @@ -173,52 +237,294 @@ # Bartender - type: loadout id: LoadoutServiceBartenderUniformPurple - category: Jobs - cost: 2 + category: JobsServiceBartender + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsService - !type:CharacterJobRequirement jobs: - Bartender items: - ClothingUniformJumpsuitBartenderPurple +- type: loadout + id: LoadoutServiceBartenderArmorDuraVest + category: JobsServiceBartender + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBartenderOuterwear + - !type:CharacterJobRequirement + jobs: + - Bartender + items: + - ClothingOuterArmorDuraVest + +- type: loadout + id: LoadoutServiceBartenderBoxBeanbags + category: JobsServiceBartender + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBartenderAmmo + - !type:CharacterJobRequirement + jobs: + - Bartender + items: + - BoxBeanbag + +- type: loadout + id: LoadoutServiceBartenderBoxLightRifleRubber + category: JobsServiceBartender + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBartenderAmmo + - !type:CharacterJobRequirement + jobs: + - Bartender + items: + - MagazineBoxLightRifleRubber + +- type: loadout + id: LoadoutServiceBartenderShotgunDoubleBarreledRubber + category: JobsServiceBartender + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBartenderWeapon + - !type:CharacterJobRequirement + jobs: + - Bartender + items: + - WeaponShotgunDoubleBarreledRubber + +- type: loadout + id: LoadoutServiceBartenderMosinRubber + category: JobsServiceBartender + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBartenderWeapon + - !type:CharacterJobRequirement + jobs: + - Bartender + items: + - WeaponSniperMosinRubber + +- type: loadout + id: LoadoutServiceJumpsuitBartenderNt + category: JobsServiceBartender + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBartenderUniforms + - !type:CharacterJobRequirement + jobs: + - Bartender + items: + - ClothingUniformJumpsuitBartenderNt + +- type: loadout + id: LoadoutServiceJumpsuitBartenderIdris + category: JobsServiceBartender + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBartenderUniforms + - !type:CharacterJobRequirement + jobs: + - Bartender + items: + - ClothingUniformJumpsuitBartenderIdris + +- type: loadout + id: LoadoutServiceJumpsuitBartenderOrion + category: JobsServiceBartender + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBartenderUniforms + - !type:CharacterJobRequirement + jobs: + - Bartender + items: + - ClothingUniformJumpsuitBartenderOrion + +- type: loadout + id: LoadoutServiceHeadBartenderNt + category: JobsServiceBartender + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBartenderHead + - !type:CharacterJobRequirement + jobs: + - Bartender + items: + - ClothingHeadHatFlatcapBartenderNanotrasen + +- type: loadout + id: LoadoutServiceHeadBartenderIdris + category: JobsServiceBartender + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBartenderHead + - !type:CharacterJobRequirement + jobs: + - Bartender + items: + - ClothingHeadHatFlatcapBartenderIdris + +- type: loadout + id: LoadoutServiceHeadBartenderOrion + category: JobsServiceBartender + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBartenderHead + - !type:CharacterJobRequirement + jobs: + - Bartender + items: + - ClothingHeadHatFlatcapBartenderOrion + +- type: loadout + id: LoadoutServiceOuterBartenderNt + category: JobsServiceBartender + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBartenderOuterwear + - !type:CharacterJobRequirement + jobs: + - Bartender + items: + - ClothingOuterVestNt + +- type: loadout + id: LoadoutServiceOuterBartenderIdris + category: JobsServiceBartender + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBartenderOuterwear + - !type:CharacterJobRequirement + jobs: + - Bartender + items: + - ClothingOuterVestIdris + +- type: loadout + id: LoadoutServiceOuterBartenderOrion + category: JobsServiceBartender + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBartenderOuterwear + - !type:CharacterJobRequirement + jobs: + - Bartender + items: + - ClothingOuterVestOrion + # Botanist - type: loadout id: LoadoutServiceBotanistUniformOveralls - category: Jobs - cost: 2 + category: JobsServiceBotanist + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsService - !type:CharacterJobRequirement - jobs: + jobs: - Botanist items: - ClothingUniformOveralls +- type: loadout + id: LoadoutServiceJumpsuitHydroponicsNt + category: JobsServiceBotanist + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBotanistUniforms + - !type:CharacterJobRequirement + jobs: + - Botanist + items: + - ClothingUniformJumpsuitHydroponicsNt + +- type: loadout + id: LoadoutServiceJumpsuitHydroponicsIdris + category: JobsServiceBotanist + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBotanistUniforms + - !type:CharacterJobRequirement + jobs: + - Botanist + items: + - ClothingUniformJumpsuitHydroponicsIdris + +- type: loadout + id: LoadoutServiceJumpsuitHydroponicsOrion + category: JobsServiceBotanist + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBotanistUniforms + - !type:CharacterJobRequirement + jobs: + - Botanist + items: + - ClothingUniformJumpsuitHydroponicsOrion + # Lawyer - type: loadout id: LoadoutServiceLawyerUniformBlueSuit - category: Jobs - cost: 2 + category: JobsServiceUncategorized + cost: 0 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsService - !type:CharacterJobRequirement - jobs: + jobs: - Lawyer items: - ClothingUniformJumpsuitLawyerBlue - type: loadout id: LoadoutServiceLawyerUniformBlueSkirt - category: Jobs - cost: 2 + category: JobsServiceUncategorized + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsService - !type:CharacterJobRequirement jobs: - Lawyer @@ -227,14 +533,12 @@ - type: loadout id: LoadoutServiceLawyerUniformRedSuit - category: Jobs - cost: 2 + category: JobsServiceUncategorized + cost: 0 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsService - !type:CharacterJobRequirement jobs: - Lawyer @@ -243,10 +547,12 @@ - type: loadout id: LoadoutServiceLawyerUniformRedSkirt - category: Jobs - cost: 2 + category: JobsServiceUncategorized + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsService - !type:CharacterJobRequirement jobs: - Lawyer @@ -255,14 +561,12 @@ - type: loadout id: LoadoutServiceLawyerUniformPurpleSuit - category: Jobs - cost: 2 + category: JobsServiceUncategorized + cost: 0 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsService - !type:CharacterJobRequirement jobs: - Lawyer @@ -271,10 +575,12 @@ - type: loadout id: LoadoutServiceLawyerUniformPurpleSkirt - category: Jobs - cost: 2 + category: JobsServiceUncategorized + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsService - !type:CharacterJobRequirement jobs: - Lawyer @@ -283,14 +589,12 @@ - type: loadout id: LoadoutServiceLawyerUniformGoodSuit - category: Jobs - cost: 2 + category: JobsServiceUncategorized + cost: 0 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsService - !type:CharacterJobRequirement jobs: - Lawyer @@ -299,10 +603,12 @@ - type: loadout id: LoadoutServiceLawyerUniformGoodSkirt - category: Jobs - cost: 2 + category: JobsServiceUncategorized + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsService - !type:CharacterJobRequirement jobs: - Lawyer @@ -311,10 +617,12 @@ - type: loadout id: LoadoutServiceReporterUniformJournalist - category: Jobs - cost: 2 + category: JobsServiceUncategorized + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsService - !type:CharacterJobRequirement jobs: - Reporter @@ -324,14 +632,12 @@ # Reporter - type: loadout id: LoadoutServiceReporterUniformDetectivesuit - category: Jobs - cost: 2 + category: JobsServiceUncategorized + cost: 0 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsService - !type:CharacterJobRequirement jobs: - Reporter @@ -340,24 +646,184 @@ - type: loadout id: LoadoutServiceReporterUniformDetectiveskirt - category: Jobs - cost: 2 + category: JobsServiceUncategorized + cost: 0 exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsService - !type:CharacterJobRequirement jobs: - Reporter items: - ClothingUniformJumpskirtDetective -# Musician +# Chef +- type: loadout + id: LoadoutServiceJumpsuitChefNt + category: JobsServiceChef + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutChefUniforms + - !type:CharacterJobRequirement + jobs: + - Chef + items: + - ClothingUniformJumpsuitChefNt + +- type: loadout + id: LoadoutServiceJumpsuitChefIdris + category: JobsServiceChef + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutChefUniforms + - !type:CharacterJobRequirement + jobs: + - Chef + items: + - ClothingUniformJumpsuitChefIdris + +- type: loadout + id: LoadoutServiceJumpsuitChefOrion + category: JobsServiceChef + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutChefUniforms + - !type:CharacterJobRequirement + jobs: + - Chef + items: + - ClothingUniformJumpsuitChefOrion + +- type: loadout + id: LoadoutServiceHeadChefNt + category: JobsServiceChef + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutChefHead + - !type:CharacterJobRequirement + jobs: + - Chef + items: + - ClothingHeadHatChefNt + +- type: loadout + id: LoadoutServiceHeadChefIdris + category: JobsServiceChef + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutChefHead + - !type:CharacterJobRequirement + jobs: + - Chef + items: + - ClothingHeadHatChefIdris + +- type: loadout + id: LoadoutServiceHeadChefOrion + category: JobsServiceChef + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutChefHead + - !type:CharacterJobRequirement + jobs: + - Chef + items: + - ClothingHeadHatChefOrion + +- type: loadout + id: LoadoutServiceOuterChefNt + category: JobsServiceChef + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutChefOuter + - !type:CharacterJobRequirement + jobs: + - Chef + items: + - ClothingOuterJacketChefNt + +- type: loadout + id: LoadoutServiceOuterChefIdris + category: JobsServiceChef + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutChefOuter + - !type:CharacterJobRequirement + jobs: + - Chef + items: + - ClothingOuterJacketChefIdris + +- type: loadout + id: LoadoutServiceOuterChefOrion + category: JobsServiceChef + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutChefOuter + - !type:CharacterJobRequirement + jobs: + - Chef + items: + - ClothingOuterJacketChefOrion + +# Janitor - type: loadout - id: LoadoutItemSynthesizerInstrument - category: Jobs - cost: 8 + id: LoadoutServiceJumpsuitJanitorNt + category: JobsServiceJanitor + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutJanitorUniforms + - !type:CharacterJobRequirement + jobs: + - Janitor + items: + - ClothingUniformJumpsuitJanitorNt + +- type: loadout + id: LoadoutServiceJumpsuitJanitorIdris + category: JobsServiceJanitor + cost: 0 + exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutJanitorUniforms + - !type:CharacterJobRequirement + jobs: + - Janitor + items: + - ClothingUniformJumpsuitJanitorIdris + +- type: loadout + id: LoadoutServiceJumpsuitJanitorOrion + category: JobsServiceJanitor + cost: 0 + exclusive: true requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutJanitorUniforms - !type:CharacterJobRequirement jobs: - - Musician + - Janitor items: - - SynthesizerInstrument + - ClothingUniformJumpsuitJanitorOrion diff --git a/Resources/Prototypes/Loadouts/backpacks.yml b/Resources/Prototypes/Loadouts/backpacks.yml new file mode 100644 index 0000000000..5fd812380c --- /dev/null +++ b/Resources/Prototypes/Loadouts/backpacks.yml @@ -0,0 +1,268 @@ +- type: loadout + id: LoadoutBackpack + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpack + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + +- type: loadout + id: LoadoutBackpackClown + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackClown + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterJobRequirement + jobs: + - Clown + +- type: loadout + id: LoadoutBackpackIan + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackIan + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterJobRequirement + jobs: + - HeadOfPersonnel + +- type: loadout + id: LoadoutBackpackSecurity + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackSecurity + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterDepartmentRequirement + departments: + - Security + +- type: loadout + id: LoadoutBackpackBrigmedic + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackBrigmedic + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterJobRequirement + jobs: + - Brigmedic + +- type: loadout + id: LoadoutBackpackEngineering + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackEngineering + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterDepartmentRequirement + departments: + - Engineering + +- type: loadout + id: LoadoutBackpackAtmospherics + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackAtmospherics + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterDepartmentRequirement + departments: + - Engineering + +- type: loadout + id: LoadoutBackpackMedical + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackMedical + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterDepartmentRequirement + departments: + - Medical + +- type: loadout + id: LoadoutBackpackCaptain + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackCaptain + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterJobRequirement + jobs: + - Captain + +- type: loadout + id: LoadoutBackpackMime + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackMime + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterJobRequirement + jobs: + - Mime + +- type: loadout + id: LoadoutBackpackChemistry + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackChemistry + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterDepartmentRequirement + departments: + - Medical + +- type: loadout + id: LoadoutBackpackHydroponics + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackHydroponics + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterDepartmentRequirement + departments: + - Civilian + +- type: loadout + id: LoadoutBackpackScience + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackScience + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterDepartmentRequirement + departments: + - Epistemics + +- type: loadout + id: LoadoutBackpackRobotics + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackRobotics + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterDepartmentRequirement + departments: + - Epistemics + +- type: loadout + id: LoadoutBackpackVirology + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackVirology + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterDepartmentRequirement + departments: + - Medical + +- type: loadout + id: LoadoutBackpackGenetics + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackGenetics + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterDepartmentRequirement + departments: + - Medical + +- type: loadout + id: LoadoutBackpackCargo + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackCargo + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterDepartmentRequirement + departments: + - Logistics + +- type: loadout + id: LoadoutBackpackSalvage + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackSalvage + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterDepartmentRequirement + departments: + - Logistics + +- type: loadout + id: LoadoutBackpackMerc + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackMerc + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterLogicOrRequirement + requirements: + - !type:CharacterDepartmentRequirement + departments: + - Security + - Civilian + - !type:CharacterJobRequirement + jobs: + - SalvageSpecialist diff --git a/Resources/Prototypes/Loadouts/categories.yml b/Resources/Prototypes/Loadouts/categories.yml deleted file mode 100644 index 0dfccb096c..0000000000 --- a/Resources/Prototypes/Loadouts/categories.yml +++ /dev/null @@ -1,37 +0,0 @@ -# Alphabetically ordered, except for Uncategorized since it is always first - -- type: loadoutCategory - id: Uncategorized - -- type: loadoutCategory - id: Eyes - -- type: loadoutCategory - id: Hands - -- type: loadoutCategory - id: Head - -- type: loadoutCategory - id: Items - -- type: loadoutCategory - id: Jobs - -- type: loadoutCategory - id: Mask - -- type: loadoutCategory - id: Neck - -- type: loadoutCategory - id: Outer - -- type: loadoutCategory - id: Shoes - -- type: loadoutCategory - id: Species - -- type: loadoutCategory - id: Uniform diff --git a/Resources/Prototypes/Loadouts/duffelbags.yml b/Resources/Prototypes/Loadouts/duffelbags.yml new file mode 100644 index 0000000000..89997326f1 --- /dev/null +++ b/Resources/Prototypes/Loadouts/duffelbags.yml @@ -0,0 +1,234 @@ +- type: loadout + id: LoadoutBackpackDuffel + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackDuffel + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + +- type: loadout + id: LoadoutBackpackDuffelClown + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackDuffelClown + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterJobRequirement + jobs: + - Clown + +- type: loadout + id: LoadoutBackpackDuffelSecurity + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackDuffelSecurity + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterDepartmentRequirement + departments: + - Security + +- type: loadout + id: LoadoutBackpackDuffelBrigmedic + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackDuffelBrigmedic + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterJobRequirement + jobs: + - Brigmedic + +- type: loadout + id: LoadoutBackpackDuffelEngineering + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackDuffelEngineering + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterDepartmentRequirement + departments: + - Engineering + +- type: loadout + id: LoadoutBackpackDuffelAtmospherics + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackDuffelAtmospherics + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterDepartmentRequirement + departments: + - Engineering + +- type: loadout + id: LoadoutBackpackDuffelMedical + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackDuffelMedical + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterDepartmentRequirement + departments: + - Medical + +- type: loadout + id: LoadoutBackpackDuffelCaptain + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackDuffelCaptain + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterJobRequirement + jobs: + - Captain + +- type: loadout + id: LoadoutBackpackDuffelMime + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackDuffelMime + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterJobRequirement + jobs: + - Mime + +- type: loadout + id: LoadoutBackpackDuffelChemistry + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackDuffelChemistry + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterDepartmentRequirement + departments: + - Medical + +- type: loadout + id: LoadoutBackpackDuffelHydroponics + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackDuffelHydroponics + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterDepartmentRequirement + departments: + - Civilian + +- type: loadout + id: LoadoutBackpackDuffelScience + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackDuffelScience + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterDepartmentRequirement + departments: + - Epistemics + +- type: loadout + id: LoadoutBackpackDuffelRobotics + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackDuffelRobotics + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterDepartmentRequirement + departments: + - Epistemics + +- type: loadout + id: LoadoutBackpackDuffelVirology + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackDuffelVirology + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterDepartmentRequirement + departments: + - Medical + +- type: loadout + id: LoadoutBackpackDuffelGenetics + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackDuffelGenetics + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterDepartmentRequirement + departments: + - Medical + +- type: loadout + id: LoadoutBackpackDuffelCargo + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackDuffelCargo + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterDepartmentRequirement + departments: + - Logistics + +- type: loadout + id: LoadoutBackpackDuffelSalvage + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackDuffelSalvage + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterDepartmentRequirement + departments: + - Logistics diff --git a/Resources/Prototypes/Loadouts/eyes.yml b/Resources/Prototypes/Loadouts/eyes.yml index 8278c3ed49..e0d5636c5f 100644 --- a/Resources/Prototypes/Loadouts/eyes.yml +++ b/Resources/Prototypes/Loadouts/eyes.yml @@ -1,60 +1,84 @@ - type: loadout id: LoadoutEyesEyepatch category: Eyes - cost: 1 + cost: 0 items: - ClothingEyesEyepatch + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutEyes - type: loadout id: LoadoutEyesGlasses category: Eyes - cost: 1 + cost: 0 items: - ClothingEyesGlasses + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutEyes - type: loadout id: LoadoutEyesGlassesJamjar category: Eyes - cost: 2 + cost: 1 exclusive: true items: - ClothingEyesGlassesJamjar + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutEyes - type: loadout id: LoadoutEyesGlassesJensen category: Eyes - cost: 2 + cost: 1 exclusive: true items: - ClothingEyesGlassesJensen + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutEyes - type: loadout id: LoadoutEyesBlindfold category: Eyes - cost: 2 + cost: 0 items: - ClothingEyesBlindfold + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutEyes - type: loadout id: LoadoutItemCheapSunglasses category: Eyes - cost: 2 + cost: 1 exclusive: true items: - ClothingEyesGlassesCheapSunglasses + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutEyes - type: loadout id: LoadoutItemSunglasses category: Eyes - cost: 5 + cost: 3 exclusive: true items: - ClothingEyesGlassesSunglasses + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutEyes - type: loadout id: LoadoutItemBlindfoldFake category: Eyes - cost: 2 + cost: 0 exclusive: true items: - - ClothingEyesBlindfoldFake \ No newline at end of file + - ClothingEyesBlindfoldFake + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutEyes diff --git a/Resources/Prototypes/Loadouts/hands.yml b/Resources/Prototypes/Loadouts/hands.yml index 6cef642057..3861b180b1 100644 --- a/Resources/Prototypes/Loadouts/hands.yml +++ b/Resources/Prototypes/Loadouts/hands.yml @@ -1,123 +1,167 @@ - type: loadout id: LoadoutHandsColorPurple category: Hands - cost: 1 + cost: 0 exclusive: true items: - ClothingHandsGlovesColorPurple + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutGloves - type: loadout id: LoadoutHandsColorRed category: Hands - cost: 1 + cost: 0 exclusive: true items: - ClothingHandsGlovesColorRed + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutGloves - type: loadout id: LoadoutHandsColorBlack category: Hands - cost: 1 + cost: 0 exclusive: true items: - ClothingHandsGlovesColorBlack + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutGloves - type: loadout id: LoadoutHandsColorBlue category: Hands - cost: 1 + cost: 0 exclusive: true items: - ClothingHandsGlovesColorBlue + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutGloves - type: loadout id: LoadoutHandsColorBrown category: Hands - cost: 1 + cost: 0 exclusive: true items: - ClothingHandsGlovesColorBrown + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutGloves - type: loadout id: LoadoutHandsColorGray category: Hands - cost: 1 + cost: 0 exclusive: true items: - ClothingHandsGlovesColorGray + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutGloves - type: loadout id: LoadoutHandsColorGreen category: Hands - cost: 1 + cost: 0 exclusive: true items: - ClothingHandsGlovesColorGreen + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutGloves - type: loadout id: LoadoutHandsColorLightBrown category: Hands - cost: 1 + cost: 0 exclusive: true items: - ClothingHandsGlovesColorLightBrown + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutGloves - type: loadout id: LoadoutHandsColorOrange category: Hands - cost: 1 + cost: 0 exclusive: true items: - ClothingHandsGlovesColorOrange + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutGloves - type: loadout id: LoadoutHandsColorWhite category: Hands - cost: 1 + cost: 0 exclusive: true items: - ClothingHandsGlovesColorWhite + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutGloves - type: loadout id: LoadoutHandsColorYellowBudget category: Hands - cost: 4 + cost: 3 exclusive: true requirements: - !type:CharacterJobRequirement jobs: - Passenger + - !type:CharacterItemGroupRequirement + group: LoadoutGloves items: - ClothingHandsGlovesColorYellowBudget - type: loadout id: LoadoutHandsGlovesLeather category: Hands - cost: 1 + cost: 0 exclusive: true items: - ClothingHandsGlovesLeather + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutGloves - type: loadout id: LoadoutHandsGlovesPowerglove category: Hands - cost: 2 + cost: 1 exclusive: true items: - ClothingHandsGlovesPowerglove + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutGloves - type: loadout id: LoadoutHandsGlovesRobohands category: Hands - cost: 1 + cost: 0 exclusive: true items: - ClothingHandsGlovesRobohands + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutGloves - type: loadout id: LoadoutHandsGlovesFingerless category: Hands - cost: 1 + cost: 0 exclusive: true items: - ClothingHandsGlovesFingerless + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutGloves diff --git a/Resources/Prototypes/Loadouts/head.yml b/Resources/Prototypes/Loadouts/head.yml index 1b47e307b7..27e2ea8e7d 100644 --- a/Resources/Prototypes/Loadouts/head.yml +++ b/Resources/Prototypes/Loadouts/head.yml @@ -2,7 +2,7 @@ - type: loadout id: LoadoutHeadBeaverHat category: Head - cost: 2 + cost: 1 exclusive: true items: - ClothingHeadHatBeaverHat @@ -11,27 +11,35 @@ inverted: true departments: - Security + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadTophat category: Head - cost: 2 + cost: 1 exclusive: true items: - ClothingHeadHatTophat + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadFedoraBlack category: Head - cost: 2 + cost: 1 exclusive: true items: - ClothingHeadHatFedoraBlack + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadFedoraBrown category: Head - cost: 2 + cost: 1 exclusive: true items: - ClothingHeadHatFedoraBrown @@ -40,19 +48,24 @@ inverted: true departments: - Security + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadFedoraGrey category: Head - cost: 2 + cost: 1 exclusive: true items: - ClothingHeadHatFedoraGrey + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadFedoraChoc category: Head - cost: 2 + cost: 1 exclusive: true items: - ClothingHeadHatFedoraChoc @@ -61,27 +74,35 @@ inverted: true departments: - Security + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadFedoraWhite category: Head - cost: 2 + cost: 1 exclusive: true items: - ClothingHeadHatFedoraWhite + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadFlatBlack category: Head - cost: 2 + cost: 1 exclusive: true items: - ClothingHeadHatFlatBlack + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadFlatBrown category: Head - cost: 2 + cost: 1 exclusive: true items: - ClothingHeadHatFlatBrown @@ -90,84 +111,113 @@ inverted: true departments: - Security + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadHatCowboyBrown category: Head - cost: 2 + cost: 1 exclusive: true items: - ClothingHeadHatCowboyBrown + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadHatCowboyBlack category: Head - cost: 2 + cost: 1 exclusive: true items: - ClothingHeadHatCowboyBlack + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadHatCowboyGrey category: Head - cost: 2 + cost: 1 exclusive: true items: - ClothingHeadHatCowboyGrey + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadHatCowboyRed category: Head - cost: 2 + cost: 1 exclusive: true items: - ClothingHeadHatCowboyRed + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadHatCowboyWhite category: Head - cost: 2 + cost: 1 exclusive: true items: - ClothingHeadHatCowboyWhite + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadHatCowboyBountyHunter category: Head - cost: 3 + cost: 1 exclusive: true items: - ClothingHeadHatCowboyBountyHunter + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadTinfoil category: Head - cost: 3 + cost: 2 exclusive: true items: - ClothingHeadTinfoil + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadBellhop category: Head - cost: 2 + cost: 1 exclusive: true items: - ClothingHeadHatBellhop + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadPoppy category: Head - cost: 1 + cost: 0 exclusive: true items: - FoodPoppy + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHead # Color Hats - type: loadout id: LoadoutHeadHatBluesoft category: Head - cost: 1 + cost: 0 exclusive: true items: - ClothingHeadHatBluesoft @@ -176,11 +226,13 @@ inverted: true departments: - Security + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadHatBluesoftFlipped category: Head - cost: 1 + cost: 0 exclusive: true items: - ClothingHeadHatBluesoftFlipped @@ -189,27 +241,35 @@ inverted: true departments: - Security + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadHatCorpsoft category: Head - cost: 1 + cost: 0 exclusive: true items: - ClothingHeadHatCorpsoft + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadHatCorpsoftFlipped category: Head - cost: 1 + cost: 0 exclusive: true items: - ClothingHeadHatCorpsoftFlipped + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadHatGreensoft category: Head - cost: 1 + cost: 0 exclusive: true items: - ClothingHeadHatGreensoft @@ -218,11 +278,13 @@ inverted: true departments: - Security + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadHatGreensoftFlipped category: Head - cost: 1 + cost: 0 exclusive: true items: - ClothingHeadHatGreensoftFlipped @@ -231,43 +293,57 @@ inverted: true departments: - Security + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadHatGreysoft category: Head - cost: 1 + cost: 0 exclusive: true items: - ClothingHeadHatGreysoft + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadHatGreysoftFlipped category: Head - cost: 1 + cost: 0 exclusive: true items: - ClothingHeadHatGreysoftFlipped + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadHatMimesoft category: Head - cost: 1 + cost: 0 exclusive: true items: - ClothingHeadHatMimesoft + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadHatMimesoftFlipped category: Head - cost: 1 + cost: 0 exclusive: true items: - ClothingHeadHatMimesoftFlipped + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadHatOrangesoft category: Head - cost: 1 + cost: 0 exclusive: true items: - ClothingHeadHatOrangesoft @@ -276,11 +352,13 @@ inverted: true departments: - Security + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadHatOrangesoftFlipped category: Head - cost: 1 + cost: 0 exclusive: true items: - ClothingHeadHatOrangesoftFlipped @@ -289,11 +367,13 @@ inverted: true departments: - Security + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadHatPurplesoft category: Head - cost: 1 + cost: 0 exclusive: true items: - ClothingHeadHatPurplesoft @@ -302,11 +382,13 @@ inverted: true departments: - Security + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadHatPurplesoftFlipped category: Head - cost: 1 + cost: 0 exclusive: true items: - ClothingHeadHatPurplesoftFlipped @@ -315,27 +397,35 @@ inverted: true departments: - Security + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadHatRedsoft category: Head - cost: 1 + cost: 0 exclusive: true items: - ClothingHeadHatRedsoft + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadHatRedsoftFlipped category: Head - cost: 1 + cost: 0 exclusive: true items: - ClothingHeadHatRedsoftFlipped + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadHatYellowsoft category: Head - cost: 1 + cost: 0 exclusive: true items: - ClothingHeadHatYellowsoft @@ -344,11 +434,13 @@ inverted: true departments: - Security + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadHatYellowsoftFlipped category: Head - cost: 1 + cost: 0 exclusive: true items: - ClothingHeadHatYellowsoftFlipped @@ -357,20 +449,25 @@ inverted: true departments: - Security + - !type:CharacterItemGroupRequirement + group: LoadoutHead # Headbands - type: loadout id: LoadoutHeadBandBlack category: Head - cost: 1 + cost: 0 exclusive: true items: - ClothingHeadBandBlack + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadBandBlue category: Head - cost: 1 + cost: 0 exclusive: true items: - ClothingHeadBandBlue @@ -379,11 +476,13 @@ inverted: true departments: - Security + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadBandGold category: Head - cost: 1 + cost: 0 exclusive: true items: - ClothingHeadBandGold @@ -392,11 +491,13 @@ inverted: true departments: - Security + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadBandGreen category: Head - cost: 1 + cost: 0 exclusive: true items: - ClothingHeadBandGreen @@ -405,11 +506,13 @@ inverted: true departments: - Security + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadBandGrey category: Head - cost: 1 + cost: 0 exclusive: true items: - ClothingHeadBandGrey @@ -418,27 +521,35 @@ inverted: true departments: - Security + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadBandRed category: Head - cost: 1 + cost: 0 exclusive: true items: - ClothingHeadBandRed + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadBandSkull category: Head - cost: 1 + cost: 0 exclusive: true items: - ClothingHeadBandSkull + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadBandMerc category: Head - cost: 2 + cost: 1 exclusive: true items: - ClothingHeadBandMerc @@ -447,11 +558,13 @@ inverted: true departments: - Security + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadBandBrown category: Head - cost: 1 + cost: 0 exclusive: true items: - ClothingHeadBandBrown @@ -460,11 +573,13 @@ inverted: true departments: - Security + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadFishCap category: Head - cost: 2 + cost: 1 exclusive: true items: - ClothingHeadFishCap @@ -473,11 +588,13 @@ inverted: true departments: - Security + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadRastaHat category: Head - cost: 4 + cost: 2 exclusive: true items: - ClothingHeadRastaHat @@ -486,36 +603,47 @@ inverted: true departments: - Security + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadFez category: Head - cost: 2 + cost: 1 exclusive: true items: - ClothingHeadHatFez + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadBowlerHat category: Head - cost: 2 + cost: 1 exclusive: true items: - ClothingHeadHatBowlerHat + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHead # Flatcaps - type: loadout id: LoadoutHeadGreyFlatcap category: Head - cost: 2 + cost: 1 exclusive: true items: - ClothingHeadHatGreyFlatcap + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadBrownFlatcap category: Head - cost: 2 + cost: 1 exclusive: true items: - ClothingHeadHatBrownFlatcap @@ -524,29 +652,37 @@ inverted: true departments: - Security + - !type:CharacterItemGroupRequirement + group: LoadoutHead # Berets - type: loadout id: LoadoutHeadBeret category: Head - cost: 2 + cost: 1 exclusive: true items: - ClothingHeadHatBeret + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadBeretFrench category: Head - cost: 2 + cost: 1 exclusive: true items: - ClothingHeadHatBeretFrench + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHead # Cowboy hats - type: loadout id: LoadoutHeadCowboyBrown category: Head - cost: 2 + cost: 1 exclusive: true items: - ClothingHeadHatCowboyBrown @@ -555,35 +691,49 @@ inverted: true departments: - Security + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadCowboyBlack category: Head - cost: 2 + cost: 1 exclusive: true items: - ClothingHeadHatCowboyBlack + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadCowboyWhite category: Head - cost: 2 + cost: 1 exclusive: true items: - ClothingHeadHatCowboyWhite + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadCowboyGrey category: Head - cost: 2 + cost: 1 exclusive: true items: - ClothingHeadHatCowboyGrey + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHead - type: loadout id: LoadoutHeadCowboyRed category: Head - cost: 2 + cost: 1 exclusive: true items: - ClothingHeadHatCowboyRed + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutHead diff --git a/Resources/Prototypes/Loadouts/items.yml b/Resources/Prototypes/Loadouts/items.yml index 87ba2e71d4..d992c93fa0 100644 --- a/Resources/Prototypes/Loadouts/items.yml +++ b/Resources/Prototypes/Loadouts/items.yml @@ -2,191 +2,337 @@ - type: loadout id: LoadoutItemCig category: Items - cost: 1 + cost: 0 items: - Cigarette + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutSmokes - type: loadout id: LoadoutItemCigsGreen category: Items - cost: 2 + cost: 0 items: - CigPackGreen + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutSmokes - type: loadout id: LoadoutItemCigsRed category: Items - cost: 2 + cost: 0 items: - CigPackRed + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutSmokes - type: loadout id: LoadoutItemCigsBlue category: Items - cost: 2 + cost: 0 items: - CigPackBlue + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutSmokes - type: loadout id: LoadoutItemCigsBlack category: Items - cost: 2 + cost: 0 items: - CigPackBlack + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutSmokes - type: loadout id: LoadoutItemCigsMixed category: Items - cost: 3 + cost: 1 items: - CigPackMixed + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutSmokes - type: loadout id: LoadoutItemCigsSyndicate category: Items - cost: 4 + cost: 2 items: - CigPackSyndicate + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutSmokes - type: loadout id: LoadoutItemLighter category: Items - cost: 2 + cost: 1 items: - Lighter + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutLighters - type: loadout id: LoadoutItemLighterCheap category: Items - cost: 1 + cost: 0 items: - CheapLighter + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutLighters - type: loadout id: LoadoutItemLighterFlippo category: Items - cost: 3 + cost: 2 items: - FlippoLighter + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutLighters - type: loadout id: LoadoutItemSmokingPipeFilledTobacco category: Items - cost: 2 + cost: 0 items: - SmokingPipeFilledTobacco + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutSmokes + +- type: loadout + id: LoadoutItemBlunt + category: Items + cost: 0 + items: + - Blunt + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutSmokes + +- type: loadout + id: LoadoutItemJoint + category: Items + cost: 0 + items: + - Joint + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutSmokes # Instruments - type: loadout id: LoadoutItemMicrophoneInstrument category: Items - cost: 4 + cost: 2 items: - MicrophoneInstrument + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutInstrumentsAny + - !type:CharacterJobRequirement + inverted: true + jobs: + - Musician - type: loadout id: LoadoutItemKalimbaInstrument category: Items - cost: 4 + cost: 2 items: - KalimbaInstrument + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutInstrumentsAny + - !type:CharacterJobRequirement + inverted: true + jobs: + - Musician - type: loadout id: LoadoutItemTrumpetInstrument category: Items - cost: 6 + cost: 4 items: - TrumpetInstrument + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutInstrumentsAny + - !type:CharacterJobRequirement + inverted: true + jobs: + - Musician - type: loadout id: LoadoutItemElectricGuitar category: Items - cost: 7 + cost: 5 items: - ElectricGuitarInstrument + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutInstrumentsAny + - !type:CharacterJobRequirement + inverted: true + jobs: + - Musician - type: loadout id: LoadoutItemBassGuitar category: Items - cost: 7 + cost: 5 items: - BassGuitarInstrument + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutInstrumentsAny + - !type:CharacterJobRequirement + inverted: true + jobs: + - Musician - type: loadout id: LoadoutItemRockGuitar category: Items - cost: 7 + cost: 5 items: - RockGuitarInstrument + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutInstrumentsAny + - !type:CharacterJobRequirement + inverted: true + jobs: + - Musician - type: loadout id: LoadoutItemAcousticGuitar category: Items - cost: 7 + cost: 5 items: - AcousticGuitarInstrument + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutInstrumentsAny + - !type:CharacterJobRequirement + inverted: true + jobs: + - Musician - type: loadout id: LoadoutItemViolin category: Items - cost: 6 + cost: 4 items: - ViolinInstrument + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutInstrumentsAny + - !type:CharacterJobRequirement + inverted: true + jobs: + - Musician - type: loadout id: LoadoutItemHarmonica category: Items - cost: 3 + cost: 1 items: - HarmonicaInstrument + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutInstrumentsAny + - !type:CharacterJobRequirement + inverted: true + jobs: + - Musician - type: loadout id: LoadoutItemAccordion category: Items - cost: 6 + cost: 4 items: - AccordionInstrument + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutInstrumentsAny + - !type:CharacterJobRequirement + inverted: true + jobs: + - Musician - type: loadout id: LoadoutItemFlute category: Items - cost: 4 + cost: 2 items: - FluteInstrument + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutInstrumentsAny + - !type:CharacterJobRequirement + inverted: true + jobs: + - Musician - type: loadout id: LoadoutItemOcarina category: Items - cost: 3 + cost: 1 items: - OcarinaInstrument + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutInstrumentsAny + - !type:CharacterJobRequirement + inverted: true + jobs: + - Musician # Survival Kit - type: loadout id: LoadoutItemsEmergencyOxygenTank category: Items - cost: 1 + cost: 0 items: - EmergencyOxygenTankFilled + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutAirTank - type: loadout id: LoadoutItemsExtendedEmergencyOxygenTank category: Items - cost: 2 + cost: 1 items: - ExtendedEmergencyOxygenTankFilled + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutAirTank - type: loadout id: LoadoutItemsDoubleEmergencyOxygenTank category: Items - cost: 4 + cost: 2 items: - DoubleEmergencyOxygenTankFilled + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutAirTank - type: loadout id: LoadoutItemClothingMaskBreath category: Items - cost: 1 + cost: 0 items: - ClothingMaskBreath @@ -236,19 +382,25 @@ - type: loadout id: LoadoutItemPapers category: Items - cost: 1 + cost: 0 items: - Paper - Paper - Paper - Paper + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutWritables - type: loadout id: LoadoutItemBoxFolderGrey category: Items - cost: 1 + cost: 0 items: - BoxFolderGrey + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutWritables - type: loadout id: LoadoutBookRandom @@ -256,13 +408,19 @@ cost: 1 items: - BookRandom + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutWritables - type: loadout id: LoadoutPen category: Items - cost: 1 + cost: 0 items: - Pen + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutWritables # Food and drink - type: loadout @@ -279,11 +437,18 @@ items: - LunchboxGenericFilledRandom +- type: loadout + id: LoadoutItemDrinkMREFlask + category: Items + cost: 2 + items: + - DrinkMREFlask + # Survival boxes - type: loadout id: LoadoutItemBoxSurvival category: Items - cost: 2 #All survival kits are intentionally cheaper than their contents to help encourage people to buy them. The contents can be bought separately to save space, or as spares + cost: 0 items: - BoxSurvival requirements: @@ -297,22 +462,26 @@ inverted: true jobs: - Clown + - !type:CharacterItemGroupRequirement + group: LoadoutBoxKits - type: loadout id: LoadoutItemBoxSurvivalEngineering category: Items - cost: 2 + cost: 0 items: - BoxSurvivalEngineering requirements: - !type:CharacterDepartmentRequirement departments: - Engineering + - !type:CharacterItemGroupRequirement + group: LoadoutBoxKits - type: loadout id: LoadoutItemBoxSurvivalSecurity category: Items - cost: 2 + cost: 0 items: - BoxSurvivalSecurity requirements: @@ -323,52 +492,63 @@ inverted: true jobs: - Brigmedic + - !type:CharacterItemGroupRequirement + group: LoadoutBoxKits - type: loadout id: LoadoutItemBoxSurvivalBrigmedic category: Items - cost: 2 + cost: 0 items: - BoxSurvivalBrigmedic requirements: - !type:CharacterJobRequirement jobs: - Brigmedic + - !type:CharacterItemGroupRequirement + group: LoadoutBoxKits - type: loadout id: LoadoutItemBoxSurvivalMedical category: Items - cost: 2 + cost: 0 items: - BoxSurvivalMedical requirements: - !type:CharacterDepartmentRequirement departments: - Medical + - !type:CharacterItemGroupRequirement + group: LoadoutBoxKits - type: loadout id: LoadoutItemBoxHug category: Items - cost: 2 + cost: 0 items: - BoxHug requirements: - !type:CharacterJobRequirement jobs: - Clown + - !type:CharacterItemGroupRequirement + group: LoadoutBoxKits # Medkits - type: loadout id: LoadoutMedkitFilled category: Items - cost: 4 + cost: 2 items: - MedkitFilled + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBoxKits - type: loadout id: LoadoutMedkitBruteFilled category: Items - cost: 4 + cost: 2 items: - MedkitBruteFilled requirements: @@ -379,11 +559,13 @@ - ChiefMedicalOfficer - MedicalIntern - Brigmedic + - !type:CharacterItemGroupRequirement + group: LoadoutBoxKits - type: loadout id: LoadoutMedkitBurnFilled category: Items - cost: 4 + cost: 2 items: - MedkitBurnFilled requirements: @@ -394,11 +576,13 @@ - ChiefMedicalOfficer - MedicalIntern - Brigmedic + - !type:CharacterItemGroupRequirement + group: LoadoutBoxKits - type: loadout id: LoadoutMedkitToxinFilled category: Items - cost: 4 + cost: 2 items: - MedkitToxinFilled requirements: @@ -409,11 +593,13 @@ - ChiefMedicalOfficer - MedicalIntern - Brigmedic + - !type:CharacterItemGroupRequirement + group: LoadoutBoxKits - type: loadout id: LoadoutMedkitOxygenFilled category: Items - cost: 4 + cost: 2 items: - MedkitOxygenFilled requirements: @@ -424,11 +610,13 @@ - ChiefMedicalOfficer - MedicalIntern - Brigmedic + - !type:CharacterItemGroupRequirement + group: LoadoutBoxKits - type: loadout id: LoadoutMedkitRadiationFilled category: Items - cost: 4 + cost: 2 items: - MedkitRadiationFilled requirements: @@ -439,11 +627,13 @@ - ChiefMedicalOfficer - MedicalIntern - Brigmedic + - !type:CharacterItemGroupRequirement + group: LoadoutBoxKits - type: loadout id: LoadoutMedkitAdvancedFilled category: Items - cost: 5 + cost: 3 items: - MedkitAdvancedFilled requirements: @@ -454,11 +644,13 @@ - ChiefMedicalOfficer - MedicalIntern - Brigmedic + - !type:CharacterItemGroupRequirement + group: LoadoutBoxKits - type: loadout id: LoadoutMedkitCombatFilled category: Items - cost: 4 # Discounted for the CMO and Corpsman + cost: 3 items: - MedkitCombatFilled requirements: @@ -466,6 +658,8 @@ jobs: - ChiefMedicalOfficer - Brigmedic + - !type:CharacterItemGroupRequirement + group: LoadoutBoxKits #Misc Items - type: loadout @@ -489,18 +683,6 @@ items: - PersonalAI -- type: loadout - id: LoadoutItemBackpackSatchelLeather - category: Items - cost: 1 - items: - - ClothingBackpackSatchelLeather - requirements: - - !type:CharacterJobRequirement - inverted: true - jobs: - - Prisoner - - type: loadout id: LoadoutItemWaistbag category: Items @@ -540,3 +722,38 @@ cost: 3 items: - HandLabeler + +- type: loadout + id: LoadoutItemHandheldStationMap + category: Items + cost: 2 + items: + - HandheldStationMap + +- type: loadout + id: LoadoutItemLantern + category: Items + cost: 2 + items: + - Lantern + +- type: loadout + id: LoadoutItemDrinkShinyFlask + category: Items + cost: 1 + items: + - DrinkShinyFlask + +- type: loadout + id: LoadoutItemDrinkLithiumFlask + category: Items + cost: 1 + items: + - DrinkLithiumFlask + +- type: loadout + id: LoadoutItemDrinkVacuumFlask + category: Items + cost: 1 + items: + - DrinkVacuumFlask diff --git a/Resources/Prototypes/Loadouts/mask.yml b/Resources/Prototypes/Loadouts/mask.yml index 67be1e70ff..4ea27fd0fa 100644 --- a/Resources/Prototypes/Loadouts/mask.yml +++ b/Resources/Prototypes/Loadouts/mask.yml @@ -1,18 +1,24 @@ - type: loadout id: LoadoutMaskSterile category: Mask - cost: 1 + cost: 0 exclusive: true items: - ClothingMaskSterile + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMasks - type: loadout id: LoadoutMaskMuzzle category: Mask - cost: 2 + cost: 1 exclusive: true items: - ClothingMaskMuzzle + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMasks - type: loadout id: LoadoutMaskGas @@ -21,20 +27,26 @@ exclusive: true items: - ClothingMaskGas + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMasks # Maskbands - type: loadout id: LoadoutMaskBandBlack category: Mask - cost: 1 + cost: 0 exclusive: true items: - ClothingMaskBandBlack + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMasks - type: loadout id: LoadoutMaskBandBlue category: Mask - cost: 1 + cost: 0 exclusive: true items: - ClothingMaskBandBlue @@ -43,11 +55,13 @@ inverted: true departments: - Security + - !type:CharacterItemGroupRequirement + group: LoadoutMasks - type: loadout id: LoadoutMaskBandGold category: Mask - cost: 1 + cost: 0 exclusive: true items: - ClothingMaskBandGold @@ -56,11 +70,13 @@ inverted: true departments: - Security + - !type:CharacterItemGroupRequirement + group: LoadoutMasks - type: loadout id: LoadoutMaskBandGreen category: Mask - cost: 1 + cost: 0 exclusive: true items: - ClothingMaskBandGreen @@ -69,11 +85,13 @@ inverted: true departments: - Security + - !type:CharacterItemGroupRequirement + group: LoadoutMasks - type: loadout id: LoadoutMaskBandGrey category: Mask - cost: 1 + cost: 0 exclusive: true items: - ClothingMaskBandGrey @@ -82,27 +100,35 @@ inverted: true departments: - Security + - !type:CharacterItemGroupRequirement + group: LoadoutMasks - type: loadout id: LoadoutMaskBandRed category: Mask - cost: 1 + cost: 0 exclusive: true items: - ClothingMaskBandRed + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMasks - type: loadout id: LoadoutMaskBandSkull category: Mask - cost: 1 + cost: 0 exclusive: true items: - ClothingMaskBandSkull + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMasks - type: loadout id: LoadoutMaskBandMerc category: Mask - cost: 2 + cost: 1 exclusive: true items: - ClothingMaskBandMerc @@ -111,11 +137,13 @@ inverted: true departments: - Security + - !type:CharacterItemGroupRequirement + group: LoadoutMasks - type: loadout id: LoadoutMaskBandBrown category: Mask - cost: 1 + cost: 0 exclusive: true items: - ClothingMaskBandBrown @@ -124,20 +152,28 @@ inverted: true departments: - Security + - !type:CharacterItemGroupRequirement + group: LoadoutMasks # Gaiters - type: loadout id: LoadoutMaskNeckGaiter category: Mask - cost: 2 + cost: 1 exclusive: true items: - ClothingMaskNeckGaiter + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMasks - type: loadout id: LoadoutMaskNeckGaiterRed category: Mask - cost: 2 + cost: 1 exclusive: true items: - ClothingMaskNeckGaiterRed + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutMasks diff --git a/Resources/Prototypes/Loadouts/neck.yml b/Resources/Prototypes/Loadouts/neck.yml index 3e0227d1da..e5f1442efe 100644 --- a/Resources/Prototypes/Loadouts/neck.yml +++ b/Resources/Prototypes/Loadouts/neck.yml @@ -1,231 +1,314 @@ - type: loadout id: LoadoutNeckHeadphones category: Neck - cost: 2 + cost: 1 exclusive: true items: - ClothingNeckHeadphones + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeck - type: loadout id: LoadoutNeckBellCollar category: Neck - cost: 2 + cost: 1 exclusive: true items: - ClothingNeckBellCollar + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeck - type: loadout id: LoadoutNeckOldMantle category: Neck - cost: 1 + cost: 0 exclusive: true items: - ClothingNeckOldMantle + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeck - type: loadout id: LoadoutNeckUnathiMantle category: Neck - cost: 1 + cost: 0 exclusive: true items: - ClothingNeckUnathiMantle + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeck # Scarves - type: loadout id: LoadoutNeckScarfStripedRed category: Neck - cost: 1 + cost: 0 exclusive: true items: - ClothingNeckScarfStripedRed + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeck - type: loadout id: LoadoutNeckScarfStripedBlue category: Neck - cost: 1 + cost: 0 exclusive: true items: - ClothingNeckScarfStripedBlue + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeck - type: loadout id: LoadoutNeckScarfStripedGreen category: Neck - cost: 1 + cost: 0 exclusive: true items: - ClothingNeckScarfStripedGreen + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeck - type: loadout id: LoadoutNeckScarfStripedBlack category: Neck - cost: 1 + cost: 0 exclusive: true items: - ClothingNeckScarfStripedBlack + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeck - type: loadout id: LoadoutNeckScarfStripedBrown category: Neck - cost: 1 + cost: 0 exclusive: true items: - ClothingNeckScarfStripedBrown + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeck - type: loadout id: LoadoutNeckScarfStripedLightBlue category: Neck - cost: 1 + cost: 0 exclusive: true items: - ClothingNeckScarfStripedLightBlue + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeck - type: loadout id: LoadoutNeckScarfStripedOrange category: Neck - cost: 1 + cost: 0 exclusive: true items: - ClothingNeckScarfStripedOrange + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeck - type: loadout id: LoadoutNeckScarfStripedPurple category: Neck - cost: 1 + cost: 0 exclusive: true items: - ClothingNeckScarfStripedPurple + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeck - type: loadout id: LoadoutNeckScarfStripedZebra category: Neck - cost: 1 + cost: 0 exclusive: true items: - ClothingNeckScarfStripedZebra + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeck # Ties - type: loadout id: LoadoutNeckTieRed category: Neck - cost: 1 + cost: 0 exclusive: true items: - ClothingNeckTieRed + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeck - type: loadout id: LoadoutNeckTieWhite category: Neck - cost: 1 + cost: 0 exclusive: true items: - ClothingNeckTieWhite + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeck - type: loadout id: LoadoutNeckTieBlack category: Neck - cost: 1 + cost: 0 exclusive: true items: - ClothingNeckTieBlack + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeck - type: loadout id: LoadoutNeckTieBlue category: Neck - cost: 1 + cost: 0 exclusive: true items: - ClothingNeckTieBlue + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeck - type: loadout id: LoadoutNeckTieGreen category: Neck - cost: 1 + cost: 0 exclusive: true items: - ClothingNeckTieGreen + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeck #Pride Accessories - type: loadout id: LoadoutItemsPrideLGBTPin category: Neck - cost: 1 + cost: 0 exclusive: true items: - ClothingNeckLGBTPin + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeck - type: loadout id: LoadoutItemsPrideAromanticPin category: Neck - cost: 1 + cost: 0 exclusive: true items: - ClothingNeckAromanticPin + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeck - type: loadout id: LoadoutItemsPrideAsexualPin category: Neck - cost: 1 + cost: 0 exclusive: true items: - ClothingNeckAsexualPin + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeck - type: loadout id: LoadoutItemsPrideBisexualPin category: Neck - cost: 1 + cost: 0 exclusive: true items: - ClothingNeckBisexualPin + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeck - type: loadout id: LoadoutItemsPrideIntersexPin category: Neck - cost: 1 + cost: 0 exclusive: true items: - ClothingNeckIntersexPin + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeck - type: loadout id: LoadoutItemsPrideLesbianPin category: Neck - cost: 1 + cost: 0 exclusive: true items: - ClothingNeckLesbianPin + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeck - type: loadout id: LoadoutItemsPrideNonBinaryPin category: Neck - cost: 1 + cost: 0 exclusive: true items: - ClothingNeckNonBinaryPin + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeck - type: loadout id: LoadoutItemsPridePansexualPin category: Neck - cost: 1 + cost: 0 exclusive: true items: - ClothingNeckPansexualPin + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeck - type: loadout id: LoadoutItemsPrideTransPin category: Neck - cost: 1 + cost: 0 exclusive: true items: - ClothingNeckTransPin + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeck # Bedsheets - type: loadout id: LoadoutNeckBedsheetBlack category: Neck - cost: 2 + cost: 1 exclusive: true items: - BedsheetBlack requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeck - !type:CharacterDepartmentRequirement departments: - Civilian @@ -237,11 +320,13 @@ - type: loadout id: LoadoutNeckBedsheetBlue category: Neck - cost: 2 + cost: 1 exclusive: true items: - BedsheetBlue requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeck - !type:CharacterDepartmentRequirement departments: - Civilian @@ -253,11 +338,13 @@ - type: loadout id: LoadoutNeckBedsheetBrown category: Neck - cost: 2 + cost: 1 exclusive: true items: - BedsheetBrown requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeck - !type:CharacterDepartmentRequirement departments: - Civilian @@ -269,11 +356,13 @@ - type: loadout id: LoadoutNeckBedsheetCosmos category: Neck - cost: 3 + cost: 2 exclusive: true items: - BedsheetCosmos requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeck - !type:CharacterDepartmentRequirement departments: - Civilian @@ -285,11 +374,13 @@ - type: loadout id: LoadoutNeckBedsheetGreen category: Neck - cost: 2 + cost: 1 exclusive: true items: - BedsheetGreen requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeck - !type:CharacterDepartmentRequirement departments: - Civilian @@ -301,11 +392,13 @@ - type: loadout id: LoadoutNeckBedsheetGrey category: Neck - cost: 2 + cost: 1 exclusive: true items: - BedsheetGrey requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeck - !type:CharacterDepartmentRequirement departments: - Civilian @@ -317,11 +410,13 @@ - type: loadout id: LoadoutNeckBedsheetOrange category: Neck - cost: 2 + cost: 1 exclusive: true items: - BedsheetOrange requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeck - !type:CharacterDepartmentRequirement departments: - Civilian @@ -334,11 +429,13 @@ - type: loadout id: LoadoutNeckBedsheetPurple category: Neck - cost: 2 + cost: 1 exclusive: true items: - BedsheetPurple requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeck - !type:CharacterDepartmentRequirement departments: - Civilian @@ -351,11 +448,13 @@ - type: loadout id: LoadoutNeckBedsheetRainbow category: Neck - cost: 2 + cost: 1 exclusive: true items: - BedsheetRainbow requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeck - !type:CharacterDepartmentRequirement departments: - Civilian @@ -367,11 +466,13 @@ - type: loadout id: LoadoutNeckBedsheetRed category: Neck - cost: 2 + cost: 1 exclusive: true items: - BedsheetRed requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeck - !type:CharacterDepartmentRequirement departments: - Civilian @@ -384,11 +485,13 @@ - type: loadout id: LoadoutNeckBedsheetWhite category: Neck - cost: 2 + cost: 1 exclusive: true items: - BedsheetWhite requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeck - !type:CharacterDepartmentRequirement departments: - Civilian @@ -400,11 +503,13 @@ - type: loadout id: LoadoutNeckBedsheetYellow category: Neck - cost: 2 + cost: 1 exclusive: true items: - BedsheetYellow requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeck - !type:CharacterDepartmentRequirement departments: - Civilian @@ -417,11 +522,13 @@ - type: loadout id: LoadoutNeckBedsheetNT category: Neck - cost: 2 + cost: 1 exclusive: true items: - BedsheetNT requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutNeck - !type:CharacterDepartmentRequirement departments: - Civilian diff --git a/Resources/Prototypes/Loadouts/outerClothing.yml b/Resources/Prototypes/Loadouts/outerClothing.yml index da091412b6..8411b4c40f 100644 --- a/Resources/Prototypes/Loadouts/outerClothing.yml +++ b/Resources/Prototypes/Loadouts/outerClothing.yml @@ -1,51 +1,72 @@ - type: loadout id: LoadoutOuterCoatBomberjacket category: Outer - cost: 3 + cost: 1 items: - ClothingOuterCoatBomber + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuter - type: loadout id: LoadoutOuterCoatHoodieBlack category: Outer - cost: 2 + cost: 0 items: - ClothingOuterHoodieBlack + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuter - type: loadout id: LoadoutOuterCoatHoodieGrey category: Outer - cost: 2 + cost: 0 items: - ClothingOuterHoodieGrey + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuter - type: loadout id: LoadoutOuterCoatWinterCoat category: Outer - cost: 3 + cost: 1 items: - ClothingOuterWinterCoat + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuter - type: loadout id: LoadoutOuterCoatHyenhSweater category: Outer - cost: 3 + cost: 1 items: - ClothingOuterCoatHyenhSweater + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuter - type: loadout id: LoadoutOuterWinterCoatLong category: Outer - cost: 3 + cost: 1 items: - ClothingOuterWinterCoatLong + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuter - type: loadout id: LoadoutOuterWinterCoatPlaid category: Outer - cost: 3 + cost: 1 items: - ClothingOuterWinterCoatPlaid + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuter - type: loadout id: LoadoutOuterVestValet @@ -53,6 +74,9 @@ cost: 1 items: - ClothingOuterVestValet + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuter - type: loadout id: LoadoutOuterVest @@ -60,182 +84,260 @@ cost: 1 items: - ClothingOuterVest + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuter # Letterman Jackets - type: loadout id: LoadoutOuterCoatLettermanBlue category: Outer - cost: 3 + cost: 1 items: - ClothingOuterCoatLettermanBlue + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuter - type: loadout id: LoadoutOuterCoatLettermanRed category: Outer - cost: 3 + cost: 1 items: - ClothingOuterCoatLettermanRed + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuter # MNK - type: loadout id: LoadoutOuterCoatMNKWhiteHoodie category: Outer - cost: 3 + cost: 1 items: - ClothingOuterCoatMNKWhiteHoodie + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuter - type: loadout id: LoadoutOuterCoatMNKBlackTopCoat category: Outer - cost: 3 + cost: 1 items: - ClothingOuterCoatMNKBlackTopCoat + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuter - type: loadout id: LoadoutOuterCoatMNKBlackJacket category: Outer - cost: 3 + cost: 1 items: - ClothingOuterCoatMNKBlackJacket + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuter # Contractor Jackets - type: loadout id: LoadoutOuterCorporateJacket category: Outer - cost: 2 + cost: 0 items: - ClothingOuterCorporateJacket + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuter - type: loadout id: LoadoutOuterCsCorporateJacket category: Outer - cost: 2 + cost: 0 items: - ClothingOuterCsCorporateJacket + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuter - type: loadout id: LoadoutOuterEeCorporateJacket category: Outer - cost: 2 + cost: 0 items: - ClothingOuterEeCorporateJacket + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuter - type: loadout id: LoadoutOuterHiCorporateJacket category: Outer - cost: 2 + cost: 0 items: - ClothingOuterHiCorporateJacket + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuter - type: loadout id: LoadoutOuterHmCorporateJacket category: Outer - cost: 2 + cost: 0 items: - ClothingOuterHmCorporateJacket + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuter - type: loadout id: LoadoutOuterIdCorporateJacket category: Outer - cost: 2 + cost: 0 items: - ClothingOuterIdCorporateJacket + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuter - type: loadout id: LoadoutOuterBcCorporateJacket category: Outer - cost: 2 + cost: 0 items: - ClothingOuterBcCorporateJacket + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuter - type: loadout id: LoadoutOuterDdCorporateJacket category: Outer - cost: 2 + cost: 0 items: - ClothingOuterDdCorporateJacket + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuter - type: loadout id: LoadoutOuterFaCorporateJacket category: Outer - cost: 2 + cost: 0 items: - ClothingOuterFaCorporateJacket + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuter - type: loadout id: LoadoutOuterGeCorporateJacket category: Outer - cost: 2 + cost: 0 items: - ClothingOuterGeCorporateJacket + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuter - type: loadout id: LoadoutOuterZhCorporateJacket category: Outer - cost: 2 + cost: 0 items: - ClothingOuterZhCorporateJacket + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuter - type: loadout id: LoadoutOuterDenimJacket category: Outer - cost: 3 + cost: 1 items: - ClothingOuterDenimJacket + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuter # Flannel - type: loadout id: LoadoutOuterFlannelRed category: Outer - cost: 3 + cost: 1 items: - ClothingOuterFlannelRed + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuter - type: loadout id: LoadoutOuterFlannelGreen category: Outer - cost: 3 + cost: 1 items: - ClothingOuterFlannelGreen + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuter - type: loadout id: LoadoutOuterFlannelBlue category: Outer - cost: 3 + cost: 1 items: - ClothingOuterFlannelBlue + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuter - type: loadout id: LoadoutOuterCoatTrench category: Outer - cost: 3 + cost: 1 items: - ClothingOuterCoatTrench + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuter - type: loadout id: LoadoutOuterCoatJensen category: Outer - cost: 3 + cost: 1 items: - ClothingOuterCoatJensen + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuter - type: loadout id: LoadoutOuterCoatGentle category: Outer - cost: 3 + cost: 1 items: - ClothingOuterCoatGentle + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuter - type: loadout id: LoadoutOuterCoatInspector category: Outer - cost: 3 + cost: 1 items: - ClothingOuterCoatInspector + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuter - type: loadout id: LoadoutOuterCoatOvercoat category: Outer - cost: 4 + cost: 2 items: - ClothingOuterCoatOvercoat + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutOuter diff --git a/Resources/Prototypes/Loadouts/satchels.yml b/Resources/Prototypes/Loadouts/satchels.yml new file mode 100644 index 0000000000..4f21dc7656 --- /dev/null +++ b/Resources/Prototypes/Loadouts/satchels.yml @@ -0,0 +1,247 @@ +- type: loadout + id: LoadoutBackpackSatchel + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackSatchel + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + +- type: loadout + id: LoadoutItemBackpackSatchelLeather + category: Backpacks + cost: 1 + exclusive: true + items: + - ClothingBackpackSatchelLeather + requirements: + - !type:CharacterJobRequirement + inverted: true + jobs: + - Prisoner + +- type: loadout + id: LoadoutBackpackSatchelClown + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackSatchelClown + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterJobRequirement + jobs: + - Clown + +- type: loadout + id: LoadoutBackpackSatchelSecurity + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackSatchelSecurity + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterDepartmentRequirement + departments: + - Security + +- type: loadout + id: LoadoutBackpackSatchelBrigmedic + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackSatchelBrigmedic + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterJobRequirement + jobs: + - Brigmedic + +- type: loadout + id: LoadoutBackpackSatchelEngineering + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackSatchelEngineering + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterDepartmentRequirement + departments: + - Engineering + +- type: loadout + id: LoadoutBackpackSatchelAtmospherics + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackSatchelAtmospherics + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterDepartmentRequirement + departments: + - Engineering + +- type: loadout + id: LoadoutBackpackSatchelMedical + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackSatchelMedical + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterDepartmentRequirement + departments: + - Medical + +- type: loadout + id: LoadoutBackpackSatchelCaptain + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackSatchelCaptain + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterJobRequirement + jobs: + - Captain + +- type: loadout + id: LoadoutBackpackSatchelMime + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackSatchelMime + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterJobRequirement + jobs: + - Mime + +- type: loadout + id: LoadoutBackpackSatchelChemistry + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackSatchelChemistry + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterDepartmentRequirement + departments: + - Medical + +- type: loadout + id: LoadoutBackpackSatchelHydroponics + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackSatchelHydroponics + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterDepartmentRequirement + departments: + - Civilian + +- type: loadout + id: LoadoutBackpackSatchelScience + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackSatchelScience + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterDepartmentRequirement + departments: + - Epistemics + +- type: loadout + id: LoadoutBackpackSatchelRobotics + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackSatchelRobotics + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterDepartmentRequirement + departments: + - Epistemics + +- type: loadout + id: LoadoutBackpackSatchelVirology + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackSatchelVirology + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterDepartmentRequirement + departments: + - Medical + +- type: loadout + id: LoadoutBackpackSatchelGenetics + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackSatchelGenetics + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterDepartmentRequirement + departments: + - Medical + +- type: loadout + id: LoadoutBackpackSatchelCargo + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackSatchelCargo + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterDepartmentRequirement + departments: + - Logistics + +- type: loadout + id: LoadoutBackpackSatchelSalvage + category: Backpacks + cost: 0 + exclusive: true + items: + - ClothingBackpackSatchelSalvage + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutBackpacks + - !type:CharacterDepartmentRequirement + departments: + - Logistics diff --git a/Resources/Prototypes/Loadouts/shoes.yml b/Resources/Prototypes/Loadouts/shoes.yml index e67ca70b39..38ca6bab6c 100644 --- a/Resources/Prototypes/Loadouts/shoes.yml +++ b/Resources/Prototypes/Loadouts/shoes.yml @@ -2,154 +2,121 @@ - type: loadout id: LoadoutShoesBlack category: Shoes - cost: 1 + cost: 0 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Diona - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutShoes items: - ClothingShoesColorBlack - type: loadout id: LoadoutShoesBlue category: Shoes - cost: 1 + cost: 0 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Diona - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutShoes items: - ClothingShoesColorBlue - type: loadout id: LoadoutShoesBrown category: Shoes - cost: 1 + cost: 0 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Diona - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutShoes items: - ClothingShoesColorBrown - type: loadout id: LoadoutShoesGreen category: Shoes - cost: 1 + cost: 0 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Diona - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutShoes items: - ClothingShoesColorGreen - type: loadout id: LoadoutShoesOrange category: Shoes - cost: 1 + cost: 0 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Diona - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutShoes items: - ClothingShoesColorOrange - type: loadout id: LoadoutShoesPurple category: Shoes - cost: 1 + cost: 0 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Diona - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutShoes items: - ClothingShoesColorPurple - type: loadout id: LoadoutShoesRed category: Shoes - cost: 1 + cost: 0 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Diona - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutShoes items: - ClothingShoesColorRed - type: loadout id: LoadoutShoesWhite category: Shoes - cost: 1 + cost: 0 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Diona - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutShoes items: - ClothingShoesColorWhite - type: loadout id: LoadoutShoesYellow category: Shoes - cost: 1 + cost: 0 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Diona - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutShoes items: - ClothingShoesColorYellow - type: loadout id: LoadoutShoesGeta category: Shoes - cost: 1 + cost: 0 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Diona - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutShoes items: - ClothingShoesGeta - type: loadout id: LoadoutShoesTourist category: Shoes - cost: 2 + cost: 1 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Diona - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutShoes items: - ClothingShoesTourist @@ -157,112 +124,88 @@ - type: loadout id: LoadoutShoesBootsWork category: Shoes - cost: 1 + cost: 0 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Diona - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutShoes items: - ClothingShoesBootsWork - type: loadout id: LoadoutShoesBootsLaceup category: Shoes - cost: 2 + cost: 1 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Diona - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutShoes items: - ClothingShoesBootsLaceup - type: loadout id: LoadoutShoesBootsWinter category: Shoes - cost: 1 + cost: 0 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Diona - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutShoes items: - ClothingShoesBootsWinter - type: loadout id: LoadoutShoesBootsCowboyBrown category: Shoes - cost: 2 + cost: 1 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Diona - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutShoes items: - ClothingShoesBootsCowboyBrown - type: loadout id: LoadoutShoesBootsCowboyBlack category: Shoes - cost: 2 + cost: 1 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Diona - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutShoes items: - ClothingShoesBootsCowboyBlack - type: loadout id: LoadoutShoesBootsCowboyWhite category: Shoes - cost: 2 + cost: 1 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Diona - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutShoes items: - ClothingShoesBootsCowboyWhite - type: loadout id: LoadoutShoesBootsCowboyFancy category: Shoes - cost: 2 + cost: 1 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Diona - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutShoes items: - ClothingShoesBootsCowboyFancy - type: loadout id: LoadoutShoesBootsFishing category: Shoes - cost: 2 + cost: 1 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Diona - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutShoes items: - ClothingShoesBootsFishing @@ -270,16 +213,13 @@ - type: loadout id: LoadoutShoesSlippersDuck category: Shoes - cost: 2 + cost: 1 exclusive: true items: - ClothingShoeSlippersDuck requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Diona - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutShoes - !type:CharacterJobRequirement jobs: - Clown @@ -287,42 +227,33 @@ - type: loadout id: LoadoutShoesLeather category: Shoes - cost: 1 + cost: 0 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Diona - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutShoes items: - ClothingShoesLeather - type: loadout id: LoadoutShoesMiscWhite category: Shoes - cost: 1 + cost: 0 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Diona - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutShoes items: - ClothingShoesMiscWhite - type: loadout id: LoadoutShoesHighheelBoots category: Shoes - cost: 3 + cost: 1 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Diona - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutShoes items: - ClothingShoesHighheelBoots @@ -330,35 +261,32 @@ - type: loadout id: LoadoutShoesUnderSocksCoder category: Shoes - cost: 3 + cost: 1 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Diona - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutShoes items: - ClothingUnderSocksCoder - type: loadout id: LoadoutShoesUnderSocksBee category: Shoes - cost: 3 + cost: 1 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Diona - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutShoes items: - ClothingUnderSocksBee - type: loadout id: LoadoutShoesClothWrap category: Shoes - cost: 1 + cost: 0 exclusive: true + requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutShoes items: - ClothingClothWrap diff --git a/Resources/Prototypes/Loadouts/species.yml b/Resources/Prototypes/Loadouts/species.yml index 8c7bd22858..e7455a00fb 100644 --- a/Resources/Prototypes/Loadouts/species.yml +++ b/Resources/Prototypes/Loadouts/species.yml @@ -7,6 +7,8 @@ species: - SlimePerson - Vox + - !type:CharacterItemGroupRequirement + group: LoadoutAirTank items: - EmergencyNitrogenTankFilled @@ -19,17 +21,21 @@ species: - SlimePerson - Vox + - !type:CharacterItemGroupRequirement + group: LoadoutAirTank items: - ExtendedEmergencyNitrogenTankFilled - type: loadout id: LoadoutSpeciesDoubleEmergencyNitrogenTank category: Species - cost: 3 + cost: 2 requirements: - !type:CharacterSpeciesRequirement species: - SlimePerson - Vox + - !type:CharacterItemGroupRequirement + group: LoadoutAirTank items: - DoubleEmergencyNitrogenTankFilled diff --git a/Resources/Prototypes/Loadouts/uniform.yml b/Resources/Prototypes/Loadouts/uniform.yml index 5843cb30ee..f0f29cf8b1 100644 --- a/Resources/Prototypes/Loadouts/uniform.yml +++ b/Resources/Prototypes/Loadouts/uniform.yml @@ -1,13 +1,11 @@ - type: loadout id: LoadoutUniformAncientJumpsuit category: Uniform - cost: 2 + cost: 1 exclusive: true requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterJobRequirement jobs: - Passenger @@ -15,1504 +13,1529 @@ - ClothingUniformJumpsuitAncient # Colored jumpsuits +# Someone please alphabetically order these... - type: loadout id: LoadoutUniformJumpsuitColorBlack category: Uniform - cost: 2 + cost: 0 exclusive: true items: - ClothingUniformJumpsuitColorBlack requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - departments: - - Civilian - - Security + departments: + - Civilian + - Security - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutUniformJumpskirtColorBlack category: Uniform - cost: 2 + cost: 0 exclusive: true items: - ClothingUniformJumpskirtColorBlack requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - departments: - - Civilian - - Security + departments: + - Civilian + - Security - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutUniformJumpsuitColorBlue category: Uniform - cost: 2 + cost: 0 exclusive: true items: - ClothingUniformJumpsuitColorBlue requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - departments: - - Medical - - Civilian + departments: + - Medical + - Civilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutUniformJumpskirtColorBlue category: Uniform - cost: 2 + cost: 0 exclusive: true items: - ClothingUniformJumpskirtColorBlue requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - departments: - - Medical - - Civilian + departments: + - Medical + - Civilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutUniformJumpsuitColorGreen category: Uniform - cost: 2 + cost: 0 exclusive: true items: - ClothingUniformJumpsuitColorGreen requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - departments: - - Civilian + departments: + - Civilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutUniformJumpskirtColorGreen category: Uniform - cost: 2 + cost: 0 exclusive: true items: - ClothingUniformJumpskirtColorGreen requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - departments: - - Civilian + departments: + - Civilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutUniformJumpsuitColorOrange category: Uniform - cost: 2 + cost: 0 exclusive: true items: - ClothingUniformJumpsuitColorOrange requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - departments: - - Civilian + departments: + - Civilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutUniformJumpskirtColorOrange category: Uniform - cost: 2 + cost: 0 exclusive: true items: - ClothingUniformJumpskirtColorOrange requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - departments: - - Civilian + departments: + - Civilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutUniformJumpsuitColorPink category: Uniform - cost: 2 + cost: 0 exclusive: true items: - ClothingUniformJumpsuitColorPink requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - departments: - - Civilian + departments: + - Civilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutUniformJumpskirtColorPink category: Uniform - cost: 2 + cost: 0 exclusive: true items: - ClothingUniformJumpskirtColorPink requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - departments: - - Civilian + departments: + - Civilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutUniformJumpsuitColorRed category: Uniform - cost: 2 + cost: 0 exclusive: true items: - ClothingUniformJumpsuitColorRed requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - departments: - - Civilian - - Security + departments: + - Civilian + - Security - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutUniformJumpskirtColorRed category: Uniform - cost: 2 + cost: 0 exclusive: true items: - ClothingUniformJumpskirtColorRed requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - departments: - - Civilian - - Security + departments: + - Civilian + - Security - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutUniformJumpsuitColorWhite category: Uniform - cost: 2 + cost: 0 exclusive: true items: - ClothingUniformJumpsuitColorWhite requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - departments: - - Civilian - - Medical - - Epistemics + departments: + - Civilian + - Medical + - Epistemics - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutUniformJumpskirtColorWhite category: Uniform - cost: 2 + cost: 0 exclusive: true items: - ClothingUniformJumpskirtColorWhite requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - departments: - - Civilian - - Medical - - Epistemics + departments: + - Civilian + - Medical + - Epistemics - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutUniformJumpsuitColorYellow category: Uniform - cost: 2 + cost: 0 exclusive: true items: - ClothingUniformJumpsuitColorYellow requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - departments: - - Civilian - - Engineering + departments: + - Civilian + - Engineering - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutUniformJumpskirtColorYellow category: Uniform - cost: 2 + cost: 0 exclusive: true items: - ClothingUniformJumpskirtColorYellow requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - departments: - - Civilian - - Engineering + departments: + - Civilian + - Engineering - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutUniformJumpsuitColorDarkBlue category: Uniform - cost: 2 + cost: 0 exclusive: true items: - ClothingUniformJumpsuitColorDarkBlue requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - departments: - - Civilian + departments: + - Civilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutUniformJumpskirtColorDarkBlue category: Uniform - cost: 2 + cost: 0 exclusive: true items: - ClothingUniformJumpskirtColorDarkBlue requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - departments: - - Civilian + departments: + - Civilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutUniformJumpsuitColorTeal category: Uniform - cost: 2 + cost: 0 exclusive: true items: - ClothingUniformJumpsuitColorTeal requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - departments: - - Civilian + departments: + - Civilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutUniformJumpskirtColorTeal category: Uniform - cost: 2 + cost: 0 exclusive: true items: - ClothingUniformJumpskirtColorTeal requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - departments: - - Civilian + departments: + - Civilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutUniformJumpsuitColorPurple category: Uniform - cost: 2 + cost: 0 exclusive: true items: - ClothingUniformJumpsuitColorPurple requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - departments: - - Civilian - - Epistemics + departments: + - Civilian + - Epistemics - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutUniformJumpskirtColorPurple category: Uniform - cost: 2 + cost: 0 exclusive: true items: - ClothingUniformJumpskirtColorPurple requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - departments: - - Civilian - - Epistemics + departments: + - Civilian + - Epistemics - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutUniformJumpsuitColorDarkGreen category: Uniform - cost: 2 + cost: 0 exclusive: true items: - ClothingUniformJumpsuitColorDarkGreen requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - departments: - - Civilian + departments: + - Civilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutUniformJumpskirtColorDarkGreen category: Uniform - cost: 2 + cost: 0 exclusive: true items: - ClothingUniformJumpskirtColorDarkGreen requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - departments: - - Civilian + departments: + - Civilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutUniformJumpsuitColorLightBrown category: Uniform - cost: 2 + cost: 0 exclusive: true items: - ClothingUniformJumpsuitColorLightBrown requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - departments: - - Civilian + departments: + - Civilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutUniformJumpskirtColorLightBrown category: Uniform - cost: 2 + cost: 0 exclusive: true items: - ClothingUniformJumpskirtColorLightBrown requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - departments: - - Civilian + departments: + - Civilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutUniformJumpsuitColorBrown category: Uniform - cost: 2 + cost: 0 exclusive: true items: - ClothingUniformJumpsuitColorBrown requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - departments: - - Civilian + departments: + - Civilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutUniformJumpskirtColorBrown category: Uniform - cost: 2 + cost: 0 exclusive: true items: - ClothingUniformJumpskirtColorBrown requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - departments: - - Civilian + departments: + - Civilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutUniformJumpsuitColorMaroon category: Uniform - cost: 2 + cost: 0 exclusive: true items: - ClothingUniformJumpsuitColorMaroon requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - departments: - - Civilian + departments: + - Civilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutUniformJumpskirtColorMaroon category: Uniform - cost: 2 + cost: 0 exclusive: true items: - ClothingUniformJumpskirtColorMaroon requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - departments: - - Civilian + departments: + - Civilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutUniformJumpsuitFlannel category: Uniform - cost: 2 + cost: 1 exclusive: true items: - ClothingUniformJumpsuitFlannel requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutUniformJumpskirtCasualBlue category: Uniform - cost: 2 + cost: 0 exclusive: true items: - ClothingUniformJumpskirtCasualBlue requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Security - - Command + inverted: true + departments: + - Security + - Command - type: loadout id: LoadoutUniformJumpsuitCasualPurple category: Uniform - cost: 2 + cost: 0 exclusive: true items: - ClothingUniformJumpsuitCasualPurple requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Security - - Command + inverted: true + departments: + - Security + - Command - type: loadout id: LoadoutUniformJumpskirtCasualPurple category: Uniform - cost: 2 + cost: 0 exclusive: true items: - ClothingUniformJumpskirtCasualPurple requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Security - - Command + inverted: true + departments: + - Security + - Command - type: loadout id: LoadoutUniformJumpsuitCasualRed category: Uniform - cost: 2 + cost: 0 exclusive: true items: - ClothingUniformJumpsuitCasualRed requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutUniformJumpskirtCasualRed category: Uniform - cost: 2 + cost: 0 exclusive: true items: - ClothingUniformJumpskirtCasualRed requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutUniformJumpsuitTshirtJeans category: Uniform - cost: 2 + cost: 0 exclusive: true items: - ClothingUniformJumpsuitTshirtJeans requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Security - - Command + inverted: true + departments: + - Security + - Command - type: loadout id: LoadoutUniformJumpsuitTshirtJeansGray category: Uniform - cost: 2 + cost: 0 exclusive: true items: - ClothingUniformJumpsuitTshirtJeansGray requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutUniformJumpsuitTshirtJeansPeach category: Uniform - cost: 2 + cost: 0 exclusive: true items: - ClothingUniformJumpsuitTshirtJeansPeach requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutUniformJumpsuitJeansGreen category: Uniform - cost: 2 + cost: 0 exclusive: true items: - ClothingUniformJumpsuitJeansGreen requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Security - - Command + inverted: true + departments: + - Security + - Command - type: loadout id: LoadoutUniformJumpsuitJeansRed category: Uniform - cost: 2 + cost: 0 exclusive: true items: - ClothingUniformJumpsuitJeansRed requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutUniformJumpsuitJeansBrown category: Uniform - cost: 2 + cost: 0 exclusive: true items: - ClothingUniformJumpsuitJeansBrown requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Security - - Command + inverted: true + departments: + - Security + - Command - type: loadout id: LoadoutUniformJumpsuitLostTourist category: Uniform - cost: 3 + cost: 1 exclusive: true items: - ClothingUniformJumpsuitLostTourist requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Security - - Command + inverted: true + departments: + - Security + - Command # Hawaiian shirts - type: loadout id: LoadoutUniformJumpsuitHawaiBlack category: Uniform - cost: 2 + cost: 1 exclusive: true items: - ClothingUniformJumpsuitHawaiBlack requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Security - - Command + inverted: true + departments: + - Security + - Command - type: loadout id: LoadoutUniformJumpsuitHawaiBlue category: Uniform - cost: 2 + cost: 1 exclusive: true items: - ClothingUniformJumpsuitHawaiBlue requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Security - - Command + inverted: true + departments: + - Security + - Command - type: loadout id: LoadoutUniformJumpsuitHawaiRed category: Uniform - cost: 2 + cost: 1 exclusive: true items: - ClothingUniformJumpsuitHawaiRed requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutUniformJumpsuitHawaiYellow category: Uniform - cost: 2 + cost: 1 exclusive: true items: - ClothingUniformJumpsuitHawaiYellow requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Security - - Command + inverted: true + departments: + - Security + - Command # AutoDrobe clothes - type: loadout id: LoadoutUniformDressRed category: Uniform - cost: 4 + cost: 2 exclusive: true items: - ClothingUniformDressRed requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutUniformJumpsuitSober category: Uniform - cost: 2 + cost: 0 exclusive: true items: - ClothingUniformJumpsuitSober requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutUniformSkirtTurtle category: Uniform - cost: 2 + cost: 0 exclusive: true items: - ClothingUniformSkirtTurtle requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutUniformGeisha category: Uniform - cost: 3 + cost: 1 exclusive: true items: - UniformGeisha requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutUniformCostumeArcDress category: Uniform - cost: 2 + cost: 1 exclusive: true items: - ClothingCostumeArcDress requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Security - - Command + inverted: true + departments: + - Security + - Command - type: loadout id: LoadoutUniformCostumeMioSkirt category: Uniform - cost: 2 + cost: 1 exclusive: true items: - ClothingCostumeMioSkirt requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Security - - Command + inverted: true + departments: + - Security + - Command - type: loadout id: LoadoutUniformCostumeNaota category: Uniform - cost: 2 + cost: 1 exclusive: true items: - ClothingCostumeNaota requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Security - - Command + inverted: true + departments: + - Security + - Command - type: loadout id: LoadoutUniformJumpsuitLoungewear category: Uniform - cost: 2 + cost: 1 exclusive: true items: - ClothingUniformJumpsuitLoungewear requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command # Bartender clothes - type: loadout id: LoadoutUniformJumpsuitBartender category: Uniform - cost: 3 + cost: 0 exclusive: true items: - ClothingUniformJumpsuitBartender requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Security - - Command + inverted: true + departments: + - Security + - Command - type: loadout id: LoadoutUniformJumpskirtBartender category: Uniform - cost: 3 + cost: 0 exclusive: true items: - ClothingUniformJumpskirtBartender requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Security - - Command + inverted: true + departments: + - Security + - Command # Kendo - type: loadout id: LoadoutUniformKendoHakama category: Uniform - cost: 2 + cost: 1 exclusive: true items: - ClothingUniformKendoHakama requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutUniformMartialGi category: Uniform - cost: 2 + cost: 1 exclusive: true items: - ClothingUniformMartialGi requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Security - - Command + inverted: true + departments: + - Security + - Command # Kimono - type: loadout id: LoadoutClothingJumpsuitKimono category: Uniform - cost: 3 + cost: 1 exclusive: true items: - ClothingUniformJumpsuitKimono requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Security - - Command + inverted: true + departments: + - Security + - Command - type: loadout id: LoadoutClothingKimonoBlue category: Uniform - cost: 3 + cost: 1 exclusive: true items: - ClothingKimonoBlue requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Security - - Command + inverted: true + departments: + - Security + - Command - type: loadout id: LoadoutClothingKimonoPink category: Uniform - cost: 3 + cost: 1 exclusive: true items: - ClothingKimonoPink requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Security - - Command + inverted: true + departments: + - Security + - Command - type: loadout id: LoadoutClothingKimonoPurple category: Uniform - cost: 3 + cost: 1 exclusive: true items: - ClothingKimonoPurple requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Security - - Command + inverted: true + departments: + - Security + - Command - type: loadout id: LoadoutClothingKimonoSky category: Uniform - cost: 3 + cost: 1 exclusive: true items: - ClothingKimonoSky requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Security - - Command + inverted: true + departments: + - Security + - Command - type: loadout id: LoadoutClothingKimonoGreen category: Uniform - cost: 3 + cost: 1 exclusive: true items: - ClothingKimonoGreen requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Security - - Command + inverted: true + departments: + - Security + - Command # Gakuran - type: loadout id: LoadoutUniformSchoolGakuranBlack category: Uniform - cost: 2 + cost: 1 exclusive: true items: - ClothingUniformSchoolGakuranBlack requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command # Schoolgirl uniform - type: loadout id: LoadoutUniformSchoolgirlBlack category: Uniform - cost: 3 + cost: 1 exclusive: true items: - UniformSchoolgirlBlack requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutUniformSchoolgirlBlue category: Uniform - cost: 3 + cost: 1 exclusive: true items: - UniformSchoolgirlBlue requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Security - - Command + inverted: true + departments: + - Security + - Command - type: loadout id: LoadoutUniformSchoolgirlCyan category: Uniform - cost: 3 + cost: 1 exclusive: true items: - UniformSchoolgirlCyan requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Security - - Command + inverted: true + departments: + - Security + - Command - type: loadout id: LoadoutUniformSchoolgirlGreen category: Uniform - cost: 3 + cost: 1 exclusive: true items: - UniformSchoolgirlGreen requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Security - - Command + inverted: true + departments: + - Security + - Command - type: loadout id: LoadoutUniformSchoolgirlOrange category: Uniform - cost: 3 + cost: 1 exclusive: true items: - UniformSchoolgirlOrange requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Security - - Command + inverted: true + departments: + - Security + - Command - type: loadout id: LoadoutUniformSchoolgirlPink category: Uniform - cost: 3 + cost: 1 exclusive: true items: - UniformSchoolgirlPink requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Security - - Command + inverted: true + departments: + - Security + - Command - type: loadout id: LoadoutUniformSchoolgirlPurple category: Uniform - cost: 3 + cost: 1 exclusive: true items: - UniformSchoolgirlPurple requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Security - - Command + inverted: true + departments: + - Security + - Command - type: loadout id: LoadoutUniformSchoolgirlRed category: Uniform - cost: 3 + cost: 1 exclusive: true items: - UniformSchoolgirlRed requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutUniformSchoolgirlDusk category: Uniform - cost: 3 + cost: 1 exclusive: true items: - UniformSchoolgirlDusk requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Security - - Command + inverted: true + departments: + - Security + - Command - type: loadout id: LoadoutUniformSchoolgirlBlazerTan category: Uniform - cost: 3 + cost: 1 exclusive: true items: - UniformSchoolgirlBlazerTan requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Security - - Command + inverted: true + departments: + - Security + - Command # MNK Uniforms - type: loadout id: LoadoutClothingMNKOfficeSkirt category: Uniform - cost: 3 + cost: 1 exclusive: true items: - ClothingUniformMNKOfficeSkirt requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutClothingMNKUnderGarment category: Uniform - cost: 3 + cost: 1 exclusive: true items: - ClothingUniformMNKUnderGarment requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutClothingMNKGymBra category: Uniform - cost: 3 + cost: 1 exclusive: true items: - ClothingUniformMNKGymBra requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutClothingMNKDressBlack category: Uniform - cost: 4 + cost: 2 exclusive: true items: - ClothingUniformMNKDressBlack requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutClothingMNKBlackOveralls category: Uniform - cost: 3 + cost: 1 exclusive: true items: - ClothingUniformMNKBlackOveralls requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutClothingMNKBlackShoulder category: Uniform - cost: 3 + cost: 1 exclusive: true items: - ClothingUniformMNKBlackShoulder requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutClothingMNKTracksuitBlack category: Uniform - cost: 3 + cost: 1 exclusive: true items: - ClothingUniformMNKTracksuitBlack requirements: + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command # Suits - type: loadout id: LoadoutClothingJumpsuitSuitBlack category: Uniform - cost: 2 + cost: 1 exclusive: true items: - ClothingUniformJumpsuitSuitBlack requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutClothingJumpsuitSuitBlackAlt category: Uniform - cost: 2 + cost: 1 exclusive: true items: - ClothingUniformJumpsuitSuitBlackAlt requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutClothingJumpsuitSuitBlackMob category: Uniform - cost: 2 + cost: 1 exclusive: true items: - ClothingUniformJumpsuitSuitBlackMob requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutClothingJumpsuitSuitBrown category: Uniform - cost: 2 + cost: 1 exclusive: true items: - ClothingUniformJumpsuitSuitBrown requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command - - Security + inverted: true + departments: + - Command + - Security - type: loadout id: LoadoutClothingJumpsuitSuitBrownAlt category: Uniform - cost: 2 + cost: 1 exclusive: true items: - ClothingUniformJumpsuitSuitBrownAlt requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutClothingJumpsuitSuitBrownMob category: Uniform - cost: 2 + cost: 1 exclusive: true items: - ClothingUniformJumpsuitSuitBrownMob requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutClothingJumpsuitSuitWhite category: Uniform - cost: 2 + cost: 1 exclusive: true items: - ClothingUniformJumpsuitSuitWhite requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command - - Security + inverted: true + departments: + - Command + - Security - type: loadout id: LoadoutClothingJumpsuitSuitWhiteAlt category: Uniform - cost: 2 + cost: 1 exclusive: true items: - ClothingUniformJumpsuitSuitWhiteAlt requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command - type: loadout id: LoadoutClothingJumpsuitSuitWhiteMob category: Uniform - cost: 2 + cost: 1 exclusive: true items: - ClothingUniformJumpsuitSuitWhiteMob requirements: - - !type:CharacterSpeciesRequirement - inverted: true - species: - - Harpy + - !type:CharacterItemGroupRequirement + group: LoadoutUniformsCivilian - !type:CharacterDepartmentRequirement - inverted: true - departments: - - Command + inverted: true + departments: + - Command diff --git a/Resources/Prototypes/Magic/event_spells.yml b/Resources/Prototypes/Magic/event_spells.yml new file mode 100644 index 0000000000..e59e1b2db8 --- /dev/null +++ b/Resources/Prototypes/Magic/event_spells.yml @@ -0,0 +1,13 @@ +- type: entity + id: ActionSummonGhosts + name: Summon Ghosts + description: Makes all current ghosts permanently invisible + noSpawn: true + components: + - type: InstantAction + useDelay: 120 + itemIconStyle: BigAction + icon: + sprite: Mobs/Ghosts/ghost_human.rsi + state: icon + event: !type:ToggleGhostVisibilityToAllEvent diff --git a/Resources/Prototypes/Magic/knock_spell.yml b/Resources/Prototypes/Magic/knock_spell.yml index f00897d32c..e2c3dcfd4c 100644 --- a/Resources/Prototypes/Magic/knock_spell.yml +++ b/Resources/Prototypes/Magic/knock_spell.yml @@ -7,6 +7,8 @@ - type: InstantAction useDelay: 10 itemIconStyle: BigAction + sound: !type:SoundPathSpecifier + path: /Audio/Magic/knock.ogg icon: sprite: Objects/Magic/magicactions.rsi state: knock diff --git a/Resources/Prototypes/Magic/projectile_spells.yml b/Resources/Prototypes/Magic/projectile_spells.yml index 196472fe7b..b8db7557bb 100644 --- a/Resources/Prototypes/Magic/projectile_spells.yml +++ b/Resources/Prototypes/Magic/projectile_spells.yml @@ -4,10 +4,12 @@ description: Fires an explosive fireball towards the clicked location. noSpawn: true components: + - type: Magic - type: WorldTargetAction useDelay: 15 itemIconStyle: BigAction checkCanAccess: false + raiseOnUser: true range: 60 sound: !type:SoundPathSpecifier path: /Audio/Magic/fireball.ogg @@ -16,25 +18,25 @@ state: fireball event: !type:ProjectileSpellEvent prototype: ProjectileFireball - posData: !type:TargetCasterPos speech: action-speech-spell-fireball - type: ActionUpgrade effectedLevels: 2: ActionFireballII + 3: ActionFireballIII - type: entity id: ActionFireballII parent: ActionFireball name: Fireball II - description: Fire three explosive fireball towards the clicked location. + description: Fires a fireball, but faster! noSpawn: true components: - type: WorldTargetAction - useDelay: 5 - charges: 3 + useDelay: 10 renewCharges: true itemIconStyle: BigAction checkCanAccess: false + raiseOnUser: true range: 60 sound: !type:SoundPathSpecifier path: /Audio/Magic/fireball.ogg @@ -43,5 +45,27 @@ state: fireball event: !type:ProjectileSpellEvent prototype: ProjectileFireball - posData: !type:TargetCasterPos speech: action-speech-spell-fireball + +- type: entity + id: ActionFireballIII + parent: ActionFireball + name: Fireball III + description: The fastest fireball in the west! + noSpawn: true + components: + - type: WorldTargetAction + useDelay: 8 + renewCharges: true + itemIconStyle: BigAction + checkCanAccess: false + raiseOnUser: true + range: 60 + sound: !type:SoundPathSpecifier + path: /Audio/Magic/fireball.ogg + icon: + sprite: Objects/Magic/magicactions.rsi + state: fireball + event: !type:ProjectileSpellEvent + prototype: ProjectileFireball + speech: action-speech-spell-fireball diff --git a/Resources/Prototypes/Magic/teleport_spells.yml b/Resources/Prototypes/Magic/teleport_spells.yml index 30c83891ee..cc89cf8ee0 100644 --- a/Resources/Prototypes/Magic/teleport_spells.yml +++ b/Resources/Prototypes/Magic/teleport_spells.yml @@ -8,9 +8,11 @@ useDelay: 10 range: 16 # default examine-range. # ^ should probably add better validation that the clicked location is on the users screen somewhere, + sound: !type:SoundPathSpecifier + path: /Audio/Magic/blink.ogg itemIconStyle: BigAction checkCanAccess: false - repeat: true + repeat: false icon: sprite: Objects/Magic/magicactions.rsi state: blink diff --git a/Resources/Prototypes/Magic/utility_spells.yml b/Resources/Prototypes/Magic/utility_spells.yml new file mode 100644 index 0000000000..dccdda3789 --- /dev/null +++ b/Resources/Prototypes/Magic/utility_spells.yml @@ -0,0 +1,15 @@ +- type: entity + id: ActionChargeSpell + name: Charge + description: Adds a charge back to your wand + noSpawn: true + components: + - type: InstantAction + useDelay: 30 + itemIconStyle: BigAction + icon: + sprite: Objects/Weapons/Guns/Basic/wands.rsi + state: nothing + event: !type:ChargeSpellEvent + charge: 1 + speech: DI'RI CEL! diff --git a/Resources/Prototypes/Mood/categories.yml b/Resources/Prototypes/Mood/categories.yml new file mode 100644 index 0000000000..8c03338ca8 --- /dev/null +++ b/Resources/Prototypes/Mood/categories.yml @@ -0,0 +1,9 @@ +# Alphabetically Ordered +- type: moodCategory + id: Health + +- type: moodCategory + id: Hunger + +- type: moodCategory + id: Thirst \ No newline at end of file diff --git a/Resources/Prototypes/Mood/drugs.yml b/Resources/Prototypes/Mood/drugs.yml new file mode 100644 index 0000000000..379dd96006 --- /dev/null +++ b/Resources/Prototypes/Mood/drugs.yml @@ -0,0 +1,45 @@ +# Drugs should be paired up in Trios of "Bonus, Penalty, Category" + +# Lotophagoi Oil +- type: moodEffect + id: LotoTranscendence + moodChange: 30 + timeout: 1020 #17 minutes + moodletOnEnd: LotoEnthrallment + category: "LotophagoiAddiction" + +- type: moodEffect + id: LotoEnthrallment + moodChange: -30 + timeout: 600 #10 minutes + category: "LotophagoiAddiction" + +- type: moodCategory + id: LotophagoiAddiction + +# Nicotine +- type: moodEffect + id: NicotineBenefit + moodChange: 5 + timeout: 1020 #17 minutes + moodletOnEnd: NicotineWithdrawal + category: "NicotineAddiction" + +- type: moodEffect + id: NicotineWithdrawal + moodChange: -7 #No timeout + category: "NicotineAddiction" + +- type: moodCategory + id: NicotineAddiction + +# Non-Addictive Drugs +- type: moodEffect + id: EthanolBenefit + moodChange: 7 + timeout: 300 #5 minutes + +- type: moodEffect + id: SpaceDrugsBenefit + moodChange: 7 + timeout: 300 #5 minutes diff --git a/Resources/Prototypes/Mood/genericNeeds.yml b/Resources/Prototypes/Mood/genericNeeds.yml new file mode 100644 index 0000000000..aeb43bcd3f --- /dev/null +++ b/Resources/Prototypes/Mood/genericNeeds.yml @@ -0,0 +1,63 @@ +# Hunger +- type: moodEffect + id: HungerOverfed + moodChange: 10 + category: "Hunger" + +- type: moodEffect + id: HungerOkay + moodChange: 7 + category: "Hunger" + +- type: moodEffect + id: HungerPeckish + moodChange: -3 + category: "Hunger" + +- type: moodEffect + id: HungerStarving + moodChange: -7 + category: "Hunger" + +# Thirst +- type: moodEffect + id: ThirstOverHydrated + moodChange: 10 + category: "Thirst" + +- type: moodEffect + id: ThirstOkay + moodChange: 7 + category: "Thirst" + +- type: moodEffect + id: ThirstThirsty + moodChange: -3 + category: "Thirst" + +- type: moodEffect + id: ThirstParched + moodChange: -7 + category: "Thirst" + +# Health +- type: moodEffect + id: HealthNoDamage + moodChange: 0 + hidden: true + category: "Health" + +- type: moodEffect + id: HealthLightDamage + moodChange: -3 + category: "Health" + +- type: moodEffect + id: HealthSevereDamage + moodChange: -7 + category: "Health" + +- type: moodEffect + id: HealthHeavyDamage + moodChange: -20 + category: "Health" \ No newline at end of file diff --git a/Resources/Prototypes/Mood/genericNegativeEffects.yml b/Resources/Prototypes/Mood/genericNegativeEffects.yml new file mode 100644 index 0000000000..ab707ad28c --- /dev/null +++ b/Resources/Prototypes/Mood/genericNegativeEffects.yml @@ -0,0 +1,51 @@ +- type: moodEffect + id: Handcuffed + moodChange: -3 + +- type: moodEffect + id: Suffocating + moodChange: -7 + timeout: 6 + +- type: moodEffect + id: OnFire + moodChange: -10 + timeout: 600 + +- type: moodEffect + id: Creampied + moodChange: -3 + +- type: moodEffect + id: MobSlipped + moodChange: -3 + timeout: 180 + +- type: moodEffect + id: MobVomit + moodChange: -3 + timeout: 480 + +- type: moodEffect + id: MobLowPressure + moodChange: -7 + timeout: 10 + +- type: moodEffect + id: MobHighPressure + moodChange: -7 + timeout: 10 + +- type: moodEffect + id: TraitSaturnine + moodChange: -20 + +- type: moodEffect + id: Dead + moodChange: -1000 + +# Surgery +- type: moodEffect + id: SurgeryPain + moodChange: -30 # lol too lazy to make a category of ramping surgery moods, wonder how bad this will be. + timeout: 360 diff --git a/Resources/Prototypes/Mood/genericPositiveEffects.yml b/Resources/Prototypes/Mood/genericPositiveEffects.yml new file mode 100644 index 0000000000..a4d5ae6ce0 --- /dev/null +++ b/Resources/Prototypes/Mood/genericPositiveEffects.yml @@ -0,0 +1,47 @@ +- type: moodEffect + id: BeingHugged + moodChange: 3 + timeout: 120 + category: PositiveInteraction + +- type: moodEffect + id: BeingPet + moodChange: 3 + timeout: 120 + category: PositiveInteraction + +- type: moodEffect + id: ArcadePlay + moodChange: 3 + timeout: 480 + +- type: moodEffect + id: GotBlessed + moodChange: 3 + timeout: 480 + +- type: moodEffect + id: PetAnimal + moodChange: 3 + timeout: 300 + +- type: moodEffect + id: SavedLife + moodChange: 7 + timeout: 480 + +- type: moodEffect + id: TraitorFocused # Used for traitors to boost their goals completion. + moodChange: 7 + +- type: moodEffect + id: RevolutionFocused # Used for revolution + moodChange: 7 + +- type: moodEffect + id: CultFocused + moodChange: 10 + +- type: moodEffect + id: TraitSanguine + moodChange: 15 diff --git a/Resources/Prototypes/NPCs/Combat/melee.yml b/Resources/Prototypes/NPCs/Combat/melee.yml index 122875ed97..b0746e5679 100644 --- a/Resources/Prototypes/NPCs/Combat/melee.yml +++ b/Resources/Prototypes/NPCs/Combat/melee.yml @@ -17,6 +17,29 @@ - !type:HTNCompoundTask task: PickupMeleeCompound + - preconditions: + - !type:BuckledPrecondition + isBuckled: true + tasks: + - !type:HTNPrimitiveTask + operator: !type:UnbuckleOperator + shutdownState: TaskFinished + + - preconditions: + - !type:InContainerPrecondition + isInContainer: true + tasks: + - !type:HTNCompoundTask + task: EscapeCompound + + - preconditions: + - !type:PulledPrecondition + isPulled: true + tasks: + - !type:HTNPrimitiveTask + operator: !type:UnPullOperator + shutdownState: TaskFinished + # Melee combat (unarmed or otherwise) - tasks: - !type:HTNPrimitiveTask @@ -101,6 +124,21 @@ proto: NearbyMeleeTargets key: Target +- type: htnCompound + id: EscapeCompound + branches: + - tasks: + - !type:HTNPrimitiveTask + operator: !type:ContainerOperator + targetKey: Target + shutdownState: TaskFinished + - !type:HTNPrimitiveTask + operator: !type:EscapeOperator + targetKey: Target + preconditions: + - !type:InContainerPrecondition + isInContainer: true + - type: htnCompound id: MeleeAttackOrderedTargetCompound branches: diff --git a/Resources/Prototypes/NPCs/debug.yml b/Resources/Prototypes/NPCs/debug.yml new file mode 100644 index 0000000000..c7929be103 --- /dev/null +++ b/Resources/Prototypes/NPCs/debug.yml @@ -0,0 +1,90 @@ +- type: htnCompound + id: DebugCounterCompound + branches: + - tasks: + - !type:HTNPrimitiveTask + operator: !type:AddFloatOperator + targetKey: Count + amount: 1 + + - !type:HTNPrimitiveTask + operator: !type:SayKeyOperator + key: Count + + - !type:HTNPrimitiveTask + operator: !type:RandomOperator + targetKey: IdleTime + minKey: MinimumIdleTime + maxKey: MaximumIdleTime + + - !type:HTNPrimitiveTask + operator: !type:WaitOperator + key: IdleTime + preconditions: + - !type:KeyExistsPrecondition + key: IdleTime + +- type: htnCompound + id: DebugRandomCounterCompound + branches: + - tasks: + - !type:HTNPrimitiveTask + operator: !type:SetRandomFloatOperator + targetKey: Count + minAmount: 0 + maxAmount: 100 + + - !type:HTNPrimitiveTask + operator: !type:SayKeyOperator + key: Count + + - !type:HTNPrimitiveTask + operator: !type:RandomOperator + targetKey: IdleTime + minKey: MinimumIdleTime + maxKey: MaximumIdleTime + + - !type:HTNPrimitiveTask + operator: !type:WaitOperator + key: IdleTime + preconditions: + - !type:KeyExistsPrecondition + key: IdleTime + +- type: htnCompound + id: DebugRandomLessCompound + branches: + - tasks: + - !type:HTNPrimitiveTask + operator: !type:SetRandomFloatOperator + targetKey: Count + minAmount: 0 + maxAmount: 100 + + - !type:HTNPrimitiveTask + operator: !type:SayKeyOperator + key: Count + preconditions: + - !type:KeyFloatLessPrecondition + key: Count + value: 50 + + - !type:HTNPrimitiveTask + operator: !type:RandomOperator + targetKey: IdleTime + minKey: MinimumIdleTime + maxKey: MaximumIdleTime + + - !type:HTNPrimitiveTask + operator: !type:WaitOperator + key: IdleTime + preconditions: + - !type:KeyExistsPrecondition + key: IdleTime + + - tasks: + - !type:HTNPrimitiveTask + operator: !type:SpeakOperator + speech: "fuck!" + + \ No newline at end of file diff --git a/Resources/Prototypes/NPCs/psionic.yml b/Resources/Prototypes/NPCs/psionic.yml new file mode 100644 index 0000000000..0c3772bd91 --- /dev/null +++ b/Resources/Prototypes/NPCs/psionic.yml @@ -0,0 +1,25 @@ +- type: htnCompound + id: MeleePsionicFamiliarCompound + branches: + - tasks: + - !type:HTNCompoundTask + task: MeleeCombatCompound + - tasks: + - !type:HTNCompoundTask + task: FollowCompound + - tasks: + - !type:HTNCompoundTask + task: IdleCompound + +- type: htnCompound + id: RangedPsionicFamiliarCompound + branches: + - tasks: + - !type:HTNCompoundTask + task: InnateRangedCombatCompound + - tasks: + - !type:HTNCompoundTask + task: FollowCompound + - tasks: + - !type:HTNCompoundTask + task: IdleCompound diff --git a/Resources/Prototypes/NPCs/wisp.yml b/Resources/Prototypes/NPCs/wisp.yml new file mode 100644 index 0000000000..3793a47cb2 --- /dev/null +++ b/Resources/Prototypes/NPCs/wisp.yml @@ -0,0 +1,28 @@ +- type: htnCompound + id: GlimmerWispCompound + branches: + - tasks: + - !type:HTNCompoundTask + task: RangedCombatCompound + - tasks: + - !type:HTNCompoundTask + task: DrainPsionicCompound + - tasks: + - !type:HTNCompoundTask + task: IdleCompound + +- type: htnCompound + id: DrainPsionicCompound + branches: + - tasks: + - !type:HTNPrimitiveTask + operator: !type:PickDrainTargetOperator + targetKey: TargetCoordinates + drainKey: DrainTarget + rangeKey: IdleRange + - !type:HTNPrimitiveTask + operator: !type:MoveToOperator + pathfindInPlanning: false + - !type:HTNPrimitiveTask + operator: !type:DrainOperator + drainKey: DrainTarget diff --git a/Resources/Prototypes/Nyanotrasen/Catalog/Fills/Vending/Inventories/maildrobe.yml b/Resources/Prototypes/Nyanotrasen/Catalog/Fills/Vending/Inventories/maildrobe.yml index 579c57ced4..2764255824 100644 --- a/Resources/Prototypes/Nyanotrasen/Catalog/Fills/Vending/Inventories/maildrobe.yml +++ b/Resources/Prototypes/Nyanotrasen/Catalog/Fills/Vending/Inventories/maildrobe.yml @@ -3,6 +3,8 @@ startingInventory: ClothingUniformMailCarrier: 2 ClothingUniformSkirtMailCarrier: 2 + WeaponMailLake: 1 # Frontier + BoxMailCapsulePrimed: 2 # Frontier ClothingHeadMailCarrier: 2 MailBag: 2 ClothingHeadsetCargo: 2 diff --git a/Resources/Prototypes/Nyanotrasen/Damage/groups.yml b/Resources/Prototypes/Nyanotrasen/Damage/groups.yml index d2f9906f45..3160660571 100644 --- a/Resources/Prototypes/Nyanotrasen/Damage/groups.yml +++ b/Resources/Prototypes/Nyanotrasen/Damage/groups.yml @@ -1,4 +1,5 @@ - type: damageGroup id: Immaterial + name: damage-group-immaterial damageTypes: - Holy diff --git a/Resources/Prototypes/Nyanotrasen/Damage/types.yml b/Resources/Prototypes/Nyanotrasen/Damage/types.yml index f9aba7e2ac..22c045e9a6 100644 --- a/Resources/Prototypes/Nyanotrasen/Damage/types.yml +++ b/Resources/Prototypes/Nyanotrasen/Damage/types.yml @@ -1,5 +1,6 @@ # Only affects magical beings. - type: damageType id: Holy + name: damage-type-holy armorCoefficientPrice: 25 armorFlatPrice: 150 diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Clothing/Head/hardsuit-helmets.yml b/Resources/Prototypes/Nyanotrasen/Entities/Clothing/Head/hardsuit-helmets.yml index 1a83a14bd5..3896cd1ef1 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Clothing/Head/hardsuit-helmets.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Clothing/Head/hardsuit-helmets.yml @@ -11,9 +11,6 @@ destroyOnFry: false - type: ClothingGrantPsionicPower power: MetapsionicPower - - type: GuideHelp - guides: - - Psionics - type: entity parent: ClothingHeadHelmetHardsuitSyndie diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Clothing/Head/hats.yml b/Resources/Prototypes/Nyanotrasen/Entities/Clothing/Head/hats.yml index deaca17558..2f23944549 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Clothing/Head/hats.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Clothing/Head/hats.yml @@ -74,9 +74,6 @@ - type: Construction graph: TinfoilHat node: tinfoilhat - - type: GuideHelp - guides: - - Psionics - type: entity parent: ClothingHeadBase diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Clothing/OuterClothing/hardsuits.yml b/Resources/Prototypes/Nyanotrasen/Entities/Clothing/OuterClothing/hardsuits.yml index 6498eda075..4e988c2740 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Clothing/OuterClothing/hardsuits.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Clothing/OuterClothing/hardsuits.yml @@ -19,8 +19,6 @@ sprite: Nyanotrasen/Clothing/OuterClothing/ReverseEngineering/syndicate.rsi - type: ToggleableClothing clothingPrototype: ClothingHeadHelmetHardsuitSyndieReverseEngineered - - type: ReverseEngineering - newItem: null - type: entity parent: ClothingOuterHardsuitJuggernaut @@ -35,8 +33,6 @@ sprite: Nyanotrasen/Clothing/OuterClothing/ReverseEngineering/juggernaut.rsi - type: ToggleableClothing clothingPrototype: ClothingHeadHelmetHardsuitJuggernautReverseEngineered - - type: ReverseEngineering - newItem: null - type: entity parent: ClothingOuterHardsuitERTLeader diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Clothing/Uniforms/jumpsuits.yml b/Resources/Prototypes/Nyanotrasen/Entities/Clothing/Uniforms/jumpsuits.yml index b238ef7b06..55b8522248 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Clothing/Uniforms/jumpsuits.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Clothing/Uniforms/jumpsuits.yml @@ -8,8 +8,8 @@ sprite: Nyanotrasen/Clothing/Uniforms/Jumpsuit/mailman.rsi - type: Clothing sprite: Nyanotrasen/Clothing/Uniforms/Jumpsuit/mailman.rsi -# - type: ClothingAddFaction -# faction: Mailman + - type: ClothingAddFaction + faction: Mailman - type: entity parent: ClothingUniformSkirtBase @@ -21,8 +21,8 @@ sprite: Nyanotrasen/Clothing/Uniforms/Jumpskirt/mailman.rsi - type: Clothing sprite: Nyanotrasen/Clothing/Uniforms/Jumpskirt/mailman.rsi -# - type: ClothingAddFaction -# faction: Mailman + - type: ClothingAddFaction + faction: Mailman - type: entity parent: ClothingUniformJumpsuitResearchDirector diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Markers/Spawners/Random/animals.yml b/Resources/Prototypes/Nyanotrasen/Entities/Markers/Spawners/Random/animals.yml index 0a84109a98..a632a0a22b 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Markers/Spawners/Random/animals.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Markers/Spawners/Random/animals.yml @@ -51,4 +51,5 @@ - MobFrog - MobMothroach - MobArcticFox + - MobPibble rareChance: 0.15 diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Markers/Spawners/ghost_roles.yml b/Resources/Prototypes/Nyanotrasen/Entities/Markers/Spawners/ghost_roles.yml index 046a324e6f..a21976fa7a 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Markers/Spawners/ghost_roles.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Markers/Spawners/ghost_roles.yml @@ -51,23 +51,23 @@ - state: prisoner # - type: MidRoundAntagSpawnLocation # When MidRoundAntag? -- type: entity - id: SpawnPointGhostVampSpider - name: ghost role spawn point - suffix: Vampire spider - parent: MarkerBase - noSpawn: true - components: - - type: GhostRoleMobSpawner - prototype: MobGiantSpiderVampireAngry - - type: GhostRole - makeSentient: true - name: ghost-role-information-giant-spider-vampire-name - description: ghost-role-information-giant-spider-vampire-description - rules: No antagonist restrictions. Just don't talk in emote; you have telepathic chat. - - type: Sprite - sprite: Markers/jobs.rsi - layers: - - state: green - - sprite: Mobs/Animals/bat.rsi - state: bat +#- type: entity +# id: SpawnPointGhostVampSpider +# name: ghost role spawn point +# suffix: Vampire spider +# parent: MarkerBase +# noSpawn: true +# components: +# - type: GhostRoleMobSpawner +# prototype: MobGiantSpiderVampireAngry +# - type: GhostRole +# makeSentient: true +# name: ghost-role-information-giant-spider-vampire-name +# description: ghost-role-information-giant-spider-vampire-description +# rules: No antagonist restrictions. Just don't talk in emote; you have telepathic chat. +# - type: Sprite +# sprite: Markers/jobs.rsi +# layers: +# - state: green +# - sprite: Mobs/Animals/bat.rsi +# state: bat diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Customization/Markings/moth.yml b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Customization/Markings/moth.yml index 680ffb9516..defcc7268c 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Customization/Markings/moth.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Customization/Markings/moth.yml @@ -740,7 +740,7 @@ - type: marking id: MothLArmClassicDeathshead bodyPart: LArm - markingCategory: Arms + markingCategory: LeftArm forcedColoring: true speciesRestriction: [Moth] coloring: @@ -755,7 +755,7 @@ - type: marking id: MothRArmClassicDeathshead bodyPart: RArm - markingCategory: Arms + markingCategory: RightArm forcedColoring: true speciesRestriction: [Moth] coloring: @@ -770,7 +770,7 @@ - type: marking id: MothLLegClassicDeathshead bodyPart: LLeg - markingCategory: Legs + markingCategory: LeftLeg forcedColoring: true speciesRestriction: [Moth] coloring: @@ -785,7 +785,7 @@ - type: marking id: MothRLegClassicDeathshead bodyPart: RLeg - markingCategory: Legs + markingCategory: RightLeg forcedColoring: true speciesRestriction: [Moth] coloring: @@ -832,7 +832,7 @@ - type: marking id: MothLArmClassicFirewatch bodyPart: LArm - markingCategory: Arms + markingCategory: LeftArm forcedColoring: true speciesRestriction: [Moth] coloring: @@ -847,7 +847,7 @@ - type: marking id: MothRArmClassicFirewatch bodyPart: RArm - markingCategory: Arms + markingCategory: RightArm forcedColoring: true speciesRestriction: [Moth] coloring: @@ -862,7 +862,7 @@ - type: marking id: MothLLegClassicFirewatch bodyPart: LLeg - markingCategory: Legs + markingCategory: LeftLeg forcedColoring: true speciesRestriction: [Moth] coloring: @@ -877,7 +877,7 @@ - type: marking id: MothRLegClassicFirewatch bodyPart: RLeg - markingCategory: Legs + markingCategory: RightLeg forcedColoring: true speciesRestriction: [Moth] coloring: @@ -923,7 +923,7 @@ - type: marking id: MothLArmClassicGothic bodyPart: LArm - markingCategory: Arms + markingCategory: LeftArm forcedColoring: true speciesRestriction: [Moth] coloring: @@ -938,7 +938,7 @@ - type: marking id: MothRArmClassicGothic bodyPart: RArm - markingCategory: Arms + markingCategory: RightArm forcedColoring: true speciesRestriction: [Moth] coloring: @@ -953,7 +953,7 @@ - type: marking id: MothLLegClassicGothicL bodyPart: LLeg - markingCategory: Legs + markingCategory: LeftLeg forcedColoring: true speciesRestriction: [Moth] coloring: @@ -968,7 +968,7 @@ - type: marking id: MothRLegClassicGothic bodyPart: RLeg - markingCategory: Legs + markingCategory: RightLeg forcedColoring: true speciesRestriction: [Moth] coloring: @@ -1014,7 +1014,7 @@ - type: marking id: MothLArmClassicJungle bodyPart: LArm - markingCategory: Arms + markingCategory: LeftArm forcedColoring: true speciesRestriction: [Moth] coloring: @@ -1029,7 +1029,7 @@ - type: marking id: MothRArmClassicJungle bodyPart: RArm - markingCategory: Arms + markingCategory: RightArm forcedColoring: true speciesRestriction: [Moth] coloring: @@ -1044,7 +1044,7 @@ - type: marking id: MothLLegClassicJungle bodyPart: LLeg - markingCategory: Legs + markingCategory: LeftLeg forcedColoring: true speciesRestriction: [Moth] coloring: @@ -1059,7 +1059,7 @@ - type: marking id: MothRLegClassicJungle bodyPart: RLeg - markingCategory: Legs + markingCategory: RightLeg forcedColoring: true speciesRestriction: [Moth] coloring: @@ -1105,7 +1105,7 @@ - type: marking id: MothLArmClassicLovers bodyPart: LArm - markingCategory: Arms + markingCategory: LeftArm forcedColoring: true speciesRestriction: [Moth] coloring: @@ -1120,7 +1120,7 @@ - type: marking id: MothRArmClassicLovers bodyPart: RArm - markingCategory: Arms + markingCategory: RightArm forcedColoring: true speciesRestriction: [Moth] coloring: @@ -1135,7 +1135,7 @@ - type: marking id: MothLLegClassicLovers bodyPart: LLeg - markingCategory: Legs + markingCategory: LeftLeg forcedColoring: true speciesRestriction: [Moth] coloring: @@ -1150,7 +1150,7 @@ - type: marking id: MothRLegClassicLovers bodyPart: RLeg - markingCategory: Legs + markingCategory: RightLeg forcedColoring: true speciesRestriction: [Moth] coloring: @@ -1196,7 +1196,7 @@ - type: marking id: MothLArmClassicMoonfly bodyPart: LArm - markingCategory: Arms + markingCategory: LeftArm forcedColoring: true speciesRestriction: [Moth] coloring: @@ -1211,7 +1211,7 @@ - type: marking id: MothRArmClassicMoonfly bodyPart: RArm - markingCategory: Arms + markingCategory: RightArm forcedColoring: true speciesRestriction: [Moth] coloring: @@ -1226,7 +1226,7 @@ - type: marking id: MothLLegClassicMoonfly bodyPart: LLeg - markingCategory: Legs + markingCategory: LeftLeg forcedColoring: true speciesRestriction: [Moth] coloring: @@ -1241,7 +1241,7 @@ - type: marking id: MothRLegClassicMoonfly bodyPart: RLeg - markingCategory: Legs + markingCategory: RightLeg forcedColoring: true speciesRestriction: [Moth] coloring: @@ -1287,7 +1287,7 @@ - type: marking id: MothLArmClassicOakworm bodyPart: LArm - markingCategory: Arms + markingCategory: LeftArm forcedColoring: true speciesRestriction: [Moth] coloring: @@ -1302,7 +1302,7 @@ - type: marking id: MothRArmClassicOakworm bodyPart: RArm - markingCategory: Arms + markingCategory: RightArm forcedColoring: true speciesRestriction: [Moth] coloring: @@ -1317,7 +1317,7 @@ - type: marking id: MothLLegClassicOakworm bodyPart: LLeg - markingCategory: Legs + markingCategory: LeftLeg forcedColoring: true speciesRestriction: [Moth] coloring: @@ -1332,7 +1332,7 @@ - type: marking id: MothRLegClassicOakworm bodyPart: RLeg - markingCategory: Legs + markingCategory: RightLeg forcedColoring: true speciesRestriction: [Moth] coloring: @@ -1378,7 +1378,7 @@ - type: marking id: MothLArmClassicPoison bodyPart: LArm - markingCategory: Arms + markingCategory: LeftArm forcedColoring: true speciesRestriction: [Moth] coloring: @@ -1393,7 +1393,7 @@ - type: marking id: MothRArmClassicPoison bodyPart: RArm - markingCategory: Arms + markingCategory: RightArm forcedColoring: true speciesRestriction: [Moth] coloring: @@ -1408,7 +1408,7 @@ - type: marking id: MothLLegClassicPoison bodyPart: LLeg - markingCategory: Legs + markingCategory: LeftLeg forcedColoring: true speciesRestriction: [Moth] coloring: @@ -1423,7 +1423,7 @@ - type: marking id: MothRLegClassicPoison bodyPart: RLeg - markingCategory: Legs + markingCategory: RightLeg forcedColoring: true speciesRestriction: [Moth] coloring: @@ -1469,7 +1469,7 @@ - type: marking id: MothLArmClassicRagged bodyPart: LArm - markingCategory: Arms + markingCategory: LeftArm forcedColoring: true speciesRestriction: [Moth] coloring: @@ -1484,7 +1484,7 @@ - type: marking id: MothRArmClassicRagged bodyPart: RArm - markingCategory: Arms + markingCategory: RightArm forcedColoring: true speciesRestriction: [Moth] coloring: @@ -1499,7 +1499,7 @@ - type: marking id: MothLLegClassicRagged bodyPart: LLeg - markingCategory: Legs + markingCategory: LeftLeg forcedColoring: true speciesRestriction: [Moth] coloring: @@ -1514,7 +1514,7 @@ - type: marking id: MothRLegClassicRagged bodyPart: RLeg - markingCategory: Legs + markingCategory: RightLeg forcedColoring: true speciesRestriction: [Moth] coloring: @@ -1560,7 +1560,7 @@ - type: marking id: MothLArmClassicReddish bodyPart: LArm - markingCategory: Arms + markingCategory: LeftArm forcedColoring: true speciesRestriction: [Moth] coloring: @@ -1575,7 +1575,7 @@ - type: marking id: MothRArmClassicReddish bodyPart: RArm - markingCategory: Arms + markingCategory: RightArm forcedColoring: true speciesRestriction: [Moth] coloring: @@ -1590,7 +1590,7 @@ - type: marking id: MothLLegClassicReddish bodyPart: LLeg - markingCategory: Legs + markingCategory: LeftLeg forcedColoring: true speciesRestriction: [Moth] coloring: @@ -1605,7 +1605,7 @@ - type: marking id: MothRLegClassicReddish bodyPart: RLeg - markingCategory: Legs + markingCategory: RightLeg forcedColoring: true speciesRestriction: [Moth] coloring: @@ -1651,7 +1651,7 @@ - type: marking id: MothLArmClassicRoyal bodyPart: LArm - markingCategory: Arms + markingCategory: LeftArm forcedColoring: true speciesRestriction: [Moth] coloring: @@ -1666,7 +1666,7 @@ - type: marking id: MothRArmClassicRoyal bodyPart: RArm - markingCategory: Arms + markingCategory: RightArm forcedColoring: true speciesRestriction: [Moth] coloring: @@ -1681,7 +1681,7 @@ - type: marking id: MothLLegClassicRoyal bodyPart: LLeg - markingCategory: Legs + markingCategory: LeftLeg forcedColoring: true speciesRestriction: [Moth] coloring: @@ -1696,7 +1696,7 @@ - type: marking id: MothRLegClassicRoyal bodyPart: RLeg - markingCategory: Legs + markingCategory: RightLeg forcedColoring: true speciesRestriction: [Moth] coloring: @@ -1742,7 +1742,7 @@ - type: marking id: MothLArmClassicWhitefly bodyPart: LArm - markingCategory: Arms + markingCategory: LeftArm forcedColoring: true speciesRestriction: [Moth] coloring: @@ -1757,7 +1757,7 @@ - type: marking id: MothRArmClassicWhitefly bodyPart: RArm - markingCategory: Arms + markingCategory: RightArm forcedColoring: true speciesRestriction: [Moth] coloring: @@ -1772,7 +1772,7 @@ - type: marking id: MothLLegClassicWhitefly bodyPart: LLeg - markingCategory: Legs + markingCategory: LeftLeg forcedColoring: true speciesRestriction: [Moth] coloring: @@ -1787,7 +1787,7 @@ - type: marking id: MothRLegClassicWhitefly bodyPart: RLeg - markingCategory: Legs + markingCategory: RightLeg forcedColoring: true speciesRestriction: [Moth] coloring: @@ -1833,7 +1833,7 @@ - type: marking id: MothLArmClassicWitchking bodyPart: LArm - markingCategory: Arms + markingCategory: LeftArm forcedColoring: true speciesRestriction: [Moth] coloring: @@ -1848,7 +1848,7 @@ - type: marking id: MothRArmClassicWitchking bodyPart: RArm - markingCategory: Arms + markingCategory: RightArm forcedColoring: true speciesRestriction: [Moth] coloring: @@ -1863,7 +1863,7 @@ - type: marking id: MothLLegClassicWitchking bodyPart: LLeg - markingCategory: Legs + markingCategory: LeftLeg forcedColoring: true speciesRestriction: [Moth] coloring: @@ -1878,7 +1878,7 @@ - type: marking id: MothRLegClassicWitchking bodyPart: RLeg - markingCategory: Legs + markingCategory: RightLeg forcedColoring: true speciesRestriction: [Moth] coloring: diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/NPCs/mutants.yml b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/NPCs/mutants.yml index 462b3254f1..b3de9e0191 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/NPCs/mutants.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/NPCs/mutants.yml @@ -70,130 +70,132 @@ - type: Produce - type: NoSlip -- type: entity - name: oneirophage - parent: MobGiantSpider - id: MobGiantSpiderVampire - description: The 'dream-eater' spider, rumored to be one of the potential genetic sources for arachne. - components: - - type: Sprite - drawdepth: Mobs - layers: - - map: ["enum.DamageStateVisualLayers.Base", "movement"] - state: viper - sprite: Mobs/Animals/spider.rsi - - type: SpriteMovement - movementLayers: - movement: - state: viper-moving - noMovementLayers: - movement: - state: viper - - type: Appearance - - type: DamageStateVisuals - states: - Alive: - Base: viper - Critical: - Base: viper_dead - Dead: - Base: viper_dead - - type: ReplacementAccent - accent: xeno - - type: InteractionPopup - successChance: 0.5 - interactSuccessString: petting-success-tarantula - interactFailureString: petting-failure-generic - interactSuccessSpawn: EffectHearts - interactSuccessSound: - path: /Audio/Animals/snake_hiss.ogg - - type: Puller - needsHands: false - - type: Arachne - cocoonDelay: 8 - - type: SolutionContainerManager - solutions: - melee: - reagents: - - ReagentId: Nocturine - Quantity: 20 - - type: MeleeChemicalInjector - solution: melee - transferAmount: 3.5 - - type: SolutionRegeneration - solution: melee - generated: - reagents: - - ReagentId: Nocturine - Quantity: 0.15 - - type: BloodSucker - unitsToSucc: 35 - injectWhenSucc: true - injectReagent: Cryptobiolin - unitsToInject: 10 - webRequired: true - - type: Bloodstream - bloodReagent: DemonsBlood - - type: Body - prototype: VampiricAnimalLarge - - type: PotentialPsionic - - type: Psionic - removable: false - - type: MetapsionicPower - - type: AntiPsionicWeapon - punish: false - modifiers: - coefficients: - Piercing: 2.25 - - type: Damageable - damageContainer: HalfSpirit - damageModifierSet: HalfSpirit - - type: StatusEffects - allowed: - - Stun - - KnockedDown - - SlowedDown - - Stutter - - SeeingRainbows - - Electrocution - - Drunk - - SlurredSpeech - - PressureImmunity - - Muted - - ForcedSleep - - TemporaryBlindness - - Pacified - - PsionicsDisabled - - PsionicallyInsulated - - type: Tag - tags: - - Oneirophage - - type: MovementAlwaysTouching - - type: PsionicInvisibleContacts - whitelist: - tags: - - ArachneWeb +#- type: entity +# name: oneirophage +# parent: MobGiantSpider +# id: MobGiantSpiderVampire +# description: The 'dream-eater' spider, rumored to be one of the potential genetic sources for arachne. +# components: +# - type: Sprite +# drawdepth: Mobs +# layers: +# - map: ["enum.DamageStateVisualLayers.Base", "movement"] +# state: viper +# sprite: Mobs/Animals/spider.rsi +# - type: SpriteMovement +# movementLayers: +# movement: +# state: viper-moving +# noMovementLayers: +# movement: +# state: viper +# - type: Appearance +# - type: DamageStateVisuals +# states: +# Alive: +# Base: viper +# Critical: +# Base: viper_dead +# Dead: +# Base: viper_dead +# - type: ReplacementAccent +# accent: xeno +# - type: InteractionPopup +# successChance: 0.5 +# interactSuccessString: petting-success-tarantula +# interactFailureString: petting-failure-generic +# interactSuccessSpawn: EffectHearts +# interactSuccessSound: +# path: /Audio/Animals/snake_hiss.ogg +# - type: Puller +# needsHands: false +# - type: Cocooner +# cocoonDelay: 8 +# - type: SolutionContainerManager +# solutions: +# melee: +# reagents: +# - ReagentId: Nocturine +# Quantity: 20 +# - type: MeleeChemicalInjector +# solution: melee +# transferAmount: 3.5 +# - type: SolutionRegeneration +# solution: melee +# generated: +# reagents: +# - ReagentId: Nocturine +# Quantity: 0.15 +# - type: BloodSucker +# unitsToSucc: 35 +# injectWhenSucc: true +# injectReagent: Cryptobiolin +# unitsToInject: 10 +# webRequired: true +# - type: Bloodstream +# bloodReagent: DemonsBlood +# - type: Body +# prototype: VampiricAnimalLarge +# - type: Psionic +# removable: false +# - type: InnatePsionicPowers +# powersToAdd: +# - MetapsionicPower +# - PsionicInvisibilityPower +# - type: AntiPsionicWeapon +# punish: false +# modifiers: +# coefficients: +# Piercing: 2.25 +# - type: Damageable +# damageContainer: HalfSpirit +# damageModifierSet: HalfSpirit +# - type: StatusEffects +# allowed: +# - Stun +# - KnockedDown +# - SlowedDown +# - Stutter +# - SeeingRainbows +# - Electrocution +# - Drunk +# - SlurredSpeech +# - PressureImmunity +# - Muted +# - ForcedSleep +# - TemporaryBlindness +# - Pacified +# - PsionicsDisabled +# - PsionicallyInsulated +# - type: Tag +# tags: +# - Oneirophage +# - type: MovementAlwaysTouching +# - type: PsionicInvisibleContacts +# whitelist: +# tags: +# - ArachneWeb -- type: entity - name: oneirophage - parent: MobGiantSpiderVampire - id: MobGiantSpiderVampireAngry - suffix: Angry - components: - - type: NpcFactionMember - factions: - - SimpleHostile - - type: InputMover - - type: MobMover - - type: HTN - rootTask: - task: SimpleHostileCompound - - type: GhostRole - makeSentient: true - name: ghost-role-information-giant-spider-vampire-name - description: ghost-role-information-giant-spider-vampire-description - rules: No antagonist restrictions. Just don't talk in emote; you have telepathic chat. - - type: GhostTakeoverAvailable +#- type: entity +# name: oneirophage +# parent: MobGiantSpiderVampire +# id: MobGiantSpiderVampireAngry +# suffix: Angry +# components: +# - type: NpcFactionMember +# factions: +# - SimpleHostile +# - type: InputMover +# - type: MobMover +# - type: HTN +# rootTask: +# task: SimpleHostileCompound +# - type: GhostRole +# makeSentient: true +# name: ghost-role-information-giant-spider-vampire-name +# description: ghost-role-information-giant-spider-vampire-description +# rules: No antagonist restrictions. Just don't talk in emote; you have telepathic chat. +# - type: GhostTakeoverAvailable - type: entity parent: SimpleMobBase @@ -296,9 +298,10 @@ - type: Puller needsHands: false - type: Vocal - # mice are gender neutral who cares - maleScream: /Audio/Animals/mouse_squeak.ogg - femaleScream: /Audio/Animals/mouse_squeak.ogg + sounds: + Male: Mouse + Female: Mouse + Unsexed: Mouse wilhelmProbability: 0.001 - type: Tag tags: @@ -333,11 +336,7 @@ proper: true gender: male - type: IntrinsicRadioReceiver - channels: - - Common - type: IntrinsicRadioTransmitter - channels: - - Common - type: ActiveRadio channels: - Common diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/NPCs/xeno.yml b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/NPCs/xeno.yml index 9dbe9f95f7..e12b14d490 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/NPCs/xeno.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/NPCs/xeno.yml @@ -6,6 +6,7 @@ id: MobPurpleSnakeGhost components: - type: GhostTakeoverAvailable + - type: GhostRole allowMovement: true allowSpeech: false makeSentient: true @@ -19,6 +20,7 @@ id: MobSmallPurpleSnakeGhost components: - type: GhostTakeoverAvailable + - type: GhostRole allowMovement: true allowSpeech: false makeSentient: true diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Player/Oni.yml b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Player/Oni.yml index e1c867691a..8e9e2c62df 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Player/Oni.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Player/Oni.yml @@ -27,4 +27,3 @@ - type: NpcFactionMember factions: - NanoTrasen - - type: PotentialPsionic diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Player/felinid.yml b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Player/felinid.yml index 9b79c55670..9a04a0458e 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Player/felinid.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Player/felinid.yml @@ -44,5 +44,5 @@ - type: NpcFactionMember factions: - NanoTrasen - - type: PotentialPsionic + - Cat diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Player/special.yml b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Player/special.yml index abe2833e89..70628ec4e5 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Player/special.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Player/special.yml @@ -42,6 +42,7 @@ visMask: - Normal - TelegnosticProjection + - PsionicInvisibility - type: Input context: "ghost" - type: Examiner diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Species/Oni.yml b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Species/Oni.yml index c1655287fd..8a5663dce4 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Species/Oni.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Species/Oni.yml @@ -40,11 +40,13 @@ proto: oni - type: LanguageKnowledge speaks: - - GalacticCommon + - TauCetiBasic - Nekomimetic understands: - - GalacticCommon + - TauCetiBasic - Nekomimetic + - type: PotentiaModifier + potentiaMultiplier: 0.5 - type: entity save: false diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Species/felinid.yml b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Species/felinid.yml index 285c7340b2..44779fe950 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Species/felinid.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Species/felinid.yml @@ -67,18 +67,23 @@ - type: NoShoesSilentFootsteps - type: LanguageKnowledge speaks: - - GalacticCommon - - SolCommon + - TauCetiBasic - Nekomimetic understands: - - GalacticCommon - - SolCommon + - TauCetiBasic - Nekomimetic - type: Thieving ignoreStripHidden: true stealth: Subtle stripTimeReduction: 0 stripTimeMultiplier: 0.667 + - type: StepTriggerImmune + whitelist: + types: + - Shard + - Landmine + - Mousetrap + - SlipEntity - type: entity save: false diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Devices/CircuitBoards/production.yml b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Devices/CircuitBoards/production.yml index fc40ea1639..2ed4a594e3 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Devices/CircuitBoards/production.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Devices/CircuitBoards/production.yml @@ -1,24 +1,3 @@ -- type: entity - id: MetempsychoticMachineCircuitboard - parent: BaseMachineCircuitboard - name: metempsychotic machine machine board - description: A machine printed circuit board for a cloning pod - components: - - type: Sprite - state: medical - - type: MachineBoard - prototype: MetempsychoticMachine - requirements: - Capacitor: 2 - Manipulator: 2 - materialRequirements: - Glass: 1 - Cable: 1 - - type: ReverseEngineering - difficulty: 3 - recipes: - - MetempsychoticMachineCircuitboard - - type: entity id: ReverseEngineeringMachineCircuitboard parent: BaseMachineCircuitboard @@ -33,9 +12,8 @@ MatterBin: 1 Manipulator: 1 materialRequirements: - Glass: 1 Cable: 1 - Diamond: 10 + PlasmaGlass: 5 tagRequirements: BorgArm: Amount: 3 diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Devices/pda.yml b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Devices/pda.yml index d898124b77..c85255f814 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Devices/pda.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Devices/pda.yml @@ -32,7 +32,7 @@ accentVColor: "#DFDFDF" - type: Icon state: pda-security - - type: CartridgeLoader # DeltaV - Crime Assist + SecWatch + - type: CartridgeLoader # Adds Crime Assist and SecWatch preinstalled: - CrewManifestCartridge - NotekeeperCartridge @@ -43,7 +43,7 @@ - type: entity parent: BasePDA id: MailCarrierPDA - name: courier PDA # DeltaV - Mail Carrier to Courier replacement + name: courier PDA description: Smells like unopened letters. components: - type: Sprite @@ -67,6 +67,12 @@ - type: Icon sprite: DeltaV/Objects/Devices/pda.rsi state: pda-mailcarrier + - type: CartridgeLoader # Adds a courier performance tracker + preinstalled: + - CrewManifestCartridge + - NotekeeperCartridge + - NewsReaderCartridge + - MailMetricsCartridge - type: entity parent: BasePDA diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Chapel/amphorae.yml b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Chapel/amphorae.yml index 10f5d631aa..2ef262fd53 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Chapel/amphorae.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Chapel/amphorae.yml @@ -17,7 +17,6 @@ jar: maxVol: 120 - type: Drink - isOpen: true solution: jar - type: Spillable solution: jar @@ -35,8 +34,8 @@ canChangeTransferAmount: true - type: UserInterface interfaces: - - key: enum.TransferAmountUiKey.Key - type: TransferAmountBoundUserInterface + enum.TransferAmountUiKey.Key: + type: TransferAmountBoundUserInterface - type: Damageable damageContainer: Inorganic - type: Destructible diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/base_mail.yml b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/base_mail.yml index c1ca2cd60b..75007db11c 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/base_mail.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/base_mail.yml @@ -5,11 +5,13 @@ name: mail-item-name-unaddressed components: - type: Item - size: Normal +# size: Normal # Frontier + storedRotation: -90 - type: Mail - type: AccessReader - type: Sprite - sprite: Nyanotrasen/Objects/Specific/Mail/mail.rsi + scale: 0.7, 0.7 # Frontier + sprite: Objects/Specific/Mail/mail.rsi layers: - state: icon map: ["enum.MailVisualLayers.Icon"] @@ -18,8 +20,8 @@ map: ["enum.MailVisualLayers.FragileStamp"] visible: false - map: ["enum.MailVisualLayers.JobStamp"] - scale: 0.5, 0.5 - offset: 0.275, 0.2 + scale: 0.8, 0.8 # Frontier 0.5<0.8 + offset: 0.225, 0.165 # Frontier (0.275, 0.2)<(0.225, 0.165) - state: locked map: ["enum.MailVisualLayers.Lock"] - state: priority @@ -77,7 +79,6 @@ - trigger: !type:DamageTrigger damage: 5 - triggersOnce: true behaviors: - !type:DoActsBehavior acts: [ "Breakage" ] @@ -92,6 +93,20 @@ damage: types: Blunt: 10 + - type: CargoSellBlacklist # Frontier + - type: Food # Frontier - Moth food + requiresSpecialDigestion: true + - type: SolutionContainerManager + solutions: + food: + maxVol: 1 + reagents: + - ReagentId: Nothing + Quantity: 1 + - type: LanguageSpeaker + - type: LanguageKnowledge + speaks: [TauCetiBasic] # So that them foreigners can't understand what the broken mail is saying + understands: [] # This empty parcel is allowed to exist and evade the tests for the admin # mailto command. diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/mail.yml b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/mail.yml deleted file mode 100644 index 7f1b26d9cb..0000000000 --- a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/mail.yml +++ /dev/null @@ -1,835 +0,0 @@ -- type: entity - noSpawn: true - parent: BaseMail - id: MailAlcohol - suffix: alcohol - components: - - type: Mail - contents: - - id: DrinkAbsintheBottleFull - orGroup: Drink - - id: DrinkBlueCuracaoBottleFull - orGroup: Drink - - id: DrinkGinBottleFull - orGroup: Drink - - id: DrinkMelonLiquorBottleFull - orGroup: Drink - - id: DrinkRumBottleFull - orGroup: Drink - - id: DrinkTequilaBottleFull - orGroup: Drink - - id: DrinkVermouthBottleFull - orGroup: Drink - - id: DrinkVodkaBottleFull - orGroup: Drink - - id: DrinkWineBottleFull - orGroup: Drink - - id: DrinkGlass - amount: 2 - -- type: entity - noSpawn: true - parent: BaseMail - id: MailSake - suffix: osake - components: - - type: Mail - contents: - - id: DrinkSakeCup - amount: 2 - - id: DrinkTokkuri - -- type: entity - noSpawn: true - parent: BaseMail - id: MailAMEGuide - suffix: ameguide - components: - - type: Mail - contents: - - id: PaperWrittenAMEScribbles - - id: Pen - -- type: entity - noSpawn: true - parent: BaseMail - id: MailBible - suffix: bible - components: - - type: Mail - contents: - - id: Bible - -- type: entity - noSpawn: true - parent: BaseMail - id: MailBikeHorn - suffix: bike horn - components: - - type: Mail - contents: - - id: BikeHorn - -- type: entity - noSpawn: true - parent: BaseMail - id: MailBlockGameDIY - suffix: blockgamediy - components: - - type: Mail - contents: - - id: BlockGameArcadeComputerCircuitboard - -#- type: entity -# noSpawn: true -# parent: BaseMail -# id: MailBooks -# suffix: books -# components: -# - type: Mail -# contents: -# # Don't use BookDemonomiconRandom. -# # It uses a RandomSpawner which just spawns the book outside of the mail. -# - id: BookDemonomicon1 -# orGroup: Demonomicon -# - id: BookDemonomicon2 -# orGroup: Demonomicon -# - id: BookDemonomicon3 -# orGroup: Demonomicon -# # There's no way to signal "spawn nothing" with an orGroup, -# # so have this blank book instead. Write your own demon summoning tome! -# - id: BookRandom -# prob: 3 -# orGroup: Demonomicon -# - id: BookChemistryInsane -# prob: 0.10 -# - id: BookBotanicalTextbook -# prob: 0.5 -# - id: BookFishing -# prob: 0.10 -# - id: BookDetective -# prob: 0.10 -# - id: BookGnominomicon -# prob: 0.2 -# - id: BookFishops # DeltaV - fishops book -# prob: 0.2 -# - id: BookSalvageEpistemics1 -# prob: 0.025 - -- type: entity - noSpawn: true - parent: BaseMail - id: MailCake - suffix: cake - components: - - type: Mail - isFragile: true - isPriority: true - contents: - - id: FoodCakeBlueberry - orGroup: Cake - - id: FoodCakeCarrot - orGroup: Cake - - id: FoodCakeCheese - orGroup: Cake - - id: FoodCakeChocolate - orGroup: Cake - - id: FoodCakeChristmas - orGroup: Cake - - id: FoodCakeClown - orGroup: Cake - - id: FoodCakeLemon - orGroup: Cake - - id: FoodCakeLime - orGroup: Cake - - id: FoodCakeOrange - orGroup: Cake - - id: FoodCakePumpkin - orGroup: Cake - - id: FoodCakeVanilla - orGroup: Cake - - id: FoodMothMothmallow - orGroup: Cake - prob: 0.5 - - id: KnifePlastic - - id: ForkPlastic - amount: 2 - -- type: entity - noSpawn: true - parent: BaseMail - id: MailCallForHelp - suffix: call-for-help - components: - - type: Mail - contents: - - id: PaperMailCallForHelp1 - orGroup: Paper - - id: PaperMailCallForHelp2 - orGroup: Paper - - id: PaperMailCallForHelp3 - orGroup: Paper - - id: PaperMailCallForHelp4 - orGroup: Paper - - id: PaperMailCallForHelp5 - orGroup: Paper - - id: FlashlightLantern - orGroup: Gift - - id: Crowbar - orGroup: Gift - prob: 0.5 - - id: CrowbarRed - orGroup: Gift - prob: 0.5 - - id: ClothingMaskGas - orGroup: Gift - - id: WeaponFlareGun - orGroup: Gift - prob: 0.25 - -- type: entity - noSpawn: true - parent: BaseMail - id: MailCheese - suffix: cheese - components: - - type: Mail - isFragile: true - isPriority: true - contents: - - id: FoodCheese - - id: KnifePlastic - -- type: entity - noSpawn: true - parent: BaseMail - id: MailChocolate - suffix: chocolate - components: - - type: Mail - contents: - # TODO make some actual chocolate candy items. - - id: FoodSnackChocolate - amount: 3 - -- type: entity - noSpawn: true - parent: BaseMail - id: MailCigarettes - suffix: cigs - components: - - type: Mail - contents: - - id: CigPackRed - - id: CheapLighter - -- type: entity - noSpawn: true - parent: BaseMail - id: MailCigars - suffix: Cigars - components: - - type: Mail - contents: - - id: CigarCase - - id: Lighter - -- type: entity - noSpawn: true - parent: BaseMail - id: MailCookies - suffix: cookies - components: - - type: Mail - # What, you want to eat stale cookies? - isPriority: true - contents: - - id: FoodBakedCookie - - id: FoodBakedCookieOatmeal - - id: FoodBakedCookieRaisin - - id: FoodBakedCookieSugar - -- type: entity - noSpawn: true - parent: BaseMail - id: MailCosplayArc - suffix: cosplay-arc - components: - - type: Mail - openSound: /Audio/Nyanotrasen/Voice/Felinid/cat_wilhelm.ogg - contents: - - id: ClothingCostumeArcDress - -- type: entity - noSpawn: true - parent: BaseMail - id: MailCosplayGeisha - suffix: cosplay-geisha - components: - - type: Mail - contents: - - id: UniformGeisha - - id: DrinkTeapot - - id: DrinkTeacup - amount: 3 - -- type: entity - noSpawn: true - parent: BaseMail - id: MailCosplayMaid - suffix: cosplay-maid - components: - - type: Mail - contents: - - id: UniformMaid - - id: SprayBottleSpaceCleaner - -- type: entity - noSpawn: true - parent: BaseMail - id: MailCosplayNurse - suffix: cosplay-nurse - components: - - type: Mail - contents: - - id: ClothingUniformJumpskirtNurse - - id: Syringe - -- type: entity - noSpawn: true - parent: BaseMail - id: MailCosplaySchoolgirl - suffix: cosplay-schoolgirl - components: - - type: Mail - contents: - - id: UniformSchoolgirlBlack - orGroup: Color - - id: UniformSchoolgirlBlue - orGroup: Color - - id: UniformSchoolgirlCyan - orGroup: Color - - id: UniformSchoolgirlGreen - orGroup: Color - - id: UniformSchoolgirlOrange - orGroup: Color - - id: UniformSchoolgirlPink - orGroup: Color - - id: UniformSchoolgirlPurple - orGroup: Color - - id: UniformSchoolgirlRed - orGroup: Color - -- type: entity - noSpawn: true - parent: BaseMail - id: MailCosplayWizard - suffix: cosplay-wizard - components: - - type: Mail - contents: - - id: ClothingOuterWizardFake - - id: ClothingHeadHatWizardFake - - id: ClothingShoesWizardFake - -- type: entity - noSpawn: true - parent: BaseMail - id: MailCrayon - suffix: Crayon - components: - - type: Mail - contents: - - id: CrayonBox - -- type: entity - noSpawn: true - parent: BaseMail - id: MailFigurine - suffix: figurine - components: - - type: Mail - isFragile: true - contents: - - id: ToyAi - orGroup: Toy - - id: ToyNuke - orGroup: Toy - - id: ToyFigurinePassenger - orGroup: Toy - - id: ToyGriffin - orGroup: Toy - - id: ToyHonk - orGroup: Toy - - id: ToyIan - orGroup: Toy - - id: ToyMarauder - orGroup: Toy - - id: ToyMauler - orGroup: Toy - - id: ToyGygax - orGroup: Toy - - id: ToyOdysseus - orGroup: Toy - - id: ToyOwlman - orGroup: Toy - - id: ToyDeathRipley - orGroup: Toy - - id: ToyPhazon - orGroup: Toy - - id: ToyFireRipley - orGroup: Toy - - id: ToyReticence - orGroup: Toy - - id: ToyRipley - orGroup: Toy - - id: ToySeraph - orGroup: Toy - - id: ToyDurand - orGroup: Toy - -- type: entity - noSpawn: true - parent: BaseMail - id: MailFishingCap - suffix: fishingcap - components: - - type: Mail - contents: - - id: ClothingHeadFishCap - -- type: entity - noSpawn: true - parent: BaseMail - id: MailFlashlight - suffix: Flashlight - components: - - type: Mail - contents: - - id: FlashlightLantern - -#- type: entity -# noSpawn: true -# parent: BaseMail -# id: MailFlowers -# suffix: flowers -# components: -# - type: Mail -# contents: -# # TODO actual flowers -# - id: ClothingHeadHatFlowerCrown -# orGroup: Flower -# - id: ClothingHeadHatHairflower -# orGroup: Flower - -- type: entity - noSpawn: true - parent: BaseMail - id: MailHighlander - suffix: highlander - components: - - type: Mail - contents: - - id: ClothingUniformJumpskirtColorRed - - id: ClothingHeadHatBeret - - id: DrinkRedMeadGlass - - id: Claymore - -- type: entity - noSpawn: true - parent: BaseMail - id: MailHighlanderDulled - suffix: highlander, dulled - components: - - type: Mail - contents: - - id: ClothingUniformJumpskirtColorRed - - id: ClothingHeadHatBeret - - id: DrinkGlass - - id: ClaymoreDulled - -- type: entity - noSpawn: true - parent: BaseMail - id: MailHoneyBuns - suffix: honeybuns - components: - - type: Mail - contents: - - id: FoodBakedBunHoney - amount: 2 - -- type: entity - noSpawn: true - parent: BaseMail - id: MailJunkFood - suffix: junk food - components: - - type: Mail - contents: - - id: FoodBoxDonkpocket - - id: FoodSnackChips - -- type: entity - noSpawn: true - parent: BaseMail - id: MailKatana - suffix: Katana - components: - - type: Mail - contents: - - id: Katana - prob: 0.1 - orGroup: Katana - - id: KatanaDulled - prob: 0.9 - orGroup: Katana - -- type: entity - noSpawn: true - parent: BaseMail - id: MailKnife - suffix: Knife - components: - - type: Mail - contents: - - id: CombatKnife - -- type: entity - noSpawn: true - parent: BaseMail - id: MailMoney - suffix: money - components: - - type: Mail - contents: - - id: SpaceCash100 - orGroup: Cash - prob: 0.3 - - id: SpaceCash500 - orGroup: Cash - prob: 0.6 - - id: SpaceCash1000 - orGroup: Cash - prob: 0.3 - -- type: entity - noSpawn: true - parent: BaseMail - id: MailMuffins - suffix: muffins - components: - - type: Mail - isPriority: true - contents: - - id: FoodBakedMuffinBerry - - id: FoodBakedMuffinCherry - - id: FoodBakedMuffinBluecherry - -- type: entity - noSpawn: true - parent: BaseMail - id: MailMoffins - suffix: moffins - components: - - type: Mail - isPriority: true - contents: - - id: FoodMothMoffin - amount: 3 - -- type: entity - noSpawn: true - parent: BaseMail - id: MailNoir - suffix: noir - components: - - type: Mail - contents: - - id: ClothingUniformJumpsuitDetectiveGrey - - id: ClothingUniformJumpskirtDetectiveGrey - - id: ClothingHeadHatBowlerHat - - id: ClothingOuterCoatGentle - -- type: entity - noSpawn: true - parent: BaseMail - id: MailPAI - suffix: PAI - components: - - type: Mail - contents: - - id: PersonalAI - -- type: entity - noSpawn: true - parent: BaseMail - id: MailPlushie - suffix: plushie - components: - - type: Mail - contents: - - id: PlushieMothRandom - orGroup: Prize - - id: PlushieMothMusician - orGroup: Prize - - id: PlushieMothBartender - orGroup: Prize - - id: PlushieBee - orGroup: Prize - - id: PlushieHampter - orGroup: Prize - - id: PlushieRouny - orGroup: Prize - - id: PlushieLamp - orGroup: Prize - - id: PlushieArachind - orGroup: Prize - - id: PlushieLizard - orGroup: Prize - - id: PlushieLizardMirrored - orGroup: Prize - - id: PlushieSpaceLizard - orGroup: Prize - - id: PlushieDiona - orGroup: Prize - - id: PlushieSharkBlue - orGroup: Prize - - id: PlushieSharkPink - orGroup: Prize - - id: PlushieSharkGrey - orGroup: Prize - - id: PlushieCarp - orGroup: Prize - - id: PlushieMagicarp - orGroup: Prize - - id: PlushieHolocarp - orGroup: Prize - - id: PlushieSlime - orGroup: Prize - - id: PlushieSnake - orGroup: Prize - - id: PlushieVox - orGroup: Prize - - id: PlushieAtmosian - orGroup: Prize - - id: PlushiePenguin - orGroup: Prize - - id: PlushieHuman - orGroup: Prize - - id: PlushieArachne - orGroup: Prize - - id: PlushieGnome - orGroup: Prize - - id: PlushieLoveable - orGroup: Prize - - id: PlushieDeer - orGroup: Prize - - id: PlushieIpc - orGroup: Prize - - id: PlushieGrey - orGroup: Prize - - id: PlushieRedFox - orGroup: Prize - - id: PlushiePurpleFox - orGroup: Prize - - id: PlushiePinkFox - orGroup: Prize - - id: PlushieOrangeFox - orGroup: Prize - - id: PlushieMarbleFox - orGroup: Prize - - id: PlushieCrimsonFox - orGroup: Prize - - id: PlushieCoffeeFox - orGroup: Prize - - id: PlushieBlueFox - orGroup: Prize - - id: PlushieBlackFox - orGroup: Prize - - id: PlushieVulp - orGroup: Prize - - id: PlushieCorgi - orGroup: Prize - - id: PlushieGirlyCorgi - orGroup: Prize - - id: PlushieRobotCorgi - orGroup: Prize - - id: PlushieCatBlack - orGroup: Prize - - id: PlushieCatGrey - orGroup: Prize - - id: PlushieCatOrange - orGroup: Prize - - id: PlushieCatSiames - orGroup: Prize - - id: PlushieCatTabby - orGroup: Prize - - id: PlushieCatTuxedo - orGroup: Prize - - id: PlushieCatWhite - orGroup: Prize - - id: PlushieGhost - orGroup: Prize - - id: PlushieRGBee - orGroup: Prize - - id: PlushieRatvar - orGroup: Prize - - id: PlushieNar - orGroup: Prize - - id: PlushieRainbowCarp - orGroup: Prize - - id: PlushieXeno - orGroup: Prize - - id: PlushieJester - orGroup: Prize - - id: PlushieSlips - orGroup: Prize - - id: PlushieAbductor - orGroup: Prize - - id: PlushieTrystan - orGroup: Prize - - id: PlushieAbductorAgent - orGroup: Prize - - id: PlushieNuke - orGroup: Prize - -- type: entity - noSpawn: true - parent: BaseMail - id: MailRestraints - suffix: restraints - components: - - type: Mail - contents: - - id: Handcuffs - - id: ClothingMaskMuzzle - - id: ClothingEyesBlindfold - -- type: entity - noSpawn: true - parent: BaseMail - id: MailSignallerKit - suffix: signallerkit - components: - - type: Mail - contents: - - id: Multitool - - id: RemoteSignaller - -# - type: entity -# noSpawn: true -# parent: BaseMail -# id: MailSixPack -# suffix: sixpack -# components: -# - type: Mail -# contents: -# - id: DrinkCanPack - -- type: entity - noSpawn: true - parent: BaseMail - id: MailSkub - suffix: skub - components: - - type: Mail - contents: - - id: Skub - -- type: entity - noSpawn: true - parent: BaseMail - id: MailSoda - suffix: soda - components: - - type: Mail - contents: - - id: DrinkColaBottleFull - orGroup: Soda - - id: DrinkSpaceMountainWindBottleFull - orGroup: Soda - - id: DrinkSpaceUpBottleFull - orGroup: Soda - -- type: entity - noSpawn: true - parent: BaseMail - id: MailSpaceVillainDIY - suffix: spacevilliandiy - components: - - type: Mail - contents: - - id: SpaceVillainArcadeComputerCircuitboard - -- type: entity - noSpawn: true - parent: BaseMail - id: MailSunglasses - suffix: Sunglasses - components: - - type: Mail - contents: - - id: ClothingEyesGlassesSunglasses - -- type: entity - noSpawn: true - parent: BaseMail - id: MailVagueThreat - suffix: vague-threat - components: - - type: Mail - contents: - - id: PaperMailVagueThreat1 - orGroup: Paper - - id: PaperMailVagueThreat2 - orGroup: Paper - - id: PaperMailVagueThreat3 - orGroup: Paper - - id: PaperMailVagueThreat4 - orGroup: Paper - - id: PaperMailVagueThreat5 - orGroup: Paper - - id: PaperMailVagueThreat6 - orGroup: Paper - - id: PaperMailVagueThreat7 - orGroup: Paper - - id: PaperMailVagueThreat8 - orGroup: Paper - - id: PaperMailVagueThreat9 - orGroup: Paper - - id: PaperMailVagueThreat10 - orGroup: Paper - - id: PaperMailVagueThreat11 - orGroup: Paper - - id: PaperMailVagueThreat12 - orGroup: Paper - - id: KitchenKnife - orGroup: ThreateningObject - - id: ButchCleaver - orGroup: ThreateningObject - - id: CombatKnife - orGroup: ThreateningObject - - id: SurvivalKnife - orGroup: ThreateningObject - - id: SoapHomemade - orGroup: ThreateningObject - - id: FoodMeat - orGroup: ThreateningObject - - id: OrganHumanHeart - orGroup: ThreateningObject - -- type: entity - noSpawn: true - parent: BaseMail - id: MailWinterCoat - suffix: wintercoat - components: - - type: Mail - contents: - - id: ClothingOuterWinterCoat - orGroup: Coat - - id: ClothingOuterWinterCoatLong - orGroup: Coat - - id: ClothingOuterWinterCoatPlaid - orGroup: Coat diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/mail_civilian.yml b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/mail_civilian.yml deleted file mode 100644 index a41fac14ff..0000000000 --- a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/mail_civilian.yml +++ /dev/null @@ -1,195 +0,0 @@ -- type: entity - noSpawn: true - parent: BaseMail - id: MailBotanistChemicalBottles - suffix: botanistchemicals - components: - - type: Mail - contents: - - id: RobustHarvestChemistryBottle - orGroup: Chemical - prob: 0.6 - - id: WeedSpray - orGroup: Chemical - prob: 0.4 - -- type: entity - noSpawn: true - parent: BaseMail - id: MailBotanistMutagen - suffix: mutagen - components: - - type: Mail - isFragile: true - isPriority: true - contents: - - id: UnstableMutagenChemistryBottle - -- type: entity - noSpawn: true - parent: BaseMail - id: MailBotanistSeeds - suffix: seeds - components: - - type: Mail - contents: - - id: AloeSeeds - orGroup: Seeds - - id: AmbrosiaVulgarisSeeds - orGroup: Seeds - - id: AppleSeeds - orGroup: Seeds - - id: BananaSeeds - orGroup: Seeds - - id: CarrotSeeds - orGroup: Seeds - - id: ChanterelleSeeds - orGroup: Seeds - - id: ChiliSeeds - orGroup: Seeds - - id: CornSeeds - orGroup: Seeds - - id: EggplantSeeds - orGroup: Seeds - - id: GalaxythistleSeeds - orGroup: Seeds - - id: LemonSeeds - orGroup: Seeds - - id: LingzhiSeeds - orGroup: Seeds - - id: OatSeeds - orGroup: Seeds - - id: OnionSeeds - orGroup: Seeds - - id: PoppySeeds - orGroup: Seeds - - id: PotatoSeeds - orGroup: Seeds - - id: SugarcaneSeeds - orGroup: Seeds - - id: TomatoSeeds - orGroup: Seeds - - id: TowercapSeeds - orGroup: Seeds - - id: WheatSeeds - orGroup: Seeds - -- type: entity - noSpawn: true - parent: BaseMail - id: MailClownGildedBikeHorn - suffix: honk - components: - - type: Mail - isFragile: true - contents: - - id: BikeHornInstrument - -- type: entity - noSpawn: true - parent: BaseMail - id: MailClownHonkSupplement - suffix: honk - components: - - type: Mail - isFragile: true - contents: - - id: BikeHorn - - id: FoodPieBananaCream - - id: FoodBanana - -- type: entity - noSpawn: true - parent: BaseMail - id: MailHoPBureaucracy - suffix: hoppaper - components: - - type: Mail - contents: - - id: Paper - maxAmount: 3 - -- type: entity - noSpawn: true - parent: BaseMail - id: MailHoPSupplement - suffix: hopsupplement - components: - - type: Mail - contents: - - id: ClearPDA - - id: ClothingHeadsetGrey - - id: Paper - -- type: entity - noSpawn: true - parent: BaseMail - id: MailMimeArtsCrafts - suffix: artscrafts - components: - - type: Mail - contents: - - id: CrayonBox - - id: Paper - maxAmount: 3 - -- type: entity - noSpawn: true - parent: BaseMail - id: MailMimeBlankBook - suffix: blankbook - components: - - type: Mail - contents: - - id: BookRandom - -- type: entity - noSpawn: true - parent: BaseMail - id: MailMimeBottleOfNothing - suffix: bottleofnothing - components: - - type: Mail - contents: - - id: DrinkBottleOfNothingFull - -- type: entity - noSpawn: true - parent: BaseMail - id: MailMusicianInstrumentSmall - suffix: instrument-small - components: - - type: Mail - isFragile: true - contents: - - id: FluteInstrument - orGroup: Instrument - - id: HarmonicaInstrument - orGroup: Instrument - - id: OcarinaInstrument - orGroup: Instrument - - id: PanFluteInstrument - orGroup: Instrument - - id: RecorderInstrument - orGroup: Instrument - -- type: entity - noSpawn: true - parent: BaseMail - id: MailPassengerMoney - suffix: passengermoney - components: - - type: Mail - contents: - - id: SpaceCash100 - orGroup: Cash - prob: 0.1 - maxAmount: 10 - - id: SpaceCash500 - orGroup: Cash - prob: 0.3 - maxAmount: 5 - - id: SpaceCash1000 - orGroup: Cash - prob: 0.6 - maxAmount: 3 diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/mail_command.yml b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/mail_command.yml deleted file mode 100644 index 7e2a935f90..0000000000 --- a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/mail_command.yml +++ /dev/null @@ -1,9 +0,0 @@ -- type: entity - noSpawn: true - parent: BaseMail - id: MailCommandPinpointerNuclear - suffix: pinpointernuclear - components: - - type: Mail - contents: - - id: PinpointerNuclear diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/mail_engineering.yml b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/mail_engineering.yml deleted file mode 100644 index 461d9bf136..0000000000 --- a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/mail_engineering.yml +++ /dev/null @@ -1,45 +0,0 @@ -- type: entity - noSpawn: true - parent: BaseMail - id: MailEngineeringCables - suffix: cables - components: - - type: Mail - contents: - - id: CableHVStack - orGroup: Cables - - id: CableMVStack - orGroup: Cables - - id: CableApcStack - orGroup: Cables - -- type: entity - noSpawn: true - parent: BaseMail - id: MailEngineeringKudzuDeterrent - suffix: antikudzu - components: - - type: Mail - contents: - - id: PlantBGoneSpray - -- type: entity - noSpawn: true - parent: BaseMail - id: MailEngineeringSheetGlass - suffix: sheetglass - components: - - type: Mail - contents: - - id: SheetGlass - -- type: entity - noSpawn: true - parent: BaseMail - id: MailEngineeringWelderReplacement - suffix: welder - components: - - type: Mail - contents: - - id: Welder - diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/mail_epistemology.yml b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/mail_epistemology.yml deleted file mode 100644 index 38526966b8..0000000000 --- a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/mail_epistemology.yml +++ /dev/null @@ -1,58 +0,0 @@ -- type: entity - noSpawn: true - parent: BaseMail - id: MailEpistemologyBluespace - suffix: bluespace - components: - - type: Mail - contents: - - id: MaterialBluespace1 - -- type: entity - noSpawn: true - parent: BaseMail - id: MailEpistemologyIngotGold - suffix: ingotgold - components: - - type: Mail - contents: - - id: IngotGold1 - maxAmount: 3 - -- type: entity - noSpawn: true - parent: BaseMail - id: MailEpistemologyResearchDisk - suffix: researchdisk - components: - - type: Mail - contents: - - id: ResearchDisk - orGroup: Disk - prob: 0.6 - - id: ResearchDisk5000 - orGroup: Disk - prob: 0.3 - - id: ResearchDisk10000 - orGroup: Disk - prob: 0.1 - -- type: entity - noSpawn: true - parent: BaseMail - id: MailEpistemologyTinfoilHat - suffix: tinfoilhat - components: - - type: Mail - contents: - - id: ClothingHeadTinfoil - -- type: entity - noSpawn: true - parent: BaseMail - id: MailDetectiveForensicSupplement # Deltav - Detective is in charge of investigating crimes. - suffix: detectivesupplement # Deltav - Detective is in charge of investigating crimes. - components: - - type: Mail - contents: - - id: BoxForensicPad diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/mail_security.yml b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/mail_security.yml deleted file mode 100644 index b47d5af56e..0000000000 --- a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/mail_security.yml +++ /dev/null @@ -1,56 +0,0 @@ -- type: entity - noSpawn: true - parent: BaseMail - id: MailSecurityDonuts - suffix: donuts - components: - - type: Mail - contents: - - id: FoodBoxDonut - -- type: entity - noSpawn: true - parent: BaseMail - id: MailSecurityFlashlight - suffix: seclite - components: - - type: Mail - contents: - - id: FlashlightSeclite - -- type: entity - noSpawn: true - parent: BaseMail - id: MailSecurityNonlethalsKit - suffix: nonlethalskit - components: - - type: Mail - contents: - - id: Flash - maxAmount: 2 - - id: GrenadeFlashBang - maxAmount: 2 - - id: Handcuffs - maxAmount: 2 - -#- type: entity -# noSpawn: true -# parent: BaseMail -# id: MailSecuritySpaceLaw -# suffix: spacelaw -# components: -# - type: Mail -# contents: -# - id: HyperlinkBookSpaceLaw -# Will we ever readd hyperlinks books? Who knows! - -- type: entity - noSpawn: true - parent: BaseMail - id: MailWardenCrowdControl - suffix: crowdcontrol - components: - - type: Mail - contents: - - id: BoxBeanbag - diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/mail_specific_items.yml b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/mail_specific_items.yml deleted file mode 100644 index b4d2b54779..0000000000 --- a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Mail/mail_specific_items.yml +++ /dev/null @@ -1,169 +0,0 @@ -- type: entity - id: PaperMailCallForHelp1 - noSpawn: true - suffix: "call for help 1" - parent: Paper - components: - - type: Paper - content: | - Help! They're coming! Take this! - -- type: entity - id: PaperMailCallForHelp2 - noSpawn: true - suffix: "call for help 2" - parent: Paper - components: - - type: Paper - content: | - Check disposals! - -- type: entity - id: PaperMailCallForHelp3 - noSpawn: true - suffix: "call for help 3" - parent: Paper - components: - - type: Paper - content: | - GET ME OUT! - -- type: entity - id: PaperMailCallForHelp4 - noSpawn: true - suffix: "call for help 4" - parent: Paper - components: - - type: Paper - content: | - Check maintenance! - -- type: entity - id: PaperMailCallForHelp5 - noSpawn: true - suffix: "call for help 5" - parent: Paper - components: - - type: Paper - content: | - Save me, please! - -- type: entity - id: PaperMailVagueThreat1 - noSpawn: true - suffix: "vague mail threat 1" - parent: Paper - components: - - type: Paper - content: | - I know what you did. You don't know what I'm going to do to you. - -- type: entity - id: PaperMailVagueThreat2 - noSpawn: true - suffix: "vague mail threat 2" - parent: Paper - components: - - type: Paper - content: | - I'm coming for you. - -- type: entity - id: PaperMailVagueThreat3 - noSpawn: true - suffix: "vague mail threat 3" - parent: Paper - components: - - type: Paper - content: | - You're next. - -- type: entity - id: PaperMailVagueThreat4 - noSpawn: true - suffix: "vague mail threat 4" - parent: Paper - components: - - type: Paper - content: | - We see you. - -- type: entity - id: PaperMailVagueThreat5 - noSpawn: true - suffix: "vague mail threat 5" - parent: Paper - components: - - type: Paper - content: | - I hope your affairs are in order. - -- type: entity - id: PaperMailVagueThreat6 - noSpawn: true - suffix: "vague mail threat 6" - parent: Paper - components: - - type: Paper - content: | - It's only a matter of time. Enjoy it while it lasts. - -- type: entity - id: PaperMailVagueThreat7 - noSpawn: true - suffix: "vague mail threat 7" - parent: Paper - components: - - type: Paper - content: | - Who should we mail your pieces to? - -- type: entity - id: PaperMailVagueThreat8 - noSpawn: true - suffix: "vague mail threat 8" - parent: Paper - components: - - type: Paper - content: | - Do you prefer to die slowly or quickly? Just kidding. We don't care what you think. - -- type: entity - id: PaperMailVagueThreat9 - noSpawn: true - suffix: "vague mail threat 9" - parent: Paper - components: - - type: Paper - content: | - I think your head would look nice on my mantel. - -- type: entity - id: PaperMailVagueThreat10 - noSpawn: true - suffix: "vague mail threat 10" - parent: Paper - components: - - type: Paper - content: | - You should have paid up. It's too late now. - -- type: entity - id: PaperMailVagueThreat11 - noSpawn: true - suffix: "vague mail threat 11" - parent: Paper - components: - - type: Paper - content: | - Your family will miss you, but don't worry. We'll take care of them too. - -- type: entity - id: PaperMailVagueThreat12 - noSpawn: true - suffix: "vague mail threat 12" - parent: Paper - components: - - type: Paper - content: | - I have a bet that you're going to die today. I'm not afraid of cheating. diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Research/crystals.yml b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Research/crystals.yml deleted file mode 100644 index b4d3ca1abf..0000000000 --- a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Specific/Research/crystals.yml +++ /dev/null @@ -1,36 +0,0 @@ -- type: entity - parent: BaseItem - id: CrystalNormality - name: normality crystal - description: It looks... normal. Placeholder sprite. - components: - - type: Sprite - sprite: Nyanotrasen/Objects/Materials/materials.rsi - state: bluespace - color: gray - - type: Tag - tags: - - NormalityCrystal - -# - type: entity -# parent: BaseItem -# id: CrystalSoul -# name: soul crystal -# description: Contains a soul. Placeholder sprite. -# components: -# - type: Sprite -# sprite: Nyanotrasen/Objects/Materials/materials.rsi -# state: bluespace -# color: purple -# - type: Speech -# - type: Psionic -# - type: SoulCrystal -# - type: SolutionContainerManager -# solutions: -# ectoplasm: -# maxvol: 50 -# reagents: -# - ReagentId: Ectoplasm -# Quantity: 50 -# - type: Extractable -# grindableSolutionName: ectoplasm diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Weapons/Guns/Projectiles/shotgun.yml b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Weapons/Guns/Projectiles/shotgun.yml index 293eba3aca..42826c94cb 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Weapons/Guns/Projectiles/shotgun.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Weapons/Guns/Projectiles/shotgun.yml @@ -24,6 +24,3 @@ blockSlots: NONE #tranquillizer darts shouldn't be blocked by a mask - type: InjectableSolution solution: ammo - - type: GuideHelp - guides: - - Psionics diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Stations/mail.yml b/Resources/Prototypes/Nyanotrasen/Entities/Stations/mail.yml index ceb87bbaa1..b85cfef87a 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Stations/mail.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Stations/mail.yml @@ -3,3 +3,4 @@ abstract: true components: - type: StationMailRouter + - type: StationLogisticStats # DeltaV - Tracks statistics related to mail and income diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Doors/Airlocks/access.yml b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Doors/Airlocks/access.yml index 19226d067f..d9e4645446 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Doors/Airlocks/access.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Doors/Airlocks/access.yml @@ -3,13 +3,15 @@ id: AirlockMailLocked suffix: Mail, Locked components: - - type: AccessReader - access: [["Mail"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsMail ] - type: entity parent: AirlockCargoGlass id: AirlockMailGlassLocked suffix: Mail, Locked components: - - type: AccessReader - access: [["Mail"]] + - type: ContainerFill + containers: + board: [ DoorElectronicsMail ] diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Machines/deep_fryer.yml b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Machines/deep_fryer.yml index fa3e0537db..9749a4295d 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Machines/deep_fryer.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Machines/deep_fryer.yml @@ -124,15 +124,14 @@ - type: RefillableSolution solution: vat_oil - type: Drink - isOpen: true solution: vat_oil - type: Appearance - type: ActivatableUI key: enum.DeepFryerUiKey.Key - type: UserInterface interfaces: - - key: enum.DeepFryerUiKey.Key - type: DeepFryerBoundUserInterface + enum.DeepFryerUiKey.Key: + type: DeepFryerBoundUserInterface - type: Destructible thresholds: - trigger: diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Machines/laundry.yml b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Machines/laundry.yml index ce982d41c2..47518c78f6 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Machines/laundry.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Machines/laundry.yml @@ -84,8 +84,8 @@ ents: [] - type: UserInterface interfaces: - - key: enum.StorageUiKey.Key - type: StorageBoundUserInterface + enum.StorageUiKey.Key: + type: StorageBoundUserInterface - type: UseDelay delay: 0.5 - type: Repairable diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Machines/metempsychoticMachine.yml b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Machines/metempsychoticMachine.yml deleted file mode 100644 index d8e791af1e..0000000000 --- a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Machines/metempsychoticMachine.yml +++ /dev/null @@ -1,25 +0,0 @@ -- type: entity - parent: CloningPod - id: MetempsychoticMachine - name: metempsychotic machine - description: Speeds along the transmigration of a soul to its next vessel. - components: - - type: MetempsychoticMachine - - type: CloningPod - - type: Machine - board: MetempsychoticMachineCircuitboard - - type: Sprite - sprite: Nyanotrasen/Structures/Machines/metempsychotic.rsi - snapCardinals: true - layers: - - state: pod_0 - - type: Appearance - - type: GenericVisualizer - visuals: - enum.CloningPodVisuals.Status: - base: - Cloning: { state: pod_1 } - NoMind: { state: pod_1 } - Gore: { state: pod_1 } - Idle: { state: pod_0 } - - type: Psionic diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Machines/reverseEngineering.yml b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Machines/reverseEngineering.yml index c28c395261..3c3a9908c6 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Machines/reverseEngineering.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Machines/reverseEngineering.yml @@ -19,8 +19,8 @@ key: enum.ReverseEngineeringMachineUiKey.Key - type: UserInterface interfaces: - - key: enum.ReverseEngineeringMachineUiKey.Key - type: ReverseEngineeringMachineBoundUserInterface + enum.ReverseEngineeringMachineUiKey.Key: + type: ReverseEngineeringMachineBoundUserInterface - type: ActivatableUIRequiresPower - type: ItemSlots slots: diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Research/glimmer_prober.yml b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Research/glimmer_prober.yml index 102000f8b2..0eede9c281 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Research/glimmer_prober.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Research/glimmer_prober.yml @@ -5,6 +5,10 @@ description: Probes the noösphere to generate research points. Might be worth turning off if glimmer is a problem. components: - type: Psionic + removable: false + - type: InnatePsionicPowers + powersToAdd: + - TelepathyPower - type: GlimmerSource - type: Construction graph: GlimmerDevices @@ -61,9 +65,6 @@ damageContainer: Inorganic damageModifierSet: ShockAbsorber - type: CargoSellBlacklist - - type: GuideHelp - guides: - - Psionics - type: AmbientSound range: 6 volume: -6 @@ -91,6 +92,7 @@ description: Uses electricity to try and sort out the noösphere, reducing its level of entropy. components: - type: Psionic + removable: false - type: GlimmerSource addToGlimmer: false - type: Construction @@ -114,9 +116,6 @@ False: {visible: false} - type: PowerSwitch - type: CargoSellBlacklist - - type: GuideHelp - guides: - - Psionics - type: NpcFactionMember factions: - PsionicInterloper # :^) diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Research/sophicscribe.yml b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Research/sophicscribe.yml index ae85cd25e0..f92c9b0740 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Research/sophicscribe.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Research/sophicscribe.yml @@ -1,7 +1,7 @@ - type: entity parent: BaseStructure id: SophicScribe - name: sophie + name: Sophia description: Latest reports on the Noösphere! components: - type: Sprite @@ -16,24 +16,33 @@ - type: Speech speechSounds: Tenor - type: IntrinsicRadioReceiver - channels: - - Common - - Science - type: IntrinsicRadioTransmitter - channels: - - Common - - Science - type: ActiveRadio channels: - Common - Science - - type: PotentialPsionic #this makes her easier to access for glimmer events, dw about it + - type: Prayable + - type: Actions - type: Psionic + removable: false + psychognomicDescriptors: + - p-descriptor-old + - p-descriptor-demiurgic + - p-descriptor-mysterious + - type: InnatePsionicPowers + powersToAdd: + - XenoglossyPower + - TelepathyPower + - NoosphericZapPower - type: Grammar attributes: gender: female proper: true - type: SpriteFade - - type: GuideHelp - guides: - - Psionics + - type: LanguageSpeaker + currentLanguage: TauCetiBasic + - type: LanguageKnowledge + speaks: + - TauCetiBasic + understands: + - TauCetiBasic diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Walls/walls.yml b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Walls/walls.yml index 1254829991..971e292f90 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Walls/walls.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Walls/walls.yml @@ -49,8 +49,6 @@ node: girder - !type:DoActsBehavior acts: ["Destruction"] - destroySound: - collection: MetalBreak - type: IconSmooth key: walls base: paperwall diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Webbing/webs.yml b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Webbing/webs.yml index e483ea5da7..c3e020090f 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Webbing/webs.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Webbing/webs.yml @@ -60,7 +60,7 @@ butcheringType: Knife butcherDelay: 12 spawned: - - id: MaterialCloth1 + - id: MaterialWebSilk1 amount: 1 prob: 0.5 #This doesn't cost hunger so should at least make it not worth it time-wise - type: Appearance diff --git a/Resources/Prototypes/Nyanotrasen/GameRules/events.yml b/Resources/Prototypes/Nyanotrasen/GameRules/events.yml index 43347ca7d0..de9ea15a69 100644 --- a/Resources/Prototypes/Nyanotrasen/GameRules/events.yml +++ b/Resources/Prototypes/Nyanotrasen/GameRules/events.yml @@ -24,13 +24,13 @@ earliestStart: 25 - type: MidRoundAntagRule -#- type: entity -# noSpawn: true -# parent: BaseMidRoundAntag -# id: RatKingSpawn -# components: -# - type: MidRoundAntagRule -# spawner: SpawnPointGhostRatKing +- type: entity + noSpawn: true + parent: BaseMidRoundAntag + id: RatKingSpawn + components: + - type: MidRoundAntagRule + spawner: SpawnPointGhostRatKing - type: entity noSpawn: true @@ -94,7 +94,7 @@ noSpawn: true components: - type: GlimmerEvent - minimumGlimmer: 590 + minimumGlimmer: 400 maximumGlimmer: 1000 glimmerBurnLower: 18 glimmerBurnUpper: 40 @@ -112,65 +112,69 @@ - type: MassMindSwapRule isTemporary: true # Permanent mindswap is hell. -#- type: entity -# id: GlimmerWispSpawn -# parent: BaseGlimmerEvent -# noSpawn: true -# components: -# - type: GlimmerEvent -# minimumGlimmer: 300 -# maximumGlimmer: 1000 -# report: glimmer-event-report-signatures -# - type: GlimmerWispRule +- type: entity + abstract: true + parent: BaseGlimmerEvent + id: BaseGlimmerSignaturesEvent + noSpawn: true + components: + - type: GlimmerEvent + minimumGlimmer: 300 + maximumGlimmer: 1000 + report: glimmer-event-report-signatures - type: entity + id: GlimmerWispSpawn + parent: BaseGlimmerSignaturesEvent + noSpawn: true + components: + - type: GlimmerMobRule + mobPrototype: MobGlimmerWisp + +- type: entity + parent: BaseGlimmerSignaturesEvent id: FreeProber - parent: BaseGlimmerEvent noSpawn: true components: - - type: GlimmerEvent - minimumGlimmer: 550 # Requires at least some noospheric activity - maximumGlimmer: 1000 - report: glimmer-event-report-signatures - - type: FreeProberRule + - type: FreeProberRule ## converted upstream events - type: entity + parent: BaseGlimmerSignaturesEvent id: GlimmerRandomSentience - parent: BaseGlimmerEvent noSpawn: true components: - - type: StationEvent - weight: 7 - duration: 1 - earliestStart: 15 - reoccurrenceDelay: 15 - minimumPlayers: 10 - - type: GlimmerEvent - minimumGlimmer: 500 - maximumGlimmer: 900 - report: glimmer-event-report-signatures - - type: GlimmerRandomSentienceRule + - type: StationEvent + weight: 7 + duration: 1 + earliestStart: 15 + reoccurrenceDelay: 15 + minimumPlayers: 10 + - type: GlimmerEvent + minimumGlimmer: 350 + maximumGlimmer: 1000 + - type: GlimmerRandomSentienceRule - type: entity + parent: BaseGlimmerSignaturesEvent id: GlimmerRevenantSpawn - parent: BaseGlimmerEvent noSpawn: true components: - type: GlimmerEvent - minimumGlimmer: 700 + minimumGlimmer: 450 maximumGlimmer: 900 - report: glimmer-event-report-signatures + glimmerBurnLower: 50 + glimmerBurnUpper: 100 # Gives epi a chance to recover - type: GlimmerRevenantRule - type: entity + parent: BaseGlimmerSignaturesEvent id: GlimmerMiteSpawn - parent: BaseGlimmerEvent noSpawn: true components: - - type: GlimmerEvent - minimumGlimmer: 50 - maximumGlimmer: 900 - report: glimmer-event-report-signatures - - type: GlimmerMobRule - mobPrototype: MobGlimmerMite + - type: GlimmerEvent + minimumGlimmer: 250 + maximumGlimmer: 900 + - type: GlimmerMobRule + mobPrototype: MobGlimmerMite + glimmerTier: Low # get more mites earlier on diff --git a/Resources/Prototypes/Nyanotrasen/Guidebook/epistemics.yml b/Resources/Prototypes/Nyanotrasen/Guidebook/epistemics.yml index 77b180d6ea..2ae051100b 100644 --- a/Resources/Prototypes/Nyanotrasen/Guidebook/epistemics.yml +++ b/Resources/Prototypes/Nyanotrasen/Guidebook/epistemics.yml @@ -1,12 +1,7 @@ - type: guideEntry - id: Psionics - name: guide-entry-psionics - text: "/ServerInfo/Nyanotrasen/Guidebook/Epistemics/Psionics.xml" - -# - type: guideEntry # When it's added -# id: AltarsGolemancy -# name: guide-entry-altars-golemancy -# text: "/ServerInfo/Nyanotrasen/Guidebook/Epistemics/Altar.xml" + id: AltarsGolemancy + name: guide-entry-altars-golemancy + text: "/ServerInfo/Nyanotrasen/Guidebook/Epistemics/Altar.xml" - type: guideEntry id: ReverseEngineering diff --git a/Resources/Prototypes/Nyanotrasen/Hydroponics/seeds.yml b/Resources/Prototypes/Nyanotrasen/Hydroponics/seeds.yml index 688af9fc50..08f4482787 100644 --- a/Resources/Prototypes/Nyanotrasen/Hydroponics/seeds.yml +++ b/Resources/Prototypes/Nyanotrasen/Hydroponics/seeds.yml @@ -17,14 +17,13 @@ nutrientConsumption: 0.25 idealLight: 8 idealHeat: 298 - juicy: true splatPrototype: PuddleSplatter chemicals: Nutriment: Min: 1 Max: 10 - PotencyDivisor: 10 + potencyDivisor: 10 DemonsBlood: Min: 1 Max: 4 - PotencyDivisor: 25 + potencyDivisor: 25 diff --git a/Resources/Prototypes/Nyanotrasen/Objectives/traitor.yml b/Resources/Prototypes/Nyanotrasen/Objectives/traitor.yml index 53910a54a9..e6e497003d 100644 --- a/Resources/Prototypes/Nyanotrasen/Objectives/traitor.yml +++ b/Resources/Prototypes/Nyanotrasen/Objectives/traitor.yml @@ -9,29 +9,6 @@ stealGroup: AntiPsychicKnife owner: job-name-mantis -- type: entity - id: BecomePsionicObjective - parent: BaseTraitorObjective - name: Become psionic - description: We need you to acquire psionics and keep them until your mission is complete. - noSpawn: true - components: - - type: NotJobsRequirement - jobs: - - Mime - - ForensicMantis - - type: Objective - difficulty: 2.5 - #unique: false - icon: - sprite: Nyanotrasen/Icons/psi.rsi - state: psi - - type: ObjectiveBlacklistRequirement - blacklist: - components: - - BecomeGolemCondition - - type: BecomePsionicCondition - #- type: entity # id: BecomeGolemObjective # parent: BaseTraitorObjective diff --git a/Resources/Prototypes/Nyanotrasen/Reagents/Consumable/Drink/alcohol.yml b/Resources/Prototypes/Nyanotrasen/Reagents/Consumable/Drink/alcohol.yml index 972fc08239..19d49b913a 100644 --- a/Resources/Prototypes/Nyanotrasen/Reagents/Consumable/Drink/alcohol.yml +++ b/Resources/Prototypes/Nyanotrasen/Reagents/Consumable/Drink/alcohol.yml @@ -19,7 +19,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.07 + amount: 0.15 - type: reagent id: Soju @@ -42,7 +42,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.15 + amount: 0.2 - type: reagent id: OrangeCreamice @@ -65,7 +65,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.05 + amount: 0.1 - type: reagent id: Silverjack @@ -88,7 +88,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.0625 + amount: 0.1375 - type: reagent id: Brainbomb @@ -118,7 +118,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.085 + amount: 0.13 - !type:AdjustReagent reagent: THC amount: 0.33 @@ -147,7 +147,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.025 + amount: 0.08 - type: reagent id: CircusJuice @@ -170,7 +170,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.0625 + amount: 0.12 - !type:Emote #It's very funny emote: Laugh probability: 0.15 @@ -196,7 +196,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.065 + amount: 0.1125 - !type:AdjustTemperature amount: 75 # thermal energy, not temperature! @@ -221,4 +221,4 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.03 + amount: 0.12 diff --git a/Resources/Prototypes/Nyanotrasen/Reagents/Materials/materials.yml b/Resources/Prototypes/Nyanotrasen/Reagents/Materials/materials.yml deleted file mode 100644 index 9146f29713..0000000000 --- a/Resources/Prototypes/Nyanotrasen/Reagents/Materials/materials.yml +++ /dev/null @@ -1,8 +0,0 @@ -- type: material - id: Bluespace - name: bluespace - unit: materials-unit-crystal - icon: /Textures/Nyanotrasen/Objects/Materials/materials.rsi/bluespace.png - color: "#53a9ff" - stackEntity: MaterialBluespace - price: 15 diff --git a/Resources/Prototypes/Nyanotrasen/Recipes/Construction/Graphs/structures/glimmerdevices.yml b/Resources/Prototypes/Nyanotrasen/Recipes/Construction/Graphs/structures/glimmerdevices.yml index 1926992b69..a79f93ec0e 100644 --- a/Resources/Prototypes/Nyanotrasen/Recipes/Construction/Graphs/structures/glimmerdevices.yml +++ b/Resources/Prototypes/Nyanotrasen/Recipes/Construction/Graphs/structures/glimmerdevices.yml @@ -30,36 +30,9 @@ conditions: - !type:EntityAnchored {} steps: - - tag: NormalityCrystal - icon: - sprite: Nyanotrasen/Objects/Materials/materials.rsi - state: bluespace - name: a normality crystal - doAfter: 1 - - tag: NormalityCrystal - icon: - sprite: Nyanotrasen/Objects/Materials/materials.rsi - state: bluespace - name: a normality crystal - doAfter: 1 - - tag: NormalityCrystal - icon: - sprite: Nyanotrasen/Objects/Materials/materials.rsi - state: bluespace - name: a normality crystal - doAfter: 1 - - tag: NormalityCrystal - icon: - sprite: Nyanotrasen/Objects/Materials/materials.rsi - state: bluespace - name: a normality crystal - doAfter: 1 - - tag: NormalityCrystal - icon: - sprite: Nyanotrasen/Objects/Materials/materials.rsi - state: bluespace - name: a normality crystal - doAfter: 1 + - material: Normality + amount: 5 + doAfter: 10 - tool: Welding doAfter: 5 diff --git a/Resources/Prototypes/Nyanotrasen/Recipes/Cooking/meal_recipes.yml b/Resources/Prototypes/Nyanotrasen/Recipes/Cooking/meal_recipes.yml index 51777e2f59..b61e99ba92 100644 --- a/Resources/Prototypes/Nyanotrasen/Recipes/Cooking/meal_recipes.yml +++ b/Resources/Prototypes/Nyanotrasen/Recipes/Cooking/meal_recipes.yml @@ -26,7 +26,7 @@ solids: FoodSnackBoritos: 1 FoodCheeseSlice: 1 - FoodChili: 1 + FoodChiliPepper: 1 FoodMeatMeatball: 1 # Base ingredients: Should be moved out of microwave as soon as possible. @@ -62,7 +62,7 @@ solids: FoodTofuSlice: 2 FoodOnionSlice: 1 - FoodChili: 1 + FoodChiliPepper: 1 FoodCarrot: 1 FoodCheeseCurds: 1 @@ -97,7 +97,7 @@ solids: FoodCheeseSlice: 1 FoodCabbage: 1 - FoodChili: 1 + FoodChiliPepper: 1 FoodPotato: 1 FoodOnionSlice: 2 @@ -111,7 +111,7 @@ solids: FoodCheeseSlice: 1 #Grilled cheese slice FoodMothSaladBase: 1 - FoodChili: 1 + FoodChiliPepper: 1 FoodCabbage: 1 - type: microwaveMealRecipe @@ -142,7 +142,7 @@ FoodRiceBoiled: 1 FoodPotato: 2 FoodCabbage: 1 - FoodChili: 1 + FoodChiliPepper: 1 #Herbs: 1 - type: microwaveMealRecipe @@ -307,7 +307,7 @@ solids: FoodBowlBig: 1 FoodTofuSlice: 1 - FoodChili: 1 + FoodChiliPepper: 1 #FoodYogurt: 1 #Milk and Cream placeholder - type: microwaveMealRecipe @@ -368,7 +368,7 @@ solids: FoodBowlBig: 1 FoodTomato: 1 - FoodChili: 1 + FoodChiliPepper: 1 # Salads: These should be moved out of the microwave as soon as possible @@ -421,7 +421,7 @@ solids: FoodBowlBig: 1 FoodMothSaladBase: 1 - FoodChili: 1 + FoodChiliPepper: 1 FoodOnionRed: 1 FoodAmbrosiaVulgaris: 1 #Herbs @@ -438,7 +438,7 @@ FoodDoughFlat: 1 FoodCheeseSlice: 1 FoodMothBakedCorn: 1 - FoodChili: 1 + FoodChiliPepper: 1 - type: microwaveMealRecipe id: RecipeMothFiveCheesePizza diff --git a/Resources/Prototypes/Nyanotrasen/Recipes/Lathes/bluespace.yml b/Resources/Prototypes/Nyanotrasen/Recipes/Lathes/bluespace.yml deleted file mode 100644 index 443f2a03f9..0000000000 --- a/Resources/Prototypes/Nyanotrasen/Recipes/Lathes/bluespace.yml +++ /dev/null @@ -1,22 +0,0 @@ -- type: latheRecipe - id: CoreSilver - icon: - sprite: Objects/Misc/guardian_info.rsi - state: icon - result: CoreSilver - completetime: 10 - materials: - Bluespace: 500 - Silver: 1000 - Plastic: 1000 - -- type: latheRecipe - id: BluespaceCrystal - icon: - sprite: Nyanotrasen/Objects/Materials/materials.rsi - state: bluespace - result: MaterialBluespace1 - applyMaterialDiscount: false - completetime: 4 - materials: - Bluespace: 100 diff --git a/Resources/Prototypes/Nyanotrasen/Recipes/Lathes/electronics.yml b/Resources/Prototypes/Nyanotrasen/Recipes/Lathes/electronics.yml index 418864cd40..695fb42150 100644 --- a/Resources/Prototypes/Nyanotrasen/Recipes/Lathes/electronics.yml +++ b/Resources/Prototypes/Nyanotrasen/Recipes/Lathes/electronics.yml @@ -1,19 +1,10 @@ -- type: latheRecipe - id: MetempsychoticMachineCircuitboard - result: MetempsychoticMachineCircuitboard - completetime: 4 - materials: - Steel: 100 - Glass: 900 - Gold: 100 - - type: latheRecipe id: ReverseEngineeringMachineCircuitboard result: ReverseEngineeringMachineCircuitboard completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 700 Gold: 100 - type: latheRecipe @@ -22,7 +13,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 700 Gold: 100 - type: latheRecipe @@ -30,8 +21,8 @@ result: CrewMonitoringComputerCircuitboard completetime: 4 materials: - Steel: 100 - Glass: 900 + Steel: 100 + Glass: 700 - type: latheRecipe id: ClothingEyesHudMedical @@ -50,4 +41,4 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 700 diff --git a/Resources/Prototypes/Nyanotrasen/Recipes/Lathes/hardsuits.yml b/Resources/Prototypes/Nyanotrasen/Recipes/Lathes/hardsuits.yml deleted file mode 100644 index 560c03db49..0000000000 --- a/Resources/Prototypes/Nyanotrasen/Recipes/Lathes/hardsuits.yml +++ /dev/null @@ -1,21 +0,0 @@ -- type: latheRecipe - id: ClothingOuterHardsuitSyndieReverseEngineered - result: ClothingOuterHardsuitSyndieReverseEngineered - completetime: 25 - materials: - Steel: 6000 - Glass: 1500 - Uranium: 100 - Plastic: 100 - Gold: 200 - -- type: latheRecipe - id: ClothingOuterHardsuitJuggernautReverseEngineered - result: ClothingOuterHardsuitJuggernautReverseEngineered - completetime: 25 - materials: - Steel: 12000 - Glass: 3000 - Uranium: 500 - Plastic: 500 - Gold: 250 diff --git a/Resources/Prototypes/Nyanotrasen/Recipes/Reactions/psionic.yml b/Resources/Prototypes/Nyanotrasen/Recipes/Reactions/psionic.yml index 304c9b686a..d9d2ed51b5 100644 --- a/Resources/Prototypes/Nyanotrasen/Recipes/Reactions/psionic.yml +++ b/Resources/Prototypes/Nyanotrasen/Recipes/Reactions/psionic.yml @@ -30,6 +30,6 @@ catalyst: true effects: - !type:CreateEntityReactionEffect - entity: CrystalNormality + entity: MaterialNormality1 - !type:ChangeGlimmerReactionEffect count: -10 diff --git a/Resources/Prototypes/Nyanotrasen/Research/experimental.yml b/Resources/Prototypes/Nyanotrasen/Research/experimental.yml index 7c89c0f7d0..289efd317c 100644 --- a/Resources/Prototypes/Nyanotrasen/Research/experimental.yml +++ b/Resources/Prototypes/Nyanotrasen/Research/experimental.yml @@ -14,21 +14,6 @@ - ClothingHeadCage # - ShellSoulbreaker # DeltaV - Placing it under Exotic Ammunition because that's what it is. -- type: technology - id: Metempsychosis - name: research-technology-metempsychosis - icon: - sprite: Nyanotrasen/Structures/Machines/metempsychotic.rsi - state: pod_0 - discipline: Experimental - tier: 2 - cost: 15000 - recipeUnlocks: - - BiomassReclaimerMachineCircuitboard - - CloningConsoleComputerCircuitboard - - MedicalScannerMachineCircuitboard - - MetempsychoticMachineCircuitboard - # Tier 3 diff --git a/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Epistemics/forensicmantis.yml b/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Epistemics/forensicmantis.yml index b783fdb3e5..8debdbe6ec 100644 --- a/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Epistemics/forensicmantis.yml +++ b/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Epistemics/forensicmantis.yml @@ -10,6 +10,21 @@ - !type:CharacterDepartmentTimeRequirement department: Epistemics # DeltaV - Epistemics Department replacing Science min: 3600 + - !type:CharacterLogicOrRequirement + requirements: + - !type:CharacterSpeciesRequirement + inverted: true + species: + - IPC + - !type:CharacterTraitRequirement + traits: + - AnomalousPositronics + - !type:CharacterLogicOrRequirement + requirements: + - !type:CharacterSpeciesRequirement + inverted: true + species: + - Shadowkin startingGear: ForensicMantisGear icon: "JobIconForensicMantis" supervisors: job-supervisors-rd @@ -24,7 +39,13 @@ - !type:AddComponentSpecial components: - type: Psionic - - type: MetapsionicPower + powerSlots: 2 + - !type:AddComponentSpecial + components: + - type: InnatePsionicPowers + powersToAdd: + - MetapsionicPower + - TelepathyPower - type: startingGear id: ForensicMantisGear diff --git a/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Wildcards/prisoner.yml b/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Wildcards/prisoner.yml index 7c66dd1a41..3920837b90 100644 --- a/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Wildcards/prisoner.yml +++ b/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Wildcards/prisoner.yml @@ -13,6 +13,15 @@ - !type:CharacterDepartmentTimeRequirement department: Security min: 21600 + - !type:CharacterLogicOrRequirement + requirements: + - !type:CharacterSpeciesRequirement + inverted: true + species: + - Shadowkin + - !type:CharacterTraitRequirement + traits: + - ShadowkinBlackeye - type: startingGear id: PrisonerGear diff --git a/Resources/Prototypes/Nyanotrasen/Species/Oni.yml b/Resources/Prototypes/Nyanotrasen/Species/Oni.yml index c3a4629311..36e7096638 100644 --- a/Resources/Prototypes/Nyanotrasen/Species/Oni.yml +++ b/Resources/Prototypes/Nyanotrasen/Species/Oni.yml @@ -41,9 +41,27 @@ Chest: points: 1 required: false - Legs: - points: 6 + RightLeg: + points: 2 + required: false + RightFoot: + points: 2 + required: false + LeftLeg: + points: 2 + required: false + LeftFoot: + points: 2 required: false - Arms: - points: 6 + RightArm: + points: 2 + required: false + RightHand: + points: 2 + required: false + LeftArm: + points: 2 + required: false + LeftHand: + points: 2 required: false diff --git a/Resources/Prototypes/Nyanotrasen/Species/felinid.yml b/Resources/Prototypes/Nyanotrasen/Species/felinid.yml index 5eb26edd51..a0f8d2ad65 100644 --- a/Resources/Prototypes/Nyanotrasen/Species/felinid.yml +++ b/Resources/Prototypes/Nyanotrasen/Species/felinid.yml @@ -34,9 +34,27 @@ Chest: points: 1 required: false - Legs: - points: 6 + RightLeg: + points: 2 required: false - Arms: - points: 6 + RightFoot: + points: 2 + required: false + LeftLeg: + points: 2 + required: false + LeftFoot: + points: 2 + required: false + RightArm: + points: 2 + required: false + RightHand: + points: 2 + required: false + LeftArm: + points: 2 + required: false + LeftHand: + points: 2 required: false diff --git a/Resources/Prototypes/Nyanotrasen/Stacks/materials.yml b/Resources/Prototypes/Nyanotrasen/Stacks/materials.yml deleted file mode 100644 index e8c1b98b93..0000000000 --- a/Resources/Prototypes/Nyanotrasen/Stacks/materials.yml +++ /dev/null @@ -1,7 +0,0 @@ -- type: stack - id: Bluespace - name: bluespace - icon: { sprite: Nyanotrasen/Objects/Materials/materials.rsi, state: bluespace } - spawn: MaterialBluespace - maxCount: 5 - itemSize: 1 diff --git a/Resources/Prototypes/Nyanotrasen/Voice/speech_emotes.yml b/Resources/Prototypes/Nyanotrasen/Voice/speech_emotes.yml index fa471f3d70..f9bd3f60b4 100644 --- a/Resources/Prototypes/Nyanotrasen/Voice/speech_emotes.yml +++ b/Resources/Prototypes/Nyanotrasen/Voice/speech_emotes.yml @@ -1,6 +1,7 @@ # vocal emotes - type: emote id: Hiss + name: chat-emote-name-cathisses category: Vocal chatMessages: [ hisses ] chatTriggers: @@ -8,6 +9,7 @@ - type: emote id: Meow + name: chat-emote-name-catmeow category: Vocal chatMessages: [ meows ] chatTriggers: @@ -18,6 +20,7 @@ - type: emote id: Mew + name: chat-emote-name-catmew category: Vocal chatMessages: [ mews ] chatTriggers: @@ -25,6 +28,7 @@ - type: emote id: Growl + name: chat-emote-name-catgrowl category: Vocal chatMessages: [ growls ] chatTriggers: @@ -32,6 +36,7 @@ - type: emote id: Purr + name: chat-emote-name-catpurr category: Vocal chatMessages: [ purrs ] chatTriggers: diff --git a/Resources/Prototypes/Nyanotrasen/metempsychoticNonHumanoids.yml b/Resources/Prototypes/Nyanotrasen/metempsychoticNonHumanoids.yml index dcbe23f608..0011464441 100644 --- a/Resources/Prototypes/Nyanotrasen/metempsychoticNonHumanoids.yml +++ b/Resources/Prototypes/Nyanotrasen/metempsychoticNonHumanoids.yml @@ -3,7 +3,8 @@ weights: MobMonkey: 1 MobGorilla: 1 - # MobKangaroo: 0.5 # Mobs here need to be either VERY funny or up to standard. + MobKangaroo: 0.5 MobXenoQueen: 0.01 MobCrab: 0.01 - MobPenguin: 1 #ODJ's orders + MobPenguin: 1 + MobSpaceShrimp: 1 diff --git a/Resources/Prototypes/Nyanotrasen/psionicArtifacts.yml b/Resources/Prototypes/Nyanotrasen/psionicArtifacts.yml deleted file mode 100644 index 8f952b3905..0000000000 --- a/Resources/Prototypes/Nyanotrasen/psionicArtifacts.yml +++ /dev/null @@ -1,21 +0,0 @@ -- type: weightedRandom - id: PsionicArtifactPool - weights: - ClothingHandsDispelGloves: 1 - ClothingEyesTelegnosisSpectacles: 1 - ClothingHandsGlovesColorYellowUnsulated: 1 - PonderingOrbTelepathic: 1 - ClothingShoesBootsMagBlinding: 0.5 - LidOSaws: 0.25 - BedsheetInvisibilityCloak: 0.15 - # WeaponWandPolymorphCarp: 0.05 - # WeaponWandPolymorphMonkey: 0.05 - # WeaponWandFireball: 0.025 - # WeaponWandDeath: 0.001 - # WeaponWandPolymorphDoor: 0.05 - SpawnSpellbook: 0.025 - ForceWallSpellbook: 0.025 - BlinkBook: 0.025 - SmiteBook: 0.00025 - KnockSpellbook: 0.025 - FireballSpellbook: 0.01 diff --git a/Resources/Prototypes/Nyanotrasen/psionicPowers.yml b/Resources/Prototypes/Nyanotrasen/psionicPowers.yml index f40b688fd1..8b2911018f 100644 --- a/Resources/Prototypes/Nyanotrasen/psionicPowers.yml +++ b/Resources/Prototypes/Nyanotrasen/psionicPowers.yml @@ -3,8 +3,17 @@ weights: MetapsionicPower: 1 DispelPower: 1 - TelegnosisPower: 1 + #TelegnosisPower: 1 PsionicRegenerationPower: 1 + XenoglossyPower: 0.75 + PsychognomyPower: 0.75 MassSleepPower: 0.3 # PsionicInvisibilityPower: 0.15 MindSwapPower: 0.15 + TelepathyPower: 1 + HealingWordPower: 0.85 + RevivifyPower: 0.1 + ShadeskipPower: 0.15 + TelekineticPulsePower: 0.15 + PyrokineticFlare: 0.3 + SummonImpPower: 0.15 diff --git a/Resources/Prototypes/Objectives/base_objectives.yml b/Resources/Prototypes/Objectives/base_objectives.yml index e24b26e6e8..2ab5149213 100644 --- a/Resources/Prototypes/Objectives/base_objectives.yml +++ b/Resources/Prototypes/Objectives/base_objectives.yml @@ -103,3 +103,11 @@ id: BaseSurviveObjective components: - type: SurviveCondition + +# objective progress is controlled by a system and not the objective itself +- type: entity + abstract: true + parent: BaseObjective + id: BaseCodeObjective + components: + - type: CodeCondition diff --git a/Resources/Prototypes/Objectives/ninja.yml b/Resources/Prototypes/Objectives/ninja.yml index 0495be2935..fb94f2b378 100644 --- a/Resources/Prototypes/Objectives/ninja.yml +++ b/Resources/Prototypes/Objectives/ninja.yml @@ -46,7 +46,7 @@ - type: entity noSpawn: true - parent: BaseNinjaObjective + parent: [BaseNinjaObjective, BaseCodeObjective] id: SpiderChargeObjective description: This bomb can be detonated in a specific location. Note that the bomb will not work anywhere else! components: @@ -54,7 +54,6 @@ icon: sprite: Objects/Weapons/Bombs/spidercharge.rsi state: icon - - type: SpiderChargeCondition - type: entity noSpawn: true @@ -70,7 +69,7 @@ - type: entity noSpawn: true - parent: BaseNinjaObjective + parent: [BaseNinjaObjective, BaseCodeObjective] id: TerrorObjective name: Call in a threat description: Use your gloves on a communication console in order to bring another threat to the station. @@ -79,4 +78,15 @@ icon: sprite: Objects/Fun/Instruments/otherinstruments.rsi state: red_phone - - type: TerrorCondition + +- type: entity + noSpawn: true + parent: [BaseNinjaObjective, BaseCodeObjective] + id: MassArrestObjective + name: Set everyone to wanted + description: Use your gloves to hack a criminal records console, setting the entire station to be wanted! + components: + - type: Objective + icon: + sprite: Objects/Weapons/Melee/stunbaton.rsi + state: stunbaton_on diff --git a/Resources/Prototypes/Objectives/objectiveGroups.yml b/Resources/Prototypes/Objectives/objectiveGroups.yml index b3acba3a7c..be716b1bfc 100644 --- a/Resources/Prototypes/Objectives/objectiveGroups.yml +++ b/Resources/Prototypes/Objectives/objectiveGroups.yml @@ -40,8 +40,6 @@ EscapeShuttleObjective: 1 # DieObjective: 0.05 # DeltaV - Disable the lrp objective aka murderbone justification #HijackShuttleObjective: 0.02 - BecomePsionicObjective: 1 # Nyanotrasen - Become Psionic objective, see Resources/Prototypes/Nyanotrasen/Objectives/traitor.yml - #BecomeGolemObjective: 0.5 # Nyanotrasen - Become a golem objective, see Resources/Prototypes/Nyanotrasen/Objectives/traitor.yml - type: weightedRandom id: TraitorObjectiveGroupSocial diff --git a/Resources/Prototypes/Objectives/stealTargetGroups.yml b/Resources/Prototypes/Objectives/stealTargetGroups.yml index 5995278433..9c56881bd8 100644 --- a/Resources/Prototypes/Objectives/stealTargetGroups.yml +++ b/Resources/Prototypes/Objectives/stealTargetGroups.yml @@ -91,6 +91,13 @@ sprite: Objects/Misc/nukedisk.rsi state: icon +- type: stealTargetGroup + id: HoSAntiqueWeapon + name: head of security's personal weapon + sprite: + sprite: Objects/Weapons/Guns/Battery/pulse_pistol.rsi + state: base + # Thief Collection - type: stealTargetGroup diff --git a/Resources/Prototypes/Objectives/terminator.yml b/Resources/Prototypes/Objectives/terminator.yml deleted file mode 100644 index 1b569599a7..0000000000 --- a/Resources/Prototypes/Objectives/terminator.yml +++ /dev/null @@ -1,40 +0,0 @@ -- type: entity - abstract: true - parent: BaseObjective - id: BaseTerminatorObjective - components: - - type: Objective - difficulty: 1 - issuer: susnet - - type: RoleRequirement - roles: - components: - - TerminatorRole - -- type: entity - noSpawn: true - parent: [BaseTerminatorObjective, BaseKillObjective] - id: TerminateObjective - description: Follow your programming and terminate the target. - components: - - type: Objective - unique: false - - type: TargetObjective - title: objective-terminate-title - - type: PickRandomPerson - - type: TerminatorTargetOverride - - type: KillPersonCondition - requireDead: true - -- type: entity - noSpawn: true - parent: BaseTerminatorObjective - id: ShutDownObjective - name: Shut down - description: Once the mission is complete die to prevent our technology from being stolen. - components: - - type: Objective - icon: - sprite: Mobs/Species/Terminator/parts.rsi - state: skull_icon - - type: DieCondition diff --git a/Resources/Prototypes/Objectives/traitor.yml b/Resources/Prototypes/Objectives/traitor.yml index d9c071c30c..b00d12529a 100644 --- a/Resources/Prototypes/Objectives/traitor.yml +++ b/Resources/Prototypes/Objectives/traitor.yml @@ -33,6 +33,20 @@ - type: ObjectiveLimit limit: 2 # there is usually only 1 of each steal objective, have 2 max for drama +- type: entity # Head of Security steal objective. + noSpawn: true + parent: BaseTraitorStealObjective + id: HoSAntiqueWeaponStealObjective + components: + - type: Objective + difficulty: 3 # HoS will mostly be using the gun to stop you from stealing it + - type: NotJobRequirement + job: HeadOfSecurity + - type: StealCondition + verifyMapExistence: true + stealGroup: HoSAntiqueWeapon + owner: job-name-hos + # state - type: entity @@ -191,6 +205,9 @@ components: - type: StealCondition stealGroup: ClothingOuterHardsuitRd + - type: Objective + # This item must be worn or stored in a slowing duffelbag, very hard to hide. + difficulty: 3 - type: entity noSpawn: true @@ -320,4 +337,4 @@ - type: StealCondition stealGroup: SupermatterSliver objectiveNoOwnerText: objective-condition-steal-smsliver-title - descriptionText: objective-condition-steal-smsliver-description \ No newline at end of file + descriptionText: objective-condition-steal-smsliver-description diff --git a/Resources/Prototypes/Parkstation/Entities/Mobs/Customization/antenna.yml b/Resources/Prototypes/Parkstation/Entities/Mobs/Customization/antenna.yml deleted file mode 100644 index 4d32e5b516..0000000000 --- a/Resources/Prototypes/Parkstation/Entities/Mobs/Customization/antenna.yml +++ /dev/null @@ -1,89 +0,0 @@ -- type: marking - speciesRestriction: [IPC] - id: RobotAntennaTv - bodyPart: Hair - markingCategory: Hair - sprites: - - sprite: Parkstation/Mobs/Customization/ipc_antenna.rsi - state: ipc_antenna_tv - -- type: marking - speciesRestriction: [IPC] - id: RobotAntennaTesla - bodyPart: Hair - markingCategory: Hair - sprites: - - sprite: Parkstation/Mobs/Customization/ipc_antenna.rsi - state: ipc_antenna_tesla - -# - type: marking -# speciesRestriction: [IPC] -# id: RobotAntennaLightb -# bodyPart: Hair -# markingCategory: Hair -# sprites: -# - sprite: Parkstation/Mobs/Customization/ipc_antenna.rsi -# state: ipc_antenna_lightb - -# - type: marking -# speciesRestriction: [IPC] -# id: RobotAntennaLight -# bodyPart: Hair -# markingCategory: Hair -# sprites: -# - sprite: Parkstation/Mobs/Customization/ipc_antenna.rsi -# state: ipc_antenna_light - -- type: marking - speciesRestriction: [IPC] - id: RobotAntennaCyberhead - bodyPart: Hair - markingCategory: Hair - sprites: - - sprite: Parkstation/Mobs/Customization/ipc_antenna.rsi - state: ipc_antenna_cyberhead - -# - type: marking -# speciesRestriction: [IPC] -# id: RobotAntennaSidelights -# bodyPart: Hair -# markingCategory: Hair -# sprites: -# - sprite: Parkstation/Mobs/Customization/ipc_antenna.rsi -# state: ipc_antenna_sidelights - -- type: marking - speciesRestriction: [IPC] - id: RobotAntennaAntlers - bodyPart: Hair - markingCategory: Hair - sprites: - - sprite: Parkstation/Mobs/Customization/ipc_antenna.rsi - state: ipc_antenna_antlers - -- type: marking - speciesRestriction: [IPC] - id: RobotAntennaDroneeyes - bodyPart: Hair - markingCategory: Hair - sprites: - - sprite: Parkstation/Mobs/Customization/ipc_antenna.rsi - state: ipc_antenna_droneeyes - -- type: marking - speciesRestriction: [IPC] - id: RobotAntennaCrowned - bodyPart: Hair - markingCategory: Hair - sprites: - - sprite: Parkstation/Mobs/Customization/ipc_antenna.rsi - state: ipc_antenna_crowned - -- type: marking - speciesRestriction: [IPC] - id: RobotAntennaTowers - bodyPart: Hair - markingCategory: Hair - sprites: - - sprite: Parkstation/Mobs/Customization/ipc_antenna.rsi - state: ipc_antenna_towers diff --git a/Resources/Prototypes/Parkstation/Entities/Mobs/Customization/ears.yml b/Resources/Prototypes/Parkstation/Entities/Mobs/Customization/ears.yml index f51d9424a3..ac756bd390 100644 --- a/Resources/Prototypes/Parkstation/Entities/Mobs/Customization/ears.yml +++ b/Resources/Prototypes/Parkstation/Entities/Mobs/Customization/ears.yml @@ -89,26 +89,6 @@ - sprite: Parkstation/Mobs/Customization/ears.rsi state: mouse -- type: marking - id: EarsShadowkin - bodyPart: HeadTop - markingCategory: HeadTop - speciesRestriction: [Shadowkin] - sprites: - - sprite: Parkstation/Mobs/Customization/ears.rsi - state: shadowkin - -- type: marking - id: EarsShadowkinStriped - bodyPart: HeadTop - markingCategory: HeadTop - speciesRestriction: [Shadowkin] - sprites: - - sprite: Parkstation/Mobs/Customization/ears.rsi - state: shadowkin - - sprite: Parkstation/Mobs/Customization/ears.rsi - state: shadowkin_stripes - - type: marking id: EarsSylveon bodyPart: HeadTop diff --git a/Resources/Prototypes/Parkstation/Entities/Mobs/Customization/screens.yml b/Resources/Prototypes/Parkstation/Entities/Mobs/Customization/screens.yml deleted file mode 100644 index 6a70ed98d3..0000000000 --- a/Resources/Prototypes/Parkstation/Entities/Mobs/Customization/screens.yml +++ /dev/null @@ -1,350 +0,0 @@ -- type: marking - speciesRestriction: [IPC] - id: ScreenStatic - bodyPart: FacialHair - markingCategory: FacialHair - sprites: - - sprite: Parkstation/Mobs/Customization/ipc_screens.rsi - state: ipc_screen_static - -- type: marking - speciesRestriction: [IPC] - id: ScreenBlue - bodyPart: FacialHair - markingCategory: FacialHair - sprites: - - sprite: Parkstation/Mobs/Customization/ipc_screens.rsi - state: ipc_screen_blue - -- type: marking - speciesRestriction: [IPC] - id: ScreenBreakout - bodyPart: FacialHair - markingCategory: FacialHair - sprites: - - sprite: Parkstation/Mobs/Customization/ipc_screens.rsi - state: ipc_screen_breakout - -- type: marking - speciesRestriction: [IPC] - id: ScreenEight - bodyPart: FacialHair - markingCategory: FacialHair - sprites: - - sprite: Parkstation/Mobs/Customization/ipc_screens.rsi - state: ipc_screen_eight - -- type: marking - speciesRestriction: [IPC] - id: ScreenGoggles - bodyPart: FacialHair - markingCategory: FacialHair - sprites: - - sprite: Parkstation/Mobs/Customization/ipc_screens.rsi - state: ipc_screen_goggles - -- type: marking - speciesRestriction: [IPC] - id: ScreenExclaim - bodyPart: FacialHair - markingCategory: FacialHair - sprites: - - sprite: Parkstation/Mobs/Customization/ipc_screens.rsi - state: ipc_screen_exclaim - -- type: marking - speciesRestriction: [IPC] - id: ScreenHeart - bodyPart: FacialHair - markingCategory: FacialHair - sprites: - - sprite: Parkstation/Mobs/Customization/ipc_screens.rsi - state: ipc_screen_heart - -- type: marking - speciesRestriction: [IPC] - id: ScreenMonoeye - bodyPart: FacialHair - markingCategory: FacialHair - sprites: - - sprite: Parkstation/Mobs/Customization/ipc_screens.rsi - state: ipc_screen_monoeye - -- type: marking - speciesRestriction: [IPC] - id: ScreenNature - bodyPart: FacialHair - markingCategory: FacialHair - sprites: - - sprite: Parkstation/Mobs/Customization/ipc_screens.rsi - state: ipc_screen_nature - -- type: marking - speciesRestriction: [IPC] - id: ScreenOrange - bodyPart: FacialHair - markingCategory: FacialHair - sprites: - - sprite: Parkstation/Mobs/Customization/ipc_screens.rsi - state: ipc_screen_orange - -- type: marking - speciesRestriction: [IPC] - id: ScreenPink - bodyPart: FacialHair - markingCategory: FacialHair - sprites: - - sprite: Parkstation/Mobs/Customization/ipc_screens.rsi - state: ipc_screen_pink - -- type: marking - speciesRestriction: [IPC] - id: ScreenQuestion - bodyPart: FacialHair - markingCategory: FacialHair - sprites: - - sprite: Parkstation/Mobs/Customization/ipc_screens.rsi - state: ipc_screen_question - -- type: marking - speciesRestriction: [IPC] - id: ScreenShower - bodyPart: FacialHair - markingCategory: FacialHair - sprites: - - sprite: Parkstation/Mobs/Customization/ipc_screens.rsi - state: ipc_screen_shower - -- type: marking - speciesRestriction: [IPC] - id: ScreenYellow - bodyPart: FacialHair - markingCategory: FacialHair - sprites: - - sprite: Parkstation/Mobs/Customization/ipc_screens.rsi - state: ipc_screen_yellow - -- type: marking - speciesRestriction: [IPC] - id: ScreenScroll - bodyPart: FacialHair - markingCategory: FacialHair - sprites: - - sprite: Parkstation/Mobs/Customization/ipc_screens.rsi - state: ipc_screen_scroll - -- type: marking - speciesRestriction: [IPC] - id: ScreenConsole - bodyPart: FacialHair - markingCategory: FacialHair - sprites: - - sprite: Parkstation/Mobs/Customization/ipc_screens.rsi - state: ipc_screen_console - -- type: marking - speciesRestriction: [IPC] - id: ScreenRgb - bodyPart: FacialHair - markingCategory: FacialHair - sprites: - - sprite: Parkstation/Mobs/Customization/ipc_screens.rsi - state: ipc_screen_rgb - -- type: marking - speciesRestriction: [IPC] - id: ScreenGlider - bodyPart: FacialHair - markingCategory: FacialHair - sprites: - - sprite: Parkstation/Mobs/Customization/ipc_screens.rsi - state: ipc_screen_glider - -- type: marking - speciesRestriction: [IPC] - id: ScreenRainbowhoriz - bodyPart: FacialHair - markingCategory: FacialHair - sprites: - - sprite: Parkstation/Mobs/Customization/ipc_screens.rsi - state: ipc_screen_rainbowhoriz - -- type: marking - speciesRestriction: [IPC] - id: ScreenBsod - bodyPart: FacialHair - markingCategory: FacialHair - sprites: - - sprite: Parkstation/Mobs/Customization/ipc_screens.rsi - state: ipc_screen_bsod - -- type: marking - speciesRestriction: [IPC] - id: ScreenRedtext - bodyPart: FacialHair - markingCategory: FacialHair - sprites: - - sprite: Parkstation/Mobs/Customization/ipc_screens.rsi - state: ipc_screen_redtext - -- type: marking - speciesRestriction: [IPC] - id: ScreenSinewave - bodyPart: FacialHair - markingCategory: FacialHair - sprites: - - sprite: Parkstation/Mobs/Customization/ipc_screens.rsi - state: ipc_screen_sinewave - -- type: marking - speciesRestriction: [IPC] - id: ScreenSquarewave - bodyPart: FacialHair - markingCategory: FacialHair - sprites: - - sprite: Parkstation/Mobs/Customization/ipc_screens.rsi - state: ipc_screen_squarewave - -- type: marking - speciesRestriction: [IPC] - id: ScreenEcgwave - bodyPart: FacialHair - markingCategory: FacialHair - sprites: - - sprite: Parkstation/Mobs/Customization/ipc_screens.rsi - state: ipc_screen_ecgwave - -- type: marking - speciesRestriction: [IPC] - id: ScreenEyes - bodyPart: FacialHair - markingCategory: FacialHair - sprites: - - sprite: Parkstation/Mobs/Customization/ipc_screens.rsi - state: ipc_screen_eyes - -- type: marking - speciesRestriction: [IPC] - id: ScreenEyestall - bodyPart: FacialHair - markingCategory: FacialHair - sprites: - - sprite: Parkstation/Mobs/Customization/ipc_screens.rsi - state: ipc_screen_eyestall - -- type: marking - speciesRestriction: [IPC] - id: ScreenEyesangry - bodyPart: FacialHair - markingCategory: FacialHair - sprites: - - sprite: Parkstation/Mobs/Customization/ipc_screens.rsi - state: ipc_screen_eyesangry - -- type: marking - speciesRestriction: [IPC] - id: ScreenLoading - bodyPart: FacialHair - markingCategory: FacialHair - sprites: - - sprite: Parkstation/Mobs/Customization/ipc_screens.rsi - state: ipc_screen_loading - -- type: marking - speciesRestriction: [IPC] - id: ScreenWindowsxp - bodyPart: FacialHair - markingCategory: FacialHair - sprites: - - sprite: Parkstation/Mobs/Customization/ipc_screens.rsi - state: ipc_screen_windowsxp - -- type: marking - speciesRestriction: [IPC] - id: ScreenTetris - bodyPart: FacialHair - markingCategory: FacialHair - sprites: - - sprite: Parkstation/Mobs/Customization/ipc_screens.rsi - state: ipc_screen_tetris - -- type: marking - speciesRestriction: [IPC] - id: ScreenTv - bodyPart: FacialHair - markingCategory: FacialHair - sprites: - - sprite: Parkstation/Mobs/Customization/ipc_screens.rsi - state: ipc_screen_tv - -- type: marking - speciesRestriction: [IPC] - id: ScreenTextdrop - bodyPart: FacialHair - markingCategory: FacialHair - sprites: - - sprite: Parkstation/Mobs/Customization/ipc_screens.rsi - state: ipc_screen_textdrop - -- type: marking - speciesRestriction: [IPC] - id: ScreenStars - bodyPart: FacialHair - markingCategory: FacialHair - sprites: - - sprite: Parkstation/Mobs/Customization/ipc_screens.rsi - state: ipc_screen_stars - -- type: marking - speciesRestriction: [IPC] - id: ScreenRainbowdiag - bodyPart: FacialHair - markingCategory: FacialHair - sprites: - - sprite: Parkstation/Mobs/Customization/ipc_screens.rsi - state: ipc_screen_rainbowdiag - -- type: marking - speciesRestriction: [IPC] - id: ScreenBlank - bodyPart: FacialHair - markingCategory: FacialHair - sprites: - - sprite: Parkstation/Mobs/Customization/ipc_screens.rsi - state: ipc_screen_blank - -- type: marking - speciesRestriction: [IPC] - id: ScreenSmile - bodyPart: FacialHair - markingCategory: FacialHair - sprites: - - sprite: Parkstation/Mobs/Customization/ipc_screens.rsi - state: ipc_screen_smile - -- type: marking - speciesRestriction: [IPC] - id: ScreenFrown - bodyPart: FacialHair - markingCategory: FacialHair - sprites: - - sprite: Parkstation/Mobs/Customization/ipc_screens.rsi - state: ipc_screen_frown - -- type: marking - speciesRestriction: [IPC] - id: ScreenRing - bodyPart: FacialHair - markingCategory: FacialHair - sprites: - - sprite: Parkstation/Mobs/Customization/ipc_screens.rsi - state: ipc_screen_ring - -- type: marking - speciesRestriction: [IPC] - id: ScreenL - bodyPart: FacialHair - markingCategory: FacialHair - sprites: - - sprite: Parkstation/Mobs/Customization/ipc_screens.rsi - state: ipc_screen_l diff --git a/Resources/Prototypes/Parkstation/Entities/Mobs/Customization/tails.yml b/Resources/Prototypes/Parkstation/Entities/Mobs/Customization/tails.yml index a665f455fb..1e0c454430 100644 --- a/Resources/Prototypes/Parkstation/Entities/Mobs/Customization/tails.yml +++ b/Resources/Prototypes/Parkstation/Entities/Mobs/Customization/tails.yml @@ -136,49 +136,3 @@ sprites: - sprite: Parkstation/Mobs/Customization/tails32x32.rsi state: succubus - -- type: marking - id: TailShadowkin - bodyPart: Tail - markingCategory: Tail - speciesRestriction: [Shadowkin] - sprites: - - sprite: Parkstation/Mobs/Customization/tails64x32.rsi - state: shadowkin - -- type: marking - id: TailShadowkinBig - bodyPart: Tail - markingCategory: Tail - speciesRestriction: [Shadowkin] - sprites: - - sprite: Parkstation/Mobs/Customization/tails64x32.rsi - state: shadowkin_big - -- type: marking - id: TailShadowkinBigFluff - bodyPart: Tail - markingCategory: Tail - speciesRestriction: [Shadowkin] - sprites: - - sprite: Parkstation/Mobs/Customization/tails64x32.rsi - state: shadowkin_big_fluff - -- type: marking - id: TailShadowkinShorter - bodyPart: Tail - markingCategory: Tail - speciesRestriction: [Shadowkin] - sprites: - - sprite: Parkstation/Mobs/Customization/tails32x32.rsi - state: shadowkin_shorter - -- type: marking - id: TailShadowkinMedium - bodyPart: Tail - markingCategory: Tail - speciesRestriction: [Shadowkin] - sprites: - - sprite: Parkstation/Mobs/Customization/tails32x32.rsi - state: shadowkin_medium - diff --git a/Resources/Prototypes/Parkstation/Roles/Jobs/Service/chief_service_supervisor.yml b/Resources/Prototypes/Parkstation/Roles/Jobs/Service/chief_service_supervisor.yml index 479b9c643b..961182a4ca 100644 --- a/Resources/Prototypes/Parkstation/Roles/Jobs/Service/chief_service_supervisor.yml +++ b/Resources/Prototypes/Parkstation/Roles/Jobs/Service/chief_service_supervisor.yml @@ -29,11 +29,6 @@ - Theatre - Kitchen - Hydroponics - special: - - !type:AddComponentSpecial - components: - - type: PsionicBonusChance - flatBonus: 0.025 - type: startingGear id: CSSGear diff --git a/Resources/Prototypes/Parkstation/SoundCollections/Music/events.yml b/Resources/Prototypes/Parkstation/SoundCollections/Music/events.yml deleted file mode 100644 index cb4e661cd3..0000000000 --- a/Resources/Prototypes/Parkstation/SoundCollections/Music/events.yml +++ /dev/null @@ -1,5 +0,0 @@ -- type: soundCollection - id: ExpeditionCountdownDefault - files: - - /Audio/Misc/tension_session.ogg - - /Audio/Parkstation/Music/deadline.ogg diff --git a/Resources/Prototypes/Parkstation/Traits/disabilities.yml b/Resources/Prototypes/Parkstation/Traits/disabilities.yml deleted file mode 100644 index b7aa00353c..0000000000 --- a/Resources/Prototypes/Parkstation/Traits/disabilities.yml +++ /dev/null @@ -1,12 +0,0 @@ -- type: trait - id: Nearsighted - category: Visual - points: 1 - requirements: - - !type:CharacterJobRequirement - inverted: true - jobs: - - Borg - - MedicalBorg - components: - - type: Nearsighted diff --git a/Resources/Prototypes/Parkstation/tags.yml b/Resources/Prototypes/Parkstation/tags.yml deleted file mode 100644 index 3b885a5801..0000000000 --- a/Resources/Prototypes/Parkstation/tags.yml +++ /dev/null @@ -1,2 +0,0 @@ -- type: Tag - id: GlassesNearsight diff --git a/Resources/Prototypes/Polymorphs/polymorph.yml b/Resources/Prototypes/Polymorphs/polymorph.yml index b4249f8a3e..a1a805c74f 100644 --- a/Resources/Prototypes/Polymorphs/polymorph.yml +++ b/Resources/Prototypes/Polymorphs/polymorph.yml @@ -75,6 +75,17 @@ inventory: Transfer revertOnDeath: true +- type: polymorph + id: SlimeMorphGeras + configuration: + entity: MobSlimesGeras + transferName: true + transferHumanoidAppearance: false + inventory: Drop + transferDamage: true + revertOnDeath: true + revertOnCrit: true + # this is a test for transferring some visual appearance stuff - type: polymorph id: TestHumanMorph @@ -164,3 +175,25 @@ revertOnDeath: true revertOnCrit: true duration: 20 + +# Polymorphs for Wizards polymorph self spell +- type: polymorph + id: WizardSpider + configuration: + entity: MobGiantSpiderWizard #Not angry so ghosts can't just take over the wizard + transferName: true + inventory: None + revertOnDeath: true + revertOnCrit: true + +- type: polymorph + id: WizardRod + configuration: + entity: ImmovableRodWizard #CLANG + transferName: true + transferDamage: false + inventory: None + duration: 1 + forced: true + revertOnCrit: false + revertOnDeath: false diff --git a/Resources/Prototypes/Procedural/Magnet/asteroid.yml b/Resources/Prototypes/Procedural/Magnet/asteroid.yml index a21b709afa..8fcd265297 100644 --- a/Resources/Prototypes/Procedural/Magnet/asteroid.yml +++ b/Resources/Prototypes/Procedural/Magnet/asteroid.yml @@ -10,6 +10,8 @@ OrePlasma: 0.15 OreUranium: 0.15 OreArtifactFragment: 0.15 + OreBluespace: 0.1 + OreNormality: 0.1 # Large asteroids, typically 1 - type: dungeonConfig diff --git a/Resources/Prototypes/Procedural/biome_ore_templates.yml b/Resources/Prototypes/Procedural/biome_ore_templates.yml index 4a60427e30..c0001367d5 100644 --- a/Resources/Prototypes/Procedural/biome_ore_templates.yml +++ b/Resources/Prototypes/Procedural/biome_ore_templates.yml @@ -144,3 +144,32 @@ minGroupSize: 1 maxGroupSize: 2 radius: 4 + +# Bluespace +- type: biomeMarkerLayer + id: OreBluespace + entityMask: + AsteroidRock: AsteroidRockBluespace + WallRock: WallRockBluespace + WallRockBasalt: WallRockBasaltBluespace + WallRockChromite: WallRockChromiteBluespace + WallRockSand: WallRockSandBluespace + WallRockSnow: WallRockSnowBluespace + maxCount: 6 + minGroupSize: 1 + maxGroupSize: 2 + radius: 4 + +- type: biomeMarkerLayer + id: OreNormality + entityMask: + AsteroidRock: AsteroidRockNormality + WallRock: WallRockNormality + WallRockBasalt: WallRockBasaltNormality + WallRockChromite: WallRockChromiteNormality + WallRockSand: WallRockSandNormality + WallRockSnow: WallRockSnowNormality + maxCount: 6 + minGroupSize: 1 + maxGroupSize: 2 + radius: 4 diff --git a/Resources/Prototypes/Procedural/salvage_loot.yml b/Resources/Prototypes/Procedural/salvage_loot.yml index f12e8c7ffd..b256c73dfd 100644 --- a/Resources/Prototypes/Procedural/salvage_loot.yml +++ b/Resources/Prototypes/Procedural/salvage_loot.yml @@ -185,3 +185,17 @@ loots: - !type:BiomeMarkerLoot proto: OreArtifactFragment + +- type: salvageLoot + id: OreBluespace + guaranteed: true + loots: + - !type:BiomeMarkerLoot + proto: OreBluespace + +- type: salvageLoot + id: OreNormality + guaranteed: true + loots: + - !type:BiomeMarkerLoot + proto: OreNormality diff --git a/Resources/Prototypes/Psionics/psionics.yml b/Resources/Prototypes/Psionics/psionics.yml new file mode 100644 index 0000000000..7ee6e193e3 --- /dev/null +++ b/Resources/Prototypes/Psionics/psionics.yml @@ -0,0 +1,275 @@ +- type: psionicPower + id: DispelPower + name: Dispel + description: dispel-power-description + actions: + - ActionDispel + components: + - type: DispelPower + initializationFeedback: dispel-power-initialization-feedback + metapsionicFeedback: dispel-power-metapsionic-feedback + dampeningModifier: 1 + +- type: psionicPower + id: MassSleepPower + name: Mass Sleep + description: mass-sleep-power-description + actions: + - ActionMassSleep + components: + - type: MassSleepPower + # initializationFeedback: mass-sleep-power-initialization-feedback # I apologize, I don't feel like writing a paragraph of feedback for a power that's getting replaced with a new one. + metapsionicFeedback: mass-sleep-power-metapsionic-feedback + amplificationModifier: 0.5 + dampeningModifier: 0.5 + +- type: psionicPower + id: MindSwapPower + name: Mind Swap + description: mind-swap-power-description + actions: + - ActionMindSwap + components: + - type: MindSwapPower + initializationFeedback: mind-swap-power-initialization-feedback + metapsionicFeedback: mind-swap-power-metapsionic-feedback + amplificationModifier: 1 + +- type: psionicPower + id: NoosphericZapPower + name: Noospheric Zap + description: noospheric-zap-power-description + actions: + - ActionNoosphericZap + components: + - type: NoosphericZapPower + initializationFeedback: noospheric-zap-power-initialization-feedback + metapsionicFeedback: noospheric-zap-power-metapsionic-feedback + amplificationModifier: 1 + +- type: psionicPower + id: PyrokinesisPower + name: Pyrokinesis + description: pyrokinesis-power-description + actions: + - ActionPyrokinesis + components: + - type: PyrokinesisPower + initializationFeedback: pyrokinesis-power-initialization-feedback + metapsionicFeedback: pyrokinesis-power-metapsionic-feedback + amplificationModifier: 1 + +- type: psionicPower + id: MetapsionicPower + name: Metapsionic Pulse + description: metapsionic-power-description + actions: + - ActionMetapsionic + components: + - type: MetapsionicPower + initializationFeedback: metapsionic-power-initialization-feedback + metapsionicFeedback: metapsionic-power-metapsionic-feedback + amplificationModifier: 0.5 + dampeningModifier: 0.5 + +- type: psionicPower + id: PsionicRegenerationPower + name: Psionic Regeneration + description: psionic-regeneration-power-description + actions: + - ActionPsionicRegeneration + components: + - type: PsionicRegenerationPower + initializationFeedback: psionic-regeneration-power-initialization-feedback + metapsionicFeedback: psionic-regeneration-power-metapsionic-feedback + amplificationModifier: 0.5 + dampeningModifier: 0.5 + +- type: psionicPower + id: TelegnosisPower + name: Telegnosis + description: telegnosis-power-description + actions: + - ActionTelegnosis + components: + - type: TelegnosisPower + initializationFeedback: telegnosis-power-initialization-feedback + metapsionicFeedback: telegnosis-power-metapsionic-feedback + amplificationModifier: 0.5 + dampeningModifier: 0.5 + +- type: psionicPower + id: PsionicInvisibilityPower + name: Psionic Invisibility + description: psionic-invisibility-power-description + actions: + - ActionDispel + components: + - type: PsionicInvisibilityPower + initializationFeedback: psionic-invisibility-power-initialization-feedback + metapsionicFeedback: psionic-invisibility-power-metapsionic-feedback + amplificationModifier: 0.5 + dampeningModifier: 0.5 + +- type: psionicPower + id: XenoglossyPower + name: Xenoglossy + description: xenoglossy-power-description + components: + - type: UniversalLanguageSpeaker + initializationFeedback: xenoglossy-power-initialization-feedback + metapsionicFeedback: psionic-language-power-feedback # Reuse for telepathy, clairaudience, etc + powerSlotCost: 0 + +- type: psionicPower + id: PsychognomyPower #i.e. reverse physiognomy + name: Psychognomy #psycho- + -gnomy. I reccomend starting with your language's equilvalent of "physiognomy" and working backwards. i.e. психо(г)номика + description: psychognomy-power-description + components: + - type: Psychognomist + initializationFeedback: psychognomy-power-initialization-feedback + metapsionicFeedback: psionic-language-power-feedback + powerSlotCost: 0 + +- type: psionicPower + id: TelepathyPower + name: Telepathy + description: telepathy-power-description + components: + - type: Telepathy + initializationFeedback: telepathy-power-initialization-feedback + metapsionicFeedback: psionic-language-power-feedback # Reuse for telepathy, clairaudience, etc + powerSlotCost: 0 + +- type: psionicPower + id: HealingWordPower + name: HealingWord + description: healing-word-power-description + actions: + - ActionHealingWord + initializationFeedback: healing-word-power-initialization-feedback + metapsionicFeedback: healing-word-power-feedback + amplificationModifier: 0.5 + dampeningModifier: 0.5 + +- type: psionicPower + id: RevivifyPower + name: Revivify + description: revivify-power-description + actions: + - ActionRevivify + initializationFeedback: revivify-power-initialization-feedback + metapsionicFeedback: revivify-power-feedback + amplificationModifier: 2.5 # An extremely rare and dangerous power + powerSlotCost: 2 + +- type: psionicPower + id: LowAmplification + name: LowAmplification + description: low-amplification-power-description + amplificationModifier: -0.25 + powerSlotCost: 0 + +- type: psionicPower + id: HighAmplification + name: HighAmplification + description: high-amplification-power-description + amplificationModifier: 0.25 + powerSlotCost: 0 + +- type: psionicPower + id: PowerOverwhelming + name: PowerOverwhelming + description: power-overwhelming-power-description + metapsionicFeedback: power-overwhelming-power-feedback + amplificationModifier: 2 + powerSlotCost: 2 + +- type: psionicPower + id: LowDampening + name: LowDampening + description: low-dampening-power-description + dampeningModifier: -0.25 + powerSlotCost: 0 + +- type: psionicPower + id: HighDampening + name: HighDampening + description: high-dampening-power-description + dampeningModifier: 0.25 + powerSlotCost: 0 + +- type: psionicPower + id: ShadeskipPower + name: Shadeskip + description: shadeskip-power-description + actions: + - ActionShadeskip + initializationFeedback: shadeskip-power-initialization-feedback + metapsionicFeedback: shadeskip-power-metapsionic-feedback + amplificationModifier: 1 + +- type: psionicPower + id: TelekineticPulsePower + name: Telekinetic Pulse + description: telekinetic-pulse-power-description + actions: + - ActionTelekineticPulse + initializationFeedback: telekinetic-pulse-power-initialization-feedback + metapsionicFeedback: telekinetic-pulse-power-metapsionic-feedback + amplificationModifier: 1 + +- type: psionicPower + id: ShadowkinPowers + name: Shadowkin Powers + description: shadowkin-powers-description + actions: + - ActionShadowkinShadeskip + - ActionDarkSwap + powerSlotCost: 1 + +- type: psionicPower + id: EtherealVisionPower + name: Ethereal Vision + description: ethereal-vision-powers-description + components: + - type: ShowEthereal + powerSlotCost: 0 + +- type: psionicPower + id: DarkSwapPower + name: DarkSwap + description: darkswap-power-description + actions: + - ActionDarkSwap + powerSlotCost: 1 + +- type: psionicPower + id: PyrokineticFlare + name: Pyrokinetic Flare + description: pyrokinetic-flare-power-description + actions: + - ActionPyrokineticFlare + powerSlotCost: 1 + initializationFeedback: pyrokinetic-flare-power-initialization-feedback + metapsionicFeedback: pyrokinetic-flare-power-metapsionic-feedback + amplificationModifier: 0.25 + +- type: psionicPower + id: SummonImpPower + name: Summon Imp + description: summon-imp-power-description + actions: + - ActionSummonImp + powerSlotCost: 1 + initializationFeedback: summon-imp-power-initialization-feedback + amplificationModifier: 0.5 + dampeningModifier: 0.5 + +- type: psionicPower + id: SummonRemiliaPower + name: Summon Remilia + description: summon-imp-power-description + actions: + - ActionSummonRemilia + powerSlotCost: 0 diff --git a/Resources/Prototypes/Reagents/Consumable/Drink/alcohol.yml b/Resources/Prototypes/Reagents/Consumable/Drink/alcohol.yml index e31087c309..f374a3debf 100644 --- a/Resources/Prototypes/Reagents/Consumable/Drink/alcohol.yml +++ b/Resources/Prototypes/Reagents/Consumable/Drink/alcohol.yml @@ -22,7 +22,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.3 + amount: 0.7 - type: reagent id: Ale @@ -39,6 +39,7 @@ metamorphicMaxFillLevels: 5 metamorphicFillBaseName: fill- metamorphicChangeColor: false + fizziness: 0.6 - type: reagent id: Beer @@ -55,6 +56,7 @@ metamorphicMaxFillLevels: 6 metamorphicFillBaseName: fill- metamorphicChangeColor: true + fizziness: 0.6 - type: reagent id: BlueCuracao @@ -77,7 +79,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.1 + amount: 0.25 - type: reagent id: BlueHawaiian @@ -97,7 +99,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.2 + amount: 0.154 - type: reagent id: Cognac @@ -121,7 +123,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.2 + amount: 0.4 - type: reagent id: DeadRum @@ -144,7 +146,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.2 + amount: 0.6 - type: reagent id: Ethanol @@ -163,7 +165,7 @@ - !type:HealthChange conditions: - !type:ReagentThreshold - min: 15 + min: 45 damage: types: Poison: 1 @@ -255,6 +257,12 @@ Cold: -1.60 - !type:Drunk boozePower: 2 + - !type:ChemAddMoodlet + moodPrototype: EthanolBenefit + conditions: + - !type:ReagentThreshold + reagent: Ethanol + min: 5 - type: reagent id: Gin @@ -278,7 +286,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.2 + amount: 0.45 - type: reagent id: CoffeeLiqueur @@ -294,6 +302,13 @@ metamorphicMaxFillLevels: 3 metamorphicFillBaseName: fill- metamorphicChangeColor: true + metabolisms: + Drink: + effects: + - !type:AdjustReagent + reagent: Ethanol + amount: 0.20 + - type: reagent id: MelonLiquor @@ -309,6 +324,12 @@ metamorphicMaxFillLevels: 5 metamorphicFillBaseName: fill- metamorphicChangeColor: true + metabolisms: + Drink: + effects: + - !type:AdjustReagent + reagent: Ethanol + amount: 0.20 - type: reagent id: NTCahors @@ -324,6 +345,12 @@ metamorphicMaxFillLevels: 5 metamorphicFillBaseName: fill- metamorphicChangeColor: true + metabolisms: + Drink: + effects: + - !type:AdjustReagent + reagent: Ethanol + amount: 0.17 - type: reagent id: PoisonWine @@ -346,7 +373,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.05 + amount: 0.11 Poison: effects: - !type:HealthChange @@ -376,7 +403,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.2 + amount: 0.40 ##Commented out in favor of Nyano sake/soju #- type: reagent @@ -399,7 +426,7 @@ metamorphicSprite: sprite: Objects/Consumable/Drinks/tequillaglass.rsi state: icon_empty - metamorphicMaxFillLevels: 4 + metamorphicMaxFillLevels: 3 metamorphicFillBaseName: fill- metamorphicChangeColor: false metabolisms: @@ -409,7 +436,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.2 + amount: 0.40 - type: reagent id: Vermouth @@ -425,6 +452,12 @@ metamorphicMaxFillLevels: 4 metamorphicFillBaseName: fill- metamorphicChangeColor: false + metabolisms: + Drink: + effects: + - !type:AdjustReagent + reagent: Ethanol + amount: 0.16 - type: reagent id: Vodka @@ -448,7 +481,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.2 + amount: 0.40 - type: reagent id: Whiskey @@ -472,7 +505,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.2 + amount: 0.40 - type: reagent id: Wine @@ -489,6 +522,14 @@ metamorphicMaxFillLevels: 5 metamorphicFillBaseName: fill- metamorphicChangeColor: true + metabolisms: + Drink: + effects: + - !type:SatiateThirst + factor: 2 + - !type:AdjustReagent + reagent: Ethanol + amount: 0.11 - type: reagent id: Champagne @@ -512,7 +553,8 @@ factor: 3 - !type:AdjustReagent reagent: Ethanol - amount: 0.3 + amount: 0.12 + fizziness: 0.8 # Mixed Alcohol @@ -530,6 +572,14 @@ metamorphicMaxFillLevels: 5 metamorphicFillBaseName: fill- metamorphicChangeColor: false + metabolisms: + Drink: + effects: + - !type:SatiateThirst + factor: 1 + - !type:AdjustReagent + reagent: Ethanol + amount: 0.073 - type: reagent id: AlliesCocktail @@ -545,6 +595,14 @@ metamorphicMaxFillLevels: 4 metamorphicFillBaseName: fill- metamorphicChangeColor: false + metabolisms: + Drink: + effects: + - !type:SatiateThirst + factor: 2 + - !type:AdjustReagent + reagent: Ethanol + amount: 0.37 - type: reagent id: Aloe @@ -575,6 +633,14 @@ metamorphicMaxFillLevels: 5 metamorphicFillBaseName: fill- metamorphicChangeColor: false + metabolisms: + Drink: + effects: + - !type:SatiateThirst + factor: 2 + - !type:AdjustReagent + reagent: Ethanol + amount: 0.204 - type: reagent id: Andalusia @@ -590,6 +656,14 @@ metamorphicMaxFillLevels: 4 metamorphicFillBaseName: fill- metamorphicChangeColor: false + metabolisms: + Drink: + effects: + - !type:SatiateThirst + factor: 2 + - !type:AdjustReagent + reagent: Ethanol + amount: 0.27 - type: reagent id: Antifreeze @@ -612,7 +686,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.15 + amount: 0.2 - type: reagent id: AtomicBomb @@ -635,7 +709,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.15 + amount: 0.254 - !type:AdjustReagent reagent: Uranium amount: 0.05 @@ -661,7 +735,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.15 + amount: 0.28 - type: reagent id: BahamaMama @@ -677,6 +751,14 @@ metamorphicMaxFillLevels: 5 metamorphicFillBaseName: fill- metamorphicChangeColor: true + metabolisms: + Drink: + effects: + - !type:SatiateThirst + factor: 2 + - !type:AdjustReagent + reagent: Ethanol + amount: 0.11 - type: reagent id: BananaHonk @@ -707,6 +789,14 @@ metamorphicMaxFillLevels: 3 metamorphicFillBaseName: fill- metamorphicChangeColor: true + metabolisms: + Drink: + effects: + - !type:SatiateThirst + factor: 2 + - !type:AdjustReagent + reagent: Ethanol + amount: 0.05 - type: reagent id: BeepskySmash @@ -729,7 +819,8 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.15 + amount: 0.13 + fizziness: 0.3 - type: reagent id: BlackRussian @@ -752,7 +843,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.2 + amount: 0.33 - type: reagent id: BloodyMary @@ -768,6 +859,14 @@ metamorphicMaxFillLevels: 5 metamorphicFillBaseName: fill- metamorphicChangeColor: true + metabolisms: + Drink: + effects: + - !type:SatiateThirst + factor: 2 + - !type:AdjustReagent + reagent: Ethanol + amount: 0.13 - type: reagent id: Booger @@ -783,6 +882,14 @@ metamorphicMaxFillLevels: 5 metamorphicFillBaseName: fill- metamorphicChangeColor: true + metabolisms: + Drink: + effects: + - !type:SatiateThirst + factor: 2 + - !type:AdjustReagent + reagent: Ethanol + amount: 0.08 - type: reagent id: BraveBull @@ -805,7 +912,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.2 + amount: 0.33 - type: reagent id: CoconutRum @@ -825,7 +932,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.2 + amount: 0.26 - type: reagent id: Cosmopolitan @@ -845,7 +952,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.15 + amount: 0.13 - type: reagent id: CubaLibre @@ -868,7 +975,8 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.07 + amount: 0.13 + fizziness: 0.2 - type: reagent id: DemonsBlood @@ -884,6 +992,15 @@ metamorphicMaxFillLevels: 4 metamorphicFillBaseName: fill- metamorphicChangeColor: true + metabolisms: + Drink: + effects: + - !type:SatiateThirst + factor: 2 + - !type:AdjustReagent + reagent: Ethanol + amount: 0.10 + fizziness: 0.3 - type: reagent id: DevilsKiss @@ -899,6 +1016,14 @@ metamorphicMaxFillLevels: 3 metamorphicFillBaseName: fill- metamorphicChangeColor: true + metabolisms: + Drink: + effects: + - !type:SatiateThirst + factor: 2 + - !type:AdjustReagent + reagent: Ethanol + amount: 0.20 - type: reagent id: DoctorsDelight @@ -921,9 +1046,6 @@ factor: 2 - !type:SatiateHunger factor: -2 - - !type:AdjustReagent - reagent: Ethanol - amount: 0.05 Medicine: effects: - !type:HealthChange @@ -955,7 +1077,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.15 + amount: 0.225 - type: reagent id: ErikaSurprise @@ -971,6 +1093,15 @@ metamorphicMaxFillLevels: 5 metamorphicFillBaseName: fill- metamorphicChangeColor: true + metabolisms: + Drink: + effects: + - !type:SatiateThirst + factor: 2 + - !type:AdjustReagent + reagent: Ethanol + amount: 0.02 + fizziness: 0.15 - type: reagent id: GargleBlaster @@ -993,7 +1124,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.2 + amount: 0.32 - type: reagent id: GinFizz @@ -1016,7 +1147,8 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.07 + amount: 0.15 + fizziness: 0.4 - type: reagent id: GinTonic @@ -1039,7 +1171,8 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.07 + amount: 0.15 + fizziness: 0.4 - type: reagent id: Gildlager @@ -1062,7 +1195,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.2 + amount: 0.363 - type: reagent id: Grog @@ -1078,6 +1211,14 @@ metamorphicMaxFillLevels: 5 metamorphicFillBaseName: fill- metamorphicChangeColor: true + metabolisms: + Drink: + effects: + - !type:SatiateThirst + factor: 2 + - !type:AdjustReagent + reagent: Ethanol + amount: 0.10 - type: reagent id: HippiesDelight @@ -1093,6 +1234,14 @@ metamorphicMaxFillLevels: 6 metamorphicFillBaseName: fill- metamorphicChangeColor: false + metabolisms: + Drink: + effects: + - !type:SatiateThirst + factor: 2 + - !type:AdjustReagent + reagent: Ethanol + amount: 0.16 - type: reagent id: Hooch @@ -1102,6 +1251,14 @@ physicalDesc: reagent-physical-desc-strong-smelling flavor: alcohol color: "#664e00" + metabolisms: + Drink: + effects: + - !type:SatiateThirst + factor: 2 + - !type:AdjustReagent + reagent: Ethanol + amount: 0.50 - type: reagent id: IcedBeer @@ -1117,6 +1274,7 @@ metamorphicMaxFillLevels: 6 metamorphicFillBaseName: fill- metamorphicChangeColor: false + fizziness: 0.6 - type: reagent id: IrishCarBomb @@ -1139,7 +1297,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.15 + amount: 0.16 - type: reagent id: IrishCream @@ -1162,7 +1320,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.2 + amount: 0.266 - type: reagent id: IrishCoffee @@ -1185,7 +1343,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.15 + amount: 0.133 - type: reagent id: LongIslandIcedTea @@ -1208,7 +1366,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.15 + amount: 0.273 - type: reagent id: Manhattan @@ -1224,6 +1382,14 @@ metamorphicMaxFillLevels: 3 metamorphicFillBaseName: fill- metamorphicChangeColor: false + metabolisms: + Drink: + effects: + - !type:SatiateThirst + factor: 2 + - !type:AdjustReagent + reagent: Ethanol + amount: 0.32 - type: reagent id: ManhattanProject @@ -1239,6 +1405,14 @@ metamorphicMaxFillLevels: 3 metamorphicFillBaseName: fill- metamorphicChangeColor: false + metabolisms: + Drink: + effects: + - !type:SatiateThirst + factor: 2 + - !type:AdjustReagent + reagent: Ethanol + amount: 0.363 - type: reagent id: ManlyDorf @@ -1254,6 +1428,7 @@ metamorphicMaxFillLevels: 2 metamorphicFillBaseName: fill- metamorphicChangeColor: false + fizziness: 0.7 - type: reagent id: Margarita @@ -1291,7 +1466,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.15 + amount: 0.353 - type: reagent id: Mead @@ -1307,6 +1482,7 @@ metamorphicMaxFillLevels: 5 metamorphicFillBaseName: fill- metamorphicChangeColor: true + fizziness: 0.4 - type: reagent id: Mojito @@ -1322,6 +1498,15 @@ metamorphicMaxFillLevels: 6 metamorphicFillBaseName: fill- metamorphicChangeColor: false + metabolisms: + Drink: + effects: + - !type:SatiateThirst + factor: 2 + - !type:AdjustReagent + reagent: Ethanol + amount: 0.10 + fizziness: 0.3 - type: reagent id: Moonshine @@ -1331,6 +1516,12 @@ physicalDesc: reagent-physical-desc-strong-smelling flavor: moonshine color: "#d1d7d155" + metamorphicSprite: + sprite: Objects/Consumable/Drinks/moonshineglass.rsi + state: icon_empty + metamorphicMaxFillLevels: 6 + metamorphicFillBaseName: fill- + metamorphicChangeColor: false metabolisms: Drink: effects: @@ -1338,7 +1529,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.25 + amount: 0.40 - type: reagent id: Neurotoxin @@ -1361,7 +1552,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.25 + amount: 0.16 Poison: effects: - !type:HealthChange @@ -1387,7 +1578,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.2 + amount: 0.04 - type: reagent id: Patron @@ -1410,7 +1601,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.2 + amount: 0.50 - type: reagent id: RedMead @@ -1426,6 +1617,7 @@ metamorphicMaxFillLevels: 5 metamorphicFillBaseName: fill- metamorphicChangeColor: true + fizziness: 0.4 - type: reagent id: PinaColada @@ -1438,6 +1630,14 @@ metamorphicSprite: sprite: Objects/Consumable/Drinks/pinacolada.rsi state: icon + metabolisms: + Drink: + effects: + - !type:SatiateThirst + factor: 2 + - !type:AdjustReagent + reagent: Ethanol + amount: 0.06 - type: reagent id: Sbiten @@ -1453,6 +1653,14 @@ metamorphicMaxFillLevels: 5 metamorphicFillBaseName: fill- metamorphicChangeColor: true + metabolisms: + Drink: + effects: + - !type:SatiateThirst + factor: 2 + - !type:AdjustReagent + reagent: Ethanol + amount: 0.2 - type: reagent id: ScrewdriverCocktail @@ -1475,7 +1683,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.07 + amount: 0.13 - type: reagent id: CogChamp @@ -1501,7 +1709,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.15 + amount: 0.176 - type: reagent id: Silencer @@ -1540,6 +1748,14 @@ metamorphicMaxFillLevels: 5 metamorphicFillBaseName: fill- metamorphicChangeColor: false + metabolisms: + Drink: + effects: + - !type:SatiateThirst + factor: 2 + - !type:AdjustReagent + reagent: Ethanol + amount: 0.23 - type: reagent id: SnowWhite @@ -1555,6 +1771,15 @@ metamorphicMaxFillLevels: 6 metamorphicFillBaseName: fill- metamorphicChangeColor: true + metabolisms: + Drink: + effects: + - !type:SatiateThirst + factor: 2 + - !type:AdjustReagent + reagent: Ethanol + amount: 0.03 + fizziness: 0.3 - type: reagent id: SuiDream @@ -1570,6 +1795,15 @@ metamorphicMaxFillLevels: 5 metamorphicFillBaseName: fill- metamorphicChangeColor: false + metabolisms: + Drink: + effects: + - !type:SatiateThirst + factor: 2 + - !type:AdjustReagent + reagent: Ethanol + amount: 0.13 + fizziness: 0.2 - type: reagent id: SyndicateBomb @@ -1585,6 +1819,15 @@ metamorphicMaxFillLevels: 6 metamorphicFillBaseName: fill- metamorphicChangeColor: true + metabolisms: + Drink: + effects: + - !type:SatiateThirst + factor: 2 + - !type:AdjustReagent + reagent: Ethanol + amount: 0.095 + fizziness: 0.6 - type: reagent id: TequilaSunrise @@ -1607,7 +1850,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.15 + amount: 0.26 - type: reagent id: TheMartinez @@ -1623,6 +1866,15 @@ metamorphicMaxFillLevels: 3 metamorphicFillBaseName: fill- metamorphicChangeColor: false + metabolisms: + Drink: + effects: + - !type:SatiateThirst + factor: 2 + - !type:AdjustReagent + reagent: Ethanol + amount: 0.13 + fizziness: 0.2 - type: reagent id: ThreeMileIsland @@ -1645,7 +1897,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.15 + amount: 0.273 - !type:AdjustReagent reagent: Uranium amount: 0.05 @@ -1664,6 +1916,14 @@ metamorphicMaxFillLevels: 4 metamorphicFillBaseName: fill- metamorphicChangeColor: false + metabolisms: + Drink: + effects: + - !type:SatiateThirst + factor: 2 + - !type:AdjustReagent + reagent: Ethanol + amount: 0.112 - type: reagent id: VodkaMartini @@ -1686,7 +1946,7 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.15 + amount: 0.32 - type: reagent id: VodkaTonic @@ -1709,7 +1969,8 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.07 + amount: 0.13 + fizziness: 0.4 - type: reagent id: WhiskeyCola @@ -1732,7 +1993,8 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.07 + amount: 0.013 + fizziness: 0.3 - type: reagent id: WhiskeySoda @@ -1755,7 +2017,8 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.07 + amount: 0.13 + fizziness: 0.4 - type: reagent id: WhiteGilgamesh @@ -1772,7 +2035,8 @@ factor: 1 - !type:AdjustReagent reagent: Ethanol - amount: 0.15 + amount: 0.04 + fizziness: 0.5 - type: reagent id: WhiteRussian @@ -1795,4 +2059,166 @@ factor: 2 - !type:AdjustReagent reagent: Ethanol - amount: 0.15 + amount: 0.33 + +- type: reagent + id: VodkaRedBool + name: reagent-name-vodka-red-bool + parent: BaseAlcohol + desc: reagent-desc-vodka-red-bool + physicalDesc: reagent-physical-desc-strong-smelling + flavor: vodkaredbool + color: "#c4c27655" + metamorphicSprite: + sprite: Objects/Consumable/Drinks/ginvodkaglass.rsi + state: icon_empty + metamorphicMaxFillLevels: 4 + metamorphicFillBaseName: fill- + metamorphicChangeColor: true + metabolisms: + Drink: + effects: + - !type:SatiateThirst + factor: 1 + - !type:AdjustReagent + reagent: Ethanol + amount: 0.10 + - !type:AdjustReagent + reagent: Theobromine + amount: 0.05 + fizziness: 0.25 + +- type: reagent + id: XenoBasher + name: reagent-name-xeno-basher + parent: BaseAlcohol + desc: reagent-desc-xeno-basher + physicalDesc: reagent-physical-desc-fizzy-and-creamy + flavor: xenobasher + color: "#4d6600" + metamorphicSprite: + sprite: Objects/Consumable/Drinks/xenobasher.rsi + state: icon_empty + metamorphicMaxFillLevels: 2 + metamorphicFillBaseName: fill- + metamorphicChangeColor: false + metabolisms: + Drink: + effects: + - !type:SatiateThirst + factor: 1 + - !type:AdjustReagent + reagent: Ethanol + amount: 0.15 + - !type:AdjustReagent + reagent: Theobromine + amount: 0.05 + fizziness: 0.15 + +- type: reagent + id: IrishBool + name: reagent-name-irish-bool + parent: BaseAlcohol + desc: reagent-desc-irish-bool + physicalDesc: reagent-physical-desc-bubbly + flavor: irishbool + color: "#71672e99" + metamorphicSprite: + sprite: Objects/Consumable/Drinks/beerglass.rsi + state: icon_empty + metamorphicMaxFillLevels: 6 + metamorphicFillBaseName: fill- + metamorphicChangeColor: true + metabolisms: + Drink: + effects: + - !type:SatiateThirst + factor: 1 + - !type:AdjustReagent + reagent: Ethanol + amount: 0.10 + - !type:AdjustReagent + reagent: Theobromine + amount: 0.05 + fizziness: 0.15 + +- type: reagent + id: BudgetInsulsDrink + name: reagent-name-budget-insuls + parent: BaseAlcohol + desc: reagent-desc-budget-insuls + physicalDesc: reagent-physical-desc-strong-smelling + flavor: budgetinsulsdrink + color: "#dede73" + metamorphicSprite: + sprite: Objects/Consumable/Drinks/budgetinsulsdrink.rsi + state: icon_empty + metamorphicMaxFillLevels: 3 + metamorphicFillBaseName: fill- + metamorphicChangeColor: false + metabolisms: + Drink: + effects: + - !type:SatiateThirst + factor: 1 + - !type:AdjustReagent + reagent: Ethanol + amount: 0.15 + - !type:AdjustReagent + reagent: Theobromine + amount: 0.05 + fizziness: 0.25 + +- type: reagent + id: WatermelonWakeup + name: reagent-name-watermelon-wakeup + parent: BaseAlcohol + desc: reagent-desc-watermelon-wakeup + physicalDesc: reagent-physical-desc-sweet + flavor: watermelonwakeup + color: "#d49dca" + metamorphicSprite: + sprite: Objects/Consumable/Drinks/champagneglass.rsi + state: icon_empty + metamorphicMaxFillLevels: 4 + metamorphicFillBaseName: fill- + metamorphicChangeColor: true + metabolisms: + Drink: + effects: + - !type:SatiateThirst + factor: 1 + - !type:AdjustReagent + reagent: Ethanol + amount: 0.07 + - !type:AdjustReagent + reagent: Theobromine + amount: 0.05 + fizziness: 0.15 + +- type: reagent + id: Rubberneck + name: reagent-name-rubberneck + parent: BaseAlcohol + desc: reagent-desc-rubberneck + physicalDesc: reagent-physical-desc-strong-smelling + flavor: rubberneck + color: "#f0d74a" + metamorphicSprite: + sprite: Objects/Consumable/Drinks/rubberneck.rsi + state: icon_empty + metamorphicMaxFillLevels: 3 + metamorphicFillBaseName: fill- + metamorphicChangeColor: false + metabolisms: + Drink: + effects: + - !type:SatiateThirst + factor: 1 + - !type:AdjustReagent + reagent: Ethanol + amount: 0.15 + - !type:AdjustReagent + reagent: Theobromine + amount: 0.05 + fizziness: 0.25 diff --git a/Resources/Prototypes/Reagents/Consumable/Drink/base_drink.yml b/Resources/Prototypes/Reagents/Consumable/Drink/base_drink.yml index 9984b4c0cf..19a5e1bf8f 100644 --- a/Resources/Prototypes/Reagents/Consumable/Drink/base_drink.yml +++ b/Resources/Prototypes/Reagents/Consumable/Drink/base_drink.yml @@ -40,6 +40,7 @@ collection: FootstepSticky params: volume: 6 + fizziness: 0.5 - type: reagent id: BaseAlcohol @@ -75,4 +76,4 @@ footstepSound: collection: FootstepSticky params: - volume: 6 \ No newline at end of file + volume: 6 diff --git a/Resources/Prototypes/Reagents/Consumable/Drink/drinks.yml b/Resources/Prototypes/Reagents/Consumable/Drink/drinks.yml index 5c09b3c909..52a01d973f 100644 --- a/Resources/Prototypes/Reagents/Consumable/Drink/drinks.yml +++ b/Resources/Prototypes/Reagents/Consumable/Drink/drinks.yml @@ -15,6 +15,12 @@ - !type:AdjustReagent reagent: Theobromine amount: 0.05 + metamorphicSprite: + sprite: Objects/Consumable/Drinks/coffeeglass.rsi + state: icon_empty + metamorphicMaxFillLevels: 4 + metamorphicFillBaseName: fill- + metamorphicChangeColor: false - type: reagent id: HotCocoa @@ -100,9 +106,9 @@ flavor: tea color: "#7EB626" metamorphicSprite: - sprite: Objects/Consumable/Drinks/glass_green.rsi + sprite: Objects/Consumable/Drinks/greenteaglass.rsi state: icon_empty - metamorphicMaxFillLevels: 5 + metamorphicMaxFillLevels: 4 metamorphicFillBaseName: fill- metamorphicChangeColor: false @@ -149,7 +155,7 @@ flavor: icedtea color: "#5B821B" metamorphicSprite: - sprite: Objects/Consumable/Drinks/glass_green.rsi + sprite: Objects/Consumable/Drinks/icedgreenteaglass.rsi state: icon_empty metamorphicMaxFillLevels: 5 metamorphicFillBaseName: fill- @@ -322,6 +328,7 @@ damage: types: Poison: 1 + fizziness: 0.5 - type: reagent id: SodaWater @@ -331,6 +338,7 @@ physicalDesc: reagent-physical-desc-fizzy flavor: fizzy color: "#619494" + fizziness: 0.8 - type: reagent id: SoyLatte @@ -364,6 +372,12 @@ - !type:AdjustReagent reagent: Theobromine amount: 0.05 + metamorphicSprite: + sprite: Objects/Consumable/Drinks/teaglass.rsi + state: icon_empty + metamorphicMaxFillLevels: 4 + metamorphicFillBaseName: fill- + metamorphicChangeColor: false - type: reagent id: TonicWater @@ -373,6 +387,13 @@ physicalDesc: reagent-physical-desc-fizzy flavor: tonicwater color: "#0064C8" + fizziness: 0.4 + metamorphicSprite: + sprite: Objects/Consumable/Drinks/tonicglass.rsi + state: icon_empty + metamorphicMaxFillLevels: 5 + metamorphicFillBaseName: fill- + metamorphicChangeColor: false - type: reagent id: Water @@ -405,6 +426,12 @@ plantMetabolism: - !type:PlantAdjustWater amount: 1 + metamorphicSprite: + sprite: Objects/Consumable/Drinks/iceglass.rsi + state: icon_empty + metamorphicMaxFillLevels: 3 + metamorphicFillBaseName: fill- + metamorphicChangeColor: false - type: reagent id: DryRamen @@ -467,6 +494,7 @@ effects: - !type:SatiateThirst factor: 1 + fizziness: 0.3 - type: reagent id: Posca @@ -491,6 +519,7 @@ metamorphicMaxFillLevels: 3 metamorphicFillBaseName: fill- metamorphicChangeColor: false + fizziness: 0.3 - type: reagent id: Rewriter @@ -506,6 +535,7 @@ metamorphicMaxFillLevels: 5 metamorphicFillBaseName: fill- metamorphicChangeColor: true + fizziness: 0.3 - type: reagent id: Mopwata diff --git a/Resources/Prototypes/Reagents/Consumable/Drink/juice.yml b/Resources/Prototypes/Reagents/Consumable/Drink/juice.yml index ee1492b45e..c42791fa8f 100644 --- a/Resources/Prototypes/Reagents/Consumable/Drink/juice.yml +++ b/Resources/Prototypes/Reagents/Consumable/Drink/juice.yml @@ -63,6 +63,12 @@ physicalDesc: reagent-physical-desc-citric flavor: sour color: "#fff690" + metamorphicSprite: + sprite: Objects/Consumable/Drinks/lemonjuiceglass.rsi + state: icon_empty + metamorphicMaxFillLevels: 5 + metamorphicFillBaseName: fill- + metamorphicChangeColor: false - type: reagent id: JuiceLime @@ -89,6 +95,12 @@ physicalDesc: reagent-physical-desc-citric flavor: orange color: "#E78108" + metamorphicSprite: + sprite: Objects/Consumable/Drinks/orangejuiceglass.rsi + state: icon_empty + metamorphicMaxFillLevels: 5 + metamorphicFillBaseName: fill- + metamorphicChangeColor: false - type: reagent id: JuicePineapple @@ -125,3 +137,9 @@ physicalDesc: reagent-physical-desc-sweet flavor: watermelon color: "#EF3520" + metamorphicSprite: + sprite: Objects/Consumable/Drinks/watermelonglass.rsi + state: icon_empty + metamorphicMaxFillLevels: 4 + metamorphicFillBaseName: fill- + metamorphicChangeColor: false diff --git a/Resources/Prototypes/Reagents/Consumable/Drink/soda.yml b/Resources/Prototypes/Reagents/Consumable/Drink/soda.yml index ba5adc4f2a..d78b0351ce 100644 --- a/Resources/Prototypes/Reagents/Consumable/Drink/soda.yml +++ b/Resources/Prototypes/Reagents/Consumable/Drink/soda.yml @@ -7,6 +7,12 @@ flavor: soda color: "#6c2828" recognizable: true + metamorphicSprite: + sprite: Objects/Consumable/Drinks/colaglass.rsi + state: icon_empty + metamorphicMaxFillLevels: 5 + metamorphicFillBaseName: fill- + metamorphicChangeColor: false - type: reagent id: RoyRogers @@ -42,6 +48,12 @@ physicalDesc: reagent-physical-desc-fizzy flavor: drgibb color: "#102000" + metamorphicSprite: + sprite: Objects/Consumable/Drinks/dr_gibb_glass.rsi + state: icon_empty + metamorphicMaxFillLevels: 5 + metamorphicFillBaseName: fill- + metamorphicChangeColor: false - type: reagent id: EnergyDrink @@ -58,7 +70,8 @@ factor: 2 - !type:AdjustReagent reagent: Theobromine - amount: 0.05 + amount: 0.1 + fizziness: 0.4 - type: reagent id: GrapeSoda @@ -84,6 +97,7 @@ metamorphicMaxFillLevels: 5 metamorphicFillBaseName: fill- metamorphicChangeColor: true + fizziness: 0 - type: reagent id: LemonLime @@ -102,6 +116,7 @@ physicalDesc: reagent-physical-desc-fizzy flavor: pwrgamesoda color: "#9385bf" + fizziness: 0.9 # gamers crave the fizz - type: reagent id: RootBeer @@ -132,6 +147,7 @@ metamorphicMaxFillLevels: 7 metamorphicFillBaseName: fill- metamorphicChangeColor: false + fizziness: 0.4 - type: reagent id: SolDry @@ -170,6 +186,12 @@ physicalDesc: reagent-physical-desc-fizzy flavor: sodacitrus color: "#a6fa5a" + metamorphicSprite: + sprite: Objects/Consumable/Drinks/space_mountain_wind_glass.rsi + state: icon_empty + metamorphicMaxFillLevels: 5 + metamorphicFillBaseName: fill- + metamorphicChangeColor: false - type: reagent id: SpaceUp @@ -179,6 +201,12 @@ physicalDesc: reagent-physical-desc-fizzy flavor: spaceup color: "#e3e3e37d" + metamorphicSprite: + sprite: Objects/Consumable/Drinks/space-up_glass.rsi + state: icon_empty + metamorphicMaxFillLevels: 6 + metamorphicFillBaseName: fill- + metamorphicChangeColor: false - type: reagent id: Starkist diff --git a/Resources/Prototypes/Reagents/Consumable/Food/food.yml b/Resources/Prototypes/Reagents/Consumable/Food/food.yml index 03ebf7cc32..c9625c663c 100644 --- a/Resources/Prototypes/Reagents/Consumable/Food/food.yml +++ b/Resources/Prototypes/Reagents/Consumable/Food/food.yml @@ -93,6 +93,12 @@ amount: 2 - !type:PlantAdjustPests amount: 2 + metamorphicSprite: + sprite: Objects/Consumable/Drinks/sugarglass.rsi + state: icon_empty + metamorphicMaxFillLevels: 7 + metamorphicFillBaseName: fill- + metamorphicChangeColor: false - type: reagent id: PumpkinFlesh #Just so pumpkins spill orange stuff when smashed @@ -100,4 +106,4 @@ name: reagent-name-pumpkin-flesh desc: reagent-desc-pumpkin-flesh flavor: pumpkin - color: "#fc9300" \ No newline at end of file + color: "#fc9300" diff --git a/Resources/Prototypes/Reagents/Materials/materials.yml b/Resources/Prototypes/Reagents/Materials/materials.yml index fa51608a22..f56a712cd8 100644 --- a/Resources/Prototypes/Reagents/Materials/materials.yml +++ b/Resources/Prototypes/Reagents/Materials/materials.yml @@ -116,3 +116,21 @@ icon: { sprite: Objects/Materials/ore.rsi, state: coal } color: "#404040" price: 0 + +- type: material + id: Bluespace + stackEntity: MaterialBluespace1 + name: materials-bluespace + unit: materials-unit-piece + icon: { sprite: Objects/Materials/materials.rsi, state: bluespace } + color: "#53a9ff" + price: 7.5 + +- type: material + id: Normality + stackEntity: MaterialNormality1 + name: materials-normality + unit: materials-unit-piece + icon: { sprite: Objects/Materials/materials.rsi, state: normality } + color: "#53a9ff" + price: 7.5 diff --git a/Resources/Prototypes/Reagents/Materials/ores.yml b/Resources/Prototypes/Reagents/Materials/ores.yml index 18f1d9ebb3..7b887b2f43 100644 --- a/Resources/Prototypes/Reagents/Materials/ores.yml +++ b/Resources/Prototypes/Reagents/Materials/ores.yml @@ -67,4 +67,22 @@ unit: materials-unit-chunk icon: { sprite: Objects/Materials/ore.rsi, state: salt } color: "#f5e7d7" - price: 0.075 \ No newline at end of file + price: 0.075 + +- type: material + id: RawBluespace + stackEntity: BluespaceOre1 + name: materials-raw-bluespace + unit: materials-unit-piece + icon: { sprite: Objects/Materials/ore.rsi, state: bluespace } + color: "#53a9ff" + price: 7.5 + +- type: material + id: RawNormality + stackEntity: NormalityOre1 + name: materials-raw-normality + unit: materials-unit-piece + icon: { sprite: Objects/Materials/ore.rsi, state: normality } + color: "#53a9ff" + price: 7.5 diff --git a/Resources/Prototypes/Reagents/biological.yml b/Resources/Prototypes/Reagents/biological.yml index 5c0cef314c..4c1341ea7d 100644 --- a/Resources/Prototypes/Reagents/biological.yml +++ b/Resources/Prototypes/Reagents/biological.yml @@ -18,7 +18,7 @@ Drink: effects: - !type:SatiateThirst - factor: 1.5 + factor: 0.5 conditions: - !type:OrganType type: Human @@ -39,7 +39,7 @@ - !type:OrganType type: Vampiric reagent: Protein - amount: 0.15 + amount: 0.125 # See below - !type:AdjustReagent conditions: - !type:OrganType @@ -50,7 +50,11 @@ effects: - !type:AdjustReagent reagent: UncookedAnimalProteins - amount: 0.5 + amount: 0.125 # 0.25 proteins for 1u of blood - restores 0.75 hunger, adds 0.25 blood per unit + conditions: + - !type:OrganType + type: Vampiric + shouldHave: false Medicine: effects: - !type:HealthChange @@ -99,7 +103,7 @@ # Delicious! effects: - !type:SatiateHunger - factor: 1.5 + factor: 1 footstepSound: collection: FootstepBlood params: @@ -123,9 +127,9 @@ # Sweet! effects: - !type:SatiateHunger - factor: 1 + factor: 0.5 - !type:SatiateThirst - factor: 1 + factor: 0.5 footstepSound: collection: FootstepBlood params: diff --git a/Resources/Prototypes/Reagents/botany.yml b/Resources/Prototypes/Reagents/botany.yml index a03c3826a4..cdd19dc308 100644 --- a/Resources/Prototypes/Reagents/botany.yml +++ b/Resources/Prototypes/Reagents/botany.yml @@ -222,6 +222,9 @@ - !type:OrganType type: Rat shouldHave: false + - !type:OrganType + type: Vox + shouldHave: false - !type:ReagentThreshold reagent: Ammonia min: 0.8 @@ -247,6 +250,13 @@ groups: Brute: -5 Burn: -5 + types: + Bloodloss: -5 + - !type:Oxygenate # ammonia displaces nitrogen in vox blood + conditions: + - !type:OrganType + type: Vox + factor: -4 - type: reagent diff --git a/Resources/Prototypes/Reagents/gases.yml b/Resources/Prototypes/Reagents/gases.yml index 9cb73fffb8..298bcb923e 100644 --- a/Resources/Prototypes/Reagents/gases.yml +++ b/Resources/Prototypes/Reagents/gases.yml @@ -35,6 +35,25 @@ ratios: CarbonDioxide: 1.0 Oxygen: -1.0 + - !type:HealthChange + conditions: + - !type:OrganType + type: Vox + scaleByQuantity: true + ignoreResistances: true + damage: + types: + Poison: + 7 + - !type:AdjustAlert + alertType: Toxins + conditions: + - !type:ReagentThreshold + min: 0.5 + - !type:OrganType + type: Vox + clear: true + time: 5 - type: reagent id: Plasma @@ -142,6 +161,9 @@ - !type:OrganType type: Plant shouldHave: false + - !type:OrganType + type: Vox + shouldHave: false # Don't want people to get toxin damage from the gas they just # exhaled, right? - !type:ReagentThreshold @@ -194,7 +216,7 @@ - !type:OrganType type: Vox ratios: - CarbonDioxide: 1.0 + Ammonia: 1.0 Nitrogen: -1.0 - !type:ModifyLungGas conditions: @@ -221,23 +243,13 @@ - !type:ReagentThreshold reagent: NitrousOxide min: 0.2 + max: 0.5 - !type:OrganType type: Slime shouldHave: false emote: Laugh showInChat: true probability: 0.1 - - !type:Emote - conditions: - - !type:ReagentThreshold - reagent: NitrousOxide - min: 0.2 - - !type:OrganType - type: Slime - shouldHave: false - emote: Scream - showInChat: true - probability: 0.01 - !type:PopupMessage conditions: - !type:ReagentThreshold @@ -264,13 +276,13 @@ conditions: - !type:ReagentThreshold reagent: NitrousOxide - min: 1.8 + min: 1 - !type:OrganType type: Slime shouldHave: false key: ForcedSleep component: ForcedSleeping - time: 3 + time: 200 # This reeks, but I guess it works LMAO type: Add - !type:HealthChange conditions: diff --git a/Resources/Prototypes/Reagents/medicine.yml b/Resources/Prototypes/Reagents/medicine.yml index dfe96359e6..6b441711e1 100644 --- a/Resources/Prototypes/Reagents/medicine.yml +++ b/Resources/Prototypes/Reagents/medicine.yml @@ -11,16 +11,20 @@ metabolismRate: 0.1 effects: - !type:GenericStatusEffect - key: PsionicallyInsulated #Nyano - Summary: makes the user psionically insulated from effects. - component: PsionicInsulation #Nyano - Summary: see above. + key: PsionicallyInsulated + component: PsionicInsulation type: Add + time: 900 - !type:GenericStatusEffect key: Stutter component: ScrambledAccent + type: Add + time: 900 - !type:GenericStatusEffect - key: PsionicsDisabled #Nyano - Summary: disables psinoics from being used by the wearer. - component: PsionicsDisabled #Nyano - Summary: see above. + key: PsionicsDisabled + component: PsionicsDisabled type: Add + time: 900 - !type:Drunk slurSpeech: false boozePower: 20 @@ -1121,6 +1125,27 @@ - !type:ReagentThreshold min: 12 +- type: reagent + id: Opporozidone #Name based of an altered version of the startreck chem "Opporozine" + name: reagent-name-opporozidone + group: Medicine + desc: reagent-desc-opporozidone + physicalDesc: reagent-physical-desc-sickly + flavor: acid + color: "#b5e36d" + worksOnTheDead: true + metabolisms: + Medicine: + effects: + - !type:ReduceRotting + seconds: 20 + conditions: + #Patient must be dead and in a cryo tube (or something cold) + - !type:Temperature + max: 150.0 + - !type:MobStateCondition + mobstate: Dead + - type: reagent id: Necrosol name: reagent-name-necrosol @@ -1171,3 +1196,67 @@ Heat: -3.0 Shock: -3.0 Caustic: -1.0 + +- type: reagent + id : Mannitol # currently this is just a way to create psicodine + name: reagent-name-mannitol + group: Medicine + desc: reagent-desc-mannitol + physicalDesc: reagent-physical-desc-opaque + flavor: sweet + color: "#A0A0A0" + metabolisms: + Medicine: + effects: + - !type:PopupMessage + conditions: + - !type:ReagentThreshold + min: 15 + type: Local + visualType: Medium + messages: [ "mannitol-effect-enlightened" ] + probability: 0.2 + +- type: reagent + id: Psicodine + name: reagent-name-psicodine + group: Medicine + desc: reagent-desc-psicodine + physicalDesc: reagent-physical-desc-shiny + flavor: bitter + color: "#07E79E" + metabolisms: + Medicine: + effects: + - !type:HealthChange + conditions: + - !type:ReagentThreshold + min: 30 + damage: + types: + Poison: 2 + - !type:GenericStatusEffect + conditions: + - !type:ReagentThreshold + min: 30 + key: SeeingRainbows + component: SeeingRainbows + type: Add + time: 8 + refresh: false + - !type:GenericStatusEffect + key: Jitter + time: 2.0 + type: Remove + - !type:GenericStatusEffect + key: Drunk + time: 6.0 + type: Remove + - !type:PopupMessage # we dont have sanity/mood so this will have to do + type: Local + visualType: Medium + messages: + - "psicodine-effect-fearless" + - "psicodine-effect-anxieties-wash-away" + - "psicodine-effect-at-peace" + probability: 0.2 diff --git a/Resources/Prototypes/Reagents/narcotics.yml b/Resources/Prototypes/Reagents/narcotics.yml index 2ab323c309..ebb8b0fc43 100644 --- a/Resources/Prototypes/Reagents/narcotics.yml +++ b/Resources/Prototypes/Reagents/narcotics.yml @@ -182,25 +182,6 @@ time: 16 refresh: false -- type: reagent - id: THCOil # deprecated in favor of THC, preserved here for forks - name: reagent-name-thc-oil - group: Narcotics - desc: reagent-desc-thc-oil - physicalDesc: reagent-physical-desc-skunky - flavor: bitter - flavorMinimum: 0.05 - color: "#DAA520" - metabolisms: - Narcotic: - effects: - - !type:GenericStatusEffect - key: SeeingRainbows - component: SeeingRainbows - type: Add - time: 16 - refresh: false - - type: reagent id: Nicotine name: reagent-name-nicotine @@ -212,6 +193,11 @@ plantMetabolism: - !type:PlantAdjustHealth amount: -5 + metabolisms: + Narcotic: + effects: + - !type:ChemAddMoodlet + moodPrototype: NicotineBenefit # TODO: Replace these nonstandardized effects with generic brain damage - type: reagent @@ -260,11 +246,17 @@ type: Add time: 5 refresh: false - - !type:ChemRerollPsionic #Nyano - Summary: lets the imbiber become psionic. + - !type:ChemRerollPsionic #Nyano - Summary: lets the imbiber become psionic. conditions: - !type:ReagentThreshold reagent: SpaceDrugs min: 15 + - !type:ChemAddMoodlet + moodPrototype: SpaceDrugsBenefit + conditions: + - !type:ReagentThreshold + reagent: SpaceDrugs + min: 5 - type: reagent id: Bananadine @@ -412,3 +404,53 @@ conditions: - !type:ReagentThreshold min: 20 + +- type: reagent + id: Happiness + name: reagent-name-happiness + group: Narcotics + desc: reagent-desc-happiness + physicalDesc: reagent-physical-desc-soothing + flavor: paintthinner + color: "#EE35FF" + metabolisms: + Narcotic: + effects: + - !type:Emote + emote: Laugh + showInChat: true + probability: 0.1 + conditions: + - !type:ReagentThreshold + max: 20 + - !type:Emote + emote: Whistle + showInChat: true + probability: 0.1 + conditions: + - !type:ReagentThreshold + max: 20 + - !type:Emote + emote: Crying + showInChat: true + probability: 0.1 + conditions: + - !type:ReagentThreshold + min: 20 + - !type:PopupMessage # we dont have sanity/mood so this will have to do + type: Local + visualType: Medium + messages: + - "psicodine-effect-fearless" + - "psicodine-effect-anxieties-wash-away" + - "psicodine-effect-at-peace" + probability: 0.2 + conditions: + - !type:ReagentThreshold + max: 20 + - !type:GenericStatusEffect + key: SeeingRainbows + component: SeeingRainbows + type: Add + time: 5 + refresh: false diff --git a/Resources/Prototypes/Reagents/psionic.yml b/Resources/Prototypes/Reagents/psionic.yml index 3e8415fc0e..f17a84047a 100644 --- a/Resources/Prototypes/Reagents/psionic.yml +++ b/Resources/Prototypes/Reagents/psionic.yml @@ -92,6 +92,12 @@ - !type:GenericStatusEffect key: SlurredSpeech component: TelepathicRepeater + - !type:ChemAddMoodlet + moodPrototype: LotoTranscendence + conditions: + - !type:ReagentThreshold + reagent: LotophagoiOil + min: 5 - type: reagent id: Ectoplasm diff --git a/Resources/Prototypes/Reagents/toxins.yml b/Resources/Prototypes/Reagents/toxins.yml index 661e1b7dd1..abb33f832a 100644 --- a/Resources/Prototypes/Reagents/toxins.yml +++ b/Resources/Prototypes/Reagents/toxins.yml @@ -486,7 +486,6 @@ - !type:OrganType type: Animal shouldHave: false - reagent: Protein type: Local visualType: MediumCaution messages: [ "generic-reagent-effect-sick" ] @@ -512,6 +511,12 @@ shouldHave: true reagent: Protein amount: 0.5 + - !type:AdjustReagent + conditions: + - !type:OrganType + type: Shadowkin + reagent: Protein + amount: 0.5 - type: reagent id: Allicin @@ -647,3 +652,23 @@ - !type:Electrocute probability: 0.8 +- type: reagent + id: Lipolicide + name: reagent-name-lipolicide + group: Toxins + desc: reagent-desc-lipolicide + physicalDesc: reagent-physical-desc-strong-smelling + flavor: mothballs #why does weightloss juice taste like mothballs + color: "#F0FFF0" + metabolisms: + Poison: + effects: + - !type:HealthChange + conditions: + - !type:Hunger + max: 50 + damage: + types: + Poison: 2 + - !type:SatiateHunger + factor: -6 diff --git a/Resources/Prototypes/DeltaV/Recipes/Construction/Graphs/clothing/prescription_huds.yml b/Resources/Prototypes/Recipes/Construction/Graphs/clothing/prescriptionhuds.yml similarity index 100% rename from Resources/Prototypes/DeltaV/Recipes/Construction/Graphs/clothing/prescription_huds.yml rename to Resources/Prototypes/Recipes/Construction/Graphs/clothing/prescriptionhuds.yml diff --git a/Resources/Prototypes/Recipes/Construction/Graphs/clothing/quiver.yml b/Resources/Prototypes/Recipes/Construction/Graphs/clothing/quiver.yml new file mode 100644 index 0000000000..10736a5876 --- /dev/null +++ b/Resources/Prototypes/Recipes/Construction/Graphs/clothing/quiver.yml @@ -0,0 +1,19 @@ +- type: constructionGraph + id: Quiver + start: start + graph: + - node: start + edges: + - to: Quiver + steps: + - material: Cable + amount: 2 + doAfter: 1 + - material: Cloth + amount: 5 + doAfter: 2 + - material: WoodPlank + amount: 1 + doAfter: 2 + - node: Quiver + entity: ClothingBeltQuiver diff --git a/Resources/Prototypes/Recipes/Construction/Graphs/structures/glassbox.yml b/Resources/Prototypes/Recipes/Construction/Graphs/structures/glassbox.yml new file mode 100644 index 0000000000..081f22ea8d --- /dev/null +++ b/Resources/Prototypes/Recipes/Construction/Graphs/structures/glassbox.yml @@ -0,0 +1,160 @@ +- type: constructionGraph + id: GlassBox + start: start + graph: + - node: start + actions: + - !type:DeleteEntity + edges: + - to: boxMissingWires + completed: + - !type:SetAnchor + value: false + steps: + - material: WoodPlank + amount: 10 + doAfter: 5 + + - node: boxMissingWires + entity: GlassBoxFrame + edges: + - to: boxMissingTrigger + conditions: + - !type:EntityAnchored + steps: + - material: Cable + amount: 2 + doAfter: 0.5 + + - to: start + steps: + - tool: Prying + doAfter: 5 + completed: + - !type:SpawnPrototype + prototype: MaterialWoodPlank1 + amount: 10 + + - node: boxMissingTrigger + edges: + - to: boxTriggerUnsecured + conditions: + - !type:EntityAnchored + steps: + - tag: SignalTrigger + name: a Signal Trigger + icon: + sprite: Objects/Devices/signaltrigger.rsi + state: signaltrigger + doAfter: 0.5 + + - to: boxMissingWires + conditions: + - !type:EntityAnchored + steps: + - tool: Cutting + doAfter: 0.25 + completed: + - !type:SpawnPrototype + prototype: CableApcStack1 + amount: 2 + + - node: boxTriggerUnsecured + edges: + - to: boxMissingRGlass + conditions: + - !type:EntityAnchored + steps: + - tool: Screwing + doAfter: 0.5 + + - to: boxMissingTrigger + conditions: + - !type:EntityAnchored + steps: + - tool: Prying + doAfter: 0.5 + completed: + - !type:SpawnPrototype + prototype: SignalTrigger + amount: 1 + + - node: boxMissingRGlass + edges: + - to: boxRGlassUnsecured + conditions: + - !type:EntityAnchored + steps: + - material: ReinforcedGlass + amount: 5 + doAfter: 2.5 + + - to: boxTriggerUnsecured + conditions: + - !type:EntityAnchored + steps: + - tool: Screwing + doAfter: 0.5 + + - node: boxRGlassUnsecured + edges: + - to: glassBox + conditions: + - !type:EntityAnchored + steps: + - tool: Screwing + doAfter: 0.5 + + - to: boxMissingRGlass + conditions: + - !type:EntityAnchored + steps: + - tool: Prying + doAfter: 2 + completed: + - !type:SpawnPrototype + prototype: SheetRGlass1 + amount: 5 + + - node: brokenGlassBox + entity: GlassBoxBroken + edges: + - to: boxMissingWires + steps: + - tool: Prying + doAfter: 2 + completed: + - !type:SpawnPrototype + prototype: ShardGlassReinforced + amount: 1 + + - node: glassBox + entity: GlassBoxLaser + edges: + - to: boxMissingWires + steps: + - tool: Screwing + doAfter: 4 + - tool: Pulsing + doAfter: 2 + - tool: Cutting + doAfter: 2 + - tool: Screwing + doAfter: 2 + - tool: Welding + doAfter: 10 + - tool: Anchoring + doAfter: 2 + - tool: Prying + doAfter: 2 + completed: + - !type:EmptyAllContainers + - !type:SpawnPrototype + prototype: CableApcStack1 + amount: 2 + - !type:SpawnPrototype + prototype: SignalTrigger + amount: 1 + - !type:SpawnPrototype + prototype: SheetRGlass1 + amount: 5 diff --git a/Resources/Prototypes/Recipes/Construction/Graphs/structures/plastic_flaps.yml b/Resources/Prototypes/Recipes/Construction/Graphs/structures/plastic_flaps.yml index 776c1491a6..781dd4aa87 100644 --- a/Resources/Prototypes/Recipes/Construction/Graphs/structures/plastic_flaps.yml +++ b/Resources/Prototypes/Recipes/Construction/Graphs/structures/plastic_flaps.yml @@ -33,16 +33,6 @@ - tool: Welding doAfter: 5 - - to: airtightFlaps - completed: - - !type:SnapToGrid { } - steps: - - material: Plastic - amount: 5 - doAfter: 5 - - tool: Screwing - doAfter: 5 - - node: opaqueFlaps entity: PlasticFlapsOpaque edges: @@ -54,44 +44,3 @@ steps: - tool: Anchoring doAfter: 10 - - - to: airtightopaqueFlaps - completed: - - !type:SnapToGrid { } - steps: - - material: Plastic - amount: 5 - doAfter: 5 - - tool: Screwing - doAfter: 5 - - - node: airtightFlaps - entity: PlasticFlapsAirtightClear - edges: - - to: plasticFlaps - completed: - - !type:SpawnPrototype - prototype: SheetPlastic - amount: 5 - steps: - - tool: Screwing - doAfter: 10 - - - to: airtightopaqueFlaps #test - completed: - - !type:SnapToGrid { } - steps: - - tool: Welding - doAfter: 5 - - - node: airtightopaqueFlaps - entity: PlasticFlapsAirtightOpaque - edges: - - to: opaqueFlaps - completed: - - !type:SpawnPrototype - prototype: SheetPlastic - amount: 5 - steps: - - tool: Screwing - doAfter: 10 diff --git a/Resources/Prototypes/Recipes/Construction/Graphs/structures/windowdirectional.yml b/Resources/Prototypes/Recipes/Construction/Graphs/structures/windowdirectional.yml index 96f009fabb..effddd7ca3 100644 --- a/Resources/Prototypes/Recipes/Construction/Graphs/structures/windowdirectional.yml +++ b/Resources/Prototypes/Recipes/Construction/Graphs/structures/windowdirectional.yml @@ -28,6 +28,7 @@ - material: ReinforcedPlasmaGlass amount: 1 doAfter: 3 + - to: uraniumWindowDirectional steps: - material: UraniumGlass @@ -136,6 +137,7 @@ doAfter: 2 - tool: Anchoring doAfter: 3 + - node: uraniumWindowDirectional entity: UraniumWindowDirectional edges: diff --git a/Resources/Prototypes/Recipes/Construction/clothing.yml b/Resources/Prototypes/Recipes/Construction/clothing.yml index 54218d2822..ba0c0d6c59 100644 --- a/Resources/Prototypes/Recipes/Construction/clothing.yml +++ b/Resources/Prototypes/Recipes/Construction/clothing.yml @@ -107,3 +107,14 @@ description: A roll of treated canvas used for wrapping claws or paws. icon: { sprite: Clothing/Shoes/Misc/clothWrap.rsi, state: icon } objectType: Item + +- type: construction + name: quiver + id: ClothingBeltQuiver + graph: Quiver + startNode: start + targetNode: Quiver + category: construction-category-clothing + description: Can hold up to 15 arrows, and fits snug around your waist. + icon: { sprite: Clothing/Belt/quiver.rsi, state: icon } + objectType: Item diff --git a/Resources/Prototypes/Recipes/Construction/storage.yml b/Resources/Prototypes/Recipes/Construction/storage.yml index 41abf881b6..c8edebc509 100644 --- a/Resources/Prototypes/Recipes/Construction/storage.yml +++ b/Resources/Prototypes/Recipes/Construction/storage.yml @@ -49,3 +49,21 @@ canBuildInImpassable: false conditions: - !type:TileNotBlocked + +# ItemCabinets +- type: construction + id: ShowCase + name: showcase + description: A sturdy showcase for an expensive exhibit. + graph: GlassBox + startNode: start + targetNode: glassBox + category: construction-category-storage + icon: + sprite: Structures/Storage/glassbox.rsi + state: icon + objectType: Structure + placementMode: SnapgridCenter + canBuildInImpassable: false + conditions: + - !type:TileNotBlocked diff --git a/Resources/Prototypes/Recipes/Construction/structures.yml b/Resources/Prototypes/Recipes/Construction/structures.yml index b152e827e6..a80ac9a3eb 100644 --- a/Resources/Prototypes/Recipes/Construction/structures.yml +++ b/Resources/Prototypes/Recipes/Construction/structures.yml @@ -688,6 +688,42 @@ objectType: Structure placementMode: SnapgridCenter +- type: construction + name: directional uranium window + id: UraniumWindowDirectional + graph: WindowDirectional + startNode: start + targetNode: uraniumWindowDirectional + category: construction-category-structures + canBuildInImpassable: true + description: Clear and tougher than regular glass, with added RadAbsorb to protect you from deadly radiation. + conditions: + - !type:EmptyOrWindowValidInTile + - !type:NoWindowsInTile + icon: + sprite: Structures/Windows/directional.rsi + state: uranium_window + objectType: Structure + placementMode: SnapgridCenter + +- type: construction + name: directional reinforced uranium window + id: UraniumReinforcedWindowDirectional + graph: WindowDirectional + startNode: start + targetNode: uraniumReinforcedWindowDirectional + category: construction-category-structures + canBuildInImpassable: true + description: Clear and much tougher than regular glass, with added RadAbsorb to protect you from deadly radiation. + conditions: + - !type:EmptyOrWindowValidInTile + - !type:NoWindowsInTile + icon: + sprite: Structures/Windows/directional.rsi + state: uranium_reinforced_window + objectType: Structure + placementMode: SnapgridCenter + - type: construction name: firelock id: Firelock @@ -1552,23 +1588,6 @@ conditions: - !type:TileNotBlocked -- type: construction - name: airtight plastic flaps - id: PlasticFlapsAirtight - graph: PlasticFlapsGraph - startNode: start - targetNode: airtightFlaps - category: construction-category-structures - placementMode: SnapgridCenter - description: An airtight plastic flap to let items through and keep people out. - objectType: Structure - canBuildInImpassable: false - icon: - sprite: Structures/plastic_flaps.rsi - state: plasticflaps - conditions: - - !type:TileNotBlocked - - type: construction name: opaque plastic flaps id: PlasticFlapsOpaque @@ -1586,23 +1605,6 @@ conditions: - !type:TileNotBlocked -- type: construction - name: airtight opaque plastic flaps - id: PlasticFlapsAirtightOpaque - graph: PlasticFlapsGraph - startNode: start - targetNode: airtightopaqueFlaps - category: construction-category-structures - placementMode: SnapgridCenter - description: An opaque, airtight plastic flap to let items through and keep people out. - objectType: Structure - canBuildInImpassable: false - icon: - sprite: Structures/plastic_flaps.rsi - state: plasticflaps - conditions: - - !type:TileNotBlocked - - type: construction name: bananium clown statue id: BananiumClownStatue diff --git a/Resources/Prototypes/Recipes/Construction/utilities.yml b/Resources/Prototypes/Recipes/Construction/utilities.yml index 1647e98bce..19f2fee183 100644 --- a/Resources/Prototypes/Recipes/Construction/utilities.yml +++ b/Resources/Prototypes/Recipes/Construction/utilities.yml @@ -178,7 +178,7 @@ targetNode: pipe category: construction-category-utilities placementMode: SnapgridCenter - canBuildInImpassable: true + canBuildInImpassable: false icon: sprite: Structures/Piping/disposal.rsi state: conpipe-s diff --git a/Resources/Prototypes/Recipes/Cooking/meal_recipes.yml b/Resources/Prototypes/Recipes/Cooking/meal_recipes.yml index 2f280f6e46..f3176cc5e7 100644 --- a/Resources/Prototypes/Recipes/Cooking/meal_recipes.yml +++ b/Resources/Prototypes/Recipes/Cooking/meal_recipes.yml @@ -135,7 +135,7 @@ FoodBreadBun: 1 FoodMeat: 2 FoodCheeseSlice: 2 - FoodChili: 1 + FoodChiliPepper: 1 FoodCabbage: 1 CrayonGreen: 1 Flare: 1 @@ -177,7 +177,7 @@ solids: FoodBreadBun: 1 FoodMeat: 1 - FoodChili: 3 + FoodChiliPepper: 3 - type: microwaveMealRecipe id: RecipeGhostBurger @@ -675,7 +675,7 @@ solids: FoodRiceBoiled: 1 FoodMeatCutlet: 3 - FoodChili: 2 + FoodChiliPepper: 2 - type: microwaveMealRecipe id: RecipeEggRice @@ -894,7 +894,7 @@ solids: FoodBowlBig: 1 FoodBungo: 2 - FoodChili: 1 + FoodChiliPepper: 1 #Pies @@ -965,7 +965,7 @@ time: 15 solids: FoodDoughPie: 1 - FoodChilly: 3 + FoodChillyPepper: 3 FoodPlateTin: 1 - type: microwaveMealRecipe @@ -1063,7 +1063,7 @@ solids: FoodDough: 1 FoodCheeseSlice: 2 - FoodChili: 1 + FoodChiliPepper: 1 FoodMeatFish: 2 - type: microwaveMealRecipe @@ -1513,7 +1513,7 @@ time: 20 solids: FoodBowlBig: 1 - FoodChili: 1 + FoodChiliPepper: 1 FoodMeatCutlet: 1 FoodOnionSlice: 1 FoodTomato: 1 @@ -1546,7 +1546,7 @@ time: 30 solids: FoodBowlBig: 1 - FoodChili: 1 + FoodChiliPepper: 1 FoodMeatCutlet: 1 FoodOnionSlice: 1 FoodTomato: 1 @@ -1561,7 +1561,7 @@ #reagents: #blackpepper: 5 solids: - FoodChili: 1 + FoodChiliPepper: 1 FoodCheeseSlice: 2 - type: microwaveMealRecipe @@ -1581,7 +1581,7 @@ result: FoodMealEnchiladas time: 20 solids: - FoodChili: 2 + FoodChiliPepper: 2 FoodMeatCutlet: 1 FoodCorn: 1 @@ -1712,6 +1712,14 @@ solids: LeavesCannabis: 1 +- type: microwaveMealRecipe + id: RecipeDriedCannabisRainbow + name: dried rainbow cannabis leaves recipe + result: LeavesCannabisRainbowDried + time: 10 + solids: + LeavesCannabisRainbow: 1 + - type: microwaveMealRecipe id: RecipeTrashBakedBananaPeel name: baked banana peel recipe @@ -1749,7 +1757,7 @@ result: FoodMeatHawaiianKebab time: 5 solids: - FoodChili: 1 + FoodChiliPepper: 1 FoodMeatCutlet: 1 FoodPineappleSlice: 1 FoodKebabSkewer: 1 @@ -1760,7 +1768,7 @@ result: FoodMeatFiestaKebab time: 5 solids: - FoodChili: 1 + FoodChiliPepper: 1 FoodCorn: 1 FoodMeatCutlet: 1 FoodTomato: 1 diff --git a/Resources/Prototypes/Recipes/Crafting/Graphs/bots/honkbot.yml b/Resources/Prototypes/Recipes/Crafting/Graphs/bots/honkbot.yml index ff3f6d2e2a..6806aacc24 100644 --- a/Resources/Prototypes/Recipes/Crafting/Graphs/bots/honkbot.yml +++ b/Resources/Prototypes/Recipes/Crafting/Graphs/bots/honkbot.yml @@ -6,17 +6,11 @@ edges: - to: bot steps: - - tag: BoxHug - icon: - sprite: Objects/Storage/boxes.rsi - state: box_hug - name: box of hugs - - tag: ClownRubberStamp + - tag: HappyHonk icon: - sprite: Objects/Misc/stamps.rsi - state: stamp-clown - name: clown's rubber stamp - doAfter: 2 + sprite: Objects/Storage/Happyhonk/clown.rsi + state: box + name: happy honk meal - tag: BikeHorn icon: sprite: Objects/Fun/bikehorn.rsi @@ -45,21 +39,15 @@ edges: - to: bot steps: - - tag: HappyHonk + - tag: CluwneHappyHonk icon: - sprite: Objects/Storage/Happyhonk/clown.rsi + sprite: Objects/Storage/Happyhonk/cluwne.rsi state: box - name: happy honk meal - - tag: ClownRubberStamp - icon: - sprite: Objects/Misc/stamps.rsi - state: stamp-clown - name: clown's rubber stamp - doAfter: 2 + name: woeful cluwne meal - tag: CluwneHorn icon: - sprite: Objects/Fun/cluwnehorn.rsi - state: icon + sprite: Objects/Fun/cluwnehorn.rsi + state: icon name: broken bike horn doAfter: 2 - tag: ProximitySensor diff --git a/Resources/Prototypes/Recipes/Crafting/Graphs/bots/supplybot.yml b/Resources/Prototypes/Recipes/Crafting/Graphs/bots/supplybot.yml new file mode 100644 index 0000000000..efabb849bb --- /dev/null +++ b/Resources/Prototypes/Recipes/Crafting/Graphs/bots/supplybot.yml @@ -0,0 +1,23 @@ +- type: constructionGraph + id: SupplyBot + start: start + graph: + - node: start + edges: + - to: bot + steps: + - tag: ProximitySensor + icon: + sprite: Objects/Misc/proximity_sensor.rsi + state: icon + name: proximity sensor + - tag: BorgHead + icon: + sprite: Objects/Specific/Robotics/cyborg_parts.rsi + state: borg_head + name: borg head + doAfter: 1 + - material: Steel + amount: 10 + - node: bot + entity: MobSupplyBot diff --git a/Resources/Prototypes/Recipes/Crafting/Graphs/improvised/makeshifthandcuffs.yml b/Resources/Prototypes/Recipes/Crafting/Graphs/improvised/makeshifthandcuffs.yml index 08d6d57fb9..47eaf30e5c 100644 --- a/Resources/Prototypes/Recipes/Crafting/Graphs/improvised/makeshifthandcuffs.yml +++ b/Resources/Prototypes/Recipes/Crafting/Graphs/improvised/makeshifthandcuffs.yml @@ -11,4 +11,3 @@ doAfter: 5 - node: cuffscable entity: Cablecuffs - diff --git a/Resources/Prototypes/Recipes/Crafting/Graphs/improvised/makeshiftstunprod.yml b/Resources/Prototypes/Recipes/Crafting/Graphs/improvised/makeshiftstunprod.yml index fa006a938b..024a7c5876 100644 --- a/Resources/Prototypes/Recipes/Crafting/Graphs/improvised/makeshiftstunprod.yml +++ b/Resources/Prototypes/Recipes/Crafting/Graphs/improvised/makeshiftstunprod.yml @@ -8,24 +8,23 @@ steps: - material: MetalRod amount: 1 - - material: Cable - amount: 15 - - tag: DrinkSpaceGlue - name: Drink Space Glue - icon: - sprite: Objects/Consumable/Drinks/glue-tube.rsi - state: icon - tag: PowerCellSmall name: Power Cell Small icon: sprite: Objects/Power/power_cells.rsi state: small - - tag: CapacitorStockPart - name: Capacitor Stock Part + - tag: Handcuffs icon: - sprite: Objects/Misc/stock_parts.rsi - state: capacitor - doAfter: 20 + sprite: Objects/Misc/cablecuffs.rsi + state: cuff + color: red + name: cuffs + - tag: Igniter + name: Igniter + icon: + sprite: Objects/Devices/igniter.rsi + state: icon + doAfter: 15 - node: msstunprod entity: Stunprod diff --git a/Resources/Prototypes/Recipes/Crafting/Graphs/smokeables.yml b/Resources/Prototypes/Recipes/Crafting/Graphs/smokeables.yml index 00900a95ef..419d7bff33 100644 --- a/Resources/Prototypes/Recipes/Crafting/Graphs/smokeables.yml +++ b/Resources/Prototypes/Recipes/Crafting/Graphs/smokeables.yml @@ -12,6 +12,21 @@ doAfter: 2 - node: joint entity: Joint + +- type: constructionGraph + id: smokeableJointRainbow + start: start + graph: + - node: start + edges: + - to: jointRainbow + steps: + - material: PaperRolling + - material: CigaretteFilter + - material: GroundCannabisRainbow + doAfter: 2 + - node: jointRainbow + entity: JointRainbow - type: constructionGraph id: smokeableBlunt @@ -27,6 +42,20 @@ - node: blunt entity: Blunt +- type: constructionGraph + id: smokeableBluntRainbow + start: start + graph: + - node: start + edges: + - to: bluntRainbow + steps: + - material: LeavesTobaccoDried + - material: GroundCannabisRainbow + doAfter: 2 + - node: bluntRainbow + entity: BluntRainbow + - type: constructionGraph id: smokeableCigarette start: start @@ -56,6 +85,20 @@ - node: ground entity: GroundCannabis +- type: constructionGraph + id: smokeableGroundCannabisRainbow + start: start + graph: + - node: start + edges: + - to: groundRainbow + steps: + - material: LeavesCannabisRainbowDried + amount: 2 + doAfter: 5 + - node: groundRainbow + entity: GroundCannabisRainbow + - type: constructionGraph id: smokeableGroundTobacco start: start diff --git a/Resources/Prototypes/Recipes/Crafting/Graphs/storage/tallbox.yml b/Resources/Prototypes/Recipes/Crafting/Graphs/storage/tallbox.yml index 7e450513af..e72c56ff44 100644 --- a/Resources/Prototypes/Recipes/Crafting/Graphs/storage/tallbox.yml +++ b/Resources/Prototypes/Recipes/Crafting/Graphs/storage/tallbox.yml @@ -19,8 +19,6 @@ conditions: - !type:StorageWelded welded: false - - !type:Locked - locked: false completed: - !type:SpawnPrototype prototype: SheetSteel1 diff --git a/Resources/Prototypes/Recipes/Crafting/bots.yml b/Resources/Prototypes/Recipes/Crafting/bots.yml index 9a70a19c86..3031f4a780 100644 --- a/Resources/Prototypes/Recipes/Crafting/bots.yml +++ b/Resources/Prototypes/Recipes/Crafting/bots.yml @@ -62,3 +62,16 @@ icon: sprite: Mobs/Silicon/Bots/mimebot.rsi state: mimebot + +- type: construction + name: supplybot + id: supplybot + graph: SupplyBot + startNode: start + targetNode: bot + category: construction-category-utilities + objectType: Item + description: This bot can be loaded with cargo to make deliveries. + icon: + sprite: Mobs/Silicon/Bots/supplybot.rsi + state: supplybot diff --git a/Resources/Prototypes/Recipes/Crafting/smokeables.yml b/Resources/Prototypes/Recipes/Crafting/smokeables.yml index 6d7d4e30bc..e4280f6d66 100644 --- a/Resources/Prototypes/Recipes/Crafting/smokeables.yml +++ b/Resources/Prototypes/Recipes/Crafting/smokeables.yml @@ -8,6 +8,17 @@ description: "A roll of dried plant matter wrapped in thin paper." icon: { sprite: Objects/Consumable/Smokeables/Cannabis/joint.rsi, state: unlit-icon } objectType: Item + +- type: construction + name: rainbow joint + id: smokeableJointRainbow + graph: smokeableJointRainbow + startNode: start + targetNode: jointRainbow + category: construction-category-misc + description: "A roll of dried plant matter wrapped in thin paper." + icon: { sprite: Objects/Consumable/Smokeables/Cannabis/joint.rsi, state: unlit-icon } + objectType: Item - type: construction name: blunt @@ -20,6 +31,17 @@ icon: { sprite: Objects/Consumable/Smokeables/Cannabis/blunt.rsi, state: unlit-icon } objectType: Item +- type: construction + name: rainbow blunt + id: smokeableBluntRainbow + graph: smokeableBluntRainbow + startNode: start + targetNode: bluntRainbow + category: construction-category-misc + description: "A roll of dried plant matter wrapped in a dried tobacco leaf." + icon: { sprite: Objects/Consumable/Smokeables/Cannabis/blunt.rsi, state: unlit-icon } + objectType: Item + - type: construction name: cigarette id: smokeableCigarette @@ -45,6 +67,17 @@ # color: darkgreen objectType: Item +- type: construction + name: ground rainbow cannabis + id: smokeableGroundCannabisRainbow + graph: smokeableGroundCannabisRainbow + startNode: start + targetNode: groundRainbow + category: construction-category-misc + description: "Ground rainbow cannabis, ready to take you on a trip." + icon: { sprite: Objects/Specific/Hydroponics/rainbow_cannabis.rsi, state: powderpile_rainbow } + objectType: Item + - type: construction name: ground tobacco id: smokeableGroundTobacco diff --git a/Resources/Prototypes/Recipes/Lathes/Parts.yml b/Resources/Prototypes/Recipes/Lathes/Parts.yml index 90cff2174d..4534bf6097 100644 --- a/Resources/Prototypes/Recipes/Lathes/Parts.yml +++ b/Resources/Prototypes/Recipes/Lathes/Parts.yml @@ -1,3 +1,4 @@ +#Rating 1 - type: latheRecipe id: CapacitorStockPart result: CapacitorStockPart @@ -24,3 +25,93 @@ materials: Steel: 50 Plastic: 50 + +#Rating 2 +- type: latheRecipe + id: AdvancedCapacitorStockPart + result: AdvancedCapacitorStockPart + completetime: 3 + materials: + Steel: 80 + Plastic: 80 + Plasma: 75 + +- type: latheRecipe + id: AdvancedMatterBinStockPart + result: AdvancedMatterBinStockPart + completetime: 3 + materials: + Steel: 80 + Plastic: 80 + Plasma: 75 + +- type: latheRecipe + id: NanoManipulatorStockPart + result: NanoManipulatorStockPart + completetime: 3 + materials: + Steel: 80 + Plastic: 80 + Plasma: 75 + +#Rating 3 +- type: latheRecipe + id: SuperCapacitorStockPart + result: SuperCapacitorStockPart + completetime: 3 + materials: + Steel: 150 + Plastic: 150 + Plasma: 75 + Gold: 75 + +- type: latheRecipe + id: SuperMatterBinStockPart + result: SuperMatterBinStockPart + completetime: 3 + materials: + Steel: 150 + Plastic: 150 + Plasma: 75 + Gold: 75 + +- type: latheRecipe + id: PicoManipulatorStockPart + result: PicoManipulatorStockPart + completetime: 3 + materials: + Steel: 150 + Plastic: 150 + Plasma: 75 + Gold: 75 + +#Rating 4 +- type: latheRecipe + id: BluespaceCapacitorStockPart + result: BluespaceCapacitorStockPart + completetime: 3 + materials: + Steel: 150 + Plastic: 150 + Gold: 75 + Bluespace: 300 + +- type: latheRecipe + id: BluespaceMatterBinStockPart + result: BluespaceMatterBinStockPart + completetime: 3 + materials: + Steel: 150 + Plastic: 150 + Gold: 75 + Bluespace: 300 + +- type: latheRecipe + id: BluespaceManipulatorStockPart + result: BluespaceManipulatorStockPart + completetime: 3 + materials: + Steel: 150 + Plastic: 150 + Gold: 75 + Bluespace: 300 diff --git a/Resources/Prototypes/Recipes/Lathes/clothing.yml b/Resources/Prototypes/Recipes/Lathes/clothing.yml index 19b2fbb883..729f20e979 100644 --- a/Resources/Prototypes/Recipes/Lathes/clothing.yml +++ b/Resources/Prototypes/Recipes/Lathes/clothing.yml @@ -706,8 +706,16 @@ Durathread: 300 - type: latheRecipe - id: ClothingOuterWinterHoS - result: ClothingOuterWinterHoS + id: ClothingOuterWinterHoSUnarmored + result: ClothingOuterWinterHoSUnarmored + completetime: 3.2 + materials: + Cloth: 500 + Durathread: 300 + +- type: latheRecipe + id: ClothingOuterWinterWardenUnarmored + result: ClothingOuterWinterWardenUnarmored completetime: 3.2 materials: Cloth: 500 diff --git a/Resources/Prototypes/Recipes/Lathes/cooking.yml b/Resources/Prototypes/Recipes/Lathes/cooking.yml index a8836ff392..577d8299da 100644 --- a/Resources/Prototypes/Recipes/Lathes/cooking.yml +++ b/Resources/Prototypes/Recipes/Lathes/cooking.yml @@ -49,6 +49,13 @@ materials: Glass: 100 +- type: latheRecipe + id: CustomDrinkJug + result: CustomDrinkJug + completetime: 2 + materials: + Plastic: 200 + - type: latheRecipe id: FoodPlate result: FoodPlate diff --git a/Resources/Prototypes/Recipes/Lathes/devices.yml b/Resources/Prototypes/Recipes/Lathes/devices.yml index d41b5fdce8..2b0d6fa44f 100644 --- a/Resources/Prototypes/Recipes/Lathes/devices.yml +++ b/Resources/Prototypes/Recipes/Lathes/devices.yml @@ -76,7 +76,7 @@ Steel: 100 Plastic: 200 Glass: 100 - + - type: latheRecipe id: SignallerAdvanced result: RemoteSignallerAdvanced @@ -178,15 +178,23 @@ Plasma: 1000 #DeltaV: Bluespace Exists so less plasma used, no uranium Bluespace: 200 #DeltaV: Bluespace Exists -#- type: latheRecipe #DeltaV - LRP -# id: WeaponForceGun -# result: WeaponForceGun -# category: Tools -# completetime: 5 -# materials: -# Steel: 500 -# Glass: 400 -# Silver: 200 +- type: latheRecipe + id: ClothingMaskWeldingGas + result: ClothingMaskWeldingGas + completetime: 3 + materials: + Steel: 600 + Glass: 200 + +- type: latheRecipe + id: WeaponForceGun + result: WeaponForceGun + category: Tools + completetime: 5 + materials: + Steel: 500 + Glass: 400 + Silver: 200 - type: latheRecipe id: DeviceQuantumSpinInverter @@ -207,22 +215,22 @@ Glass: 500 Silver: 100 -#- type: latheRecipe #DeltaV - LRP -# id: WeaponTetherGun -# result: WeaponTetherGun -# category: Tools -# completetime: 5 -# materials: -# Steel: 500 -# Glass: 400 -# Silver: 100 - -#- type: latheRecipe #DeltaV - LRP -# id: WeaponGrapplingGun -# result: WeaponGrapplingGun -# category: Tools -# completetime: 5 -# materials: -# Steel: 500 -# Glass: 400 -# Gold: 100 +- type: latheRecipe + id: WeaponTetherGun + result: WeaponTetherGun + category: Tools + completetime: 5 + materials: + Steel: 500 + Glass: 400 + Silver: 100 + +- type: latheRecipe + id: WeaponGrapplingGun + result: WeaponGrapplingGun + category: Tools + completetime: 5 + materials: + Steel: 500 + Glass: 400 + Gold: 100 diff --git a/Resources/Prototypes/Recipes/Lathes/electronics.yml b/Resources/Prototypes/Recipes/Lathes/electronics.yml index 050dfa05cf..af258ad31b 100644 --- a/Resources/Prototypes/Recipes/Lathes/electronics.yml +++ b/Resources/Prototypes/Recipes/Lathes/electronics.yml @@ -112,6 +112,15 @@ result: CloningPodMachineCircuitboard category: Circuitry completetime: 4 + materials: + Steel: 100 + Glass: 500 + Gold: 100 + +- type: latheRecipe + id: MetempsychoticMachineCircuitboard + result: MetempsychoticMachineCircuitboard + completetime: 4 materials: Steel: 100 Glass: 900 @@ -124,7 +133,7 @@ completetime: 4 materials: Steel: 150 - Glass: 900 + Glass: 500 Gold: 50 - type: latheRecipe @@ -134,7 +143,7 @@ completetime: 4 materials: Steel: 150 - Glass: 900 + Glass: 500 Gold: 50 - type: latheRecipe @@ -144,7 +153,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 - type: latheRecipe id: PortableScrubberMachineCircuitBoard @@ -153,7 +162,7 @@ completetime: 4 materials: Steel: 150 - Glass: 900 + Glass: 500 Gold: 50 - type: latheRecipe @@ -163,7 +172,7 @@ completetime: 4 materials: Steel: 150 - Glass: 900 + Glass: 500 Gold: 50 - type: latheRecipe @@ -173,7 +182,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 - type: latheRecipe id: CryoPodMachineCircuitboard @@ -182,7 +191,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 Gold: 100 - type: latheRecipe @@ -192,7 +201,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 - type: latheRecipe id: ChemDispenserMachineCircuitboard @@ -201,7 +210,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 Gold: 100 - type: latheRecipe @@ -211,7 +220,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 Gold: 100 - type: latheRecipe @@ -221,7 +230,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 Gold: 100 - type: latheRecipe @@ -231,7 +240,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 - type: latheRecipe id: AutolatheMachineCircuitboard @@ -240,7 +249,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 - type: latheRecipe id: ProtolatheMachineCircuitboard @@ -249,7 +258,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 - type: latheRecipe id: AutolatheHyperConvectionMachineCircuitboard @@ -258,7 +267,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 Gold: 100 - type: latheRecipe @@ -268,7 +277,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 Gold: 100 - type: latheRecipe @@ -277,8 +286,19 @@ category: Circuitry completetime: 4 materials: - Steel: 100 - Glass: 900 + Steel: 100 + Glass: 500 + +- type: latheRecipe + id: CircuitImprinterHyperConvectionMachineCircuitboard + result: CircuitImprinterHyperConvectionMachineCircuitboard + category: Circuitry + completetime: 4 + materials: + Steel: 100 + Glass: 900 + Gold: 100 + - type: latheRecipe id: ExosuitFabricatorMachineCircuitboard @@ -287,7 +307,7 @@ completetime: 5 materials: Steel: 100 - Glass: 900 + Glass: 500 - type: latheRecipe id: UniformPrinterMachineCircuitboard @@ -296,7 +316,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 - type: latheRecipe id: VaccinatorMachineCircuitboard @@ -305,7 +325,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 Gold: 100 - type: latheRecipe @@ -315,7 +335,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 Gold: 100 - type: latheRecipe @@ -325,17 +345,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 - Gold: 100 - -- type: latheRecipe - id: TraversalDistorterMachineCircuitboard - result: TraversalDistorterMachineCircuitboard - category: Circuitry - completetime: 4 - materials: - Steel: 100 - Glass: 900 + Glass: 500 Gold: 100 - type: latheRecipe @@ -345,7 +355,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 Gold: 100 - type: latheRecipe @@ -355,7 +365,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 - type: latheRecipe id: AnomalyVesselExperimentalCircuitboard @@ -364,7 +374,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 Gold: 100 - type: latheRecipe @@ -384,7 +394,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 - type: latheRecipe id: ReagentGrinderMachineCircuitboard @@ -393,7 +403,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 - type: latheRecipe id: HotplateMachineCircuitboard @@ -402,7 +412,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 - type: latheRecipe id: AnalysisComputerCircuitboard @@ -411,7 +421,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 Gold: 100 - type: latheRecipe @@ -421,7 +431,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 Gold: 100 - type: latheRecipe @@ -431,7 +441,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 Gold: 100 - type: latheRecipe @@ -441,7 +451,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 - type: latheRecipe id: DawInstrumentMachineCircuitboard @@ -450,7 +460,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 - type: latheRecipe id: StasisBedMachineCircuitboard @@ -459,7 +469,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 Gold: 100 - type: latheRecipe @@ -469,7 +479,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 - type: latheRecipe id: CentrifugeMachineCircuitboard @@ -478,7 +488,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 - type: latheRecipe id: MaterialReclaimerMachineCircuitboard @@ -487,7 +497,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 - type: latheRecipe id: OreProcessorMachineCircuitboard @@ -496,7 +506,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 - type: latheRecipe id: OreProcessorIndustrialMachineCircuitboard @@ -505,7 +515,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 Gold: 100 - type: latheRecipe @@ -515,7 +525,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 Gold: 100 - type: latheRecipe @@ -525,7 +535,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 Gold: 100 - type: latheRecipe @@ -535,7 +545,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 Bananium: 100 - type: latheRecipe @@ -545,7 +555,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 Bananium: 100 - type: latheRecipe @@ -555,7 +565,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 Bananium: 100 - type: latheRecipe @@ -565,7 +575,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 Gold: 100 - type: latheRecipe @@ -575,7 +585,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 Gold: 100 # Power @@ -613,7 +623,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 - type: latheRecipe id: PortableGeneratorPacmanMachineCircuitboard @@ -667,7 +677,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 - type: latheRecipe id: SolarTrackerElectronics @@ -685,7 +695,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 - type: latheRecipe id: CloningConsoleComputerCircuitboard @@ -694,7 +704,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 - type: latheRecipe id: MicrowaveMachineCircuitboard @@ -703,7 +713,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 - type: latheRecipe id: ElectricGrillMachineCircuitboard @@ -712,7 +722,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 - type: latheRecipe id: FatExtractorMachineCircuitboard @@ -721,7 +731,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 - type: latheRecipe id: FlatpackerMachineCircuitboard @@ -730,7 +740,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 Gold: 100 - type: latheRecipe @@ -740,7 +750,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 - type: latheRecipe id: SurveillanceCameraRouterCircuitboard @@ -749,7 +759,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 - type: latheRecipe id: SurveillanceCameraWirelessRouterCircuitboard @@ -758,7 +768,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 - type: latheRecipe id: SurveillanceWirelessCameraAnchoredCircuitboard @@ -767,7 +777,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 - type: latheRecipe id: SurveillanceWirelessCameraMovableCircuitboard @@ -776,7 +786,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 - type: latheRecipe id: SurveillanceCameraMonitorCircuitboard @@ -785,7 +795,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 - type: latheRecipe id: SurveillanceWirelessCameraMonitorCircuitboard @@ -794,7 +804,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 - type: latheRecipe id: ComputerTelevisionCircuitboard @@ -803,7 +813,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 - type: latheRecipe id: EmitterCircuitboard @@ -812,7 +822,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 - type: latheRecipe id: ThrusterMachineCircuitboard @@ -821,7 +831,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 - type: latheRecipe id: GyroscopeMachineCircuitboard @@ -830,7 +840,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 - type: latheRecipe id: GasRecyclerMachineCircuitboard @@ -839,7 +849,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 - type: latheRecipe id: SeedExtractorMachineCircuitboard @@ -848,7 +858,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 - type: latheRecipe id: BoozeDispenserMachineCircuitboard @@ -857,7 +867,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 - type: latheRecipe id: CargoTelepadMachineCircuitboard @@ -866,7 +876,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 Gold: 100 - type: latheRecipe @@ -876,7 +886,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 - type: latheRecipe id: TelecomServerCircuitboard @@ -885,7 +895,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 - type: latheRecipe id: MassMediaCircuitboard @@ -894,7 +904,7 @@ completetime: 4 materials: Steel: 100 - Glass: 900 + Glass: 500 - type: latheRecipe id: MiniGravityGeneratorCircuitboard @@ -903,7 +913,7 @@ completetime: 6 materials: Steel: 100 - Glass: 900 + Glass: 500 Gold: 100 - type: latheRecipe @@ -912,7 +922,7 @@ completetime: 6 materials: Steel: 100 - Glass: 900 + Glass: 500 - type: latheRecipe id: ShuttleGunSvalinnMachineGunCircuitboard @@ -920,41 +930,42 @@ completetime: 6 materials: Steel: 100 - Glass: 900 - + + Glass: 500 + - type: latheRecipe id: ShuttleGunPerforatorCircuitboard result: ShuttleGunPerforatorCircuitboard completetime: 10 materials: Steel: 100 - Glass: 900 + Glass: 500 Gold: 100 - + - type: latheRecipe id: ShuttleGunKineticCircuitboard result: ShuttleGunKineticCircuitboard completetime: 6 materials: Steel: 100 - Glass: 900 - + Glass: 500 + - type: latheRecipe id: ShuttleGunFriendshipCircuitboard result: ShuttleGunFriendshipCircuitboard completetime: 8 materials: Steel: 100 - Glass: 900 + Glass: 500 Gold: 50 - + - type: latheRecipe id: ShuttleGunDusterCircuitboard result: ShuttleGunDusterCircuitboard completetime: 12 materials: Steel: 100 - Glass: 900 + Glass: 500 Gold: 100 - type: latheRecipe @@ -963,5 +974,13 @@ completetime: 5 materials: Steel: 100 - Glass: 900 + Glass: 500 Gold: 100 + +- type: latheRecipe + id: JukeboxCircuitBoard + result: JukeboxCircuitBoard + completetime: 4 + materials: + Steel: 100 + Glass: 500 \ No newline at end of file diff --git a/Resources/Prototypes/Recipes/Lathes/janitorial.yml b/Resources/Prototypes/Recipes/Lathes/janitorial.yml index a3b968a331..9ba7dfa188 100644 --- a/Resources/Prototypes/Recipes/Lathes/janitorial.yml +++ b/Resources/Prototypes/Recipes/Lathes/janitorial.yml @@ -18,7 +18,7 @@ result: Bucket completetime: 2 materials: - Steel: 100 + Plastic: 100 - type: latheRecipe id: WetFloorSign diff --git a/Resources/Prototypes/Recipes/Lathes/medical.yml b/Resources/Prototypes/Recipes/Lathes/medical.yml index 631829ed53..bec71ce3d1 100644 --- a/Resources/Prototypes/Recipes/Lathes/medical.yml +++ b/Resources/Prototypes/Recipes/Lathes/medical.yml @@ -70,6 +70,14 @@ Glass: 25 Plastic: 25 +- type: latheRecipe + id: BoneGel + result: BoneGel + completetime: 2 + materials: + Plastic: 200 + Plasma: 200 + - type: latheRecipe id: Gauze result: Gauze1 @@ -116,7 +124,7 @@ result: ClothingMaskSterile completetime: 2 materials: - Plastic: 100 + Plastic: 50 - type: latheRecipe id: DiseaseSwab diff --git a/Resources/Prototypes/Recipes/Lathes/misc.yml b/Resources/Prototypes/Recipes/Lathes/misc.yml index ab13dc4573..2c0e1eeec3 100644 --- a/Resources/Prototypes/Recipes/Lathes/misc.yml +++ b/Resources/Prototypes/Recipes/Lathes/misc.yml @@ -207,3 +207,19 @@ materials: Steel: 400 Glass: 200 + +- type: latheRecipe + id: ClothingShoesBootsMagAdv + result: ClothingShoesBootsMagAdv + completetime: 12 + materials: + Silver: 1000 + Gold: 500 + +- type: latheRecipe + id: MailCapsule + result: MailCapsulePrimed + completetime: 1 + materials: + Glass: 100 + Plastic: 100 diff --git a/Resources/Prototypes/Recipes/Lathes/prizecounter.yml b/Resources/Prototypes/Recipes/Lathes/prizecounter.yml index e893c17835..9e5e239c6f 100644 --- a/Resources/Prototypes/Recipes/Lathes/prizecounter.yml +++ b/Resources/Prototypes/Recipes/Lathes/prizecounter.yml @@ -6,6 +6,14 @@ materials: PrizeTicket: 30 +- type: latheRecipe + id: PlushieShadowkin + result: PlushieShadowkin + applyMaterialDiscount: false + completetime: 0.1 + materials: + PrizeTicket: 50 + - type: latheRecipe id: PlushieMothRandom result: PlushieMothRandom @@ -720,6 +728,14 @@ materials: PrizeTicket: 200 +- type: latheRecipe + id: BwoinkHammer + result: BwoinkHammer + applyMaterialDiscount: false + completetime: 0.1 + materials: + PrizeTicket: 200 + - type: latheRecipe id: ThronglerToy result: ThronglerToy diff --git a/Resources/Prototypes/Recipes/Lathes/rehydrateable.yml b/Resources/Prototypes/Recipes/Lathes/rehydrateable.yml index c38259dd81..b0b3b90838 100644 --- a/Resources/Prototypes/Recipes/Lathes/rehydrateable.yml +++ b/Resources/Prototypes/Recipes/Lathes/rehydrateable.yml @@ -72,3 +72,101 @@ materials: # abominations are slow and essentially worse than even carp Biomass: 28 Plasma: 500 # more biomass but less plasma + +- type: latheRecipe + id: SynthHeart + result: BioSynthHeart + completetime: 30 + materials: + Biomass: 10 + +- type: latheRecipe + id: SynthLungs + result: BioSynthLungs + completetime: 30 + materials: + Biomass: 10 + +- type: latheRecipe + id: SynthLiver + result: BioSynthLiver + completetime: 30 + materials: + Biomass: 10 + +- type: latheRecipe + id: SynthEyes + result: BioSynthEyes + completetime: 30 + materials: + Biomass: 10 + +- type: latheRecipe + id: SynthLeftArm + result: BioSynthLeftArm + completetime: 30 + materials: + Biomass: 10 + +- type: latheRecipe + id: SynthLeftHand + result: BioSynthLeftHand + completetime: 30 + materials: + Biomass: 10 + +- type: latheRecipe + id: SynthRightArm + result: BioSynthRightArm + completetime: 30 + materials: + Biomass: 10 + +- type: latheRecipe + id: SynthRightHand + result: BioSynthRightHand + completetime: 30 + materials: + Biomass: 10 + +- type: latheRecipe + id: SynthLeftLeg + result: BioSynthLeftLeg + completetime: 30 + materials: + Biomass: 10 + +- type: latheRecipe + id: SynthRightLeg + result: BioSynthRightLeg + completetime: 30 + materials: + Biomass: 10 + +- type: latheRecipe + id: SynthLeftFoot + result: BioSynthLeftFoot + completetime: 30 + materials: + Biomass: 10 + +- type: latheRecipe + id: SynthRightFoot + result: BioSynthRightFoot + completetime: 30 + materials: + Biomass: 10 + +- type: latheRecipe + id: PizzaLeftArm + result: PizzaLeftArm + completetime: 30 + materials: + Biomass: 5 + +- type: latheRecipe + id: PizzaRightArm + result: PizzaRightArm + completetime: 30 + materials: + Biomass: 5 diff --git a/Resources/Prototypes/Recipes/Lathes/robotics.yml b/Resources/Prototypes/Recipes/Lathes/robotics.yml index bf6f479703..2dc0a65748 100644 --- a/Resources/Prototypes/Recipes/Lathes/robotics.yml +++ b/Resources/Prototypes/Recipes/Lathes/robotics.yml @@ -427,16 +427,16 @@ Glass: 250 Plastic: 250 -#- type: latheRecipe -# id: BorgModuleGrapplingGun -# result: BorgModuleGrapplingGun -# category: Robotics -# completetime: 3 -# materials: -# Steel: 500 -# Glass: 500 -# Plastic: 250 -# Gold: 50 +- type: latheRecipe + id: BorgModuleGrapplingGun + result: BorgModuleGrapplingGun + category: Robotics + completetime: 3 + materials: + Steel: 500 + Glass: 500 + Plastic: 250 + Gold: 50 - type: latheRecipe id: BorgModuleAdvancedTool diff --git a/Resources/Prototypes/Recipes/Lathes/security.yml b/Resources/Prototypes/Recipes/Lathes/security.yml index 08e11e4ff8..4a2e737e15 100644 --- a/Resources/Prototypes/Recipes/Lathes/security.yml +++ b/Resources/Prototypes/Recipes/Lathes/security.yml @@ -12,6 +12,14 @@ materials: Plastic: 200 +- type: latheRecipe + id: BolaEnergy + result: BolaEnergy + completetime: 2 + materials: + Plastic: 200 + Plasma: 150 + - type: latheRecipe id: Stunbaton result: Stunbaton @@ -38,7 +46,7 @@ materials: Steel: 250 Plastic: 100 - + - type: latheRecipe id: WeaponLaserCarbine result: WeaponLaserCarbine @@ -146,15 +154,6 @@ Plastic: 200 Steel: 100 -- type: latheRecipe - id: ShellShotgunBeanbag - result: ShellShotgunBeanbag - category: Ammo - completetime: 2 - materials: - Plastic: 15 - Steel: 10 - - type: latheRecipe id: CartridgePistolRubber result: CartridgePistolRubber @@ -173,14 +172,6 @@ Plastic: 5 Steel: 5 -- type: latheRecipe - id: CartridgeRifle - result: CartridgeRifle - category: Ammo - completetime: 2 - materials: - Steel: 15 - - type: latheRecipe id: CartridgeLightRifleRubber result: CartridgeLightRifleRubber @@ -199,54 +190,6 @@ Plastic: 10 Steel: 5 -- type: latheRecipe - id: CartridgePistol - result: CartridgePistol - category: Ammo - completetime: 2 - materials: - Steel: 10 - -- type: latheRecipe - id: ShellShotgun - result: ShellShotgun - category: Ammo - completetime: 2 - materials: - Steel: 20 - -- type: latheRecipe - id: ShellShotgunSlug - result: ShellShotgunSlug - completetime: 2 - materials: - Steel: 25 - -- type: latheRecipe - id: CartridgeMagnum - result: CartridgeMagnum - category: Ammo - completetime: 2 - materials: - Steel: 20 - -- type: latheRecipe - id: CartridgeLightRifle - result: CartridgeLightRifle - category: Ammo - completetime: 2 - materials: - Steel: 30 - -- type: latheRecipe - id: ShellShotgunFlare - result: ShellShotgunFlare - category: Ammo - completetime: 2 - materials: - Plastic: 20 - Steel: 5 - - type: latheRecipe id: ShellTranquilizer result: ShellTranquilizer @@ -283,13 +226,56 @@ materials: Steel: 500 +- type: latheRecipe + id: MagazinePistolEmpty + result: MagazinePistolEmpty + category: Ammo + completetime: 5 + materials: + Steel: 25 + - type: latheRecipe id: MagazinePistol result: MagazinePistol category: Ammo completetime: 5 materials: - Steel: 100 + Steel: 145 + +- type: latheRecipe + id: MagazinePistolPractice + result: MagazinePistolPractice + category: Ammo + completetime: 5 + materials: + Steel: 85 + +- type: latheRecipe + id: MagazinePistolUranium + result: MagazinePistolUranium + category: Ammo + completetime: 5 + materials: + Steel: 25 + Plastic: 65 + Uranium: 120 + +- type: latheRecipe + id: MagazinePistolIncendiary + result: MagazinePistolIncendiary + category: Ammo + completetime: 5 + materials: + Steel: 25 + Plastic: 120 + +- type: latheRecipe + id: MagazinePistolSubMachineGunEmpty + result: MagazinePistolSubMachineGunEmpty + category: Ammo + completetime: 5 + materials: + Steel: 30 - type: latheRecipe id: MagazinePistolSubMachineGun @@ -299,6 +285,14 @@ materials: Steel: 300 +- type: latheRecipe + id: MagazinePistolSubMachineGunTopMountedEmpty + result: MagazinePistolSubMachineGunTopMountedEmpty + category: Ammo + completetime: 5 + materials: + Steel: 30 + - type: latheRecipe id: MagazinePistolSubMachineGunTopMounted result: MagazinePistolSubMachineGunTopMounted @@ -313,7 +307,7 @@ category: Ammo completetime: 5 materials: - Steel: 650 + Steel: 600 - type: latheRecipe id: MagazineBoxPistolRubber @@ -330,7 +324,15 @@ category: Ammo completetime: 5 materials: - Steel: 1250 + Steel: 240 + +- type: latheRecipe + id: MagazineRifleEmpty + result: MagazineRifleEmpty + category: Ammo + completetime: 5 + materials: + Steel: 25 - type: latheRecipe id: MagazineBoxMagnumRubber @@ -347,7 +349,43 @@ category: Ammo completetime: 5 materials: - Steel: 375 + Steel: 475 + + +- type: latheRecipe + id: MagazineRiflePractice + result: MagazineRiflePractice + category: Ammo + completetime: 5 + materials: + Steel: 175 + +- type: latheRecipe + id: MagazineRifleUranium + result: MagazineRifleUranium + category: Ammo + completetime: 5 + materials: + Steel: 25 + Plastic: 300 + Uranium: 300 + +- type: latheRecipe + id: MagazineRifleIncendiary + result: MagazineRifleIncendiary + category: Ammo + completetime: 5 + materials: + Steel: 25 + Plastic: 450 + +- type: latheRecipe + id: MagazineLightRifleEmpty + result: MagazineLightRifleEmpty + category: Ammo + completetime: 5 + materials: + Steel: 25 - type: latheRecipe id: MagazineLightRifle @@ -355,7 +393,35 @@ category: Ammo completetime: 5 materials: - Steel: 375 + Steel: 565 + +- type: latheRecipe + id: MagazineLightRiflePractice + result: MagazineLightRiflePractice + category: Ammo + completetime: 5 + materials: + Steel: 205 + + +- type: latheRecipe + id: MagazineLightRifleUranium + result: MagazineLightRifleUranium + category: Ammo + completetime: 5 + materials: + Steel: 25 + Plastic: 360 + Uranium: 360 + +- type: latheRecipe + id: MagazineLightRifleIncendiary + result: MagazineLightRifleIncendiary + category: Ammo + completetime: 5 + materials: + Steel: 25 + Plastic: 540 - type: latheRecipe id: MagazineBoxRifle @@ -363,7 +429,7 @@ category: Ammo completetime: 5 materials: - Steel: 950 + Steel: 750 - type: latheRecipe id: MagazineBoxRifleRubber @@ -380,7 +446,41 @@ category: Ammo completetime: 5 materials: - Steel: 1800 + Steel: 900 + +- type: latheRecipe + id: BoxLethalshot + result: BoxLethalshot + category: Ammo + completetime: 5 + materials: + Steel: 320 + +- type: latheRecipe + id: BoxBeanbag + result: BoxBeanbag + category: Ammo + completetime: 5 + materials: + Steel: 160 + Plastic: 240 + +- type: latheRecipe + id: BoxShotgunSlug + result: BoxShotgunSlug + category: Ammo + completetime: 5 + materials: + Steel: 240 + Plastic: 160 + +- type: latheRecipe + id: SpeedLoaderMagnumEmpty + result: SpeedLoaderMagnumEmpty + category: Ammo + completetime: 5 + materials: + Steel: 50 - type: latheRecipe id: MagazineBoxLightRifleRubber @@ -397,47 +497,77 @@ category: Ammo completetime: 5 materials: - Steel: 200 + Steel: 190 - type: latheRecipe - id: ShellShotgunIncendiary - result: ShellShotgunIncendiary + id: SpeedLoaderMagnumPractice + result: SpeedLoaderMagnumPractice category: Ammo - completetime: 2 + completetime: 5 materials: - Plastic: 20 + Steel: 90 - type: latheRecipe - id: CartridgePistolIncendiary - result: CartridgePistolIncendiary + id: SpeedLoaderMagnumUranium + result: SpeedLoaderMagnumUranium category: Ammo - completetime: 2 + completetime: 5 materials: - Plastic: 10 + Steel: 50 + Plastic: 150 + Uranium: 110 - type: latheRecipe - id: CartridgeMagnumIncendiary - result: CartridgeMagnumIncendiary + id: SpeedLoaderMagnumIncendiary + result: SpeedLoaderMagnumIncendiary category: Ammo - completetime: 2 + completetime: 5 materials: - Plastic: 20 + Steel: 50 + Plastic: 150 - type: latheRecipe - id: CartridgeLightRifleIncendiary - result: CartridgeLightRifleIncendiary + id: MagazineShotgunEmpty + result: MagazineShotgunEmpty category: Ammo - completetime: 2 + completetime: 5 materials: - Plastic: 20 + Steel: 50 - type: latheRecipe - id: CartridgeRifleIncendiary - result: CartridgeRifleIncendiary + id: MagazineShotgun + result: MagazineShotgun category: Ammo - completetime: 2 + completetime: 5 materials: - Plastic: 15 + Steel: 240 + +- type: latheRecipe + id: MagazineShotgunBeanbag + result: MagazineShotgunBeanbag + category: Ammo + completetime: 5 + materials: + Steel: 150 + Plastic: 140 + +- type: latheRecipe + id: MagazineShotgunSlug + result: MagazineShotgunSlug + category: Ammo + completetime: 5 + materials: + Steel: 190 + Plastic: 100 + +- type: latheRecipe + id: MagazineShotgunIncendiary + result: MagazineShotgunIncendiary + category: Ammo + completetime: 5 + materials: + Steel: 100 + Plastic: 190 - type: latheRecipe id: MagazineBoxPistolIncendiary @@ -445,7 +575,7 @@ category: Ammo completetime: 5 materials: - Plastic: 650 + Plastic: 600 - type: latheRecipe id: MagazineBoxMagnumIncendiary @@ -453,7 +583,7 @@ category: Ammo completetime: 5 materials: - Plastic: 1250 + Plastic: 240 - type: latheRecipe id: MagazineBoxLightRifleIncendiary @@ -461,7 +591,7 @@ category: Ammo completetime: 5 materials: - Plastic: 1800 + Plastic: 900 - type: latheRecipe id: MagazineBoxRifleIncendiary @@ -469,15 +599,25 @@ category: Ammo completetime: 5 materials: - Plastic: 950 + Plastic: 750 - type: latheRecipe - id: ShellShotgunPractice - result: ShellShotgunPractice + id: BoxShotgunFlare + result: BoxShotgunFlare category: Ammo - completetime: 2 + completetime: 5 materials: - Plastic: 20 + Steel: 80 + Plastic: 80 + +- type: latheRecipe + id: BoxShotgunIncendiary + result: BoxShotgunIncendiary + category: Ammo + completetime: 5 + materials: + Steel: 80 + Plastic: 320 - type: latheRecipe id: MagazineBoxPistolPractice @@ -485,7 +625,7 @@ category: Ammo completetime: 5 materials: - Plastic: 600 + Steel: 300 - type: latheRecipe id: MagazineBoxMagnumPractice @@ -493,7 +633,7 @@ category: Ammo completetime: 5 materials: - Plastic: 1200 + Steel: 60 - type: latheRecipe id: MagazineBoxLightRiflePractice @@ -501,7 +641,7 @@ category: Ammo completetime: 5 materials: - Plastic: 1000 + Steel: 300 - type: latheRecipe id: MagazineBoxRiflePractice @@ -509,7 +649,7 @@ category: Ammo completetime: 5 materials: - Plastic: 900 + Steel: 250 - type: latheRecipe id: WeaponLaserCarbinePractice @@ -532,49 +672,12 @@ Plastic: 200 - type: latheRecipe - id: ShellShotgunUranium - result: ShellShotgunUranium - category: Ammo - completetime: 2 - materials: - Plastic: 15 - Uranium: 10 - -- type: latheRecipe - id: CartridgePistolUranium - result: CartridgePistolUranium - category: Ammo - completetime: 2 - materials: - Plastic: 5 - Uranium: 10 - -- type: latheRecipe - id: CartridgeMagnumUranium - result: CartridgeMagnumUranium - category: Ammo - completetime: 2 - materials: - Plastic: 20 - Uranium: 10 - -- type: latheRecipe - id: CartridgeLightRifleUranium - result: CartridgeLightRifleUranium - category: Ammo - completetime: 2 - materials: - Plastic: 20 - Uranium: 10 - -- type: latheRecipe - id: CartridgeRifleUranium - result: CartridgeRifleUranium + id: BoxShotgunPractice + result: BoxShotgunPractice category: Ammo - completetime: 2 + completetime: 5 materials: - Plastic: 15 - Uranium: 10 + Steel: 80 - type: latheRecipe id: MagazineBoxPistolUranium @@ -582,8 +685,8 @@ category: Ammo completetime: 5 materials: - Plastic: 650 - Uranium: 65 + Plastic: 300 + Uranium: 600 - type: latheRecipe id: MagazineBoxMagnumUranium @@ -591,8 +694,8 @@ category: Ammo completetime: 5 materials: - Plastic: 1250 - Uranium: 125 + Plastic: 240 + Uranium: 180 - type: latheRecipe id: MagazineBoxLightRifleUranium @@ -600,8 +703,8 @@ category: Ammo completetime: 5 materials: - Plastic: 1800 - Uranium: 180 + Plastic: 600 + Uranium: 600 - type: latheRecipe id: MagazineBoxRifleUranium @@ -609,8 +712,27 @@ category: Ammo completetime: 5 materials: - Plastic: 950 - Uranium: 95 + Plastic: 500 + Uranium: 500 + +- type: latheRecipe + id: BoxShotgunUranium + result: BoxShotgunUranium + category: Ammo + completetime: 5 + materials: + Plastic: 320 + Uranium: 240 + +- type: latheRecipe + id: WeaponDisabler + result: WeaponDisabler + category: Weapons + completetime: 6 + materials: + Steel: 300 + Glass: 200 + Plastic: 200 - type: latheRecipe id: WeaponDisablerSMG @@ -621,7 +743,7 @@ Steel: 1000 Glass: 500 Plastic: 500 - + - type: latheRecipe id: MagazineGrenadeEmpty result: MagazineGrenadeEmpty @@ -629,7 +751,7 @@ materials: Steel: 150 Plastic: 50 - + - type: latheRecipe id: GrenadeEMP result: GrenadeEMP @@ -638,7 +760,7 @@ Steel: 150 Plastic: 100 Glass: 20 - + - type: latheRecipe id: GrenadeBlast result: GrenadeBlast @@ -647,7 +769,7 @@ Steel: 150 Plastic: 100 Gold: 50 - + - type: latheRecipe id: GrenadeFlash result: GrenadeFlash @@ -656,4 +778,113 @@ Steel: 150 Plastic: 100 Glass: 20 - \ No newline at end of file + +- type: latheRecipe + id: PortableRecharger + result: PortableRecharger + completetime: 15 + materials: + Steel: 2000 + Uranium: 2000 + Plastic: 1000 + Plasma: 500 + Glass: 500 + +- type: latheRecipe + id: ShellShotgun + result: ShellShotgun + category: Ammo + completetime: 2 + materials: + Steel: 20 + +- type: latheRecipe + id: ShellShotgunSlug + result: ShellShotgunSlug + completetime: 2 + materials: + Steel: 25 + +- type: latheRecipe + id: ShellShotgunFlare + result: ShellShotgunFlare + category: Ammo + completetime: 2 + materials: + Plastic: 20 + Steel: 5 + +- type: latheRecipe + id: CartridgeLightRifleIncendiary + result: CartridgeLightRifleIncendiary + category: Ammo + completetime: 2 + materials: + Plastic: 20 + +- type: latheRecipe + id: CartridgeMagnumIncendiary + result: CartridgeMagnumIncendiary + category: Ammo + completetime: 2 + materials: + Plastic: 20 + +- type: latheRecipe + id: CartridgePistolIncendiary + result: CartridgePistolIncendiary + category: Ammo + completetime: 2 + materials: + Plastic: 10 + +- type: latheRecipe + id: CartridgeRifleIncendiary + result: CartridgeRifleIncendiary + category: Ammo + completetime: 2 + materials: + Plastic: 15 + +- type: latheRecipe + id: CartridgePistolUranium + result: CartridgePistolUranium + category: Ammo + completetime: 2 + materials: + Plastic: 5 + Uranium: 10 + +- type: latheRecipe + id: CartridgeMagnumUranium + result: CartridgeMagnumUranium + category: Ammo + completetime: 2 + materials: + Plastic: 20 + Uranium: 10 + +- type: latheRecipe + id: CartridgeLightRifleUranium + result: CartridgeLightRifleUranium + category: Ammo + completetime: 2 + materials: + Plastic: 20 + Uranium: 10 + +- type: latheRecipe + id: CartridgeRifleUranium + result: CartridgeRifleUranium + category: Ammo + completetime: 2 + materials: + Plastic: 15 + Uranium: 10 + +- type: latheRecipe + id: ShadowkinRestraints + result: ClothingOuterShadowkinRestraints + completetime: 6 + materials: + Steel: 300 diff --git a/Resources/Prototypes/Recipes/Lathes/sheet.yml b/Resources/Prototypes/Recipes/Lathes/sheet.yml index 053715a181..9f83c58e66 100644 --- a/Resources/Prototypes/Recipes/Lathes/sheet.yml +++ b/Resources/Prototypes/Recipes/Lathes/sheet.yml @@ -176,3 +176,37 @@ completetime: 1 materials: Wood: 50 + +- type: latheRecipe + id: CoreSilver + icon: + sprite: Objects/Misc/guardian_info.rsi + state: icon + result: CoreSilver + completetime: 10 + materials: + Bluespace: 500 + Silver: 1000 + Plastic: 1000 + +- type: latheRecipe + id: BluespaceCrystal + icon: + sprite: Objects/Materials/materials.rsi + state: bluespace + result: MaterialBluespace1 + applyMaterialDiscount: false + completetime: 4 + materials: + RawBluespace: 100 + +- type: latheRecipe + id: NormalityCrystal + icon: + sprite: Objects/Materials/materials.rsi + state: normality + result: MaterialNormality1 + applyMaterialDiscount: false + completetime: 4 + materials: + RawNormality: 100 diff --git a/Resources/Prototypes/Recipes/Reactions/drinks.yml b/Resources/Prototypes/Recipes/Reactions/drinks.yml index c810ebb0ce..afb315c09d 100644 --- a/Resources/Prototypes/Recipes/Reactions/drinks.yml +++ b/Resources/Prototypes/Recipes/Reactions/drinks.yml @@ -86,6 +86,16 @@ products: B52: 3 +- type: reaction + id: BudgetInsulsDrink + reactants: + Tequila: + amount: 1 + VodkaRedBool: + amount: 2 + products: + BudgetInsulsDrink: 3 + - type: reaction id: BlueHawaiian reactants: @@ -516,6 +526,16 @@ products: IcedTea: 3 +- type: reaction + id: IrishBool + reactants: + EnergyDrink: + amount: 1 + IrishCarBomb: + amount: 1 + products: + IrishBool: 2 + - type: reaction id: IrishCarBomb reactants: @@ -794,6 +814,18 @@ products: RoyRogers: 3 +- type: reaction + id: Rubberneck + reactants: + EnergyDrink: + amount: 2 + Moonshine: + amount: 1 + Sugar: + amount: 1 + products: + Rubberneck: 4 + - type: reaction id: Sbiten reactants: @@ -857,7 +889,7 @@ LemonLime: amount: 1 products: - SnowWhite: 3 + SnowWhite: 2 - type: reaction id: SoyLatte @@ -943,6 +975,16 @@ products: VodkaMartini: 3 +- type: reaction + id: VodkaRedBool + reactants: + Vodka: + amount: 1 + EnergyDrink: + amount: 2 + products: + VodkaRedBool: 3 + - type: reaction id: VodkaTonic reactants: @@ -963,6 +1005,18 @@ products: WhiskeyCola: 3 +- type: reaction + id: WatermelonWakeup + reactants: + JuiceWatermelon: + amount: 1 + Whiskey: + amount: 1 + EnergyDrink: + amount: 1 + products: + WatermelonWakeup: 3 + - type: reaction id: WhiteRussian reactants: @@ -983,6 +1037,16 @@ products: WhiskeySoda: 3 +- type: reaction + id: XenoBasher + reactants: + ManlyDorf: + amount: 2 + EnergyDrink: + amount: 1 + products: + XenoBasher: 3 + - type: reaction id: HotRamen minTemp: 323.15 diff --git a/Resources/Prototypes/Recipes/Reactions/medicine.yml b/Resources/Prototypes/Recipes/Reactions/medicine.yml index a1ca3ea38e..2e9b1d4f85 100644 --- a/Resources/Prototypes/Recipes/Reactions/medicine.yml +++ b/Resources/Prototypes/Recipes/Reactions/medicine.yml @@ -298,6 +298,18 @@ products: Lipozine: 3 +- type: reaction + id: Mannitol + reactants: + Hydrogen: + amount: 1 + Water: + amount: 1 + Sugar: + amount: 1 + products: + Mannitol: 3 + - type: reaction id: MindbreakerToxin minTemp: 370 @@ -542,11 +554,21 @@ amount: 1 Silicon: amount: 1 - Benzene: - amount: 1 products: Insuzine: 3 - Ash: 1 + +- type: reaction + id: Opporozidone + minTemp: 400 #Maybe if a method of reducing reagent temp exists one day, this could be -50 + reactants: + Cognizine: + amount: 1 + Plasma: + amount: 2 + Doxarubixadone: + amount: 1 + products: + Opporozidone: 3 - type: reaction id: Necrosol @@ -574,3 +596,43 @@ amount: 2 products: Aloxadone: 4 + +- type: reaction + id: Psicodine + impact: Medium + reactants: + Mannitol: + amount: 2 + Impedrezene: + amount: 1 + Water: + amount: 2 + products: + Psicodine: 4 + +- type: reaction + id: Lipolicide + reactants: + Ephedrine: + amount: 1 + Diethylamine: + amount: 1 + Mercury: + amount: 1 + products: + Lipolicide: 3 + +- type: reaction + id: Happiness + reactants: + Laughter: + amount: 2 + Epinephrine: + amount: 1 + Ethanol: + amount: 1 + Plasma: + amount: 5 + catalyst: true + products: + Happiness: 4 diff --git a/Resources/Prototypes/Recipes/Reactions/pyrotechnic.yml b/Resources/Prototypes/Recipes/Reactions/pyrotechnic.yml index 318490931f..3591ce7008 100644 --- a/Resources/Prototypes/Recipes/Reactions/pyrotechnic.yml +++ b/Resources/Prototypes/Recipes/Reactions/pyrotechnic.yml @@ -31,6 +31,7 @@ - type: reaction id: ChlorineTrifluoride + minTemp: 370 priority: 20 reactants: Chlorine: @@ -38,7 +39,6 @@ Fluorine: amount: 3 effects: - # TODO solution temperature!! - !type:ExplosionReactionEffect explosionType: Default # 15 damage per intensity. maxIntensity: 200 diff --git a/Resources/Prototypes/Research/arsenal.yml b/Resources/Prototypes/Research/arsenal.yml index 7432d4225f..ee2be33c83 100644 --- a/Resources/Prototypes/Research/arsenal.yml +++ b/Resources/Prototypes/Research/arsenal.yml @@ -24,11 +24,12 @@ tier: 1 cost: 10000 recipeUnlocks: - - ShellShotgunIncendiary - - CartridgePistolIncendiary - - CartridgeMagnumIncendiary - - CartridgeLightRifleIncendiary - - CartridgeRifleIncendiary + - BoxShotgunIncendiary + - MagazineRifleIncendiary + - MagazinePistolIncendiary + - MagazineLightRifleIncendiary + - SpeedLoaderMagnumIncendiary + - MagazineShotgunIncendiary - MagazineBoxPistolIncendiary - MagazineBoxMagnumIncendiary - MagazineBoxLightRifleIncendiary @@ -60,7 +61,9 @@ tier: 1 cost: 5000 recipeUnlocks: - - ShellShotgunBeanbag + - MagazineShotgunBeanbag + - ShellTranquilizer + - BoxBeanbag - CartridgePistolRubber - CartridgeMagnumRubber - CartridgeLightRifleRubber @@ -69,10 +72,9 @@ - MagazineBoxMagnumRubber - MagazineBoxLightRifleRubber - MagazineBoxRifleRubber - # DeltaV - .38 special rubber ammo - Adds .38 special rubber ammo to the research tree - CartridgeSpecialRubber - MagazineBoxSpecialRubber - # End of modified code + - WeaponDisabler - type: technology id: UraniumMunitions @@ -84,19 +86,17 @@ tier: 1 cost: 7500 recipeUnlocks: - - ShellShotgunUranium - - CartridgePistolUranium - - CartridgeMagnumUranium - - CartridgeLightRifleUranium - - CartridgeRifleUranium + - MagazineRifleUranium + - MagazinePistolUranium + - MagazineLightRifleUranium + - SpeedLoaderMagnumUranium - MagazineBoxPistolUranium - MagazineBoxMagnumUranium - MagazineBoxLightRifleUranium - MagazineBoxRifleUranium - # DeltaV - .38 special uranium ammo - Adds .38 special uranium ammo to the research tree + - BoxShotgunUranium - CartridgeSpecialUranium - MagazineBoxSpecialUranium - # End of modified code - type: technology id: AdvancedRiotControl @@ -113,6 +113,7 @@ - TelescopicShield - HoloprojectorSecurity - WeaponDisablerSMG + - BolaEnergy # Tier 2 @@ -173,6 +174,7 @@ - PowerCageMedium - MagazineGrenadeEmpty - GrenadeFlash + - GrenadeBlast - ShuttleGunSvalinnMachineGunCircuitboard - ShuttleGunPerforatorCircuitboard - ShuttleGunFriendshipCircuitboard @@ -192,6 +194,7 @@ cost: 15000 recipeUnlocks: - WeaponAdvancedLaser + - PortableRecharger - type: technology id: ExperimentalBatteryAmmo diff --git a/Resources/Prototypes/Research/civilianservices.yml b/Resources/Prototypes/Research/civilianservices.yml index de96588967..26b519e1ef 100644 --- a/Resources/Prototypes/Research/civilianservices.yml +++ b/Resources/Prototypes/Research/civilianservices.yml @@ -70,6 +70,7 @@ - BorgModuleClowning - DawInstrumentMachineCircuitboard - MassMediaCircuitboard + - JukeboxCircuitBoard - type: technology id: RoboticCleanliness @@ -190,8 +191,6 @@ - WeaponSprayNozzle - ClothingBackpackWaterTank -# Tier 3 - - type: technology id: BluespaceCargoTransport name: research-technology-bluespace-cargo-transport @@ -199,11 +198,13 @@ sprite: Structures/cargo_telepad.rsi state: display discipline: CivilianServices - tier: 3 + tier: 2 cost: 15000 recipeUnlocks: - CargoTelepadMachineCircuitboard +# Tier 3 + - type: technology id: QuantumFiberWeaving name: research-technology-quantum-fiber-weaving diff --git a/Resources/Prototypes/Research/experimental.yml b/Resources/Prototypes/Research/experimental.yml index 65186340d7..60017fc98d 100644 --- a/Resources/Prototypes/Research/experimental.yml +++ b/Resources/Prototypes/Research/experimental.yml @@ -56,6 +56,7 @@ cost: 5000 recipeUnlocks: - TechDiskComputerCircuitboard + - ReverseEngineeringMachineCircuitboard #DeltaV - type: technology id: MagnetsTech @@ -83,17 +84,30 @@ # Tier 2 +- type: technology + id: AdvancedParts + name: research-technology-advanced-parts + icon: + sprite: Objects/Misc/stock_parts.rsi + state: advanced_matter_bin + discipline: Experimental + tier: 2 + cost: 10000 + recipeUnlocks: + - AdvancedCapacitorStockPart + - AdvancedMatterBinStockPart + - NanoManipulatorStockPart + - type: technology id: AbnormalArtifactManipulation name: research-technology-abnormal-artifact-manipulation icon: - sprite: Structures/Machines/traversal_distorter.rsi - state: display + sprite: Structures/Machines/artifact_crusher.rsi + state: icon discipline: Experimental tier: 2 cost: 5000 recipeUnlocks: - - TraversalDistorterMachineCircuitboard - ArtifactCrusherMachineCircuitboard - type: technology @@ -137,20 +151,49 @@ - WeaponParticleDecelerator - HoloprojectorField +- type: technology + id: Metempsychosis + name: research-technology-metempsychosis + icon: + sprite: Structures/Machines/metempsychotic.rsi + state: cloning_idle + discipline: Experimental + tier: 2 + cost: 15000 + recipeUnlocks: + - BiomassReclaimerMachineCircuitboard + - CloningConsoleComputerCircuitboard + - MedicalScannerMachineCircuitboard + - MetempsychoticMachineCircuitboard + # Tier 3 -#- type: technology # DeltaV - LRP -# id: GravityManipulation -# name: research-technology-gravity-manipulation -# icon: -# sprite: Objects/Weapons/Guns/Launchers/tether_gun.rsi -# state: base -# discipline: Experimental -# tier: 3 -# cost: 10000 -# recipeUnlocks: -# - WeaponForceGun -# - WeaponTetherGun +- type: technology + id: SuperParts + name: research-technology-super-parts + icon: + sprite: Objects/Misc/stock_parts.rsi + state: super_matter_bin + discipline: Experimental + tier: 3 + cost: 15000 + recipeUnlocks: + - SuperCapacitorStockPart + - SuperMatterBinStockPart + - PicoManipulatorStockPart + +- type: technology + id: GravityManipulation + name: research-technology-gravity-manipulation + icon: + sprite: Objects/Weapons/Guns/Launchers/tether_gun.rsi + state: base + discipline: Experimental + tier: 3 + cost: 10000 + recipeUnlocks: + - WeaponForceGun + - WeaponTetherGun - type: technology id: QuantumLeaping diff --git a/Resources/Prototypes/Research/industrial.yml b/Resources/Prototypes/Research/industrial.yml index 33377f5252..2e1e7f5b26 100644 --- a/Resources/Prototypes/Research/industrial.yml +++ b/Resources/Prototypes/Research/industrial.yml @@ -14,7 +14,8 @@ - BorgModuleMining - OreProcessorIndustrialMachineCircuitboard - OreBagOfHolding - - SalvageExpeditionsComputerCircuitboard # DeltaV + - ClothingMaskWeldingGas + - SalvageExpeditionsComputerCircuitboard - type: technology id: AdvancedPowercells @@ -53,6 +54,7 @@ recipeUnlocks: - AutolatheHyperConvectionMachineCircuitboard - ProtolatheHyperConvectionMachineCircuitboard + - CircuitImprinterHyperConvectionMachineCircuitboard - SheetifierMachineCircuitboard - type: technology @@ -105,18 +107,18 @@ - RipleyPeripheralsElectronics - MechEquipmentGrabber -#- type: technology -# id: Grappling -# name: research-technology-grappling -# icon: -# sprite: Objects/Weapons/Guns/Launchers/grappling_gun.rsi -# state: base -# discipline: Industrial -# tier: 1 -# cost: 5000 -# recipeUnlocks: -# - WeaponGrapplingGun -# - BorgModuleGrapplingGun +- type: technology + id: Grappling + name: research-technology-grappling + icon: + sprite: Objects/Weapons/Guns/Launchers/grappling_gun.rsi + state: base + discipline: Industrial + tier: 1 + cost: 5000 + recipeUnlocks: + - WeaponGrapplingGun + - BorgModuleGrapplingGun # Tier 2 diff --git a/Resources/Prototypes/Roles/Antags/Thief.yml b/Resources/Prototypes/Roles/Antags/Thief.yml index 131db8cf1d..2715e6d96d 100644 --- a/Resources/Prototypes/Roles/Antags/Thief.yml +++ b/Resources/Prototypes/Roles/Antags/Thief.yml @@ -3,4 +3,4 @@ name: roles-antag-thief-name antagonist: true setPreference: true - objective: roles-antag-thief-objective + objective: roles-antag-thief-objective \ No newline at end of file diff --git a/Resources/Prototypes/Roles/Antags/nukeops.yml b/Resources/Prototypes/Roles/Antags/nukeops.yml index 26024b44b2..7b2d9893b6 100644 --- a/Resources/Prototypes/Roles/Antags/nukeops.yml +++ b/Resources/Prototypes/Roles/Antags/nukeops.yml @@ -19,10 +19,10 @@ objective: roles-antag-nuclear-operative-agent-objective requirements: - !type:CharacterOverallTimeRequirement - min: 108000 # DeltaV - 30 hours - - !type:CharacterDepartmentTimeRequirement # DeltaV - Medical dept time requirement + min: 108000 # 30 hours + - !type:CharacterDepartmentTimeRequirement department: Medical - min: 36000 # DeltaV - 10 hours + min: 36000 # 10 hours - type: antag id: NukeopsCommander @@ -40,3 +40,23 @@ department: Command min: 36000 # DeltaV - 10 hours - !type:CharacterWhitelistRequirement + +#Lone Operative Gear +- type: startingGear + id: SyndicateLoneOperativeGearFull + equipment: + jumpsuit: ClothingUniformJumpsuitOperative + back: ClothingBackpackDuffelSyndicateOperative + mask: ClothingMaskGasSyndicate + eyes: ClothingEyesHudSyndicate + ears: ClothingHeadsetAltSyndicate + gloves: ClothingHandsGlovesCombat + outerClothing: ClothingOuterHardsuitSyndie + shoes: ClothingShoesBootsCombatFilled + id: SyndiPDA + pocket1: DoubleEmergencyOxygenTankFilled + pocket2: BaseUplinkRadio40TC + belt: ClothingBeltMilitaryWebbing + innerClothingSkirt: ClothingUniformJumpskirtOperative + satchel: ClothingBackpackDuffelSyndicateOperative + duffelbag: ClothingBackpackDuffelSyndicateOperative diff --git a/Resources/Prototypes/Roles/Antags/terminator.yml b/Resources/Prototypes/Roles/Antags/terminator.yml deleted file mode 100644 index ef1f176b8d..0000000000 --- a/Resources/Prototypes/Roles/Antags/terminator.yml +++ /dev/null @@ -1,6 +0,0 @@ -- type: antag - id: Terminator - name: roles-antag-terminator-name - antagonist: true - setPreference: false - objective: roles-antag-terminator-objective diff --git a/Resources/Prototypes/Roles/Antags/traitor.yml b/Resources/Prototypes/Roles/Antags/traitor.yml index fec2280ddc..05b0553c78 100644 --- a/Resources/Prototypes/Roles/Antags/traitor.yml +++ b/Resources/Prototypes/Roles/Antags/traitor.yml @@ -6,4 +6,4 @@ objective: roles-antag-syndicate-agent-objective requirements: - !type:CharacterOverallTimeRequirement # DeltaV - Playtime requirement - min: 86400 # DeltaV - 24 hours + min: 86400 # DeltaV - 24 hours \ No newline at end of file diff --git a/Resources/Prototypes/Roles/Ghostroles/syndicate.yml b/Resources/Prototypes/Roles/Ghostroles/syndicate.yml new file mode 100644 index 0000000000..24c0d8b3e3 --- /dev/null +++ b/Resources/Prototypes/Roles/Ghostroles/syndicate.yml @@ -0,0 +1,27 @@ +- type: ghostRole + id: SyndicateKobold + name: ghost-role-information-syndicate-kobold-reinforcement-name + description: ghost-role-information-syndicate-kobold-reinforcement-description + rules: ghost-role-information-syndicate-kobold-reinforcement-rules + entityPrototype: MobKoboldSyndicateAgent + +- type: ghostRole + id: SyndicateKoboldNukeops + name: ghost-role-information-syndicate-kobold-reinforcement-name + description: ghost-role-information-syndicate-kobold-reinforcement-description + rules: ghost-role-information-syndicate-kobold-reinforcement-rules + entityPrototype: MobKoboldSyndicateAgentNukeops + +- type: ghostRole + id: SyndicateMonkey + name: ghost-role-information-syndicate-monkey-reinforcement-name + description: ghost-role-information-syndicate-monkey-reinforcement-description + rules: ghost-role-information-syndicate-monkey-reinforcement-name + entityPrototype: MobMonkeySyndicateAgent + +- type: ghostRole + id: SyndicateMonkeyNukeops + name: ghost-role-information-syndicate-monkey-reinforcement-name + description: ghost-role-information-syndicate-monkey-reinforcement-description + rules: ghost-role-information-syndicate-monkey-reinforcement-name + entityPrototype: MobMonkeySyndicateAgentNukeops \ No newline at end of file diff --git a/Resources/Prototypes/Roles/Jobs/Civilian/chaplain.yml b/Resources/Prototypes/Roles/Jobs/Civilian/chaplain.yml index 0b642ad101..fe7175172d 100644 --- a/Resources/Prototypes/Roles/Jobs/Civilian/chaplain.yml +++ b/Resources/Prototypes/Roles/Jobs/Civilian/chaplain.yml @@ -5,32 +5,50 @@ playTimeTracker: JobChaplain requirements: - !type:CharacterDepartmentTimeRequirement - department: Epistemics # DeltaV - Epistemics Department replacing Science - min: 3600 # 1 hour + department: Epistemics # Chaplain is now one of the station's "Crew-Aligned Wizards" + min: 14400 # 4 hours + - !type:CharacterLogicOrRequirement + requirements: + - !type:CharacterSpeciesRequirement + inverted: true + species: + - IPC + - !type:CharacterTraitRequirement + traits: + - AnomalousPositronics + - !type:CharacterLogicOrRequirement + requirements: + - !type:CharacterSpeciesRequirement + inverted: true + species: + - Shadowkin startingGear: ChaplainGear icon: "JobIconChaplain" supervisors: job-supervisors-rd access: - Chapel - - Research # DeltaV - Move Chaplain into Epistemics + - Research - Maintenance special: - !type:AddComponentSpecial components: - - type: BibleUser #Lets them heal with bibles - - !type:AddComponentSpecial - components: - - type: PsionicBonusChance #Nyano - Summary: makes it more likely to become psionic. - multiplier: 3 + - type: BibleUser + - type: Psionic + powerRollMultiplier: 3 + - type: InnatePsionicPowers + powersToAdd: + - TelepathyPower + - HealingWordPower + - SummonRemiliaPower - type: startingGear id: ChaplainGear equipment: jumpsuit: ClothingUniformJumpsuitChaplain - back: ClothingBackpackChaplainFilled + back: ClothingBackpack shoes: ClothingShoesColorBlack id: ChaplainPDA - ears: ClothingHeadsetScience # DeltaV - Move Chaplain into Epistemics + ears: ClothingHeadsetScience innerClothingSkirt: ClothingUniformJumpskirtChaplain satchel: ClothingBackpackSatchelChaplainFilled duffelbag: ClothingBackpackDuffelChaplainFilled diff --git a/Resources/Prototypes/Roles/Jobs/Civilian/lawyer.yml b/Resources/Prototypes/Roles/Jobs/Civilian/lawyer.yml index d188b69496..5bba2fe442 100644 --- a/Resources/Prototypes/Roles/Jobs/Civilian/lawyer.yml +++ b/Resources/Prototypes/Roles/Jobs/Civilian/lawyer.yml @@ -26,7 +26,7 @@ shoes: ClothingShoesBootsLaceup id: LawyerPDA ears: ClothingHeadsetSecurity - # TODO add copy of space law + pocket1: BookSecurity inhand: - BriefcaseBrownFilled innerClothingSkirt: ClothingUniformJumpskirtLawyerBlack diff --git a/Resources/Prototypes/Roles/Jobs/Civilian/librarian.yml b/Resources/Prototypes/Roles/Jobs/Civilian/librarian.yml index 59126f1ac7..a324da0d0b 100644 --- a/Resources/Prototypes/Roles/Jobs/Civilian/librarian.yml +++ b/Resources/Prototypes/Roles/Jobs/Civilian/librarian.yml @@ -4,15 +4,40 @@ description: job-description-librarian playTimeTracker: JobLibrarian requirements: - - !type:CharacterOverallTimeRequirement #DeltaV - min: 3600 # 1 hr + - !type:CharacterDepartmentTimeRequirement + department: Epistemics + min: 14400 + - !type:CharacterLogicOrRequirement + requirements: + - !type:CharacterSpeciesRequirement + inverted: true + species: + - IPC + - !type:CharacterTraitRequirement + traits: + - AnomalousPositronics + - !type:CharacterLogicOrRequirement + requirements: + - !type:CharacterSpeciesRequirement + inverted: true + species: + - Shadowkin startingGear: LibrarianGear icon: "JobIconLibrarian" supervisors: job-supervisors-css access: - - Service + - Research - Maintenance - - Library # DeltaV - Add Library access + - Library + special: + - !type:AddComponentSpecial + components: + - type: Psionic + - type: InnatePsionicPowers + powersToAdd: + - XenoglossyPower + - TelepathyPower + - type: startingGear id: LibrarianGear @@ -20,10 +45,11 @@ jumpsuit: ClothingUniformJumpsuitLibrarian back: ClothingBackpackLibrarianFilled shoes: ClothingShoesBootsLaceup + outerClothing: ClothingOuterCoatRnd id: LibrarianPDA - ears: ClothingHeadsetService + ears: ClothingHeadsetScience pocket1: d10Dice - pocket2: HandLabeler # for making named bestsellers + pocket2: HandLabeler innerClothingSkirt: ClothingUniformJumpskirtLibrarian satchel: ClothingBackpackSatchelLibrarianFilled duffelbag: ClothingBackpackDuffelLibrarianFilled diff --git a/Resources/Prototypes/Roles/Jobs/Civilian/mime.yml b/Resources/Prototypes/Roles/Jobs/Civilian/mime.yml index 588657fd47..f636522ae3 100644 --- a/Resources/Prototypes/Roles/Jobs/Civilian/mime.yml +++ b/Resources/Prototypes/Roles/Jobs/Civilian/mime.yml @@ -16,7 +16,6 @@ special: - !type:AddComponentSpecial components: - - type: Psionic # Nyano - Summary: Makes the mime psionic. - type: MimePowers - type: FrenchAccent diff --git a/Resources/Prototypes/Roles/Jobs/Command/captain.yml b/Resources/Prototypes/Roles/Jobs/Command/captain.yml index d624b349d5..219684cc7d 100644 --- a/Resources/Prototypes/Roles/Jobs/Command/captain.yml +++ b/Resources/Prototypes/Roles/Jobs/Command/captain.yml @@ -40,10 +40,6 @@ - !type:AddComponentSpecial components: - type: CommandStaff - - !type:AddComponentSpecial - components: - - type: PsionicBonusChance #Nyano - Summary: makes it more likely to become psionic. - flatBonus: 0.025 - type: startingGear id: CaptainGear diff --git a/Resources/Prototypes/Roles/Jobs/Command/head_of_personnel.yml b/Resources/Prototypes/Roles/Jobs/Command/head_of_personnel.yml index 8aab8eaf69..534a890703 100644 --- a/Resources/Prototypes/Roles/Jobs/Command/head_of_personnel.yml +++ b/Resources/Prototypes/Roles/Jobs/Command/head_of_personnel.yml @@ -64,10 +64,6 @@ - !type:AddComponentSpecial components: - type: CommandStaff - - !type:AddComponentSpecial - components: - - type: PsionicBonusChance #Nyano - Summary: makes it more likely to become psionic. - flatBonus: 0.025 - type: startingGear id: HoPGear diff --git a/Resources/Prototypes/Roles/Jobs/Engineering/atmospheric_technician.yml b/Resources/Prototypes/Roles/Jobs/Engineering/atmospheric_technician.yml index 0382ba9b87..e3bc642cd2 100644 --- a/Resources/Prototypes/Roles/Jobs/Engineering/atmospheric_technician.yml +++ b/Resources/Prototypes/Roles/Jobs/Engineering/atmospheric_technician.yml @@ -22,12 +22,12 @@ id: AtmosphericTechnicianGear equipment: jumpsuit: ClothingUniformJumpsuitAtmos - back: ClothingBackpackAtmosphericsFilled + back: ClothingBackpackEngineeringFilled shoes: ClothingShoesColorWhite eyes: ClothingEyesGlassesMeson id: AtmosPDA belt: ClothingBeltUtilityEngineering ears: ClothingHeadsetEngineering innerClothingSkirt: ClothingUniformJumpskirtAtmos - satchel: ClothingBackpackSatchelAtmosphericsFilled - duffelbag: ClothingBackpackDuffelAtmosphericsFilled + satchel: ClothingBackpackSatchelEngineeringFilled + duffelbag: ClothingBackpackDuffelEngineeringFilled diff --git a/Resources/Prototypes/Roles/Jobs/Engineering/chief_engineer.yml b/Resources/Prototypes/Roles/Jobs/Engineering/chief_engineer.yml index 92d69fbfc9..03879fbc46 100644 --- a/Resources/Prototypes/Roles/Jobs/Engineering/chief_engineer.yml +++ b/Resources/Prototypes/Roles/Jobs/Engineering/chief_engineer.yml @@ -36,10 +36,6 @@ - !type:AddComponentSpecial components: - type: CommandStaff - - !type:AddComponentSpecial - components: - - type: PsionicBonusChance #Nyano - Summary: makes it more likely to become psionic. - flatBonus: 0.025 - type: startingGear id: ChiefEngineerGear diff --git a/Resources/Prototypes/Roles/Jobs/Fun/emergencyresponseteam.yml b/Resources/Prototypes/Roles/Jobs/Fun/emergencyresponseteam.yml index 22d0a4d77f..f1e8fc9cf8 100644 --- a/Resources/Prototypes/Roles/Jobs/Fun/emergencyresponseteam.yml +++ b/Resources/Prototypes/Roles/Jobs/Fun/emergencyresponseteam.yml @@ -27,7 +27,7 @@ id: ERTLeaderPDA ears: ClothingHeadsetAltCentCom belt: ClothingBeltSecurityFilled - pocket1: WeaponPistolN1984Nonlethal + pocket1: WeaponPistolN1984NonLethal pocket2: FlashlightSeclite - type: startingGear @@ -44,7 +44,7 @@ id: ERTLeaderPDA ears: ClothingHeadsetAltCentCom belt: ClothingBeltSecurityFilled - pocket1: WeaponPistolN1984Nonlethal + pocket1: WeaponPistolN1984NonLethal pocket2: FlashlightSeclite - type: startingGear diff --git a/Resources/Prototypes/Roles/Jobs/Fun/misc_startinggear.yml b/Resources/Prototypes/Roles/Jobs/Fun/misc_startinggear.yml index 434a7c1083..7dd3e6823a 100644 --- a/Resources/Prototypes/Roles/Jobs/Fun/misc_startinggear.yml +++ b/Resources/Prototypes/Roles/Jobs/Fun/misc_startinggear.yml @@ -79,6 +79,15 @@ satchel: ClothingBackpackDuffelSyndicateOperative duffelbag: ClothingBackpackDuffelSyndicateOperative +# Syndicate Operative Outfit - Civilian +- type: startingGear + id: SyndicateOperativeGearCivilian + equipment: + jumpsuit: ClothingUniformJumpsuitSyndieFormal + back: ClothingBackpackDuffelSyndicate + shoes: ClothingShoesBootsCombat + gloves: ClothingHandsGlovesColorBlack + #Syndicate Operative Outfit - Basic - type: startingGear id: SyndicateOperativeGearBasic @@ -144,7 +153,7 @@ jumpsuit: ClothingUniformJumpsuitOperative back: ClothingBackpackDuffelSyndicateOperativeMedic mask: ClothingMaskGasSyndicate - eyes: ClothingEyesHudSyndicateMed # - Delta-V + eyes: ClothingEyesHudSyndicateAgent ears: ClothingHeadsetAltSyndicate gloves: ClothingHandsGlovesCombat outerClothing: ClothingOuterHardsuitSyndieMedic @@ -293,8 +302,18 @@ #Head Rev Gear - type: startingGear id: HeadRevGear - equipment: - pocket2: Flash + storage: + back: + - Flash + - ClothingEyesGlassesSunglasses + +#Thief Gear +- type: startingGear + id: ThiefGear + storage: + back: + - ToolboxThief + - ClothingHandsChameleonThief #Gladiator with spear - type: startingGear diff --git a/Resources/Prototypes/Roles/Jobs/Medical/chemist.yml b/Resources/Prototypes/Roles/Jobs/Medical/chemist.yml index 38369553f9..0a9eab6be5 100644 --- a/Resources/Prototypes/Roles/Jobs/Medical/chemist.yml +++ b/Resources/Prototypes/Roles/Jobs/Medical/chemist.yml @@ -19,6 +19,8 @@ - !type:AddComponentSpecial components: - type: CPRTraining + - type: SurgerySpeedModifier + SpeedModifier: 1.75 - type: startingGear id: ChemistGear diff --git a/Resources/Prototypes/Roles/Jobs/Medical/chief_medical_officer.yml b/Resources/Prototypes/Roles/Jobs/Medical/chief_medical_officer.yml index 96ea2daf79..9a262d0c43 100644 --- a/Resources/Prototypes/Roles/Jobs/Medical/chief_medical_officer.yml +++ b/Resources/Prototypes/Roles/Jobs/Medical/chief_medical_officer.yml @@ -11,7 +11,16 @@ department: Medical min: 18000 # 5 hours - !type:CharacterOverallTimeRequirement - min: 86400 # 24 hours + min: 72000 # DeltaV - 20 hours + - !type:CharacterLogicOrRequirement + requirements: + - !type:CharacterSpeciesRequirement + inverted: true + species: + - Shadowkin + - !type:CharacterTraitRequirement + traits: + - ShadowkinBlackeye weight: 10 startingGear: CMOGear icon: "JobIconChiefMedicalOfficer" @@ -36,9 +45,9 @@ - type: CommandStaff - !type:AddComponentSpecial components: - - type: PsionicBonusChance #Nyano - Summary: makes it more likely to become psionic. - flatBonus: 0.025 - type: CPRTraining + - type: SurgerySpeedModifier + SpeedModifier: 2.5 - type: startingGear id: CMOGear diff --git a/Resources/Prototypes/Roles/Jobs/Medical/medical_doctor.yml b/Resources/Prototypes/Roles/Jobs/Medical/medical_doctor.yml index 0946b6ba20..1ae8dab566 100644 --- a/Resources/Prototypes/Roles/Jobs/Medical/medical_doctor.yml +++ b/Resources/Prototypes/Roles/Jobs/Medical/medical_doctor.yml @@ -16,6 +16,8 @@ - !type:AddComponentSpecial components: - type: CPRTraining + - type: SurgerySpeedModifier + SpeedModifier: 1.75 - type: startingGear id: DoctorGear diff --git a/Resources/Prototypes/Roles/Jobs/Medical/medical_intern.yml b/Resources/Prototypes/Roles/Jobs/Medical/medical_intern.yml index 5fc409d5f2..fbe23488d1 100644 --- a/Resources/Prototypes/Roles/Jobs/Medical/medical_intern.yml +++ b/Resources/Prototypes/Roles/Jobs/Medical/medical_intern.yml @@ -20,6 +20,8 @@ - !type:AddComponentSpecial components: - type: CPRTraining + - type: SurgerySpeedModifier + SpeedModifier: 1.5 - type: startingGear id: MedicalInternGear diff --git a/Resources/Prototypes/Roles/Jobs/Medical/paramedic.yml b/Resources/Prototypes/Roles/Jobs/Medical/paramedic.yml index 098da8d5ea..125a5adbf3 100644 --- a/Resources/Prototypes/Roles/Jobs/Medical/paramedic.yml +++ b/Resources/Prototypes/Roles/Jobs/Medical/paramedic.yml @@ -28,6 +28,8 @@ - !type:AddComponentSpecial components: - type: CPRTraining + - type: SurgerySpeedModifier + SpeedModifier: 1.75 - type: startingGear id: ParamedicGear diff --git a/Resources/Prototypes/Roles/Jobs/Medical/senior_physician.yml b/Resources/Prototypes/Roles/Jobs/Medical/senior_physician.yml index d13fd18afd..65ac51621d 100644 --- a/Resources/Prototypes/Roles/Jobs/Medical/senior_physician.yml +++ b/Resources/Prototypes/Roles/Jobs/Medical/senior_physician.yml @@ -25,6 +25,8 @@ - !type:AddComponentSpecial components: - type: CPRTraining + - type: SurgerySpeedModifier + SpeedModifier: 2.0 - type: startingGear id: SeniorPhysicianGear diff --git a/Resources/Prototypes/Roles/Jobs/Science/research_director.yml b/Resources/Prototypes/Roles/Jobs/Science/research_director.yml index 6f868c8c2e..c9b48c75c5 100644 --- a/Resources/Prototypes/Roles/Jobs/Science/research_director.yml +++ b/Resources/Prototypes/Roles/Jobs/Science/research_director.yml @@ -10,7 +10,22 @@ department: Epistemics min: 18000 # 5 hours - !type:CharacterOverallTimeRequirement - min: 86400 # 24 hours + min: 72000 # DeltaV - 20 hours + - !type:CharacterLogicOrRequirement + requirements: + - !type:CharacterSpeciesRequirement + inverted: true + species: + - IPC + - !type:CharacterTraitRequirement + traits: + - AnomalousPositronics + - !type:CharacterLogicOrRequirement + requirements: + - !type:CharacterSpeciesRequirement + inverted: true + species: + - Shadowkin weight: 10 startingGear: ResearchDirectorGear icon: "JobIconResearchDirector" @@ -30,12 +45,17 @@ components: - type: BibleUser # Nyano - Lets them heal with bibles - type: Psionic # Nyano - They start with telepathic chat - - type: DispelPower # Nyano - They get the Dispel psionic power on spawn + powerSlots: 3 - !type:AddImplantSpecial implants: [ MindShieldImplant ] - !type:AddComponentSpecial components: - type: CommandStaff + - type: InnatePsionicPowers + powersToAdd: + - DispelPower + - MetapsionicPower + - TelepathyPower - type: startingGear id: ResearchDirectorGear diff --git a/Resources/Prototypes/Roles/Jobs/Science/roboticist.yml b/Resources/Prototypes/Roles/Jobs/Science/roboticist.yml new file mode 100644 index 0000000000..1d2434a4e0 --- /dev/null +++ b/Resources/Prototypes/Roles/Jobs/Science/roboticist.yml @@ -0,0 +1,28 @@ +- type: job + id: Roboticist + name: job-name-roboticist + description: job-description-roboticist + playTimeTracker: JobRoboticist + requirements: + - !type:CharacterDepartmentTimeRequirement + department: Epistemics + min: 14400 # 4 hours - same as scientist + startingGear: RoboticistGear + icon: "JobIconRoboticist" + supervisors: job-supervisors-rd + access: + - Research + - Maintenance + +- type: startingGear + id: RoboticistGear + equipment: + jumpsuit: ClothingUniformJumpsuitRoboticist + back: ClothingBackpackRoboticsFilled + shoes: ClothingShoesColorBlack + outerClothing: ClothingOuterCoatRobo + id: RoboticsPDA + ears: ClothingHeadsetRobotics + innerClothingSkirt: ClothingUniformJumpskirtRoboticist + satchel: ClothingBackpackSatchelRoboticsFilled + duffelbag: ClothingBackpackDuffelRoboticsFilled diff --git a/Resources/Prototypes/Roles/Jobs/Science/senior_researcher.yml b/Resources/Prototypes/Roles/Jobs/Science/senior_researcher.yml index 9023425030..25b170a46e 100644 --- a/Resources/Prototypes/Roles/Jobs/Science/senior_researcher.yml +++ b/Resources/Prototypes/Roles/Jobs/Science/senior_researcher.yml @@ -3,7 +3,7 @@ name: job-name-senior-researcher description: job-description-senior-researcher playTimeTracker: JobSeniorResearcher - setPreference: false # DeltaV - Disable Senior Roles round start selection + setPreference: true requirements: - !type:CharacterDepartmentTimeRequirement department: Epistemics # DeltaV - Epistemics Department replacing Science diff --git a/Resources/Prototypes/Roles/Jobs/Security/detective.yml b/Resources/Prototypes/Roles/Jobs/Security/detective.yml index da58fdcd57..98793451ba 100644 --- a/Resources/Prototypes/Roles/Jobs/Security/detective.yml +++ b/Resources/Prototypes/Roles/Jobs/Security/detective.yml @@ -28,7 +28,7 @@ id: DetectiveGear equipment: jumpsuit: ClothingUniformJumpsuitDetective - back: ClothingBackpackSecurityFilledDetective + back: ClothingBackpackSecurity shoes: ClothingShoesBootsCombatFilled eyes: ClothingEyesGlassesSunglasses head: ClothingHeadHatFedoraBrown @@ -37,5 +37,5 @@ ears: ClothingHeadsetSecurity belt: ClothingBeltHolsterFilled innerClothingSkirt: ClothingUniformJumpskirtDetective - satchel: ClothingBackpackSatchelSecurityFilledDetective - duffelbag: ClothingBackpackDuffelSecurityFilledDetective + satchel: ClothingBackpackSatchelSecurity + duffelbag: ClothingBackpackDuffelSecurity diff --git a/Resources/Prototypes/Roles/Jobs/Security/head_of_security.yml b/Resources/Prototypes/Roles/Jobs/Security/head_of_security.yml index b93eef84dc..8331806d4b 100644 --- a/Resources/Prototypes/Roles/Jobs/Security/head_of_security.yml +++ b/Resources/Prototypes/Roles/Jobs/Security/head_of_security.yml @@ -38,10 +38,6 @@ - !type:AddComponentSpecial components: - type: CommandStaff - - !type:AddComponentSpecial - components: - - type: PsionicBonusChance #Nyano - Summary: makes it more likely to become psionic. - flatBonus: 0.025 - type: startingGear id: HoSGear @@ -54,7 +50,6 @@ gloves: ClothingHandsGlovesCombat ears: ClothingHeadsetAltSecurity belt: ClothingBeltSecurityFilled - pocket1: WeaponPistolMk58Nonlethal innerClothingSkirt: ClothingUniformJumpskirtHoS satchel: ClothingBackpackSatchelHOSFilled duffelbag: ClothingBackpackDuffelHOSFilled diff --git a/Resources/Prototypes/Roles/Jobs/Security/security_cadet.yml b/Resources/Prototypes/Roles/Jobs/Security/security_cadet.yml index 15612036cf..3fab720c72 100644 --- a/Resources/Prototypes/Roles/Jobs/Security/security_cadet.yml +++ b/Resources/Prototypes/Roles/Jobs/Security/security_cadet.yml @@ -26,14 +26,13 @@ id: SecurityCadetGear equipment: jumpsuit: ClothingUniformJumpsuitColorRed - back: ClothingBackpackSecurityFilled + back: ClothingBackpackSecurity shoes: ClothingShoesBootsCombatFilled outerClothing: ClothingOuterArmorDuraVest # DeltaV - ClothingOuterArmorBasic replaced in favour of stab vest. Sucks to suck, cadets id: SecurityCadetPDA ears: ClothingHeadsetSecurity belt: ClothingBeltSecurityFilled -# pocket1: WeaponPistolMk58Nonlethal # DeltaV - Security Cadet doesn't spawn with a gun pocket2: BookSecurity innerClothingSkirt: ClothingUniformJumpskirtColorRed - satchel: ClothingBackpackSatchelSecurityFilled - duffelbag: ClothingBackpackDuffelSecurityFilled + satchel: ClothingBackpackSatchelSecurity + duffelbag: ClothingBackpackDuffelSecurity diff --git a/Resources/Prototypes/Roles/Jobs/Security/security_officer.yml b/Resources/Prototypes/Roles/Jobs/Security/security_officer.yml index 2a04c4bc96..57debffdf2 100644 --- a/Resources/Prototypes/Roles/Jobs/Security/security_officer.yml +++ b/Resources/Prototypes/Roles/Jobs/Security/security_officer.yml @@ -7,9 +7,6 @@ # Parkstation-Playtime - !type:CharacterDepartmentTimeRequirement department: Security - min: 3600 # 1 hour - - !type:CharacterOverallTimeRequirement - min: 14400 # 4 hours startingGear: SecurityOfficerGear icon: "JobIconSecurityOfficer" supervisors: job-supervisors-hos @@ -29,7 +26,7 @@ id: SecurityOfficerGear equipment: jumpsuit: ClothingUniformJumpsuitSec - back: ClothingBackpackSecurityFilled + back: ClothingBackpackSecurity shoes: ClothingShoesBootsCombatFilled eyes: ClothingEyesGlassesSunglasses head: ClothingHeadHelmetBasic @@ -37,7 +34,6 @@ id: SecurityPDA ears: ClothingHeadsetSecurity belt: ClothingBeltSecurityFilled - pocket1: WeaponPistolMk58Nonlethal innerClothingSkirt: ClothingUniformJumpskirtSec - satchel: ClothingBackpackSatchelSecurityFilled - duffelbag: ClothingBackpackDuffelSecurityFilled + satchel: ClothingBackpackSatchelSecurity + duffelbag: ClothingBackpackDuffelSecurity diff --git a/Resources/Prototypes/Roles/Jobs/Security/senior_officer.yml b/Resources/Prototypes/Roles/Jobs/Security/senior_officer.yml index 2623adf1fd..d395e0f5cd 100644 --- a/Resources/Prototypes/Roles/Jobs/Security/senior_officer.yml +++ b/Resources/Prototypes/Roles/Jobs/Security/senior_officer.yml @@ -35,7 +35,7 @@ id: SeniorOfficerGear equipment: jumpsuit: ClothingUniformJumpsuitSeniorOfficer - back: ClothingBackpackSecurityFilled + back: ClothingBackpackSecurity shoes: ClothingShoesBootsCombatFilled eyes: ClothingEyesGlassesSecurity head: ClothingHeadHatBeretSecurity @@ -43,7 +43,6 @@ id: SeniorOfficerPDA ears: ClothingHeadsetSecurity belt: ClothingBeltSecurityFilled - pocket1: WeaponPistolMk58Nonlethal innerClothingSkirt: ClothingUniformJumpskirtSeniorOfficer - satchel: ClothingBackpackSatchelSecurityFilled - duffelbag: ClothingBackpackDuffelSecurityFilled + satchel: ClothingBackpackSatchelSecurity + duffelbag: ClothingBackpackDuffelSecurity diff --git a/Resources/Prototypes/Roles/Jobs/Security/warden.yml b/Resources/Prototypes/Roles/Jobs/Security/warden.yml index 9710a92d89..2bc72c2baa 100644 --- a/Resources/Prototypes/Roles/Jobs/Security/warden.yml +++ b/Resources/Prototypes/Roles/Jobs/Security/warden.yml @@ -32,14 +32,13 @@ equipment: head: ClothingHeadHatWarden jumpsuit: ClothingUniformJumpsuitWarden - back: ClothingBackpackSecurityFilled + back: ClothingBackpackSecurity shoes: ClothingShoesBootsCombatFilled eyes: ClothingEyesGlassesSunglasses outerClothing: ClothingOuterCoatWarden id: WardenPDA ears: ClothingHeadsetSecurity belt: ClothingBeltSecurityFilled - pocket1: WeaponPistolMk58Nonlethal innerClothingSkirt: ClothingUniformJumpskirtWarden - satchel: ClothingBackpackSatchelSecurityFilled - duffelbag: ClothingBackpackDuffelSecurityFilled + satchel: ClothingBackpackSatchelSecurity + duffelbag: ClothingBackpackDuffelSecurity diff --git a/Resources/Prototypes/Roles/Jobs/departments.yml b/Resources/Prototypes/Roles/Jobs/departments.yml index f003d7e0cc..18c6e912fe 100644 --- a/Resources/Prototypes/Roles/Jobs/departments.yml +++ b/Resources/Prototypes/Roles/Jobs/departments.yml @@ -25,7 +25,6 @@ - ChiefServiceSupervisor # Parkstation-CSS - Janitor # - Lawyer # DeltaV - Move Lawyer into Justice - - Librarian - Mime - Musician - Passenger @@ -105,6 +104,8 @@ - ResearchAssistant - Chaplain # DeltaV - Move Chaplain into Epistemics - ForensicMantis # Nyanotrasen - ForensicMantis, see Resources/Prototypes/Nyanotrasen/Roles/Jobs/Epistemics/forensicmantis.yml + - Librarian + - Roboticist - type: department id: Specific diff --git a/Resources/Prototypes/Roles/play_time_trackers.yml b/Resources/Prototypes/Roles/play_time_trackers.yml index 27043cfbfe..a0abb1a107 100644 --- a/Resources/Prototypes/Roles/play_time_trackers.yml +++ b/Resources/Prototypes/Roles/play_time_trackers.yml @@ -121,6 +121,9 @@ - type: playTimeTracker id: JobResearchDirector +- type: playTimeTracker + id: JobRoboticist + - type: playTimeTracker id: JobSalvageSpecialist diff --git a/Resources/Prototypes/Shaders/displacement.yml b/Resources/Prototypes/Shaders/displacement.yml new file mode 100644 index 0000000000..5c90738008 --- /dev/null +++ b/Resources/Prototypes/Shaders/displacement.yml @@ -0,0 +1,10 @@ +- type: shader + id: DisplacedStencilDraw + kind: source + path: "/Textures/Shaders/displacement.swsl" + stencil: + ref: 1 + op: Keep + func: NotEqual + params: + displacementSize: 127 diff --git a/Resources/Prototypes/Shaders/shaders.yml b/Resources/Prototypes/Shaders/shaders.yml index e286dcb7a4..41d04137bb 100644 --- a/Resources/Prototypes/Shaders/shaders.yml +++ b/Resources/Prototypes/Shaders/shaders.yml @@ -99,3 +99,27 @@ id: Cataracts kind: source path: "/Textures/Shaders/cataracts.swsl" + +- type: shader + id: SaturationScale + kind: source + path: "/Textures/Shaders/saturationscale.swsl" + + # Flight shaders + +- type: shader + id: Flap + kind: source + path: "/Textures/Shaders/flap.swsl" + + # Shadowkin shaders + +- type: shader + id: ColorTint + kind: source + path: "/Textures/Shaders/color_tint.swsl" + +- type: shader + id: Ethereal + kind: source + path: "/Textures/Shaders/ethereal.swsl" \ No newline at end of file diff --git a/Resources/Prototypes/Shuttles/shuttle_incoming_event.yml b/Resources/Prototypes/Shuttles/shuttle_incoming_event.yml new file mode 100644 index 0000000000..5819a934bf --- /dev/null +++ b/Resources/Prototypes/Shuttles/shuttle_incoming_event.yml @@ -0,0 +1,29 @@ +- type: preloadedGrid + id: ShuttleStriker + path: /Maps/Shuttles/ShuttleEvent/striker.yml + copies: 2 + +- type: preloadedGrid + id: ShuttleCargoLost + path: /Maps/Shuttles/ShuttleEvent/lost_cargo.yml + copies: 2 + +- type: preloadedGrid + id: TravelingCuisine + path: /Maps/Shuttles/ShuttleEvent/traveling_china_cuisine.yml + copies: 2 + +- type: preloadedGrid + id: DisasterEvacPod + path: /Maps/Shuttles/ShuttleEvent/disaster_evacpod.yml + copies: 3 + +- type: preloadedGrid + id: Honki + path: /Maps/Shuttles/ShuttleEvent/honki.yml + copies: 1 + +- type: preloadedGrid + id: SyndieEvacPod + path: /Maps/Shuttles/ShuttleEvent/syndie_evacpod.yml + copies: 2 diff --git a/Resources/Prototypes/SoundCollections/NukeMusic.yml b/Resources/Prototypes/SoundCollections/NukeMusic.yml index 51782d8bd5..b101719139 100644 --- a/Resources/Prototypes/SoundCollections/NukeMusic.yml +++ b/Resources/Prototypes/SoundCollections/NukeMusic.yml @@ -3,4 +3,5 @@ files: - /Audio/StationEvents/running_out.ogg - /Audio/StationEvents/countdown.ogg - - /Audio/StationEvents/clearly_nuclear.ogg \ No newline at end of file + - /Audio/StationEvents/clearly_nuclear.ogg + - /Audio/StationEvents/chip_nightmare.ogg diff --git a/Resources/Prototypes/SoundCollections/buzzes.yml b/Resources/Prototypes/SoundCollections/buzzes.yml new file mode 100644 index 0000000000..3be51acba7 --- /dev/null +++ b/Resources/Prototypes/SoundCollections/buzzes.yml @@ -0,0 +1,12 @@ +- type: soundCollection + id: buzzes + files: + - /Audio/Effects/Buzzes/buzz1.ogg + - /Audio/Effects/Buzzes/buzz2.ogg + - /Audio/Effects/Buzzes/buzz3.ogg + - /Audio/Effects/Buzzes/buzz4.ogg + - /Audio/Effects/Buzzes/buzz5.ogg + - /Audio/Effects/Buzzes/buzz6.ogg + - /Audio/Effects/Buzzes/buzz7.ogg + - /Audio/Effects/Buzzes/buzz8.ogg + - /Audio/Effects/Buzzes/buzz9.ogg diff --git a/Resources/Prototypes/SoundCollections/emotes.yml b/Resources/Prototypes/SoundCollections/emotes.yml index 1fbd88b48f..f655a8cf6b 100644 --- a/Resources/Prototypes/SoundCollections/emotes.yml +++ b/Resources/Prototypes/SoundCollections/emotes.yml @@ -79,3 +79,20 @@ id: Weh files: - /Audio/Items/Toys/weh.ogg + +- type: soundCollection + id: IPCWhirrs + files: + - /Audio/Voice/IPC/whirr1.ogg + - /Audio/Voice/IPC/whirr2.ogg + - /Audio/Voice/IPC/whirr3.ogg + +- type: soundCollection + id: Mars + files: + - /Audio/Voice/Shadowkin/mar.ogg + +- type: soundCollection + id: Wurble + files: + - /Audio/Voice/Shadowkin/wurble.ogg \ No newline at end of file diff --git a/Resources/Prototypes/SoundCollections/expeditions.yml b/Resources/Prototypes/SoundCollections/expeditions.yml new file mode 100644 index 0000000000..526a16f3c2 --- /dev/null +++ b/Resources/Prototypes/SoundCollections/expeditions.yml @@ -0,0 +1,5 @@ +- type: soundCollection + id: ExpeditionEnd + files: + - /Audio/Expedition/tension_session.ogg + - /Audio/Expedition/deadline.ogg diff --git a/Resources/Prototypes/SoundCollections/flight.yml b/Resources/Prototypes/SoundCollections/flight.yml new file mode 100644 index 0000000000..cca2cfb014 --- /dev/null +++ b/Resources/Prototypes/SoundCollections/flight.yml @@ -0,0 +1,6 @@ +- type: soundCollection + id: WingFlaps + files: + - /Audio/Effects/Flight/wingflap1.ogg + - /Audio/Effects/Flight/wingflap2.ogg + - /Audio/Effects/Flight/wingflap3.ogg diff --git a/Resources/Prototypes/SoundCollections/footsteps.yml b/Resources/Prototypes/SoundCollections/footsteps.yml index bd51e6b445..e179584f6f 100644 --- a/Resources/Prototypes/SoundCollections/footsteps.yml +++ b/Resources/Prototypes/SoundCollections/footsteps.yml @@ -201,3 +201,13 @@ - /Audio/Effects/Footsteps/spurs1.ogg - /Audio/Effects/Footsteps/spurs2.ogg - /Audio/Effects/Footsteps/spurs3.ogg + +- type: soundCollection + id: FootstepBorg + files: + - /Audio/Effects/Footsteps/borgwalk1.ogg + +- type: soundCollection + id: FootstepHoverBorg + files: + - /Audio/Effects/Footsteps/borgwalk2.ogg diff --git a/Resources/Prototypes/SoundCollections/fox.yml b/Resources/Prototypes/SoundCollections/fox.yml new file mode 100644 index 0000000000..912ae24e9a --- /dev/null +++ b/Resources/Prototypes/SoundCollections/fox.yml @@ -0,0 +1,18 @@ +- type: soundCollection + id: Fox + files: + - /Audio/Animals/fox_squeak.ogg + - /Audio/Animals/fox1.ogg + - /Audio/Animals/fox2.ogg + - /Audio/Animals/fox3.ogg + - /Audio/Animals/fox4.ogg + - /Audio/Animals/fox5.ogg + - /Audio/Animals/fox6.ogg + - /Audio/Animals/fox7.ogg + - /Audio/Animals/fox8.ogg + - /Audio/Animals/fox9.ogg + - /Audio/Animals/fox10.ogg + - /Audio/Animals/fox11.ogg + - /Audio/Animals/fox12.ogg + - /Audio/Animals/fox13.ogg + - /Audio/Animals/fox14.ogg diff --git a/Resources/Prototypes/SoundCollections/glimmer_wisp.yml b/Resources/Prototypes/SoundCollections/glimmer_wisp.yml new file mode 100644 index 0000000000..4fc0990397 --- /dev/null +++ b/Resources/Prototypes/SoundCollections/glimmer_wisp.yml @@ -0,0 +1,6 @@ +- type: soundCollection + id: MagicMissile + files: + - /Audio/Effects/magic_missile_1.ogg + - /Audio/Effects/magic_missile_2.ogg + - /Audio/Effects/magic_missile_3.ogg diff --git a/Resources/Prototypes/SoundCollections/lobby.yml b/Resources/Prototypes/SoundCollections/lobby.yml index 263ea38645..8b9e286066 100644 --- a/Resources/Prototypes/SoundCollections/lobby.yml +++ b/Resources/Prototypes/SoundCollections/lobby.yml @@ -19,3 +19,5 @@ - /Audio/Lobby/hackers.ogg - /Audio/Lobby/every_light_is_blinking_at_once.ogg - /Audio/Lobby/DOS=HIGH,_UMB.ogg + - /Audio/Lobby/atomicamnesiammx.ogg + - /Audio/Lobby/Monument.ogg diff --git a/Resources/Prototypes/Species/arachne.yml b/Resources/Prototypes/Species/arachne.yml index ed494c37cc..583fc442e7 100644 --- a/Resources/Prototypes/Species/arachne.yml +++ b/Resources/Prototypes/Species/arachne.yml @@ -26,7 +26,16 @@ Chest: points: 1 required: false - Arms: + RightArm: + points: 2 + required: false + RightHand: + points: 2 + required: false + LeftArm: + points: 2 + required: false + LeftHand: points: 2 required: false diff --git a/Resources/Prototypes/Species/arachnid.yml b/Resources/Prototypes/Species/arachnid.yml index bc42f40fcd..69c8440460 100644 --- a/Resources/Prototypes/Species/arachnid.yml +++ b/Resources/Prototypes/Species/arachnid.yml @@ -67,11 +67,29 @@ Chest: points: 1 required: false - Legs: - points: 6 + RightLeg: + points: 2 required: false - Arms: - points: 6 + RightFoot: + points: 2 + required: false + LeftLeg: + points: 2 + required: false + LeftFoot: + points: 2 + required: false + RightArm: + points: 2 + required: false + RightHand: + points: 2 + required: false + LeftArm: + points: 2 + required: false + LeftHand: + points: 2 required: false - type: humanoidBaseSprite diff --git a/Resources/Prototypes/Species/diona.yml b/Resources/Prototypes/Species/diona.yml index 20bb5e4eba..ec0da9ab5b 100644 --- a/Resources/Prototypes/Species/diona.yml +++ b/Resources/Prototypes/Species/diona.yml @@ -48,10 +48,28 @@ Chest: points: 1 required: false - Legs: + RightLeg: points: 2 required: false - Arms: + RightFoot: + points: 2 + required: false + LeftLeg: + points: 2 + required: false + LeftFoot: + points: 2 + required: false + RightArm: + points: 2 + required: false + RightHand: + points: 2 + required: false + LeftArm: + points: 2 + required: false + LeftHand: points: 2 required: false Overlay: diff --git a/Resources/Prototypes/Species/harpy.yml b/Resources/Prototypes/Species/harpy.yml index 43d28daab0..cf9e044eee 100644 --- a/Resources/Prototypes/Species/harpy.yml +++ b/Resources/Prototypes/Species/harpy.yml @@ -57,13 +57,31 @@ points: 1 required: true defaultMarkings: [ HarpyChestDefault ] - Legs: + RightLeg: points: 2 required: false - Arms: + RightFoot: + points: 2 + required: false + LeftLeg: + points: 2 + required: false + LeftFoot: + points: 2 + required: false + RightArm: points: 1 required: false defaultMarkings: [ HarpyWingDefaultHuescale ] + RightHand: + points: 2 + required: false + LeftArm: + points: 2 + required: false + LeftHand: + points: 2 + required: false - type: humanoidBaseSprite id: MobHarpyHead diff --git a/Resources/Prototypes/Species/human.yml b/Resources/Prototypes/Species/human.yml index 0a4e069d7b..3bd10af648 100644 --- a/Resources/Prototypes/Species/human.yml +++ b/Resources/Prototypes/Species/human.yml @@ -48,20 +48,38 @@ Snout: points: 1 required: false - Tail: # the cat tail joke - points: 0 + Tail: + points: 1 required: false - HeadTop: # the cat ear joke - points: 0 + HeadTop: + points: 1 required: false Chest: points: 1 required: false - Legs: - points: 6 + RightLeg: + points: 2 + required: false + RightFoot: + points: 2 + required: false + LeftLeg: + points: 2 + required: false + LeftFoot: + points: 2 + required: false + RightArm: + points: 2 + required: false + RightHand: + points: 2 + required: false + LeftArm: + points: 2 required: false - Arms: - points: 6 + LeftHand: + points: 2 required: false - type: humanoidBaseSprite diff --git a/Resources/Prototypes/Species/ipc.yml b/Resources/Prototypes/Species/ipc.yml new file mode 100644 index 0000000000..c7675f781b --- /dev/null +++ b/Resources/Prototypes/Species/ipc.yml @@ -0,0 +1,262 @@ +- type: species + id: IPC + name: species-name-ipc + roundStart: true + prototype: MobIPC + sprites: MobIPCSprites + markingLimits: MobIPCMarkingLimits + dollPrototype: MobIPCDummy + skinColoration: Hues + minAge: 1 + maxAge: 240 + oldAge: 50 + youngAge: 50 + maleFirstNames: IpcFirst + femaleFirstNames: IpcFirst + lastNames: IpcLast + naming: FirstDashLast + sexes: + - Unsexed + +# The lack of a layer means that +# this person cannot have round-start anything +# applied to that layer. It has to instead +# be defined as a 'custom base layer' +# in either the mob's starting marking prototype, +# or it has to be added in C#. +- type: speciesBaseSprites + id: MobIPCSprites + sprites: + Head: MobIPCHead + HeadTop: MobHumanoidAnyMarking + HeadSide: MobHumanoidAnyMarking + Tail: MobHumanoidAnyMarking + Hair: MobHumanoidMarkingMatchSkin + Chest: MobIPCTorso + LArm: MobIPCLArm + RArm: MobIPCRArm + LHand: MobIPCLHand + RHand: MobIPCRHand + LLeg: MobIPCLLeg + RLeg: MobIPCRLeg + LFoot: MobIPCLFoot + RFoot: MobIPCRFoot + +- type: markingPoints + id: MobIPCMarkingLimits + points: + Head: + points: 1 + required: true + defaultMarkings: [ MobIPCHeadDefault ] + Chest: + points: 1 + required: true + defaultMarkings: [ MobIPCTorsoDefault ] + HeadSide: + points: 1 + required: false + RightLeg: + points: 1 + required: false + defaultMarkings: [ MobIPCRLegDefault ] + RightFoot: + points: 1 + required: false + defaultMarkings: [ MobIPCRFootDefault ] + LeftLeg: + points: 1 + required: false + defaultMarkings: [ MobIPCLLegDefault ] + LeftFoot: + points: 1 + required: false + defaultMarkings: [ MobIPCLFootDefault ] + RightArm: + points: 1 + required: false + defaultMarkings: [ MobIPCRArmDefault ] + RightHand: + points: 1 + required: false + defaultMarkings: [ MobIPCRHandDefault ] + LeftArm: + points: 1 + required: false + defaultMarkings: [ MobIPCLArmDefault ] + LeftHand: + points: 1 + required: false + defaultMarkings: [ MobIPCLHandDefault ] + +- type: humanoidBaseSprite + id: MobIPCMarkingFollowSkin + markingsMatchSkin: true + layerAlpha: 0.5 + +- type: humanoidBaseSprite + id: MobIPCScreen + +# Head + +- type: humanoidBaseSprite + id: MobIPCHead + +- type: humanoidBaseSprite + id: MobIPCHeadMale + +- type: humanoidBaseSprite + id: MobIPCHeadFemale + +- type: marking + id: MobIPCHeadDefault + bodyPart: Head + markingCategory: Head + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Species/IPC/parts.rsi + state: head_m + +# Torso + +- type: humanoidBaseSprite + id: MobIPCTorso + +- type: humanoidBaseSprite + id: MobIPCTorsoMale + +- type: humanoidBaseSprite + id: MobIPCTorsoFemale + +- type: marking + id: MobIPCTorsoDefault + bodyPart: Chest + markingCategory: Chest + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Species/IPC/parts.rsi + state: torso_m + +- type: marking + id: MobIPCTorsoFemaleDefault + bodyPart: Chest + markingCategory: Chest + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Species/IPC/parts.rsi + state: torso_f + +# Left Leg + +- type: humanoidBaseSprite + id: MobIPCLLeg + +- type: marking + id: MobIPCLLegDefault + bodyPart: LLeg + markingCategory: LeftLeg + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Species/IPC/parts.rsi + state: l_leg + +# Left Arm + +- type: humanoidBaseSprite + id: MobIPCLArm + +- type: marking + id: MobIPCLArmDefault + bodyPart: LArm + markingCategory: LeftArm + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Species/IPC/parts.rsi + state: l_arm + +#LHand + +- type: humanoidBaseSprite + id: MobIPCLHand + +- type: marking + id: MobIPCLHandDefault + bodyPart: LHand + markingCategory: LeftHand + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Species/IPC/parts.rsi + state: l_hand + +#LFoot + +- type: humanoidBaseSprite + id: MobIPCLFoot + +- type: marking + id: MobIPCLFootDefault + bodyPart: LFoot + markingCategory: LeftFoot + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Species/IPC/parts.rsi + state: l_foot + +#RLeg + +- type: humanoidBaseSprite + id: MobIPCRLeg + +- type: marking + id: MobIPCRLegDefault + bodyPart: RLeg + markingCategory: RightLeg + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Species/IPC/parts.rsi + state: r_leg + +#RArm + +- type: humanoidBaseSprite + id: MobIPCRArm + +- type: marking + id: MobIPCRArmDefault + bodyPart: RArm + markingCategory: RightArm + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Species/IPC/parts.rsi + state: r_arm + +#RHand + +- type: humanoidBaseSprite + id: MobIPCRHand + +- type: marking + id: MobIPCRHandDefault + bodyPart: RHand + markingCategory: RightHand + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Species/IPC/parts.rsi + state: r_hand + +#RFoot + +- type: humanoidBaseSprite + id: MobIPCRFoot + +- type: marking + id: MobIPCRFootDefault + bodyPart: RFoot + markingCategory: RightFoot + speciesRestriction: [IPC] + sprites: + - sprite: Mobs/Species/IPC/parts.rsi + state: r_foot + +- type: Tag + id: IPCMaskWearable diff --git a/Resources/Prototypes/Species/misc.yml b/Resources/Prototypes/Species/misc.yml new file mode 100644 index 0000000000..05f78bc2d4 --- /dev/null +++ b/Resources/Prototypes/Species/misc.yml @@ -0,0 +1,12 @@ +# This file is moreso for any base parts from Shitmed that dont fall under any particular umbrella. +- type: humanoidBaseSprite + id: MobPizzaLArm + baseSprite: + sprite: Mobs/Species/Misc/Pizza/parts.rsi + state: "l_arm" + +- type: humanoidBaseSprite + id: MobPizzaRArm + baseSprite: + sprite: Mobs/Species/Misc/Pizza/parts.rsi + state: "r_arm" diff --git a/Resources/Prototypes/Species/moth.yml b/Resources/Prototypes/Species/moth.yml index 4974c619b4..eba63ec29d 100644 --- a/Resources/Prototypes/Species/moth.yml +++ b/Resources/Prototypes/Species/moth.yml @@ -7,7 +7,7 @@ defaultSkinTone: "#ffda93" markingLimits: MobMothMarkingLimits dollPrototype: MobMothDummy - skinColoration: TintedHuesSkin # DeltaV - No rgb moths, literally 1849 + skinColoration: Hues maleFirstNames: names_moth_first_male femaleFirstNames: names_moth_first_female lastNames: names_moth_last @@ -71,11 +71,29 @@ Chest: points: 1 required: false - Legs: - points: 6 + RightLeg: + points: 2 required: false - Arms: - points: 6 + RightFoot: + points: 2 + required: false + LeftLeg: + points: 2 + required: false + LeftFoot: + points: 2 + required: false + RightArm: + points: 2 + required: false + RightHand: + points: 2 + required: false + LeftArm: + points: 2 + required: false + LeftHand: + points: 2 required: false - type: humanoidBaseSprite diff --git a/Resources/Prototypes/Species/reptilian.yml b/Resources/Prototypes/Species/reptilian.yml index 64500f3682..c76c93cb51 100644 --- a/Resources/Prototypes/Species/reptilian.yml +++ b/Resources/Prototypes/Species/reptilian.yml @@ -68,11 +68,29 @@ Chest: points: 2 # Parkstation-WideUseMarkings required: false - Legs: - points: 6 + RightLeg: + points: 2 + required: false + RightFoot: + points: 2 + required: false + LeftLeg: + points: 2 + required: false + LeftFoot: + points: 2 required: false - Arms: - points: 6 + RightArm: + points: 2 + required: false + RightHand: + points: 2 + required: false + LeftArm: + points: 2 + required: false + LeftHand: + points: 2 required: false - type: humanoidBaseSprite diff --git a/Resources/Prototypes/Species/shadowkin.yml b/Resources/Prototypes/Species/shadowkin.yml new file mode 100644 index 0000000000..f7674e80d6 --- /dev/null +++ b/Resources/Prototypes/Species/shadowkin.yml @@ -0,0 +1,179 @@ +- type: species + id: Shadowkin + name: species-name-shadowkin + roundStart: false + prototype: MobShadowkin + sprites: MobShadowkinSprites + defaultSkinTone: "#FFFFFF" + markingLimits: MobShadowkinMarkingLimits + dollPrototype: MobShadowkinDummy + skinColoration: Hues + naming: First + maleFirstNames: names_shadowkin + femaleFirstNames: names_shadowkin + minAge: 18 + maxAge: 300 + youngAge: 20 + oldAge: 250 + sexes: + - Male + - Female + - Unsexed + minHeight: 0.65 + defaultHeight: 0.85 + maxHeight: 1.15 + minWidth: 0.6 + defaultWidth: 0.85 + maxWidth: 1.2 + +- type: speciesBaseSprites + id: MobShadowkinSprites + sprites: + Head: MobShadowkinHead + Snout: MobShadowkinAnyMarkingFollowSkin + HeadTop: MobShadowkinAnyMarkingFollowSkin + HeadSide: MobShadowkinAnyMarkingFollowSkin + Tail: MobShadowkinAnyMarkingFollowSkin + Chest: MobShadowkinTorso + Eyes: MobShadowkinEyes + LArm: MobShadowkinLArm + RArm: MobShadowkinRArm + LHand: MobShadowkinLHand + RHand: MobShadowkinRHand + LLeg: MobShadowkinLLeg + RLeg: MobShadowkinRLeg + LFoot: MobShadowkinLFoot + RFoot: MobShadowkinRFoot + +- type: markingPoints + id: MobShadowkinMarkingLimits + points: + Tail: + points: 1 + required: true + defaultMarkings: [TailShadowkin] + HeadTop: + points: 1 + required: true + defaultMarkings: [EarsShadowkin] + Chest: + points: 1 + required: false + RightLeg: + points: 2 + required: false + RightFoot: + points: 2 + required: false + LeftLeg: + points: 2 + required: false + LeftFoot: + points: 2 + required: false + RightArm: + points: 2 + required: false + RightHand: + points: 2 + required: false + LeftArm: + points: 2 + required: false + LeftHand: + points: 2 + required: false + +- type: humanoidBaseSprite + id: MobShadowkinAnyMarkingFollowSkin + markingsMatchSkin: true + +- type: humanoidBaseSprite + id: MobShadowkinHead + baseSprite: + sprite: Mobs/Species/Shadowkin/parts.rsi + state: head_m + +- type: humanoidBaseSprite + id: MobShadowkinHeadMale + baseSprite: + sprite: Mobs/Species/Shadowkin/parts.rsi + state: head_m + +- type: humanoidBaseSprite + id: MobShadowkinHeadFemale + baseSprite: + sprite: Mobs/Species/Shadowkin/parts.rsi + state: head_f + +- type: humanoidBaseSprite + id: MobShadowkinTorso + baseSprite: + sprite: Mobs/Species/Shadowkin/parts.rsi + state: torso_m + +- type: humanoidBaseSprite + id: MobShadowkinTorsoMale + baseSprite: + sprite: Mobs/Species/Shadowkin/parts.rsi + state: torso_m + +- type: humanoidBaseSprite + id: MobShadowkinTorsoFemale + baseSprite: + sprite: Mobs/Species/Shadowkin/parts.rsi + state: torso_f + +- type: humanoidBaseSprite + id: MobShadowkinLLeg + baseSprite: + sprite: Mobs/Species/Shadowkin/parts.rsi + state: l_leg + +- type: humanoidBaseSprite + id: MobShadowkinLHand + baseSprite: + sprite: Mobs/Species/Shadowkin/parts.rsi + state: l_hand + +- type: humanoidBaseSprite + id: MobShadowkinEyes + baseSprite: + sprite: Mobs/Species/Shadowkin/parts.rsi + state: eyes + +- type: humanoidBaseSprite + id: MobShadowkinLArm + baseSprite: + sprite: Mobs/Species/Shadowkin/parts.rsi + state: l_arm + +- type: humanoidBaseSprite + id: MobShadowkinLFoot + baseSprite: + sprite: Mobs/Species/Shadowkin/parts.rsi + state: l_foot + +- type: humanoidBaseSprite + id: MobShadowkinRLeg + baseSprite: + sprite: Mobs/Species/Shadowkin/parts.rsi + state: r_leg + +- type: humanoidBaseSprite + id: MobShadowkinRHand + baseSprite: + sprite: Mobs/Species/Shadowkin/parts.rsi + state: r_hand + +- type: humanoidBaseSprite + id: MobShadowkinRArm + baseSprite: + sprite: Mobs/Species/Shadowkin/parts.rsi + state: r_arm + +- type: humanoidBaseSprite + id: MobShadowkinRFoot + baseSprite: + sprite: Mobs/Species/Shadowkin/parts.rsi + state: r_foot diff --git a/Resources/Prototypes/Species/slime.yml b/Resources/Prototypes/Species/slime.yml index 7c4ba0cb3d..cc5f70192f 100644 --- a/Resources/Prototypes/Species/slime.yml +++ b/Resources/Prototypes/Species/slime.yml @@ -44,11 +44,29 @@ Chest: points: 1 required: false - Legs: - points: 4 + RightLeg: + points: 2 required: false - Arms: - points: 4 + RightFoot: + points: 2 + required: false + LeftLeg: + points: 2 + required: false + LeftFoot: + points: 2 + required: false + RightArm: + points: 2 + required: false + RightHand: + points: 2 + required: false + LeftArm: + points: 2 + required: false + LeftHand: + points: 2 required: false Tail: # Parkstation-WideUseMarkings points: 1 diff --git a/Resources/Prototypes/Species/species_weights.yml b/Resources/Prototypes/Species/species_weights.yml index 990a8ccecf..d158862d38 100644 --- a/Resources/Prototypes/Species/species_weights.yml +++ b/Resources/Prototypes/Species/species_weights.yml @@ -4,8 +4,9 @@ weights: Human: 5 Reptilian: 4 - SlimePerson: 4 + SlimePerson: 3 Oni: 3 #Nyanotrasen Oni, see Prototypes/Nyanotrasen/Entities/Mobs/Species/Oni.yml Felinid: 4 # Nyanotrasen - Felinid, see Prototypes/Nyanotrasen/Entities/Mobs/Species/felinid.yml - Vulpkanin: 3 # DeltaV - Vulpkanin, see Prototypes/DeltaV/Entities/Mobs/Species/vulpkanin.yml + Vulpkanin: 4 # DeltaV - Vulpkanin, see Prototypes/DeltaV/Entities/Mobs/Species/vulpkanin.yml Diona: 2 + Shadowkin: 0 diff --git a/Resources/Prototypes/Species/terminator.yml b/Resources/Prototypes/Species/terminator.yml deleted file mode 100644 index cfc5a7107c..0000000000 --- a/Resources/Prototypes/Species/terminator.yml +++ /dev/null @@ -1,110 +0,0 @@ -- type: species - id: Terminator - name: Terminator - roundStart: false - prototype: MobTerminatorEndoskeleton - sprites: MobTerminatorSprites - defaultSkinTone: "#fff9e2" - markingLimits: MobHumanMarkingLimits - maleFirstNames: skeletonNamesFirst - femaleFirstNames: skeletonNamesFirst - dollPrototype: MobSkeletonPersonDummy - skinColoration: TintedHues - -- type: speciesBaseSprites - id: MobTerminatorSprites - sprites: - Head: MobTerminatorHead - Chest: MobTerminatorTorso - LArm: MobTerminatorLArm - RArm: MobTerminatorRArm - LHand: MobTerminatorLHand - RHand: MobTerminatorRHand - LLeg: MobTerminatorLLeg - RLeg: MobTerminatorRLeg - LFoot: MobTerminatorLFoot - RFoot: MobTerminatorRFoot - -- type: humanoidBaseSprite - id: MobTerminatorHead - baseSprite: - sprite: Mobs/Species/Terminator/parts.rsi - state: head_m - -- type: humanoidBaseSprite - id: MobTerminatorHeadMale - baseSprite: - sprite: Mobs/Species/Terminator/parts.rsi - state: head_m - -- type: humanoidBaseSprite - id: MobTerminatorHeadFemale - baseSprite: - sprite: Mobs/Species/Terminator/parts.rsi - state: head_f - -- type: humanoidBaseSprite - id: MobTerminatorTorso - baseSprite: - sprite: Mobs/Species/Terminator/parts.rsi - state: torso_m - -- type: humanoidBaseSprite - id: MobTerminatorTorsoMale - baseSprite: - sprite: Mobs/Species/Terminator/parts.rsi - state: torso_m - -- type: humanoidBaseSprite - id: MobTerminatorTorsoFemale - baseSprite: - sprite: Mobs/Species/Terminator/parts.rsi - state: torso_f - -- type: humanoidBaseSprite - id: MobTerminatorLLeg - baseSprite: - sprite: Mobs/Species/Terminator/parts.rsi - state: l_leg - -- type: humanoidBaseSprite - id: MobTerminatorLArm - baseSprite: - sprite: Mobs/Species/Terminator/parts.rsi - state: l_arm - -- type: humanoidBaseSprite - id: MobTerminatorLHand - baseSprite: - sprite: Mobs/Species/Terminator/parts.rsi - state: l_hand - -- type: humanoidBaseSprite - id: MobTerminatorLFoot - baseSprite: - sprite: Mobs/Species/Terminator/parts.rsi - state: l_foot - -- type: humanoidBaseSprite - id: MobTerminatorRLeg - baseSprite: - sprite: Mobs/Species/Terminator/parts.rsi - state: r_leg - -- type: humanoidBaseSprite - id: MobTerminatorRArm - baseSprite: - sprite: Mobs/Species/Terminator/parts.rsi - state: r_arm - -- type: humanoidBaseSprite - id: MobTerminatorRHand - baseSprite: - sprite: Mobs/Species/Terminator/parts.rsi - state: r_hand - -- type: humanoidBaseSprite - id: MobTerminatorRFoot - baseSprite: - sprite: Mobs/Species/Terminator/parts.rsi - state: r_foot diff --git a/Resources/Prototypes/Species/vox.yml b/Resources/Prototypes/Species/vox.yml index ee8055362b..b6cba51322 100644 --- a/Resources/Prototypes/Species/vox.yml +++ b/Resources/Prototypes/Species/vox.yml @@ -6,18 +6,19 @@ sprites: MobVoxSprites markingLimits: MobVoxMarkingLimits dollPrototype: MobVoxDummy - skinColoration: Hues + skinColoration: VoxFeathers + defaultSkinTone: "#6c741d" maleFirstNames: names_vox femaleFirstNames: names_vox naming: First sexes: - - Unsexed - + - Unsexed - type: speciesBaseSprites id: MobVoxSprites sprites: Head: MobVoxHead + Snout: MobHumanoidAnyMarking Hair: MobHumanoidAnyMarking FacialHair: MobHumanoidAnyMarking Chest: MobVoxTorso @@ -30,6 +31,7 @@ RLeg: MobVoxRLeg LFoot: MobVoxLFoot RFoot: MobVoxRFoot + Tail: MobHumanoidAnyMarking - type: markingPoints id: MobVoxMarkingLimits @@ -41,57 +43,94 @@ FacialHair: points: 1 required: false + Head: + points: 1 + required: true + Snout: + points: 1 + required: true + defaultMarkings: [ VoxBeak ] + RightArm: + points: 1 + required: true + defaultMarkings: [ VoxRArmScales ] + RightHand: + points: 1 + required: true + defaultMarkings: [ VoxRHandScales ] + LeftArm: + points: 1 + required: true + defaultMarkings: [ VoxLArmScales ] + LeftHand: + points: 1 + required: true + defaultMarkings: [ VoxLHandScales ] + RightLeg: + points: 1 + required: true + defaultMarkings: [ VoxRLegScales ] + LeftLeg: + points: 1 + required: true + defaultMarkings: [ VoxLLegScales ] + RightFoot: + points: 1 + required: true + defaultMarkings: [ VoxRFootScales ] + LeftFoot: + points: 1 + required: true + defaultMarkings: [ VoxLFootScales ] Chest: points: 1 required: false - Legs: - points: 2 - required: false - Arms: - points: 4 # Parkstation-WideUseMarkings - required: false + Tail: + points: 1 + required: true + defaultMarkings: [ VoxTail ] - type: humanoidBaseSprite id: MobVoxEyes baseSprite: - sprite: Mobs/Customization/eyes.rsi - state: vox_eyes_s + sprite: Mobs/Species/Vox/parts.rsi + state: eyes - type: humanoidBaseSprite id: MobVoxHead baseSprite: sprite: Mobs/Species/Vox/parts.rsi - state: head_m + state: head - type: humanoidBaseSprite id: MobVoxHeadMale baseSprite: sprite: Mobs/Species/Vox/parts.rsi - state: head_m + state: head - type: humanoidBaseSprite id: MobVoxHeadFemale baseSprite: sprite: Mobs/Species/Vox/parts.rsi - state: head_f + state: head - type: humanoidBaseSprite id: MobVoxTorso baseSprite: sprite: Mobs/Species/Vox/parts.rsi - state: torso_m + state: torso - type: humanoidBaseSprite id: MobVoxTorsoMale baseSprite: sprite: Mobs/Species/Vox/parts.rsi - state: torso_m + state: torso - type: humanoidBaseSprite id: MobVoxTorsoFemale baseSprite: sprite: Mobs/Species/Vox/parts.rsi - state: torso_f + state: torso - type: humanoidBaseSprite id: MobVoxLLeg @@ -140,5 +179,3 @@ baseSprite: sprite: Mobs/Species/Vox/parts.rsi state: r_foot - -# diff --git a/Resources/Prototypes/Stacks/Materials/crystals.yml b/Resources/Prototypes/Stacks/Materials/crystals.yml index 274f9c10ea..e4c9e48718 100644 --- a/Resources/Prototypes/Stacks/Materials/crystals.yml +++ b/Resources/Prototypes/Stacks/Materials/crystals.yml @@ -4,3 +4,19 @@ icon: Objects/Specific/Syndicate/telecrystal.rsi spawn: Telecrystal1 itemSize: 1 + +- type: stack + id: Bluespace + name: bluespace + icon: { sprite: Objects/Materials/materials.rsi, state: bluespace } + spawn: MaterialBluespace1 + maxCount: 5 + itemSize: 1 + +- type: stack + id: Normality + name: normality + icon: { sprite: Objects/Materials/materials.rsi, state: normality } + spawn: MaterialNormality1 + maxCount: 5 + itemSize: 1 diff --git a/Resources/Prototypes/Stacks/Materials/materials.yml b/Resources/Prototypes/Stacks/Materials/materials.yml index 00153ef23c..0a05a89964 100644 --- a/Resources/Prototypes/Stacks/Materials/materials.yml +++ b/Resources/Prototypes/Stacks/Materials/materials.yml @@ -54,6 +54,14 @@ maxCount: 30 itemSize: 1 +- type: stack + id: Pyrotton + name: pyrotton + icon: { sprite: /Textures/Objects/Materials/materials.rsi, state: pyrotton } + spawn: MaterialPyrotton1 + maxCount: 30 + itemSize: 1 + - type: stack id: Bananium name: bananium diff --git a/Resources/Prototypes/Stacks/Materials/ore.yml b/Resources/Prototypes/Stacks/Materials/ore.yml index 2a95393c27..cf7fcb0483 100644 --- a/Resources/Prototypes/Stacks/Materials/ore.yml +++ b/Resources/Prototypes/Stacks/Materials/ore.yml @@ -46,7 +46,6 @@ maxCount: 30 itemSize: 2 - - type: stack id: BananiumOre name: bananium ore @@ -70,3 +69,19 @@ spawn: Salt1 maxCount: 30 itemSize: 2 + +- type: stack + id: BluespaceOre + name: raw bluespace + icon: { sprite: Objects/Materials/ore.rsi, state: bluespace } + spawn: BluespaceOre1 + maxCount: 30 + itemSize: 2 + +- type: stack + id: NormalityOre + name: raw normality + icon: { sprite: Objects/Materials/ore.rsi, state: normality } + spawn: NormalityOre1 + maxCount: 30 + itemSize: 2 diff --git a/Resources/Prototypes/Stacks/consumable_stacks.yml b/Resources/Prototypes/Stacks/consumable_stacks.yml index e9f0cab7e4..2936772f08 100644 --- a/Resources/Prototypes/Stacks/consumable_stacks.yml +++ b/Resources/Prototypes/Stacks/consumable_stacks.yml @@ -51,6 +51,14 @@ maxCount: itemSize: 1 +- type: stack + id: GroundCannabisRainbow + name: ground rainbow cannabis + icon: { sprite: /Textures/Objects/Specific/Hydroponics/rainbow_cannabis.rsi, state: powderpile_rainbow } + spawn: GroundCannabisRainbow + maxCount: + itemSize: 1 + - type: stack id: LeavesTobaccoDried name: dried tobacco leaves @@ -66,3 +74,11 @@ spawn: LeavesCannabisDried maxCount: 5 itemSize: 5 + +- type: stack + id: LeavesCannabisRainbowDried + name: dried rainbow cannabis leaves + icon: { sprite: /Textures/Objects/Specific/Hydroponics/rainbow_cannabis.rsi, state: dried } + spawn: LeavesCannabisRainbowDried + maxCount: 5 + itemSize: 5 diff --git a/Resources/Prototypes/StatusEffects/health.yml b/Resources/Prototypes/StatusEffects/health.yml index 562dbb336d..073b03a2ae 100644 --- a/Resources/Prototypes/StatusEffects/health.yml +++ b/Resources/Prototypes/StatusEffects/health.yml @@ -1,8 +1,8 @@ - type: statusIcon id: HealthIcon abstract: true - priority: 1 - locationPreference: Right + priority: 3 + locationPreference: Left isShaded: true - type: statusIcon diff --git a/Resources/Prototypes/StatusEffects/security.yml b/Resources/Prototypes/StatusEffects/security.yml index 2a6695387b..31a22e5df3 100644 --- a/Resources/Prototypes/StatusEffects/security.yml +++ b/Resources/Prototypes/StatusEffects/security.yml @@ -9,33 +9,33 @@ parent: SecurityIcon id: SecurityIconDischarged icon: - sprite: Interface/Misc/security_icons.rsi + sprite: /Textures/Interface/Misc/security_icons.rsi state: hud_discharged - type: statusIcon parent: SecurityIcon id: SecurityIconIncarcerated icon: - sprite: Interface/Misc/security_icons.rsi + sprite: /Textures/Interface/Misc/security_icons.rsi state: hud_incarcerated - type: statusIcon parent: SecurityIcon id: SecurityIconParoled icon: - sprite: Interface/Misc/security_icons.rsi + sprite: /Textures/Interface/Misc/security_icons.rsi state: hud_paroled - type: statusIcon parent: SecurityIcon id: SecurityIconSuspected icon: - sprite: Interface/Misc/security_icons.rsi + sprite: /Textures/Interface/Misc/security_icons.rsi state: hud_suspected - type: statusIcon parent: SecurityIcon id: SecurityIconWanted icon: - sprite: Interface/Misc/security_icons.rsi + sprite: /Textures/Interface/Misc/security_icons.rsi state: hud_wanted diff --git a/Resources/Prototypes/StepTrigger/StepTriggerTypes.yml b/Resources/Prototypes/StepTrigger/StepTriggerTypes.yml new file mode 100644 index 0000000000..6d03908a17 --- /dev/null +++ b/Resources/Prototypes/StepTrigger/StepTriggerTypes.yml @@ -0,0 +1,20 @@ +- type: stepTriggerType + id: Lava + +- type: stepTriggerType + id: Landmine + +- type: stepTriggerType + id: Shard + +- type: stepTriggerType + id: Chasm + +- type: stepTriggerType + id: Mousetrap + +- type: stepTriggerType + id: SlipTile + +- type: stepTriggerType + id: SlipEntity diff --git a/Resources/Prototypes/Store/categories.yml b/Resources/Prototypes/Store/categories.yml index 11f8d509af..489145813e 100644 --- a/Resources/Prototypes/Store/categories.yml +++ b/Resources/Prototypes/Store/categories.yml @@ -7,6 +7,32 @@ id: Debug2 name: store-category-debug2 +#WIZARD +- type: storeCategory + id: SpellbookOffensive + name: store-caregory-spellbook-offensive + priority: 0 + +- type: storeCategory + id: SpellbookDefensive + name: store-caregory-spellbook-defensive + priority: 1 + +- type: storeCategory + id: SpellbookUtility + name: store-caregory-spellbook-utility + priority: 2 + +- type: storeCategory + id: SpellbookEquipment + name: store-caregory-spellbook-equipment + priority: 3 + +- type: storeCategory + id: SpellbookEvents + name: store-caregory-spellbook-events + priority: 4 + #uplink categoires - type: storeCategory id: UplinkWeapons @@ -68,6 +94,21 @@ name: store-category-deception priority: 10 +- type: storeCategory + id: UplinkChemicals + name: store-category-chemicals + priority: 11 + +- type: storeCategory + id: UplinkDisruption + name: store-category-disruption + priority: 12 + +- type: storeCategory + id: UplinkSales + name: Sales + priority: 10 + #revenant - type: storeCategory id: RevenantAbilities diff --git a/Resources/Prototypes/Store/currency.yml b/Resources/Prototypes/Store/currency.yml index 91039a75e6..b1cff06be2 100644 --- a/Resources/Prototypes/Store/currency.yml +++ b/Resources/Prototypes/Store/currency.yml @@ -1,7 +1,7 @@ - type: currency id: Telecrystal displayName: store-currency-display-telecrystal - cash: + cash: 1: Telecrystal1 canWithdraw: true @@ -10,7 +10,12 @@ displayName: store-currency-display-stolen-essence canWithdraw: false +- type: currency + id: WizCoin + displayName: store-currency-display-wizcoin + canWithdraw: false + #debug - type: currency id: DebugDollar - displayName: store-currency-display-debugdollar \ No newline at end of file + displayName: store-currency-display-debugdollar diff --git a/Resources/Prototypes/Store/presets.yml b/Resources/Prototypes/Store/presets.yml index e623f4c8cd..47768b68ec 100644 --- a/Resources/Prototypes/Store/presets.yml +++ b/Resources/Prototypes/Store/presets.yml @@ -13,5 +13,25 @@ - UplinkJob - UplinkArmor - UplinkPointless + - UplinkSales currencyWhitelist: - Telecrystal + sales: + enabled: true + minMultiplier: 0.2 + maxMultiplier: 0.8 + minItems: 3 + maxItems: 8 + salesCategory: UplinkSales + +- type: storePreset + id: StorePresetSpellbook + storeName: Spellbook + categories: + - SpellbookOffensive #Fireball, Rod Form + - SpellbookDefensive #Magic Missile, Wall of Force + - SpellbookUtility #Body Swap, Lich, Teleport, Knock, Polymorph + - SpellbookEquipment #Battlemage Robes, Staff of Locker + - SpellbookEvents #Summon Weapons, Summon Ghosts + currencyWhitelist: + - WizCoin diff --git a/Resources/Prototypes/Tiles/plating.yml b/Resources/Prototypes/Tiles/plating.yml index 7edb1ae784..0a74ef9fd4 100644 --- a/Resources/Prototypes/Tiles/plating.yml +++ b/Resources/Prototypes/Tiles/plating.yml @@ -4,6 +4,7 @@ sprite: /Textures/Tiles/plating.png baseTurf: Lattice isSubfloor: true + deconstructTools: [ Axing ] footstepSounds: collection: FootstepPlating friction: 0.3 @@ -20,6 +21,7 @@ - 1.0 baseTurf: Lattice isSubfloor: true + deconstructTools: [ Axing ] footstepSounds: collection: FootstepPlating friction: 0.3 @@ -31,6 +33,7 @@ sprite: /Textures/Tiles/plating_burnt.png baseTurf: Lattice isSubfloor: true + deconstructTools: [ Axing ] footstepSounds: collection: FootstepPlating friction: 0.3 @@ -42,6 +45,7 @@ sprite: /Textures/Tiles/Asteroid/asteroid_plating.png baseTurf: Lattice isSubfloor: true + deconstructTools: [ Axing ] footstepSounds: collection: FootstepPlating friction: 0.3 @@ -53,6 +57,7 @@ sprite: /Textures/Tiles/Misc/clockwork/clockwork_floor.png baseTurf: Lattice isSubfloor: true + deconstructTools: [ Axing ] footstepSounds: collection: FootstepPlating friction: 0.3 @@ -64,6 +69,7 @@ sprite: /Textures/Tiles/snow_plating.png #Not in the snow planet RSI because it doesn't have any metadata. Should probably be moved to its own folder later. baseTurf: Lattice isSubfloor: true + deconstructTools: [ Axing ] footstepSounds: collection: FootstepPlating friction: 0.15 #a little less then actual snow diff --git a/Resources/Prototypes/Traits/Misc/singer_types.yml b/Resources/Prototypes/Traits/Misc/singer_types.yml index 28e4712ee9..605f3be913 100644 --- a/Resources/Prototypes/Traits/Misc/singer_types.yml +++ b/Resources/Prototypes/Traits/Misc/singer_types.yml @@ -10,9 +10,7 @@ "Flute": {73: 0} "Sax": {66: 0} defaultInstrument: "Voice" - midiUi: - key: enum.InstrumentUiKey.Key - type: InstrumentBoundUserInterface + midiUi: Key midiActionId: ActionHarpyPlayMidi - type: SingerInstrument @@ -20,7 +18,5 @@ instrumentList: "Voice": {52: 0} defaultInstrument: "Voice" - midiUi: - key: enum.InstrumentUiKey.Key - type: InstrumentBoundUserInterface + midiUi: Key midiActionId: ActionHarpyPlayMidi # TODO: custom action maybe? diff --git a/Resources/Prototypes/Traits/categories.yml b/Resources/Prototypes/Traits/categories.yml index ba3fd7b345..fcd89d5bbe 100644 --- a/Resources/Prototypes/Traits/categories.yml +++ b/Resources/Prototypes/Traits/categories.yml @@ -2,18 +2,41 @@ - type: traitCategory id: Uncategorized + root: true - type: traitCategory id: Auditory + root: true - type: traitCategory id: Mental + root: true - type: traitCategory id: Physical + root: true - type: traitCategory id: Speech + root: true + subCategories: + - TraitsSpeechUncategorized + - TraitsSpeechAccents + - TraitsSpeechLanguages + +- type: traitCategory + id: TraitsSpeechUncategorized + +- type: traitCategory + id: TraitsSpeechAccents + +- type: traitCategory + id: TraitsSpeechLanguages - type: traitCategory id: Visual + root: true + +- type: traitCategory + id: Language + root: true \ No newline at end of file diff --git a/Resources/Prototypes/Traits/disabilities.yml b/Resources/Prototypes/Traits/disabilities.yml index ca2453e41a..efa7739101 100644 --- a/Resources/Prototypes/Traits/disabilities.yml +++ b/Resources/Prototypes/Traits/disabilities.yml @@ -1,87 +1,127 @@ - type: trait id: Blindness category: Visual - points: 2 + points: 6 requirements: - !type:CharacterJobRequirement inverted: true jobs: - Borg - MedicalBorg - components: - - type: PermanentBlindness + functions: + - !type:TraitAddComponent + components: + - type: PermanentBlindness + +- type: trait + id: Nearsighted + category: Visual + points: 1 + requirements: + - !type:CharacterJobRequirement + inverted: true + jobs: + - Borg + - MedicalBorg + functions: + - !type:TraitAddComponent + components: + - type: PermanentBlindness + blindness: 4 - type: trait id: Narcolepsy category: Mental - points: 1 + points: 2 requirements: - !type:CharacterJobRequirement inverted: true jobs: - Borg - MedicalBorg - components: - - type: Narcolepsy - timeBetweenIncidents: 300, 600 - durationOfIncident: 10, 30 + - !type:CharacterSpeciesRequirement + inverted: true + species: + - IPC + functions: + - !type:TraitAddComponent + components: + - type: Narcolepsy + timeBetweenIncidents: 300, 600 + durationOfIncident: 10, 30 - type: trait id: Pacifist category: Mental - points: 3 - components: - - type: Pacified + points: 6 + functions: + - !type:TraitAddComponent + components: + - type: Pacified - type: trait id: Paracusia category: Auditory - points: 1 - components: - - type: Paracusia - minTimeBetweenIncidents: 0.1 - maxTimeBetweenIncidents: 300 - maxSoundDistance: 7 - sounds: - collection: Paracusia + points: 2 + functions: + - !type:TraitAddComponent + components: + - type: Paracusia + minTimeBetweenIncidents: 0.1 + maxTimeBetweenIncidents: 300 + maxSoundDistance: 7 + sounds: + collection: Paracusia - type: trait id: Muted - category: Speech - points: 1 + category: Mental + points: 4 requirements: - !type:CharacterJobRequirement inverted: true jobs: - Borg - MedicalBorg - components: - - type: Muted + functions: + - !type:TraitAddComponent + components: + - type: Muted - type: trait id: Uncloneable category: Physical - points: 2 + points: 1 requirements: - !type:CharacterJobRequirement inverted: true jobs: - Borg - MedicalBorg - components: - - type: Uncloneable + functions: + - !type:TraitAddComponent + components: + - type: Uncloneable - type: trait id: FrontalLisp - category: Speech + category: TraitsSpeechAccents requirements: - !type:CharacterJobRequirement inverted: true jobs: - Borg - MedicalBorg - components: - - type: FrontalLisp + - !type:CharacterSpeciesRequirement + inverted: true + species: + - IPC + - !type:CharacterItemGroupRequirement + group: TraitsAccents + functions: + - !type:TraitAddComponent + components: + - type: FrontalLisp - type: trait id: Snoring @@ -92,13 +132,19 @@ jobs: - Borg - MedicalBorg - components: - - type: Snoring + - !type:CharacterSpeciesRequirement + inverted: true + species: + - IPC + functions: + - !type:TraitAddComponent + components: + - type: Snoring - type: trait id: Sluggish category: Physical - points: 1 + points: 3 requirements: - !type:CharacterTraitRequirement inverted: true @@ -109,19 +155,21 @@ inverted: true species: - Diona - components: - - type: TraitSpeedModifier - sprintModifier: 0.85 - walkModifier: 0.85 - - type: ClimbDelayModifier - climbDelayMultiplier: 1.35 - - type: LayingDownModifier - layingDownCooldownMultiplier: 1.2 + functions: + - !type:TraitAddComponent + components: + - type: TraitSpeedModifier + sprintModifier: 0.85 + walkModifier: 0.85 + - type: ClimbDelayModifier + climbDelayMultiplier: 1.35 + - type: LayingDownModifier + layingDownCooldownMultiplier: 1.2 - type: trait id: SnailPaced category: Physical - points: 2 + points: 5 requirements: - !type:CharacterTraitRequirement inverted: true @@ -132,42 +180,99 @@ inverted: true species: - Diona - components: - - type: TraitSpeedModifier - sprintModifier: 0.7 - walkModifier: 0.7 - - type: ClimbDelayModifier - climbDelayMultiplier: 1.66 - - type: LayingDownModifier - layingDownCooldownMultiplier: 1.6 + functions: + - !type:TraitAddComponent + components: + - type: TraitSpeedModifier + sprintModifier: 0.7 + walkModifier: 0.7 + - type: ClimbDelayModifier + climbDelayMultiplier: 1.66 + - type: LayingDownModifier + layingDownCooldownMultiplier: 1.6 - type: trait id: BloodDeficiency category: Physical - points: 2 + points: 5 requirements: - !type:CharacterJobRequirement inverted: true jobs: - Borg - MedicalBorg - components: - - type: BloodDeficiency # 0.07 = start taking bloodloss damage at around ~21.4 minutes, - bloodLossAmount: 0.07 # then become crit ~10 minutes later + - !type:CharacterSpeciesRequirement + inverted: true + species: + - IPC + functions: + - !type:TraitAddComponent + components: + - type: BloodDeficiency # By default, start taking bloodloss damage at around ~21.4 minutes, + bloodLossPercentage: 0.0002333333 # then become crit ~10 minutes - type: trait id: Hemophilia category: Physical - points: 1 + points: 2 requirements: - !type:CharacterJobRequirement inverted: true jobs: - Borg - MedicalBorg - components: - - type: Hemophilia - bleedReductionModifier: 0.5 - damageModifiers: - coefficients: - Blunt: 1.1 + - !type:CharacterSpeciesRequirement + inverted: true + species: + - IPC + functions: + - !type:TraitAddComponent + components: + - type: Hemophilia + bleedReductionModifier: 0.5 + damageModifiers: + coefficients: + Blunt: 1.1 + +- type: trait + id: Photophobia + category: Visual + points: 1 + requirements: + - !type:CharacterSpeciesRequirement + inverted: true + species: + - Vulpkanin # This trait functions exactly as-is for the Vulpkanin trait. + - Shadowkin + functions: + - !type:TraitAddComponent + components: + - type: Flashable + eyeDamageChance: 0.3 + eyeDamage: 1 + durationMultiplier: 1.5 + +- type: trait + id: Clumsy + category: Physical + points: 1 + requirements: + - !type:CharacterJobRequirement + inverted: true + jobs: + - Clown # This trait functions exactly as is for the Clown's trait. + - !type:CharacterDepartmentRequirement + inverted: true + departments: + - Command # Because I know for a fact people will play Captain and grief with their inability to fight back. + - Security # Because I know for a fact people will play Security and grief with their inability to use guns. + functions: + - !type:TraitAddComponent + components: + - type: Clumsy + clumsyDamage: + types: + Blunt: 5 + Piercing: 4 + groups: + Burn: 3 diff --git a/Resources/Prototypes/Traits/inconveniences.yml b/Resources/Prototypes/Traits/inconveniences.yml index 5e1e4e4b3f..57d166d3e3 100644 --- a/Resources/Prototypes/Traits/inconveniences.yml +++ b/Resources/Prototypes/Traits/inconveniences.yml @@ -16,49 +16,60 @@ inverted: true species: - Dwarf - components: - - type: LightweightDrunk - boozeStrengthMultiplier: 2 + - IPC + functions: + - !type:TraitAddComponent + components: + - type: LightweightDrunk + boozeStrengthMultiplier: 2 - type: trait id: Stutter - category: Mental + category: TraitsSpeechAccents requirements: - !type:CharacterJobRequirement inverted: true jobs: - Borg - MedicalBorg - components: - - type: StutteringAccent - matchRandomProb: 0.1 - fourRandomProb: 0 - threeRandomProb: 0 - cutRandomProb: 0 + - !type:CharacterItemGroupRequirement + group: TraitsAccents + functions: + - !type:TraitAddComponent + components: + - type: StutteringAccent + matchRandomProb: 0.1 + fourRandomProb: 0 + threeRandomProb: 0 + cutRandomProb: 0 - type: trait id: ForeignerLight - category: Mental - points: 1 + category: TraitsSpeechLanguages + points: 2 requirements: - !type:CharacterTraitRequirement inverted: true traits: - Foreigner - components: - - type: ForeignerTrait - cantUnderstand: false # Allows to understand - baseTranslator: TranslatorForeigner + functions: + - !type:TraitAddComponent + components: + - type: ForeignerTrait + cantUnderstand: false + baseTranslator: TranslatorForeigner - type: trait id: Foreigner - category: Mental - points: 2 + category: TraitsSpeechLanguages + points: 4 requirements: # TODO: Add a requirement to know at least 1 non-gc language - !type:CharacterTraitRequirement inverted: true traits: - ForeignerLight - components: - - type: ForeignerTrait - baseTranslator: TranslatorForeigner + functions: + - !type:TraitAddComponent + components: + - type: ForeignerTrait + baseTranslator: TranslatorForeigner diff --git a/Resources/Prototypes/Traits/languages.yml b/Resources/Prototypes/Traits/languages.yml new file mode 100644 index 0000000000..a8bdeff468 --- /dev/null +++ b/Resources/Prototypes/Traits/languages.yml @@ -0,0 +1,108 @@ +- type: trait + id: SignLanguage + category: TraitsSpeechLanguages + points: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: TraitsLanguagesBasic + functions: + - !type:TraitModifyLanguages + languagesSpoken: + - Sign + languagesUnderstood: + - Sign + +- type: trait + id: SolCommon + category: TraitsSpeechLanguages + points: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: TraitsLanguagesBasic + - !type:CharacterSpeciesRequirement + inverted: true + species: + - Human + functions: + - !type:TraitModifyLanguages + languagesSpoken: + - SolCommon + languagesUnderstood: + - SolCommon + +- type: trait + id: Tradeband + category: TraitsSpeechLanguages + points: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: TraitsLanguagesBasic + - !type:CharacterSpeciesRequirement + inverted: true + species: + - Harpy + functions: + - !type:TraitModifyLanguages + languagesSpoken: + - Tradeband + languagesUnderstood: + - Tradeband + +- type: trait + id: Freespeak + category: TraitsSpeechLanguages + points: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: TraitsLanguagesBasic + functions: + - !type:TraitModifyLanguages + languagesSpoken: + - Freespeak + languagesUnderstood: + - Freespeak + +- type: trait + id: Elyran + category: TraitsSpeechLanguages + points: 0 + requirements: + - !type:CharacterItemGroupRequirement + group: TraitsLanguagesBasic + functions: + - !type:TraitModifyLanguages + languagesSpoken: + - Elyran + languagesUnderstood: + - Elyran + +- type: trait + id: ValyrianStandard + category: TraitsSpeechLanguages + points: 1 + requirements: + - !type:CharacterItemGroupRequirement + group: TraitsLanguagesBasic + functions: + - !type:TraitModifyLanguages + languagesSpoken: + - ValyrianStandard + languagesUnderstood: + - ValyrianStandard + +- type: trait + id: Azaziba + category: TraitsSpeechLanguages + points: 1 + requirements: + - !type:CharacterSpeciesRequirement + species: + - Reptilian + - !type:CharacterItemGroupRequirement + group: TraitsLanguagesBasic + functions: + - !type:TraitModifyLanguages + languagesSpoken: + - Azaziba + languagesUnderstood: + - Azaziba diff --git a/Resources/Prototypes/Traits/mental.yml b/Resources/Prototypes/Traits/mental.yml new file mode 100644 index 0000000000..5b4fc56bf0 --- /dev/null +++ b/Resources/Prototypes/Traits/mental.yml @@ -0,0 +1,282 @@ +- type: trait + id: HighPotential + category: Mental + points: -5 + requirements: + - !type:CharacterLogicOrRequirement + requirements: + - !type:CharacterTraitRequirement + traits: + - LatentPsychic + - !type:CharacterJobRequirement + jobs: + - Chaplain + - Librarian + - ResearchDirector + - ForensicMantis + - !type:CharacterLogicOrRequirement + requirements: + - !type:CharacterSpeciesRequirement + inverted: true + species: + - IPC + - !type:CharacterTraitRequirement + traits: + - AnomalousPositronics + - !type:CharacterSpeciesRequirement + inverted: true + species: + - Oni + - Shadowkin + - !type:CharacterTraitRequirement + inverted: true + traits: + - LowPotential + functions: + - !type:TraitReplaceComponent + components: + - type: PotentiaModifier + potentiaMultiplier: 1.25 + +- type: trait + id: LowPotential + category: Mental + points: 4 + requirements: + - !type:CharacterLogicOrRequirement + requirements: + - !type:CharacterTraitRequirement + traits: + - LatentPsychic + - !type:CharacterJobRequirement + jobs: + - Chaplain + - Librarian + - ResearchDirector + - ForensicMantis + - !type:CharacterLogicOrRequirement + requirements: + - !type:CharacterSpeciesRequirement + inverted: true + species: + - IPC + - !type:CharacterTraitRequirement + traits: + - AnomalousPositronics + - !type:CharacterSpeciesRequirement + inverted: true + species: + - Oni + - Shadowkin + - !type:CharacterTraitRequirement + inverted: true + traits: + - HighPotential + functions: + - !type:TraitReplaceComponent + components: + - type: PotentiaModifier + potentiaMultiplier: 0.75 + +- type: trait + id: LowAmplification + category: Mental + points: 3 + requirements: + - !type:CharacterLogicOrRequirement + requirements: + - !type:CharacterTraitRequirement + traits: + - LatentPsychic + - !type:CharacterJobRequirement + jobs: + - Chaplain + - Librarian + - ResearchDirector + - ForensicMantis + - !type:CharacterLogicOrRequirement + requirements: + - !type:CharacterSpeciesRequirement + inverted: true + species: + - IPC + - !type:CharacterTraitRequirement + traits: + - AnomalousPositronics + - !type:CharacterLogicOrRequirement + requirements: + - !type:CharacterSpeciesRequirement + inverted: true + species: + - Shadowkin + - !type:CharacterTraitRequirement + inverted: true + traits: + - HighAmplification + - PowerOverwhelming + functions: + - !type:TraitAddPsionics + psionicPowers: + - LowAmplification + +- type: trait + id: HighAmplification + category: Mental + points: -3 + requirements: + - !type:CharacterLogicOrRequirement + requirements: + - !type:CharacterTraitRequirement + traits: + - LatentPsychic + - !type:CharacterJobRequirement + jobs: + - Chaplain + - Librarian + - ResearchDirector + - ForensicMantis + - !type:CharacterLogicOrRequirement + requirements: + - !type:CharacterSpeciesRequirement + inverted: true + species: + - IPC + - !type:CharacterTraitRequirement + traits: + - AnomalousPositronics + - !type:CharacterLogicOrRequirement + requirements: + - !type:CharacterSpeciesRequirement + inverted: true + species: + - Shadowkin + - !type:CharacterTraitRequirement + inverted: true + traits: + - LowAmplification + - PowerOverwhelming + functions: + - !type:TraitAddPsionics + psionicPowers: + - HighAmplification + +- type: trait + id: PowerOverwhelming + category: Mental + points: -10 + requirements: + - !type:CharacterLogicOrRequirement + requirements: + - !type:CharacterTraitRequirement + traits: + - LatentPsychic + - !type:CharacterJobRequirement + jobs: + - Chaplain + - Librarian + - ResearchDirector + - ForensicMantis + - !type:CharacterLogicOrRequirement + requirements: + - !type:CharacterSpeciesRequirement + inverted: true + species: + - IPC + - !type:CharacterTraitRequirement + traits: + - AnomalousPositronics + - !type:CharacterLogicOrRequirement + requirements: + - !type:CharacterSpeciesRequirement + inverted: true + species: + - Shadowkin + - !type:CharacterTraitRequirement + inverted: true + traits: + - LowAmplification + - HighAmplification + functions: + - !type:TraitAddPsionics + psionicPowers: + - PowerOverwhelming + +- type: trait + id: LowDampening + category: Mental + points: 3 + requirements: + - !type:CharacterLogicOrRequirement + requirements: + - !type:CharacterTraitRequirement + traits: + - LatentPsychic + - !type:CharacterJobRequirement + jobs: + - Chaplain + - Librarian + - ResearchDirector + - ForensicMantis + - !type:CharacterLogicOrRequirement + requirements: + - !type:CharacterSpeciesRequirement + inverted: true + species: + - IPC + - !type:CharacterTraitRequirement + traits: + - AnomalousPositronics + - !type:CharacterLogicOrRequirement + requirements: + - !type:CharacterSpeciesRequirement + inverted: true + species: + - Shadowkin + - !type:CharacterTraitRequirement + inverted: true + traits: + - HighDampening + functions: + - !type:TraitAddPsionics + psionicPowers: + - LowDampening + +- type: trait + id: HighDampening + category: Mental + points: -3 + requirements: + - !type:CharacterLogicOrRequirement + requirements: + - !type:CharacterTraitRequirement + traits: + - LatentPsychic + - !type:CharacterJobRequirement + jobs: + - Chaplain + - Librarian + - ResearchDirector + - ForensicMantis + - !type:CharacterLogicOrRequirement + requirements: + - !type:CharacterSpeciesRequirement + inverted: true + species: + - IPC + - !type:CharacterTraitRequirement + traits: + - AnomalousPositronics + - !type:CharacterLogicOrRequirement + requirements: + - !type:CharacterSpeciesRequirement + inverted: true + species: + - Shadowkin + - !type:CharacterTraitRequirement + inverted: true + traits: + - LowDampening + functions: + - !type:TraitAddPsionics + psionicPowers: + - HighDampening diff --git a/Resources/Prototypes/Traits/neutral.yml b/Resources/Prototypes/Traits/neutral.yml index ab5bcb238d..8ea7006c0c 100644 --- a/Resources/Prototypes/Traits/neutral.yml +++ b/Resources/Prototypes/Traits/neutral.yml @@ -1,12 +1,17 @@ - type: trait id: PirateAccent - category: Speech - components: - - type: PirateAccent + category: TraitsSpeechAccents + requirements: + - !type:CharacterItemGroupRequirement + group: TraitsAccents + functions: + - !type:TraitAddComponent + components: + - type: PirateAccent - type: trait id: Accentless - category: Speech + category: TraitsSpeechAccents points: -1 requirements: - !type:CharacterJobRequirement @@ -14,19 +19,28 @@ jobs: - Borg - MedicalBorg - components: - - type: Accentless - removes: - - type: LizardAccent - - type: MothAccent - - type: ReplacementAccent - accent: dwarf + - !type:CharacterItemGroupRequirement + group: TraitsAccents + functions: + - !type:TraitAddComponent + components: + - type: Accentless + removes: + - type: LizardAccent + - type: MothAccent + - type: ReplacementAccent + accent: dwarf - type: trait id: Southern - category: Speech - components: - - type: SouthernAccent + category: TraitsSpeechAccents + requirements: + - !type:CharacterItemGroupRequirement + group: TraitsAccents + functions: + - !type:TraitAddComponent + components: + - type: SouthernAccent - type: trait id: NormalVision @@ -36,5 +50,75 @@ species: - Harpy - Vulpkanin - components: - - type: NormalVision + functions: + - !type:TraitRemoveComponent + components: + - type: UltraVision + - type: DogVision + +- type: trait + id: Saturnine + category: Mental + points: 6 + requirements: + - !type:CharacterJobRequirement + inverted: true + jobs: + - Borg + - MedicalBorg + - !type:CharacterTraitRequirement + inverted: true + traits: + - Sanguine + functions: + - !type:TraitAddMoodlets + moodEffects: + - TraitSaturnine + +- type: trait + id: Sanguine + category: Mental + points: -6 + requirements: + - !type:CharacterJobRequirement + inverted: true + jobs: + - Borg + - MedicalBorg + - !type:CharacterTraitRequirement + inverted: true + traits: + - Saturnine + functions: + - !type:TraitAddMoodlets + moodEffects: + - TraitSanguine + +- type: trait + id: AddictionNicotine + category: Mental + points: 1 + requirements: + - !type:CharacterJobRequirement + inverted: true + jobs: + - Borg + - MedicalBorg + - !type:CharacterSpeciesRequirement + inverted: true + species: + - IPC + functions: + - !type:TraitAddMoodlets + moodEffects: + - NicotineWithdrawal + +- type: trait + id: Liar + category: Mental + functions: + - !type:TraitAddComponent + components: + - type: ReplacementAccent + replacementChance: 0.15 + accent: liar diff --git a/Resources/Prototypes/Traits/physical.yml b/Resources/Prototypes/Traits/physical.yml index 85d074ffe9..083a91382f 100644 --- a/Resources/Prototypes/Traits/physical.yml +++ b/Resources/Prototypes/Traits/physical.yml @@ -1,79 +1,103 @@ - type: trait id: WillToLive category: Physical - points: -2 + points: -1 requirements: - !type:CharacterJobRequirement inverted: true jobs: - Borg - MedicalBorg + - !type:CharacterSpeciesRequirement + inverted: true + species: + - IPC - !type:CharacterTraitRequirement inverted: true traits: - WillToDie - components: - - type: DeadModifier - deadThresholdModifier: 10 + functions: + - !type:TraitReplaceComponent + components: + - type: DeadModifier + deadThresholdModifier: 10 - type: trait id: WillToDie category: Physical - points: 1 + points: 2 requirements: - !type:CharacterJobRequirement inverted: true jobs: - Borg - MedicalBorg + - !type:CharacterSpeciesRequirement + inverted: true + species: + - IPC - !type:CharacterTraitRequirement inverted: true traits: - WillToLive - components: - - type: DeadModifier - deadThresholdModifier: -15 + functions: + - !type:TraitReplaceComponent + components: + - type: DeadModifier + deadThresholdModifier: -15 - type: trait id: Tenacity category: Physical - points: -2 + points: -3 requirements: - !type:CharacterJobRequirement inverted: true jobs: - Borg - MedicalBorg + - !type:CharacterSpeciesRequirement + inverted: true + species: + - IPC - !type:CharacterTraitRequirement inverted: true traits: - GlassJaw - components: - - type: CritModifier - critThresholdModifier: 5 + functions: + - !type:TraitReplaceComponent + components: + - type: CritModifier + critThresholdModifier: 5 - type: trait id: GlassJaw category: Physical - points: 1 + points: 2 requirements: - !type:CharacterJobRequirement inverted: true jobs: - Borg - MedicalBorg + - !type:CharacterSpeciesRequirement + inverted: true + species: + - IPC - !type:CharacterTraitRequirement inverted: true traits: - Tenacity - components: - - type: CritModifier - critThresholdModifier: -10 + functions: + - !type:TraitReplaceComponent + components: + - type: CritModifier + critThresholdModifier: -10 - type: trait id: Vigor category: Physical - points: -3 + points: -6 requirements: - !type:CharacterJobRequirement inverted: true @@ -88,14 +112,17 @@ inverted: true species: - Oni - components: - - type: StaminaCritModifier - critThresholdModifier: 10 + - IPC + functions: + - !type:TraitReplaceComponent + components: + - type: StaminaCritModifier + critThresholdModifier: 10 - type: trait id: Lethargy category: Physical - points: 1 + points: 4 requirements: - !type:CharacterJobRequirement inverted: true @@ -110,51 +137,65 @@ inverted: true species: - Felinid - components: - - type: StaminaCritModifier - critThresholdModifier: -15 + functions: + - !type:TraitReplaceComponent + components: + - type: StaminaCritModifier + critThresholdModifier: -15 - type: trait id: HighAdrenaline category: Physical - points: -3 + points: -5 requirements: - !type:CharacterJobRequirement inverted: true jobs: - Borg - MedicalBorg + - !type:CharacterSpeciesRequirement + inverted: true + species: + - IPC - !type:CharacterTraitRequirement inverted: true traits: - AdrenalDysfunction - components: - - type: Adrenaline - rangeModifier: 0.4 - inverse: true + functions: + - !type:TraitReplaceComponent + components: + - type: Adrenaline + rangeModifier: 0.4 + inverse: true - type: trait id: AdrenalDysfunction category: Physical - points: 1 + points: 3 requirements: - !type:CharacterJobRequirement inverted: true jobs: - Borg - MedicalBorg + - !type:CharacterSpeciesRequirement + inverted: true + species: + - IPC - !type:CharacterTraitRequirement inverted: true traits: - HighAdrenaline - components: - - type: Adrenaline - rangeModifier: 0.8 + functions: + - !type:TraitReplaceComponent + components: + - type: Adrenaline + rangeModifier: 0.8 - type: trait id: Masochism category: Physical - points: -3 + points: -5 requirements: - !type:CharacterJobRequirement inverted: true @@ -165,15 +206,17 @@ inverted: true traits: - LowPainTolerance - components: - - type: PainTolerance - rangeModifier: 0.4 - inverse: true + functions: + - !type:TraitReplaceComponent + components: + - type: PainTolerance + rangeModifier: 0.4 + inverse: true - type: trait id: LowPainTolerance category: Physical - points: 1 + points: 3 requirements: - !type:CharacterJobRequirement inverted: true @@ -184,14 +227,16 @@ inverted: true traits: - Masochism - components: - - type: PainTolerance - rangeModifier: 0.6 + functions: + - !type:TraitReplaceComponent + components: + - type: PainTolerance + rangeModifier: 0.6 - type: trait id: MartialArtist category: Physical - points: -2 + points: -3 requirements: - !type:CharacterJobRequirement inverted: true @@ -199,10 +244,401 @@ - Borg - MedicalBorg - Boxer - components: - - type: Boxer - modifiers: - coefficients: - Blunt: 1.5 - Slash: 1.5 - Piercing: 1.5 + functions: + - !type:TraitReplaceComponent + components: + - type: Boxer + modifiers: + coefficients: + Blunt: 1.5 + Slash: 1.5 + Piercing: 1.5 + +- type: trait + id: Small + category: Physical + points: -2 + requirements: + - !type:CharacterSpeciesRequirement + inverted: true + species: + - Felinid # Felinids already have this feature by default. + - !type:CharacterHeightRequirement + max: 150 + - !type:CharacterWidthRequirement + max: 32 + functions: + - !type:TraitAddComponent + components: + - type: PseudoItem + storedOffset: 0,17 + shape: + - 0,0,1,4 + - 0,2,3,4 + - 4,0,5,4 + +- type: trait + id: TemperatureTolerance + category: Physical + points: -1 + requirements: + - !type:CharacterSpeciesRequirement + inverted: true + species: + - Vulpkanin # This trait functions exactly as-is for the Vulpkanin trait. + functions: + - !type:TraitReplaceComponent + components: + - type: TemperatureProtection + coefficient: 0.1 # Enough resistance to walk into the chef's freezer, or tolerate daytime temperatures on Glacier without a jacket. + +# These traits largely exist to demonstrate more of the "Component Removals" functionality. This way contributors +# can get used to seeing that they can "Remove and Replace" a pre-existing component. +# When declared, componentRemovals work like a "RemComp" that activates upon joining a round. +- type: trait + id: Talons + category: Physical + points: -1 + requirements: + - !type:CharacterSpeciesRequirement + inverted: true + species: + - Harpy # Harpies already have talons + - Arachnid # Apparently they have a "piercing" bite + - !type:CharacterTraitRequirement + inverted: true + traits: + - Claws + functions: + - !type:TraitReplaceComponent + components: + - type: MeleeWeapon + soundHit: + collection: AlienClaw + animation: WeaponArcClaw + damage: + types: + Piercing: 5 # No, this isn't "OP", this is literally the worst brute damage type in the game. + # Same deal as Slash, except that a majority of all armor provides Piercing resistance. + +- type: trait + id: Claws + category: Physical + points: -1 + requirements: + - !type:CharacterSpeciesRequirement + inverted: true + species: + - Felinid # Felinids already have cat claws. + - Reptilian # Reptilians also have cat claws. + - Shadowkin # Shadowkins also have claws. + # - Vulpkanin # Vulpkanin have "Blunt" claws. One could argue this trait "Sharpens" their claws. + - !type:CharacterTraitRequirement + inverted: true + traits: + - Talons + functions: + - !type:TraitReplaceComponent + components: + - type: MeleeWeapon + soundHit: + collection: AlienClaw + angle: 30 + animation: WeaponArcClaw + damage: + types: + Slash: 5 # Trade stamina damage on hit for a very minor amount of extra bleed. + # Blunt also deals bleed damage, so this is more of a sidegrade. + +- type: trait + id: NaturalWeaponRemoval + category: Physical + points: 0 + requirements: + - !type:CharacterSpeciesRequirement + inverted: true + species: + - Human + - Oni + - SlimePerson + - !type:CharacterTraitRequirement + inverted: true + traits: + - Talons + - Claws + functions: + - !type:TraitReplaceComponent + components: + - type: MeleeWeapon + soundHit: + collection: Punch + angle: 30 + animation: WeaponArcFist + damage: + types: + Blunt: 5 + +- type: trait + id: StrikingCalluses + category: Physical + points: -4 + requirements: + - !type:CharacterJobRequirement + inverted: true + jobs: + - Prisoner # Bionics should be "Confiscated" from long term prisoners. + - !type:CharacterSpeciesRequirement + species: + - Human # Entirely arbitrary, I've decided I want a trait unique to humans. Since they don't normally get anything exciting. + # When we get the Character Records system in, I also want to make this require certain Backgrounds. + - !type:CharacterTraitRequirement + inverted: true + traits: + - Claws + - Talons + - !type:CharacterLogicOrRequirement + requirements: + - !type:CharacterTraitRequirement + traits: + - MartialArtist + - !type:CharacterJobRequirement + jobs: + - Boxer + functions: + - !type:TraitReplaceComponent + components: + - type: MeleeWeapon + soundHit: + collection: Punch + angle: 30 + animation: WeaponArcFist + damage: + types: + Blunt: 6 + +- type: trait + id: Spinarette + category: Physical + points: -4 + requirements: + - !type:CharacterJobRequirement + inverted: true + jobs: + - Prisoner # Bionics should be "Confiscated" from long term prisoners. + - !type:CharacterSpeciesRequirement + inverted: true + species: + - Arachnid + - Arachne + - Shadowkin + - IPC + functions: + - !type:TraitAddComponent + components: + - type: Sericulture + action: ActionSericulture + productionLength: 2 + entityProduced: MaterialWebSilk1 + hungerCost: 4 + +- type: trait + id: BionicArm + category: Physical + points: -8 + requirements: + - !type:CharacterJobRequirement + inverted: true + jobs: + - Prisoner # Bionics should be "Confiscated" from long term prisoners. + functions: + - !type:TraitAddComponent + components: + - type: Prying + speedModifier: 1 + pryPowered: true + +- type: trait + id: PlateletFactories + category: Physical + points: -10 + requirements: + - !type:CharacterJobRequirement + inverted: true + jobs: + - Prisoner # Bionics should be "Confiscated" from long term prisoners. + - !type:CharacterSpeciesRequirement + inverted: true + species: + - IPC + functions: # TODO: Code Platelet factories as an actual obtainable implant, and replace this with TraitAddImplant + - !type:TraitReplaceComponent + components: + - type: PassiveDamage + allowedStates: + - Alive + - Critical + damageCap: 200 + damage: + groups: + Brute: -0.07 + Burn: -0.07 + Airloss: -0.07 + Toxin: -0.07 + Genetic: -0.07 + +- type: trait + id: DermalArmor + category: Physical + points: -9 + requirements: + - !type:CharacterJobRequirement + inverted: true + jobs: + - Prisoner # Bionics should be "Confiscated" from long term prisoners. + - !type:CharacterSpeciesRequirement + species: + - Human + functions: + - !type:TraitReplaceComponent + components: + - type: Damageable + damageModifierSet: DermalArmor + +- type: trait + id: CyberEyes + category: Physical + points: -8 + requirements: + - !type:CharacterJobRequirement + inverted: true + jobs: + - Prisoner # Bionics should be "Confiscated" from long term prisoners. + - !type:CharacterTraitRequirement + inverted: true + traits: + - Photophobia + - Blindness + - Nearsighted + functions: + - !type:TraitRemoveComponent + components: + - type: Flashable + - !type:TraitAddComponent + components: + - type: FlashImmunity + - type: EyeProtection + - type: CyberEyes + +- type: trait + id: CyberEyesSecurity + category: Physical + points: -1 + requirements: + - !type:CharacterJobRequirement + inverted: true + jobs: + - Prisoner # Bionics should be "Confiscated" from long term prisoners. + - !type:CharacterDepartmentRequirement + departments: + - Security + - !type:CharacterTraitRequirement + traits: + - CyberEyes + - !type:CharacterTraitRequirement + inverted: true + traits: + - CyberEyesOmni + functions: + - !type:TraitAddComponent + components: + - type: ShowJobIcons + - type: ShowMindShieldIcons + - type: ShowCriminalRecordIcons + +- type: trait + id: CyberEyesMedical + category: Physical + points: -1 + requirements: + - !type:CharacterJobRequirement + inverted: true + jobs: + - Prisoner # Bionics should be "Confiscated" from long term prisoners. + - !type:CharacterTraitRequirement + traits: + - CyberEyes + - !type:CharacterTraitRequirement + inverted: true + traits: + - CyberEyesDiagnostic + - CyberEyesOmni + functions: + - !type:TraitAddComponent + components: + - type: ShowHealthBars + damageContainers: + - Biological + - type: ShowHealthIcons + damageContainers: + - Biological + +- type: trait + id: CyberEyesDiagnostic + category: Physical + points: -1 + requirements: + - !type:CharacterJobRequirement + inverted: true + jobs: + - Prisoner # Bionics should be "Confiscated" from long term prisoners. + - !type:CharacterTraitRequirement + traits: + - CyberEyes + - !type:CharacterTraitRequirement + inverted: true + traits: + - CyberEyesMedical + - CyberEyesOmni + functions: + - !type:TraitAddComponent + components: + - type: ShowHealthBars + damageContainers: + - Inorganic + - Silicon + +- type: trait + id: CyberEyesOmni + category: Physical + points: -3 + requirements: + - !type:CharacterJobRequirement + inverted: true + jobs: + - Prisoner # Bionics should be "Confiscated" from long term prisoners. + - !type:CharacterDepartmentRequirement + departments: + - Security + - !type:CharacterTraitRequirement + traits: + - CyberEyes + - !type:CharacterTraitRequirement + inverted: true + traits: + - CyberEyesMedical + - CyberEyesDiagnostic + - CyberEyesSecurity + functions: + - !type:TraitAddComponent + components: + - type: ShowJobIcons + - type: ShowMindShieldIcons + - type: ShowCriminalRecordIcons + - type: ShowHealthIcons + damageContainers: + - Biological + - type: ShowHealthBars + damageContainers: + - Biological + - Inorganic + - Silicon diff --git a/Resources/Prototypes/Traits/skills.yml b/Resources/Prototypes/Traits/skills.yml index 7856a68ab3..2306d254ed 100644 --- a/Resources/Prototypes/Traits/skills.yml +++ b/Resources/Prototypes/Traits/skills.yml @@ -1,9 +1,11 @@ - type: trait id: CPRTraining category: Mental - points: -2 - components: - - type: CPRTraining + points: -4 + functions: + - !type:TraitAddComponent + components: + - type: CPRTraining requirements: - !type:CharacterJobRequirement inverted: true @@ -18,20 +20,22 @@ - type: trait id: SelfAware category: Mental - points: -2 - components: - - type: SelfAware - analyzableTypes: - - Blunt - - Slash - - Piercing - - Heat - - Shock - - Cold - - Caustic - detectableGroups: - - Airloss - - Toxin + points: -4 + functions: + - !type:TraitAddComponent + components: + - type: SelfAware + analyzableTypes: + - Blunt + - Slash + - Piercing + - Heat + - Shock + - Cold + - Caustic + detectableGroups: + - Airloss + - Toxin - type: trait id: HeavyweightDrunk @@ -52,14 +56,17 @@ inverted: true species: - Dwarf - components: - - type: LightweightDrunk - boozeStrengthMultiplier: 0.5 + - IPC + functions: + - !type:TraitReplaceComponent + components: + - type: LightweightDrunk + boozeStrengthMultiplier: 0.5 - type: trait id: LiquorLifeline category: Physical - points: -3 + points: -6 requirements: - !type:CharacterJobRequirement inverted: true @@ -75,56 +82,53 @@ inverted: true species: - Dwarf - components: - - type: LiquorLifeline - - type: LightweightDrunk - boozeStrengthMultiplier: 0.5 + - IPC + functions: + - !type:TraitReplaceComponent + components: + - type: LiquorLifeline + - type: LightweightDrunk + boozeStrengthMultiplier: 0.5 - type: trait id: Thieving category: Physical - points: -4 - components: - - type: Thieving - ignoreStripHidden: true - stealth: Subtle - stripTimeReduction: 0 - stripTimeMultiplier: 0.667 + points: -8 + functions: + - !type:TraitReplaceComponent + components: + - type: Thieving + ignoreStripHidden: true + stealth: Subtle + stripTimeReduction: 0 + stripTimeMultiplier: 0.667 requirements: - !type:CharacterSpeciesRequirement inverted: true species: - Felinid -- type: trait - id: SignLanguage - category: Visual - points: -1 - components: - - type: LanguageKnowledgeModifier - speaks: - - Sign - understands: - - Sign - - type: trait id: Voracious category: Physical - points: -1 - components: - - type: ConsumeDelayModifier - foodDelayMultiplier: 0.5 - drinkDelayMultiplier: 0.5 + points: -2 + functions: + - !type:TraitReplaceComponent + components: + - type: ConsumeDelayModifier + foodDelayMultiplier: 0.5 + drinkDelayMultiplier: 0.5 requirements: - !type:CharacterSpeciesRequirement inverted: true species: - Vulpkanin + - IPC - type: trait id: ParkourTraining category: Physical - points: -3 + points: -5 requirements: - !type:CharacterTraitRequirement inverted: true @@ -135,21 +139,25 @@ inverted: true species: - Diona - components: - - type: ClimbDelayModifier - climbDelayMultiplier: 0.70 - - type: LayingDownModifier - layingDownCooldownMultiplier: 0.8 - downedSpeedMultiplierMultiplier: 1.25 + functions: + - !type:TraitReplaceComponent + components: + - type: ClimbDelayModifier + climbDelayMultiplier: 0.35 + - type: LayingDownModifier + layingDownCooldownMultiplier: 0.5 + downedSpeedMultiplierMultiplier: 1.65 - type: trait id: LightStep category: Auditory - points: -1 - components: - - type: FootstepVolumeModifier - sprintVolumeModifier: -10 - walkVolumeModifier: -10 + points: -2 + functions: + - !type:TraitReplaceComponent + components: + - type: FootstepVolumeModifier + sprintVolumeModifier: -10 + walkVolumeModifier: -10 requirements: - !type:CharacterSpeciesRequirement inverted: true @@ -159,12 +167,173 @@ - type: trait id: Singer category: Auditory - points: -1 + points: -2 + requirements: + - !type:CharacterSpeciesRequirement + inverted: true + species: + - Harpy + functions: + - !type:TraitAddComponent + components: + - type: Singer + proto: NormalSinger + +- type: trait + id: LatentPsychic + category: Mental + points: -4 + functions: + - !type:TraitAddComponent + components: + - type: Psionic + requirements: + - !type:CharacterJobRequirement + inverted: true + jobs: + - Borg + - MedicalBorg + - ResearchDirector + - ForensicMantis + - Chaplain + - Librarian + - !type:CharacterLogicOrRequirement + requirements: + - !type:CharacterSpeciesRequirement + inverted: true + species: + - IPC + - !type:CharacterTraitRequirement + traits: + - AnomalousPositronics + - !type:CharacterLogicOrRequirement + requirements: + - !type:CharacterSpeciesRequirement + inverted: true + species: + - Shadowkin + - !type:CharacterTraitRequirement + inverted: true + traits: + - PsionicInsulation + +- type: trait + id: PsionicInsulation + category: Mental + points: -10 #Buy a significant disability to get this. + functions: + - !type:TraitAddComponent + components: + - type: PsionicInsulation + - type: Mindbroken + requirements: + - !type:CharacterJobRequirement + inverted: true + jobs: + - Borg + - MedicalBorg + - ResearchDirector + - ForensicMantis + - Chaplain + - Librarian + - !type:CharacterLogicOrRequirement + requirements: + - !type:CharacterSpeciesRequirement + inverted: true + species: + - IPC + - !type:CharacterTraitRequirement + traits: + - AnomalousPositronics + - !type:CharacterTraitRequirement + inverted: true + traits: + - LatentPsychic + - !type:CharacterLogicOrRequirement + requirements: + - !type:CharacterSpeciesRequirement + inverted: true + species: + - Shadowkin + +- type: trait + id: NaturalTelepath + category: Mental + points: -2 + functions: + - !type:TraitAddPsionics + psionicPowers: + - TelepathyPower + requirements: + - !type:CharacterJobRequirement + inverted: true + jobs: + - ResearchDirector + - ForensicMantis + - !type:CharacterLogicOrRequirement + requirements: + - !type:CharacterTraitRequirement + traits: + - LatentPsychic + - !type:CharacterJobRequirement + jobs: + - Chaplain + - Librarian + - !type:CharacterLogicOrRequirement + requirements: + - !type:CharacterSpeciesRequirement + inverted: true + species: + - IPC + - !type:CharacterTraitRequirement + traits: + - AnomalousPositronics + - !type:CharacterLogicOrRequirement + requirements: + - !type:CharacterSpeciesRequirement + inverted: true + species: + - Shadowkin + +- type: trait + id: TrapAvoider + category: Physical + points: -3 + functions: + - !type:TraitAddComponent + components: + - type: StepTriggerImmune + whitelist: + types: + - Shard + - Landmine + - Mousetrap + - SlipEntity requirements: - !type:CharacterSpeciesRequirement inverted: true species: + - Felinid - Harpy - components: - - type: Singer - proto: NormalSinger + +- type: trait + id: AnomalousPositronics + category: Mental + points: -4 + functions: + - !type:TraitRemoveComponent + components: + - type: PsionicInsulation + requirements: + - !type:CharacterSpeciesRequirement + species: + - IPC + +- type: trait + id: AnimalFriend + category: Mental + points: -4 + functions: + - !type:TraitModifyFactions + addFactions: + - AnimalFriend diff --git a/Resources/Prototypes/Traits/species.yml b/Resources/Prototypes/Traits/species.yml index 2c29825228..a3e29b3728 100644 --- a/Resources/Prototypes/Traits/species.yml +++ b/Resources/Prototypes/Traits/species.yml @@ -1,14 +1,16 @@ - type: trait id: Swashbuckler category: Physical - points: -1 - components: - - type: OniDamageModifier - modifiers: - coefficients: - Blunt: 1.2 - Slash: 1.35 - Piercing: 1.2 + points: -2 + functions: + - !type:TraitReplaceComponent + components: + - type: OniDamageModifier + modifiers: + coefficients: + Blunt: 1.2 + Slash: 1.35 + Piercing: 1.2 requirements: - !type:CharacterSpeciesRequirement species: @@ -22,14 +24,16 @@ - type: trait id: Spearmaster category: Physical - points: -1 - components: - - type: OniDamageModifier - modifiers: - coefficients: - Blunt: 1.2 - Slash: 1.2 - Piercing: 1.35 + points: -2 + functions: + - !type:TraitReplaceComponent + components: + - type: OniDamageModifier + modifiers: + coefficients: + Blunt: 1.2 + Slash: 1.2 + Piercing: 1.35 requirements: - !type:CharacterSpeciesRequirement species: @@ -43,14 +47,16 @@ - type: trait id: WeaponsGeneralist category: Physical - points: -1 - components: - - type: OniDamageModifier - modifiers: - coefficients: - Blunt: 1.25 - Slash: 1.25 - Piercing: 1.25 + points: -2 + functions: + - !type:TraitReplaceComponent + components: + - type: OniDamageModifier + modifiers: + coefficients: + Blunt: 1.25 + Slash: 1.25 + Piercing: 1.25 requirements: - !type:CharacterSpeciesRequirement species: @@ -60,3 +66,17 @@ traits: - Swashbuckler - Spearmaster + +- type: trait + id: ShadowkinBlackeye + category: Mental + points: 4 + functions: + - !type:TraitReplaceComponent + components: + - type: Shadowkin + blackeyeSpawn: true + requirements: + - !type:CharacterSpeciesRequirement + species: + - Shadowkin diff --git a/Resources/Prototypes/Voice/disease_emotes.yml b/Resources/Prototypes/Voice/disease_emotes.yml index 7845fd3e6d..6b9914ad45 100644 --- a/Resources/Prototypes/Voice/disease_emotes.yml +++ b/Resources/Prototypes/Voice/disease_emotes.yml @@ -1,43 +1,65 @@ - type: emote id: Sneeze + name: chat-emote-name-sneeze category: Vocal - chatMessages: [ sneezes ] + chatMessages: [ "chat-emote-msg-sneeze" ] - type: emote id: Cough + name: chat-emote-name-cough + icon: Interface/Emotes/cough.png category: Vocal - chatMessages: [ coughs ] + chatMessages: [ "chat-emote-msg-cough" ] + whitelist: + components: + - Vocal + blacklist: + components: + - BorgChassischatMessages chatTriggers: - coughs - type: emote id: CatMeow + name: chat-emote-name-catmeow category: Vocal - chatMessages: [ meows ] + chatMessages: [ "chat-emote-msg-catmeow" ] - type: emote id: CatHisses + name: chat-emote-name-cathisses category: Vocal - chatMessages: [ hisses ] + chatMessages: [ "chat-emote-msg-cathisses" ] - type: emote id: MonkeyScreeches + name: chat-emote-name-monkeyscreeches category: Vocal - chatMessages: [ screeches ] + chatMessages: [ "chat-emote-msg-monkeyscreeches" ] - type: emote id: RobotBeep + name: chat-emote-name-robotbeep category: Vocal - chatMessages: [ beeps ] + chatMessages: [ "chat-emote-msg-beep" ] - type: emote id: Yawn + name: chat-emote-name-yawn + icon: Interface/Emotes/yawn.png category: Vocal - chatMessages: [ yawns ] + chatMessages: [ "chat-emote-msg-yawn" ] + whitelist: + components: + - Vocal + blacklist: + components: + - BorgChassischatMessages chatTriggers: - yawns - type: emote id: Snore + name: chat-emote-name-snore category: Vocal - chatMessages: [ snores ] + chatMessages: [ "chat-emote-msg-snore" ] diff --git a/Resources/Prototypes/Voice/speech_emote_sounds.yml b/Resources/Prototypes/Voice/speech_emote_sounds.yml index c015ca7945..5c3f8ec697 100644 --- a/Resources/Prototypes/Voice/speech_emote_sounds.yml +++ b/Resources/Prototypes/Voice/speech_emote_sounds.yml @@ -72,7 +72,7 @@ collection: Weh - type: emoteSounds - id: UnisexReptilian + id: MaleReptilian params: variation: 0.125 sounds: @@ -89,6 +89,24 @@ Weh: collection: Weh +- type: emoteSounds + id: FemaleReptilian + params: + variation: 0.125 + sounds: + Scream: + path: /Audio/Voice/Reptilian/reptilian_scream.ogg + Laugh: + path: /Audio/Animals/lizard_happy.ogg + Honk: + collection: BikeHorn + Whistle: + collection: Whistles + Crying: + collection: FemaleCry + Weh: + collection: Weh + - type: emoteSounds id: MaleSlime sounds: @@ -165,6 +183,90 @@ params: variation: 0.125 +- type: emoteSounds + id: MaleShadowkin + params: + variation: 0.125 + sounds: + Scream: + collection: SlimeMaleScreams + Laugh: + collection: MaleLaugh + Sneeze: + collection: MaleSneezes + Cough: + collection: MaleCoughs + Yawn: + collection: MaleYawn + Snore: + collection: Snores + Honk: + collection: BikeHorn + Sigh: + collection: MaleSigh + Crying: + collection: MaleCry + Whistle: + collection: Whistles + Weh: + collection: Weh + Hiss: + collection: FelinidHisses + Meow: + collection: FelinidMeows + Mew: + collection: FelinidMews + Growl: + collection: FelinidGrowls + Purr: + collection: FelinidPurrs + Mars: + collection: Mars + Wurble: + collection: Wurble + +- type: emoteSounds + id: FemaleShadowkin + params: + variation: 0.125 + sounds: + Scream: + collection: SlimeFemaleScreams + Laugh: + collection: MaleLaugh + Sneeze: + collection: FemaleSneezes + Cough: + collection: FemaleCoughs + Yawn: + collection: FemaleYawn + Snore: + collection: Snores + Honk: + collection: BikeHorn + Sigh: + collection: FemaleSigh + Crying: + collection: FemaleCry + Whistle: + collection: Whistles + Weh: + collection: Weh + Hiss: + collection: FelinidHisses + Meow: + collection: FelinidMeows + Mew: + collection: FelinidMews + Growl: + collection: FelinidGrowls + Purr: + collection: FelinidPurrs + Mars: + collection: Mars + Wurble: + collection: Wurble + - type: emoteSounds id: UnisexVox sounds: @@ -316,6 +418,26 @@ Ping: path: /Audio/Effects/Cargo/ping.ogg +- type: emoteSounds + id: UnisexSiliconSyndicate + params: + variation: 0.05 + sounds: + Laugh: + path: /Audio/Voice/Silicon/syndieborg_laugh.ogg + Beep: + path: /Audio/Machines/twobeep.ogg + Chime: + path: /Audio/Machines/chime.ogg + Buzz: + path: /Audio/Machines/buzz-sigh.ogg + Buzz-Two: + path: /Audio/Machines/buzz-two.ogg + Honk: + path: /Audio/Items/bikehorn.ogg + Ping: + path: /Audio/Effects/Cargo/ping.ogg + # body emotes - type: emoteSounds id: GeneralBodyEmotes @@ -405,3 +527,67 @@ path: /Audio/Animals/parrot_raught.ogg params: variation: 0.125 + +- type: emoteSounds + id: UnisexIPC + params: + variation: 0 + sounds: + Scream: + path: /Audio/Voice/IPC/robot-scream.ogg + params: + variation: 0.125 + Laugh: + path: /Audio/Voice/IPC/robot-laugh_3.ogg + params: + variation: 0.125 + Sigh: + path: /Audio/Voice/Talk/pai.ogg + params: + variation: 0.125 + Crying: + path: /Audio/Voice/IPC/cry_robot_1.ogg + params: + variation: 0.125 + Whistle: + path: /Audio/Voice/IPC/pai_whistle.ogg + params: + variation: 0.125 + CatMeow: + collection: CatMeows + params: + variation: 0.125 + CatHisses: + collection: CatHisses + params: + variation: 0.125 + MonkeyScreeches: + collection: MonkeyScreeches + params: + variation: 0.125 + RobotBeep: # disease + path: /Audio/Effects/tesla_consume.ogg + params: + variation: 0.125 + Beep: # normal + path: /Audio/Voice/IPC/beep_2000.ogg + Boop: + path: /Audio/Voice/IPC/beep_500.ogg + Buzz: + path: /Audio/Machines/buzz-sigh.ogg + Honk: + path: /Audio/Items/bikehorn.ogg + params: + variation: 0.125 + Chime: + path: /Audio/Effects/Cargo/ping.ogg + Buzz-Two: + path: /Audio/Machines/buzz-two.ogg + Ping: + path: /Audio/Effects/beep1.ogg + params: # to prevent it from being definitively read by players as "OH SHIT A GRENADE" + variation: 0.125 + Whirr: + collection: IPCWhirrs + params: + variation: 0 diff --git a/Resources/Prototypes/Voice/speech_emotes.yml b/Resources/Prototypes/Voice/speech_emotes.yml index 6c94294e2b..309794c1dc 100644 --- a/Resources/Prototypes/Voice/speech_emotes.yml +++ b/Resources/Prototypes/Voice/speech_emotes.yml @@ -1,8 +1,16 @@ # vocal emotes - type: emote id: Scream + name: chat-emote-name-scream category: Vocal - chatMessages: [ screams ] + icon: Interface/Actions/scream.png + whitelist: + components: + - Vocal + blacklist: + components: + - BorgChassis + chatMessages: ["chat-emote-msg-scream"] chatTriggers: - screams - shrieks @@ -11,8 +19,16 @@ - type: emote id: Laugh + name: chat-emote-name-laugh category: Vocal - chatMessages: [ laughs ] + icon: Interface/Emotes/laugh.png + whitelist: + components: + - Vocal + blacklist: + components: + - BorgChassischatMessages + chatMessages: ["chat-emote-msg-laugh"] chatTriggers: - laughs - chuckles @@ -21,73 +37,156 @@ - type: emote id: Honk + name: chat-emote-name-honk category: Vocal - chatMessages: [ honks ] + icon: Interface/Emotes/honk.png + chatMessages: ["chat-emote-msg-honk"] + whitelist: + requireAll: true + components: + - Vocal + - BorgChassischatMessages chatTriggers: - honks - type: emote id: Sigh + name: chat-emote-name-sigh category: Vocal - chatMessages: [ sighs ] + icon: Interface/Emotes/sigh.png + whitelist: + components: + - Vocal + blacklist: + components: + - BorgChassischatMessages + chatMessages: ["chat-emote-msg-sigh"] chatTriggers: - sighs - type: emote id: Whistle + name: chat-emote-name-whistle category: Vocal - chatMessages: [ whistles ] + icon: Interface/Emotes/whistle.png + whitelist: + components: + - Vocal + blacklist: + components: + - BorgChassis + chatMessages: ["chat-emote-msg-whistle"] chatTriggers: - whistles - type: emote id: Crying + name: chat-emote-name-crying category: Vocal - chatMessages: [ cries ] + icon: Interface/Emotes/cry.png + whitelist: + components: + - Vocal + blacklist: + components: + - BorgChassis + chatMessages: ["chat-emote-msg-crying"] chatTriggers: - cries - sobs - type: emote id: Squish + name: chat-emote-name-squish category: Vocal - chatMessages: [ squishes ] + available: false + icon: Interface/Emotes/squish.png + whitelist: + components: + - Vocal + blacklist: + components: + - BorgChassis + chatMessages: ["chat-emote-msg-squish"] chatTriggers: - squishes - type: emote id: Chitter + name: chat-emote-name-chitter category: Vocal - chatMessages: [ chitters ] + available: false + icon: Interface/Emotes/chitter.png + whitelist: + components: + - Vocal + blacklist: + components: + - BorgChassis + chatMessages: ["chat-emote-msg-chitter"] chatTriggers: - chitters - type: emote id: Squeak + name: chat-emote-name-squeak category: Vocal - chatMessages: [ squeaks ] + available: false + icon: Interface/Emotes/squeak.png + whitelist: + components: + - Vocal + blacklist: + components: + - BorgChassis + chatMessages: ["chat-emote-msg-squeak"] chatTriggers: - squeaks - type: emote id: Click + name: chat-emote-name-click category: Vocal - chatMessages: [ clicks ] + available: false + icon: Interface/Emotes/click.png + whitelist: + components: + - Vocal + blacklist: + components: + - BorgChassis + chatMessages: ["chat-emote-msg-click"] chatTriggers: - clicks # hand emotes - type: emote id: Clap + name: chat-emote-name-clap category: Hands - chatMessages: [ claps ] + icon: Interface/Emotes/clap.png + whitelist: + components: + - Hands + blacklist: + components: + - BorgChassis + chatMessages: ["chat-emote-msg-clap"] chatTriggers: - claps - type: emote id: Snap + name: chat-emote-name-snap category: Hands - chatMessages: [ snaps ] # snaps <{THEIR($ent)}> fingers? + icon: Interface/Emotes/snap.png + whitelist: + components: + - Hands + blacklist: + components: + - BorgChassis + chatMessages: ["chat-emote-msg-snap"] # snaps <{THEIR($ent)}> fingers? chatTriggers: - snaps - snaps fingers @@ -99,61 +198,161 @@ - type: emote id: Salute + name: chat-emote-name-salute category: Hands - chatMessages: [ salutes ] + icon: Interface/Emotes/salute.png + whitelist: + components: + - Hands + blacklist: + components: + - BorgChassis + chatMessages: ["chat-emote-msg-salute"] chatTriggers: - salutes - type: emote id: DefaultDeathgasp - chatMessages: ["emote-deathgasp"] + name: chat-emote-name-deathgasp + icon: Interface/Emotes/deathgasp.png + whitelist: + components: + - MobState + chatMessages: ["chat-emote-msg-deathgasp"] chatTriggers: - deathgasp +- type: emote + id: MonkeyDeathgasp + name: chat-emote-name-deathgasp + icon: Interface/Emotes/deathgasp.png + chatMessages: ["chat-emote-msg-deathgasp-monkey"] + +- type: emote + id: SiliconDeathgasp + name: chat-emote-name-deathgasp + chatMessages: ["chat-emote-msg-deathgasp-silicon"] + chatTriggers: + - sdeathgasp + - type: emote id: Buzz + name: chat-emote-name-buzz category: Vocal - chatMessages: [ buzzes ] + icon: Interface/Emotes/buzz.png + whitelist: + requireAll: true + components: + - BorgChassis + - Vocal + chatMessages: ["chat-emote-msg-buzz"] chatTriggers: - buzzes - type: emote id: Weh + name: chat-emote-name-weh category: Vocal + icon: Interface/Emotes/weh.png chatMessages: [ wehs ] - type: emote id: Chirp + name: chat-emote-name-chirp category: Vocal - chatMessages: [ chirps ] + icon: Interface/Emotes/chirp.png + whitelist: + requireAll: true + components: + - Nymph + chatMessages: ["chat-emote-msg-chirp"] chatTriggers: - chirps # Machine Emotes - type: emote id: Beep + name: chat-emote-name-beep category: Vocal - chatMessages: [ beeps ] + icon: Interface/Emotes/beep.png + whitelist: + requireAll: true + components: + - BorgChassis + - Vocal + chatMessages: ["chat-emote-msg-beep"] chatTriggers: - beeps +- type: emote + id: Boop + name: chat-emote-name-boop + category: Vocal + chatMessages: [ boops ] + chatTriggers: + - boops + - type: emote id: Chime + name: chat-emote-name-chime category: Vocal - chatMessages: [ chimes ] + icon: Interface/Emotes/chime.png + whitelist: + requireAll: true + components: + - BorgChassis + - Vocal + chatMessages: ["chat-emote-msg-chime"] chatTriggers: - chimes - type: emote id: Buzz-Two + name: chat-emote-name-buzztwo category: Vocal - chatMessages: [ "buzzes twice" ] + icon: Interface/Emotes/buzztwo.png + whitelist: + requireAll: true + components: + - BorgChassis + - Vocal + chatMessages: ["chat-emote-msg-buzzestwo"] chatTriggers: - buzzes twice - type: emote id: Ping + name: chat-emote-name-ping category: Vocal - chatMessages: [ pings ] + icon: Interface/Emotes/ping.png + whitelist: + requireAll: true + components: + - BorgChassis + - Vocal + chatMessages: ["chat-emote-msg-ping"] chatTriggers: - pings + +- type: emote + id: Whirr + name: chat-emote-name-whirr + chatMessages: [ whirrs ] + chatTriggers: + - whirrs + +- type: emote + id: Mars + name: chat-emote-name-mars + category: Vocal + chatMessages: [ mars ] + chatTriggers: + - mars + +- type: emote + id: Wurble + name: chat-emote-name-wurble + category: Vocal + chatMessages: [ wurble ] + chatTriggers: + - wurble diff --git a/Resources/Prototypes/Voice/speech_sounds.yml b/Resources/Prototypes/Voice/speech_sounds.yml index 2e7e7bf989..a490c734d3 100644 --- a/Resources/Prototypes/Voice/speech_sounds.yml +++ b/Resources/Prototypes/Voice/speech_sounds.yml @@ -52,6 +52,24 @@ exclaimSound: path: /Audio/Machines/vending_jingle.ogg +- type: speechSounds + id: Borg + saySound: + path: /Audio/Voice/Talk/Silicon/borg.ogg + askSound: + path: /Audio/Voice/Talk/Silicon/borg_ask.ogg + exclaimSound: + path: /Audio/Voice/Talk/Silicon/borg_exclaim.ogg + +- type: speechSounds + id: SyndieBorg + saySound: + path: /Audio/Voice/Talk/Silicon/syndieborg.ogg + askSound: + path: /Audio/Voice/Talk/Silicon/syndieborg_ask.ogg + exclaimSound: + path: /Audio/Voice/Talk/Silicon/syndieborg_exclaim.ogg + - type: speechSounds id: Pai saySound: diff --git a/Resources/Prototypes/Voice/tail_emotes.yml b/Resources/Prototypes/Voice/tail_emotes.yml index be6064b652..965b7da8d9 100644 --- a/Resources/Prototypes/Voice/tail_emotes.yml +++ b/Resources/Prototypes/Voice/tail_emotes.yml @@ -1,6 +1,7 @@ - type: emote id: WagTail - chatMessages: [wags tail] + name: chat-emote-name-tailwag + chatMessages: [wags their tail] chatTriggers: - wags tail - wags his tail diff --git a/Resources/Prototypes/Wires/layouts.yml b/Resources/Prototypes/Wires/layouts.yml index 8d6be674e8..cf76e0ea61 100644 --- a/Resources/Prototypes/Wires/layouts.yml +++ b/Resources/Prototypes/Wires/layouts.yml @@ -1,6 +1,7 @@ - type: wireLayout id: Airlock wires: + - !type:AccessWireAction - !type:PowerWireAction - !type:PowerWireAction pulseTimeout: 15 @@ -44,6 +45,7 @@ - type: wireLayout id: HighSec wires: + - !type:AccessWireAction - !type:PowerWireAction pulseTimeout: 10 - !type:DoorBoltWireAction diff --git a/Resources/Prototypes/XenoArch/Effects/normal_effects.yml b/Resources/Prototypes/XenoArch/Effects/normal_effects.yml index b9564c0366..a343ebd77b 100644 --- a/Resources/Prototypes/XenoArch/Effects/normal_effects.yml +++ b/Resources/Prototypes/XenoArch/Effects/normal_effects.yml @@ -109,7 +109,7 @@ components: - type: PointLight radius: 8 - energy: 25 + energy: 10 color: "#daa3fd" - type: TriggerArtifact - type: FlashOnTrigger @@ -584,6 +584,24 @@ messages: - shuffle-artifact-popup +- type: artifactEffect + id: EffectT4PartsSpawn + targetDepth: 3 + effectHint: artifact-effect-hint-creation + components: + - type: SpawnArtifact + maxSpawns: 10 + spawns: + - id: BluespaceCapacitorStockPart + prob: 0.5 + maxAmount: 3 + - id: BluespaceManipulatorStockPart + prob: 0.5 + maxAmount: 3 + - id: BluespaceMatterBinStockPart + prob: 0.5 + maxAmount: 3 + - type: artifactEffect id: EffectFoamDangerous targetDepth: 3 diff --git a/Resources/Prototypes/XenoArch/Effects/utility_effects.yml b/Resources/Prototypes/XenoArch/Effects/utility_effects.yml index 911c1c2e88..84df09af33 100644 --- a/Resources/Prototypes/XenoArch/Effects/utility_effects.yml +++ b/Resources/Prototypes/XenoArch/Effects/utility_effects.yml @@ -214,8 +214,8 @@ permanentComponents: - type: UserInterface interfaces: - - key: enum.SignalLinkerUiKey.Key - type: SignalPortSelectorBoundUserInterface + enum.SignalLinkerUiKey.Key: + type: SignalPortSelectorBoundUserInterface - type: ToolTileCompatible - type: Tool qualities: diff --git a/Resources/Prototypes/ai_factions.yml b/Resources/Prototypes/ai_factions.yml index 3dfb35c7a6..cdbbf86866 100644 --- a/Resources/Prototypes/ai_factions.yml +++ b/Resources/Prototypes/ai_factions.yml @@ -35,6 +35,8 @@ - type: npcFaction id: SimpleHostile + friendly: + - AnimalFriend hostile: - NanoTrasen - Syndicate @@ -83,3 +85,24 @@ - Zombie - SimpleHostile - Dragon + +- type: npcFaction + id: Pibble + friendly: + - AnimalFriend + hostile: + - Cat + - Birb + - Mailman + +- type: npcFaction + id: Mailman + +- type: npcFaction + id: Cat + +- type: npcFaction + id: Birb + +- type: npcFaction + id: AnimalFriend diff --git a/Resources/Prototypes/floor_trap.yml b/Resources/Prototypes/floor_trap.yml new file mode 100644 index 0000000000..217dd9fca2 --- /dev/null +++ b/Resources/Prototypes/floor_trap.yml @@ -0,0 +1,116 @@ +- type: entity + id: CollideFloorTrap + abstract: true + placement: + mode: SnapgridCenter + components: + - type: Sprite + sprite: Tiles/Misc/floortrap.rsi + state: floortrap + - type: Fixtures + fixtures: + floortrap: + shape: + !type:PhysShapeAabb + bounds: "-0.4,-0.4,0.4,0.4" + hard: false + mask: + - ItemMask + layer: + - SlipLayer + - type: Physics + - type: Tag + tags: + - HideContextMenu + +- type: entity + parent: CollideFloorTrap + id: CollideFloorTrapSpawn + name: floor trap spawn + abstract: true + components: + - type: Sprite + sprite: Tiles/Misc/floortrap.rsi + state: floortrapspawn + +- type: entity + parent: CollideFloorTrap + id: FloorTrapExplosion + name: explosion floor trap + components: + - type: TriggerOnCollide + fixtureID: floortrap + - type: ExplodeOnTrigger + - type: Explosive + explosionType: Default + totalIntensity: 20.0 + intensitySlope: 5 + maxIntensity: 4 + - type: DeleteOnTrigger + +- type: entity + parent: CollideFloorTrap + id: FloorTrapEMP + name: EMP floor trap + components: + - type: TriggerOnCollide + fixtureID: floortrap + - type: EmpOnTrigger + range: 2 + energyConsumption: 5000 + - type: DeleteOnTrigger + +- type: entity + parent: CollideFloorTrapSpawn + id: SpawnFloorTrapCarp + suffix: Carp + components: + - type: TriggerOnCollide + fixtureID: floortrap + - type: SpawnOnTrigger + proto: MobCarp + - type: DeleteOnTrigger + +- type: entity + parent: CollideFloorTrapSpawn + id: SpawnFloorTrapBear + suffix: Bear + components: + - type: TriggerOnCollide + fixtureID: floortrap + - type: SpawnOnTrigger + proto: MobBearSpace + - type: DeleteOnTrigger + +- type: entity + parent: CollideFloorTrapSpawn + id: SpawnFloorTrapKangaroo + suffix: Kangaroo + components: + - type: TriggerOnCollide + fixtureID: floortrap + - type: SpawnOnTrigger + proto: MobKangarooSpace + - type: DeleteOnTrigger + +- type: entity + parent: CollideFloorTrapSpawn + id: SpawnFloorTrapXenoDrone + suffix: Xeno. Drone + components: + - type: TriggerOnCollide + fixtureID: floortrap + - type: SpawnOnTrigger + proto: MobXenoDrone + - type: DeleteOnTrigger + +- type: entity + parent: CollideFloorTrapSpawn + id: SpawnFloorTrapXenoBurrower + suffix: Xeno. Burrower + components: + - type: TriggerOnCollide + fixtureID: floortrap + - type: SpawnOnTrigger + proto: MobXeno + - type: DeleteOnTrigger diff --git a/Resources/Prototypes/fonts.yml b/Resources/Prototypes/fonts.yml index 92c2947258..c61a5fb804 100644 --- a/Resources/Prototypes/fonts.yml +++ b/Resources/Prototypes/fonts.yml @@ -61,3 +61,11 @@ - type: font id: Noganas path: /Fonts/Noganas.ttf + +- type: font + id: Lymphatic + path: /Fonts/Lymphatic.ttf + +- type: font + id: Cambria + path: /Fonts/Cambria.ttf diff --git a/Resources/Prototypes/game_presets.yml b/Resources/Prototypes/game_presets.yml index 7d7169bf10..bd1cc45f35 100644 --- a/Resources/Prototypes/game_presets.yml +++ b/Resources/Prototypes/game_presets.yml @@ -8,6 +8,7 @@ rules: - RampingStationEventScheduler - BasicRoundstartVariation + - SubGamemodesRule - type: gamePreset id: SurvivalHellshift @@ -19,6 +20,27 @@ rules: - HellshiftStationEventScheduler - BasicRoundstartVariation + - SubGamemodesRule + +- type: gamePreset + id: SurvivalIrregular + alias: [irregular] + showInVote: true + name: irregular-title + description: irregular-description + rules: + - IrregularStationEventScheduler + - BasicRoundstartVariation + +- type: gamePreset + id: SurvivalIrregularExtended + alias: [irregular-extended] + showInVote: true + name: irregular-extended-title + description: irregular-extended-description + rules: + - IrregularExtendedStationEventScheduler + - BasicRoundstartVariation - type: gamePreset id: AllAtOnce @@ -43,6 +65,7 @@ rules: - BasicStationEventScheduler - BasicRoundstartVariation + - SubGamemodesRule - type: gamePreset id: Greenshift @@ -164,15 +187,3 @@ - Zombie - BasicStationEventScheduler - BasicRoundstartVariation - -- type: gamePreset - id: Pirates - alias: - - pirates - name: pirates-title - description: pirates-description - showInVote: false - rules: - - Pirates - - BasicStationEventScheduler - - BasicRoundstartVariation diff --git a/Resources/Prototypes/lobbyscreens.yml b/Resources/Prototypes/lobbyscreens.yml index 773af9a143..d8c5d28379 100644 --- a/Resources/Prototypes/lobbyscreens.yml +++ b/Resources/Prototypes/lobbyscreens.yml @@ -37,3 +37,7 @@ - type: lobbyBackground id: TerminalStation background: /Textures/LobbyScreens/terminalstation.webp + +- type: lobbyBackground + id: JustAWeekAway + background: /Textures/LobbyScreens/justaweekaway.webp \ No newline at end of file diff --git a/Resources/Prototypes/name_identifier_groups.yml b/Resources/Prototypes/name_identifier_groups.yml index 82c2f3bce9..4823e31f55 100644 --- a/Resources/Prototypes/name_identifier_groups.yml +++ b/Resources/Prototypes/name_identifier_groups.yml @@ -37,3 +37,9 @@ id: Bounty minValue: 0 maxValue: 999 + +- type: nameIdentifierGroup + id: CargoTelepads + prefix: TELE + minValue: 0 + maxValue: 999 diff --git a/Resources/Prototypes/ore.yml b/Resources/Prototypes/ore.yml index dde1516c85..d471a3ac96 100644 --- a/Resources/Prototypes/ore.yml +++ b/Resources/Prototypes/ore.yml @@ -84,6 +84,18 @@ minOreYield: 2 maxOreYield: 4 +- type: ore + id: OreBluespace + oreEntity: BluespaceOre1 + minOreYield: 1 + maxOreYield: 1 + +- type: ore + id: OreNormality + oreEntity: NormalityOre1 + minOreYield: 1 + maxOreYield: 1 + - type: weightedRandomOre id: RandomOreDistributionStandard weights: @@ -96,6 +108,8 @@ OreUranium: 1 OreBananium: 0.5 OreArtifactFragment: 0.5 + OreBluespace: 0.5 + OreNormality: 0.3 - type: weightedRandomOre id: OreCrab diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml index cc81a2ed64..e7f20f8f47 100644 --- a/Resources/Prototypes/tags.yml +++ b/Resources/Prototypes/tags.yml @@ -25,6 +25,9 @@ - type: Tag id: Arrow +- type: Tag + id: Ash + - type: Tag id: ATVKeys @@ -259,6 +262,9 @@ - type: Tag id: CableCoil +- type: Tag + id: CaneBlade + - type: Tag id: CapacitorStockPart @@ -352,6 +358,9 @@ - type: Tag id: CartridgeRocket +- type: Tag + id: CaveFactory + # Allows you to walk over tile entities such as lava without steptrigger - type: Tag id: Catwalk @@ -401,6 +410,9 @@ - type: Tag id: ClownSuit +- type: Tag + id: CluwneHappyHonk + - type: Tag id: CluwneHorn @@ -803,6 +815,12 @@ - type: Tag id: MacroBomb +- type: Tag + id: Mail + +- type: Tag + id: MailCapsule + - type: Tag id: MimeBelt @@ -877,6 +895,9 @@ - type: Tag id: Meat +- type: Tag + id: Medal + - type: Tag id: Medkit @@ -916,9 +937,18 @@ - type: Tag id: Multitool +- type: Tag + id: Mustard + +- type: Tag + id: MysteryFigureBox + - type: Tag id: NoBlockAnchoring +- type: Tag + id: NoPaint + - type: Tag id: NozzleBackTank @@ -1112,15 +1142,15 @@ - type: Tag id: Shiv -- type: Tag - id: ShoesRequiredStepTriggerImmune - - type: Tag id: Shovel - type: Tag id: Sidearm +- type: Tag + id: SignalTrigger + - type: Tag id: SkeletonMotorcycleKeys @@ -1136,9 +1166,6 @@ - type: Tag id: Smokable -- type: Tag - id: SnapPop - - type: Tag id: SnowyLabs @@ -1247,6 +1274,9 @@ - type: Tag id: TrashBag +- type: Tag + id: Truncheon + - type: Tag id: Unimplantable @@ -1286,6 +1316,9 @@ - type: Tag id: WeaponShotgunKammerer +- type: Tag + id: WeldingMask + - type: Tag id: WetFloorSign @@ -1295,6 +1328,9 @@ - type: Tag id: WhitelistChameleon +- type: Tag + id: WhoopieCushion + - type: Tag id: Window @@ -1327,5 +1363,3 @@ - type: Tag id: WriteIgnoreStamps - -# PUT YOUR TAGS IN ALPHABETICAL ORDER diff --git a/Resources/Prototypes/themes.yml b/Resources/Prototypes/themes.yml index edd2681a62..3952687255 100644 --- a/Resources/Prototypes/themes.yml +++ b/Resources/Prototypes/themes.yml @@ -12,6 +12,8 @@ concerningOrangeFore: "#A5762F" dangerousRedFore: "#BB3232" disabledFore: "#5A5A5A" + _itemstatus_content_margin_right: "#06060404" + _itemstatus_content_margin_left: "#04060604" - type: uiTheme id: SS14PlasmafireTheme path: /Textures/Interface/Plasmafire/ @@ -26,6 +28,8 @@ concerningOrangeFore: "#FFF5EE" dangerousRedFore: "#FFF5EE" disabledFore: "#FFF5EE" + _itemstatus_content_margin_right: "#06060404" + _itemstatus_content_margin_left: "#04060604" - type: uiTheme id: SS14SlimecoreTheme path: /Textures/Interface/Slimecore/ @@ -40,6 +44,8 @@ concerningOrangeFore: "#FFF5EE" dangerousRedFore: "#FFF5EE" disabledFore: "#FFF5EE" + _itemstatus_content_margin_right: "#06060404" + _itemstatus_content_margin_left: "#04060604" - type: uiTheme id: SS14ClockworkTheme path: /Textures/Interface/Clockwork/ @@ -54,6 +60,8 @@ concerningOrangeFore: "#FFF5EE" dangerousRedFore: "#FFF5EE" disabledFore: "#FFF5EE" + _itemstatus_content_margin_right: "#06060404" + _itemstatus_content_margin_left: "#04060604" - type: uiTheme id: SS14RetroTheme path: /Textures/Interface/Retro/ @@ -68,6 +76,8 @@ concerningOrangeFore: "#FFF5EE" dangerousRedFore: "#FFF5EE" disabledFore: "#FFF5EE" + _itemstatus_content_margin_right: "#06060404" + _itemstatus_content_margin_left: "#04060604" - type: uiTheme id: SS14MinimalistTheme path: /Textures/Interface/Minimalist/ @@ -82,6 +92,8 @@ concerningOrangeFore: "#A5762F" dangerousRedFore: "#BB3232" disabledFore: "#5A5A5A" + _itemstatus_content_margin_right: "#06060604" + _itemstatus_content_margin_left: "#06060604" - type: uiTheme id: SS14AshenTheme path: /Textures/Interface/Ashen/ @@ -96,3 +108,5 @@ concerningOrangeFore: "#FFF5EE" dangerousRedFore: "#FFF5EE" disabledFore: "#FFF5EE" + _itemstatus_content_margin_right: "#06060604" + _itemstatus_content_margin_left: "#06060604" diff --git a/Resources/Prototypes/tool_qualities.yml b/Resources/Prototypes/tool_qualities.yml index ff55d9fcf1..4508ea94b7 100644 --- a/Resources/Prototypes/tool_qualities.yml +++ b/Resources/Prototypes/tool_qualities.yml @@ -67,3 +67,10 @@ toolName: tool-quality-rolling-tool-name spawn: RollingPin icon: { sprite: Objects/Tools/rolling_pin.rsi, state: icon } + +- type: tool + id: Axing + name: tool-quality-axing-name + toolName: tool-quality-axing-tool-name + spawn: FireAxe + icon: { sprite: Objects/Weapons/Melee/fireaxe.rsi, state: icon } diff --git a/Resources/ServerInfo/AssDayRules.txt b/Resources/ServerInfo/AssDayRules.txt deleted file mode 100644 index 8acd6adedd..0000000000 --- a/Resources/ServerInfo/AssDayRules.txt +++ /dev/null @@ -1,20 +0,0 @@ -[color=#ff0000]You must be 17 or older to play. Users under 17 will be banned immediately.[/color] -[color=#ff0000]Speak English, please.[/color] - -[color=#FFD700] !!! ASS DAY !!! [/color] - -It's ass day! Finally, lower roleplay, something impossible to find on any other server but Delta-V. -Rules are relaxed today and admin abuse is more on the cards. - -[color=#a4885c]1.[/color] [color=#ff0000]DO NOT GRIEF OR OTHERWISE SELF-ANTAG. PLAY AS NORMAL.[/color] -[color=#a4885c]2.[/color] No intentionally crashing the server or causing lag. -[color=#a4885c]3.[/color] No bigotry. -[color=#a4885c]4.[/color] No sexual stuff. -[color=#a4885c]5.[/color] No creepy shit. -[color=#a4885c]6.[/color] No impersonating the admins. -[color=#a4885c]7.[/color] No walling off or obliterating arrivals. -[color=#a4885c]8.[/color] No singuloosing, plasma flooding, maxcapping, or spacing large parts of the station. -[color=#a4885c]9.[/color] If an admin tells you to quit doing something, quit it. -[color=#a4885c]10.[/color] No you do not get an antag token. - -Whiskey Echo Whiskey Lima Alpha Delta. diff --git a/Resources/ServerInfo/Guidebook/Antagonist/Antagonists.xml b/Resources/ServerInfo/Guidebook/Antagonist/Antagonists.xml index a0e434dc77..927a56a505 100644 --- a/Resources/ServerInfo/Guidebook/Antagonist/Antagonists.xml +++ b/Resources/ServerInfo/Guidebook/Antagonist/Antagonists.xml @@ -8,5 +8,6 @@ Antagonists can take many forms, like: - Nuclear operatives, with the goal of infiltrating and destroying the station. - Traitors infiltrating the crew who can assassinate targets and steal high value items. + - Space Ninjas, masters of espionage and sabotage, who are equipped with special gear. - Several non-humanoid creatures, who usually just try to bring down as many crewmembers as they can. diff --git a/Resources/ServerInfo/Guidebook/Antagonist/Revolutionaries.xml b/Resources/ServerInfo/Guidebook/Antagonist/Revolutionaries.xml index 8ee9ec090e..e69220fc2a 100644 --- a/Resources/ServerInfo/Guidebook/Antagonist/Revolutionaries.xml +++ b/Resources/ServerInfo/Guidebook/Antagonist/Revolutionaries.xml @@ -10,7 +10,7 @@ - [color=#5e9cff]Head Revolutionaries[/color] are chosen at the start of the shift and are tasked with taking over the station by killing or exiling all of the Command staff. Head Revolutionaries will be given a [color=#a4885c]Flash[/color] and a pair of [color=#a4885c]Sunglasses[/color] to aid them. + [color=#5e9cff]Head Revolutionaries[/color] are chosen at the start of the shift and are tasked with taking over the station by killing, exiling or cuffing all of the Command staff. Head Revolutionaries will be given a [color=#a4885c]Flash[/color] and a pair of [color=#a4885c]Sunglasses[/color] to aid them. ## Conversion @@ -41,7 +41,7 @@ ## Objectives - You must eliminate or exile all of the following Command staff on station in no particular order. + You must eliminate, exile or arrest all of the following Command staff on station in no particular order. - Captain - Head of Personnel - Chief Engineer diff --git a/Resources/ServerInfo/Guidebook/Antagonist/SpaceNinja.xml b/Resources/ServerInfo/Guidebook/Antagonist/SpaceNinja.xml index 7fed84da73..f088b9f27b 100644 --- a/Resources/ServerInfo/Guidebook/Antagonist/SpaceNinja.xml +++ b/Resources/ServerInfo/Guidebook/Antagonist/SpaceNinja.xml @@ -1,74 +1,80 @@ -# Space Ninja + # Space Ninja -The Space Ninja is a ghost role randomly available mid-late game. If you pick it you will be given your gear, your objectives and the greeting. + The [color=#66FF00]Space Ninja[/color] is a ghost role randomly available mid-late game. If you pick it you will be given your gear, your objectives and the greeting. -You are a ninja, but in space. The Spider Clan has sent you to the station to wreak all kinds of havoc, from bolting the armory open and killing the entire station to engaging in a slipping war with the clown and fighting in rage cages. + You are a ninja, but in space. [color=#66FF00]The Spider Clan[/color] has sent you to the station to wreak all kinds of havoc, and you are equipped to keep it silent-but-deadly. -# Equipment + Whether you mercilessly pick off the station's crew one by one, or assassinate the clown over and over, your discipline has taught you that [color=#66FF00]your objectives must be at least attempted[/color]. For honor! -You start with a microbomb implant, so if you get KIA or seppuku you will leave behind a nice crater and all your precious equipment is kept out of enemy hands. + # Equipment -Your bag is full of tools for more subtle sabotage, along with a survival box if you need a snack. + You begin implanted with a [color=#a4885c]death acidifier[/color], so if you are KIA or decide to commit seppuku you will leave behind one final gift to the janitor and all your precious equipment is kept out of enemy hands. -You have a jetpack and pinpointer that will let you find the station. + Your bag is full of tools for more subtle sabotage, along with a survival box if you need a snack. - + You have a [color=#a4885c]jetpack[/color] and [color=#a4885c]pinpointer[/color] that will let you find the station. -## Ninja Suit + - + ## Ninja Suit -Your single most important item is your suit, without it none of your abilities would work. -Your suit requires power to function, its internal battery can be replaced by clicking on it **with a better one**. -You can see the current charge by examining the suit or in a sweet battery alert at the top right of your screen. + -If you run out of power and need to recharge your battery, just use your gloves to drain an APC, substation or a SMES. + Your single most important item is [color=#66FF00]your suit[/color], without it none of your abilities would work. + Your suit requires power to function. Its [color=#a4885c]internal battery[/color] can be replaced by clicking on it with another one, and [color=#a4885c]higher capacity batteries[/color] mean a [color=#a4885c]highly effective ninja[/color]. + You can see the current charge by examining the suit or in a sweet battery alert at the top right of your screen. -## Ninja Gloves + If you run out of power and need to recharge your battery, just use your gloves to drain an APC, substation or a SMES. - + ## Ninja Gloves -These bad boys are your bread and butter. + -They are insulated so you can nom on wires in peace. Obviously they block your fingerprints from being left on things you touch. + [color=#66FF00]These bad boys are your bread and butter.[/color] -You have an action to toggle gloves. When the gloves are turned on, they allow you to use special abilities, which are triggered by interacting with things with an empty hand and with combat mode disabled. + They are made from [color=#a4885c]insulated nanomachines[/color] to assist you in gracefully breaking and entering into your destination without leaving behind fingerprints. -Your glove abilities include: -- Emagging an unlimited number of doors. -- Draining power from transformers such as APCs, substations or SMESes. The higher the voltage, the more efficient the draining is. -- You can shock any mob, stunning and slightly damaging them. -- You can download technologies from a R&D server for one of your objectives. -- You can hack a communications console to call in a threat. + You have an action to toggle gloves. When the gloves are turned on, they allow you to use [color=#a4885c]special abilities[/color], which are triggered by interacting with things with an empty hand and with combat mode disabled. -## Energy Katana + Your glove abilities include: + - Emagging an unlimited number of doors. + - Draining power from transformers such as APCs, substations or SMESes. The higher the voltage, the more efficient the draining is. + - You can shock any mob, stunning and slightly damaging them. + - You can download technologies from a R&D server for one of your objectives. + - You can hack a communications console to call in a threat. - + ## Energy Katana -Deals a lot of damage and can be recalled at will, costing suit power proportional to the distance teleported. -While in hand you can teleport to anywhere that you can see, meaning most doors and windows, but not past solid walls. -This has a limited number of charges which regenerate slowly, so keep a charge or two spare incase you need a quick getaway. + -## Spider Clan Charge + You have sworn yourself to the [color=#66FF00]sword[/color] and refuse to use firearms. + Deals a lot of damage and can be recalled at will, costing suit power proportional to the distance teleported. + + While in hand you can [color=#a4885c]teleport[/color] to anywhere that you can see, meaning most doors and windows, but not past solid walls. + This has a limited number of charges which regenerate slowly, so keep a charge or two spare incase you need a quick getaway. - + ## Spider Clan Charge -A modified C-4 explosive, you start with this in your pocket. Creates a large explosion but must be armed in your target area. -A random area on the map is selected for you to blow up, which is one of your objectives. It can't be activated manually, simply plant it on a wall or something. -Can't be unstuck once planted. + -## Ninja Shoes + [color=#66FF00]A modified C-4 explosive[/color], you start with this in your pocket. Creates a large explosion but must be armed in your target area. + A random area on the map is selected for you to blow up, which is one of your [color=#a4885c]objectives[/color]. -Special noslips that make you go really fast. -Energy not required. + It can't be activated manually, simply plant it on a wall or a particularly ugly piece of furniture. + Can't be unstuck once planted. -# Objectives + ## Ninja Shoes -- Download X research nodes: Use your gloves on an R&D server with a number of unlocked technologies -- Doorjack X doors on the station: Use your gloves to emag a number of doors. -- Detonate the spider clan charge: Plant your spider clan charge at a random location and watch it go boom. -- Call in a threat: Use your gloves on a communications console. -- Survive: Don't die. + Special [color=#a4885c]noslips[/color] that keep you agile, swift and upright. + Energy not required. + + # Objectives + + - Download X research nodes: Use your gloves on an R&D server with a number of unlocked technologies + - Doorjack X doors on the station: Use your gloves to emag a number of doors. + - Detonate the spider clan charge: Plant your spider clan charge at a random location and watch it go boom. + - Call in a threat: Use your gloves on a communications console. + - Survive: Don't die. diff --git a/Resources/ServerInfo/Guidebook/DeltaV/Epistemics/GlimmerCreatures.xml b/Resources/ServerInfo/Guidebook/DeltaV/Epistemics/GlimmerCreatures.xml new file mode 100644 index 0000000000..3d2f07a2fe --- /dev/null +++ b/Resources/ServerInfo/Guidebook/DeltaV/Epistemics/GlimmerCreatures.xml @@ -0,0 +1,87 @@ + +# Glimmer Creatures +As glimmer rises higher and higher, there can be occasional disturbances in the [color=#a4885c]noösphere[/color]. +When new psionic signatures are detected Central Command will make an announcement to the station. +Sometimes these signatures can be the appearance of glimmer creatures! + +Glimmer creatures can appear in numbers as low as 1, or multiple depending on how high glimmer is and how many psionics there are. + +These are the entities currently known to NanoTrasen. +Research is ongoing, more are yet to be discovered. + +# Glimmer Mite + + + +The glimmer mite is a small insect-like being that naturally provokes the noösphere. + +Minimum glimmer rating: Low + +## Assessment +As long as it is physically intact it will slowly raise glimmer. + +They show no signs of aggression and are physically harmless. + +## Procedure +Kill them as soon as they're spotted, they affect the noösphere and the brooding sound drives crew insane! + +## Analysis +You can get liquid [color=#a4885c]Ectoplasm[/color] from blending its body, so they can be a decent source of [color=#a4885c]Normality Crystals[/color]. + +In an emergency the body can be beaten to a pulp which will make it cease raising glimmer, at the cost of not yielding ectoplasm. + +Overall, a mild help to stopping glimmer, if you can find them. + +# Glimmer Wisp + + + +These wisps are physical manifestations of glimmer that are ghostly in appearance. + +Minimum glimmer rating: High + +## Assessment + +They don't directly affect the noösphere themselves, the main threat is that they [color=#a4885c]hunt down[/color] any psionics! + +Once a psionic is critically injured, they will begin to drain the life out of the body to [color=#a4885c]heal all injuries[/color]. + +## Procedure +Most physical attacks pass through them unnoticed, but holy weapons like the Chaplain's [color=#a4885c]Bible[/color] will weaken it. + +Once attacked they will retaliate for some time, so be prepared for a fight! + +They can also be dispelled by any gifted psionic. + +## Analysis +If you manage to kill a wisp, it will leave behind a large amount of ectoplasm which can easily make a drain or two. + +Extremely useful to a trained Epistemics team, but will require coordination to take it down. + +# Revenant + + + +A tortured soul taking revenge on those who wronged them, brought to this plane by the noösphere. + +Minimum glimmer rating: Dangerous + +## Assessment + +They have no effect on the noösphere but are a serious threat to the station, feeding on the souls of dead crew. + +Unlike wisps, revenants cannot be attacked in any way by default. + +Once a revenant uses their abilities, they can be attacked by any means for a short time. + +## Procedure +Holy damage from a bible or anti-psionic knife is especially effective at taking down wisps. + +Assistance from the Security department is usually recommended. + +Spare no time in putting down a revenant. + +## Analysis +Mostly annoying to the crew, limited exploitation due to low amount of ectoplasm when killed. + + diff --git a/Resources/ServerInfo/Guidebook/Engineering/AME.xml b/Resources/ServerInfo/Guidebook/Engineering/AME.xml index ae0217c4b0..0a96907203 100644 --- a/Resources/ServerInfo/Guidebook/Engineering/AME.xml +++ b/Resources/ServerInfo/Guidebook/Engineering/AME.xml @@ -1,26 +1,25 @@ -# Antimatter Engine (AME) + # Antimatter Engine (AME) -The AME is one of the simplest engines available. You put together the multi-tile structure, stick some fuel into it, and you're all set. This doesn't mean it isn't potentially dangerous with overheating though. + The AME is one of the simplest engines available. You put together the multi-tile structure, stick some fuel into it, and you're all set. This doesn't mean it is perfectly safe though; you may need to deal with the AME overheating. -## Construction -Required parts: - - - - - + ## Construction + Required parts: + + + + + -To assemble an AME, start by wrenching down the controller on the far end of a HV wire. On most stations, there's catwalks to assist with this. From there, start putting down a 3x3 or larger square of AME parts in preparation for construction, making sure to maximize the number of "center" pieces that are surrounded on all 8 sides. + To assemble an AME, start by wrenching down the controller on the near end of a HV wire. On most stations, there's catwalks to assist with this. From there, start putting down a 3x3 or larger square of AME parts in preparation for construction, making sure to maximize the number of "center" pieces that are surrounded on all 8 sides. -Once this is done, you can use a multitool to convert each AME part into shielding, which should form a finished AME configuration. From there, insert a fuel jar, set the fuel rate to [color=#a4885c]twice the core count or less[/color], and turn on injection. + Once this is done, you can use a multitool to convert each AME part into shielding, which should form a finished AME configuration. From there, insert a fuel jar, set the fuel rate to [color=#a4885c]twice the core count or less[/color], and turn on injection. Any more than this ratio will eventually result in the engine [color=#ff0000]overheating and[/color], shortly afterwards, [color=#ff0000]exploding[/color]. -## Fuel Economy -The closer you are to the perfect ratio of [color=#a4885c]1:2[/color] (1 AME core to 2 fuel rate) the more efficient you'll be. You're cutting fuel efficiency to [color=#a4885c]50% and less[/color] if you're using more cores, but less fuel injection rate. -For an example [color=#76db91]3 core and 6 fuel rate[/color] will generate [color=#76db91]240kW[/color], while [color=#f0684d]8 core 8 fuel rate[/color] will generate [color=#f0684d]160kW[/color]. Generating 80kW less while spending 2 more fuel each injection. + ## Fuel Economy + The closer you are to the perfect ratio of [color=#a4885c]1:2[/color] AME cores to fuel injection rate, the more efficient you'll be. You're cutting fuel efficiency to [color=#a4885c]50% and less[/color] if you're using more cores, but a lower fuel injection rate. + For an example, [color=#76db91]3 cores and 6 fuel injected[/color] will generate [color=#76db91]240kW[/color], while [color=#f0684d]8 cores and 8 fuel injected[/color] will generate [color=#f0684d]160kW[/color]; you'd be generating 80kW less while spending 2 more fuel per injection. -## Upgrading the AME - -You can generally only upgrade the AME by getting more cores, which can be done by ordering more AME packages from [color=#a4885c]logistics[/color]. + ## Upgrading the AME + You can generally only upgrade the AME by installing more cores, which can be done by ordering more AME flatpacks from [color=#a4885c]Logistics[/color]. diff --git a/Resources/ServerInfo/Guidebook/Engineering/AccessConfigurator.xml b/Resources/ServerInfo/Guidebook/Engineering/AccessConfigurator.xml index e54f45345d..490cbf09c3 100644 --- a/Resources/ServerInfo/Guidebook/Engineering/AccessConfigurator.xml +++ b/Resources/ServerInfo/Guidebook/Engineering/AccessConfigurator.xml @@ -1,32 +1,34 @@ -# Access Configurator -The access configurator is a tool used to specify what type of personnel may use certain devices. + # Access Configurator + The access configurator is a tool used to specify what type of personnel may use certain devices. - - - + + + -Configurable devices can include airlocks, secure crates and lockers, as well as access restricted machines. + Configurable devices can include airlocks, secure crates and lockers, as well as access restricted machines. + Note: Airlocks can have their accesses configured by the [color=#a4885c]Network Configurator[/color] (or multitool), for convenience. -## Where to find access configurators -Each station is equipped with up to two access configurators. The first is in the possession of the Chief Engineer, while the second can be found with the Head of Personnel. + ## Where to find Access Configurators + Each station is equipped with up to two access configurators. The first is in the possession of the Chief Engineer, while the second can be found with the Head of Personnel. -## How to use the access configurator -To modify a device using the access configurator -- First, use the access configurator on the chosen device to link them together. This will automatically open the configurator UI. -- Next, insert an ID card into the access configurator. -- Set the access requirements of the connected device. What requirements can be added or removed will depend upon the access privileges of the inserted ID card. -- Any changes made will be applied immediately - simply eject the ID card from the access configurator and close the UI when you are done. + ## How to use the access configurator + To modify a device using the access configurator: + - First, use the access configurator on the chosen device to link them together. This will automatically open the configurator UI. + - Next, insert an ID card into the access configurator. + - Set the access requirements of the connected device. What requirements can be added or removed will depend upon the access privileges of the inserted ID card. + - Any changes made will be applied [color=#a4885c]immediately[/color] - simply eject the ID card from the access configurator and close the UI when you are done. -## Restrictions on changing access -As a safety precaution, the inserted ID must possess *all* of the access requirements that are currently active on the connected device in order to modify it. + ## Restrictions on changing access + As a safety precaution, the inserted ID must possess [bold]all[/bold] of the access requirements that are currently active on the connected device in order to modify it. -For example, a device which can be access by both 'Epistemics' and 'Medical' personnel can only by modified using an ID card that has access to both of these departments. The access configurator will warn the user if the inserted ID card does not have sufficient privileges to modify a device. + For example, a device which can be access by both 'Epistemics' and 'Medical' personnel can only by modified using an ID card that has access to [color=#a4885c]both[/color] of these departments. + The access configurator will warn the user if the inserted ID card does not have sufficient privileges to modify a device. -A device with no access requirements set, like a public access airlock, can be modified using any valid station ID card. + A device with no access requirements set, like a public access airlock, can be modified using any valid station ID card. -## Repairing damaged ID card readers -Syndicate agents may attempt to hack access restricted devices through the use of a Cryptographic Sequencer (EMAG). This nefarious tool will completely short out any ID card readers that are attached to the device. + ## Repairing damaged ID card readers + Syndicate agents may attempt to hack access restricted devices through the use of a [color=#a4885c]Cryptographic Sequencer (EMAG)[/color]. This nefarious tool will completely short out any ID card readers that are attached to the device. -Crew members will need to partially de/reconstruct affected devices, and then set appropriate access permissions afterwards using the access configurator, to re-establish access restrictions. + Engineers will need to partially de/reconstruct affected devices, and then set appropriate access permissions afterwards using the access configurator (or network configurator, for airlocks), to re-establish access restrictions. diff --git a/Resources/ServerInfo/Guidebook/Engineering/AirlockSecurity.xml b/Resources/ServerInfo/Guidebook/Engineering/AirlockSecurity.xml index 125833a0a1..8bfd3902cc 100644 --- a/Resources/ServerInfo/Guidebook/Engineering/AirlockSecurity.xml +++ b/Resources/ServerInfo/Guidebook/Engineering/AirlockSecurity.xml @@ -1,76 +1,76 @@ -# Airlock Upgrades -It is not uncommon for plucky individuals to try and bypass an airlock by meddling with its internal wiring. + # Airlock Upgrades + It is not uncommon for plucky individuals to try and bypass an airlock by meddling with its internal wiring. -Fortunately, certain countermeasures can installed into airlocks to inconvenience any would be trespassers. + Fortunately, certain countermeasures can installed into airlocks to inconvenience any would-be trespassers. -## Medium security airlocks -The most basic form of intrusion deterrence is to install internal steel plating that will prevent access to internal wiring of the airlock. + ## Medium security airlocks + The most basic form of intrusion deterrence is to install a secured steel plating that will prevent access to internal wiring of the airlock. -To upgrade a basic airlock to a medium security airlock, you will require the following materials - - - - - - - - - - - + To upgrade a basic airlock to a medium security airlock, you will require the following materials: + + + + + + + + + + + -To upgrade the basic airlock, -- Use the screwdriver to open the airlock maintenance panel. -- Add the steel sheets to the airlock. -- Weld the steel sheets into place. -- Close the maintenance panel using the screwdriver. + To upgrade a basic airlock: + - Use the screwdriver to open the airlock maintenance panel. + - Add the steel sheets to the airlock. + - Weld the steel sheets into place. + - Close the maintenance panel using the screwdriver. -## High security airlocks -For airlocks leading to the more sensitive areas of the space station, the use of stronger deterrents are advised. High security airlocks have improved armor plating to protect its internal wiring, along with an electrified security grille. + ## High security airlocks + For airlocks leading to the more sensitive areas of the space station, the use of stronger deterrents are advised. High security airlocks have improved armor plating to protect its internal wiring, along with an electrified security grille. -To upgrade a basic airlock to a high security airlock, you will require the following materials - - - - - - - - - - - - - - + To upgrade a medium security airlock to a high security airlock, you will require the following materials: + + + + + + + + + + + + + + -To upgrade the basic airlock, -- Use the screwdriver to open the airlock maintenance panel. -- Add the plasteel sheets to the airlock. -- Weld the plasteel sheets into place. -- Add the metal rods to the airlock. -- Close the maintenance panel using the screwdriver. + To upgrade a medium security airlock: + - Use the screwdriver to open the airlock maintenance panel. + - Add the plasteel sheets to the airlock. + - Weld the plasteel sheets into place. + - Add the metal rods to the airlock. + - Close the maintenance panel using the screwdriver. -## Maximum security airlocks -You can optionally upgrade a high security airlock to a maximum security airlock. Maximum security airlocks possess an additional layer of plasteel plating on top of its other protections. + ## Maximum security airlocks + You can optionally upgrade a high security airlock to a maximum security airlock. Maximum security airlocks possess an additional layer of plasteel plating on top of its other protections. -To upgrade a high security airlock to a maximum security airlock, you will require the following materials - - - - - - - - - - - + To upgrade a high security airlock to a maximum security airlock, you will require the following materials: + + + + + + + + + + + -To upgrade the high security airlock, -- Use the screwdriver to open the airlock maintenance panel. -- Add the plasteel sheets to the airlock. -- Weld the plasteel sheets into place. -- Close the maintenance panel using the screwdriver. - \ No newline at end of file + To upgrade a high security airlock: + - Use the screwdriver to open the airlock maintenance panel. + - Add the plasteel sheets to the airlock. + - Weld the plasteel sheets into place. + - Close the maintenance panel using the screwdriver. + diff --git a/Resources/ServerInfo/Guidebook/Engineering/Atmospherics.xml b/Resources/ServerInfo/Guidebook/Engineering/Atmospherics.xml index 693e3a0209..48d0d9415e 100644 --- a/Resources/ServerInfo/Guidebook/Engineering/Atmospherics.xml +++ b/Resources/ServerInfo/Guidebook/Engineering/Atmospherics.xml @@ -1,61 +1,67 @@ - -# Atmospherics - -Atmospherics setups are a necessity for your long-term comfort but are generally undocumented, resulting in them being a bit tricky to set up. The following attempts to cover the basics. - -## Standard Mix -Breathing pure O2 or pure N2 is generally bad for the health of your crew, and it is recommended to instead aim for a mix of [color=#a4885c]78% N2 and 22% O2 at 101.24kPa.[/color] It's recommended that your gas mixer setup be set to output at least 1000kPa for faster re-pressurization of rooms. - - - - - -Variations on this mix may be necessary for the long-term comfort of atypical crew, for example crew who require a plasma gas mix to survive. For atypical crew, it is recommended to try and give them their own personal space, isolated by either airlock or disposals section. Keep in mind both methods are leaky and you will need scrubbers on both sides of the lock to clean up any leaked gasses. - - - - -## Vents and Scrubbers -Vents and scrubbers are core atmospherics devices that fill and cleanse rooms, respectively. By default, they are configured for filling rooms to standard pressure (101.24kPa) and to remove all non-O2/N2 gasses from a room. They can be reconfigured from their default settings, allowing you to configure how they respond to various types of gasses or pressure levels. This can be done by interacting with an existing air alarm nearby, or installing and connecting them to a new one. - - - - - -During standard operation, if a vent detects that the outside environment is space, it will automatically cease operation until a minimum pressure is reached to avoid destruction of necessary gasses. This can be fixed by pressurizing the room up to that minimum pressure by refilling it with gas canister (potentially multiple, if the room is of significant size). - -Should you encounter a situation where scrubbers aren't cleaning a room fast enough, employ portable scrubbers by dragging them to the affected location and wrenching them down. They work much faster than typical scrubbers and can clean up a room quite quickly. Large spills may require you to employ multiple. - - - -# Gas mixes and Burn chambers -In the event you finish all the tasks at hand, you can make some extra power or money by creating new chemical gasses. - -##Tritium -Tritium is a clear, green gas that is highly flammable, radioactive, and combusts when in contact with oxygen making it very helpful when running the [color=#a4885c]TEG.[/color] -It can be made by burning 1% Plasma and 96% or more Oxygen in the Burn Chamber. You can extract this gas through scrubbers. - - - - - - - - -##Frezon -Frezon is a bluish-green gas that is very complex and very dangerous. To obtain frezon, you must mix Tritium, Oxygen, and Nitrogen in a 70K room to start the reaction, as well as prevent the Tritium from combusting with the oxygen. - - - - - - - - -It is critical to understand that a frezon leak can devastate the station, causing a wintery hell filled with itchy sweaters and cold burns. Frezon is very cold, and can freeze the station to death if even a few moles get out, so make sure that you lock your canisters or just move your Frezon straight into a storage room. - -## Reference Sheet -- Standard atmospheric mix is [color=#a4885c]78% N2 and 22% O2 at 101.24kPa.[/color] -- Gas obeys real math. You can use the equation PV = nRT (Pressure kPa * Volume L = Moles * R * Temperature K) to derive information you might need to know about a gas. R is approximately 8.31446 + + # Atmospherics + + Atmospherics setups are a necessity for your long-term comfort, but are generally underdocumented, resulting in them being a bit tricky to set up. The following attempts to cover the basics. + + ## Standard Mix + Breathing pure O2 or pure N2 is generally bad for the health of your crew, and it is recommended to instead aim for a mix of [color=#a4885c]78% N2 and 22% O2 at 101.24kPa.[/color] It's recommended that your gas mixer setup be set to output at least 300kPA for faster re-pressurization of rooms, without posing too much of an overpressurization risk, should traitors sabotage the distro. + + + + + + Variations on this mix may be necessary for the long-term comfort of atypical crew, (for example, Voxes, who are poisoned by Oxygen and breathe Nitrogen). For atypical crew (to be implemented), it is recommended to try and give them their own personal space, isolated by either an airlock or disposals section. Keep in mind that both methods are leaky and you will need scrubbers on both sides of the lock to clean up any leaked gasses. + + + + + ## Vents and Scrubbers + Vents and scrubbers are core atmospherics devices that fill and cleanse rooms, respectively. By default, they are configured for filling rooms to standard pressure (101.24kPa) and to remove all non-O2/N2 gasses from a room. They can be reconfigured from their default settings by linking them to an Air Alarm, allowing you to configure how they respond to various types of gasses or pressure levels. + + + + + + During standard operation, if a normal vent detects that the outside environment is space, it will automatically cease operation until a minimum pressure is reached to avoid destruction of useful gasses. This can be fixed by pressurizing the room up to that minimum pressure by refilling it with gas canister (potentially multiple, if the room is of significant size). + + Should you encounter a situation where scrubbers aren't cleaning a room fast enough (and the "Siphon" functionality still cannot keep up), employ portable scrubbers by dragging them to the affected location and wrenching them down. They work much faster than typical scrubbers and can clean up a room quite quickly. Large spills may require you to employ multiple. + + + + # Gas mixes and Burn chambers + In the event you finish all the tasks at hand, you can make some extra money by creating new chemical gasses. + + ##Tritium + Tritium is a clear, green gas that is highly flammable, radioactive, and combusts when in contact with oxygen, making it very helpful when running the [color=#a4885c]TEG[/color]. + It can be made by burning 1% Plasma and 96% or more Oxygen in the Burn Chamber (Ideal ratio is 3% Plasma to 97% Oxygen). You can extract this gas through scrubbers. + + + + + + + + + ##Frezon + Frezon is a bluish-green gas that is very complex and very dangerous. To obtain frezon, you must mix Tritium, Oxygen, and Nitrogen in a 70K room to start the reaction, and prevent the Tritium from combusting with the oxygen. + + + + + + + + + It is critical to understand that a frezon leak can devastate the station, causing a wintery hell filled with itchy sweaters and cold burns. Frezon is very cold, and can freeze the station to death if even a few moles get out, so make sure that you lock your canisters or just move your Frezon straight into a storage room. + + ## Reference Sheet + - Standard atmospheric mix is [color=#a4885c]78% N2 and 22% O2 at 101.24kPa.[/color] + - Gas obeys real math. You can use the equation: + + [color=cyan]PV = nRT[/color] + + + ([color=#a4885c]Pressure kPa * Volume L = Moles * R * Temperature K[/color]) + to derive information you might need to know about a gas. R is approximately 8.31446. diff --git a/Resources/ServerInfo/Guidebook/Engineering/Construction.xml b/Resources/ServerInfo/Guidebook/Engineering/Construction.xml index 832b831d8e..15f2f15539 100644 --- a/Resources/ServerInfo/Guidebook/Engineering/Construction.xml +++ b/Resources/ServerInfo/Guidebook/Engineering/Construction.xml @@ -1,11 +1,11 @@ - -# Construction + + # Construction -By pressing [color=#a4885c]G[/color], one can open the construction menu, which allows you to craft and build a variety of objects. + By pressing [color=#a4885c][keybind="OpenCraftingMenu"][/color], one can open the construction menu, which allows you to craft and build a variety of objects. -When placing objects that "snap" to the grid, you can hold [color=#a4885c]shift[/color] to place an entire line at a time, and [color=#a4885c]ctrl[/color] to place an entire square at a time. + When placing objects that "snap" to the grid, you can hold [color=#a4885c]Shift[/color] to place an entire line at a time, and [color=#a4885c]Ctrl[/color] to place an entire grid at a time. -When crafting objects with a lot of ingredients, keep in mind you don't have to hold everything at once, you can simply place the ingredients on the floor or on a table near you and they'll be used up during crafting like normal. + When crafting objects with a lot of ingredients, keep in mind you don't have to hold everything at once; you can simply place the ingredients on the floor, in your backpack or on a table near you, and they'll be used up during crafting like normal. -When placing a "building ghost" somewhere in the world press [color=#a4885c]Middle Mouse Button[/color] to rotate the ghost clockwise. + When placing a "building ghost" somewhere in the world, press [color=#a4885c][keybind="EditorRotateObject"][/color] to rotate the ghost clockwise. If you are building a mirrorable component (think: Gas Mixers/Filters), you can press [color=#a4885c][keybind="EditorFlipObject"][/color] to flip the ghost. diff --git a/Resources/ServerInfo/Guidebook/Engineering/Engineering.xml b/Resources/ServerInfo/Guidebook/Engineering/Engineering.xml index 0f53ea3042..ab48ed1d82 100644 --- a/Resources/ServerInfo/Guidebook/Engineering/Engineering.xml +++ b/Resources/ServerInfo/Guidebook/Engineering/Engineering.xml @@ -1,22 +1,21 @@ -# Engineering + # Engineering -Engineering is a combination of construction work, repair work, maintaining a death machine that happens to produce power, and making sure the station contains breathable air. + Engineering is a combination of construction work, repair work, maintaining a death machine that happens to produce power, and making sure the station contains breathable air. -## Tools - - - - - - - - - - - -Your core toolset is a small variety of tools. If you're an engineer, then you should have a belt on your waist containing one of each, if not you can likely find them in maintenance and tool storage within assorted toolboxes and vending machines. - -Most tasks will have explainers for how to perform them on examination, for example if you're constructing a wall, it'll tell you the next step if you look at it a bit closer. + ## Tools + + + + + + + + + + + + Your core toolset is a small variety of tools. If you're an engineer, then you should have a belt on your waist containing one of each; if not, you can likely find them in maintenance shafts and in tool storage within assorted toolboxes and vending machines. + Most tasks will have explainers for how to perform them on examination; for example, if you're constructing a wall, it'll tell you the next step if you look at it a bit closer. diff --git a/Resources/ServerInfo/Guidebook/Engineering/Fires.xml b/Resources/ServerInfo/Guidebook/Engineering/Fires.xml index a1c54059b7..e2c83956cc 100644 --- a/Resources/ServerInfo/Guidebook/Engineering/Fires.xml +++ b/Resources/ServerInfo/Guidebook/Engineering/Fires.xml @@ -1,15 +1,15 @@ - -# Fires & Space + + # Fires & Space -Fires and spacings are an inevitability due to the highly flammable plasma gas and endless vacuum of space present in and around the station, so it's important to know how to manage them. + Fires and spacings are an inevitability due to the highly flammable plasma gas and the endless vacuum of space present in and around the station, so it's important to know how to manage them. -## Spacing -Space is arguably the easier of the two to handle. -While it does render an area uninhabitable, it can be trivially solved by simply sealing the hole that resulted in the vacuum. After that, assuming distro vents and pipes have not been destroyed in some unfortunate accident, the room will slowly begin to repressurize. -Be aware, that active spacings will slowly siphon the air out of the station's air reserves. If you find it impossible to fix structural damage due to some other hazard - make sure to limit the airflow to that room. + ## Spacing + Space is arguably the easier of the two to handle. + While it does render an area uninhabitable, it can be trivially solved by simply sealing the hole that resulted in the vacuum. After that, assuming distro vents and pipes have not been destroyed in some unfortunate accident, the room will slowly begin to repressurize. + Be aware; active spacings will slowly siphon the air out of the station's air reserves. If you find it impossible to fix structural damage due to some other hazard, make sure to limit the airflow to that room. (Currently only half-valid due to the Gas Miners infinitely replenishing most of the useful gases) -## Fires -Fires can be dealt with through a multitude of ways, but some of the most effective methods include: - - Spacing the enflamed area if possible. This will destroy all of the gasses in the room, which may be a problem if you're already straining life support. - - Dumping a Frezon canister into the enflamed area. This will ice over the flames and halt any ongoing reaction, provided you use enough Frezon. Additionally does not result in destruction of material, so you can simply scrub the room afterwards. + ## Fires + Fires can be dealt with through a multitude of ways, but some of the most effective methods include: + - Spacing the enflamed area if possible. This will destroy all of the gasses in the room, which may be a problem if you're already straining life support. + - Dumping a Frezon canister into the enflamed area. This will ice over the flames and halt any ongoing reaction, provided you use enough Frezon. Additionally, this does not result in destruction of material, so you can simply scrub the room afterwards. diff --git a/Resources/ServerInfo/Guidebook/Engineering/NetworkConfigurator.xml b/Resources/ServerInfo/Guidebook/Engineering/NetworkConfigurator.xml index 445d182ab8..ab95dd2e29 100644 --- a/Resources/ServerInfo/Guidebook/Engineering/NetworkConfigurator.xml +++ b/Resources/ServerInfo/Guidebook/Engineering/NetworkConfigurator.xml @@ -1,39 +1,40 @@ -# Network Configurator -The network configurator allows you to manipulate device lists and link devices together. - - - -The configurator has two modes: List and Link. You can press [color=gray]Alt+Z[/color] or [color=gray]Alt+Y[/color] to switch between them. + # Network Configurator + The network configurator allows you to manipulate device lists, link devices together and configure accesses for airlocks through door electronics. + + + + The configurator has two modes: List and Link. You can press [color=gray]Alt+Z[/color] or [color=gray]Alt+Y[/color] to switch between them. -## List Mode -In list mode you can click on network devices to save them on the configurator and then on a network device that has a device list like the [color=#a4885c]Air Alarm[/color]. + ## List Mode + In list mode you can click on network devices to save them on the configurator and then on a network device that has a device list like the [color=#a4885c]Air Alarm[/color]. -When clicking on a device like the Air Alarm, a UI will open displaying the list currently saved on the device and buttons to manipulate that list. + When clicking on a device like the Air Alarm, a UI will open displaying the list currently saved on the device and buttons to manipulate that list. -You can: -- Replace the current list with the one saved on the configurator -- Add the list on the configurator to the current one -- Clear the current list -- Copy the current list to the configurator -- Visualize the connections to the devices on the current list + You can: + - Replace the current list with the one saved on the configurator + - Add the list on the configurator to the current one + - Clear the current list + - Copy the current list to the configurator + - Visualize the connections to the devices on the current list -Pressing [color=gray]z[/color] or [color=gray]y[/color] opens the list saved on the configurator where you can remove saved devices. + Pressing [color=gray][keybind="ActivateItemInHand"][/color] opens the list saved on the configurator where you can remove saved devices. -## Link Mode -With link mode you can click on a device that is capable of device linking and click on any other device that is either -a sink or source. + ## Link Mode + With link mode, you can click on a device that is capable of device linking and then click on any other device that is either a sink or source. -For example, first clicking on a source like a [color=#a4885c]signal button[/color] and then on sink like a -[color=#a4885c]small light[/color] opens a UI that displays the source ports on the left side and the sink ports on the right. + For example, first clicking on a source, like a [color=#a4885c]signal button[/color], and then on sink, like a [color=#a4885c]small light[/color], opens a UI that displays the source ports on the left side and the sink ports on the right. -Now you can eiter click [color=gray]link defaults[/color] to link the default ports for a source + sink combination or press on a source and then a sink port to connect them. + Now, you can either click [color=gray]Link Defaults[/color] to link the default ports for a source + sink combination, or press on a source port and then a sink port to connect them. -An example of a default link for the aformentioned combinaton of devices would be: - - [color=cyan]Pressed 🠒 Toggle[/color] - -When you're done connecting the ports you want you can click on [color=gray]ok[/color] to close the UI. + An example of a default link for the aformentioned combinaton of devices would be: + + [color=cyan]Pressed 🠒 Toggle[/color] + + When you're done connecting the ports you want you can click on [color=gray]OK[/color] to close the UI. -You can quickly link multiple devices to their default port by first clicking on a device that can be linked and then using [color=gray]alt+left mouse button[/color] on the devices you want to link together. + You can quickly link multiple devices to their default port by first clicking on a device that can be linked and then using [color=gray]Alt+Left Mouse button[/color] on the devices you want to link together. + + ## Airlock Access + To configure an airlock's access, simply take the airlock's door electronics and interact with it using a network configurator (or multitool). Select the accesses you want, insert the door electronics into an airlock frame, and construct to finish! diff --git a/Resources/ServerInfo/Guidebook/Engineering/Networking.xml b/Resources/ServerInfo/Guidebook/Engineering/Networking.xml index 03576c789a..90d1f0891b 100644 --- a/Resources/ServerInfo/Guidebook/Engineering/Networking.xml +++ b/Resources/ServerInfo/Guidebook/Engineering/Networking.xml @@ -1,25 +1,24 @@ -# Networking -Some devices on the station need to communicate with each other, and they do this by utilizing device networking. -With networking machines and devices can send arbitrary data between each other. -There are multiple networks that get used, such as the wireless and wired network. -Each network device has a frequency it receives on. PDAs for example, use the frequency: [color=green]220.2[/color] + # Networking + Some devices on the station need to communicate with each other, and they do this by utilizing device networking. + With networking, machines and devices can send arbitrary data between each other. + There are multiple networks that get used, such as the wireless and wired network. + Each network device has a frequency it receives on. PDAs for example, use the frequency: [color=green]220.2[/color] -## Device Lists -Some devices need to know what other devices to communicate with specifically. - - - -Air alarms for example require you to tell it which vents, scrubbers, sensors, and firelocks to interact with. -You do that by using the Network Configurator. + Note: The following operations will require use of the Network Configurator to be performed: -## Linking -If devices basic or still more advanced but need finer control of how and what connects to each other they will generally use device linking. + ## Device Lists + Some devices need to know which other devices to communicate with specifically. - - + -With linking you can connect the outputs of a device like [color=gray]On[/color] or [color=gray]Off[/color] with the inputs of a device like the airlocks -[color=gray]Open[/color] or [color=gray]Close[/color] inputs. -The Network Configurator is also used for linking devices together. + Air alarms, for example, require you to tell it which vents, scrubbers, sensors, and firelocks to interact with. + + ## Linking + If a device, basic or advanced, needs finer controls of how and which devices it connects to, it will generally use device linking. + + + + + With linking, you can connect the outputs of a device, like [color=gray]On[/color] or [color=gray]Off[/color], with the inputs of a device, like the airlocks [color=gray]Open[/color] or [color=gray]Close[/color] inputs. diff --git a/Resources/ServerInfo/Guidebook/Engineering/PortableGenerator.xml b/Resources/ServerInfo/Guidebook/Engineering/PortableGenerator.xml index 2cf1fa44ea..b946bf041c 100644 --- a/Resources/ServerInfo/Guidebook/Engineering/PortableGenerator.xml +++ b/Resources/ServerInfo/Guidebook/Engineering/PortableGenerator.xml @@ -1,7 +1,7 @@ - + # Portable Generators - Need power? No engine running? The "P.A.C.M.A.N." line of portable generators has you covered. + Need power? No engines running? The "P.A.C.M.A.N." line of portable generators has you covered. @@ -16,10 +16,10 @@ - The J.R.P.A.C.M.A.N. can be found across the station in maintenance areas, and is ideal for crew to set up themselves whenever there are power issues. + The J.R.P.A.C.M.A.N. can be found across the station in maintenance shafts, and is ideal for crew to set up themselves whenever there are power issues. Setup is incredibly easy: wrench it down above an [color=green]LV[/color] power cable, give it some welding fuel, and start it up. - Welding fuel should be plentiful to find around the station. In a pinch, you can even transfer some from the big tanks with a soda can. Just remember to empty the soda can first, I don't think it likes soda as fuel. + Welding fuel should be plentiful to find around the station. In a pinch, you can even transfer some from the big tanks with a soda can or water bottle. Just remember to empty the soda can first, I don't think it likes soda as fuel. # The Big Ones @@ -33,10 +33,11 @@ - The (S.U.P.E.R.)P.A.C.M.A.N. is intended for usage by engineering for advanced power scenarios. Bootstrapping the engine, powering departments, and so on. + The (S.U.P.E.R.)P.A.C.M.A.N. is intended for usage by engineering for advanced power scenarios. Bootstrapping larger engines, powering departments, and so on. - The S.U.P.E.R.P.A.C.M.A.N. boasts larger power output and longer runtime at maximum output, but scales down to lower outputs less efficiently. + The S.U.P.E.R.P.A.C.M.A.N. boasts a larger power output and longer runtime at maximum output, but scales down to lower outputs less efficiently. - They connect directly to [color=yellow]MV[/color] or [color=orange]HV[/color] power cables, able to switch between them for flexibility. + They connect directly to [color=yellow]MV[/color] or [color=orange]HV[/color] power cables, and are able to switch between them for flexibility. + The S.U.P.E.R.P.A.C.M.A.N and P.A.C.M.A.N require uranium sheets and plasma sheets as fuel, respectively. diff --git a/Resources/ServerInfo/Guidebook/Engineering/Power.xml b/Resources/ServerInfo/Guidebook/Engineering/Power.xml index 62b38e397d..7dd227ee9b 100644 --- a/Resources/ServerInfo/Guidebook/Engineering/Power.xml +++ b/Resources/ServerInfo/Guidebook/Engineering/Power.xml @@ -1,53 +1,53 @@ -# Power - -SS14 has a fairly in-depth power system through which all devices on the station receive electricity. It's divided into three main powernets; HV, LV, and MV. - - - - - - -## Cabling -The three major cable types (HV, MV, and LV) can be used to form independent powernets. Examine them for a description of their uses. - - - - - - -## Power storage -Each power storage device presented functions as the transformer for its respective power level (HV, MV, and LV) and also provides a fairly sizable backup battery to help flatten out spikes and dips in power usage. - - - - - - -## Ramping -Contrary to what one might expect from a video game electrical simulation, power is not instantly provided upon request. Generators and batteries take time to ramp up to match the draw imposed on them, which leads to brownouts when there are large changes in current draw all at once, for example when batteries run out. - -## Installing power storage -Substations are the most self-explanatory. Simply install the machine on top of an MV and HV cable, it will draw power from the HV cable to provide to MV. - -Installing APCs is similarly simple, except APCs are exclusively wallmounted machinery and cannot be installed on the floor. Make sure it has both MV and LV connections. - -Installing a SMES requires you construct a cable terminal to use as the input. The SMES will draw power from the terminal and send power out from underneath. The terminal will ensure that the HV input and HV output do not connect. Avoid connecting a SMES to itself, this will result in a short circuit which can result in power flickering or outages depending on severity. - -## APC breaking -Currently the only power storage device that has a limit to its power network is APC. As soon as all connected devices and machinery demand more than [color=#a4885c]24kW[/color] it's breaker will pop and everything will turn off. - - - - -## Checking power grid -1. Use the [color=#a4885c]t-ray scanner[/color] in order to locate cables that are hidden under tiles. (skip this step if cables aren't hidden) -2. Pry open the tile that is blocking your access to the cable with a [color=#a4885c]crowbar[/color]. (skip this step if cables aren't hidden) -3. Equip your trusty [color=#a4885c]Multitool[/color] and click on any cable to see powergrid stats. - - - - - + # Power + + SS14 has a fairly in-depth power system through which all devices on the station receive electricity. It's divided into three main powernets; High Voltage, Medium Voltage, and Low Voltage. + + + + + + + ## Cabling + The three major cable types (HV, MV, and LV) can be used to form independent powernets. Examine them for a description of their uses. + + + + + + + ## Power storage + Each power storage device presented functions as the transformer for its respective power level (HV, MV, and LV), and also provides a fairly sizable backup battery to help flatten out spikes and dips in power usage. + + + + + + + ## Ramping + Contrary to what one might expect from a video game electrical simulation, power is not instantly provided upon request. Generators and batteries take time to ramp up to match the draw imposed on them, which leads to brownouts when there are large changes in current draw all at once; for example, when batteries run out. + + ## Installing power storage + Substations are the most self-explanatory. Simply install the machine on top of an MV and HV cable; it will draw power from the HV cable to provide to MV. + + Installing APCs is similarly simple, except APCs are exclusively wallmounted machinery and cannot be installed on the floor. Make sure it has both MV and LV connections. + + Installing a SMES requires you construct a cable terminal to use as the input. The SMES will draw power from the terminal and send power out from underneath. The terminal will ensure that the HV input and HV output do not connect. Avoid connecting a SMES to itself; this will result in a short circuit, which can result in power flickering or outages depending on severity. + + ## APC breaking + Currently the only power storage device that has a limit to its power to the network is the APC. As soon as all connected devices and machinery demand more than [color=#a4885c]24kW[/color] of power, its breaker will pop and everything will turn off. In the case that you are not an engineer, call an engineer (or cyborg) to re-enable it, after reducing the load back down to [color=#a4885c]below[/color] 24kW. + + + + + ## Checking the power grid + 1. Use the [color=#a4885c]t-ray scanner[/color] in order to locate cables that are hidden under tiles. (skip this step if cables aren't hidden) + 2. Pry open the tile that is blocking your access to the cable with a [color=#a4885c]crowbar[/color]. (skip this step if cables aren't hidden) + 3. Equip your trusty [color=#a4885c]Multitool[/color] and click on any cable to see the power-grid stats. + + + + + diff --git a/Resources/ServerInfo/Guidebook/Engineering/RTG.xml b/Resources/ServerInfo/Guidebook/Engineering/RTG.xml index 1d71ee9144..6149b58049 100644 --- a/Resources/ServerInfo/Guidebook/Engineering/RTG.xml +++ b/Resources/ServerInfo/Guidebook/Engineering/RTG.xml @@ -1,17 +1,20 @@ -# Radioisotope Thermoelectric Generator (RTG) + # Radioisotope Thermoelectric Generator (RTG) - - - - + + + -Making power using a Radioisotope Thermoelectric Generator (RTG) is similar to making power using solar. -RTGs only provide 10 kW of power, but they provide it for free and for the entire round. -Basically, if you connect an RTG to your power grid, it'll give you free power. + Making power using a Radioisotope Thermoelectric Generator (RTG) is similar to making power using solars. + RTGs only provide [color=#a4885c]10kW[/color] of power, but they provide it for free and for the entire round. + Basically, if you connect an RTG to your power grid, it'll give you [color=#a4885c]free power[/color]. + However, they're only accessible through salvage finding one on an expedition. Should they bring some in, make sure to thank them! -Sometimes, RTGs are damaged. -Damaged RTGs behave just like regular ones, but they're radioactive. -That means they're more dangerous, but on the bright side, you can put radiation collectors next to them to turn that radiation into more power. - - \ No newline at end of file + + + + Sometimes, RTGs appear damaged. + Damaged RTGs behave just like regular ones, but they're [color=yellow]radioactive[/color]. + That means they're more dangerous, but on the bright side, you can put radiation collectors next to them to turn that radiation into more power. + This is usually more worthwhile, considering the power is still free, so long as you can find a safe spot to put the RTG(s) in. + diff --git a/Resources/ServerInfo/Guidebook/Engineering/Shuttlecraft.xml b/Resources/ServerInfo/Guidebook/Engineering/Shuttlecraft.xml index 7e743ddd68..21956d600c 100644 --- a/Resources/ServerInfo/Guidebook/Engineering/Shuttlecraft.xml +++ b/Resources/ServerInfo/Guidebook/Engineering/Shuttlecraft.xml @@ -1,40 +1,39 @@ -# Shuttle-craft + # Shuttle-craft -Shuttle construction is simple and easy, albeit rather expensive and hard to pull off within an hour. It's a good activity if you have a significant amount of spare time on your hands and want a bit of a challenge. + Shuttle construction is simple and easy, albeit rather expensive and hard to pull off within an hour. It's a good activity if you have a significant amount of spare time on your hands and want a bit of a challenge. -## Getting started -Required parts: - - - - - - - - - - - - - + ## Getting started + Required parts: + + + + + + + + + + + + + -Optional parts: - - - - - - - - - - + Optional parts: + + + + + + + + + + -Head out into space with steel sheets and metal rods in hand, and once you're three or more meters away from the station, click near or under you with the rods in hand. This will place some lattice, which can then be turned into plating with the steel sheets. Expand your lattice platform by clicking just off the edge with rods in hand. + Head out into space with steel sheets and metal rods in hand, and once you're three or more tiles away from the station, click near or under you with the rods in hand. This will place some lattice, which can then be turned into plating with steel sheets or floor tiles. Expand your lattice platform by clicking just off the edge with some rods in hand. -From there, once you have the shape you want, bring out and install thrusters at the edges. They must be pointing outward into space to function and will not fire if there's a tile in the way of the nozzle. Install a gyroscope where convenient, and use your substation and generator to set up power. Construct a wall on top of an MV cable and then install an APC on that to power the devices onboard. - -Finally, install the shuttle computer wherever is convenient and ensure all your thrusters and gyroscopes are receiving power. If they are, congratulations, you should have a functional shuttle! Making it livable and good looking is left as an exercise to the reader. + From there, once you have the shape you want, bring out and install thrusters at the edges. They must be pointing outward into space to function and will not fire if there's a tile in the way of the nozzle. Install a gyroscope where convenient, and use your substation and generator to set up power. Construct a wall on top of an MV cable and then install an APC on that to power the devices onboard. + Finally, install the shuttle computer wherever is convenient and ensure all your thrusters and gyroscopes are receiving power (remember to wire the MV and LV networks!). If they are; congratulations, you should have a functional shuttle! Making it livable and good looking is left as an exercise to the reader. diff --git a/Resources/ServerInfo/Guidebook/Engineering/Singularity.xml b/Resources/ServerInfo/Guidebook/Engineering/Singularity.xml index 3553d43e6f..3c0dd665e2 100644 --- a/Resources/ServerInfo/Guidebook/Engineering/Singularity.xml +++ b/Resources/ServerInfo/Guidebook/Engineering/Singularity.xml @@ -1,109 +1,141 @@ -# Gravitational Singularity Engine - -The Gravitational Singularity Engine can yield infinite power, with no fueling required. It can also destroy the whole station with equal ease. It uses a Particle Accelerator to fire high energy particles at a Singularity Generator to form a singularity. The singularity then pulses radiation which is absorbed by Radiation Collectors. - -## Setting it up - -The Gravitational Singularity Engine requires 4 subsystems to work properly: - -## Gravitational singularity generator - - - -The generator should be anchored at the center of the containment area since this is where the singularity will appear at. - -## Containment field generators and emitters - - - - - -The emitters connect to MV cables and fire lasers as long as they have power and are turned on. -Fire emitters at containment field generators to activate them. -If two containment field generators are active, in range and in the same cardinal axis, a containment field will appear. -The containment field will repell the singularity, keeping it from escaping, and yield a little bit of power every time anything bounces off of them. -Emitter lasers and containment field can cause damage, avoid touching them when active. - -## Radiation collectors - - - - -They connect to HV cables and generate power from nearby radiation sources when turned on. -Radiation collectors require a tank full of gaseous plasma in order to operate. -Continous radiation exposure will gradually consume the stored plasma, so replace depleted tanks with fresh ones to maintain a high power output. - -## Particle accelerator - - - - - - - - - - - - - - - - - - - - - -The Particle Accelerator (PA) is a multi-tile structure that launches accelerated particles from its emitters. Its emitters should always face the gravitational singularity generator. -Some stations already have an unfinished PA. To complete, first ensure there is MV cable beneath the PA power box, anchor all parts, then add LV cable to each part. - - - -Then use a screwdriver to screw back the panels. -Scan parts using the PA control computer to check if it's operational. If it shows up as incomplete, examine for what's missing. - - - - -## Turing on the Gravitational Singularity Engine - -[color=#a4885c]Do not[/color] turn the PA on unless all other subsystems are working properly. - -Turn power on using the PA control computer. Set strength to an appropiate level. Currently the only appropriate level is [color=#f0684d]1[/color], anything above that will ensure that singularity grows too strong to handle. -The higher the output stength is set on PA control computer, the bigger the singularity will be. - -The PA will now draw power from the power net and start firing particles at the Gravitational singularity generator. - - - - - - - -A singularity will soon appear at the position of the Gravitational singularity generator. - - - - -If no particle is hitting the singularity generator, the singularity will start to slowly decay until it disappear. - -## Safety -Singularity emits radiation around it, so always keep a distance. Consider getting radiation shielding gear beforehand. Seek medical attention if experiencing health issues. - - - - - - - - - - - -A singularity might move around, but the containment field will repel it. -If a singularity escapes its containment field, often referred to as a "singuloose," it will attract and then consume everything in its way. - -In such circumstances, there is little to be done other than running in the opposite direction. + # Singularity / Tesla Engine + + The Singularity Engine / Tesla Engine can yield [color=#a4885c]infinite power[/color], with no fueling required. It can also [color=red]destroy the whole station[/color] with equal ease. It uses a Particle Accelerator to fire high energy particles at a Singularity Generator to form a singularity or ball lightning. + The singularity then pulses radiation which is absorbed by Radiation Collectors, or the ball lightning then zaps nearby tesla coils and grounding rods to provide power. + + # Setting it up + + Both engines requires 4 subsystems to work properly; two are shared between both engines: + + ## Containment field generators and Emitters + + + + + + The emitters connect to MV cables and fire lasers as long as they have power and are turned on. + Fire the emitters at enabled containment field generators to activate them. + If two containment field generators are active, in range and are in the same cardinal axis, a containment field will appear. + The containment field will repel the singularity or tesla, keeping it from escaping, and yield a little bit of power every time anything bounces off of them. + + The emitter lasers and the containment fields can also cause damage and/or cause you to be sent flying into deep space; [color=#a4885c]avoid touching them[/color] when active. + It is recommended to [color=#a4885c]lock the emitters[/color] with [keybind="AltActivateItemInWorld"/], to prevent any break-in no-gooders from loosing the singularity or tesla by simply switching off the field. + + Teslas can have significantly smaller containment fields than singularity containment fields; adjusting field size is recommended, as the tesla becomes easier to keep watch on in a simply 3x3 field setup. + + ## Particle accelerator + + + + + + + + + + + + + + + + + + + + + + The Particle Accelerator (PA) is a multi-tile structure that launches accelerated particles from its emitters. Its emitters should always face the generator. + Some stations already have an unfinished PA. To complete it, first ensure there is a MV cable beneath the PA power box, anchor all the parts, and then add an LV cable to each part. + + + + Then use a screwdriver to screw back the panels. + [color=#a4885c]Scan parts[/color] using the PA control computer to check if it's operational (the PA will not function if you do not scan it!). If it shows up as incomplete, examine what's missing. + + + + + The other two subsystems are unique to each other: + + ## Gravitational singularity generator or Ball lightning generator + + + + + The generator should be anchored at the center of the containment area, since this is where the singularity/tesla should appear at. + + ## Radiation collectors or Tesla coils + + + + + The radition collectors connect to HV cables and generate power from nearby radiation sources when turned on. + Radiation collectors require a tank full of gaseous plasma in order to operate. + Continous radiation exposure will gradually convert the stored plasma into tritium, so replace depleted plasma tanks with fresh ones regularly to maintain a high power output. + + + + + + The tesla coils connect to HV cables and provide a stream of power after being zapped by the ball lightning. + However, tesla coils usually do not fully absorb the lightning strike, and the grounding rods are required to prevent lighting from arcing to and obliterating nearby machines. + Do note that one grounding rod is not a foolproof solution; get [color=#a4885c]atleast 4 rods[/color] around the containment field to make it mathematically unlikely for the tesla to escape. + As the ball lightning zaps tesla coils, they will degrade from wear; make sure to [color=#a4885c]weld them[/color] every now and then to keep generating power. + + ## Turing on the Engines + + [color=red]Do not[/color] turn the PA on unless all the other subsystems are working properly and there is enough power to start the engine. + + Turn power on using the PA control computer. Set the strength to an appropiate level. Currently the only appropriate level is [color=#f0684d]1[/color]; anything above that will ensure that singularity grows too strong to handle. + The higher the output stength is set on PA control computer, the bigger the singularity will be. + + Currently, the output power does not affect the ball lightning, beyond giving the ball lightning extra orbs around it. + + The PA will now draw power from the power net and start firing particles at the generators. + + + + + + + + A singularity or ball lightning will soon appear at the position of the Gravitational singularity generator. + + + or + + + + If no particles are hitting the singularity, the singularity will start to slowly decay until it disappears. + This is not the case for the tesla; feel free to disconnect the PA after the tesla has been set up. + + ## Safety + The singularity emits a large amount of radiation around it, so always keep a distance from it. Consider getting [color=yellow]radiation shielding gear[/color] beforehand. Seek medical attention if you are experiencing health issues. + + + + + + + + + + + + The singularity might move around, but the containment field will repel it. + + The tesla creates large bolts of lightning around it, so make sure to wear insuls before approaching it. If you aren't, and it zaps you, pray that the ball lightning doesn't stunlock you and eventually send you into crit. + + + + If a singularity or tesla escapes its containment field, often referred to as a "singuloose" or "tesloose" respectively, it will attract and then consume everything in its way, growing larger as it does so, or it will begin to obliterate every machine in its path, and shock all crew personnel. + + In such circumstances, there is little to be done other than running in the opposite direction. + + + + However, if science has happened to research [color=#D381C9]Portable Particle Decelerators[/color], or if cargo can order them in time, you may be able to stop the singularity from eating the whole station. + Good luck on the tesla, though; it is merely too powerful to recontain after breaching. diff --git a/Resources/ServerInfo/Guidebook/Engineering/TEG.xml b/Resources/ServerInfo/Guidebook/Engineering/TEG.xml index 9e8697a9e1..63a62fecf8 100644 --- a/Resources/ServerInfo/Guidebook/Engineering/TEG.xml +++ b/Resources/ServerInfo/Guidebook/Engineering/TEG.xml @@ -19,25 +19,25 @@ Note that the circulators are [color=#a4885c]directional[/color]: they will only let gas through one way. You can see this direction in-game by examining the circulator itself. A pressure difference is required across the input and output, so pumps are generally provided and must be turned on. - There is no preference for which side must be hot or cold, there need only be a difference in temperature between them. The gases in the two "loops" are never mixed, only energy is exchanged between them. The hot side will cool down, the cold side will heat up. + There is no preference for which side must be hot or cold, there need only be a difference in temperature between them. The gases in the two "loops" are never mixed, [color=#a4885c]only energy is exchanged between them[/color]. The hot side will cool down, the cold side will heat up. ## The Pipes - There are 2 major pipenets to worry about here: The Hot Loop (where gas will be burnt for heat), and The Cold Loop (where circulated, heated waste gas will either be removed into space or cooled back down). Make sure that [bold]both pipenets do NOT mix[/bold], as only heat should be transferred between the two through the TEG. + There are 2 major pipenets to worry about here: [color=red]The Hot Loop[/color] (where gas will be burnt for heat), and [color=cyan]The Cold Loop[/color] (where circulated, heated waste gas will either be removed into space or cooled back down). Make sure that [color=#a4885c][bold]both pipenets do NOT mix[/bold][/color], as only heat should be transferred between the two through the TEG. # The Hot Loop As I'm sure a wise person once said: the best way to make something hot is to light it on fire. Well, depending on context, that may not be very wise, but luckily your engineering department has just what's needed to do it wisely after all. - As stated above, there are many different layouts one can follow to heat up (or cool down) gases; this part of the guide will cover 2 common methods one will often see for the hot loop when the TEG is setup: The Pipe Burn, and the Burn chamber. + As stated above, there are many different layouts one can follow to heat up (or cool down) gases; this part of the guide will cover 2 common methods one will often see for the hot loop when the TEG is setup: [color=#a4885c]The Pipe Burn[/color], and [color=red]the Burn Chamber[/color]. - Side note: Plasma fires burn relatively cool compared to, for example, Tritium fires. It may be viable to extract Tritium from an extraction setup (using a 97/3 ratio of O2/Plasma) and react it with Oxygen to get truly hellish temperatures for power. Although, this is just a recommendation; I'm not ya mum. + Side note: Plasma fires burn relatively cool compared to, for example, Tritium fires. It may be viable to extract Tritium from an extraction setup (using a 96/4 ratio of O2/Plasma) and react it with Oxygen to get truly hellish temperatures for power. Although, this is just a recommendation; I'm not ya mum. ## The Pipe Burn Also known as the naive method, this is generally discouraged when working for efficiency. However, if all you need is a smidge of power to run the station, and you don't feel like setting up the burn chamber, this method will do. - TODO: Remove this section when atmos pipes are updated to have pressure/temperature limits in a future atmos refactor. + [color=#444444]TODO: Remove this section when atmos pipes are updated to have pressure/temperature limits in a future atmos refactor.[/color] Most (if not all) pipe burns follow this general layout: @@ -55,8 +55,8 @@ - The Gas input is pretty self-explanatory; this is where you will input the O2-Plasma mix to be burnt. A 2:1 (67/33) ratio of Oxygen to Plasma is recommended for the hottest burn. - The High-pressure pump serves 2 purposes; first, it prevents the burn from backwashing into the supply pipe, which would be.. bad, for many reasons. Second, it maintains a positive pressure in the following pipe segment, which is important to allow the burn to continue, especially since hot gases expand. - - The Pipe segment is where the burn actually occurs; to start it off, one can use a heater to increase the temperature up to the ignition temperature of Plasma. Afterwards, the reaction should be self-sustaining, so long as the Pressure and Moles supplied remains high enough. Be warned; if you wish to remove the heater, it will carry some of this superheated gas with it, transferring it to the next pipenet you connect it to. Best to space the gas through a space vent, if you must. - - The Low-pressure pump (whose pressure should be [italics]slightly lower[/italics] than the input pump) prevents [italics]all[/italics] the gas from passing through the circulator, which could result in the loss of the Moles required to sustain a burn. + - The Pipe segment is where the burn actually occurs; to start it off, one can use a heater to increase the temperature up to the ignition temperature of Plasma. Afterwards, the reaction should be self-sustaining, so long as the Pressure and Moles supplied remains high enough. [color=#a4885c]Be warned[/color]; if you wish to remove the heater, it will carry some of this superheated gas with it, transferring it to the next pipenet you connect it to. Best to space the gas through a space vent, if you must. + - The Low-pressure pump (whose pressure should be [italic]slightly lower[/italic] than the input pump) prevents [italic]all[/italic] the gas from passing through the circulator, which could result in the loss of the Moles required to sustain a burn. - The Circulator is where this generated heat will flow to the cold loop; afterwards, feel free to space the waste gases. Note: Pressure pumps are used here as, while they pump on pressure (not flow-rate, which is comparatively faster), they are a bit easier to control when it comes to the limited Plasma supply on-station. However, the steps shown can be followed with volumetric pumps too. @@ -68,25 +68,53 @@ Most (if not all) stations have the burn chamber separated from the main atmospherics block by a 1-wide spaced grid, presumably to prevent conduction. The chambers consist of 3(+1) important parts: - The Air Injector/Passive Vent - The Space Vent - - The Radiator Loop + - The Scrubber Array + + Here is one layer of an example setup: (pipes can and do need to be layered under the scrubbers below to connect them!) + + + + + + + + + + + + + + + + + + + + + + + + + + + + - Most normal burn chambers don't come with Heat-Exchangers; instead, they have air scrubbers (and optionally, an air alarm) to help filter for Tritium, which is a highly reactive, hot-burning gas that can also be used to heat the TEG efficiently. - However, this is a slightly more advanced setup than just burning plasma, as it needs 2 burn chambers instead of 1 (one for tritium production, one for burning said tritium), so remove the scrubbers and retrofit the burn chamber with a parallel array of heat-exchangers instead. - The air injector (or Passive Vent) injects air (or allows air to flow) into the burn chamber. Either should be supplemented with a pump before it, to keep pressures high. - There is a notable difference between the passive vent and the air injector; the air injector can only keep injecting air up to 9MPa, which can be reached very easily with a good burn. Ideally, switch out the air injector for a passive vent connected to a volume pump. + There is a notable difference between the passive vent and the air injector; the air injector can only keep injecting air up to [color=#a4885c]9MPa[/color], which can be reached very easily with a good burn. Ideally, switch out the air injector for a passive vent connected to a volume pump. + + The space vent (designated as a blast door to space on one side of the burn chamber) allows waste gases to be expelled and destroyed. Open this every now and then to keep the pressure under control, or to space excess input gas. - The space vent (designated as a blast door to space on one side of the burn chamber) allows waste gases to be expelled and destroyed. Open this to keep the pressure under control. + The scrubber array filters out all the burnt gasses and sends them through the TEG. Note that using default settings on the scrubbers is a bad idea, as valuable plasma will be filtered out too. + Instead, use a network configurator to connect all the scrubbers to a nearby air alarm, and set the air alarm's scrubber settings to scrub everything except Oxygen and Plasma, and to siphon air aswell. This ensures that as much heat as available can be collected and sent to the TEG. - The radiator loop collects heat from the burnt gases and brings it to the TEG. To maximize efficiency, hook up the heat-exchangers [italics]in parallel to each other[/italics], with a pressure pump at max pressure after the array and a volumetric pump before the array. - The pressure of the volumetric pump should be set to ( 200 / number of heat-exchangers ) L/s. For example, having 2 heat-exchangers would mean you should set the pressure to 100 L/s. - Finally, fill the whole loop with (ideally) a high heat capacity gas, like Frezon or Plasma. (Yes, Frezon =/= Cold. Frezon has one of the highest heat capacities in the game; so long as it isn't reacting with Nitrogen, it can actually be heated and can store heat really well!) + Note that these are just two of many ways you can setup the hot loop; [color=#a4885c]feel free to mix and match setups as needed![/color] Volume pumps in replacement of pressure pumps, radiator loops for heat collection, or even a Pyroclastic anomaly to provide said heat! The stars are the limit! # The Cold Loop As with the Hot Loop, the Cold Loop must also be setup in order to operate the TEG. However, the Cold Loop is usually a lot more low-tech than the Hot Loop; in reality, the Cold Loop only has to be "relatively" cooler -- hey, room temperature is technically cooler than the surface of the sun, right? - There are 3 main methods you will see used for the Cold Loop: The Water Cooler (see: Liltenhead's video on the TEG), the Coolant Array and the Freezer Loop. + There are 3 main methods you will see used for the Cold Loop: [color=#a4885c]The Water Cooler[/color] (see: Liltenhead's video on the TEG), [color=cyan]the Coolant Array[/color] and [color=#a4885c]the Freezer Loop[/color]. ## The Water Cooler @@ -103,14 +131,17 @@ - TODO: Remove this section when gas miners are removed in a future atmos refactor. + [color=#444444]TODO: Remove this section when gas miners are removed in a future atmos refactor.[/color] ## Coolant Array - This is the default method for the Cold Loop you will see on a variety of stations. Being of moderate complexity and having no losses of any resource, this [italics]should[/italics] be the main method of cooling down the TEG. However, every station at the moment somehow has their heat exchangers hooked up wrong, reducing efficiency greatly. (Thanks a bunch, NT!) + This is the default method for the Cold Loop you will see on a variety of stations. Being of moderate complexity and having no losses of any resource, this [color=#a4885c]should[/color] be the main method of cooling down the TEG. However, most stations at the moment somehow have their heat exchangers hooked up wrong (or suggest incorrect piping), reducing efficiency greatly. [color=#444444](Thanks a bunch, NT!)[/color] - To use heat-exchangers properly, they must be setup in [italics]parallel[/italics], not in series (like what you see on most stations). A gas pump at max pressure should be placed after, and a volumetric pump before the heat-exchangers. - The flow-rate of the volumetric pump should be set to ( 200 / number of heat-exchangers ) L/s. + To use heat-exchangers properly, they must be setup in [color=#a4885c]parallel[/color], not in series (like what you see on most stations). A gas pump at max pressure should be placed after, and a volumetric pump before the heat-exchangers. + The flow-rate of the volumetric pump should be set using the following formula: + + [color=cyan]( 200 / number of heat-exchangers )[/color] L/s. + Simply speaking, the Coolant Array consists of 3 major parts: An input connector port, a few pumps and the heat-exchanger array out in space. It can be setup like so: @@ -161,7 +192,7 @@ - Connector Port: Use this to input a gas with high heat capacity; most of the time, Plasma or Frezon is used to do so, as they both have very high specific heat capacities (although most any gas will do). (Yes, Plasma =/= Hot. You can cool it down, and it acts as a really good heat exchange medium.) - Input/Output Pumps: Used to make sure gas keeps flowing through both the Circulator and the Heat-Exchanger array. As the gas cools down and heats up (and as it flows through the Exchanger), pressure must be applied for it to keep flowing. - - Heat-Exchanger: Basically, just a bunch of heat-exchanger pipes in space. Not much to say, besides the fact that it cools down the gas inside it. Make sure the heat-exchangers are placed on lattice, not plating! Otherwise, the heat-exchange efficiency will be greatly reduced, as the heat-exchangers aren't directly exposed to space below them. + - Heat-Exchanger: Basically, just a bunch of heat-exchanger pipes in space. Not much to say, besides the fact that it cools down the gas inside it. Make sure the heat-exchangers are [color=#a4885c]placed on lattice, not plating[/color]! Otherwise, the heat-exchange efficiency will be greatly reduced, as the heat-exchangers aren't directly exposed to space below them. ## The Freezer Loop diff --git a/Resources/ServerInfo/Guidebook/Medical/Cryogenics.xml b/Resources/ServerInfo/Guidebook/Medical/Cryogenics.xml index ef6e1a49e8..f70f43c8a8 100644 --- a/Resources/ServerInfo/Guidebook/Medical/Cryogenics.xml +++ b/Resources/ServerInfo/Guidebook/Medical/Cryogenics.xml @@ -12,7 +12,7 @@ Cryogenics can be a bit daunting to understand, but hopefully, quick run through - + diff --git a/Resources/ServerInfo/Guidebook/Medical/OrganManipulation.xml b/Resources/ServerInfo/Guidebook/Medical/OrganManipulation.xml new file mode 100644 index 0000000000..144d474bcc --- /dev/null +++ b/Resources/ServerInfo/Guidebook/Medical/OrganManipulation.xml @@ -0,0 +1,51 @@ + +# Organ Manipulation +These surgeries allow you to remove or replace organs from a patient for healthy ones. Which can help treat conditions on their bodies. + +## Anatomy +Normally a body has the following organs: + + + + + + + + + + + + + +Certain species might have more or less organs, such as Diona having a combined organ that serves multiple functions, but this is the rough outline of what you'll see. +To remove an organ, you'll need to apply the respective Organ Removal surgery on it, which will then remove it without harming the patient. + +However if you want to transplant an organ, you'll need to apply the respective Organ Transplant surgery where it originally was. + +## What does each organ transplant do? + +- Transplanting a [color=#a4885c]brain[/color] will allow the patient to take over another body. An excellent alternative to cloning if you can afford to spare another body. +- Transplanting a [color=#a4885c]liver[/color] will cure severe poisoning from a patient's body. +- Transplanting [color=#a4885c]lungs[/color] will help cure patients from severe asphyxiation. +- Transplanting a [color=#a4885c]heart[/color] will cure a patient's body of rot and decay. +- Transplanting [color=#a4885c]eyes[/color] will allow the patient to see again, and heal any eye damage. + +## Where do I get more organs? + +Normally when your patients come in needing new organs, they'll need to get a new one, as their old ones will be either missing, or damaged beyond repair. +For this you can use the Medical Biofabricator with some Biomass. Which can manufacture Biosynthetic organs for all species. + + + + + + + +By default, every Chief Medical Officer has access to a Medical Biofabricator board in their locker. + +## Why didn't the organ transplant +## do anything? + +Your patient's body rejected the organ as it is in a bad shape, try using a fresh organ from a donor, or getting a new one from the Medical Biofabricator. + + diff --git a/Resources/ServerInfo/Guidebook/Medical/PartManipulation.xml b/Resources/ServerInfo/Guidebook/Medical/PartManipulation.xml new file mode 100644 index 0000000000..2de5facee6 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/Medical/PartManipulation.xml @@ -0,0 +1,51 @@ + +# Part Manipulation +This is how you will turn felinids into humans, or vice versa if you're feeling particularly cruel. + +## Anatomy +Normally a body has 10 main parts. Those being: + + + + + + + + + + + + + + + + + + +Certain species might have more or less parts, such as tails, wings, or extra limbs, but this is the rough outline of what you'll see. +To detach a limb, you'll need to apply the Remove Part surgery on it, which will then remove it without harming the patient. + +However if you want to reattach the limb, you'll need to apply the Attach Part surgery where you want it attached. +Hands are attached to the arms, feet are attached to the legs. And everything else is attached to the torso. + + +## Where do I get more parts? + +Normally when your patients come in needing new limbs, they'll need to get a new one, as their old ones will be either missing, or damaged beyond repair. +For this you can use the Medical Biofabricator with some Biomass. Which can manufacture Biosynthetic limbs for all species. + + + + + + + +By default, every Chief Medical Officer has access to a Medical Biofabricator board in their locker. + +## I reattached my patient's limb, but +## it's not working? + +Your patient has taken enough damage to the point that their limb's nerves were badly damaged, and they need to be healed properly before it can work again. +For this you can try the Tend Wounds surgeries, or letting them use topicals on their wounds. + + diff --git a/Resources/ServerInfo/Guidebook/Medical/Surgery.xml b/Resources/ServerInfo/Guidebook/Medical/Surgery.xml new file mode 100644 index 0000000000..b37005e003 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/Medical/Surgery.xml @@ -0,0 +1,40 @@ + +# Surgery +Your usual weekly activity. By performing surgical operations on your patients, you can heal wounds, remove or add body parts, organs, and more. + +## The Basics +To start surgery your patient needs to be laying down, and ideally sedated. + +If they are not sedated, they will be in a lot of pain, which decreases their mood, and will make surgical wounds worse. + + + + + + +You also need to wear protective gear such as a mask and gloves to prevent harming the patient due to contamination. + + + + + + +## Getting Started + +To engage in surgery, you'll need a set of surgical tools. + + + + + + + + + + + + + +Once you've got tools in hand, interact with your patient to begin surgery. You'll be able to choose which part you want to operate on. + + diff --git a/Resources/ServerInfo/Guidebook/Medical/UtilitySurgeries.xml b/Resources/ServerInfo/Guidebook/Medical/UtilitySurgeries.xml new file mode 100644 index 0000000000..0a6841e1f3 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/Medical/UtilitySurgeries.xml @@ -0,0 +1,24 @@ + +# Utility Surgeries + +## Tend Wounds + +Tend Wounds is a surgery that allows you to heal brute or burn damage from a patient without the need for topicals. +To begin you need to open an incision on the patient's body, and then apply a Hemostat to it until the part is fully healed. +At which point then you can close the incision with a Cautery. + + + + + + + +This surgery performs best the more damaged your patient is, especially if they are still alive. And allows you to bring otherwise unrecoverable +patients, such as the dumb Technical Assistant that just walked into the burn chamber. + +## Cavity Implant + +This surgery allows you to implant any Tiny or Small item into a patient's torso. To begin you need to open an incision, and then open their ribcage. +Your patient cannot access the implanted item normally, though there may be uses for this, such as hiding the Nuclear Authentication Disk. + + diff --git a/Resources/ServerInfo/Guidebook/Mobs/Harpy.xml b/Resources/ServerInfo/Guidebook/Mobs/Harpy.xml new file mode 100644 index 0000000000..0b27e31473 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/Mobs/Harpy.xml @@ -0,0 +1,122 @@ + + # Harpy (Homo-Aves sapiens) + All Information included in this document is licensed under CC BY-SA 4.0, and is written by VMSolidus(Github), also known as raistlin_jag(Discord) + + + + + + Harpies were created during the first 100 years of the colonial period of human civilization, back when the corporations still answered to the Sol Government. + Their creators were a company called GenetiCorp, now long defunct. + During it's heyday, GenetiCorp developed processes for converting colonists into forms suitable for the lesser colonized planets with some extreme conditions. + For the Harpy subtype, these adaptations were created to colonize the moon of a Hycean world, Valyrian 4b(4a referring to the Hycean world itself). + It is a planet with an incredibly dense atmosphere, measuring just slightly over 150kpa at sea level, with a high oxygen concentration. + Its host star is extraordinarily rich in the UV spectrum of light, but gives off incredibly dim amounts of red light. + The planet's terrain features a vast ocean surrounding a single volcanically active continent, as well as extensive archipelagos. + Valyrian 4b is well known for its flora sporting leaves that fluoresce orange in the nearby star's UV intense light. + + Due to persistent financial troubles, GenetiCorp was bought out by its largest competitor Interdyne long before its first colony ship arrived at the intended destination. + Shortly after arrival, the original Harpy colonists were repossesed by Interdyne- who had gotten to the planet first in faster ships made after the fact. + Some Harpy colonists remained on "the Homeworld", while others were kept by the company and gradually spread across the realms of human space. + Due to the costs of their radical genemodding, most Harpies today are indebted to corporations, owing vast sums of money inherited from their parents. + These debts are often traded between corporations like stocks, resulting in a wide Diaspora of Harpies around the galaxy, and in turn gives them their extremely diverse coloration. + + ## Biology + + Harpies are biologically classed as a different species than Homo-Sapiens, due to the extreme nature of their original designer genetic modifications rendering them incapable of breeding true with Sol-Standard Humans. + They are however not true mutants, owing to the stability of their modifications such that they breed naturally as a species. + However, a "GenetiCorp-Standard Harpy" is readily recognisable as being related to humans thanks in part to retaining many body parts and organs present in the standard human. + A typical Harpy possesses a human brain, heart, stomach, kidneys, and reproductive organs, with skin not unlike a human- though it may be partially feathered, and can have a rough texture to the touch. + + Their most extreme internal modifications are the possession of a second trachea, leading to a quartet of Avian lung organs. + A Harpy's breathing is several times more efficient than a Human's at taking in oxygen, and like a bird, they have no separation of "Inhale and Exhale". + Air enters a Harpy's lungs in a continuous loop via a circular motion through their four lung chambers. Listening to a Harpy breathing via a stethoscope is something like listening to the rhythm of a heartbeat, it is fast and continuous. + + This continuous-flow breathing also permits the use of a highly advanced vocal organ called a Syrinx, which is what enables Harpies to perform iconic ability to imitate nearly any sound. + This of course comes with a significant trade-off, one that makes for a difficult life for Harpies to live in space. + They require double the volume of air a human requires, burning through tanks of oxygen twice as fast as any other species. + They are also substantially vulnerable to air impurities of any kind, and will be readily injured by simple things such as cigarette smoke, or someone hyperventilating in their face. + + ## Unique Languages +- [color=#008ecc]Valyrian Standard[/color]: + A language descended from eastern european languages of old earth - Valyrian Standard is the commonly spoken tongue of Harpies brought up on their homeworld of Valyrian 4b. + It is rarely spoken outside of the worlds of its native speakers, and has in modern times been supplanted by the 'Conlangs of the Sol Alliance. + Its speakers are those who wish to uphold the traditions and beliefs of ancient peoples from before the colonial era. + + ## Other Notes + + - No, Harpies don't lay eggs. + - In a combination of low-gravity and high atmospheric density, a Harpy is capable of flight. They are however flightless in Terran-Standard Atmospheric conditions. + - Adult Harpies can be anywhere from 135cm to 190cm tall, with an average height of around 155cm(Human average being roughly 175cm, depending on ethnicity). + - An average adult male Harpy weighs only 24kg, while an average adult female weighs 18kg. For reference, the average weight of an adult male human is 71kg. + - Curiously, Harpies do not follow the pattern of sexual dimorphism present in Terran birds. Female Harpies are usually just as colorful(or lack thereof) as their male counterparts. + - Spacer Harpies(Those not born on the Homeworld), are typically far more colorfully diverse than those native to the Homeworld. + + # Species Traits + + ## Positive Traits + - [color=#1e90ff]Mimicry[/color]: + Harpies can mimic almost any sound in the game using emotes, barring only a few exceptions. This includes the voice emotes of every other species. + The Harpy sound library contains nearly 700 possible sounds. This trait is unique only to Harpies. + + - [color=#1e90ff]Runner[/color]: + Thanks to their incredibly light build, a Harpy's base running speed is 10% faster than that of a standard Human. + + - [color=#1e90ff]Singer(Advanced)[/color]: + Harpies have a significantly more advanced variant of the [color=#1e90ff]Singer[/color] trait. This trait gives a Harpy access to an innate Midi Player, as well as a menu for swapping instruments. + Right click on yourself to open the Swappable Instrument menu, and select from a list of available instruments to mimic. Any of the following conditions both stop and prevent a Harpy from singing: + + * Being Mute + + * Being Zombified + + * Being Dead or Crit + + * Taking more than 5 damage in a single instance + + * Being shoved to the ground + + + - [color=#1e90ff]Zero Gravity Flight[/color]: + Harpies have significantly enhanced acceleration in zero gravity. This trait is shared with Nians. + + - [color=#1e90ff](Limited) True Flight[/color]: + Harpies have an activatable ability to attempt to fly. In order to fly, a Harpy must have both of their wings unencumbered, either by carrying something in hand, or by being restrained. + When activated, Flight significantly increases the maximum speed and acceleration of a Harpy, while allowing them to fly over obstacles. + Using flight costs a significant amount of stamina. + + - [color=#1e90ff]Talons[/color]: + Functions as per the [color=#1e90ff]Talons[/color] trait. A Harpy's unarmed attacks deal 5 points of Piercing damage. + + ## Negative Traits + - [color=#ffa500]Asthmatic[/color]: + Using their powerful Avian Lungs, a Harpy breathes twice as deeply, and twice as often as a standard Human. This results in two key conditions. The first is that all oxygen tanks last half as long for a Harpy. + The second condition, is that Harpies are uniquely vulnerable to poisonous fumes. When exposed to even a small amount of Carbon Dioxide and or Ammonia, a Harpy will shortly thereafter being to choke. + Choking can be ended either by moving away from the sources of poisonous fumes(such as rotting bodies, open gas tanks, or several people in an enclosed space with no scrubbers). + A choking Harpy can also stop themselves from choking by breathing from a tank of Pure Oxygen for at least 6 seconds. + + - [color=#ffa500]Extreme Low Density[/color]: + The average weight of a Harpy is only 24kg, and can be as low as 10kg, with a maximum of 31kg. Because of this, Harpies suffer from any form of Mass Contests. + This results in weaknesses such as- but are not limited to: + - Experiencing more recoil with firearms + - Being easier to shove down + - Being easy to pick up by other people + - Being easy to throw + - Having a harder time pushing or dragging heavy objects + - Having weaker melee damage with certain weapons + - Being thrown extremely easily by Space Wind + + - [color=#ffa500]Hollow Bones[/color]: + Harpies take 15% greater damage from any source of Brute damage. + + - [color=#ffa500]Ultraviolet Vision[/color]: + Harpies have a unique kind of eye lens that lets them see in Ultraviolet-Blue-Green, but that they cannot see the color Red. This functions as per the Ultraviolet Vision trait. + "Ultravision" can also be "Bought off" in character creation by taking the [color=#1e90ff]Trichromat Modification[/color] trait. + + # Unique Traitor Items + + - [color=#1e90ff]Bionic Syrinx[/color]: + Traitor Harpies can optionally purchase a Bionic Syrinx. This takes the form of an Implant which- when implanted in a Harpy, acts as an internal Voice Changer. + The Bionic Syrinx will only work on Harpies. + + diff --git a/Resources/ServerInfo/Guidebook/Mobs/IPCs.xml b/Resources/ServerInfo/Guidebook/Mobs/IPCs.xml new file mode 100644 index 0000000000..7aa9b7b779 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/Mobs/IPCs.xml @@ -0,0 +1,52 @@ + + # I.P.C. + + An IPC (short for Integrated Positronic Chassis) is a type of sentient robot and is considered an [color=yellow]independent individual[/color], meaning [color=red]they are not guided by any laws of robotics[/color]. IPCs cannot be hacked by Emags because they do not have to follow any predefined directives in their system. [color=red]IPCs are silicon-based beings, so doctors do not have the skills to repair them.[/color] + + + + + Like borgs, IPCs have a positronic brain as their processing source. However, unlike them, IPCs can't be assembled. "It's cheaper to create a shell that obeys you than one that doesn't! *wink*" + ## Recharging an IPC + + + + + + IPCs can be recharged in three different ways: + + APC Terminal: IPCs can use APC terminals to recharge. Press [color=yellow]Alt + left click[/color] on a terminal as many times as needed to fully recharge. + + Borg Rechargers: IPCs can use borg rechargers to recharge. Always prioritize the ones outside of the Sci area to avoid headaches. + + Power Cell: IPCs have an internal power cell that serves as their battery. They can simply swap it out by opening the hatch and manually replacing it. + + ## Integrated Radio + + + + + IPCs do [bold]not[/bold] use external radios because they already have one built in. They only need to get an encryption key from a radio. By clicking on an IPC with a [color=yellow]wire cutter[/color], you can remove their keys. + You can find new keys around the station or remove one from a sector radio using a [color=yellow]screwdriver[/color]. + + ## Repairing + + + Welders can be used to repair [color=yellow]Brute[/color] damage. + + + + Cables can be used to repair [color=yellow]Burns[/color]. + + + + Glass Sheets can be used to repair [color=yellow]Blindness[/color]. + + + In the event an IPC dies, after being fully repaired, it should be restarted using the [color=yellow]"Restart"[/color] button (located by right-clicking). + + [color=red]NEVER ELECTROCUTE AN IPC with a defibrillator or in any other way while it is dead, as this will cause the battery to discharge energy rays outward![/color] + + + + diff --git a/Resources/ServerInfo/Guidebook/Mobs/Reptilian.xml b/Resources/ServerInfo/Guidebook/Mobs/Reptilian.xml index 936cd9f5e2..8acdab3e51 100644 --- a/Resources/ServerInfo/Guidebook/Mobs/Reptilian.xml +++ b/Resources/ServerInfo/Guidebook/Mobs/Reptilian.xml @@ -1,16 +1,131 @@ - # Reptilians - + # Unathi +All Information included in this document is licensed under CC BY-SA 4.0, and is taken from https://wiki.aurorastation.org/index.php?title=Unathi with some modifications by VMSolidus(Github). - They can ONLY eat fruits and meat, but can eat raw meat and drink blood without any ill effects. - They prefer a somewhat higher temperature range than humans. - They can drag objects with their tail, keeping both their hands free. + The Unathi (direct plural: Unathi – You-NAWH-thee both singular and multiple) (adjective: Unathite - You-NAWH-thit) are a race of tall humanoid reptiles standing from six to seven feet tall on average, with females slightly smaller in stature. They possess a mixture of snake-like and crocodile-like features, resulting in hard and plate-like scales. + Unathi live in their home system of Uueoa-Esa with their homeworld being Moghes. The planet's history can be summarized as being divided up between various Hegemonies, names they give the most powerful Clans on Moghes throughout time. + The Clan system is deeply entrenched in Unathi society with everything else revolving around it. It forms a major part of their code of honor, which stresses the importance of martial abilities and loyalty to the Clan. + + Religion and spirituality play a major role in a Unathi society, and religious differences are the main dividing factor. + Conflict between the Sk'akh Church and the various Th'akh faiths of Moghes, as well as the various communes and cults, have caused conflict and wars between the Izweski Hegemony and the kingdoms that eventually comprised the Traditionalist Coalition. + This conflict hit a peak when they entered the intergalactic stage, which is now known as the Contact War. After this conflict, some Unathi fled the planet through Hephaestus or NanoTrasen shuttle ports. + Unathi fleeing to megacorporations like NanoTrasen find themselves working in security and engineering roles, but recently many have been proving themselves as competent surgeons and brilliant scientists, earning opportunities that Moghes would deny them because of its strict feudal hierarchy. + However, others are slipping through the cracks and becoming slavers, raiders, or pirates. As the ability for spaceship construction increases in the Unathi home system, so too does space piracy and smuggling. + + Moghes has two different biomes after the Contact War. The fertile Untouched Lands with its great jungles, vast swamps, and lush forests; all of which are protected by massive terraforming equipment kept secure by the Hegemony's wealth and power. + Contrast to that are the bombarded Wasteland kingdoms in the ashes of plains and forests, burnt into a massive planet-spanning desert rampant with punished kingdoms raiding to survive and part scavengers living in cities on treads. + + ## Megacorporate Relations + + Prior to 2465, Moghean Unathi that were not members of trade guilds were almost exclusively employed with Hephaestus Industries. + Unathi with guild memberships would primarily take contracts with NanoTrasen or Orion Express, depending on their career choice. Unathi living in Dominia, like most of their countrymen, seek employment with Zavodskoi Interstellar. + + After the Merchant's Guild declared bankruptcy, Hephaestus Industries bailed out all Hegemonic guilds and reorganised them under the banner of the company. + Since then, nearly all residents of Moghes are employed by Hephaestus in some way. + + # Biology + + Their average life expectancy is around sixty years for most Unathi. However, they tend to live for well past sixty years when given advanced medical care available to the other space-faring species. + As a cold-blooded species, they suffer fatigue and even short comas when exposed to extremely low temperatures. + + Humans and Unathi share a lot of similar internal biology with some notable exceptions. + Since they are a carnivorous species, their diet mostly consists of meat and animal products, though they do have a tolerance to some plants and fruits. + While Unathi do receive minor benefits for eating certain fruits, they do not get any nutrition from plants. + Due to their carnivorous nature, they also have an increased stomach capacity to help eat larger meals at a time. + The typical Unathi usually only eats one meal a day, and for the poorer sinta, perhaps once every two days. + They tend towards lethargic and sluggish movements more often than not because of their evolutionary history, so they move slower than most organic species. + However, the cyclical period of work for Unathi compared to other species is much faster. Unathi are polyphasic sleepers: creatures that sleep in shorter naps rather than at one time during the night. + While they tend towards sluggishness, sinta are often capable of incredible bursts of energy and speed, and their ability to work in three or four hour periods of time compensates for their more sporadic schedules. + + The reproductive system of a Sinta'Unathi is very similar to that of Earth reptiles. Females lay eggs, with the average clutch being somewhere between one and three. + They have a six-month gestation period, after which they are laid in a humid, warm area. After two months, the fetus is fully developed and hatches from the egg. + Unathi are born with their claws and the ability to walk within hours of hatching. A Sinta'Unathi is considered an adult when they are 16 years old. + + # Physical Appearance + + The Unathi as a species show a great deal of variation within body types as well as clusters of smaller physical traits passed down through clans. + Facial and body features range in size and shape with some having raptor-like aesthetics, serpentine looks, or a more draconic body. + Tail length is often genetic; conversely, tail girth is defined by a Unathi's diet as it stores most of the body's fat— nobles grow some of the largest tails. + + Unathi scales are somewhat tougher than human skin, though they shed off sporadically rather than all at once. + More vulnerable and hidden spots, such as the stomach and waist, the backs of joints, and inner thighs are all leathery skin and aren't covered in scales. + + The typical healthy adult male Unathi weighs roughly 280 pounds. Female sinta differ from males as they generally have a shorter and more rounded snout. + The typical healthy Unathi female weighs roughly 250 pounds. Female Unathi grow to about 5'9" to 6'8, while male Unathi grow to about 6'0" to 7'0". + Unusual heights tend to be smaller in scale, as the taller a Unathi is, the more likely it will be marred with health problems and not live as long as others. + + Both sexes stand on digitigrade legs, with three toes on each foot and a hooked dewclaw, and have clawed feet and hands. + They also have very long tongues, typically black or extremely dark red in color, which may stretch up to a foot and a half long and are forked just like a serpent's. + In much the same manner as snakes, Unathi can sample the air around them using their tongue. They blink and have tear ducts similar to humans. + + Unathi horns do not naturally regrow, and serious damage tends to remain permanent. + Ancient recipes using native herbs known as "horn paste" are commonly made and sold by herbalists and can counter cosmetic scratches or minor chipping. + More modern synthetic Horn Gel (based on Bone Gel), developed largely by Zeng-Hu Pharmaceuticals, can regenerate an entire lost horn, but it tends to be stratified to the upper classes and noblemen due to cost. + + ## Coloration + + Unathi are also often born into one of four major scale colour categories: red, black, orange-brown and "sand-colored", and green. + Alternative colours are rare, with dark blue and even albino Unathi being reported in very small numbers. Albinos are sometimes slightly tinted in colour while remaining predominantly white. Traditionally these off-coloured Unathi are met with prejudice, though some clans outside the Izweski may feel different. + + It has been noticed that scale colourations were more represented in some areas than other, and thus, many shades of these colours were, in Unathi culture, named after the places they tend to be seen in more often, in many cases. + + Modern red-coloured Unathi are thought to originate from a large nomadic clan that settled South of the Moghresian Sea around modern Skalamar. Nowadays, red-scaled Sinta tend to be seen on the entire Southern coast of the Moghresian Sea and the mountain ranges near it. + Today’s green-coloured Unathi hail from the Southern side of the Moghresian sea, and they are seen more often throughout this side of the coast and down to the colder lands even further South. Surprisingly, there is also a large batch of green-coloured Unathi located around Sahl lake, indicating that an old nomadic clan of mostly green Sinta settled there centuries ago. + Orange-brown and sand-yellow Unathi comprises a wide range of colours. They are a more common colour to be seen in a large area, and it is believed that their origins come from displacement in ancient conflicts that forced them to migrate and find new homelands. More precisely, Unathi harbouring such colours tend to be more represented in a large area defined between Darakath, Bahard, Mudki, and Yu’kal. + Black or “dark-scaled’ Unathi do not seem to originate from any precise area, and instead, tend to be more often seen around colder areas of Moghes, both North and South. A popular theory is that black scales are actually a consequence of exposure to the cold for generations— something that has yet to be proven. + All colours, barring albino, do not mix; if a brown Unathi and a light red Unathi have a child, the red scales the child has might be a little darker, but the colours do not blend to make something new. + + ## Genetic Variety + + Besides scale colouration, many other physical aspects of Unathi bodies can vary widely. + From horns and frills, to even extra scale crests and different structures in a Unathi’s very skeleton (namely, the skull). + While anatomical differences within a species are natural, Unathi are noteworthy for how wide these differences can get and how fast they come to be. + The exact science behind this has been lost after the Contact War, and most of the research behind such a phenomena is idly handled by Nanotrasen and Zeng-Hu specialists. + However it is assumed that Sinta simply evolve and adapt to their surroundings much faster than most of the other sentient species of the Spur, leading to such large physical discrepancies between Unathi alone. + From this, it is also assumed that, with Unathi populations moving between planets, and Moghes on its way to changing considerably (may the consequences of the of the Contact War be the of the Sinta homeworld, or may terraforming efforts save it), said biological differences between different groups will only get larger and more exotic in the next few centuries. + + # History + + The history of the Unathi is predominately a history of each Hegemony that has existed throughout their history. There have only been three clans that have managed to create an empire worthy of the title 'Hegemon.' + + The Kres’ha’nor Hegemony lasted from roughly 920 CE - 1500 CE. It is most famous for being the first empire to dominate Moghes and creating what was then a modern feudal state. + Their empire saw rise of walled cities, steel weapons, centralized taxation, and other innovations. It ended in a brutal succession war, with Moghes once again being shattered into hundreds of individual Kingdoms with no true global power. + + In the 16th century the power of Guilds began to grow, forming an early form of mercantilism. Guilds formed for bakers, butchers, grocers, millers, smiths, carpenters, weavers, mason, shoemakers, in fact, nearly every trade had its own guild. + Standards such as just weights and measures evolved from the guilds, and guild inspectors would inspect shops to ensure rules were being followed. + Guilds would help members that were sick, or in trouble, and would sometimes take care of families after the member died. + This time also saw Guilds building the first major universities. While incredibly exclusive, these universities began to create a new era of specialized labor and intellectuals. + + The 19th century saw rise of the Second Hegemony, ruled by the Sarakus Clan. + Their clan ruled harshly, and forced technological advances into the cities and towns. + In the 1930's they ruthlessly modernized their empire in The Great Endeavor, spreading electricity, radio, paved roads, modern plumbing, and other modern innovations. + + In 1994 the Izweski Clan violently deposed the Sarakus and slaughtered the entire surviving dynasty, declaring the Third Hegemony. + After they won an incredibly destructive global war to defend their claim, the Izweski have ruled Moghes until modern times. + + In 2403, a human exploration team discovered Moghes. Shortly after, first contact with the Izweski Hegemony was made. + Sol Alliance merchants and scientists flocked to Moghes, living under very careful observation and guard in Izweski cities. + Skalamar, the second largest city in Izweski, became the first Unathite city to have a shuttle port constructed. Owned by NanoTrasen, it served to transport people to and from the planet’s surface. + + The Contact War, which has more information here, was a cataclysmic global war that lasted from 2438 to 2449. + The fighting was between two global powers, the Izweski Hegemony, and the Traditionalist Coalition. + The war was fought over the very future of the Unathi and their relationship with the rest of the galaxy. + The Izweski demanded Moghes submit to a one-world government ran by themselves and embrace the influence of humanity. + The Traditionalist Coalition rejected this. The war went nuclear on September 5th, 2439, causing immense suffering across Moghes and directly leading to the creation of The Wasteland, which is set to engulf Moghes within a hundred years. + + # Species Traits + + - [color=#1e90ff]Reptilian Diet[/color]: Unathi can ONLY eat fruits and meat, but can eat raw meat and drink blood without any ill effects. + + - [color=#1e90ff]High Temperature Tolerance[/color]: Unathi prefer a somewhat higher temperature range than humans. + + - [color=#1e90ff]Grasping Tail[/color]: Unathi can drag objects with their tail, keeping both their hands free. - Their unarmed claw attacks deal Slash damage instead of Blunt. + - [color=#1e90ff]Claws[/color]: Unath unarmed claw attacks deal Slash damage instead of Blunt. - They take [color=#ffa500]30% more Cold damage.[/color] + - [color=#ffa500]Cold Intolerance[/color]: Unathi take [color=#ffa500]30% more Cold damage.[/color] diff --git a/Resources/ServerInfo/Guidebook/Mobs/Shadowkin.xml b/Resources/ServerInfo/Guidebook/Mobs/Shadowkin.xml new file mode 100644 index 0000000000..f2f73ee666 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/Mobs/Shadowkin.xml @@ -0,0 +1,36 @@ + +# Shadowkin + + + + + +Fluffy lil' guys. + +## Ability Differences + +- Need no air to survive +- Can Shadeskip +- Can travel to and from The Dark at will +- Dims nearby lights when in the The Dark +- When too low on energy, they may fall into a powerful sleep +- Can "speak" in the Empathy, which only other Shadowkin can understand +- Slightly less blunt damage +- A bit more slash damage +- Slightly more piercing damage +- Less cold damage +- Slightly more heat damage +- Near no cellular damage +- More bloodloss damage +- Slightly more shock damage +- More radiation damage + +## Physical Differences + +- Very large and brightly colored eyes with no pupils +- Sees the world through their eyes' tint +- Very large ears +- Very fluffy +- Can be Male, Female, or Unisex +- Can be 18-300 years old + \ No newline at end of file diff --git a/Resources/ServerInfo/Guidebook/Mobs/SlimePerson.xml b/Resources/ServerInfo/Guidebook/Mobs/SlimePerson.xml index fc72c60dbf..0374d4cb95 100644 --- a/Resources/ServerInfo/Guidebook/Mobs/SlimePerson.xml +++ b/Resources/ServerInfo/Guidebook/Mobs/SlimePerson.xml @@ -10,7 +10,21 @@ They exhale nitrous oxide and are unaffected by it. Their body can process 6 reagents at the same time instead of just 2. - Their Slime "blood" can not be regenerated from Iron. Slime Blood is technically a source of + Slimepeople can morph into a [bold]"geras"[/bold] (an archaic slimefolk term), which is a smaller slime form that can [bold]pass through grilles[/bold], + but forces them to drop their inventory and held items. It's handy for a quick getaway. A geras is small enough to pick up (with two hands) + and fits in a duffelbag. + + + + + + Slimepeople have an [bold]internal 2x2 storage inventory[/bold] inside of their slime membrane. Anyone can see what's inside and take it out of you without asking, + so be careful. They [bold]don't drop their internal storage when they morph into a geras, however![/bold] + + Slimepeople have slight accelerated regeneration compared to other humanoids. They're also capable of hardening their fists, and as such have stronger punches, + although they punch a little slower. + + Their slime "blood" can not be regenerated from Iron. Slime Blood is technically a source of moderately filling food for other species, although drinking the blood of your coworkers is usually frowned upon. They suffocate 80% slower, but take pressure damage 9% faster. This makes them by far the species most capable to survive in hard vacuum. For a while. diff --git a/Resources/ServerInfo/Guidebook/Mobs/Species.xml b/Resources/ServerInfo/Guidebook/Mobs/Species.xml index 5fb4ca741e..cb74fd50c9 100644 --- a/Resources/ServerInfo/Guidebook/Mobs/Species.xml +++ b/Resources/ServerInfo/Guidebook/Mobs/Species.xml @@ -14,5 +14,9 @@ - + + # Parkstation specific species + + + diff --git a/Resources/ServerInfo/Guidebook/Mobs/shadowkin.Lore.txt b/Resources/ServerInfo/Guidebook/Mobs/shadowkin.Lore.txt new file mode 100644 index 0000000000..e3cda6d77e --- /dev/null +++ b/Resources/ServerInfo/Guidebook/Mobs/shadowkin.Lore.txt @@ -0,0 +1,178 @@ +# Lore + +## Biology + +[color=#a88b5e]Physicality[/color] + +Shadowkin seem very similar to canine species when looked at. +While mammalian in nature, it is nearly impossible to find any sexual dimorphism between genders, if they even have any. +They seem to all share the same body shape and often voices. + +The head consists of a snout with a nose, sharp teeth in the mouth, and two big ears on top of the head. +In addition to the two big ears, Shadowkin have two smaller ears lower down, giving them an exceptionally good ability to detect where exactly sounds come from. + +Shadowkin eyes are usually very large and fully mono-colored; they have no discernible pupils, irises, or similar. +The eye of a Shadowkin is a solid orb of color and, perhaps, their most defining trait. + +All observations show Shadowkin do not seem to age after a certain point. +Some may look older or younger than others, but the aging process seems to stop when the Shadowkin is chronologically in their 20s. + +Unusually, compiled medical data on Shadowkin shows that they have no lungs, thus not needing to breathe. +The compiled medical data also shows that they have a large, more circular shape where their lungs would be. + +[color=#a88b5e]Mentality[/color] + +Shadowkin core personalities are mirrored by their eye color. +Some common colors and their meanings are: + +- Red: Determined to reach a goal, even if it means sacrificing others. +- Green: Eager to learn and fit in with others. +- Blue: Very calm and collected, or shy. + +Other colors also exist in addition to these but are not as common. +The other colors are just mixes of the three main colors. +For example, here are a couple of mixes: + +- Purple: Determined, yet probably won't harm, in some cases shy. +- Yellow: Quite eager to be number one, especially if related to knowledge. +- Orange: Shy and calm, yet will fight if provoked. +- White: Very robust, exceeds expectations in most things they do, but is quite boring to interact with generally. + +There are rumors other colors exist as well. +Shadowkin that lose their ability to travel between dimensions have [color=#000000]black eyes[/color] no matter their core personality. +These black-eyed Shadowkin make up the largest number of them living in Realspace. + +## History + +Shadowkin are creatures native to their so-called Pocket Dimension, a part of space that is unable to be accurately pinpointed and/or visited by most people. +From there, Shadowkin visit our worlds, and sometimes a large number of them are forced into our world in mass migrations and then live among us. +What exactly causes these mass migrations is unknown, but may be related to them losing their ability to travel between dimensions. + +[color=#a88b5e]Homeworld[/color] + +While no home system or world has been found, some Shadowkin talk about dreaming of their homeworld, describing a lush and green land with abundant resources. + +[color=#a88b5e]Racial/Government Status[/color] + +There currently exists no Shadowkin government, and with Shadowkin being as rare as they are, they tend to fit into currently established societies and civilizations, with varying degrees of success in the past. +In recent years, however, many cultures have taken to accepting Shadowkin as another galactic constant, going as far as allowing them lives similar to those that any of the other species would lead. + +Some systems early on took and experimented on Shadowkin, in an attempt to utilize their powers in various technology, and even recreating them with very experimental technology. +The tests failed and this was met with a lot of resistance from Shadowkin, many of them being killed or becoming Blackeyes in the process. +After this, those systems realized that Shadowkin were a sentient species, and deeply apologized for their actions. + +## Culture + +[color=#a88b5e]The Typical Experience[/color] + +Most times, life is pretty normal. A Shadowkin can have a job, go to the job to work, then spend time off with friends. +The fact is most times of the day, the small abilities one might have are nice, but not overly useful. Sure, a Shadowkin could dim the nearby lights, but at the same time, the Shadowkin could also just flick the light switch. +However, it can sometimes happen that when a Shadowkin slept in and needs to get to work quickly, they might just teleport to work. +Some people might be overly wary of Shadowkin, some might be drawn towards Shadowkin. +Many Shadowkin are used to not wearing clothes, which can lead to some awkward situations, but those are generally the same situations any other furred species would have. + +[color=#a88b5e]Language[/color] + +The only recorded spoken language of the Shadowkin is an unusual language named "Mar", named after the fact that every single word in this language is the word "mar". +Spoken in different tones, with more or less emphasis on the various parts, or by drawing out parts of this word, a multitude of different "mar"s can be created, but they follow no apparent conventions. +Shadowkin can perfectly understand each other though and discuss complex topics using only this one word. +The Shadowkin can hear the Mar language from anywhere and understand it via Empathy, yet do not know who it is from unless they are close enough to see the sender. +Other humanoid species are incapable of understanding or learning Mar, as they are unable to accentuate their speech in the same way or hear some of the silent, Empathic tones they use. +Shadowkin tend to learn one or more languages of the Realspace beings. +The capability in such learned languages depends fully on each single Shadowkin, where some have only very broken capabilities, while others are fluent in several languages. + +[color=#a88b5e]Naming Conventions[/color] + +Shadowkin tend to name themselves with a singular word, ranging from states of being, such as Lone and Collected, to words that describe their memories connected to their supposed homeworld, like Dreamer. +Name schemes of the humans are usually frowned upon, as they do not reflect upon the Shadowkin, resulting in reactions ranging anywhere from an actual frown to being entirely ignored. +Shadowkin following another species naming scheme are often Blackeyes, not fitting in with other Shadowkin as well. +Names hold a great deal for Shadowkin because of how closely they are often tied to describing who they are. +Following life-changing events, Shadowkin often change their names to reflect the new person they have become. + +[color=#a88b5e]Rumors and Speculation[/color] + +Shadowkin are beings from another dimension, capable of performing feats that others could only describe as "magic", with no scientific explanation. +It is speculated that Shadowkin actually have a whole nation in their dimension, and those that come to Realspace just simply refuse to talk about it. +Sometimes Shadowkin mention a "Hub", acting as evidence for this theory. + +Some believe Shadowkin are the result of experimentation, though them being taken and experimented on is a large piece of evidence denying this theory. + +Some believe Shadowkin are the result of an expedition crew that disappeared hundreds of years ago, even though the first Shadowkin sightings were long before that. +Obviously, this expedition ship performed time travel. + +## OOC Notes + +Extra information, should be read if playing Shadowkin, if not you should try to learn this in gameplay. + +[color=#a88b5e]History[/color] + +Shadowkin used to live on a very lush forest planet, using primitive technology to work with metal, create tools, and build stone structures. +According to the dreams, Shadowkin back then had forged tools and blades, but had no electricity, presumably putting them at a medieval technology level. +In a sudden event, everyone and everything organic suddenly found themselves in an alternate dimension, sucked straight out of their previous home. +The Shadowkin being ripped from their homes happened in three total "dimensional ripples". + +Now stuck in this alternate dimension, Shadowkin changed a lot. +They stopped aging and with time developed the ability to use the energy of this dimension to power their unusual "magic". +And even later, they learned how to leave the dimension and travel to Realspace and back again. +However, as the eons passes, memories became shady of their home. +Many Shadowkin forgot more and more about the thousands of years they had lived, reducing memory to focus on the more important closer time. +However in dreams, Shadowkin, even those born in Realspace, often have visions of their home planet. +Some events can trigger certain memories to return like meeting someone they knew from the past makes them remember in great detail who they are. +As such, it can happen that two Shadowkin can randomly meet and remember that they met hundreds of years ago, often confusing nearby unknowing humanoids. + +[color=#a88b5e]Biology[/color] + +If a Shadowkin personality changes drastically, their eye color will change as an effect, reflecting upon their new personality. +Shadowkin, being ageless entities, can look older or younger, but that is merely a visual representation of how they feel about their age. +22 years after birth a Shadowkin stops aging, and in all of their history no instance of them dying of old age has been recorded. + +[color=#a88b5e]Shadowkin energy[/color] + +Shadowkin have an odd organ in them, their "Core". +This organ is the source and storage place for their power. +Without it, they would be unable to use their abilities. +The Core is very sensitive to physical trauma, yet is very resilient to electrical or magical damage. +The Core can get irreversibly destroyed from overloading it, using too much power too quickly, which is what happens to the Blackeyes. +The Core can not be replaced, using a new one would require a new body, and trying to use another Shadowkin Core will result in death. + +The Shadowkin's ability to fall into a deep sleep is a method to recharge their energy at a significantly higher rate than idling. +Though while in this deep sleep, it is difficult to wake up, and cannot be woken up by anything other than themself. + +Shadowkin can know exactly how much energy they have, and feel differently based on how much they have. +They can also Empathically sense the energy of others nearby and can estimate how much energy they have based on their feelings. + +[color=#a88b5e]Shadowkin Abilities[/color] + +Shadowkin have a few abilities. +They can teleport, requiring a small amount of energy, but they can do it quickly. +They can teleport to and from The Dark, requiring much more energy than teleportation. +They can immediately fall into a deep sleep at any time. + +[color=#a88b5e]The Ritual[/color] + +When a Shadowkin reaches adulthood, they undergo a "Ritual". +During this Ritual they intentionally overload their energy reserves, forcing their capacity to expand rapidly in order to gain enough energy to travel between dimensions. +Most Shadowkin pass the Ritual and that's the end of it, but some Shadowkin perish or become Blackeyes during the ritual. + +[color=#a88b5e]Important Terminology[/color] + +[color=#a88b5e]Shadowkin:[/color] +The name given for your race. +While your race doesn't have a name for itself, this is how most of the Galaxy refers to you, so it's how you refer to yourself too. + +[color=#a88b5e]The Dark:[/color] +The other dimension where you are from. +There are theories that The Dark and Bluespace are connected, but you don't know the details about that. +The Dark is a dimension where strange rules apply. +For outsiders who somehow enter it, whatever they imagine being in there, they see there, if not too complex. +If an outsider comes with a willing Shadowkin they may see what the Shadowkin sees, being anything they imagine, or a reflection of the station. + +[color=#a88b5e]Blackeyes:[/color] +A Shadowkin who has undergone and failed the Ritual, or lost their ability via other methods. +The Blackeyes have completely black eyes, no matter their actual personality. +They choose a new name, which follows a favorite species' naming scheme. +They are often ignored or left alone more by other Shadowkin. +[color=#a88b5e]Note:[/color] Blackeyes correlation with black eyes is done entirely via roleplay, the game will not handle this for you. + +[color=#a88b5e]Mar:[/color] +A word used to verbally communicate feelings, emotions, wishes, hopes, ideas, and whatever, in addition to your Empathy. \ No newline at end of file diff --git a/Resources/ServerInfo/Guidebook/Science/ArtifactReports.xml b/Resources/ServerInfo/Guidebook/Science/ArtifactReports.xml index a377c980e5..5aaa9ba34a 100644 --- a/Resources/ServerInfo/Guidebook/Science/ArtifactReports.xml +++ b/Resources/ServerInfo/Guidebook/Science/ArtifactReports.xml @@ -1,25 +1,24 @@ -# Artifact Reports -A large portion of Xenoarchaeology gameplay revolves around the interpretation of artifact reports, which are created at the [color=#a4885c]analysis console[/color] after an artifact is scanned. Reports contain the following information: + # Artifact Reports + A large portion of Xenoarchaeology gameplay revolves around the interpretation of artifact reports, which are created at the [color=#a4885c]analysis console[/color] after an artifact is scanned. Reports contain the following information: -- [color=#a4885c]Node ID:[/color] a unique numeric ID corresponding to this artifact's node. Useful in conjunction with a [color=#a4885c]node scanner[/color] for quickly identifying recurring nodes. + - [color=#a4885c]Node ID:[/color] a unique numeric ID corresponding to this artifact's node. Useful in conjunction with a [color=#a4885c]node scanner[/color] for quickly identifying recurring nodes. -- [color=#a4885c]Depth:[/color] a distance from the starting node (depth 0). This is a good shorthand for the value and danger of a node. + - [color=#a4885c]Depth:[/color] a distance from the starting node (depth 0). This is a good shorthand for the value and danger of a node. -- [color=#a4885c]Activation status:[/color] whether or not a node has been activated in the past. + - [color=#a4885c]Activation status:[/color] whether or not a node has been activated in the past. -- [color=#a4885c]Stimulus:[/color] the stimulus for that particular node. + - [color=#a4885c]Stimulus:[/color] the stimulus for that particular node. -- [color=#a4885c]Reaction:[/color] the reaction the stimulus induces. This is often vague, so caution is advised. + - [color=#a4885c]Reaction:[/color] the reaction the stimulus induces. This is often vague, so caution is advised. -- [color=#a4885c]Edges:[/color] the amount of nodes that are connected to the current node. Using this, you can calculate the total number of nodes as well as organize a map of their connections. + - [color=#a4885c]Edges:[/color] the amount of nodes that are connected to the current node. Using this, you can calculate the total number of nodes as well as organize a map of their connections. -- [color=#a4885c]Current value:[/color] the amount of research points an artifact is currently worth. Extracting will set this to zero and traversing new nodes will increase it. - -Reports are a helpful tool in manipulating an artifact, especially in the later stages where you are traversing nodes that have already been activated. - - - -To help with this process, consider printing out reports, writing down details uncovered during activation, or storing them in a folder nearby. + - [color=#a4885c]Current value:[/color] the amount of research points an artifact is currently worth. Extracting will set this to zero and traversing new nodes will increase it. + Reports are a helpful tool in manipulating an artifact, especially in the later stages where you are traversing nodes that have already been activated. + + + + To help with this process, consider printing out reports, writing down details uncovered during activation, or storing them in a folder nearby. diff --git a/Resources/ServerInfo/Guidebook/Science/MachineUpgrading.xml b/Resources/ServerInfo/Guidebook/Science/MachineUpgrading.xml new file mode 100644 index 0000000000..286219b4d9 --- /dev/null +++ b/Resources/ServerInfo/Guidebook/Science/MachineUpgrading.xml @@ -0,0 +1,38 @@ + +# Machine Upgrading + +Machines help the station run smoothly, and as a scientist, you can help them run even better! + +## Parts +Stock Parts: + + + + + +You can examine each machine part to see both the type and the rating, which range from 1 to 4. + +Parts of higher levels can be researched as well as found through artifacts or salvage. + + + + + + +## Upgrading +To know if a machine can be upgraded, you can examine it and check for the [color=#a4885c]lightning bolt[/color] icon in the lower right corner. Clicking on it will allow you to see what kinds of upgrades the machine has. + +To check what parts a machine needs, you can examine its board. Try it here: + + + + + + +You can use any rating part for any part requirement. Using higher rated parts will increase how effective the machine is. + +If you want to upgrade an existing machine, simply deconstruct it with a screwdriver and crowbar, and replace the existing parts with parts of a higher level. + +You can also quickly upgrade machines by using an RPED, loading it with machine parts, and then clicking on a machine. It will quickly be upgraded with whatever parts were inserted. + + diff --git a/Resources/ServerInfo/Guidebook/Science/Xenoarchaeology.xml b/Resources/ServerInfo/Guidebook/Science/Xenoarchaeology.xml index 8cd94e8526..b42b8de530 100644 --- a/Resources/ServerInfo/Guidebook/Science/Xenoarchaeology.xml +++ b/Resources/ServerInfo/Guidebook/Science/Xenoarchaeology.xml @@ -2,18 +2,28 @@ # Xenoarchaeology Xenoarchaeology is a epistemics subdepartment focused on researching and experimenting on alien artifacts. -## Artifacts +At the start of each shift, the Science department will usually have access to at least two artifacts to experiment on. You can buy more by talking to the Cargo department. + +By researching the unique things each artifact can do, you gain Research Points, increase the artifact's sale value, and potentially discover a useful ability or two that can help your department or the whole station! + +## Artifact Nodes -Artifacts consist of a randomly-generated graph structure. They consist of nodes connected to each other by edges, the traversal of which is the main goal of the scientists working on them. +Artifacts consist of a randomly-generated tree of nodes. These nodes have a "[color=#a4885c]depth[/color]", representing how dangerous the node is, and the number of other nodes connected to it, called "[color=#a4885c]edges[/color]", + +Artifacts always start at depth zero, the root of the tree. Travelling the tree to find as many nodes as possible is the main goal of the scientists working on them. Knowledge is extracted from nodes to gain Research Points and increase the artifact's sale value. + +Each node has two components: its [color=#a4885c]stimulus[/color] and a [color=#a4885c]reaction[/color]. + +A stimulus is the external behavior that triggers the reaction. There's a variety of these, and higher depth nodes have more difficult to accomplish stimuli. Some stimuli will need improvisation to trigger, and you may need to talk to other departments to get everything you need. -Each node has two main components: a [color=#a4885c]stimulus[/color] and a [color=#a4885c]reaction[/color]. A stimulus is the external behavior that triggers the reaction. Some reactions are instantaneous effects while others are permanent changes. Triggering the reaction causes the artifact to move to one of the node's edges. +Some reactions are instantaneous effects while others are permanent changes. Once an artifact is triggered, the reaction causes the artifact to randomly move to another node it is linked to. -With these basic principles, you can begin to grasp how the different nodes of an artifact are interconnected, and how one can move between them by repeatedly activating nodes. +With some experimental science, you can begin to grasp how the different nodes of an artifact are connected, and how to move between them by repeatedly activating nodes. -While it might seem random to an untrained eye, a skilled scientist can learn to understand the internal structure of any artifact. +All non-zero-depth nodes will have exactly one edge that leads up to its parent node. All other edges a node has lead down to the next depth. ## Artifact Analyzer and Analysis Console @@ -22,13 +32,26 @@ While it might seem random to an untrained eye, a skilled scientist can learn to The main equipment that you'll be using for Xenoarchaeology is the [color=#a4885c]artifact analyzer[/color] and the [color=#a4885c]analysis console[/color]. You can use these to create reports that contain valuable information about an artifact. -To set them up, simply link them with a network configurator, set an artifact on top of the analyzer, and press the [color=#a4885c]Scan[/color] button. +To set them up, simply link them with a network configurator and set an artifact on top of the analyzer. Every station has at least one of these machines already set up. -Using the console, you can extract points from the artifact using the [color=#a4885c]Extract[/color] button. The amount of points you extract is based on how many new nodes have been activated, since last extracting. You can extract multiple times, there is no reason not to!!! +Use the console's [color=#a4885c]scan[/color] button to discover what stimulus the artifact needs and what its reaction will do. Scanning takes thirty seconds. + +Use the [color=#a4885c]print[/color] button to save the scan result, so you can refer to it later. + +Once you've discovered a new node, you can extract points from the artifact using the [color=#a4885c]Extract[/color] button. ## Assembling Artifacts -It is possible to gather multiple artifact fragments and assemble them into a working artifact. You can ask for these from Salvage, they usually find these while mining asteroids or on Expeditions. +It is possible to gather multiple artifact fragments and assemble them into a working artifact. You can ask for these from Salvage, who usually find these while mining asteroids or on Expeditions. + +## Traversal Bias + + + +Artifacts placed on top of a powered artifact analyzer are subjected to a bias which affects which node they will move to after being activated. The bias can be set in the artifact console. +There are two types of biases: +- [color=#a4885c]Up:[/color] favors nodes closer to the origin. Results in a decrease of depth. +- [color=#a4885c]Down:[/color] favors nodes farther away from the origin. Results in an increase of depth. diff --git a/Resources/ServerInfo/Guidebook/Security/Forensics.xml b/Resources/ServerInfo/Guidebook/Security/Forensics.xml index 2189488c6b..3eb53b1e9a 100644 --- a/Resources/ServerInfo/Guidebook/Security/Forensics.xml +++ b/Resources/ServerInfo/Guidebook/Security/Forensics.xml @@ -1,4 +1,4 @@ - + # Forensics There are a lot of tools to help you gather and examine the evidence at your disposal @@ -40,7 +40,7 @@ ## Fibers Whenenever people wearing gloves touch anything on the station, they are bound to leave behind some fibers. This complicates things, but nothing is unsolvable for a real detective. - There are up to [color=red]16[/color] different types of fibers possible. Can that stop you from solving the case? + There are up to [color=red]25[/color] different types of fibers possible. Can that stop you from solving the case? diff --git a/Resources/ServerInfo/Guidebook/Service/Bartender.xml b/Resources/ServerInfo/Guidebook/Service/Bartender.xml index 060c39fca3..b7599fc0d1 100644 --- a/Resources/ServerInfo/Guidebook/Service/Bartender.xml +++ b/Resources/ServerInfo/Guidebook/Service/Bartender.xml @@ -18,6 +18,7 @@ Don't forget containers to serve them in! + ## Drinks diff --git a/Resources/ServerInfo/Guidebook/Service/FoodRecipes.xml b/Resources/ServerInfo/Guidebook/Service/FoodRecipes.xml index 797591dd78..c74b947dbf 100644 --- a/Resources/ServerInfo/Guidebook/Service/FoodRecipes.xml +++ b/Resources/ServerInfo/Guidebook/Service/FoodRecipes.xml @@ -1,88 +1,9 @@ -## Starting Out -This is not an extensive list of recipes, these listings are to showcase the basics. +## Recipe list +Note: Only solid foods are listed here! To learn recipes for liquid ingredients, check the chemistry guidebook. -Mixes are done in a Beaker, foods are cooked in a Microwave. Cook times will be listed. +This list is auto-generated and contains all known foods. -WARNING: This is not an automatically generated list, things here may become outdated. The wiki has much more than is listed here. - -## The Basics: Mixing - -- Dough = 15 Flour, 10 Water -- Cornmeal Dough = 1 Egg (6u), 10 Milk, 15 Cornmeal -- Tortila Dough = 15 Cornmeal, 10 Water -- Tofu = 5 Enzyme (Catalyst), 30 Soy Milk -- Pie Dough = 2 Eggs (12u), 15 Flour, 5 Table Salt -- Cake Batter = 2 Eggs(12u), 15 flour, 5 Sugar -- Vegan Cake Batter = 15 Soy Milk, 15 Flour, 5 Sugar -- Butter = 30 Milk, 5 Table Salt (Catalyst) -- Cheese Wheel = 5 Enzyme (Catalyst), 40 Milk -- Chèvre Log = 5 Enzyme (Catalyst), 10 Goat Milk -- Meatball = 1 Egg (6u), 5 Flour, 5 Uncooked Animal Proteins -- Chocolate = 6 Cocoa Powder, 2 Milk, 2 Sugar -- Uncooked Animal Protein: Grind Raw Meat - - - - - - - - - - - - - - - - - - - -## Secondary Products - -- Dough Slice: Cut Dough -- Bun: Microwave Dough Slice for 5 Seconds -- Cutlet: Slice Raw Meat -- Cheese Wedge: Slice Cheese Wheel -- Flat Dough: Use a rolling pin or a round object (fire extinguisher, soda can, bottle) on Dough. -- Tortilla Dough Slice: cut Tortilla Dough -- Flat Tortilla Dough: Use a rolling pin or a round object (fire extinguisher, soda can, bottle) on Tortilla Dough Slice -- Taco Shell: Microwave Flat Tortilla Dough for 5 Seconds - -## Food Examples - -- Bread: Microwave Dough for 10 Seconds -- Plain Burger: Microwave 1 Bun and 1 Raw Meat for 10 Seconds -- Tomato Soup: 10u Water, 1 Bowl, and 2 Tomatoes for 10 Seconds -- Citrus Salad: 1 Bowl, 1 Lemon, 1 Lime, 1 Orange for 5 Seconds -- Margherita Pizza: Microwave 1 Flat Dough, 1 Cheese Wedge, and 4 Tomatoes for 30 Seconds -- Cake: 1 Cake Batter for 15 Seconds -- Apple Pie: 1 Pie Dough, 3 Apples, and 1 Pie Tin for 15 Seconds -- Beef Taco: Microwave 1 Taco Shell, 1 Raw Meat Cutlet, 1 Cheese Wedge for 10 Seconds -- Cuban Carp : Microwave 1 Dough, 1 Cheese Wedge, 1 Chili, 1 Carp Meat for 15 Seconds -- Banana Cream Pie : Microwave 1 Pie Dough, 3 Bananas, and 1 Pie Tin for 15 Seconds -- Carrot Fries : Microwave 1 Carrot, 15u Salt for 15 Seconds -- Pancake : Microwave 5u Flour, 5u Milk, 1 Egg (6u) for 5 Seconds - - - - - - - - - - - - - - - - - - - + diff --git a/Resources/ServerInfo/Nyanotrasen/Guidebook/Epistemics/Altar.xml b/Resources/ServerInfo/Nyanotrasen/Guidebook/Epistemics/Altar.xml index c3b211d296..540e15a724 100644 --- a/Resources/ServerInfo/Nyanotrasen/Guidebook/Epistemics/Altar.xml +++ b/Resources/ServerInfo/Nyanotrasen/Guidebook/Epistemics/Altar.xml @@ -5,18 +5,11 @@ The chapel has a [color=#a4885c]sacrificial altar[/color]. To use it, a psionic humanoid must be placed on it, and someone with either psionics or clerical training must initiate the sacrifice. It appears in the context menu on the altar, which can be opened with the right mouse button by default. - -Once sacrificed, the psionic humanoid's soul is trapped inside a [color=#a4885c]soul crystal[/color]. This is not the end for them; they can still talk both vocally and telepathically, and this form is much harder to destroy. - - -10% of the time, the altar will spawn a powerful psionic item along with the soul crystal. This chance increases to 50% if the sacrifice was performed by someone with clerical training, such as the [color=#a4885c]chaplain[/color] or [color=#a4885c]mystagogue[/color]. + + +As a reward for sacrificing a psionic, glimmer will be lowered substantially and you will be given some loot. +Usually this is a few bluespace crystals, but you can also get a powerful psionic item. ## Golemancy - - - - -Soul crystals can be installed into [color=#a4885c]golems[/color] to give the soul a new body. Golems are bound to serve their master. As constructs, they do not need to eat, drink, or breathe. -Note that for wood golems, if plants are planted on top of their head, the plants will still need those things. - +[color=red]Note: Golemancy is not implemented yet. Once you sacrifice a psionic you can borg them with an MMI.[/color] diff --git a/Resources/ServerInfo/Nyanotrasen/Guidebook/Epistemics/Psionics.xml b/Resources/ServerInfo/Nyanotrasen/Guidebook/Epistemics/Psionics.xml deleted file mode 100644 index 2911dae155..0000000000 --- a/Resources/ServerInfo/Nyanotrasen/Guidebook/Epistemics/Psionics.xml +++ /dev/null @@ -1,73 +0,0 @@ - -# Psionics -[color=#a4885c]Psionics[/color] are mental abilities that interface with the [color=#a4885c]noösphere[/color]. Humans have limited access to the noösphere's full potential, allowing some to find ways to command it. -The abilities originate from distorting a noöspheric imprint into a specific shape, a bit like a lock and key. This means that humans can only have a single ability at a time. -An imprint shaped for metapsionics will not work for invisibility, and vice versa. - - -Many other creatures are much more in tune with it than humans, allowing them to access more powerful psionics like [color=#a4885c]kineses[/color]. - -## Telepathy -All psionic entities have a shared telepathic communication channel. It's anonymized, so you never know who you are talking to. Statistically, it probably is not human. - -## Glimmer -[color=#a4885c]Glimmer[/color] is a more colloquial term for [color=#a4885c]noöspheric pressure[/color], measured in [color=#a4885c]milli-Psi (mΨ)[/color]. The noösphere can be considered analogous to an atmosphere. In a localized area, everything will equalize towards a certain pressure that's based off of the total energy in the system. -Note that unlike atmospheres, areas of noopsheric pressure do not operate by physical contigiousness, but mental. This means that areas with intertwined destinies will experience the same latent glimmer regardless of physical separation. - - -The [color=#a4885c]sophic scribe[/color] tracks glimmer. - - - - - - -Epistemics can build devices to interact with glimmer. These are usually the main determiners of where glimmer is going. -[color=#a4885c]Glimmer probers[/color] will increase glimmer, but directly generate research points without further input. [color=#a4885c]Glimmer drains[/color] simply drain glimmer at the cost of electricity. - -Use of psionics will increase glimmer, as will [color=#a4885c]noöspheric storms[/color]. - -## Discharges -Glimmer will occasionally discharge if it's above 100 mΨ, causing a wide range of effects based on just how high it is. The most common is giving all entities with psionic potential a small seizure, -which is something most people working on stations with an Epistemics department have grown used to. - -## Acquiring Psionics - - -Psionics can be acquired in a number of ways. The oracle will sometimes dispense a strange liquid called [color=#a4885c]lotophagoi oil[/color], which at high glimmer levels is guaranteed to give psionics. - -Random discharges may give you psionics as the seizure knocks your noöspheric imprint into just the right shape. - -[color=#a4885c]Space drugs[/color] are an easier to acquire but much weaker alternative to lotophagoi oil. - -## Psionic Insulation - - - - - -[color=#a4885c]Insulative clothing[/color] and [color=#a4885c]cryptobiolin[/color] will render you immune to psionics, but prevent you from using any you have. - - -Security has a more forcible option for insulative clothing. - -## Handling Glimmer Emergencies - -Probers should be turned off before glimmer exceeds 500 mΨ. - - -Glimmer drains are quite effective at draining glimmer, taking it out as fast as a prober can put it in. - - -Sacrificing psychics will reduce glimmer instantly by a decent amount. - - -Metabolizing 20u or more of [color=#a4885c]mindbreaker[/color] will remove psionics from a person, meaning they will not generate any more glimmer. - - -[color=#a4885c]Soulbreaker[/color] shells will remove psionics from psychics they hit. - - -[color=#a4885c]Ectoplasm[/color], combined in a beaker with equal parts water, ash, blood, and plasma, will produce a normality crystal when heated enough. [color=#fcdf03]Use a hot plate for this.[/color] - - diff --git a/Resources/ServerInfo/RP_Rules.txt b/Resources/ServerInfo/RP_Rules.txt deleted file mode 100644 index edfe5632ac..0000000000 --- a/Resources/ServerInfo/RP_Rules.txt +++ /dev/null @@ -1,126 +0,0 @@ -[color=#ff0000]YOU MUST BE AT LEAST 16 YEARS OF AGE TO PLAY ON WIZARD'S DEN SERVERS. ANY USERS SUSPECTED OF BEING UNDER 16 YEARS OF AGE WILL BE BANNED UNTIL THEY ARE OF AGE.[/color] - -[color=#ff0000]DISCONNECTING FROM OR IGNORING/EVADING ADMIN-HELPS WILL RESULT IN AN APPEAL ONLY BAN.[/color] - -This is the "short" form of the rules, which has all the information any regular player should need. You can find the "long" form of the rules with more examples & clarifications of any ambiguity on our wiki at [color=#a4885c]wiki.spacestation14.io[/color]. If you are already familiar with LRP rules and would like to get a quick idea of what the diffences are between MRP this page clearly highlights them. -Should you need it. Some RP-specific documents available on the wiki such as Space Law, the Standard Operating Procedure, and the Alert Procedure will be mentioned here and are expected to be followed. - -[color=#ff0000]Recent Changes[/color] - - Revolutionary rules have been added (#12, #16) - - Silicon rules have been added (#23) - - Security/command rules have been updated to address forced borging (#22) - -[color=#a4885c]01.[/color] [color=#a4885c]The[/color] [color=#ffd700]Golden[/color] [color=#a4885c]Rule.[/color] Admins may excercise discretion with rules as they see fit. If you rule lawyer or line skirt, you will get removed. Admins will answer for use of this privilege. - -[color=#ff0000]ZERO TOLERANCE RULES[/color] - -[color=#a4885c]02.[/color] Absolutely no hate speech, slurs, bigotry, racism, specism (demeaning other characters in-game due to their in-game race), sexism, or anything even remotely similar. (YOU WILL GET PERMABANNED) - -[color=#a4885c]03.[/color] Absolutely no Erotic Roleplay (ERP) or sexual content, including direct or indirect mentions of sexual behavior or actions. (YOU WILL GET PERMABANNED) (Leeway is given to insults, ex: 'You are a dickhead', do not push it) - -[color=#a4885c]04.[/color] Don't communicate in-game/in-character information through methods outside of the game (such as talking in Discord with other users actively playing or by talking to your sibling across the room while you are both playing). This is referred to as "Metacomming". Adminstrators cannot police metacommunications, we must assume it is being abused. (ALL INVOLVED WILL GET PERMABANNED) - -[color=#a4885c]05.[/color] Attempting to evade game bans will result in an automatic appeal-only permanent ban that is only appealable after six months and only with a voucher of good behavior from another SS13/SS14 server. Attempting to evade job bans will result in an appeal-only permanent ban. (YOU WILL GET BANNED MUCH WORSE THAN YOU ALREADY WERE) - -[color=#ff0000]GENERAL ETIQUETTE[/color] - -[color=#a4885c]06.[/color] These are English servers. Speak only English in IC and OOC. - -[color=#a4885c]07.[/color] Don't use exploits or external programs to play, gain an advantage, or disrupt/crash the round/server. This includes autoclickers and scripts to automate the game or evade AFK detection. Intentionally attempting to lag/crash the server will result in an immediate appeal-only ban. - -[color=#a4885c]08.[/color] Don't use multiple SS14 accounts to play (referred to as "multi-keying"). Users knowingly using multiple SS14 accounts will have all of their accounts banned. - -[color=#a4885c]09.[/color] Do not ignore the admin help relay or abuse it by flooding it with garbage, checking for admins before stating a problem (ex: "hello?", "any admins?"), using it as a chatroom, or sending messages of no substance. Hostility to administators in the relay will likely result in your removal. All admin helps are sent to the SS14 Discord. - -[color=#ff0000]IN GAME & ROLEPLAY RULES[/color] - -[color=#a4885c]10.[/color] Pick a realistic name that could appear on a birth certificate with at least a first and last name (leeway is given to this for Clowns, Mimes, and non-human races). - - Names of notable famous or fictional persons or names that resemble/parody them are strictly forbidden. You are not clever if you slightly change a famous name around. Terrible names open you up to being politely reminded to change it, smited, or instantly banned, depending on severity. - - Names resulting in inappropriate phonetic play-on-words are forbidden (ex: "Mike Oxlong", "Ben Dover", "Dixie Normus"). They are also extremely overdone. - -[color=#a4885c]11.[/color] Act like an actual human being on a space station in a medium-roleplay (MRP) environment. Do not use text speak or emoticons IC, and do not refer to OOC things like admins in-game. Do not threaten players that you are calling the admins on them. Do not use emotes to bypass speech filters or muteness. You are not required to write a backstory or follow procedure to exacts; however, you are expected to at least make an effort to act like your role. - - This server is a "Roleplay Expected" server. We define this as performing your assigned duties, "doing your job". This means it is important to do what is expected out of your department and not what would be expected out of other departments. - - Don’t do other peoples jobs for them. Opt in for the role you intend to play or change your job by visiting the Head of Personnel. Failure to do your basic duties may result in a job ban. - -[color=#a4885c]12.[/color] Don't be a dick. You are playing a multiplayer game with other people who also want to enjoy the game. - - [color=#ff0000]The arrivals station, shuttle, and general arrivals docking area are completely off-limits to any hostile activity, including activity by antagonists. Attacking newly spawned players in these areas or damaging/sabotaging these areas is strictly forbidden.[/color] - - Do not intentionally make other players' lives hell for your own amusement. - - [color=#ff0000]THE ROUND IS NOT OVER UNTIL THE GAME RETURNS TO THE LOBBY. ESCALATION AND SELF-ANTAG RULES APPLY EVEN AFTER THE ROUND SUMMARY APPEARS.[/color] - - Antagonists have a lot of leeway with this rule and may kill/sabotage as they see fit, however if your behavior degrades the experience for the majority of the server you will be told to stop. Antagonists are still generally forbidden from causing massive station damage early into the round (less than 30 minutes) and are forbidden from needlessly prolonging rounds. For antagonist specific rules, see the "long" form of the rules at [color=#a4885c]wiki.spacestation14.io[/color]. - -[color=#a4885c]13.[/color] Don't harass or target players across rounds for actions in prior rounds or for actions outside of the game (this is referred to as "Metagrudging".) - - Annoying players for IC reasons in the current round is fine; doing it across rounds or as a ghost role after they kill you is not. - -[color=#a4885c]14.[/color] Don't use information gained from outside your character's knowledge to gain an advantage (this is referred to as "Metagaming"). - - Using information you gain from outside your own character (such as spectating while a ghost, metacomms, or other means) to your advantage is strictly forbidden. If you take a ghost role, unless otherwise stated, you DO NOT REMEMBER ANYTHING from your past life. - - New Life Rule is in effect, you remember all events up until you fall unconscious unless you enter a dead state. Being defibrillated will return all your memories expect for the events leading up to your death, being cloned will make your character forget everything since the shift started. - -[color=#a4885c]15.[/color] Do your best not to interact negatively with SSD/AFK players. Moving them to safety is acceptable; killing, looting, or otherwise griefing them while they are away is not. - - SSD characters are players who are disconnected or AFK. It is possible for players to return to SSD bodies. Players become catatonic when they take a ghost spawner role or commit suicide. Players are NOT able to return to these bodies without admin intervention. - - If the player in question is an antag’s target or they are being secured by security, interactions to finish what is required of your duties/objectives are always acceptable. - - Ahelping about an SSD/AFK player may grant an exception to this rule. - - This does not apply to players that are catatonic, although needlessly killing or harming catatonic players is discouraged. - -[color=#a4885c]16.[/color] Follow escalation rules and don't murder someone for slipping you, use common sense. Do not make Cargonia. - - You can defend yourself to the extent of protecting your own life. - - Security may use less-lethal force to effect arrests of criminals. - - Don't outright leave people to die if you get in a fight, make an effort to heal them or bring them to Medbay. - - Adminhelp the situation if you think someone is over-escalating. - - Department strikes, revolutions (ex: cargonia and any variation thereof), riots, cults, and any other type of similar largely disruptive behavior are strictly forbidden. Other than for revolutionary antagonists, these activities are strictly forbidden. All players, including antagonists other than revolutionaries, must obtain admin permission before engaging in this behavior (forewarning: you are unlikely to get permission). - -[color=#a4885c]17.[/color] Don't immediately ghost or suicide from your role if you do not get antagonist (referred to as "Antag-rolling") or from head roles without notifying your chain of command or administrators. - - This is not fair to other players actually waiting patiently for an antagonist round. Alternatively, if you do not want to play an antagonist or do not want to cause conflict, do not opt-in for antagonist roles. - - Head of Staff roles help drive rounds. If you need to leave, please admin-help your role and that you are leaving. There is no need to wait for a reply when admin-helping that you need to disconnect as a head role. - -[color=#a4885c]18.[/color] Don't rush for or prepare equipment unrelated to your job for no purpose other then to have it "just in case" (referred to as "Powergaming"). - - A medical doctor does not need insulated gloves, and the Head of Personnel does not need to give themselves armory access so they can go grab a gun. Have an actual reason for needing these things. - - Don't manufacture weapons, bombs, or death poisons before you know of any reason you would need them. - - Don't pre-emptively hide antagonist objectives or pre-emptively secure them with higher security then normally required. - - Don't manufacture or prepare things for the "end of the round" when the shuttle docks with Central Command. - -[color=#a4885c]19.[/color] Intentionally making yourself a major problem/annoyance/disruption for the crew or other players at large while not an antagonist is forbidden (referred to as "self-antagging"). - - Don't openly try to cooperate with obvious or known antagonists as a non-antagonist. - -[color=#ff0000]SECURITY & COMMAND RULES[/color] -[color=#ff0000]Space Law, Standard Operating Procedure and Alert Procedure policy/guidelines are expected to be adhered to[/color]. These can be found at wiki.spacestation14.io. -If you regularly play Security or Command roles and got this far, we applaud you for reading. These rules also apply to any individual who is promoted or is acting in the place of a Security/Command role (unless they are an antagonist). - - -[color=#a4885c]20.[/color] Command and Security are held to a higher standard of play. - - Be competent in your job and department. Failure to know the basics of your department is liable to result in a job ban. - - Do not willingly and openly cooperate with terrorists/antagonists. Do not give away your objective items. Some leeway is given to making deals with antagonists if the deal benefits the safety or situation of the station as a whole and not just yourself. - - Uphold the Law & maintain order. Do not engage in lawbreaking activity or troublemaker behavior. Security is expected to intervene into criminal activity where possible. Heads of Staff are at minimum expected to report criminal activity to Security. - - Do not immediately abandon your position as a Command role and go do whatever you want instead of managing your department/the station. Do not abuse your position or use it to make arbitrary choices to the detriment of the station. - - Do not hire random crew to be your bodyguards or promote random to Captain or a Head of Staff at random. If you need bodyguards, talk to your security department. If you need a new Command role, talk to the personnel in that related department. - - '''Do not abandon the station during Nuclear Operatives. You are supposed to protect the station, not let operatives kill everyone on it without a fight.''' - -[color=#a4885c]21.[/color] Security/Command should try to remain non-lethal and effect arrests, except in the following special circumstances, where they may choose to use lethal force: - - Lethal force is used against you (ex: firearms, lasers, disabling/stunning weapons with intent to kill, deadly melee weapons) - - Suspect is wearing clothing or showing immediately dangerous equipment only used by enemy agents/antagonists (ex: Syndicate EVA Suit, Bloodred Hardsuit, Holoparasite, C-20R, etc.). - - You determine that your life or the life of an innocent is in immediate danger. - - The suspect is unable to be safely detained by less-lethal means. This includes suspects who continually resist efforts to be cuffed or continually manages to escape. - - If no other reasonable options are readily available and allowing the suspect to continue would be an unreasonable danger to the station/crew. - -Security/Command will be expected to answer for use of lethal force. Security/Command will be expected to effect arrests on criminals and prevent them from dying while in custody, even if lethal force is used. Security/Command is strongly required to clone antagonists and effect a sentence as deemed appropriate by Space Law. - -[color=#a4885c]22.[/color] Security/Command are expected to protect detainees in their custody to the best of their ability so as long as it does not come to unreasonable risk to themselves, the crew, or the station at large to do so. - - Brig times should generally not exceed 20 minutes unless stated otherwise in Space Law. Repeat offenders or antagonists may be permabrigged in accordance with Space Law. - - Security may choose to confiscate dangerous items (weapons, firearms) as well as items used to commission crimes or items that prove problematic in possession of the detainee (tools, insulated gloves, etc.). - - Detainees that die in your custody must be cloned unless they have been (legally) executed, suicide, or there is strong reason to believe they are an antagonist. - - Executions must be for a executable crime and approved by the Captain/Acting Captain, who will answer for approving it alongside Security's chain of command. Those who willingly attempt to damage/destroy or escape from the permabrig may be executed. - - Any prisoner may be borged with their consent. Borging may be offered as an alternative to execution, but cannot be forced if the prisoner is able to consent. - - Detainees in the brig have the right to know what they are being charged with, as well as basic medical aid, at least to the point they are no longer at risk of dying. - -[color=#ff0000]SILICON RULES[/color] -These rules also apply to any individual who is a silicon, including cyborgs and AI. As with other rules, more details are available on our wiki at [color=#a4885c]wiki.spacestation14.io[/color]. - -[color=#a4885c]23.[/color] You must follow your laws - - Silicons without any laws are free from any restrictions that would normally be placed by laws, but self-antagging rules still apply unless they are also antagonists. - - The order of your laws determines law priority. Law 1 takes priority over laws 2 and 3, and so on. - - Each individual silicon must remain consistent in their interpretations of laws through the round. - - Any silicon role not following their laws, or having laws that are a danger to the crew or station may be disabled or destroyed. Any silicon role posing a danger or disruption to the crew may be disabled or destroyed if there is no other reasonable and less severe way of dealing with them. - - Characters who are turned into cyborgs can remember their former lives, however they are still bound to their laws. - - Syndicate Agents and Revolutionaries are considered "crewmembers" for the purpose of laws that refer to crewmembers. Generally speaking, if the person appears on the crew manifest, they can be considered a crewmember. The captain or acting captain may hire or fire crewmembers by making it clear that they are no longer crew. Simply demoting someone to passenger is not sufficient to consider them fired. - - "Harm" is at minimum seen as physical violence or damage against someone or something. If the player wishes, they may choose to interpret psychological harm or similar aspects as harm as well. If two actions are likely to cause harm via action or inaction, silicons will be expected to try and pursue the option with the least potential for harm, however silicons instructed to prevent harm are still forbidden from directly causing harm. You can take an action or not act in cases that might result in eventual harm if it minimizes harm, but you cannot do so if it results in immediate harm. Silicons should default to inaction if neither action nor inaction can prevent harm. - - When receiving orders or directives from crewmembers and with a law that instructs you must obey, conflicting orders typically defer the choice to the silicon player of which directive you choose to obey if they conflict (taking into account the priorities of your other laws). - - Orders to silicons that are clearly unreasonable or obnoxious are a violation of the "Don't be a dick" rule. They can be ignored and can be ahelped. diff --git a/Resources/ServerInfo/RedialAddresses.txt b/Resources/ServerInfo/RedialAddresses.txt deleted file mode 100644 index 16a49cf76a..0000000000 --- a/Resources/ServerInfo/RedialAddresses.txt +++ /dev/null @@ -1,12 +0,0 @@ -# 1. Use addresses in the format ss14://example.com:1212 or ss14s://example.com:1212 or ss14://192.168.0.1:1212 etc -# 2. Make sure that your server has hub.server_url CVar set so that it will not redial people to itself. -# 3. Put comments on their own line -# Mining Station -ss14://nfn.mooo.com:1213 -# Delta-V -ss14://delta-v.org:1212 -ss14://delta-v.org:1213 -# Frontier -ss14://167.235.179.74:1212 -# BRC Nyano -ss14://167.235.179.74:1213 diff --git a/Resources/ServerInfo/RedialAddressesCentral.txt b/Resources/ServerInfo/RedialAddressesCentral.txt deleted file mode 100644 index 98812dfe91..0000000000 --- a/Resources/ServerInfo/RedialAddressesCentral.txt +++ /dev/null @@ -1,12 +0,0 @@ -# 1. Use addresses in the format ss14://example.moe:1212 or ss14s://example.moe:1212 or ss14://192.168.0.1:1212 etc -# 2. Make sure that your server has hub.server_url CVar set so that it will not redial people to itself. -# 3. Put comments on their own line -# This version contains only servers listed on the hub located at https://central.spacestation14.io/hub/ -# Mining Station -ss14://nfn.mooo.com:1213 -# Delta-V -ss14://delta-v.org:1212 -# Frontier -ss14://167.235.179.74:1212 -# BRC Nyano -ss14://167.235.179.74:1213 diff --git a/Resources/ServerInfo/Rules.txt b/Resources/ServerInfo/Rules.txt index c16976b944..9323242916 100644 --- a/Resources/ServerInfo/Rules.txt +++ b/Resources/ServerInfo/Rules.txt @@ -1,135 +1,60 @@ -[color=#ff0000]YOU MUST BE AT LEAST 17 YEARS OF AGE TO PLAY ON DELTA-V SERVERS. ANY USERS SUSPECTED OF BEING UNDER 17 YEARS OF AGE WILL BE BANNED UNTIL THEY ARE OF AGE.[/color] - -[color=#ff0000]DISCONNECTING FROM OR IGNORING/EVADING ADMIN-HELPS WILL RESULT IN AN APPEAL ONLY BAN.[/color] - -[color=#ff0000]THE USAGE OF ANY THIRD-PARTY APPLICATIONS/SCRIPTS/CLIENT MODIFICATIONS TO GAIN AN ADVANTAGE, AVOID INTENDED GAME/SERVER MECHANICS, OR TO HARM SERVER INFRASTRUCTURE IS STRICTLY PROHIBITED. ANY AND ALL INSTANCES OF THIS WILL BE MET WITH AN APPEAL-ONLY BAN.[/color] - -[color=#00ff00]Rules Update 11Mar2024 - Added Cryo Rules, Changed the Prisoner Rule, Expanded on EORG, added rules aimed at meta-grudging/vitriolic OOC/LOOC, and clarified part of Rule 6[/color] - -[color=#ffff00]Delta-V is a Medium Roleplay server. Try to immerse yourself into your character. This includes doing your job, interacting with your fellow crewmates, and using roleplay as the primary vessel to play the game. MRP places less emphasis on “winning” and more on just telling a story.[/color] - -If you have any questions about these rules, please use the admin help (ahelp) menu by hitting F1 in-game or clicking the “Ahelp” button in the lobby. - -[color=#a4885c]0.[/color] Admins can disregard any and all of these rules if they deem it in the best interest of the current round, server, and/or community at large. - - Administrators will be held fully accountable for their actions if they exercise this privilege. - - All of these rules apply as they are intended. Every example of a rule break cannot be defined as written, therefore, enforcement of the rules is subject to staff interpretation of the rule's intention. - -[color=#a4885c]1.[/color] Erotic Roleplay (ERP), erotic content, or 18+ sexual content is [color=#ff0000]not allowed under any circumstance[/color]. This includes comments not explicitly sexual in nature that contain words, phrases, or ideations that are deemed inappropriate. [color=#ff0000]If roleplay reaches a point where it has become sexual and/or uncomfortable, immediately stop and contact an administrator.[/color] - -[color=#a4885c]2.[/color] Follow the server expectations - - Do not cheat. - - Do not abuse glitches and exploits. [color=#ff0000]We have zero tolerance for abusing exploits/bugs. If you’re not sure, ask an admin.[/color] Otherwise, please use the #bug-reports channel on the Discord to report the bug. - - If you are banned from a role or department, you may not play that role. This includes seeking or accepting promotions into roles you are banned from. - - Do not make yourself a major problem/annoyance/disruption for the crew while not being an antagonist (i.e. self-antagging). - - Do not make yourself a major problem/annoyance/disruption for one specific crew member in a way that would actively detract from the other player's enjoyment of the shift. - - Do not, as a crewmate, hide the nuclear fission explosive (i.e. "the nuke") in an impossible to see location. - - Do not ignore the admin help relay or abuse it by flooding it with garbage, checking for admins before stating a problem (ex: "hello?", "any admins?"), using it as a chatroom, or sending messages of no substance. Hostility to administrators in the relay will result in your removal. All ahelp messages are sent to the Delta-V Discord. - - Department strikes (ex: cargonia and any variation thereof), riots, cults, and any other type of similar largely disruptive behavior are strictly forbidden. These activities are generally antagonist-only and all players regardless of antagonist status must obtain admin Central Command permission before engaging in this behavior (you are extremely unlikely to get permission). - - AFK (aka SSD) and catatonic players are considered to have the same rights as a conscious crewmate. - - End-of-round grief (EORG) is not allowed. This includes attacking, destroying, polluting, and injuring without reason both *at* and *on the way to* Central Command. - * This also extends to the following: - * Neutral parties, such as space dragons or sentient artifacts activating harmful nodes. - * Attempts to block the docking ports or ports leading into Central Command. - -[color=#a4885c]3.[/color] Follow Chat Guidelines - - Use English as your primary method of communication. - - Do not spam. - - Do not advertise. - - Be respectful towards other players in LOOC and OOC channels, and avoid making others uncomfortable. This can range anywhere from starting arguments to personal attacks against others, depending on the context. - - Do not use netspeak in character (i.e. btw, lmfao). - - Do not use the Emotes channel to bypass an inability to speak. This includes examples like "motions for you to put parmesan THEN sauce on the spaghetti," "im friendly," and "huh weird looks like I can't type." - - Use the LOOC and OOC channels properly. Don’t speak of in-character matters in those channels unless you’re asking questions related to in-game concepts. - * DO NOT use in-character channels to bypass a lack of ability to use OOC/LOOC chats (e.g. "Huh, wonder where captain went? *OOC* He probably left to get dinner lol"). This will result in an immediate dewhitelist. - - Hate speech, slurs and bigotry are [color=#ff0000]not allowed[/color]. Words that are closely tied to real life slurs are [color=#ff0000]not allowed[/color]. - - No racist remarks towards in-game races/morphotypes (i.e. Simulated Racism), while you don’t have to like everybody, you should not be acting upon nor expressing your distaste for other races/morphotypes. - -[color=#a4885c]4.[/color] Follow Metagaming guidelines - - If you die and get revived, do not act on things you saw while you were dead. - - Do not engage in meta-communications. This includes using chat channels outside of what is available in-game to communicate with other players in the same game. - - Players are allowed to have in-character relationships (friends, enemies, or otherwise), however they cannot be used as a reason to grant or deny things based exclusively on having a relationship with one another(i.e. Meta-Friending). - - You are allowed to have knowledge of past experiences with someone else in prior shifts. This does not give you permission to hold a grudge against someone that results in you treating them differently in an unfair way (i.e. Meta-Grudging). - - Specific players who were antagonists in previous rounds must not be treated differently because of it. - - Do not "Antag Roll." This is the act of joining rounds for the purpose of seeing if you joined as an antagonist, and leaving soon after if not. Players who have a history of this behavior will have their whitelist revoked and/or face a ban. - - [color=#ff0000]Do not stream the current round to the Delta-V Discord.[/color] - - Do not place players into cryosleep unless they have given consent to do so, they are fully catatonic, or they have been sentenced to preservative stasis. Always examine a character to double check if they are SSD or catatonic prior to placing them into cryosleep. - -[color=#a4885c]5.[/color] If a player dies and is brought back by way of cloning or borgification, they forget the last five minutes leading up to their death and cannot describe who or what killed them. - -Players that are revived by using a defibrillator CAN recall what killed them and do not have any forgetfulness about what happened while they were alive. - -[color=#ff0000]Please report players who violate this rule.[/color] - -[color=#a4885c]6.[/color] All constructed/summoned beings that have laws are bound by those laws and must abide by them. - - If a being has a master, they MUST follow their master's orders. - - Your master is held accountable if they order you to do something malicious. - *This does not apply to breaking server rules. If you are ordered to do something that would break a server rule, disregard it and inform an admin. - - Not having orders, or being given free will by your master does NOT give you permission to self-antag or grief the crew. - -[color=#a4885c]7.[/color] Follow Naming Conventions - - In-character names must fit the server standards of: - * Doesn't make obvious references - * Doesn't disturb roleplay - OR - * Is a result of random name generation. - - The only exception to the above is that theatrical roles like clown, boxer, and mime are allowed stage names with some freedom, as long as it is not obscene. - -[color=#a4885c]8.[/color] Follow Roleplay Guidelines - - Treat your character as a separate entity from you, the player. Your character's actions, feelings, and knowledge in-game should be based solely on the character's experiences and not your own as the player. Low roleplay actions that have no regard for your character or the setting (Memes, silly copy paste spam IC) are not acceptable. - - Character development can occur over rounds but each round is a soft-reset, meaning you can have previous shift experience but your character will never have died in the past. - - Command and Security will be held to a higher standard for roleplay. - - By picking prisoner, you have chosen to RP as a prisoner. You are still subject to rules pertaining to escalation, and should only seek to escape from the brig with good roleplay reasoning, such as abusive security, or a badly damaged perma. Escaping for no reason is considered a self-antag activity. If you are unsure whether your escape reason is valid, feel free to AHelp it first. - -[color=#a4885c]9.[/color] Follow Escalation Guidelines - - Do not escalate situations needlessly. Very few things are deserving of a fight to the death. - - Antagonistic ghost roles, and pest ghost roles like mice are always fair game for attacking. Don't grief crew-aligned ghost roles like familiars, drones, or pets without provocation. - - If a fight results in someone being critically injured, seek medical help for them. If they die, do not destroy or otherwise hide their body. - -[color=#a4885c]10.[/color] Follow Antagonist Guidelines - - The damage antagonists cause should be roughly proportional to their objectives, and contribute towards achieving them in some way. However antagonists have leniency in regards to what they can and can’t do. If you are concerned as to whether or not what you're about to do is allowed, feel free to ahelp and ask an admin. - - Other antagonists are not necessarily your friends. Traitors, rat kings, and possibly even space dragons are free agents, but no one should be working together with xenomorphs or zombies. - - Exploits, arrivals camping, unnecessary round extensions, and other extremely lame behavior are forbidden. - - Ghost roles have their own independent rules that must be followed. [color=#ff0000]Breaking these rules can result in a ban, whitelist removal, or both.[/color] - -[color=#a4885c]11.[/color] Psionics - - Players that have psionic powers are allowed to use them at-will to accomplish their roleplay goals. It should be noted that in-character consequences can happen as a result of their use, including being stripped of psionic powers or even death. - - As a mantis, it is not your goal to hunt down psionics. Do not mindbreak others against their will solely because they have psionic powers. - -[color=#a4885c]12.[/color] Don't rush for or prepare equipment unrelated to your job for no purpose other than to have it "just in case" (referred to as "Powergaming"). - - A medical doctor does not need insulated gloves, and the Head of Personnel does not need to give themselves armory access so they can go grab a gun. Have an actual reason for needing these things. - - Don't manufacture weapons, bombs, or deadly poisons before you know of any reason you would need them. - - Don't preemptively hide antagonist objectives or preemptively secure them with higher security than normally required. - - Don't manufacture or prepare things for the "end of the round" when the shuttle docks with Central Command. - -[color=#ff0000]SECURITY & COMMAND RULES[/color] -These rules apply to any individual who is promoted or is acting in the place of a Security/Command role (unless they are an antagonist). - -[color=#a4885c]13.[/color] Security and command roles are held to a higher standard and must follow space law. - - If you don’t have time to play a full round, do not select these roles. - - If you need to leave your computer, send an ahelp or notify your fellow crew via the radio. - - Be competent in your job and department. Failure to know the basics of your department is liable to result in a job ban. - - Security and Command roles are forbidden from using a syndicate uplink to receive contraband without written permission from Central Command. - - Do not give away your objective items (e.g. Captain’s equipment, Head of Personnel’s ID, etc.). Some leeway is given to making deals with criminals if the deal benefits the safety or situation of the station as a whole and not just yourself. - - Uphold the Law & maintain order. Do not engage in lawbreaking activity or troublemaker behavior. Security is expected to intervene into criminal activity where possible. Heads of Staff are at minimum expected to report criminal activity to Security. - - Do not immediately abandon your position as a Command role and go do whatever you want instead of managing your department/the station. Do not abuse your position or use it to make arbitrary choices to the detriment of the station. - - Do not hire random crew to be your bodyguards or promote random crewmember to Captain or a Head of Staff at random. If you need bodyguards, talk to your security department. If you need a new Command role, talk to the personnel in that related department. - - Do not abandon the station during Nuclear Operatives. You are supposed to protect the station, not let operatives kill everyone on it without a fight. - -[color=#a4885c]14.[/color] Security (and Command where applicable) should try to remain non-lethal and effect arrests. Security/Command will be expected to answer for use of lethal force. Security/Command will be expected to effect arrests on criminals and prevent them from dying while in custody, even if lethal force is used. - -In the following special circumstances, lethal force may be used by Security: - - Lethal force is used against you (ex: firearms, lasers, disabling/stunning weapons with intent to kill, deadly melee weapons) - - Suspect is equipped with dangerous equipment only used by enemy agents/antagonists and is not cuffed nor surrendering (ex: Bloodred Hardsuit, China Lake, C-20R, etc.). - - You determine that your life or the life of another person is in immediate danger. - - The suspect is unable to be safely detained by less-lethal means. This includes suspects who continually resist efforts to be cuffed or continuously manage to escape. - - If no other reasonable options are readily available and allowing the suspect to continue would be an unreasonable danger to the station/crew. - -[color=#a4885c]15.[/color] Security/Command are expected to protect detainees in their custody to the best of their ability so as long as it does not come to unreasonable risk to themselves, the crew, or the station at large to do so. - - Brig times should generally not exceed 20 minutes unless the crime is permabriggable. - - Security may choose to confiscate dangerous items (weapons, firearms) as well as items used to commit crimes or items that prove problematic in possession of the detainee (tools, insulated gloves, etc.). - - Security may inspect PDAs of detainees and withhold them for the duration of detention, but can only confiscate them if they are obviously contraband. Suspicion alone is NOT sufficient for PDA confiscation by Security. - - Security is prohibited from checking crewmates for implants without reasonable suspicion. - - Detainees that die in your custody must be cloned unless they have been (legally) executed or have committed suicide. - - Executions must be for a capital crime, used only as a last resort, and MUST be authorized by the highest ranking member of Security, who will answer to the use of execution. - - Detainees in the brig have the right to know what they are being charged with. - -[color=#a4885c]16.[/color] Command members besides the Logistics Officer are not permitted to leave the station on salvage expeditions. +[color=#ff0000]DISCONNECTING FROM OR IGNORING/EVADING COMMUNICATION FROM ADMINS WILL RESULT IN AN APPEAL ONLY BAN. The job gets really hard when you refuse to talk to the Admins, just come to our Discord and talk it out, hurt feelings will not be held.[/color] + +[color=#bb00bb]This is the only source of server rules that apply here, which ideally has all the information any regular player should need. Please do not ever hesitate to ask either an Admin in[/color] [color=#DC143C]AHelp (F1)[/color][color=#bb00bb] or in the Discord server if you ever want clarification on the rules.[/color] + +We do not have a wiki, instead, we have or will have all the needed information available via Guidebooks (NumPad0), such as how to power the station. If we do not have some bit of information (how to do a job or how specifically to execute something a document says) you may ask an admin via [color=#DC143C]AHelp (F1)[/color] or ask other players via the [color=#66bbff]OOC[/color] or [color=#66e0e0]LOOC[/color] chat channels. + +[color=#a4885c]0.[/color] [color=#a4885c]The[/color] [color=#ffd700]Golden[/color] [color=#a4885c]Rule.[/color] Admins may exercise discretion with rules as they see fit. If you rule lawyer or line skirt, you will get removed. Admins will answer for use of this privilege. + +[color=#a4885c]1.[/color] Users [color=#ff0000]must[/color] be at least 13 years old to take any part in anything related to this server. + +[color=#a4885c]2.[/color] This is an English server, and you are expected to use English when communicating through any server-related channels. + +[color=#a4885c]3.[/color] Do not ignore the [color=#DC143C]AHelp[/color] or abuse it by flooding it with garbage, checking for admins before stating a problem (i.e. "hello?", "any admins?" - also called "asking to ask"), or sending messages of no substance. + +[color=#a4885c]4.[/color] Attempting to evade game bans will result in an automatic appeal-only permanent ban. Similarly, attempting to evade job bans will result in an appeal-only permanent ban. + +[color=#a4885c]5.[/color] Absolutely no hate speech, bigotry, intolerance, or anything remotely similar. + +[color=#a4885c]6.[/color] No engaging in sexual acts. + +[color=#a4885c]7.[/color] Don't use custom clients, exploits, or external programs to gain an advantage or disrupt the round/server. This includes auto clickers and auto-hotkey scripts. + +[color=#a4885c]8.[/color] Don't communicate in-game/in-character information through methods outside the game (such as talking in Discord with other users actively playing about the game or by talking to your sibling across the room while you are both playing). This is referred to as "Metacomming" and it is strictly forbidden. + +[color=#a4885c]9.[/color] Don't "multi-key" (use multiple accounts simultaneously). Users knowingly using multiple SS14 accounts will have all of their accounts banned. + +[color=#a4885c]10.[/color] Don't use information gained from outside your character's knowledge to gain an advantage (this is referred to as "Metagaming"). + +[color=#a4885c]11.[/color] Don't rush for or prepare equipment unrelated to your job for no purpose other than to have it "just in case" (referred to as "Powergaming"). + +[color=#a4885c]12.[/color] Don't immediately ghost, suicide, or cryostasis if you do not get an antagonist role (referred to as "Antag-rolling"). + - This is not fair to other players actually waiting patiently for an antagonist round or wanting good staff. Alternatively, if you do not want to play an antagonist or do not want to cause conflict, do not opt in for antagonist roles. + +[color=#a4885c]13.[/color] Act like an actual human being on a space station. Avoid use of text speak or emoticons [color=#bbbbbb]IC[/color], and do not refer to [color=#66bbff]OOC[/color] things like admins in-game. Remember that this is a roleplaying game, and people are expected to try to react to situations realistically, even if it's not the "optimal" thing to do. What's good for Roleplay > What's good for Gameplay. + +[color=#a4885c]14.[/color] Don't harass or target players across rounds for actions in prior rounds or for actions outside the game without their approval (this is referred to as "Metagrudging"). + +[color=#a4885c]15.[/color] Intentionally making yourself a major problem/annoyance/disruption for the crew or other players at large while not an antagonist is forbidden without admin approval (referred to as "self-antagging"). + +[color=#a4885c]16.[/color] Follow escalation rules and don't murder someone for slipping you, use common sense. + - Don't outright leave people to die if you get in a fight, make an effort to heal them or bring them to [color=#52B4E9]Medical[/color]. + - [color=#DC143C]AHelp (F1)[/color] the situation if you think someone is over-escalating. + +[color=#ff0000]COMMAND/SECURITY RULES[/color] + +[color=#a4885c]17.[/color] If you sign up for a [color=#334E6D]Command[/color] or [color=#DE3A3A]Security[/color] role, you are expected to know the basics of the game, your job, and the job(s) you supervise, if any. + - Don't engage in common troublemaker behavior and lawbreaking as a [color=#334E6D]Command[/color] or [color=#DE3A3A]Security[/color] role. + - Do not immediately abandon your position as a [color=#334E6D]Command[/color] role and go do whatever you want instead of managing your department/the station. + - As a [color=#334E6D]Command[/color] role, do not take over the jobs of others. The [color=#334E6D]HoP[/color] is not the [color=#DE3A3A]HoS[/color], for instance, and holds no direct power over Security. + +[color=#a4885c]18.[/color] [color=#DE3A3A]Security[/color] should try to remain non-lethal and effect arrests, unless there is a great risk of loss of life. + +[color=#a4885c]19.[/color] [color=#DE3A3A]Security[/color] are expected to answer for use of lethal force. [color=#DE3A3A]Security[/color] will be expected to effect arrests of criminals and prevent them from dying while in custody, even if lethal force is used. [color=#DE3A3A]Security[/color] is strongly encouraged, but not required, to clone antagonists and effect a permabrigging or other sentence as deemed appropriate. + +[color=#a4885c]20.[/color] [color=#DE3A3A]Security[/color] are expected to protect detainees in their custody to the best of their ability, so as long as it does not come to an unreasonable risk to themselves, the crew, or the station at large to do so. + - Detainees that die in custody must be revived, unless they have been legally executed. + +[color=#a4885c]21.[/color] All [color=#334E6D]Command[/color] jobs should [color=#DC143C]AHelp (F1)[/color] when they need to stop playing. Do not play these roles if you do not expect to be able to finish the round. + - These roles must exhibit some competency. Incompetence can result in a job ban. [color=#334E6D]Command[/color] roles are designed to lead and manage departments first. They are not intended to become do-it-all members of their departments. + - Crew promoted to acting heads of staff must step down when an official head of staff arrives at the station. This is to prevent confusion with the Chain of command when the new crew mate arrives. diff --git a/Resources/ServerInfo/RulesLRP.txt b/Resources/ServerInfo/RulesLRP.txt deleted file mode 100644 index cb8d58819f..0000000000 --- a/Resources/ServerInfo/RulesLRP.txt +++ /dev/null @@ -1,64 +0,0 @@ -[color=#ff0000]You must be 16 or older to play. Users under 16 will be banned immediately.[/color] -[color=#ff0000]Speak English, please.[/color] - -[color=#ff0000]ERP OF ANY KIND IS BANNED.[/color] - -[color=#ff0000]This server is an LRP server, not an NRP server. You are still expected to RP somewhat, but not as much as you are expected on the MRP server.[/color] - -[color=#a4885c]The[/color] [color=#ffd700]Golden[/color] [color=#a4885c]Rule.[/color] Admins can disregard any and all of these rules if they deem it in the best interest of the current round, server, and/or community at large. They will of course be held fully accountable for their actions if they exercise this privilege. - -[color=#a4885c]2.[/color] Follow the community expectations. This includes both in game and elsewhere in our community. - -Don't be a dick. -Do not grief as a non-antagonist; this includes against AFK and catatonic players. -Don't be racist or bigoted. -Do not cheat or abuse glitches; please report all bugs on Discord. -Do not evade bans. - -[color=#a4885c]3.[/color] Do not use information gained outside of in-character means, and do not say In Character (IC) things in the Local Out Of Character (LOOC) chat channel. - -I.e. metagaming. This especially refers to communication between players outside of the game via things like Discord, known as meta-comms. Characters are otherwise allowed to know everything about in-game mechanics or antagonists, as well as keep persistent friendships and relationships with other characters when not for the purpose of unfair advantage by teaming up together for little IC reason. -Do not say LOOC things in IC either. - -[color=#a4885c]4.[/color] After cloning, metempsychosis or taking a ghost role, you forget your previous life. - -If cloned, you don't have amnesia, but you do forget the last five minutes leading to your death and cannot describe who or what killed you. -Don't act on anything you saw while ghosted. - -[color=#a4885c]5.[/color] Security and Command roles are held to a higher standard and must follow space law. - -If you don’t have time to play a full round, do not select these roles. -If you need to leave your computer, send an ahelp and notify your fellow crew via the radio. - -[color=#a4885c]6.[/color] Follow Chat Guidelines - -Only speak in English. -Do not spam. -Do not advertise. -Do not use netspeak in character (i.e. LOL, ROFL). -Hate speech, slurs and bigotry are not allowed. Words that are closely tied to real life slurs are not allowed. -No Simulated Racism, While you don’t have to like everybody, you should not be acting upon your distaste for other races/morphotypes. - -[color=#a4885c]7.[/color] All constructed/summoned beings are bound by their laws and must abide by these laws. - -If a being has a master, they must follow their master's orders. -Your master is held accountable if they order you to do something malicious. - -[color=#a4885c]8.[/color] Follow Roleplay Guidelines - -Be sure to capitalize your character's First and Last name. -Your character's name cannot include profanity (i.e. Dixie Nourmus, Mike Hawk, John Dildo, etc.) -Treat your character as a separate entity from you, the player. Your character's actions, feelings, and knowledge in-game should be based solely on the character's experiences and not your own as the player. -By picking prisoner, you have chosen to RP as a prisoner. While escape is allowed, there are consequences for your actions. - -[color=#a4885c]9.[/color] Follow Escalation Guidelines - -Do not escalate situations needlessly. Very few things are deserving of a fight to the death. -Antagonistic ghost roles, and pest ghost roles like mice are always fair game for attacking. Don't grief crew-aligned ghost roles like familiars, drones, or pets without provocation. -If a fight results in someone being critically injured, seek medical help for them. - -[color=#a4885c]10.[/color] Follow Antagonist Guidelines - -The damage antagonists cause should be roughly proportional to their objectives, and contribute towards achieving them in some way. However antagonists have leniency in regards to what they can and can’t do. If you are concerned as to whether or not what you're about to do is allowed, feel free to ahelp and ask an admin. -Other antagonists are not necessarily your friends. Traitors, rat kings, and possibly even space dragons are free agents, but no one should be working together with xenomorphs, nuclear operatives, or zombies. -Exploits, arrivals camping, and other extremely lame behavior are forbidden. \ No newline at end of file diff --git a/Resources/Textures/Clothing/Back/Backpacks/robotics.rsi/equipped-BACKPACK.png b/Resources/Textures/Clothing/Back/Backpacks/robotics.rsi/equipped-BACKPACK.png new file mode 100644 index 0000000000..45e4d346aa Binary files /dev/null and b/Resources/Textures/Clothing/Back/Backpacks/robotics.rsi/equipped-BACKPACK.png differ diff --git a/Resources/Textures/Clothing/Back/Backpacks/robotics.rsi/icon.png b/Resources/Textures/Clothing/Back/Backpacks/robotics.rsi/icon.png new file mode 100644 index 0000000000..8c4b6b0038 Binary files /dev/null and b/Resources/Textures/Clothing/Back/Backpacks/robotics.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Back/Backpacks/robotics.rsi/inhand-left.png b/Resources/Textures/Clothing/Back/Backpacks/robotics.rsi/inhand-left.png new file mode 100644 index 0000000000..5a410c0f66 Binary files /dev/null and b/Resources/Textures/Clothing/Back/Backpacks/robotics.rsi/inhand-left.png differ diff --git a/Resources/Textures/Clothing/Back/Backpacks/robotics.rsi/inhand-right.png b/Resources/Textures/Clothing/Back/Backpacks/robotics.rsi/inhand-right.png new file mode 100644 index 0000000000..d043e56cc1 Binary files /dev/null and b/Resources/Textures/Clothing/Back/Backpacks/robotics.rsi/inhand-right.png differ diff --git a/Resources/Textures/Clothing/Back/Backpacks/robotics.rsi/meta.json b/Resources/Textures/Clothing/Back/Backpacks/robotics.rsi/meta.json new file mode 100644 index 0000000000..d570609296 --- /dev/null +++ b/Resources/Textures/Clothing/Back/Backpacks/robotics.rsi/meta.json @@ -0,0 +1,26 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Sprites sprites taken from tgstation at commit https://github.com/tgstation/tgstation/commit/547852588166c8e091b441e4e67169e156bb09c1 and recolored by Mnemotechnician.", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-BACKPACK", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/Back/Duffels/robotics.rsi/equipped-BACKPACK.png b/Resources/Textures/Clothing/Back/Duffels/robotics.rsi/equipped-BACKPACK.png new file mode 100644 index 0000000000..a365df4ab0 Binary files /dev/null and b/Resources/Textures/Clothing/Back/Duffels/robotics.rsi/equipped-BACKPACK.png differ diff --git a/Resources/Textures/Clothing/Back/Duffels/robotics.rsi/icon.png b/Resources/Textures/Clothing/Back/Duffels/robotics.rsi/icon.png new file mode 100644 index 0000000000..66f43801dd Binary files /dev/null and b/Resources/Textures/Clothing/Back/Duffels/robotics.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Back/Duffels/robotics.rsi/inhand-left.png b/Resources/Textures/Clothing/Back/Duffels/robotics.rsi/inhand-left.png new file mode 100644 index 0000000000..7838e90df4 Binary files /dev/null and b/Resources/Textures/Clothing/Back/Duffels/robotics.rsi/inhand-left.png differ diff --git a/Resources/Textures/Clothing/Back/Duffels/robotics.rsi/inhand-right.png b/Resources/Textures/Clothing/Back/Duffels/robotics.rsi/inhand-right.png new file mode 100644 index 0000000000..e0f418d85e Binary files /dev/null and b/Resources/Textures/Clothing/Back/Duffels/robotics.rsi/inhand-right.png differ diff --git a/Resources/Textures/Clothing/Back/Duffels/robotics.rsi/meta.json b/Resources/Textures/Clothing/Back/Duffels/robotics.rsi/meta.json new file mode 100644 index 0000000000..36e32c5aaf --- /dev/null +++ b/Resources/Textures/Clothing/Back/Duffels/robotics.rsi/meta.json @@ -0,0 +1,26 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Icon taken from paradise station at commit https://github.com/ParadiseSS13/Paradise/commit/bd91a1b962fe9bd38e346e9fafd4ebb10784fcb3. Other sprites taken from tgstation at commit https://github.com/tgstation/tgstation/commit/547852588166c8e091b441e4e67169e156bb09c1 and recolored by Mnemotechnician.", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-BACKPACK", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/Back/Satchels/robotics.rsi/equipped-BACKPACK.png b/Resources/Textures/Clothing/Back/Satchels/robotics.rsi/equipped-BACKPACK.png new file mode 100644 index 0000000000..21504835fe Binary files /dev/null and b/Resources/Textures/Clothing/Back/Satchels/robotics.rsi/equipped-BACKPACK.png differ diff --git a/Resources/Textures/Clothing/Back/Satchels/robotics.rsi/icon.png b/Resources/Textures/Clothing/Back/Satchels/robotics.rsi/icon.png new file mode 100644 index 0000000000..a4fe210ec6 Binary files /dev/null and b/Resources/Textures/Clothing/Back/Satchels/robotics.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Back/Satchels/robotics.rsi/inhand-left.png b/Resources/Textures/Clothing/Back/Satchels/robotics.rsi/inhand-left.png new file mode 100644 index 0000000000..df00ce112d Binary files /dev/null and b/Resources/Textures/Clothing/Back/Satchels/robotics.rsi/inhand-left.png differ diff --git a/Resources/Textures/Clothing/Back/Satchels/robotics.rsi/inhand-right.png b/Resources/Textures/Clothing/Back/Satchels/robotics.rsi/inhand-right.png new file mode 100644 index 0000000000..b1010e3ce9 Binary files /dev/null and b/Resources/Textures/Clothing/Back/Satchels/robotics.rsi/inhand-right.png differ diff --git a/Resources/Textures/Clothing/Back/Satchels/robotics.rsi/meta.json b/Resources/Textures/Clothing/Back/Satchels/robotics.rsi/meta.json new file mode 100644 index 0000000000..d570609296 --- /dev/null +++ b/Resources/Textures/Clothing/Back/Satchels/robotics.rsi/meta.json @@ -0,0 +1,26 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Sprites sprites taken from tgstation at commit https://github.com/tgstation/tgstation/commit/547852588166c8e091b441e4e67169e156bb09c1 and recolored by Mnemotechnician.", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-BACKPACK", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/Back/etherealteleporter.rsi/equipped-BACKPACK.png b/Resources/Textures/Clothing/Back/etherealteleporter.rsi/equipped-BACKPACK.png new file mode 100644 index 0000000000..7496c91c55 Binary files /dev/null and b/Resources/Textures/Clothing/Back/etherealteleporter.rsi/equipped-BACKPACK.png differ diff --git a/Resources/Textures/Clothing/Back/etherealteleporter.rsi/icon.png b/Resources/Textures/Clothing/Back/etherealteleporter.rsi/icon.png new file mode 100644 index 0000000000..265318a96f Binary files /dev/null and b/Resources/Textures/Clothing/Back/etherealteleporter.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Back/etherealteleporter.rsi/meta.json b/Resources/Textures/Clothing/Back/etherealteleporter.rsi/meta.json new file mode 100644 index 0000000000..e4f5aa0f92 --- /dev/null +++ b/Resources/Textures/Clothing/Back/etherealteleporter.rsi/meta.json @@ -0,0 +1,56 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from https://github.com/ParadiseSS13/Paradise/blob/master/icons/mob/clothing/back.dmi", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-BACKPACK", + "directions": 4, + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Clothing/Eyes/Glasses/etherealgoogles.rsi/equipped-EYES.png b/Resources/Textures/Clothing/Eyes/Glasses/etherealgoogles.rsi/equipped-EYES.png new file mode 100644 index 0000000000..39fafeb144 Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Glasses/etherealgoogles.rsi/equipped-EYES.png differ diff --git a/Resources/Textures/Clothing/Eyes/Glasses/etherealgoogles.rsi/icon.png b/Resources/Textures/Clothing/Eyes/Glasses/etherealgoogles.rsi/icon.png new file mode 100644 index 0000000000..8ef5aa68d0 Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Glasses/etherealgoogles.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Eyes/Glasses/etherealgoogles.rsi/inhand-left.png b/Resources/Textures/Clothing/Eyes/Glasses/etherealgoogles.rsi/inhand-left.png new file mode 100644 index 0000000000..7ecbd93e8a Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Glasses/etherealgoogles.rsi/inhand-left.png differ diff --git a/Resources/Textures/Clothing/Eyes/Glasses/etherealgoogles.rsi/inhand-right.png b/Resources/Textures/Clothing/Eyes/Glasses/etherealgoogles.rsi/inhand-right.png new file mode 100644 index 0000000000..26fd8e57a4 Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Glasses/etherealgoogles.rsi/inhand-right.png differ diff --git a/Resources/Textures/Clothing/Eyes/Glasses/etherealgoogles.rsi/meta.json b/Resources/Textures/Clothing/Eyes/Glasses/etherealgoogles.rsi/meta.json new file mode 100644 index 0000000000..555efbc7da --- /dev/null +++ b/Resources/Textures/Clothing/Eyes/Glasses/etherealgoogles.rsi/meta.json @@ -0,0 +1,26 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "JustAnOrange", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-EYES", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/Eyes/Hud/syndagent.rsi/equipped-EYES.png b/Resources/Textures/Clothing/Eyes/Hud/syndagent.rsi/equipped-EYES.png new file mode 100644 index 0000000000..84979d1097 Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Hud/syndagent.rsi/equipped-EYES.png differ diff --git a/Resources/Textures/Clothing/Eyes/Hud/syndagent.rsi/icon.png b/Resources/Textures/Clothing/Eyes/Hud/syndagent.rsi/icon.png new file mode 100644 index 0000000000..900b438c7e Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Hud/syndagent.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Eyes/Hud/syndagent.rsi/inhand-left.png b/Resources/Textures/Clothing/Eyes/Hud/syndagent.rsi/inhand-left.png new file mode 100644 index 0000000000..b888ce227a Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Hud/syndagent.rsi/inhand-left.png differ diff --git a/Resources/Textures/Clothing/Eyes/Hud/syndagent.rsi/inhand-right.png b/Resources/Textures/Clothing/Eyes/Hud/syndagent.rsi/inhand-right.png new file mode 100644 index 0000000000..0e248905fb Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Hud/syndagent.rsi/inhand-right.png differ diff --git a/Resources/Textures/Clothing/Eyes/Hud/syndagent.rsi/meta.json b/Resources/Textures/Clothing/Eyes/Hud/syndagent.rsi/meta.json new file mode 100644 index 0000000000..f3acabcc54 --- /dev/null +++ b/Resources/Textures/Clothing/Eyes/Hud/syndagent.rsi/meta.json @@ -0,0 +1,26 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Made by IntegerTempest, edited by Golinth", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-EYES", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/Hands/Gloves/combat.rsi/equipped-HAND.png b/Resources/Textures/Clothing/Hands/Gloves/combat.rsi/equipped-HAND.png new file mode 100644 index 0000000000..7ca224617c Binary files /dev/null and b/Resources/Textures/Clothing/Hands/Gloves/combat.rsi/equipped-HAND.png differ diff --git a/Resources/Textures/Clothing/Hands/Gloves/combat.rsi/icon.png b/Resources/Textures/Clothing/Hands/Gloves/combat.rsi/icon.png new file mode 100644 index 0000000000..eedef5290d Binary files /dev/null and b/Resources/Textures/Clothing/Hands/Gloves/combat.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Hands/Gloves/combat.rsi/inhand-left.png b/Resources/Textures/Clothing/Hands/Gloves/combat.rsi/inhand-left.png new file mode 100644 index 0000000000..2e6ce0b9b9 Binary files /dev/null and b/Resources/Textures/Clothing/Hands/Gloves/combat.rsi/inhand-left.png differ diff --git a/Resources/Textures/Clothing/Hands/Gloves/combat.rsi/inhand-right.png b/Resources/Textures/Clothing/Hands/Gloves/combat.rsi/inhand-right.png new file mode 100644 index 0000000000..41ca0dddfb Binary files /dev/null and b/Resources/Textures/Clothing/Hands/Gloves/combat.rsi/inhand-right.png differ diff --git a/Resources/Textures/Clothing/Hands/Gloves/combat.rsi/meta.json b/Resources/Textures/Clothing/Hands/Gloves/combat.rsi/meta.json new file mode 100644 index 0000000000..1ad417f8f1 --- /dev/null +++ b/Resources/Textures/Clothing/Hands/Gloves/combat.rsi/meta.json @@ -0,0 +1,26 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Sprited by OnsenCapy (NamelessName on Discord)", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-HAND", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/Head/Bandanas/black.rsi/equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Bandanas/black.rsi/equipped-HELMET-vox.png new file mode 100644 index 0000000000..b74049130c Binary files /dev/null and b/Resources/Textures/Clothing/Head/Bandanas/black.rsi/equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Bandanas/black.rsi/equipped-MASK-vox.png b/Resources/Textures/Clothing/Head/Bandanas/black.rsi/equipped-MASK-vox.png new file mode 100644 index 0000000000..a3def50f88 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Bandanas/black.rsi/equipped-MASK-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Bandanas/black.rsi/meta.json b/Resources/Textures/Clothing/Head/Bandanas/black.rsi/meta.json index a555369032..a39a46ac81 100644 --- a/Resources/Textures/Clothing/Head/Bandanas/black.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Bandanas/black.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. equipped-MASK-vox & equipped-HELMET-vox states taken from Paradise at https://github.com/ParadiseSS13/Paradise/blob/bc095ad398790a2b718b2bab4f2157cdd80a51da/icons/mob/clothing/species/vox/mask.dmi", "size": { "x": 32, "y": 32 @@ -17,10 +17,18 @@ "name": "equipped-HELMET", "directions": 4 }, + { + "name": "equipped-HELMET-vox", + "directions": 4 + }, { "name": "equipped-MASK", "directions": 4 }, + { + "name": "equipped-MASK-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/Head/Bandanas/blue.rsi/equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Bandanas/blue.rsi/equipped-HELMET-vox.png new file mode 100644 index 0000000000..7266432372 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Bandanas/blue.rsi/equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Bandanas/blue.rsi/equipped-MASK-vox.png b/Resources/Textures/Clothing/Head/Bandanas/blue.rsi/equipped-MASK-vox.png new file mode 100644 index 0000000000..6bc5266367 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Bandanas/blue.rsi/equipped-MASK-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Bandanas/blue.rsi/meta.json b/Resources/Textures/Clothing/Head/Bandanas/blue.rsi/meta.json index a555369032..a39a46ac81 100644 --- a/Resources/Textures/Clothing/Head/Bandanas/blue.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Bandanas/blue.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. equipped-MASK-vox & equipped-HELMET-vox states taken from Paradise at https://github.com/ParadiseSS13/Paradise/blob/bc095ad398790a2b718b2bab4f2157cdd80a51da/icons/mob/clothing/species/vox/mask.dmi", "size": { "x": 32, "y": 32 @@ -17,10 +17,18 @@ "name": "equipped-HELMET", "directions": 4 }, + { + "name": "equipped-HELMET-vox", + "directions": 4 + }, { "name": "equipped-MASK", "directions": 4 }, + { + "name": "equipped-MASK-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/Head/Bandanas/botany.rsi/equipped-HELMET-hamster.png b/Resources/Textures/Clothing/Head/Bandanas/botany.rsi/equipped-HELMET-hamster.png deleted file mode 100644 index 3b89ea8bdf..0000000000 Binary files a/Resources/Textures/Clothing/Head/Bandanas/botany.rsi/equipped-HELMET-hamster.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Bandanas/botany.rsi/equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Bandanas/botany.rsi/equipped-HELMET-vox.png new file mode 100644 index 0000000000..c576b30f47 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Bandanas/botany.rsi/equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Bandanas/botany.rsi/equipped-MASK-vox.png b/Resources/Textures/Clothing/Head/Bandanas/botany.rsi/equipped-MASK-vox.png new file mode 100644 index 0000000000..378a1a3c19 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Bandanas/botany.rsi/equipped-MASK-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Bandanas/botany.rsi/meta.json b/Resources/Textures/Clothing/Head/Bandanas/botany.rsi/meta.json index a9b3b1556d..a39a46ac81 100644 --- a/Resources/Textures/Clothing/Head/Bandanas/botany.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Bandanas/botany.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. equipped-MASK-vox & equipped-HELMET-vox states taken from Paradise at https://github.com/ParadiseSS13/Paradise/blob/bc095ad398790a2b718b2bab4f2157cdd80a51da/icons/mob/clothing/species/vox/mask.dmi", "size": { "x": 32, "y": 32 @@ -18,13 +18,17 @@ "directions": 4 }, { - "name": "equipped-HELMET-hamster", + "name": "equipped-HELMET-vox", "directions": 4 }, { "name": "equipped-MASK", "directions": 4 }, + { + "name": "equipped-MASK-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/Head/Bandanas/gold.rsi/equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Bandanas/gold.rsi/equipped-HELMET-vox.png new file mode 100644 index 0000000000..5189f62e25 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Bandanas/gold.rsi/equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Bandanas/gold.rsi/equipped-MASK-vox.png b/Resources/Textures/Clothing/Head/Bandanas/gold.rsi/equipped-MASK-vox.png new file mode 100644 index 0000000000..616bb8eeb3 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Bandanas/gold.rsi/equipped-MASK-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Bandanas/gold.rsi/meta.json b/Resources/Textures/Clothing/Head/Bandanas/gold.rsi/meta.json index a555369032..a39a46ac81 100644 --- a/Resources/Textures/Clothing/Head/Bandanas/gold.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Bandanas/gold.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. equipped-MASK-vox & equipped-HELMET-vox states taken from Paradise at https://github.com/ParadiseSS13/Paradise/blob/bc095ad398790a2b718b2bab4f2157cdd80a51da/icons/mob/clothing/species/vox/mask.dmi", "size": { "x": 32, "y": 32 @@ -17,10 +17,18 @@ "name": "equipped-HELMET", "directions": 4 }, + { + "name": "equipped-HELMET-vox", + "directions": 4 + }, { "name": "equipped-MASK", "directions": 4 }, + { + "name": "equipped-MASK-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/Head/Bandanas/green.rsi/equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Bandanas/green.rsi/equipped-HELMET-vox.png new file mode 100644 index 0000000000..ed4c08a325 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Bandanas/green.rsi/equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Bandanas/green.rsi/equipped-MASK-vox.png b/Resources/Textures/Clothing/Head/Bandanas/green.rsi/equipped-MASK-vox.png new file mode 100644 index 0000000000..958bcdf0ea Binary files /dev/null and b/Resources/Textures/Clothing/Head/Bandanas/green.rsi/equipped-MASK-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Bandanas/green.rsi/meta.json b/Resources/Textures/Clothing/Head/Bandanas/green.rsi/meta.json index a555369032..ef97b40b36 100644 --- a/Resources/Textures/Clothing/Head/Bandanas/green.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Bandanas/green.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. equipped-MASK-vox & equipped-HELMET-vox states taken from Paradise at https://github.com/ParadiseSS13/Paradise/blob/bc095ad398790a2b718b2bab4f2157cdd80a51da/icons/mob/clothing/species/vox/mask.dmi", "size": { "x": 32, "y": 32 @@ -21,6 +21,14 @@ "name": "equipped-MASK", "directions": 4 }, + { + "name": "equipped-HELMET-vox", + "directions": 4 + }, + { + "name": "equipped-MASK-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/Head/Bandanas/grey.rsi/equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Bandanas/grey.rsi/equipped-HELMET-vox.png new file mode 100644 index 0000000000..92f305f183 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Bandanas/grey.rsi/equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Bandanas/grey.rsi/equipped-MASK-vox.png b/Resources/Textures/Clothing/Head/Bandanas/grey.rsi/equipped-MASK-vox.png new file mode 100644 index 0000000000..5d2af65f4b Binary files /dev/null and b/Resources/Textures/Clothing/Head/Bandanas/grey.rsi/equipped-MASK-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Bandanas/grey.rsi/meta.json b/Resources/Textures/Clothing/Head/Bandanas/grey.rsi/meta.json index a555369032..a39a46ac81 100644 --- a/Resources/Textures/Clothing/Head/Bandanas/grey.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Bandanas/grey.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. equipped-MASK-vox & equipped-HELMET-vox states taken from Paradise at https://github.com/ParadiseSS13/Paradise/blob/bc095ad398790a2b718b2bab4f2157cdd80a51da/icons/mob/clothing/species/vox/mask.dmi", "size": { "x": 32, "y": 32 @@ -17,10 +17,18 @@ "name": "equipped-HELMET", "directions": 4 }, + { + "name": "equipped-HELMET-vox", + "directions": 4 + }, { "name": "equipped-MASK", "directions": 4 }, + { + "name": "equipped-MASK-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/Head/Bandanas/red.rsi/equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Bandanas/red.rsi/equipped-HELMET-vox.png new file mode 100644 index 0000000000..22df7888e7 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Bandanas/red.rsi/equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Bandanas/red.rsi/equipped-MASK-vox.png b/Resources/Textures/Clothing/Head/Bandanas/red.rsi/equipped-MASK-vox.png new file mode 100644 index 0000000000..7cea36d539 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Bandanas/red.rsi/equipped-MASK-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Bandanas/red.rsi/meta.json b/Resources/Textures/Clothing/Head/Bandanas/red.rsi/meta.json index a555369032..a39a46ac81 100644 --- a/Resources/Textures/Clothing/Head/Bandanas/red.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Bandanas/red.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. equipped-MASK-vox & equipped-HELMET-vox states taken from Paradise at https://github.com/ParadiseSS13/Paradise/blob/bc095ad398790a2b718b2bab4f2157cdd80a51da/icons/mob/clothing/species/vox/mask.dmi", "size": { "x": 32, "y": 32 @@ -17,10 +17,18 @@ "name": "equipped-HELMET", "directions": 4 }, + { + "name": "equipped-HELMET-vox", + "directions": 4 + }, { "name": "equipped-MASK", "directions": 4 }, + { + "name": "equipped-MASK-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/Head/Bandanas/skull.rsi/equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Bandanas/skull.rsi/equipped-HELMET-vox.png new file mode 100644 index 0000000000..99ed4f9cc2 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Bandanas/skull.rsi/equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Bandanas/skull.rsi/equipped-MASK-vox.png b/Resources/Textures/Clothing/Head/Bandanas/skull.rsi/equipped-MASK-vox.png new file mode 100644 index 0000000000..a5fdbe4aac Binary files /dev/null and b/Resources/Textures/Clothing/Head/Bandanas/skull.rsi/equipped-MASK-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Bandanas/skull.rsi/meta.json b/Resources/Textures/Clothing/Head/Bandanas/skull.rsi/meta.json index a555369032..96c7993dd3 100644 --- a/Resources/Textures/Clothing/Head/Bandanas/skull.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Bandanas/skull.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. equipped-MASK-vox & equipped-HELMET-vox states taken from Paradise at https://github.com/ParadiseSS13/Paradise/blob/bc095ad398790a2b718b2bab4f2157cdd80a51da/icons/mob/clothing/species/vox/mask.dmi and modified by Flareguy", "size": { "x": 32, "y": 32 @@ -17,10 +17,18 @@ "name": "equipped-HELMET", "directions": 4 }, + { + "name": "equipped-HELMET-vox", + "directions": 4 + }, { "name": "equipped-MASK", "directions": 4 }, + { + "name": "equipped-MASK-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertengineer.rsi/meta.json b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertengineer.rsi/meta.json index fe9f38cad4..bcd0d726dc 100644 --- a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertengineer.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertengineer.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from paradisestation at commit https://github.com/ParadiseSS13/Paradise/commit/f09526480788c2e18fff8c16c4318fd6b4272c10 inhands by peptide", + "copyright": "Taken from paradisestation at commit https://github.com/ParadiseSS13/Paradise/commit/f09526480788c2e18fff8c16c4318fd6b4272c10. Vox states by Flareguy for Space Station 14", "size": { "x": 32, "y": 32 @@ -18,11 +18,7 @@ "directions": 4 }, { - "name": "off-inhand-left", - "directions": 4 - }, - { - "name": "off-inhand-right", + "name": "off-equipped-HELMET-vox", "directions": 4 }, { @@ -30,11 +26,7 @@ "directions": 4 }, { - "name": "on-inhand-left", - "directions": 4 - }, - { - "name": "on-inhand-right", + "name": "on-equipped-HELMET-vox", "directions": 4 }, { diff --git a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertengineer.rsi/off-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertengineer.rsi/off-equipped-HELMET-vox.png new file mode 100644 index 0000000000..e24011cc92 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertengineer.rsi/off-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertengineer.rsi/off-inhand-left.png b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertengineer.rsi/off-inhand-left.png deleted file mode 100644 index 8617b5a239..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertengineer.rsi/off-inhand-left.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertengineer.rsi/off-inhand-right.png b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertengineer.rsi/off-inhand-right.png deleted file mode 100644 index d205602ccf..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertengineer.rsi/off-inhand-right.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertengineer.rsi/on-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertengineer.rsi/on-equipped-HELMET-vox.png new file mode 100644 index 0000000000..5ad3f9c79a Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertengineer.rsi/on-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertengineer.rsi/on-inhand-left.png b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertengineer.rsi/on-inhand-left.png deleted file mode 100644 index 22a6c42d8f..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertengineer.rsi/on-inhand-left.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertengineer.rsi/on-inhand-right.png b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertengineer.rsi/on-inhand-right.png deleted file mode 100644 index a204bb0a78..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertengineer.rsi/on-inhand-right.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertjanitor.rsi/meta.json b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertjanitor.rsi/meta.json index fe9f38cad4..bcd0d726dc 100644 --- a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertjanitor.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertjanitor.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from paradisestation at commit https://github.com/ParadiseSS13/Paradise/commit/f09526480788c2e18fff8c16c4318fd6b4272c10 inhands by peptide", + "copyright": "Taken from paradisestation at commit https://github.com/ParadiseSS13/Paradise/commit/f09526480788c2e18fff8c16c4318fd6b4272c10. Vox states by Flareguy for Space Station 14", "size": { "x": 32, "y": 32 @@ -18,11 +18,7 @@ "directions": 4 }, { - "name": "off-inhand-left", - "directions": 4 - }, - { - "name": "off-inhand-right", + "name": "off-equipped-HELMET-vox", "directions": 4 }, { @@ -30,11 +26,7 @@ "directions": 4 }, { - "name": "on-inhand-left", - "directions": 4 - }, - { - "name": "on-inhand-right", + "name": "on-equipped-HELMET-vox", "directions": 4 }, { diff --git a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertjanitor.rsi/off-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertjanitor.rsi/off-equipped-HELMET-vox.png new file mode 100644 index 0000000000..34d61a07ca Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertjanitor.rsi/off-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertjanitor.rsi/off-inhand-left.png b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertjanitor.rsi/off-inhand-left.png deleted file mode 100644 index 8de0449073..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertjanitor.rsi/off-inhand-left.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertjanitor.rsi/off-inhand-right.png b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertjanitor.rsi/off-inhand-right.png deleted file mode 100644 index 7a96aed731..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertjanitor.rsi/off-inhand-right.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertjanitor.rsi/on-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertjanitor.rsi/on-equipped-HELMET-vox.png new file mode 100644 index 0000000000..99955187a8 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertjanitor.rsi/on-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertjanitor.rsi/on-inhand-left.png b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertjanitor.rsi/on-inhand-left.png deleted file mode 100644 index 030472239b..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertjanitor.rsi/on-inhand-left.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertjanitor.rsi/on-inhand-right.png b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertjanitor.rsi/on-inhand-right.png deleted file mode 100644 index 270a9249db..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertjanitor.rsi/on-inhand-right.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertleader.rsi/meta.json b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertleader.rsi/meta.json index fe9f38cad4..bcd0d726dc 100644 --- a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertleader.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertleader.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from paradisestation at commit https://github.com/ParadiseSS13/Paradise/commit/f09526480788c2e18fff8c16c4318fd6b4272c10 inhands by peptide", + "copyright": "Taken from paradisestation at commit https://github.com/ParadiseSS13/Paradise/commit/f09526480788c2e18fff8c16c4318fd6b4272c10. Vox states by Flareguy for Space Station 14", "size": { "x": 32, "y": 32 @@ -18,11 +18,7 @@ "directions": 4 }, { - "name": "off-inhand-left", - "directions": 4 - }, - { - "name": "off-inhand-right", + "name": "off-equipped-HELMET-vox", "directions": 4 }, { @@ -30,11 +26,7 @@ "directions": 4 }, { - "name": "on-inhand-left", - "directions": 4 - }, - { - "name": "on-inhand-right", + "name": "on-equipped-HELMET-vox", "directions": 4 }, { diff --git a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertleader.rsi/off-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertleader.rsi/off-equipped-HELMET-vox.png new file mode 100644 index 0000000000..f79fe95b57 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertleader.rsi/off-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertleader.rsi/off-inhand-left.png b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertleader.rsi/off-inhand-left.png deleted file mode 100644 index b53973feec..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertleader.rsi/off-inhand-left.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertleader.rsi/off-inhand-right.png b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertleader.rsi/off-inhand-right.png deleted file mode 100644 index 0f534f88b1..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertleader.rsi/off-inhand-right.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertleader.rsi/on-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertleader.rsi/on-equipped-HELMET-vox.png new file mode 100644 index 0000000000..f94eecb411 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertleader.rsi/on-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertleader.rsi/on-inhand-left.png b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertleader.rsi/on-inhand-left.png deleted file mode 100644 index 819ca15f44..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertleader.rsi/on-inhand-left.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertleader.rsi/on-inhand-right.png b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertleader.rsi/on-inhand-right.png deleted file mode 100644 index 848bfcfe88..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertleader.rsi/on-inhand-right.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertmedical.rsi/meta.json b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertmedical.rsi/meta.json index fe9f38cad4..bcd0d726dc 100644 --- a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertmedical.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertmedical.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from paradisestation at commit https://github.com/ParadiseSS13/Paradise/commit/f09526480788c2e18fff8c16c4318fd6b4272c10 inhands by peptide", + "copyright": "Taken from paradisestation at commit https://github.com/ParadiseSS13/Paradise/commit/f09526480788c2e18fff8c16c4318fd6b4272c10. Vox states by Flareguy for Space Station 14", "size": { "x": 32, "y": 32 @@ -18,11 +18,7 @@ "directions": 4 }, { - "name": "off-inhand-left", - "directions": 4 - }, - { - "name": "off-inhand-right", + "name": "off-equipped-HELMET-vox", "directions": 4 }, { @@ -30,11 +26,7 @@ "directions": 4 }, { - "name": "on-inhand-left", - "directions": 4 - }, - { - "name": "on-inhand-right", + "name": "on-equipped-HELMET-vox", "directions": 4 }, { diff --git a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertmedical.rsi/off-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertmedical.rsi/off-equipped-HELMET-vox.png new file mode 100644 index 0000000000..17790bee54 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertmedical.rsi/off-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertmedical.rsi/off-inhand-left.png b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertmedical.rsi/off-inhand-left.png deleted file mode 100644 index 7d3ce26157..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertmedical.rsi/off-inhand-left.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertmedical.rsi/off-inhand-right.png b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertmedical.rsi/off-inhand-right.png deleted file mode 100644 index 67f7494249..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertmedical.rsi/off-inhand-right.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertmedical.rsi/on-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertmedical.rsi/on-equipped-HELMET-vox.png new file mode 100644 index 0000000000..d25030e13a Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertmedical.rsi/on-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertmedical.rsi/on-equipped-HELMET.png b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertmedical.rsi/on-equipped-HELMET.png index 8f01538a7a..486ae09969 100644 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertmedical.rsi/on-equipped-HELMET.png and b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertmedical.rsi/on-equipped-HELMET.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertmedical.rsi/on-inhand-left.png b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertmedical.rsi/on-inhand-left.png deleted file mode 100644 index 4eaae604cd..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertmedical.rsi/on-inhand-left.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertmedical.rsi/on-inhand-right.png b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertmedical.rsi/on-inhand-right.png deleted file mode 100644 index f5f2f5c6f7..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertmedical.rsi/on-inhand-right.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertsecurity.rsi/meta.json b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertsecurity.rsi/meta.json index fe9f38cad4..bcd0d726dc 100644 --- a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertsecurity.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertsecurity.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from paradisestation at commit https://github.com/ParadiseSS13/Paradise/commit/f09526480788c2e18fff8c16c4318fd6b4272c10 inhands by peptide", + "copyright": "Taken from paradisestation at commit https://github.com/ParadiseSS13/Paradise/commit/f09526480788c2e18fff8c16c4318fd6b4272c10. Vox states by Flareguy for Space Station 14", "size": { "x": 32, "y": 32 @@ -18,11 +18,7 @@ "directions": 4 }, { - "name": "off-inhand-left", - "directions": 4 - }, - { - "name": "off-inhand-right", + "name": "off-equipped-HELMET-vox", "directions": 4 }, { @@ -30,11 +26,7 @@ "directions": 4 }, { - "name": "on-inhand-left", - "directions": 4 - }, - { - "name": "on-inhand-right", + "name": "on-equipped-HELMET-vox", "directions": 4 }, { diff --git a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertsecurity.rsi/off-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertsecurity.rsi/off-equipped-HELMET-vox.png new file mode 100644 index 0000000000..d300533c20 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertsecurity.rsi/off-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertsecurity.rsi/off-inhand-left.png b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertsecurity.rsi/off-inhand-left.png deleted file mode 100644 index c7b53126d4..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertsecurity.rsi/off-inhand-left.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertsecurity.rsi/off-inhand-right.png b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertsecurity.rsi/off-inhand-right.png deleted file mode 100644 index b6be1feebb..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertsecurity.rsi/off-inhand-right.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertsecurity.rsi/on-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertsecurity.rsi/on-equipped-HELMET-vox.png new file mode 100644 index 0000000000..a9b1843872 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertsecurity.rsi/on-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertsecurity.rsi/on-inhand-left.png b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertsecurity.rsi/on-inhand-left.png deleted file mode 100644 index 946a56c89a..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertsecurity.rsi/on-inhand-left.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertsecurity.rsi/on-inhand-right.png b/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertsecurity.rsi/on-inhand-right.png deleted file mode 100644 index e490de88da..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/ERThelmets/ertsecurity.rsi/on-inhand-right.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/atmospherics.rsi/equipped-head-light-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/atmospherics.rsi/equipped-head-light-vox.png new file mode 100644 index 0000000000..879ce40a74 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/atmospherics.rsi/equipped-head-light-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/atmospherics.rsi/equipped-head-unshaded-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/atmospherics.rsi/equipped-head-unshaded-vox.png new file mode 100644 index 0000000000..40f2eca45a Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/atmospherics.rsi/equipped-head-unshaded-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/atmospherics.rsi/equipped-head-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/atmospherics.rsi/equipped-head-vox.png new file mode 100644 index 0000000000..f61a157050 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/atmospherics.rsi/equipped-head-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/atmospherics.rsi/inhand-left-light.png b/Resources/Textures/Clothing/Head/Hardsuits/atmospherics.rsi/inhand-left-light.png deleted file mode 100644 index 9ee768d5ee..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/atmospherics.rsi/inhand-left-light.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/atmospherics.rsi/inhand-left-unshaded.png b/Resources/Textures/Clothing/Head/Hardsuits/atmospherics.rsi/inhand-left-unshaded.png deleted file mode 100644 index 8a7835b20d..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/atmospherics.rsi/inhand-left-unshaded.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/atmospherics.rsi/inhand-left.png b/Resources/Textures/Clothing/Head/Hardsuits/atmospherics.rsi/inhand-left.png deleted file mode 100644 index e9ba55de1e..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/atmospherics.rsi/inhand-left.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/atmospherics.rsi/inhand-right-light.png b/Resources/Textures/Clothing/Head/Hardsuits/atmospherics.rsi/inhand-right-light.png deleted file mode 100644 index 2990605237..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/atmospherics.rsi/inhand-right-light.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/atmospherics.rsi/inhand-right-unshaded.png b/Resources/Textures/Clothing/Head/Hardsuits/atmospherics.rsi/inhand-right-unshaded.png deleted file mode 100644 index 6c80725c0a..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/atmospherics.rsi/inhand-right-unshaded.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/atmospherics.rsi/inhand-right.png b/Resources/Textures/Clothing/Head/Hardsuits/atmospherics.rsi/inhand-right.png deleted file mode 100644 index 45d73f73df..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/atmospherics.rsi/inhand-right.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/atmospherics.rsi/meta.json b/Resources/Textures/Clothing/Head/Hardsuits/atmospherics.rsi/meta.json index 93d9fece1d..c24aa39b80 100644 --- a/Resources/Textures/Clothing/Head/Hardsuits/atmospherics.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hardsuits/atmospherics.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Vox states made by Flareguy for SS14 | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d", "size": { "x": 32, "y": 32 @@ -32,27 +32,15 @@ "directions": 4 }, { - "name": "inhand-left", + "name": "equipped-head-unshaded-vox", "directions": 4 }, { - "name": "inhand-left-unshaded", + "name": "equipped-head-vox", "directions": 4 }, { - "name": "inhand-left-light", - "directions": 4 - }, - { - "name": "inhand-right", - "directions": 4 - }, - { - "name": "inhand-right-unshaded", - "directions": 4 - }, - { - "name": "inhand-right-light", + "name": "equipped-head-light-vox", "directions": 4 }, { diff --git a/Resources/Textures/Clothing/Head/Hardsuits/capspace.rsi/meta.json b/Resources/Textures/Clothing/Head/Hardsuits/capspace.rsi/meta.json index cc8edd3819..1ccf847b3e 100644 --- a/Resources/Textures/Clothing/Head/Hardsuits/capspace.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hardsuits/capspace.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e, modified by Emisse for SS14", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e, modified by Emisse for SS14. Vox states by Flareguy for SS14", "size": { "x": 32, "y": 32 @@ -18,11 +18,7 @@ "directions": 4 }, { - "name": "off-inhand-left", - "directions": 4 - }, - { - "name": "off-inhand-right", + "name": "off-equipped-HELMET-vox", "directions": 4 }, { @@ -30,11 +26,7 @@ "directions": 4 }, { - "name": "on-inhand-left", - "directions": 4 - }, - { - "name": "on-inhand-right", + "name": "on-equipped-HELMET-vox", "directions": 4 }, { diff --git a/Resources/Textures/Clothing/Head/Hardsuits/capspace.rsi/off-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/capspace.rsi/off-equipped-HELMET-vox.png new file mode 100644 index 0000000000..f02e33c658 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/capspace.rsi/off-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/capspace.rsi/off-inhand-left.png b/Resources/Textures/Clothing/Head/Hardsuits/capspace.rsi/off-inhand-left.png deleted file mode 100644 index 5216ba5dd9..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/capspace.rsi/off-inhand-left.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/capspace.rsi/off-inhand-right.png b/Resources/Textures/Clothing/Head/Hardsuits/capspace.rsi/off-inhand-right.png deleted file mode 100644 index 2ffe013c12..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/capspace.rsi/off-inhand-right.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/capspace.rsi/on-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/capspace.rsi/on-equipped-HELMET-vox.png new file mode 100644 index 0000000000..9a0101f09a Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/capspace.rsi/on-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/capspace.rsi/on-inhand-left.png b/Resources/Textures/Clothing/Head/Hardsuits/capspace.rsi/on-inhand-left.png deleted file mode 100644 index 6a5d075d80..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/capspace.rsi/on-inhand-left.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/capspace.rsi/on-inhand-right.png b/Resources/Textures/Clothing/Head/Hardsuits/capspace.rsi/on-inhand-right.png deleted file mode 100644 index 25796fc014..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/capspace.rsi/on-inhand-right.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/equipped-head-unshaded-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/equipped-head-unshaded-vox.png new file mode 100644 index 0000000000..40d1f9a0c6 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/equipped-head-unshaded-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/equipped-head-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/equipped-head-vox.png new file mode 100644 index 0000000000..cc2fa16917 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/equipped-head-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/inhand-left-unshaded.png b/Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/inhand-left-unshaded.png deleted file mode 100644 index 60ca0efa13..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/inhand-left-unshaded.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/inhand-left.png b/Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/inhand-left.png deleted file mode 100644 index 43f27a890f..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/inhand-left.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/inhand-right-unshaded.png b/Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/inhand-right-unshaded.png deleted file mode 100644 index 118914c8d3..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/inhand-right-unshaded.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/inhand-right.png b/Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/inhand-right.png deleted file mode 100644 index 602020b276..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/inhand-right.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/meta.json b/Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/meta.json index 16f509fa99..1df9c2ddbf 100644 --- a/Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Made by EmoGarbage404", + "copyright": "Made by EmoGarbage404. Vox states by Flareguy for SS14", "size": { "x": 32, "y": 32 @@ -28,19 +28,11 @@ "directions": 4 }, { - "name": "inhand-left", + "name": "equipped-head-vox", "directions": 4 }, { - "name": "inhand-left-unshaded", - "directions": 4 - }, - { - "name": "inhand-right", - "directions": 4 - }, - { - "name": "inhand-right-unshaded", + "name": "equipped-head-unshaded-vox", "directions": 4 } ] diff --git a/Resources/Textures/Clothing/Head/Hardsuits/clown.rsi/meta.json b/Resources/Textures/Clothing/Head/Hardsuits/clown.rsi/meta.json index 5a77b01e50..5956a9b0f4 100644 --- a/Resources/Textures/Clothing/Head/Hardsuits/clown.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hardsuits/clown.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e, modified by brainfood1183 (github)", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e, modified by brainfood1183 (github). Vox state by Flareguy for SS14", "size": { "x": 32, "y": 32 @@ -20,6 +20,14 @@ { "name": "on-equipped-HELMET", "directions": 4 + }, + { + "name": "off-equipped-HELMET-vox", + "directions": 4 + }, + { + "name": "on-equipped-HELMET-vox", + "directions": 4 } ] } diff --git a/Resources/Textures/Clothing/Head/Hardsuits/clown.rsi/off-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/clown.rsi/off-equipped-HELMET-vox.png new file mode 100644 index 0000000000..a974f19a2b Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/clown.rsi/off-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/clown.rsi/on-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/clown.rsi/on-equipped-HELMET-vox.png new file mode 100644 index 0000000000..88ee1fbad6 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/clown.rsi/on-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/cybersun.rsi/equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/cybersun.rsi/equipped-HELMET-vox.png new file mode 100644 index 0000000000..02363a7faa Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/cybersun.rsi/equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/cybersun.rsi/inhand-left.png b/Resources/Textures/Clothing/Head/Hardsuits/cybersun.rsi/inhand-left.png deleted file mode 100644 index ee1c8d1c4b..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/cybersun.rsi/inhand-left.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/cybersun.rsi/inhand-right.png b/Resources/Textures/Clothing/Head/Hardsuits/cybersun.rsi/inhand-right.png deleted file mode 100644 index fa19eea1f7..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/cybersun.rsi/inhand-right.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/cybersun.rsi/meta.json b/Resources/Textures/Clothing/Head/Hardsuits/cybersun.rsi/meta.json index f6b8613891..0c1323d3f0 100644 --- a/Resources/Textures/Clothing/Head/Hardsuits/cybersun.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hardsuits/cybersun.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from Paradise SS13 at commit https://github.com/ParadiseSS13/Paradise/commit/a67c929b7394f78e7787114457ba42f4df6cc3a1", + "copyright": "Taken from Paradise SS13 at commit https://github.com/ParadiseSS13/Paradise/commit/a67c929b7394f78e7787114457ba42f4df6cc3a1. Vox state by Flareguy for SS14", "size": { "x": 32, "y": 32 @@ -15,16 +15,12 @@ "directions": 4 }, { - "name": "inhand-left", - "directions": 4 - }, - { - "name": "inhand-right", + "name": "equipped-HELMET-harpy", "directions": 4 }, { - "name": "equipped-HELMET-harpy", - "directions": 4 + "name": "equipped-HELMET-vox", + "directions": 4 } ] } diff --git a/Resources/Textures/Clothing/Head/Hardsuits/deathsquad.rsi/equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/deathsquad.rsi/equipped-HELMET-vox.png new file mode 100644 index 0000000000..1a7ed3b866 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/deathsquad.rsi/equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/deathsquad.rsi/inhand-left.png b/Resources/Textures/Clothing/Head/Hardsuits/deathsquad.rsi/inhand-left.png deleted file mode 100644 index 6be2c34d76..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/deathsquad.rsi/inhand-left.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/deathsquad.rsi/inhand-right.png b/Resources/Textures/Clothing/Head/Hardsuits/deathsquad.rsi/inhand-right.png deleted file mode 100644 index e3c056ada0..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/deathsquad.rsi/inhand-right.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/deathsquad.rsi/meta.json b/Resources/Textures/Clothing/Head/Hardsuits/deathsquad.rsi/meta.json index 83e8e1a788..782ce631af 100644 --- a/Resources/Textures/Clothing/Head/Hardsuits/deathsquad.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hardsuits/deathsquad.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e | vulpkanin versions taken from https://github.com/SPLURT-Station/S.P.L.U.R.T-Station-13/commit/091d9ec00f186052b87bd65125e896f78faefe38 and edited by Floofers", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e | vulpkanin versions taken from https://github.com/SPLURT-Station/S.P.L.U.R.T-Station-13/commit/091d9ec00f186052b87bd65125e896f78faefe38 and edited by Floofers | Vox state by Flareguy", "size": { "x": 32, "y": 32 @@ -15,15 +15,11 @@ "directions": 4 }, { - "name": "inhand-left", - "directions": 4 - }, - { - "name": "inhand-right", + "name": "equipped-HELMET-vulpkanin", "directions": 4 }, { - "name": "equipped-HELMET-vulpkanin", + "name": "equipped-HELMET-vox", "directions": 4 } ] diff --git a/Resources/Textures/Clothing/Head/Hardsuits/engineering-white.rsi/meta.json b/Resources/Textures/Clothing/Head/Hardsuits/engineering-white.rsi/meta.json index 1ac1729cd2..7dbf66ad67 100644 --- a/Resources/Textures/Clothing/Head/Hardsuits/engineering-white.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hardsuits/engineering-white.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e | vulpkanin versions taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Vox states by Flareguy for Space Station 14 | vulpkanin versions taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d | Vox states by Flareguy for Space Station 14", "size": { "x": 32, "y": 32 @@ -18,34 +18,26 @@ "directions": 4 }, { - "name": "off-inhand-left", - "directions": 4 - }, - { - "name": "off-inhand-right", - "directions": 4 - }, - { - "name": "on-equipped-HELMET", - "directions": 4 - }, - { - "name": "on-inhand-left", - "directions": 4 + "name": "on-equipped-HELMET", + "directions": 4 }, { - "name": "on-inhand-right", + "name": "on-equipped-HELMET-vulpkanin", "directions": 4 }, { - "name": "on-equipped-HELMET-vulpkanin", + "name": "off-equipped-HELMET-vulpkanin", "directions": 4 }, { - "name": "off-equipped-HELMET-vulpkanin", + "name": "on-equipped-HELMET-vox", "directions": 4 - }, - { + }, + { + "name": "off-equipped-HELMET-vox", + "directions": 4 + }, + { "name": "on-equipped-HELMET-harpy", "directions": 4 }, diff --git a/Resources/Textures/Clothing/Head/Hardsuits/engineering-white.rsi/off-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/engineering-white.rsi/off-equipped-HELMET-vox.png new file mode 100644 index 0000000000..683d6ce97d Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/engineering-white.rsi/off-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/engineering-white.rsi/off-inhand-left.png b/Resources/Textures/Clothing/Head/Hardsuits/engineering-white.rsi/off-inhand-left.png deleted file mode 100644 index f7375fd600..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/engineering-white.rsi/off-inhand-left.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/engineering-white.rsi/off-inhand-right.png b/Resources/Textures/Clothing/Head/Hardsuits/engineering-white.rsi/off-inhand-right.png deleted file mode 100644 index c2adc46cdb..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/engineering-white.rsi/off-inhand-right.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/engineering-white.rsi/on-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/engineering-white.rsi/on-equipped-HELMET-vox.png new file mode 100644 index 0000000000..574b3d985b Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/engineering-white.rsi/on-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/engineering-white.rsi/on-inhand-left.png b/Resources/Textures/Clothing/Head/Hardsuits/engineering-white.rsi/on-inhand-left.png deleted file mode 100644 index 7c6e7831c8..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/engineering-white.rsi/on-inhand-left.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/engineering-white.rsi/on-inhand-right.png b/Resources/Textures/Clothing/Head/Hardsuits/engineering-white.rsi/on-inhand-right.png deleted file mode 100644 index 5ee7bc104c..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/engineering-white.rsi/on-inhand-right.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/engineering.rsi/meta.json b/Resources/Textures/Clothing/Head/Hardsuits/engineering.rsi/meta.json index 4dae2c687d..f20b167c36 100644 --- a/Resources/Textures/Clothing/Head/Hardsuits/engineering.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hardsuits/engineering.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e | vulpkanin versions taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Vox states by Flareguy for Space Station 14 | vulpkanin versions taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d | Vox states by Flareguy for Space Station 14", "size": { "x": 32, "y": 32 @@ -18,25 +18,17 @@ "directions": 4 }, { - "name": "off-inhand-left", + "name": "off-equipped-HELMET-vox", "directions": 4 }, { - "name": "off-inhand-right", - "directions": 4 + "name": "on-equipped-HELMET-vox", + "directions": 4 }, { "name": "on-equipped-HELMET", "directions": 4 }, - { - "name": "on-inhand-left", - "directions": 4 - }, - { - "name": "on-inhand-right", - "directions": 4 - }, { "name": "on-equipped-HELMET-vulpkanin", "directions": 4 diff --git a/Resources/Textures/Clothing/Head/Hardsuits/engineering.rsi/off-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/engineering.rsi/off-equipped-HELMET-vox.png new file mode 100644 index 0000000000..900ed95dc0 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/engineering.rsi/off-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/engineering.rsi/off-inhand-left.png b/Resources/Textures/Clothing/Head/Hardsuits/engineering.rsi/off-inhand-left.png deleted file mode 100644 index cafca9a938..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/engineering.rsi/off-inhand-left.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/engineering.rsi/off-inhand-right.png b/Resources/Textures/Clothing/Head/Hardsuits/engineering.rsi/off-inhand-right.png deleted file mode 100644 index 9645b59e2d..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/engineering.rsi/off-inhand-right.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/engineering.rsi/on-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/engineering.rsi/on-equipped-HELMET-vox.png new file mode 100644 index 0000000000..1a40bc3a26 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/engineering.rsi/on-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/engineering.rsi/on-inhand-left.png b/Resources/Textures/Clothing/Head/Hardsuits/engineering.rsi/on-inhand-left.png deleted file mode 100644 index bff16d72c4..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/engineering.rsi/on-inhand-left.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/engineering.rsi/on-inhand-right.png b/Resources/Textures/Clothing/Head/Hardsuits/engineering.rsi/on-inhand-right.png deleted file mode 100644 index 355977d540..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/engineering.rsi/on-inhand-right.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/luxury.rsi/meta.json b/Resources/Textures/Clothing/Head/Hardsuits/luxury.rsi/meta.json index d431123de2..d80c50a1c4 100644 --- a/Resources/Textures/Clothing/Head/Hardsuits/luxury.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hardsuits/luxury.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Texture edit from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Texture edit from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Vox states by Flareguy for Space Station 14", "size": { "x": 32, "y": 32 @@ -17,24 +17,16 @@ "name": "off-equipped-HELMET", "directions": 4 }, - { - "name": "off-inhand-left", - "directions": 4 - }, - { - "name": "off-inhand-right", - "directions": 4 - }, { "name": "on-equipped-HELMET", "directions": 4 }, { - "name": "on-inhand-left", + "name": "off-equipped-HELMET-vox", "directions": 4 }, { - "name": "on-inhand-right", + "name": "on-equipped-HELMET-vox", "directions": 4 } ] diff --git a/Resources/Textures/Clothing/Head/Hardsuits/luxury.rsi/off-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/luxury.rsi/off-equipped-HELMET-vox.png new file mode 100644 index 0000000000..efacfba741 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/luxury.rsi/off-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/luxury.rsi/off-inhand-left.png b/Resources/Textures/Clothing/Head/Hardsuits/luxury.rsi/off-inhand-left.png deleted file mode 100644 index b26ae9a31f..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/luxury.rsi/off-inhand-left.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/luxury.rsi/off-inhand-right.png b/Resources/Textures/Clothing/Head/Hardsuits/luxury.rsi/off-inhand-right.png deleted file mode 100644 index fc0edd4a97..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/luxury.rsi/off-inhand-right.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/luxury.rsi/on-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/luxury.rsi/on-equipped-HELMET-vox.png new file mode 100644 index 0000000000..09d835e9b9 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/luxury.rsi/on-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/luxury.rsi/on-inhand-left.png b/Resources/Textures/Clothing/Head/Hardsuits/luxury.rsi/on-inhand-left.png deleted file mode 100644 index 3f5034da6c..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/luxury.rsi/on-inhand-left.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/luxury.rsi/on-inhand-right.png b/Resources/Textures/Clothing/Head/Hardsuits/luxury.rsi/on-inhand-right.png deleted file mode 100644 index 45776a03b8..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/luxury.rsi/on-inhand-right.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/maxim.rsi/equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/maxim.rsi/equipped-HELMET-vox.png new file mode 100644 index 0000000000..11de602a6e Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/maxim.rsi/equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/maxim.rsi/inhand-left.png b/Resources/Textures/Clothing/Head/Hardsuits/maxim.rsi/inhand-left.png deleted file mode 100644 index 81e8a07fdd..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/maxim.rsi/inhand-left.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/maxim.rsi/inhand-right.png b/Resources/Textures/Clothing/Head/Hardsuits/maxim.rsi/inhand-right.png deleted file mode 100644 index e10648fd9b..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/maxim.rsi/inhand-right.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/maxim.rsi/meta.json b/Resources/Textures/Clothing/Head/Hardsuits/maxim.rsi/meta.json index 2d5aaae8c7..2a085063a4 100644 --- a/Resources/Textures/Clothing/Head/Hardsuits/maxim.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hardsuits/maxim.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/6b3f58d7de4d4e374282819a7001eaa9bde1676d", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/6b3f58d7de4d4e374282819a7001eaa9bde1676d. Vox state by Flareguy for Space Station 14", "size": { "x": 32, "y": 32 @@ -15,11 +15,7 @@ "directions": 4 }, { - "name": "inhand-left", - "directions": 4 - }, - { - "name": "inhand-right", + "name": "equipped-HELMET-vox", "directions": 4 } ] diff --git a/Resources/Textures/Clothing/Head/Hardsuits/medical.rsi/meta.json b/Resources/Textures/Clothing/Head/Hardsuits/medical.rsi/meta.json index d168108fdf..7954c0611e 100644 --- a/Resources/Textures/Clothing/Head/Hardsuits/medical.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hardsuits/medical.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Vox states by Flareguy for Space Station 14 | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d | Vox states by Flareguy for Space Station 14", "size": { "x": 32, "y": 32 @@ -14,28 +14,20 @@ "name": "icon-flash" }, { - "name": "off-equipped-HELMET", - "directions": 4 - }, - { - "name": "off-inhand-left", - "directions": 4 - }, + "name": "on-equipped-HELMET", + "directions": 4 + }, { - "name": "off-inhand-right", - "directions": 4 - }, - { - "name": "on-equipped-HELMET", + "name": "off-equipped-HELMET", "directions": 4 }, { - "name": "on-inhand-left", + "name": "on-equipped-HELMET-vox", "directions": 4 }, { - "name": "on-inhand-right", - "directions": 4 + "name": "off-equipped-HELMET-vox", + "directions": 4 }, { "name": "on-equipped-HELMET-vulpkanin", diff --git a/Resources/Textures/Clothing/Head/Hardsuits/medical.rsi/off-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/medical.rsi/off-equipped-HELMET-vox.png new file mode 100644 index 0000000000..1952c3152d Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/medical.rsi/off-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/medical.rsi/off-inhand-left.png b/Resources/Textures/Clothing/Head/Hardsuits/medical.rsi/off-inhand-left.png deleted file mode 100644 index 8fbfbbf6c6..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/medical.rsi/off-inhand-left.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/medical.rsi/off-inhand-right.png b/Resources/Textures/Clothing/Head/Hardsuits/medical.rsi/off-inhand-right.png deleted file mode 100644 index 035c6fc061..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/medical.rsi/off-inhand-right.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/medical.rsi/on-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/medical.rsi/on-equipped-HELMET-vox.png new file mode 100644 index 0000000000..3011afac41 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/medical.rsi/on-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/medical.rsi/on-inhand-left.png b/Resources/Textures/Clothing/Head/Hardsuits/medical.rsi/on-inhand-left.png deleted file mode 100644 index 85cbfb321a..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/medical.rsi/on-inhand-left.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/medical.rsi/on-inhand-right.png b/Resources/Textures/Clothing/Head/Hardsuits/medical.rsi/on-inhand-right.png deleted file mode 100644 index c876fefb6c..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/medical.rsi/on-inhand-right.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/piratecaptainhelm.rsi/equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/piratecaptainhelm.rsi/equipped-HELMET-vox.png new file mode 100644 index 0000000000..8d0cd093f3 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/piratecaptainhelm.rsi/equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/piratecaptainhelm.rsi/meta.json b/Resources/Textures/Clothing/Head/Hardsuits/piratecaptainhelm.rsi/meta.json index 2244815b78..48f0cbb615 100644 --- a/Resources/Textures/Clothing/Head/Hardsuits/piratecaptainhelm.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hardsuits/piratecaptainhelm.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Made by brainfood1183 (github) for ss14", + "copyright": "Made by brainfood1183 (github) for ss14. Vox states by Flareguy for Space Station 14", "size": { "x": 32, "y": 32 @@ -13,6 +13,10 @@ { "name": "equipped-HELMET", "directions": 4 + }, + { + "name": "equipped-HELMET-vox", + "directions": 4 } ] } diff --git a/Resources/Textures/Clothing/Head/Hardsuits/pirateeva.rsi/equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/pirateeva.rsi/equipped-HELMET-vox.png new file mode 100644 index 0000000000..663733cfe8 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/pirateeva.rsi/equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/pirateeva.rsi/meta.json b/Resources/Textures/Clothing/Head/Hardsuits/pirateeva.rsi/meta.json index 2244815b78..5bb20fd0fb 100644 --- a/Resources/Textures/Clothing/Head/Hardsuits/pirateeva.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hardsuits/pirateeva.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Made by brainfood1183 (github) for ss14", + "copyright": "Made by brainfood1183 (github) for ss14. Vox state by Flareguy for Space Station 14", "size": { "x": 32, "y": 32 @@ -13,6 +13,10 @@ { "name": "equipped-HELMET", "directions": 4 + }, + { + "name": "equipped-HELMET-vox", + "directions": 4 } ] } diff --git a/Resources/Textures/Clothing/Head/Hardsuits/rd.rsi/meta.json b/Resources/Textures/Clothing/Head/Hardsuits/rd.rsi/meta.json index a2c357d43f..db7f740811 100644 --- a/Resources/Textures/Clothing/Head/Hardsuits/rd.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hardsuits/rd.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e | vulpkanin versions taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Vox states made by Flareguy for SS14 | vulpkanin versions taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d | Vox states by Flareguy for Space Station 14", "size": { "x": 32, "y": 32 @@ -14,28 +14,20 @@ "name": "icon-flash" }, { - "name": "off-equipped-HELMET", - "directions": 4 - }, - { - "name": "off-inhand-left", - "directions": 4 + "name": "on-equipped-HELMET", + "directions": 4 }, { - "name": "off-inhand-right", - "directions": 4 - }, - { - "name": "on-equipped-HELMET", + "name": "off-equipped-HELMET", "directions": 4 }, { - "name": "on-inhand-left", + "name": "on-equipped-HELMET-vox", "directions": 4 }, { - "name": "on-inhand-right", - "directions": 4 + "name": "off-equipped-HELMET-vox", + "directions": 4 }, { "name": "on-equipped-HELMET-vulpkanin", diff --git a/Resources/Textures/Clothing/Head/Hardsuits/rd.rsi/off-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/rd.rsi/off-equipped-HELMET-vox.png new file mode 100644 index 0000000000..9f2f5c41df Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/rd.rsi/off-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/rd.rsi/off-inhand-left.png b/Resources/Textures/Clothing/Head/Hardsuits/rd.rsi/off-inhand-left.png deleted file mode 100644 index dc16b147b8..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/rd.rsi/off-inhand-left.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/rd.rsi/off-inhand-right.png b/Resources/Textures/Clothing/Head/Hardsuits/rd.rsi/off-inhand-right.png deleted file mode 100644 index e289c62a84..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/rd.rsi/off-inhand-right.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/rd.rsi/on-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/rd.rsi/on-equipped-HELMET-vox.png new file mode 100644 index 0000000000..b577d32feb Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/rd.rsi/on-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/rd.rsi/on-inhand-left.png b/Resources/Textures/Clothing/Head/Hardsuits/rd.rsi/on-inhand-left.png deleted file mode 100644 index b107272dda..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/rd.rsi/on-inhand-left.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/rd.rsi/on-inhand-right.png b/Resources/Textures/Clothing/Head/Hardsuits/rd.rsi/on-inhand-right.png deleted file mode 100644 index 59a3181afb..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/rd.rsi/on-inhand-right.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/salvage.rsi/meta.json b/Resources/Textures/Clothing/Head/Hardsuits/salvage.rsi/meta.json index d168108fdf..051b7e25ea 100644 --- a/Resources/Textures/Clothing/Head/Hardsuits/salvage.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hardsuits/salvage.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Vox states by Flareguy for Space Station 14 | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d | Vox states by Flareguy for Space Station 14", "size": { "x": 32, "y": 32 @@ -14,28 +14,20 @@ "name": "icon-flash" }, { - "name": "off-equipped-HELMET", - "directions": 4 - }, - { - "name": "off-inhand-left", - "directions": 4 + "name": "on-equipped-HELMET", + "directions": 4 }, { - "name": "off-inhand-right", - "directions": 4 - }, - { - "name": "on-equipped-HELMET", + "name": "off-equipped-HELMET", "directions": 4 }, { - "name": "on-inhand-left", + "name": "on-equipped-HELMET-vox", "directions": 4 }, { - "name": "on-inhand-right", - "directions": 4 + "name": "off-equipped-HELMET-vox", + "directions": 4 }, { "name": "on-equipped-HELMET-vulpkanin", diff --git a/Resources/Textures/Clothing/Head/Hardsuits/salvage.rsi/off-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/salvage.rsi/off-equipped-HELMET-vox.png new file mode 100644 index 0000000000..c3f24c8b34 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/salvage.rsi/off-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/salvage.rsi/off-inhand-left.png b/Resources/Textures/Clothing/Head/Hardsuits/salvage.rsi/off-inhand-left.png deleted file mode 100644 index b11a533c26..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/salvage.rsi/off-inhand-left.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/salvage.rsi/off-inhand-right.png b/Resources/Textures/Clothing/Head/Hardsuits/salvage.rsi/off-inhand-right.png deleted file mode 100644 index b9c9df2350..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/salvage.rsi/off-inhand-right.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/salvage.rsi/on-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/salvage.rsi/on-equipped-HELMET-vox.png new file mode 100644 index 0000000000..007852fc10 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/salvage.rsi/on-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/salvage.rsi/on-inhand-left.png b/Resources/Textures/Clothing/Head/Hardsuits/salvage.rsi/on-inhand-left.png deleted file mode 100644 index 12d13f0e18..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/salvage.rsi/on-inhand-left.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/salvage.rsi/on-inhand-right.png b/Resources/Textures/Clothing/Head/Hardsuits/salvage.rsi/on-inhand-right.png deleted file mode 100644 index 141dc492a4..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/salvage.rsi/on-inhand-right.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/santahelm.rsi/equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/santahelm.rsi/equipped-HELMET-vox.png new file mode 100644 index 0000000000..025e702667 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/santahelm.rsi/equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/santahelm.rsi/inhand-left.png b/Resources/Textures/Clothing/Head/Hardsuits/santahelm.rsi/inhand-left.png deleted file mode 100644 index c0c5863a3b..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/santahelm.rsi/inhand-left.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/santahelm.rsi/inhand-right.png b/Resources/Textures/Clothing/Head/Hardsuits/santahelm.rsi/inhand-right.png deleted file mode 100644 index f29877bf5a..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/santahelm.rsi/inhand-right.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/santahelm.rsi/meta.json b/Resources/Textures/Clothing/Head/Hardsuits/santahelm.rsi/meta.json index c53de83901..afc9f219ac 100644 --- a/Resources/Textures/Clothing/Head/Hardsuits/santahelm.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hardsuits/santahelm.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Edited by StanTheCarpenter. Originally taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Edited by StanTheCarpenter. Originally taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Vox states by Flareguy for Space Station 14", "size": { "x": 32, "y": 32 @@ -15,11 +15,7 @@ "directions": 4 }, { - "name": "inhand-left", - "directions": 4 - }, - { - "name": "inhand-right", + "name": "equipped-HELMET-vox", "directions": 4 } ] diff --git a/Resources/Textures/Clothing/Head/Hardsuits/security-red.rsi/meta.json b/Resources/Textures/Clothing/Head/Hardsuits/security-red.rsi/meta.json index 40c5c72b11..f7be4bb5d4 100644 --- a/Resources/Textures/Clothing/Head/Hardsuits/security-red.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hardsuits/security-red.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e | vulpkanin versions taken from https://github.com/SPLURT-Station/S.P.L.U.R.T-Station-13/commit/091d9ec00f186052b87bd65125e896f78faefe38 and edited by Floofers", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Vox states by Flareguy for Space Station 14 | vulpkanin versions taken from https://github.com/SPLURT-Station/S.P.L.U.R.T-Station-13/commit/091d9ec00f186052b87bd65125e896f78faefe38 and edited by Floofers", "size": { "x": 32, "y": 32 @@ -14,28 +14,20 @@ "name": "icon-flash" }, { - "name": "off-equipped-HELMET", - "directions": 4 - }, - { - "name": "off-inhand-left", - "directions": 4 + "name": "on-equipped-HELMET", + "directions": 4 }, { - "name": "off-inhand-right", - "directions": 4 - }, - { - "name": "on-equipped-HELMET", + "name": "off-equipped-HELMET", "directions": 4 }, { - "name": "on-inhand-left", + "name": "on-equipped-HELMET-vox", "directions": 4 }, { - "name": "on-inhand-right", - "directions": 4 + "name": "off-equipped-HELMET-vox", + "directions": 4 }, { "name": "on-equipped-HELMET-vulpkanin", diff --git a/Resources/Textures/Clothing/Head/Hardsuits/security-red.rsi/off-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/security-red.rsi/off-equipped-HELMET-vox.png new file mode 100644 index 0000000000..6954a4e1a8 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/security-red.rsi/off-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/security-red.rsi/off-inhand-left.png b/Resources/Textures/Clothing/Head/Hardsuits/security-red.rsi/off-inhand-left.png deleted file mode 100644 index c81cf30dc9..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/security-red.rsi/off-inhand-left.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/security-red.rsi/off-inhand-right.png b/Resources/Textures/Clothing/Head/Hardsuits/security-red.rsi/off-inhand-right.png deleted file mode 100644 index f5b449d7b9..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/security-red.rsi/off-inhand-right.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/security-red.rsi/on-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/security-red.rsi/on-equipped-HELMET-vox.png new file mode 100644 index 0000000000..b48bb710d9 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/security-red.rsi/on-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/security-red.rsi/on-inhand-left.png b/Resources/Textures/Clothing/Head/Hardsuits/security-red.rsi/on-inhand-left.png deleted file mode 100644 index 32f699cb47..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/security-red.rsi/on-inhand-left.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/security-red.rsi/on-inhand-right.png b/Resources/Textures/Clothing/Head/Hardsuits/security-red.rsi/on-inhand-right.png deleted file mode 100644 index 71d2a09325..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/security-red.rsi/on-inhand-right.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/security-warden.rsi/meta.json b/Resources/Textures/Clothing/Head/Hardsuits/security-warden.rsi/meta.json index 14121523ca..3d94d382c3 100644 --- a/Resources/Textures/Clothing/Head/Hardsuits/security-warden.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hardsuits/security-warden.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "sprite made by Gtheglorious based on the sprite made by Alekshhh for SS14 | vulpkanin versions made by Floofers", + "copyright": "sprite made by Gtheglorious based on the sprite made by Alekshhh for SS14 | vulpkanin versions made by Floofers. Vox states by Flareguy for Space Station 14", "size": { "x": 32, "y": 32 @@ -28,6 +28,14 @@ { "name": "off-equipped-HELMET-vulpkanin", "directions": 4 + }, + { + "name": "off-equipped-HELMET-vox", + "directions": 4 + }, + { + "name": "on-equipped-HELMET-vox", + "directions": 4 } ] } diff --git a/Resources/Textures/Clothing/Head/Hardsuits/security-warden.rsi/off-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/security-warden.rsi/off-equipped-HELMET-vox.png new file mode 100644 index 0000000000..2a3619db8e Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/security-warden.rsi/off-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/security-warden.rsi/on-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/security-warden.rsi/on-equipped-HELMET-vox.png new file mode 100644 index 0000000000..5b9e2b19ad Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/security-warden.rsi/on-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/security.rsi/meta.json b/Resources/Textures/Clothing/Head/Hardsuits/security.rsi/meta.json index a2e10d4300..24f2a06e37 100644 --- a/Resources/Textures/Clothing/Head/Hardsuits/security.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hardsuits/security.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Sprite made by Gtheglorious based on the sprite taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e, | vulpkanin versions taken from https://github.com/SPLURT-Station/S.P.L.U.R.T-Station-13/commit/091d9ec00f186052b87bd65125e896f78faefe38 and edited by Floofers", + "copyright": "Sprite made by Gtheglorious based on the sprite taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Vox states by Flareguy for Space Station 14 | vulpkanin versions taken from https://github.com/SPLURT-Station/S.P.L.U.R.T-Station-13/commit/091d9ec00f186052b87bd65125e896f78faefe38 and edited by Floofers", "size": { "x": 32, "y": 32 @@ -14,28 +14,20 @@ "name": "icon-flash" }, { - "name": "off-equipped-HELMET", - "directions": 4 - }, - { - "name": "off-inhand-left", - "directions": 4 + "name": "on-equipped-HELMET", + "directions": 4 }, { - "name": "off-inhand-right", - "directions": 4 - }, - { - "name": "on-equipped-HELMET", + "name": "off-equipped-HELMET", "directions": 4 }, { - "name": "on-inhand-left", + "name": "on-equipped-HELMET-vox", "directions": 4 }, { - "name": "on-inhand-right", - "directions": 4 + "name": "off-equipped-HELMET-vox", + "directions": 4 }, { "name": "on-equipped-HELMET-vulpkanin", diff --git a/Resources/Textures/Clothing/Head/Hardsuits/security.rsi/off-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/security.rsi/off-equipped-HELMET-vox.png new file mode 100644 index 0000000000..439b6c6ebc Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/security.rsi/off-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/security.rsi/off-inhand-left.png b/Resources/Textures/Clothing/Head/Hardsuits/security.rsi/off-inhand-left.png deleted file mode 100644 index 68329b89c1..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/security.rsi/off-inhand-left.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/security.rsi/off-inhand-right.png b/Resources/Textures/Clothing/Head/Hardsuits/security.rsi/off-inhand-right.png deleted file mode 100644 index 38cc8ca8a6..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/security.rsi/off-inhand-right.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/security.rsi/on-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/security.rsi/on-equipped-HELMET-vox.png new file mode 100644 index 0000000000..56bca1c618 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/security.rsi/on-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/security.rsi/on-inhand-left.png b/Resources/Textures/Clothing/Head/Hardsuits/security.rsi/on-inhand-left.png deleted file mode 100644 index 742af4cd00..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/security.rsi/on-inhand-left.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/security.rsi/on-inhand-right.png b/Resources/Textures/Clothing/Head/Hardsuits/security.rsi/on-inhand-right.png deleted file mode 100644 index 79f70ac121..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/security.rsi/on-inhand-right.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/spatiohelm.rsi/equipped-head-light-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/spatiohelm.rsi/equipped-head-light-vox.png new file mode 100644 index 0000000000..d0124f743b Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/spatiohelm.rsi/equipped-head-light-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/spatiohelm.rsi/equipped-head-unshaded-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/spatiohelm.rsi/equipped-head-unshaded-vox.png new file mode 100644 index 0000000000..4486bff562 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/spatiohelm.rsi/equipped-head-unshaded-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/spatiohelm.rsi/equipped-head-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/spatiohelm.rsi/equipped-head-vox.png new file mode 100644 index 0000000000..80a31d544f Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/spatiohelm.rsi/equipped-head-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/spatiohelm.rsi/inhand-left-light.png b/Resources/Textures/Clothing/Head/Hardsuits/spatiohelm.rsi/inhand-left-light.png deleted file mode 100644 index 7f1657d9e5..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/spatiohelm.rsi/inhand-left-light.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/spatiohelm.rsi/inhand-left-unshaded.png b/Resources/Textures/Clothing/Head/Hardsuits/spatiohelm.rsi/inhand-left-unshaded.png deleted file mode 100644 index fe450b8df1..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/spatiohelm.rsi/inhand-left-unshaded.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/spatiohelm.rsi/inhand-left.png b/Resources/Textures/Clothing/Head/Hardsuits/spatiohelm.rsi/inhand-left.png deleted file mode 100644 index 81cecd01c2..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/spatiohelm.rsi/inhand-left.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/spatiohelm.rsi/inhand-right-light.png b/Resources/Textures/Clothing/Head/Hardsuits/spatiohelm.rsi/inhand-right-light.png deleted file mode 100644 index 7749494c56..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/spatiohelm.rsi/inhand-right-light.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/spatiohelm.rsi/inhand-right-unshaded.png b/Resources/Textures/Clothing/Head/Hardsuits/spatiohelm.rsi/inhand-right-unshaded.png deleted file mode 100644 index be08f2c257..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/spatiohelm.rsi/inhand-right-unshaded.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/spatiohelm.rsi/inhand-right.png b/Resources/Textures/Clothing/Head/Hardsuits/spatiohelm.rsi/inhand-right.png deleted file mode 100644 index 2bbec0f04c..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/spatiohelm.rsi/inhand-right.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/spatiohelm.rsi/meta.json b/Resources/Textures/Clothing/Head/Hardsuits/spatiohelm.rsi/meta.json index 35a6a87bfd..64ba4dc5d6 100644 --- a/Resources/Textures/Clothing/Head/Hardsuits/spatiohelm.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hardsuits/spatiohelm.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Original by Emisse, modified by EmoGarbage404 | vulpkanin version made by Floofers", + "copyright": "Original by Emisse, modified by EmoGarbage404 | vulpkanin version made by Floofers. Vox states by Flareguy for SS14", "size": { "x": 32, @@ -33,27 +33,15 @@ "directions": 4 }, { - "name": "inhand-left", + "name": "equipped-head-unshaded-vox", "directions": 4 }, { - "name": "inhand-left-unshaded", + "name": "equipped-head-vox", "directions": 4 }, { - "name": "inhand-left-light", - "directions": 4 - }, - { - "name": "inhand-right", - "directions": 4 - }, - { - "name": "inhand-right-unshaded", - "directions": 4 - }, - { - "name": "inhand-right-light", + "name": "equipped-head-light-vox", "directions": 4 }, { @@ -61,4 +49,4 @@ "directions": 4 } ] -} \ No newline at end of file +} diff --git a/Resources/Textures/Clothing/Head/Hardsuits/syndicate.rsi/meta.json b/Resources/Textures/Clothing/Head/Hardsuits/syndicate.rsi/meta.json index e9de1ae57b..9b960ec32b 100644 --- a/Resources/Textures/Clothing/Head/Hardsuits/syndicate.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hardsuits/syndicate.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Vox states by Flareguy for Space Station 14", "size": { "x": 32, "y": 32 @@ -21,24 +21,16 @@ "name": "off-equipped-HELMET", "directions": 4 }, - { - "name": "off-inhand-left", - "directions": 4 - }, - { - "name": "off-inhand-right", - "directions": 4 - }, { "name": "on-equipped-HELMET", "directions": 4 }, { - "name": "on-inhand-left", + "name": "off-equipped-HELMET-vox", "directions": 4 }, { - "name": "on-inhand-right", + "name": "on-equipped-HELMET-vox", "directions": 4 } ] diff --git a/Resources/Textures/Clothing/Head/Hardsuits/syndicate.rsi/off-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/syndicate.rsi/off-equipped-HELMET-vox.png new file mode 100644 index 0000000000..eb2027c5fa Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/syndicate.rsi/off-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/syndicate.rsi/off-inhand-left.png b/Resources/Textures/Clothing/Head/Hardsuits/syndicate.rsi/off-inhand-left.png deleted file mode 100644 index 1b108753b3..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/syndicate.rsi/off-inhand-left.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/syndicate.rsi/off-inhand-right.png b/Resources/Textures/Clothing/Head/Hardsuits/syndicate.rsi/off-inhand-right.png deleted file mode 100644 index dc0500af77..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/syndicate.rsi/off-inhand-right.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/syndicate.rsi/on-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/syndicate.rsi/on-equipped-HELMET-vox.png new file mode 100644 index 0000000000..8804afa3d8 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/syndicate.rsi/on-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/syndicate.rsi/on-inhand-left.png b/Resources/Textures/Clothing/Head/Hardsuits/syndicate.rsi/on-inhand-left.png deleted file mode 100644 index c1350c8242..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/syndicate.rsi/on-inhand-left.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/syndicate.rsi/on-inhand-right.png b/Resources/Textures/Clothing/Head/Hardsuits/syndicate.rsi/on-inhand-right.png deleted file mode 100644 index db67f97b6f..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/syndicate.rsi/on-inhand-right.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/syndiecommander.rsi/meta.json b/Resources/Textures/Clothing/Head/Hardsuits/syndiecommander.rsi/meta.json index 038aabe605..6999f12991 100644 --- a/Resources/Textures/Clothing/Head/Hardsuits/syndiecommander.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hardsuits/syndiecommander.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from vg at commit https://github.com/vgstation-coders/vgstation13/commit/a16e41020a93479e9a7e2af343b1b74f7f2a61bd", + "copyright": "Taken from vg at commit https://github.com/vgstation-coders/vgstation13/commit/a16e41020a93479e9a7e2af343b1b74f7f2a61bd. Vox states by Flareguy for Space Station 14", "size": { "x": 32, @@ -18,25 +18,17 @@ "name": "off-equipped-HELMET", "directions": 4 }, - { - "name": "off-inhand-left", - "directions": 4 - }, - { - "name": "off-inhand-right", - "directions": 4 - }, { "name": "on-equipped-HELMET", "directions": 4 }, { - "name": "on-inhand-left", + "name": "off-equipped-HELMET-vox", "directions": 4 }, { - "name": "on-inhand-right", + "name": "on-equipped-HELMET-vox", "directions": 4 } ] -} \ No newline at end of file +} diff --git a/Resources/Textures/Clothing/Head/Hardsuits/syndiecommander.rsi/off-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/syndiecommander.rsi/off-equipped-HELMET-vox.png new file mode 100644 index 0000000000..5276e3f681 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/syndiecommander.rsi/off-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/syndiecommander.rsi/off-inhand-left.png b/Resources/Textures/Clothing/Head/Hardsuits/syndiecommander.rsi/off-inhand-left.png deleted file mode 100644 index 79a96cd711..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/syndiecommander.rsi/off-inhand-left.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/syndiecommander.rsi/off-inhand-right.png b/Resources/Textures/Clothing/Head/Hardsuits/syndiecommander.rsi/off-inhand-right.png deleted file mode 100644 index 0c7b0caddf..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/syndiecommander.rsi/off-inhand-right.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/syndiecommander.rsi/on-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/syndiecommander.rsi/on-equipped-HELMET-vox.png new file mode 100644 index 0000000000..65069f91c7 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/syndiecommander.rsi/on-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/syndiecommander.rsi/on-inhand-left.png b/Resources/Textures/Clothing/Head/Hardsuits/syndiecommander.rsi/on-inhand-left.png deleted file mode 100644 index 505808fc9d..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/syndiecommander.rsi/on-inhand-left.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/syndiecommander.rsi/on-inhand-right.png b/Resources/Textures/Clothing/Head/Hardsuits/syndiecommander.rsi/on-inhand-right.png deleted file mode 100644 index 862f6294fb..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/syndiecommander.rsi/on-inhand-right.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/syndieelite.rsi/meta.json b/Resources/Textures/Clothing/Head/Hardsuits/syndieelite.rsi/meta.json index 038aabe605..5c7932bb28 100644 --- a/Resources/Textures/Clothing/Head/Hardsuits/syndieelite.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hardsuits/syndieelite.rsi/meta.json @@ -1,42 +1,33 @@ { - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Taken from vg at commit https://github.com/vgstation-coders/vgstation13/commit/a16e41020a93479e9a7e2af343b1b74f7f2a61bd", - - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "icon" + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from vg at commit https://github.com/vgstation-coders/vgstation13/commit/a16e41020a93479e9a7e2af343b1b74f7f2a61bd. Vox states by Flareguy for Space Station 14", + "size": { + "x": 32, + "y": 32 }, - { - "name": "icon-flash" - }, - { - "name": "off-equipped-HELMET", - "directions": 4 - }, - { - "name": "off-inhand-left", - "directions": 4 - }, - { - "name": "off-inhand-right", - "directions": 4 - }, - { - "name": "on-equipped-HELMET", - "directions": 4 - }, - { - "name": "on-inhand-left", - "directions": 4 - }, - { - "name": "on-inhand-right", - "directions": 4 - } - ] + "states": [ + { + "name": "icon" + }, + { + "name": "icon-flash" + }, + { + "name": "off-equipped-HELMET", + "directions": 4 + }, + { + "name": "on-equipped-HELMET", + "directions": 4 + }, + { + "name": "off-equipped-HELMET-vox", + "directions": 4 + }, + { + "name": "on-equipped-HELMET-vox", + "directions": 4 + } + ] } \ No newline at end of file diff --git a/Resources/Textures/Clothing/Head/Hardsuits/syndieelite.rsi/off-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/syndieelite.rsi/off-equipped-HELMET-vox.png new file mode 100644 index 0000000000..4f4b2f0f77 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/syndieelite.rsi/off-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/syndieelite.rsi/off-inhand-left.png b/Resources/Textures/Clothing/Head/Hardsuits/syndieelite.rsi/off-inhand-left.png deleted file mode 100644 index 22a8dd14b1..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/syndieelite.rsi/off-inhand-left.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/syndieelite.rsi/off-inhand-right.png b/Resources/Textures/Clothing/Head/Hardsuits/syndieelite.rsi/off-inhand-right.png deleted file mode 100644 index 71572c0ffd..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/syndieelite.rsi/off-inhand-right.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/syndieelite.rsi/on-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/syndieelite.rsi/on-equipped-HELMET-vox.png new file mode 100644 index 0000000000..bf7243ed32 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/syndieelite.rsi/on-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/syndieelite.rsi/on-inhand-left.png b/Resources/Textures/Clothing/Head/Hardsuits/syndieelite.rsi/on-inhand-left.png deleted file mode 100644 index 5a40993d72..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/syndieelite.rsi/on-inhand-left.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/syndieelite.rsi/on-inhand-right.png b/Resources/Textures/Clothing/Head/Hardsuits/syndieelite.rsi/on-inhand-right.png deleted file mode 100644 index 27a27d1e8e..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/syndieelite.rsi/on-inhand-right.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/syndiemedic.rsi/meta.json b/Resources/Textures/Clothing/Head/Hardsuits/syndiemedic.rsi/meta.json index b69addc476..9adc3b7cef 100644 --- a/Resources/Textures/Clothing/Head/Hardsuits/syndiemedic.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hardsuits/syndiemedic.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Based on tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e, modified by EmoGarbage404 (github)", + "copyright": "Based on tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e, modified by EmoGarbage404 (github). Vox states by Flareguy for Space Station 14", "size": { "x": 32, "y": 32 @@ -17,24 +17,16 @@ "name": "off-equipped-HELMET", "directions": 4 }, - { - "name": "off-inhand-left", - "directions": 4 - }, - { - "name": "off-inhand-right", - "directions": 4 - }, { "name": "on-equipped-HELMET", "directions": 4 }, { - "name": "on-inhand-left", + "name": "off-equipped-HELMET-vox", "directions": 4 }, { - "name": "on-inhand-right", + "name": "on-equipped-HELMET-vox", "directions": 4 } ] diff --git a/Resources/Textures/Clothing/Head/Hardsuits/syndiemedic.rsi/off-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/syndiemedic.rsi/off-equipped-HELMET-vox.png new file mode 100644 index 0000000000..4d5f2cf4b7 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/syndiemedic.rsi/off-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/syndiemedic.rsi/off-inhand-left.png b/Resources/Textures/Clothing/Head/Hardsuits/syndiemedic.rsi/off-inhand-left.png deleted file mode 100644 index 97f21fd350..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/syndiemedic.rsi/off-inhand-left.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/syndiemedic.rsi/off-inhand-right.png b/Resources/Textures/Clothing/Head/Hardsuits/syndiemedic.rsi/off-inhand-right.png deleted file mode 100644 index 0e950a6674..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/syndiemedic.rsi/off-inhand-right.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/syndiemedic.rsi/on-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/syndiemedic.rsi/on-equipped-HELMET-vox.png new file mode 100644 index 0000000000..8c072609c9 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/syndiemedic.rsi/on-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/syndiemedic.rsi/on-inhand-left.png b/Resources/Textures/Clothing/Head/Hardsuits/syndiemedic.rsi/on-inhand-left.png deleted file mode 100644 index 0e174c04c3..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/syndiemedic.rsi/on-inhand-left.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/syndiemedic.rsi/on-inhand-right.png b/Resources/Textures/Clothing/Head/Hardsuits/syndiemedic.rsi/on-inhand-right.png deleted file mode 100644 index b049400348..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/syndiemedic.rsi/on-inhand-right.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/wizard.rsi/meta.json b/Resources/Textures/Clothing/Head/Hardsuits/wizard.rsi/meta.json index d168108fdf..b72a96092e 100644 --- a/Resources/Textures/Clothing/Head/Hardsuits/wizard.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hardsuits/wizard.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Vox states by Flareguy for Space Station 14 | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d", "size": { "x": 32, "y": 32 @@ -14,28 +14,20 @@ "name": "icon-flash" }, { - "name": "off-equipped-HELMET", - "directions": 4 - }, - { - "name": "off-inhand-left", - "directions": 4 + "name": "on-equipped-HELMET", + "directions": 4 }, { - "name": "off-inhand-right", - "directions": 4 - }, - { - "name": "on-equipped-HELMET", + "name": "off-equipped-HELMET", "directions": 4 }, { - "name": "on-inhand-left", + "name": "on-equipped-HELMET-vox", "directions": 4 }, { - "name": "on-inhand-right", - "directions": 4 + "name": "off-equipped-HELMET-vox", + "directions": 4 }, { "name": "on-equipped-HELMET-vulpkanin", diff --git a/Resources/Textures/Clothing/Head/Hardsuits/wizard.rsi/off-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/wizard.rsi/off-equipped-HELMET-vox.png new file mode 100644 index 0000000000..647d0e76c1 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/wizard.rsi/off-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/wizard.rsi/off-inhand-left.png b/Resources/Textures/Clothing/Head/Hardsuits/wizard.rsi/off-inhand-left.png deleted file mode 100644 index 90b7c2574a..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/wizard.rsi/off-inhand-left.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/wizard.rsi/off-inhand-right.png b/Resources/Textures/Clothing/Head/Hardsuits/wizard.rsi/off-inhand-right.png deleted file mode 100644 index de647331f9..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/wizard.rsi/off-inhand-right.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/wizard.rsi/on-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hardsuits/wizard.rsi/on-equipped-HELMET-vox.png new file mode 100644 index 0000000000..a0345048e8 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hardsuits/wizard.rsi/on-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/wizard.rsi/on-inhand-left.png b/Resources/Textures/Clothing/Head/Hardsuits/wizard.rsi/on-inhand-left.png deleted file mode 100644 index fc11467e8f..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/wizard.rsi/on-inhand-left.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hardsuits/wizard.rsi/on-inhand-right.png b/Resources/Textures/Clothing/Head/Hardsuits/wizard.rsi/on-inhand-right.png deleted file mode 100644 index 131989b592..0000000000 Binary files a/Resources/Textures/Clothing/Head/Hardsuits/wizard.rsi/on-inhand-right.png and /dev/null differ diff --git a/Resources/Textures/Clothing/Head/Hats/chefhat_idris.rsi/equipped-HELMET.png b/Resources/Textures/Clothing/Head/Hats/chefhat_idris.rsi/equipped-HELMET.png new file mode 100644 index 0000000000..a6ccc52dc2 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hats/chefhat_idris.rsi/equipped-HELMET.png differ diff --git a/Resources/Textures/Clothing/Head/Hats/chefhat_idris.rsi/icon.png b/Resources/Textures/Clothing/Head/Hats/chefhat_idris.rsi/icon.png new file mode 100644 index 0000000000..76c55d1b96 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hats/chefhat_idris.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Head/Hats/chefhat_idris.rsi/meta.json b/Resources/Textures/Clothing/Head/Hats/chefhat_idris.rsi/meta.json new file mode 100644 index 0000000000..dcf3d382c4 --- /dev/null +++ b/Resources/Textures/Clothing/Head/Hats/chefhat_idris.rsi/meta.json @@ -0,0 +1,18 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from Aurorastation at commit https://github.com/Aurorastation/Aurora.3/commit/a8759f3c77b61315df50ee86969799f5a0afe894", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-HELMET", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/Head/Hats/chefhat_nt.rsi/equipped-HELMET.png b/Resources/Textures/Clothing/Head/Hats/chefhat_nt.rsi/equipped-HELMET.png new file mode 100644 index 0000000000..47a4ef7a60 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hats/chefhat_nt.rsi/equipped-HELMET.png differ diff --git a/Resources/Textures/Clothing/Head/Hats/chefhat_nt.rsi/icon.png b/Resources/Textures/Clothing/Head/Hats/chefhat_nt.rsi/icon.png new file mode 100644 index 0000000000..391b8ae3a0 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hats/chefhat_nt.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Head/Hats/chefhat_nt.rsi/meta.json b/Resources/Textures/Clothing/Head/Hats/chefhat_nt.rsi/meta.json new file mode 100644 index 0000000000..dcf3d382c4 --- /dev/null +++ b/Resources/Textures/Clothing/Head/Hats/chefhat_nt.rsi/meta.json @@ -0,0 +1,18 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from Aurorastation at commit https://github.com/Aurorastation/Aurora.3/commit/a8759f3c77b61315df50ee86969799f5a0afe894", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-HELMET", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/Head/Hats/chefhat_orion.rsi/equipped-HELMET.png b/Resources/Textures/Clothing/Head/Hats/chefhat_orion.rsi/equipped-HELMET.png new file mode 100644 index 0000000000..95a8ad5b42 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hats/chefhat_orion.rsi/equipped-HELMET.png differ diff --git a/Resources/Textures/Clothing/Head/Hats/chefhat_orion.rsi/icon.png b/Resources/Textures/Clothing/Head/Hats/chefhat_orion.rsi/icon.png new file mode 100644 index 0000000000..05bd50b8cb Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hats/chefhat_orion.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Head/Hats/chefhat_orion.rsi/meta.json b/Resources/Textures/Clothing/Head/Hats/chefhat_orion.rsi/meta.json new file mode 100644 index 0000000000..dcf3d382c4 --- /dev/null +++ b/Resources/Textures/Clothing/Head/Hats/chefhat_orion.rsi/meta.json @@ -0,0 +1,18 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from Aurorastation at commit https://github.com/Aurorastation/Aurora.3/commit/a8759f3c77b61315df50ee86969799f5a0afe894", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-HELMET", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/Head/Hats/flatcap_idris.rsi/equipped-HELMET.png b/Resources/Textures/Clothing/Head/Hats/flatcap_idris.rsi/equipped-HELMET.png new file mode 100644 index 0000000000..2a8480d237 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hats/flatcap_idris.rsi/equipped-HELMET.png differ diff --git a/Resources/Textures/Clothing/Head/Hats/flatcap_idris.rsi/icon.png b/Resources/Textures/Clothing/Head/Hats/flatcap_idris.rsi/icon.png new file mode 100644 index 0000000000..52b439669a Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hats/flatcap_idris.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Head/Hats/flatcap_idris.rsi/meta.json b/Resources/Textures/Clothing/Head/Hats/flatcap_idris.rsi/meta.json new file mode 100644 index 0000000000..dcf3d382c4 --- /dev/null +++ b/Resources/Textures/Clothing/Head/Hats/flatcap_idris.rsi/meta.json @@ -0,0 +1,18 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from Aurorastation at commit https://github.com/Aurorastation/Aurora.3/commit/a8759f3c77b61315df50ee86969799f5a0afe894", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-HELMET", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/Head/Hats/flatcap_nt.rsi/equipped-HELMET.png b/Resources/Textures/Clothing/Head/Hats/flatcap_nt.rsi/equipped-HELMET.png new file mode 100644 index 0000000000..bc25822a7e Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hats/flatcap_nt.rsi/equipped-HELMET.png differ diff --git a/Resources/Textures/Clothing/Head/Hats/flatcap_nt.rsi/icon.png b/Resources/Textures/Clothing/Head/Hats/flatcap_nt.rsi/icon.png new file mode 100644 index 0000000000..397bc77453 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hats/flatcap_nt.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Head/Hats/flatcap_nt.rsi/meta.json b/Resources/Textures/Clothing/Head/Hats/flatcap_nt.rsi/meta.json new file mode 100644 index 0000000000..dcf3d382c4 --- /dev/null +++ b/Resources/Textures/Clothing/Head/Hats/flatcap_nt.rsi/meta.json @@ -0,0 +1,18 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from Aurorastation at commit https://github.com/Aurorastation/Aurora.3/commit/a8759f3c77b61315df50ee86969799f5a0afe894", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-HELMET", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/Head/Hats/flatcap_orion.rsi/equipped-HELMET.png b/Resources/Textures/Clothing/Head/Hats/flatcap_orion.rsi/equipped-HELMET.png new file mode 100644 index 0000000000..cd78cb38a4 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hats/flatcap_orion.rsi/equipped-HELMET.png differ diff --git a/Resources/Textures/Clothing/Head/Hats/flatcap_orion.rsi/icon.png b/Resources/Textures/Clothing/Head/Hats/flatcap_orion.rsi/icon.png new file mode 100644 index 0000000000..915b2d54b2 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hats/flatcap_orion.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Head/Hats/flatcap_orion.rsi/meta.json b/Resources/Textures/Clothing/Head/Hats/flatcap_orion.rsi/meta.json new file mode 100644 index 0000000000..dcf3d382c4 --- /dev/null +++ b/Resources/Textures/Clothing/Head/Hats/flatcap_orion.rsi/meta.json @@ -0,0 +1,18 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from Aurorastation at commit https://github.com/Aurorastation/Aurora.3/commit/a8759f3c77b61315df50ee86969799f5a0afe894", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-HELMET", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/Head/Hats/gladiator.rsi/equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hats/gladiator.rsi/equipped-HELMET-vox.png new file mode 100644 index 0000000000..b432655c5a Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hats/gladiator.rsi/equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hats/gladiator.rsi/meta.json b/Resources/Textures/Clothing/Head/Hats/gladiator.rsi/meta.json index bbb0aac664..057d0b0ab2 100644 --- a/Resources/Textures/Clothing/Head/Hats/gladiator.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hats/gladiator.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. equipped-HELMET-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79", "size": { "x": 32, "y": 32 @@ -20,7 +20,11 @@ }, { "name": "inhand-right", - "direction": 4 + "directions": 4 + }, + { + "name": "equipped-HELMET-vox", + "directions": 4 } ] } diff --git a/Resources/Textures/Clothing/Head/Hats/paper.rsi/equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hats/paper.rsi/equipped-HELMET-vox.png new file mode 100644 index 0000000000..5f5c3376d7 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hats/paper.rsi/equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hats/paper.rsi/meta.json b/Resources/Textures/Clothing/Head/Hats/paper.rsi/meta.json index a470e00944..057d0b0ab2 100644 --- a/Resources/Textures/Clothing/Head/Hats/paper.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hats/paper.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. equipped-HELMET-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79", "size": { "x": 32, "y": 32 @@ -21,6 +21,10 @@ { "name": "inhand-right", "directions": 4 + }, + { + "name": "equipped-HELMET-vox", + "directions": 4 } ] } diff --git a/Resources/Textures/Clothing/Head/Hats/surgcap_blue.rsi/equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hats/surgcap_blue.rsi/equipped-HELMET-vox.png new file mode 100644 index 0000000000..a51c268041 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hats/surgcap_blue.rsi/equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hats/surgcap_blue.rsi/meta.json b/Resources/Textures/Clothing/Head/Hats/surgcap_blue.rsi/meta.json index ade65863af..e099085bde 100644 --- a/Resources/Textures/Clothing/Head/Hats/surgcap_blue.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hats/surgcap_blue.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. equipped-HELMET-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79", "size": { "x": 32, "y": 32 @@ -25,6 +25,10 @@ { "name": "inhand-right", "directions": 4 + }, + { + "name": "equipped-HELMET-vox", + "directions": 4 } ] } diff --git a/Resources/Textures/Clothing/Head/Hats/surgcap_green.rsi/equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hats/surgcap_green.rsi/equipped-HELMET-vox.png new file mode 100644 index 0000000000..81601c6c2b Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hats/surgcap_green.rsi/equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hats/surgcap_green.rsi/meta.json b/Resources/Textures/Clothing/Head/Hats/surgcap_green.rsi/meta.json index a470e00944..057d0b0ab2 100644 --- a/Resources/Textures/Clothing/Head/Hats/surgcap_green.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hats/surgcap_green.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. equipped-HELMET-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79", "size": { "x": 32, "y": 32 @@ -21,6 +21,10 @@ { "name": "inhand-right", "directions": 4 + }, + { + "name": "equipped-HELMET-vox", + "directions": 4 } ] } diff --git a/Resources/Textures/Clothing/Head/Hats/surgcap_purple.rsi/equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hats/surgcap_purple.rsi/equipped-HELMET-vox.png new file mode 100644 index 0000000000..24ea1365cd Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hats/surgcap_purple.rsi/equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hats/surgcap_purple.rsi/meta.json b/Resources/Textures/Clothing/Head/Hats/surgcap_purple.rsi/meta.json index a470e00944..057d0b0ab2 100644 --- a/Resources/Textures/Clothing/Head/Hats/surgcap_purple.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hats/surgcap_purple.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. equipped-HELMET-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79", "size": { "x": 32, "y": 32 @@ -21,6 +21,10 @@ { "name": "inhand-right", "directions": 4 + }, + { + "name": "equipped-HELMET-vox", + "directions": 4 } ] } diff --git a/Resources/Textures/Clothing/Head/Helmets/atmos_firehelmet.rsi/meta.json b/Resources/Textures/Clothing/Head/Helmets/atmos_firehelmet.rsi/meta.json index 26d3341e24..f2d8747c03 100644 --- a/Resources/Textures/Clothing/Head/Helmets/atmos_firehelmet.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Helmets/atmos_firehelmet.rsi/meta.json @@ -1,41 +1,49 @@ -{ - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation https://github.com/tgstation/tgstation/blob/master/icons/mob/clothing/head.dmi", - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "icon" - }, - { - "name": "icon-flash" - }, - { - "name": "off-equipped-HELMET", - "directions": 4 - }, - { - "name": "off-inhand-left", - "directions": 4 - }, - { - "name": "off-inhand-right", - "directions": 4 - }, - { - "name": "on-equipped-HELMET", - "directions": 4 - }, - { - "name": "on-inhand-left", - "directions": 4 - }, - { - "name": "on-inhand-right", - "directions": 4 - } - ] -} +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation https://github.com/tgstation/tgstation/blob/master/icons/mob/clothing/head.dmi. Vox states taken from paradise at https://github.com/ParadiseSS13/Paradise/blob/765461f14aa4dd4f1edc33242c667843134678b5/icons/mob/clothing/species/vox/head.dmi", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "icon-flash" + }, + { + "name": "off-equipped-HELMET", + "directions": 4 + }, + { + "name": "off-equipped-HELMET-vox", + "directions": 4 + }, + { + "name": "off-inhand-left", + "directions": 4 + }, + { + "name": "off-inhand-right", + "directions": 4 + }, + { + "name": "on-equipped-HELMET", + "directions": 4 + }, + { + "name": "on-equipped-HELMET-vox", + "directions": 4 + }, + { + "name": "on-inhand-left", + "directions": 4 + }, + { + "name": "on-inhand-right", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Clothing/Head/Helmets/atmos_firehelmet.rsi/off-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Helmets/atmos_firehelmet.rsi/off-equipped-HELMET-vox.png new file mode 100644 index 0000000000..ea6e7edce8 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Helmets/atmos_firehelmet.rsi/off-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Helmets/atmos_firehelmet.rsi/on-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Helmets/atmos_firehelmet.rsi/on-equipped-HELMET-vox.png new file mode 100644 index 0000000000..301ff00e34 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Helmets/atmos_firehelmet.rsi/on-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Helmets/bombsuit.rsi/equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Helmets/bombsuit.rsi/equipped-HELMET-vox.png new file mode 100644 index 0000000000..58e8aa9e70 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Helmets/bombsuit.rsi/equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Helmets/bombsuit.rsi/meta.json b/Resources/Textures/Clothing/Head/Helmets/bombsuit.rsi/meta.json index e0ee93d642..e55d1eb469 100644 --- a/Resources/Textures/Clothing/Head/Helmets/bombsuit.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Helmets/bombsuit.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from cev-eris at commit https://github.com/discordia-space/CEV-Eris/commit/a75dee2e6d236612dbd403dd5f8687ca930c01f1", + "copyright": "Taken from cev-eris at commit https://github.com/discordia-space/CEV-Eris/commit/a75dee2e6d236612dbd403dd5f8687ca930c01f1. Vox state by Flareguy for SS14", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-HELMET", "directions": 4 }, + { + "name": "equipped-HELMET-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/Head/Helmets/eva.rsi/equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Helmets/eva.rsi/equipped-HELMET-vox.png new file mode 100644 index 0000000000..50d4ec39d5 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Helmets/eva.rsi/equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Helmets/eva.rsi/meta.json b/Resources/Textures/Clothing/Head/Helmets/eva.rsi/meta.json index 3c406ff57c..c7a2236b61 100644 --- a/Resources/Textures/Clothing/Head/Helmets/eva.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Helmets/eva.rsi/meta.json @@ -14,6 +14,10 @@ "name": "equipped-HELMET", "directions": 4 }, + { + "name": "equipped-HELMET-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/Head/Helmets/eva_large.rsi/equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Helmets/eva_large.rsi/equipped-HELMET-vox.png new file mode 100644 index 0000000000..b5f224b98a Binary files /dev/null and b/Resources/Textures/Clothing/Head/Helmets/eva_large.rsi/equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Helmets/eva_large.rsi/equipped-HELMET.png b/Resources/Textures/Clothing/Head/Helmets/eva_large.rsi/equipped-HELMET.png index c64f69b0d8..f6f0e58c6b 100644 Binary files a/Resources/Textures/Clothing/Head/Helmets/eva_large.rsi/equipped-HELMET.png and b/Resources/Textures/Clothing/Head/Helmets/eva_large.rsi/equipped-HELMET.png differ diff --git a/Resources/Textures/Clothing/Head/Helmets/eva_large.rsi/meta.json b/Resources/Textures/Clothing/Head/Helmets/eva_large.rsi/meta.json index 3c406ff57c..c7a2236b61 100644 --- a/Resources/Textures/Clothing/Head/Helmets/eva_large.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Helmets/eva_large.rsi/meta.json @@ -14,6 +14,10 @@ "name": "equipped-HELMET", "directions": 4 }, + { + "name": "equipped-HELMET-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/Head/Helmets/eva_syndicate.rsi/equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Helmets/eva_syndicate.rsi/equipped-HELMET-vox.png new file mode 100644 index 0000000000..62ff233af1 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Helmets/eva_syndicate.rsi/equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Helmets/eva_syndicate.rsi/meta.json b/Resources/Textures/Clothing/Head/Helmets/eva_syndicate.rsi/meta.json index 3c406ff57c..c7a2236b61 100644 --- a/Resources/Textures/Clothing/Head/Helmets/eva_syndicate.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Helmets/eva_syndicate.rsi/meta.json @@ -14,6 +14,10 @@ "name": "equipped-HELMET", "directions": 4 }, + { + "name": "equipped-HELMET-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/Head/Helmets/firehelmet.rsi/meta.json b/Resources/Textures/Clothing/Head/Helmets/firehelmet.rsi/meta.json index 9530d0f7aa..014284471a 100644 --- a/Resources/Textures/Clothing/Head/Helmets/firehelmet.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Helmets/firehelmet.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from paradise station and modified by Peptide90 https://github.com/ParadiseSS13/Paradise/commit/46bb8d448f52278a8f027b3f5bdf061e39d06140", + "copyright": "Taken from paradise station and modified by Peptide90 https://github.com/ParadiseSS13/Paradise/commit/46bb8d448f52278a8f027b3f5bdf061e39d06140. Vox states by Flareguy for SS14", "size": { "x": 32, "y": 32 @@ -17,6 +17,10 @@ "name": "off-equipped-HELMET", "directions": 4 }, + { + "name": "off-equipped-HELMET-vox", + "directions": 4 + }, { "name": "off-inhand-left", "directions": 4 @@ -29,6 +33,10 @@ "name": "on-equipped-HELMET", "directions": 4 }, + { + "name": "on-equipped-HELMET-vox", + "directions": 4 + }, { "name": "on-inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/Head/Helmets/firehelmet.rsi/off-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Helmets/firehelmet.rsi/off-equipped-HELMET-vox.png new file mode 100644 index 0000000000..ad7bbad447 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Helmets/firehelmet.rsi/off-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Helmets/firehelmet.rsi/on-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Helmets/firehelmet.rsi/on-equipped-HELMET-vox.png new file mode 100644 index 0000000000..2ebb820f60 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Helmets/firehelmet.rsi/on-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Helmets/ihvoid.rsi/equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Helmets/ihvoid.rsi/equipped-HELMET-vox.png new file mode 100644 index 0000000000..522a2eb923 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Helmets/ihvoid.rsi/equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Helmets/ihvoid.rsi/meta.json b/Resources/Textures/Clothing/Head/Helmets/ihvoid.rsi/meta.json index 4427c7fb51..69b03ad731 100644 --- a/Resources/Textures/Clothing/Head/Helmets/ihvoid.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Helmets/ihvoid.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Vox state by Flareguy for Space Station 14 | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-HELMET", "directions": 4 }, + { + "name": "equipped-HELMET-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/Head/Helmets/light_riot.rsi/equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Helmets/light_riot.rsi/equipped-HELMET-vox.png new file mode 100644 index 0000000000..952b32f3a1 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Helmets/light_riot.rsi/equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Helmets/light_riot.rsi/meta.json b/Resources/Textures/Clothing/Head/Helmets/light_riot.rsi/meta.json index 5048ec6bb0..abfed0635a 100644 --- a/Resources/Textures/Clothing/Head/Helmets/light_riot.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Helmets/light_riot.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from TGstation github https://github.com/tgstation/tgstation/commit/fed2ddeb54d0fb8bb97cb0a899a088b7d7423bbb", + "copyright": "Taken from TGstation github https://github.com/tgstation/tgstation/commit/fed2ddeb54d0fb8bb97cb0a899a088b7d7423bbb. Vox state by Flareguy for SS14", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-HELMET", "directions": 4 }, + { + "name": "equipped-HELMET-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/Head/Helmets/security.rsi/equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Helmets/security.rsi/equipped-HELMET-vox.png new file mode 100644 index 0000000000..703d927ecf Binary files /dev/null and b/Resources/Textures/Clothing/Head/Helmets/security.rsi/equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Helmets/security.rsi/meta.json b/Resources/Textures/Clothing/Head/Helmets/security.rsi/meta.json index db2bbfafd4..a0653e5c65 100644 --- a/Resources/Textures/Clothing/Head/Helmets/security.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Helmets/security.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Vox state by Flareguy for SS14", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-HELMET", "directions": 4 }, + { + "name": "equipped-HELMET-vox", + "directions": 4 + }, { "name": "light-equipped-HELMET", "directions": 4 diff --git a/Resources/Textures/Clothing/Head/Helmets/spaceninja.rsi/equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Helmets/spaceninja.rsi/equipped-HELMET-vox.png new file mode 100644 index 0000000000..59f1e3263b Binary files /dev/null and b/Resources/Textures/Clothing/Head/Helmets/spaceninja.rsi/equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Helmets/spaceninja.rsi/meta.json b/Resources/Textures/Clothing/Head/Helmets/spaceninja.rsi/meta.json index 61c8ce163c..dae510390f 100644 --- a/Resources/Textures/Clothing/Head/Helmets/spaceninja.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Helmets/spaceninja.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from paradise https://github.com/ParadiseSS13/Paradise/tree/master/icons (unknown commit) | vulpkanin version made by Floofers", + "copyright": "Taken from paradise https://github.com/ParadiseSS13/Paradise/tree/master/icons (unknown commit) | vulpkanin version made by Floofers. Vox state by Flareguy for SS14", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-HELMET", "directions": 4 }, + { + "name": "equipped-HELMET-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/Head/Helmets/swat.rsi/equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Helmets/swat.rsi/equipped-HELMET-vox.png new file mode 100644 index 0000000000..1ed974c206 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Helmets/swat.rsi/equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Helmets/swat.rsi/meta.json b/Resources/Textures/Clothing/Head/Helmets/swat.rsi/meta.json index 8836c0b646..f505b4e75e 100644 --- a/Resources/Textures/Clothing/Head/Helmets/swat.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Helmets/swat.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/fb2d71495bfe81446159ef528534193d09dd8d34, inhand sprites by Flareguy", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/fb2d71495bfe81446159ef528534193d09dd8d34, inhand sprites & vox state by Flareguy", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-HELMET", "directions": 4 }, + { + "name": "equipped-HELMET-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/Head/Helmets/swat_syndicate.rsi/equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Helmets/swat_syndicate.rsi/equipped-HELMET-vox.png new file mode 100644 index 0000000000..e228a8a33a Binary files /dev/null and b/Resources/Textures/Clothing/Head/Helmets/swat_syndicate.rsi/equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Helmets/swat_syndicate.rsi/meta.json b/Resources/Textures/Clothing/Head/Helmets/swat_syndicate.rsi/meta.json index 8bedbfb78e..a0bf954585 100644 --- a/Resources/Textures/Clothing/Head/Helmets/swat_syndicate.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Helmets/swat_syndicate.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/fb2d71495bfe81446159ef528534193d09dd8d34, inhand sprites by Flareguy, icon edited by Flareguy", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/fb2d71495bfe81446159ef528534193d09dd8d34, inhand sprites by Flareguy, icon edited by Flareguy. Vox state taken from from Paradise at https://github.com/ParadiseSS13/Paradise/blob/b1658390731745adf5cc9fb039bb0f17ad3c11a7/icons/mob/clothing/species/vox/helmet.dmi ", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-HELMET", "directions": 4 }, + { + "name": "equipped-HELMET-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/Head/Helmets/templar.rsi/equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Helmets/templar.rsi/equipped-HELMET-vox.png new file mode 100644 index 0000000000..3572a5de9f Binary files /dev/null and b/Resources/Textures/Clothing/Head/Helmets/templar.rsi/equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Helmets/templar.rsi/meta.json b/Resources/Textures/Clothing/Head/Helmets/templar.rsi/meta.json index 7bd2e3e22a..5c8b454ea6 100644 --- a/Resources/Textures/Clothing/Head/Helmets/templar.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Helmets/templar.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. equipped-HELMET-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79", "size": { "x": 32, "y": 32 @@ -13,6 +13,10 @@ { "name": "equipped-HELMET", "directions": 4 + }, + { + "name": "equipped-HELMET-vox", + "directions": 4 } ] } diff --git a/Resources/Textures/Clothing/Head/Helmets/wizardhelm.rsi/equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Helmets/wizardhelm.rsi/equipped-HELMET-vox.png new file mode 100644 index 0000000000..0cabbe106c Binary files /dev/null and b/Resources/Textures/Clothing/Head/Helmets/wizardhelm.rsi/equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Helmets/wizardhelm.rsi/meta.json b/Resources/Textures/Clothing/Head/Helmets/wizardhelm.rsi/meta.json index a470e00944..8e639d26bc 100644 --- a/Resources/Textures/Clothing/Head/Helmets/wizardhelm.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Helmets/wizardhelm.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. equipped-HELMET-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79 and modified to remove duplicate states", "size": { "x": 32, "y": 32 @@ -21,6 +21,10 @@ { "name": "inhand-right", "directions": 4 + }, + { + "name": "equipped-HELMET-vox", + "directions": 4 } ] } diff --git a/Resources/Textures/Clothing/Head/Hoods/Bio/bio.rsi/equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hoods/Bio/bio.rsi/equipped-HELMET-vox.png new file mode 100644 index 0000000000..35cfa8ddc0 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hoods/Bio/bio.rsi/equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hoods/Bio/bio.rsi/meta.json b/Resources/Textures/Clothing/Head/Hoods/Bio/bio.rsi/meta.json index 4427c7fb51..fd2c470dc4 100644 --- a/Resources/Textures/Clothing/Head/Hoods/Bio/bio.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hoods/Bio/bio.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d | equipped-HELMET-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79", "size": { "x": 32, "y": 32 @@ -25,6 +25,10 @@ { "name": "equipped-HELMET-vulpkanin", "directions": 4 + }, + { + "name": "equipped-HELMET-vox", + "directions": 4 } ] } diff --git a/Resources/Textures/Clothing/Head/Hoods/Bio/cmo.rsi/equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hoods/Bio/cmo.rsi/equipped-HELMET-vox.png new file mode 100644 index 0000000000..90ad846f9b Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hoods/Bio/cmo.rsi/equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hoods/Bio/cmo.rsi/meta.json b/Resources/Textures/Clothing/Head/Hoods/Bio/cmo.rsi/meta.json index 4427c7fb51..d95bcad823 100644 --- a/Resources/Textures/Clothing/Head/Hoods/Bio/cmo.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hoods/Bio/cmo.rsi/meta.json @@ -1,30 +1,34 @@ { - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d", - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "icon" + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d | equipped-HELMET-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79", + "size": { + "x": 32, + "y": 32 }, - { - "name": "equipped-HELMET", - "directions": 4 - }, - { - "name": "inhand-left", - "directions": 4 - }, - { - "name": "inhand-right", - "directions": 4 - }, - { - "name": "equipped-HELMET-vulpkanin", - "directions": 4 - } - ] -} + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-HELMET", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "equipped-HELMET-vulpkanin", + "directions": 4 + }, + { + "name": "equipped-HELMET-vox", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Clothing/Head/Hoods/Bio/general.rsi/equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hoods/Bio/general.rsi/equipped-HELMET-vox.png new file mode 100644 index 0000000000..35cfa8ddc0 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hoods/Bio/general.rsi/equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hoods/Bio/general.rsi/meta.json b/Resources/Textures/Clothing/Head/Hoods/Bio/general.rsi/meta.json index 4427c7fb51..d95bcad823 100644 --- a/Resources/Textures/Clothing/Head/Hoods/Bio/general.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hoods/Bio/general.rsi/meta.json @@ -1,30 +1,34 @@ { - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d", - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "icon" + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d | equipped-HELMET-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79", + "size": { + "x": 32, + "y": 32 }, - { - "name": "equipped-HELMET", - "directions": 4 - }, - { - "name": "inhand-left", - "directions": 4 - }, - { - "name": "inhand-right", - "directions": 4 - }, - { - "name": "equipped-HELMET-vulpkanin", - "directions": 4 - } - ] -} + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-HELMET", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "equipped-HELMET-vulpkanin", + "directions": 4 + }, + { + "name": "equipped-HELMET-vox", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Clothing/Head/Hoods/Bio/janitor.rsi/equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hoods/Bio/janitor.rsi/equipped-HELMET-vox.png new file mode 100644 index 0000000000..2a8f1b0cdb Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hoods/Bio/janitor.rsi/equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hoods/Bio/janitor.rsi/meta.json b/Resources/Textures/Clothing/Head/Hoods/Bio/janitor.rsi/meta.json index 4427c7fb51..d95bcad823 100644 --- a/Resources/Textures/Clothing/Head/Hoods/Bio/janitor.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hoods/Bio/janitor.rsi/meta.json @@ -1,30 +1,34 @@ { - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d", - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "icon" + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d | equipped-HELMET-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79", + "size": { + "x": 32, + "y": 32 }, - { - "name": "equipped-HELMET", - "directions": 4 - }, - { - "name": "inhand-left", - "directions": 4 - }, - { - "name": "inhand-right", - "directions": 4 - }, - { - "name": "equipped-HELMET-vulpkanin", - "directions": 4 - } - ] -} + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-HELMET", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "equipped-HELMET-vulpkanin", + "directions": 4 + }, + { + "name": "equipped-HELMET-vox", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Clothing/Head/Hoods/Bio/scientist.rsi/equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hoods/Bio/scientist.rsi/equipped-HELMET-vox.png new file mode 100644 index 0000000000..4b90b700a8 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hoods/Bio/scientist.rsi/equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hoods/Bio/scientist.rsi/meta.json b/Resources/Textures/Clothing/Head/Hoods/Bio/scientist.rsi/meta.json index 4427c7fb51..d95bcad823 100644 --- a/Resources/Textures/Clothing/Head/Hoods/Bio/scientist.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hoods/Bio/scientist.rsi/meta.json @@ -1,30 +1,34 @@ { - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d", - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "icon" + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d | equipped-HELMET-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79", + "size": { + "x": 32, + "y": 32 }, - { - "name": "equipped-HELMET", - "directions": 4 - }, - { - "name": "inhand-left", - "directions": 4 - }, - { - "name": "inhand-right", - "directions": 4 - }, - { - "name": "equipped-HELMET-vulpkanin", - "directions": 4 - } - ] -} + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-HELMET", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "equipped-HELMET-vulpkanin", + "directions": 4 + }, + { + "name": "equipped-HELMET-vox", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Clothing/Head/Hoods/Bio/security.rsi/equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hoods/Bio/security.rsi/equipped-HELMET-vox.png new file mode 100644 index 0000000000..402d6bf76a Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hoods/Bio/security.rsi/equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hoods/Bio/security.rsi/meta.json b/Resources/Textures/Clothing/Head/Hoods/Bio/security.rsi/meta.json index e830cae282..6d404a14b8 100644 --- a/Resources/Textures/Clothing/Head/Hoods/Bio/security.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hoods/Bio/security.rsi/meta.json @@ -1,30 +1,34 @@ { - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d edited by Floofers", - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "icon" + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d edited by Floofers | equipped-HELMET-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79", + "size": { + "x": 32, + "y": 32 }, - { - "name": "equipped-HELMET", - "directions": 4 - }, - { - "name": "inhand-left", - "directions": 4 - }, - { - "name": "inhand-right", - "directions": 4 - }, - { - "name": "equipped-HELMET-vulpkanin", - "directions": 4 - } - ] -} + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-HELMET", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "equipped-HELMET-vulpkanin", + "directions": 4 + }, + { + "name": "equipped-HELMET-vox", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Clothing/Head/Hoods/Bio/virology.rsi/equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Hoods/Bio/virology.rsi/equipped-HELMET-vox.png new file mode 100644 index 0000000000..d4a1f0848e Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hoods/Bio/virology.rsi/equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Hoods/Bio/virology.rsi/meta.json b/Resources/Textures/Clothing/Head/Hoods/Bio/virology.rsi/meta.json index 4427c7fb51..d95bcad823 100644 --- a/Resources/Textures/Clothing/Head/Hoods/Bio/virology.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hoods/Bio/virology.rsi/meta.json @@ -1,30 +1,34 @@ { - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d", - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "icon" + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d | equipped-HELMET-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79", + "size": { + "x": 32, + "y": 32 }, - { - "name": "equipped-HELMET", - "directions": 4 - }, - { - "name": "inhand-left", - "directions": 4 - }, - { - "name": "inhand-right", - "directions": 4 - }, - { - "name": "equipped-HELMET-vulpkanin", - "directions": 4 - } - ] -} + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-HELMET", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "equipped-HELMET-vulpkanin", + "directions": 4 + }, + { + "name": "equipped-HELMET-vox", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Clothing/Head/Misc/chickenhead.rsi/equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Misc/chickenhead.rsi/equipped-HELMET-vox.png new file mode 100644 index 0000000000..65a8cb83c1 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Misc/chickenhead.rsi/equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Misc/chickenhead.rsi/meta.json b/Resources/Textures/Clothing/Head/Misc/chickenhead.rsi/meta.json index a470e00944..8e639d26bc 100644 --- a/Resources/Textures/Clothing/Head/Misc/chickenhead.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Misc/chickenhead.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. equipped-HELMET-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79 and modified to remove duplicate states", "size": { "x": 32, "y": 32 @@ -21,6 +21,10 @@ { "name": "inhand-right", "directions": 4 + }, + { + "name": "equipped-HELMET-vox", + "directions": 4 } ] } diff --git a/Resources/Textures/Clothing/Head/Welding/blue_flame_welding_mask.rsi/equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Welding/blue_flame_welding_mask.rsi/equipped-HELMET-vox.png new file mode 100644 index 0000000000..5b2c255049 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Welding/blue_flame_welding_mask.rsi/equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Welding/blue_flame_welding_mask.rsi/meta.json b/Resources/Textures/Clothing/Head/Welding/blue_flame_welding_mask.rsi/meta.json index 39eb54f44c..22039e8e30 100644 --- a/Resources/Textures/Clothing/Head/Welding/blue_flame_welding_mask.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Welding/blue_flame_welding_mask.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e, icon by лазік#7305 | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d edited by Floofers", + "copyright": "Taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79, icon by лазік#7305. equipped-HELMET-vox state modified by Flareguy from vox welding helmet state | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d edited by Floofers", "size": { "x": 32, "y": 32 @@ -21,6 +21,14 @@ "name": "up-equipped-HELMET", "directions": 4 }, + { + "name": "equipped-HELMET-vox", + "directions": 4 + }, + { + "name": "up-equipped-HELMET-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/Head/Welding/blue_flame_welding_mask.rsi/up-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Welding/blue_flame_welding_mask.rsi/up-equipped-HELMET-vox.png new file mode 100644 index 0000000000..14129a80cb Binary files /dev/null and b/Resources/Textures/Clothing/Head/Welding/blue_flame_welding_mask.rsi/up-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Welding/flame_welding_mask.rsi/equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Welding/flame_welding_mask.rsi/equipped-HELMET-vox.png new file mode 100644 index 0000000000..6401496252 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Welding/flame_welding_mask.rsi/equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Welding/flame_welding_mask.rsi/meta.json b/Resources/Textures/Clothing/Head/Welding/flame_welding_mask.rsi/meta.json index 39eb54f44c..d0931b9be1 100644 --- a/Resources/Textures/Clothing/Head/Welding/flame_welding_mask.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Welding/flame_welding_mask.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e, icon by лазік#7305 | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d edited by Floofers", + "copyright": "Taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79 and up-equipped-HELMET modified by Flareguy, icon by лазік#7305. equipped-HELMET-vox state modified by Flareguy from vox welding helmet state | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d edited by Floofers", "size": { "x": 32, "y": 32 @@ -21,6 +21,14 @@ "name": "up-equipped-HELMET", "directions": 4 }, + { + "name": "equipped-HELMET-vox", + "directions": 4 + }, + { + "name": "up-equipped-HELMET-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/Head/Welding/flame_welding_mask.rsi/up-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Welding/flame_welding_mask.rsi/up-equipped-HELMET-vox.png new file mode 100644 index 0000000000..d011e47c2d Binary files /dev/null and b/Resources/Textures/Clothing/Head/Welding/flame_welding_mask.rsi/up-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Welding/flame_welding_mask.rsi/up-equipped-HELMET.png b/Resources/Textures/Clothing/Head/Welding/flame_welding_mask.rsi/up-equipped-HELMET.png index b60219becc..e7ef24dcee 100644 Binary files a/Resources/Textures/Clothing/Head/Welding/flame_welding_mask.rsi/up-equipped-HELMET.png and b/Resources/Textures/Clothing/Head/Welding/flame_welding_mask.rsi/up-equipped-HELMET.png differ diff --git a/Resources/Textures/Clothing/Head/Welding/paintedwelding.rsi/equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Welding/paintedwelding.rsi/equipped-HELMET-vox.png new file mode 100644 index 0000000000..0b9342b236 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Welding/paintedwelding.rsi/equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Welding/paintedwelding.rsi/meta.json b/Resources/Textures/Clothing/Head/Welding/paintedwelding.rsi/meta.json index 39eb54f44c..825fa48b80 100644 --- a/Resources/Textures/Clothing/Head/Welding/paintedwelding.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Welding/paintedwelding.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e, icon by лазік#7305 | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d edited by Floofers", + "copyright": "Taken from CEV-Eris at https://github.com/discordia-space/CEV-Eris/blob/2a0d963d5bf68bd8ddf6fba6f60479bec172b51d/icons/inventory/head/mob.dmi, icon by лазік#7305. equipped-HELMET-vox state modified by Flareguy from 'welding' state in /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79 | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d edited by Floofers", "size": { "x": 32, "y": 32 @@ -21,6 +21,14 @@ "name": "up-equipped-HELMET", "directions": 4 }, + { + "name": "equipped-HELMET-vox", + "directions": 4 + }, + { + "name": "up-equipped-HELMET-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/Head/Welding/paintedwelding.rsi/up-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Welding/paintedwelding.rsi/up-equipped-HELMET-vox.png new file mode 100644 index 0000000000..6ecdfda3ea Binary files /dev/null and b/Resources/Textures/Clothing/Head/Welding/paintedwelding.rsi/up-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Welding/welding.rsi/equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Welding/welding.rsi/equipped-HELMET-vox.png new file mode 100644 index 0000000000..6b19f42a82 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Welding/welding.rsi/equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Head/Welding/welding.rsi/meta.json b/Resources/Textures/Clothing/Head/Welding/welding.rsi/meta.json index 3a84e3148e..569555b869 100644 --- a/Resources/Textures/Clothing/Head/Welding/welding.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Welding/welding.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d", + "copyright": "Taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79 | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d", "size": { "x": 32, "y": 32 @@ -21,6 +21,14 @@ "name": "up-equipped-HELMET", "directions": 4 }, + { + "name": "equipped-HELMET-vox", + "directions": 4 + }, + { + "name": "up-equipped-HELMET-vox", + "directions": 4 + }, { "name": "equipped-HELMET-hamster", "directions": 4 diff --git a/Resources/Textures/Clothing/Head/Welding/welding.rsi/up-equipped-HELMET-vox.png b/Resources/Textures/Clothing/Head/Welding/welding.rsi/up-equipped-HELMET-vox.png new file mode 100644 index 0000000000..b13423cd8b Binary files /dev/null and b/Resources/Textures/Clothing/Head/Welding/welding.rsi/up-equipped-HELMET-vox.png differ diff --git a/Resources/Textures/Clothing/Mask/bee.rsi/equipped-MASK-vox.png b/Resources/Textures/Clothing/Mask/bee.rsi/equipped-MASK-vox.png new file mode 100644 index 0000000000..05af835e9d Binary files /dev/null and b/Resources/Textures/Clothing/Mask/bee.rsi/equipped-MASK-vox.png differ diff --git a/Resources/Textures/Clothing/Mask/bee.rsi/meta.json b/Resources/Textures/Clothing/Mask/bee.rsi/meta.json index cc214ea433..628b5d1ffc 100644 --- a/Resources/Textures/Clothing/Mask/bee.rsi/meta.json +++ b/Resources/Textures/Clothing/Mask/bee.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/dannno/-tg-station/blob/a51c5a85d327f562004cef2f36c081ad0d95fd9a/icons/obj/clothing/masks.dmi. Reptilian edit by Nairod(Github)", + "copyright": "Taken from tgstation at commit https://github.com/dannno/-tg-station/blob/a51c5a85d327f562004cef2f36c081ad0d95fd9a/icons/obj/clothing/masks.dmi. Reptilian edit by Nairod(Github). equipped-MASK-vox state taken from Paradise at https://github.com/ParadiseSS13/Paradise/blob/bc095ad398790a2b718b2bab4f2157cdd80a51da/icons/mob/clothing/species/vox/mask.dmi", "size": { "x": 32, "y": 32 @@ -17,6 +17,10 @@ { "name": "equipped-MASK-reptilian", "directions": 4 + }, + { + "name": "equipped-MASK-vox", + "directions": 4 } ] } diff --git a/Resources/Textures/Clothing/Mask/breath.rsi/meta.json b/Resources/Textures/Clothing/Mask/breath.rsi/meta.json index bed9ae64b0..f0ec0118d8 100644 --- a/Resources/Textures/Clothing/Mask/breath.rsi/meta.json +++ b/Resources/Textures/Clothing/Mask/breath.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Reptilian edit by Nairod(Github) equipped-MASK-secdog modified from equipped-MASK-dog by TJohnson. | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Reptilian edit by Nairod(Github) equipped-MASK-secdog modified from equipped-MASK-dog by TJohnson. | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d | equipped-MASK-vox & up-equipped-MASK-vox state in /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/4638130fab5ff0e9faa220688811349d3297a33e", "size": { "x": 32, "y": 32 @@ -18,6 +18,14 @@ "name": "up-equipped-MASK", "directions": 4 }, + { + "name": "equipped-MASK-vox", + "directions": 4 + }, + { + "name": "up-equipped-MASK-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 @@ -54,10 +62,6 @@ "name": "equipped-MASK-possum", "directions": 4 }, - { - "name": "equipped-MASK-vox", - "directions": 4 - }, { "name": "equipped-MASK-pig", "directions": 4 diff --git a/Resources/Textures/Clothing/Mask/breath.rsi/up-equipped-MASK-vox.png b/Resources/Textures/Clothing/Mask/breath.rsi/up-equipped-MASK-vox.png new file mode 100644 index 0000000000..770ad70cf5 Binary files /dev/null and b/Resources/Textures/Clothing/Mask/breath.rsi/up-equipped-MASK-vox.png differ diff --git a/Resources/Textures/Clothing/Mask/clown.rsi/equipped-MASK-vox.png b/Resources/Textures/Clothing/Mask/clown.rsi/equipped-MASK-vox.png new file mode 100644 index 0000000000..0a7c6fc327 Binary files /dev/null and b/Resources/Textures/Clothing/Mask/clown.rsi/equipped-MASK-vox.png differ diff --git a/Resources/Textures/Clothing/Mask/clown.rsi/meta.json b/Resources/Textures/Clothing/Mask/clown.rsi/meta.json index b3709bcd83..6671a11d69 100644 --- a/Resources/Textures/Clothing/Mask/clown.rsi/meta.json +++ b/Resources/Textures/Clothing/Mask/clown.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Reptilian edit by Nairod(Github) | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d edited by Floofers", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Reptilian edit by Nairod(Github) | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d edited by Floofers | equipped-MASK-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/4638130fab5ff0e9faa220688811349d3297a33e and slightly modified by Flareguy", "size": { "x": 32, "y": 32 @@ -33,6 +33,10 @@ { "name": "equipped-MASK-reptilian", "directions": 4 + }, + { + "name": "equipped-MASK-vox", + "directions": 4 } ] } diff --git a/Resources/Textures/Clothing/Mask/gas.rsi/equipped-MASK-vox.png b/Resources/Textures/Clothing/Mask/gas.rsi/equipped-MASK-vox.png new file mode 100644 index 0000000000..7170d7ebdf Binary files /dev/null and b/Resources/Textures/Clothing/Mask/gas.rsi/equipped-MASK-vox.png differ diff --git a/Resources/Textures/Clothing/Mask/gas.rsi/meta.json b/Resources/Textures/Clothing/Mask/gas.rsi/meta.json index 07a0c908b1..7d52a23d9c 100644 --- a/Resources/Textures/Clothing/Mask/gas.rsi/meta.json +++ b/Resources/Textures/Clothing/Mask/gas.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Reptilian edit by Nairod(Github) | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d edited by Floofers", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Reptilian edit by Nairod(Github) | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d edited by Floofers | equipped-MASK-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/4638130fab5ff0e9faa220688811349d3297a33e and modified by Flareguy", "size": { "x": 32, "y": 32 @@ -33,6 +33,10 @@ { "name": "equipped-MASK-reptilian", "directions": 4 + }, + { + "name": "equipped-MASK-vox", + "directions": 4 } ] } diff --git a/Resources/Textures/Clothing/Mask/gasatmos.rsi/equipped-MASK-vox.png b/Resources/Textures/Clothing/Mask/gasatmos.rsi/equipped-MASK-vox.png new file mode 100644 index 0000000000..8975b1eb04 Binary files /dev/null and b/Resources/Textures/Clothing/Mask/gasatmos.rsi/equipped-MASK-vox.png differ diff --git a/Resources/Textures/Clothing/Mask/gasatmos.rsi/meta.json b/Resources/Textures/Clothing/Mask/gasatmos.rsi/meta.json index 63476f222b..6e2e524547 100644 --- a/Resources/Textures/Clothing/Mask/gasatmos.rsi/meta.json +++ b/Resources/Textures/Clothing/Mask/gasatmos.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Reptilian edit by Nairod(Github) | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d edited by Floofers", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Reptilian edit by Nairod(Github) | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d edited by Floofers | equipped-MASK-vox state modified by Flareguy from 'gas-alt' state in /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/4638130fab5ff0e9faa220688811349d3297a33e", "size": { "x": 32, "y": 32 @@ -29,6 +29,10 @@ { "name": "equipped-MASK-reptilian", "directions": 4 + }, + { + "name": "equipped-MASK-vox", + "directions": 4 } ] } diff --git a/Resources/Textures/Clothing/Mask/gascaptain.rsi/equipped-MASK-vox.png b/Resources/Textures/Clothing/Mask/gascaptain.rsi/equipped-MASK-vox.png new file mode 100644 index 0000000000..bc0f5e9011 Binary files /dev/null and b/Resources/Textures/Clothing/Mask/gascaptain.rsi/equipped-MASK-vox.png differ diff --git a/Resources/Textures/Clothing/Mask/gascaptain.rsi/meta.json b/Resources/Textures/Clothing/Mask/gascaptain.rsi/meta.json index 4b2d043352..332a8fed81 100644 --- a/Resources/Textures/Clothing/Mask/gascaptain.rsi/meta.json +++ b/Resources/Textures/Clothing/Mask/gascaptain.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e, edited by Emisse for ss14. Reptilian edit by Nairod(Github) | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d edited by Floofers", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e, edited by Emisse for ss14. Reptilian edit by Nairod(Github) | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d edited by Floofers | equipped-MASK-vox state taken from 'gas-alt' state in /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/4638130fab5ff0e9faa220688811349d3297a33e, and modified by Flareguy", "size": { "x": 32, "y": 32 @@ -29,6 +29,10 @@ { "name": "equipped-MASK-reptilian", "directions": 4 + }, + { + "name": "equipped-MASK-vox", + "directions": 4 } ] } diff --git a/Resources/Textures/Clothing/Mask/gasexplorer.rsi/equipped-MASK-vox.png b/Resources/Textures/Clothing/Mask/gasexplorer.rsi/equipped-MASK-vox.png new file mode 100644 index 0000000000..f504228f7d Binary files /dev/null and b/Resources/Textures/Clothing/Mask/gasexplorer.rsi/equipped-MASK-vox.png differ diff --git a/Resources/Textures/Clothing/Mask/gasexplorer.rsi/meta.json b/Resources/Textures/Clothing/Mask/gasexplorer.rsi/meta.json index 600054012e..42e2775eca 100644 --- a/Resources/Textures/Clothing/Mask/gasexplorer.rsi/meta.json +++ b/Resources/Textures/Clothing/Mask/gasexplorer.rsi/meta.json @@ -1,38 +1,42 @@ { - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Reptilian edit by Nairod(Github) | vulpkanin version edited by Floofers", - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "icon" + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Reptilian edit by Nairod(Github) | vulpkanin version edited by Floofers. Vox state by Flareguy for SS14", + "size": { + "x": 32, + "y": 32 }, - { - "name": "equipped-MASK", - "directions": 4 - }, - { - "name": "up-equipped-MASK", - "directions": 4 - }, - { - "name": "inhand-left", - "directions": 4 - }, - { - "name": "inhand-right", - "directions": 4 - }, - { - "name": "equipped-MASK-vulpkanin", - "directions": 4 - }, - { - "name": "equipped-MASK-reptilian", - "directions": 4 - } - ] -} + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-MASK", + "directions": 4 + }, + { + "name": "equipped-MASK-vox", + "directions": 4 + }, + { + "name": "up-equipped-MASK", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "equipped-MASK-vulpkanin", + "directions": 4 + }, + { + "name": "equipped-MASK-reptilian", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Clothing/Mask/gassecurity.rsi/equipped-MASK-vox.png b/Resources/Textures/Clothing/Mask/gassecurity.rsi/equipped-MASK-vox.png new file mode 100644 index 0000000000..ff33f30944 Binary files /dev/null and b/Resources/Textures/Clothing/Mask/gassecurity.rsi/equipped-MASK-vox.png differ diff --git a/Resources/Textures/Clothing/Mask/gassecurity.rsi/meta.json b/Resources/Textures/Clothing/Mask/gassecurity.rsi/meta.json index 9ed76c4839..a92fcef8bf 100644 --- a/Resources/Textures/Clothing/Mask/gassecurity.rsi/meta.json +++ b/Resources/Textures/Clothing/Mask/gassecurity.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Reptilian edit by Nairod(Github) | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Reptilian edit by Nairod(Github) | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d | equipped-MASK-vox & up-equipped-MASK-vox states taken from Paradise at https://github.com/ParadiseSS13/Paradise/blob/bc095ad398790a2b718b2bab4f2157cdd80a51da/icons/mob/clothing/species/vox/mask.dmi", "size": { "x": 32, "y": 32 @@ -14,24 +14,32 @@ "name": "equipped-MASK", "directions": 4 }, + { + "name": "equipped-MASK-reptilian", + "directions": 4 + }, + { + "name": "equipped-MASK-vox", + "directions": 4 + }, { "name": "up-equipped-MASK", "directions": 4 }, { - "name": "inhand-left", + "name": "up-equipped-MASK-vox", "directions": 4 }, { - "name": "inhand-right", + "name": "inhand-left", "directions": 4 }, { - "name": "equipped-MASK-vulpkanin", + "name": "inhand-right", "directions": 4 }, { - "name": "equipped-MASK-reptilian", + "name": "equipped-MASK-vulpkanin", "directions": 4 } ] diff --git a/Resources/Textures/Clothing/Mask/gassecurity.rsi/up-equipped-MASK-vox.png b/Resources/Textures/Clothing/Mask/gassecurity.rsi/up-equipped-MASK-vox.png new file mode 100644 index 0000000000..e701323f55 Binary files /dev/null and b/Resources/Textures/Clothing/Mask/gassecurity.rsi/up-equipped-MASK-vox.png differ diff --git a/Resources/Textures/Clothing/Mask/gassyndicate.rsi/equipped-MASK-vox.png b/Resources/Textures/Clothing/Mask/gassyndicate.rsi/equipped-MASK-vox.png new file mode 100644 index 0000000000..59a80479f7 Binary files /dev/null and b/Resources/Textures/Clothing/Mask/gassyndicate.rsi/equipped-MASK-vox.png differ diff --git a/Resources/Textures/Clothing/Mask/gassyndicate.rsi/meta.json b/Resources/Textures/Clothing/Mask/gassyndicate.rsi/meta.json index 1e01e88dc4..b2871a2797 100644 --- a/Resources/Textures/Clothing/Mask/gassyndicate.rsi/meta.json +++ b/Resources/Textures/Clothing/Mask/gassyndicate.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Reptilian edit by Nairod(Github) | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Reptilian edit by Nairod(Github) | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d | equipped-MASK-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/4638130fab5ff0e9faa220688811349d3297a33e and slightly modified to fix an error", "size": { "x": 32, "y": 32 @@ -47,6 +47,16 @@ [ 0.5, 0.5, 0.5 ], [ 0.5, 0.5, 0.5 ] ] + }, + { + "name": "equipped-MASK-vox", + "directions": 4, + "delays": [ + [ 0.5, 0.5, 0.5 ], + [ 0.5, 0.5, 0.5 ], + [ 0.5, 0.5, 0.5 ], + [ 0.5, 0.5, 0.5 ] + ] } ] } diff --git a/Resources/Textures/Clothing/Mask/italian_moustache.rsi/equipped-MASK-vox.png b/Resources/Textures/Clothing/Mask/italian_moustache.rsi/equipped-MASK-vox.png new file mode 100644 index 0000000000..9bd2b35d77 Binary files /dev/null and b/Resources/Textures/Clothing/Mask/italian_moustache.rsi/equipped-MASK-vox.png differ diff --git a/Resources/Textures/Clothing/Mask/italian_moustache.rsi/meta.json b/Resources/Textures/Clothing/Mask/italian_moustache.rsi/meta.json index 9621b18fe9..af0873d23a 100644 --- a/Resources/Textures/Clothing/Mask/italian_moustache.rsi/meta.json +++ b/Resources/Textures/Clothing/Mask/italian_moustache.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/6665eec76c98a4f3f89bebcd10b34b47dcc0b8ae | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/6665eec76c98a4f3f89bebcd10b34b47dcc0b8ae | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d | equipped-MASK-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-MASK", "directions": 4 }, + { + "name": "equipped-MASK-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/Mask/medical.rsi/equipped-MASK-vox.png b/Resources/Textures/Clothing/Mask/medical.rsi/equipped-MASK-vox.png new file mode 100644 index 0000000000..c3fa7f5917 Binary files /dev/null and b/Resources/Textures/Clothing/Mask/medical.rsi/equipped-MASK-vox.png differ diff --git a/Resources/Textures/Clothing/Mask/medical.rsi/meta.json b/Resources/Textures/Clothing/Mask/medical.rsi/meta.json index 59e292cbd4..ba8e8da405 100644 --- a/Resources/Textures/Clothing/Mask/medical.rsi/meta.json +++ b/Resources/Textures/Clothing/Mask/medical.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Reptilian edit by Nairod(Github) | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Reptilian edit by Nairod(Github) | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d | equipped-MASK-vox & up-equipped-MASK-vox states taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/4638130fab5ff0e9faa220688811349d3297a33e", "size": { "x": 32, "y": 32 @@ -75,6 +75,14 @@ { "name": "equipped-MASK-reptilian", "directions": 4 + }, + { + "name": "equipped-MASK-vox", + "directions": 4 + }, + { + "name": "up-equipped-MASK-vox", + "directions": 4 } ] } diff --git a/Resources/Textures/Clothing/Mask/medical.rsi/up-equipped-MASK-vox.png b/Resources/Textures/Clothing/Mask/medical.rsi/up-equipped-MASK-vox.png new file mode 100644 index 0000000000..770ad70cf5 Binary files /dev/null and b/Resources/Textures/Clothing/Mask/medical.rsi/up-equipped-MASK-vox.png differ diff --git a/Resources/Textures/Clothing/Mask/mime.rsi/equipped-MASK-vox.png b/Resources/Textures/Clothing/Mask/mime.rsi/equipped-MASK-vox.png new file mode 100644 index 0000000000..cab47297f0 Binary files /dev/null and b/Resources/Textures/Clothing/Mask/mime.rsi/equipped-MASK-vox.png differ diff --git a/Resources/Textures/Clothing/Mask/mime.rsi/meta.json b/Resources/Textures/Clothing/Mask/mime.rsi/meta.json index 34248cb9b9..e1a387cdd3 100644 --- a/Resources/Textures/Clothing/Mask/mime.rsi/meta.json +++ b/Resources/Textures/Clothing/Mask/mime.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Reptilian edit by Nairod(Github) | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d edited by Floofers", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Reptilian edit by Nairod(Github) | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d edited by Floofers | equipped-MASK-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/4638130fab5ff0e9faa220688811349d3297a33e", "size": { "x": 32, "y": 32 @@ -25,6 +25,10 @@ { "name": "equipped-MASK-reptilian", "directions": 4 + }, + { + "name": "equipped-MASK-vox", + "directions": 4 } ] } diff --git a/Resources/Textures/Clothing/Mask/muzzle.rsi/equipped-MASK-vox.png b/Resources/Textures/Clothing/Mask/muzzle.rsi/equipped-MASK-vox.png new file mode 100644 index 0000000000..529c87878e Binary files /dev/null and b/Resources/Textures/Clothing/Mask/muzzle.rsi/equipped-MASK-vox.png differ diff --git a/Resources/Textures/Clothing/Mask/muzzle.rsi/meta.json b/Resources/Textures/Clothing/Mask/muzzle.rsi/meta.json index 33176d73e7..d5fd7a68ee 100644 --- a/Resources/Textures/Clothing/Mask/muzzle.rsi/meta.json +++ b/Resources/Textures/Clothing/Mask/muzzle.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Reptilian edit by Nairod(Github) | vulpkanin version taken from https://github.com/SPLURT-Station/S.P.L.U.R.T-Station-13/commit/091d9ec00f186052b87bd65125e896f78faefe38", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Reptilian edit by Nairod(Github) | vulpkanin version taken from https://github.com/SPLURT-Station/S.P.L.U.R.T-Station-13/commit/091d9ec00f186052b87bd65125e896f78faefe38 | equipped-MASK-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/4638130fab5ff0e9faa220688811349d3297a33e", "size": { "x": 32, "y": 32 @@ -29,6 +29,10 @@ { "name": "equipped-MASK-reptilian", "directions": 4 + }, + { + "name": "equipped-MASK-vox", + "directions": 4 } ] } diff --git a/Resources/Textures/Clothing/Mask/ninja.rsi/equipped-MASK-vox.png b/Resources/Textures/Clothing/Mask/ninja.rsi/equipped-MASK-vox.png new file mode 100644 index 0000000000..924742fa94 Binary files /dev/null and b/Resources/Textures/Clothing/Mask/ninja.rsi/equipped-MASK-vox.png differ diff --git a/Resources/Textures/Clothing/Mask/ninja.rsi/meta.json b/Resources/Textures/Clothing/Mask/ninja.rsi/meta.json index da51268a11..26fff924c1 100644 --- a/Resources/Textures/Clothing/Mask/ninja.rsi/meta.json +++ b/Resources/Textures/Clothing/Mask/ninja.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from paradise at commit https://github.com/ParadiseSS13/Paradise/commit/33f7c1ef477fa67db5dda48078b469ab59aa7997 | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d edited by Floofers", + "copyright": "Taken from paradise at commit https://github.com/ParadiseSS13/Paradise/commit/33f7c1ef477fa67db5dda48078b469ab59aa7997 | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d edited by Floofers | equipped-MASK-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/4638130fab5ff0e9faa220688811349d3297a33e", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-MASK", "directions": 4 }, + { + "name": "equipped-MASK-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/Mask/plaguedoctormask.rsi/equipped-MASK-vox.png b/Resources/Textures/Clothing/Mask/plaguedoctormask.rsi/equipped-MASK-vox.png new file mode 100644 index 0000000000..67d3c25cac Binary files /dev/null and b/Resources/Textures/Clothing/Mask/plaguedoctormask.rsi/equipped-MASK-vox.png differ diff --git a/Resources/Textures/Clothing/Mask/plaguedoctormask.rsi/meta.json b/Resources/Textures/Clothing/Mask/plaguedoctormask.rsi/meta.json index 0f2358a55f..4536363585 100644 --- a/Resources/Textures/Clothing/Mask/plaguedoctormask.rsi/meta.json +++ b/Resources/Textures/Clothing/Mask/plaguedoctormask.rsi/meta.json @@ -1,30 +1,34 @@ -{ - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Taken from TGstation github https://github.com/tgstation/tgstation/commit/e89db4dd4f42377b0adafb06806a763314a89034 , edited by Alekshhh | vulpkanin version taken from https://github.com/SPLURT-Station/S.P.L.U.R.T-Station-13/commit/091d9ec00f186052b87bd65125e896f78faefe38", - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "icon" - }, - { - "name": "equipped-MASK", - "directions": 4 - }, - { - "name": "inhand-left", - "directions": 4 - }, - { - "name": "inhand-right", - "directions": 4 - }, - { - "name": "equipped-MASK-vulpkanin", - "directions": 4 - } - ] -} +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from TGstation github https://github.com/tgstation/tgstation/commit/e89db4dd4f42377b0adafb06806a763314a89034 , edited by Alekshhh | vulpkanin version taken from https://github.com/SPLURT-Station/S.P.L.U.R.T-Station-13/commit/091d9ec00f186052b87bd65125e896f78faefe38 | equipped-MASK-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/4638130fab5ff0e9faa220688811349d3297a33e", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-MASK", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "equipped-MASK-vulpkanin", + "directions": 4 + }, + { + "name": "equipped-MASK-vox", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/Mask/sexyclown.rsi/equipped-MASK-vox.png b/Resources/Textures/Clothing/Mask/sexyclown.rsi/equipped-MASK-vox.png new file mode 100644 index 0000000000..b1cb32a157 Binary files /dev/null and b/Resources/Textures/Clothing/Mask/sexyclown.rsi/equipped-MASK-vox.png differ diff --git a/Resources/Textures/Clothing/Mask/sexyclown.rsi/meta.json b/Resources/Textures/Clothing/Mask/sexyclown.rsi/meta.json index da5fdbe7cd..9e577533fc 100644 --- a/Resources/Textures/Clothing/Mask/sexyclown.rsi/meta.json +++ b/Resources/Textures/Clothing/Mask/sexyclown.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/blob/ed466a4c67828b44ddb9d9550366be5c2d745955/icons/obj/clothing/masks.dmi. Reptilian edit by Nairod(Github) | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/blob/ed466a4c67828b44ddb9d9550366be5c2d745955/icons/obj/clothing/masks.dmi. Reptilian edit by Nairod(Github) | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d | equipped-MASK-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/4638130fab5ff0e9faa220688811349d3297a33e", "size": { "x": 32, "y": 32 @@ -21,6 +21,10 @@ { "name": "equipped-MASK-reptilian", "directions": 4 + }, + { + "name": "equipped-MASK-vox", + "directions": 4 } ] } diff --git a/Resources/Textures/Clothing/Mask/sexymime.rsi/equipped-MASK-vox.png b/Resources/Textures/Clothing/Mask/sexymime.rsi/equipped-MASK-vox.png new file mode 100644 index 0000000000..4ceaf59677 Binary files /dev/null and b/Resources/Textures/Clothing/Mask/sexymime.rsi/equipped-MASK-vox.png differ diff --git a/Resources/Textures/Clothing/Mask/sexymime.rsi/meta.json b/Resources/Textures/Clothing/Mask/sexymime.rsi/meta.json index 8c0842d68a..747190632b 100644 --- a/Resources/Textures/Clothing/Mask/sexymime.rsi/meta.json +++ b/Resources/Textures/Clothing/Mask/sexymime.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/blob/ed466a4c67828b44ddb9d9550366be5c2d745955/icons/obj/clothing/masks.dmi. Reptilian edit by Nairod(Github) | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d edited by Floofers", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/blob/ed466a4c67828b44ddb9d9550366be5c2d745955/icons/obj/clothing/masks.dmi. Reptilian edit by Nairod(Github) | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d edited by Floofers | equipped-MASK-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/4638130fab5ff0e9faa220688811349d3297a33e", "size": { "x": 32, "y": 32 @@ -21,6 +21,10 @@ { "name": "equipped-MASK-reptilian", "directions": 4 + }, + { + "name": "equipped-MASK-vox", + "directions": 4 } ] } diff --git a/Resources/Textures/Clothing/Mask/sterile.rsi/equipped-MASK-vox.png b/Resources/Textures/Clothing/Mask/sterile.rsi/equipped-MASK-vox.png new file mode 100644 index 0000000000..18c64e1048 Binary files /dev/null and b/Resources/Textures/Clothing/Mask/sterile.rsi/equipped-MASK-vox.png differ diff --git a/Resources/Textures/Clothing/Mask/sterile.rsi/meta.json b/Resources/Textures/Clothing/Mask/sterile.rsi/meta.json index f61bba1a32..4115a8de19 100644 --- a/Resources/Textures/Clothing/Mask/sterile.rsi/meta.json +++ b/Resources/Textures/Clothing/Mask/sterile.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Reptilian edit by Nairod(Github) | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Reptilian edit by Nairod(Github) | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d | equipped-MASK-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/4638130fab5ff0e9faa220688811349d3297a33e", "size": { "x": 32, "y": 32 @@ -14,10 +14,22 @@ "name": "equipped-MASK", "directions": 4 }, + { + "name": "equipped-MASK-reptilian", + "directions": 4 + }, + { + "name": "equipped-MASK-vox", + "directions": 4 + }, { "name": "up-equipped-MASK", "directions": 4 }, + { + "name": "up-equipped-MASK-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 @@ -33,10 +45,6 @@ { "name": "up-equipped-MASK-vulpkanin", "directions": 4 - }, - { - "name": "equipped-MASK-reptilian", - "directions": 4 } ] } diff --git a/Resources/Textures/Clothing/Mask/sterile.rsi/up-equipped-MASK-vox.png b/Resources/Textures/Clothing/Mask/sterile.rsi/up-equipped-MASK-vox.png new file mode 100644 index 0000000000..770ad70cf5 Binary files /dev/null and b/Resources/Textures/Clothing/Mask/sterile.rsi/up-equipped-MASK-vox.png differ diff --git a/Resources/Textures/Clothing/Mask/swat.rsi/equipped-MASK-vox.png b/Resources/Textures/Clothing/Mask/swat.rsi/equipped-MASK-vox.png new file mode 100644 index 0000000000..d654e3493c Binary files /dev/null and b/Resources/Textures/Clothing/Mask/swat.rsi/equipped-MASK-vox.png differ diff --git a/Resources/Textures/Clothing/Mask/swat.rsi/meta.json b/Resources/Textures/Clothing/Mask/swat.rsi/meta.json index fb3b9e8f4a..9b8c7ad080 100644 --- a/Resources/Textures/Clothing/Mask/swat.rsi/meta.json +++ b/Resources/Textures/Clothing/Mask/swat.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/RemieRichards/-tg-station/blob/f8c05e21694cd3cb703e40edc5cfc375017944b1/icons/obj/clothing/masks.dmi. Reptilian edit by Nairod(Github) | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d", + "copyright": "Taken from tgstation at commit https://github.com/RemieRichards/-tg-station/blob/f8c05e21694cd3cb703e40edc5cfc375017944b1/icons/obj/clothing/masks.dmi. Reptilian edit by Nairod(Github) | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d | equipped-MASK-vox state taken from Paradise at https://github.com/ParadiseSS13/Paradise/blob/bc095ad398790a2b718b2bab4f2157cdd80a51da/icons/mob/clothing/species/vox/mask.dmi", "size": { "x": 32, "y": 32 @@ -15,19 +15,23 @@ "directions": 4 }, { - "name": "inhand-left", + "name": "equipped-MASK-reptilian", "directions": 4 }, { - "name": "inhand-right", + "name": "equipped-MASK-vox", "directions": 4 }, { - "name": "equipped-MASK-vulpkanin", + "name": "inhand-left", "directions": 4 }, { - "name": "equipped-MASK-reptilian", + "name": "inhand-right", + "directions": 4 + }, + { + "name": "equipped-MASK-vulpkanin", "directions": 4 } ] diff --git a/Resources/Textures/Clothing/Mask/welding-gas.rsi/equipped-MASK-vox.png b/Resources/Textures/Clothing/Mask/welding-gas.rsi/equipped-MASK-vox.png new file mode 100644 index 0000000000..e6028f3a4c Binary files /dev/null and b/Resources/Textures/Clothing/Mask/welding-gas.rsi/equipped-MASK-vox.png differ diff --git a/Resources/Textures/Clothing/Mask/welding-gas.rsi/equipped-MASK.png b/Resources/Textures/Clothing/Mask/welding-gas.rsi/equipped-MASK.png new file mode 100644 index 0000000000..5d0a7ea70d Binary files /dev/null and b/Resources/Textures/Clothing/Mask/welding-gas.rsi/equipped-MASK.png differ diff --git a/Resources/Textures/Clothing/Mask/welding-gas.rsi/icon-up.png b/Resources/Textures/Clothing/Mask/welding-gas.rsi/icon-up.png new file mode 100644 index 0000000000..352e536ca9 Binary files /dev/null and b/Resources/Textures/Clothing/Mask/welding-gas.rsi/icon-up.png differ diff --git a/Resources/Textures/Clothing/Mask/welding-gas.rsi/icon.png b/Resources/Textures/Clothing/Mask/welding-gas.rsi/icon.png new file mode 100644 index 0000000000..cdb8d4b7c7 Binary files /dev/null and b/Resources/Textures/Clothing/Mask/welding-gas.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Mask/welding-gas.rsi/inhand-left.png b/Resources/Textures/Clothing/Mask/welding-gas.rsi/inhand-left.png new file mode 100644 index 0000000000..c5111a66a9 Binary files /dev/null and b/Resources/Textures/Clothing/Mask/welding-gas.rsi/inhand-left.png differ diff --git a/Resources/Textures/Clothing/Mask/welding-gas.rsi/inhand-right.png b/Resources/Textures/Clothing/Mask/welding-gas.rsi/inhand-right.png new file mode 100644 index 0000000000..5841f0b846 Binary files /dev/null and b/Resources/Textures/Clothing/Mask/welding-gas.rsi/inhand-right.png differ diff --git a/Resources/Textures/Clothing/Mask/welding-gas.rsi/meta.json b/Resources/Textures/Clothing/Mask/welding-gas.rsi/meta.json new file mode 100644 index 0000000000..8e9b857325 --- /dev/null +++ b/Resources/Textures/Clothing/Mask/welding-gas.rsi/meta.json @@ -0,0 +1,49 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/6f5ca45e3ac06b30fb1957042214a888d8c01722. Inhands, worn sprites, and vox sprites created by EmoGarbage404 (github)", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "icon-up" + }, + { + "name": "equipped-MASK", + "directions": 4 + }, + { + "name": "up-equipped-MASK", + "directions": 4 + }, + { + "name": "equipped-MASK-vox", + "directions": 4 + }, + { + "name": "up-equipped-MASK-vox", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "up-inhand-left", + "directions": 4 + }, + { + "name": "up-inhand-right", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/Mask/welding-gas.rsi/up-equipped-MASK-vox.png b/Resources/Textures/Clothing/Mask/welding-gas.rsi/up-equipped-MASK-vox.png new file mode 100644 index 0000000000..90871d7d00 Binary files /dev/null and b/Resources/Textures/Clothing/Mask/welding-gas.rsi/up-equipped-MASK-vox.png differ diff --git a/Resources/Textures/Clothing/Mask/welding-gas.rsi/up-equipped-MASK.png b/Resources/Textures/Clothing/Mask/welding-gas.rsi/up-equipped-MASK.png new file mode 100644 index 0000000000..449c8624c9 Binary files /dev/null and b/Resources/Textures/Clothing/Mask/welding-gas.rsi/up-equipped-MASK.png differ diff --git a/Resources/Textures/Clothing/Mask/welding-gas.rsi/up-inhand-left.png b/Resources/Textures/Clothing/Mask/welding-gas.rsi/up-inhand-left.png new file mode 100644 index 0000000000..2868ab8317 Binary files /dev/null and b/Resources/Textures/Clothing/Mask/welding-gas.rsi/up-inhand-left.png differ diff --git a/Resources/Textures/Clothing/Mask/welding-gas.rsi/up-inhand-right.png b/Resources/Textures/Clothing/Mask/welding-gas.rsi/up-inhand-right.png new file mode 100644 index 0000000000..1362d99011 Binary files /dev/null and b/Resources/Textures/Clothing/Mask/welding-gas.rsi/up-inhand-right.png differ diff --git a/Resources/Textures/Clothing/Neck/Misc/whistles.rsi/meta.json b/Resources/Textures/Clothing/Neck/Misc/whistles.rsi/meta.json deleted file mode 100644 index 31cda14261..0000000000 --- a/Resources/Textures/Clothing/Neck/Misc/whistles.rsi/meta.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Drawn by Firewatchin on Discord.", - "size": { - "x": 32, - "y": 32 - }, - - "states": [ - { - "name": "equipped-NECK", - "directions": 4 - }, - { - "name": "icon" - } - ] -} diff --git a/Resources/Textures/Clothing/OuterClothing/Armor/armor_reflec.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Armor/armor_reflec.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..9d92b21329 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Armor/armor_reflec.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Armor/armor_reflec.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Armor/armor_reflec.rsi/meta.json index c7a0afcae3..5f95573206 100644 --- a/Resources/Textures/Clothing/OuterClothing/Armor/armor_reflec.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Armor/armor_reflec.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/dbc2435fa562f4ef130a7112d2a4cc9c80099894", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/dbc2435fa562f4ef130a7112d2a4cc9c80099894. equipped-OUTERCLOTHING-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Armor/bulletproof.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Armor/bulletproof.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..cffb51e5e5 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Armor/bulletproof.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Armor/bulletproof.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Armor/bulletproof.rsi/meta.json index 9ae7fc3f93..8b7a929fcd 100644 --- a/Resources/Textures/Clothing/OuterClothing/Armor/bulletproof.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Armor/bulletproof.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from https://github.com/ParadiseSS13/Paradise", + "copyright": "Taken from https://github.com/ParadiseSS13/Paradise. Vox states by Flareguy for Space Station 14", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Armor/captain_carapace.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Armor/captain_carapace.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..e0bfdfbc38 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Armor/captain_carapace.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Armor/captain_carapace.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Armor/captain_carapace.rsi/meta.json index b979f8bfb8..a9366f0bc1 100644 --- a/Resources/Textures/Clothing/OuterClothing/Armor/captain_carapace.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Armor/captain_carapace.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/pull/69842/commits/d8138946b0ed06fced522729ac8eaa0596864329 edited by Skarletto (github), edited by Emisse for ss14", + "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/pull/69842/commits/d8138946b0ed06fced522729ac8eaa0596864329 edited by Skarletto (github), edited by Emisse for ss14. Vox state by Flareguy for SS14", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Armor/cult_armour.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Armor/cult_armour.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..599e73c76f Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Armor/cult_armour.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Armor/cult_armour.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Armor/cult_armour.rsi/meta.json index e482264df5..5c326232e4 100644 --- a/Resources/Textures/Clothing/OuterClothing/Armor/cult_armour.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Armor/cult_armour.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. equipped-OUTERCLOTHING-vox state taken from Paradise at https://github.com/ParadiseSS13/Paradise/blob/fce54deb01d465957691ba2907218d4af4830db2/icons/mob/clothing/species/vox/suit.dmi and north sprite modified", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Armor/heavygreen.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Armor/heavygreen.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..f37eba3359 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Armor/heavygreen.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Armor/heavygreen.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Armor/heavygreen.rsi/meta.json index e482264df5..232bec9c0a 100644 --- a/Resources/Textures/Clothing/OuterClothing/Armor/heavygreen.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Armor/heavygreen.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. equipped-OUTERCLOTHING-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Armor/heavyred.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Armor/heavyred.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..164f5e4592 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Armor/heavyred.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Armor/heavyred.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Armor/heavyred.rsi/meta.json index e482264df5..232bec9c0a 100644 --- a/Resources/Textures/Clothing/OuterClothing/Armor/heavyred.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Armor/heavyred.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. equipped-OUTERCLOTHING-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Armor/riot.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Armor/riot.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..f0e99ee3c3 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Armor/riot.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Armor/riot.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Armor/riot.rsi/meta.json index afdd4120b5..26631d0281 100644 --- a/Resources/Textures/Clothing/OuterClothing/Armor/riot.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Armor/riot.rsi/meta.json @@ -1,26 +1,30 @@ { - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Taken from https://github.com/ParadiseSS13/Paradise", - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "icon" + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from https://github.com/ParadiseSS13/Paradise. Vox state by Flareguy for Space Station 14", + "size": { + "x": 32, + "y": 32 }, - { - "name": "equipped-OUTERCLOTHING", - "directions": 4 - }, - { - "name": "inhand-left", - "directions": 4 - }, - { - "name": "inhand-right", - "directions": 4 - } - ] -} + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-OUTERCLOTHING", + "directions": 4 + }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Clothing/OuterClothing/Armor/security.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Armor/security.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..1d9b1d1459 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Armor/security.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Armor/security.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Armor/security.rsi/meta.json index afdd4120b5..00bef7108a 100644 --- a/Resources/Textures/Clothing/OuterClothing/Armor/security.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Armor/security.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from https://github.com/ParadiseSS13/Paradise", + "copyright": "Taken from https://github.com/ParadiseSS13/Paradise. Vox state by Flareguy for Space Station 14", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Armor/security_slim.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Armor/security_slim.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..ea3c78dfbe Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Armor/security_slim.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Armor/security_slim.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Armor/security_slim.rsi/meta.json index afdd4120b5..26631d0281 100644 --- a/Resources/Textures/Clothing/OuterClothing/Armor/security_slim.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Armor/security_slim.rsi/meta.json @@ -1,26 +1,30 @@ { - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Taken from https://github.com/ParadiseSS13/Paradise", - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "icon" + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from https://github.com/ParadiseSS13/Paradise. Vox state by Flareguy for Space Station 14", + "size": { + "x": 32, + "y": 32 }, - { - "name": "equipped-OUTERCLOTHING", - "directions": 4 - }, - { - "name": "inhand-left", - "directions": 4 - }, - { - "name": "inhand-right", - "directions": 4 - } - ] -} + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-OUTERCLOTHING", + "directions": 4 + }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Clothing/OuterClothing/Bio/cmo.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Bio/cmo.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..e62fa89ab5 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Bio/cmo.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Bio/cmo.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Bio/cmo.rsi/meta.json index e482264df5..ca1146d007 100644 --- a/Resources/Textures/Clothing/OuterClothing/Bio/cmo.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Bio/cmo.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. equipped-OUTERCLOTHING-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79", "size": { "x": 32, "y": 32 @@ -21,6 +21,10 @@ { "name": "inhand-right", "directions": 4 + }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 } ] } diff --git a/Resources/Textures/Clothing/OuterClothing/Bio/general.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Bio/general.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..0a224c92b9 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Bio/general.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Bio/general.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Bio/general.rsi/meta.json index e482264df5..232bec9c0a 100644 --- a/Resources/Textures/Clothing/OuterClothing/Bio/general.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Bio/general.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. equipped-OUTERCLOTHING-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Bio/janitor.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Bio/janitor.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..82b0ea9f36 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Bio/janitor.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Bio/janitor.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Bio/janitor.rsi/meta.json index e482264df5..ca1146d007 100644 --- a/Resources/Textures/Clothing/OuterClothing/Bio/janitor.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Bio/janitor.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. equipped-OUTERCLOTHING-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79", "size": { "x": 32, "y": 32 @@ -21,6 +21,10 @@ { "name": "inhand-right", "directions": 4 + }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 } ] } diff --git a/Resources/Textures/Clothing/OuterClothing/Bio/scientist.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Bio/scientist.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..044d87e2f6 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Bio/scientist.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Bio/scientist.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Bio/scientist.rsi/meta.json index e482264df5..ca1146d007 100644 --- a/Resources/Textures/Clothing/OuterClothing/Bio/scientist.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Bio/scientist.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. equipped-OUTERCLOTHING-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79", "size": { "x": 32, "y": 32 @@ -21,6 +21,10 @@ { "name": "inhand-right", "directions": 4 + }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 } ] } diff --git a/Resources/Textures/Clothing/OuterClothing/Bio/security.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Bio/security.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..e426556e7d Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Bio/security.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Bio/security.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Bio/security.rsi/meta.json index e482264df5..ca1146d007 100644 --- a/Resources/Textures/Clothing/OuterClothing/Bio/security.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Bio/security.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. equipped-OUTERCLOTHING-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79", "size": { "x": 32, "y": 32 @@ -21,6 +21,10 @@ { "name": "inhand-right", "directions": 4 + }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 } ] } diff --git a/Resources/Textures/Clothing/OuterClothing/Bio/virology.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Bio/virology.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..f6b5c43d2b Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Bio/virology.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Bio/virology.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Bio/virology.rsi/meta.json index e482264df5..ca1146d007 100644 --- a/Resources/Textures/Clothing/OuterClothing/Bio/virology.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Bio/virology.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. equipped-OUTERCLOTHING-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79", "size": { "x": 32, "y": 32 @@ -21,6 +21,10 @@ { "name": "inhand-right", "directions": 4 + }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 } ] } diff --git a/Resources/Textures/Clothing/OuterClothing/Coats/expensive_coat.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Coats/expensive_coat.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..1fd6c3828d Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Coats/expensive_coat.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Coats/expensive_coat.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Coats/expensive_coat.rsi/meta.json index 20e876460d..cc68d97d37 100644 --- a/Resources/Textures/Clothing/OuterClothing/Coats/expensive_coat.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Coats/expensive_coat.rsi/meta.json @@ -11,6 +11,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "icon" } diff --git a/Resources/Textures/Clothing/OuterClothing/Coats/hos_trenchcoat.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Coats/hos_trenchcoat.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..31e6f31708 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Coats/hos_trenchcoat.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Coats/hos_trenchcoat.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Coats/hos_trenchcoat.rsi/meta.json index 55e082d620..fa20ea7d59 100644 --- a/Resources/Textures/Clothing/OuterClothing/Coats/hos_trenchcoat.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Coats/hos_trenchcoat.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e , edited by Alekshhh", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e , edited by Alekshhh. Vox state made by Flareguy for SS14", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Coats/jensencoat.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Coats/jensencoat.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..59bf1f287b Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Coats/jensencoat.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Coats/jensencoat.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Coats/jensencoat.rsi/meta.json index e482264df5..232bec9c0a 100644 --- a/Resources/Textures/Clothing/OuterClothing/Coats/jensencoat.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Coats/jensencoat.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. equipped-OUTERCLOTHING-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Coats/labcoat.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Coats/labcoat.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..fa32996aa8 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Coats/labcoat.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Coats/labcoat.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Coats/labcoat.rsi/meta.json index 442bfb2920..2311851853 100644 --- a/Resources/Textures/Clothing/OuterClothing/Coats/labcoat.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Coats/labcoat.rsi/meta.json @@ -21,6 +21,14 @@ "name": "open-equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, + { + "name": "open-equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Coats/labcoat.rsi/open-equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Coats/labcoat.rsi/open-equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..ce7c029026 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Coats/labcoat.rsi/open-equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Coats/labcoat_chem.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Coats/labcoat_chem.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..6d96d0a037 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Coats/labcoat_chem.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Coats/labcoat_chem.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Coats/labcoat_chem.rsi/meta.json index 442bfb2920..2311851853 100644 --- a/Resources/Textures/Clothing/OuterClothing/Coats/labcoat_chem.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Coats/labcoat_chem.rsi/meta.json @@ -21,6 +21,14 @@ "name": "open-equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, + { + "name": "open-equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Coats/labcoat_chem.rsi/open-equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Coats/labcoat_chem.rsi/open-equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..f83538ee60 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Coats/labcoat_chem.rsi/open-equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Coats/labcoat_cmo.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Coats/labcoat_cmo.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..58b84263ac Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Coats/labcoat_cmo.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Coats/labcoat_cmo.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Coats/labcoat_cmo.rsi/meta.json index 02abb07806..c2b48b1d76 100644 --- a/Resources/Textures/Clothing/OuterClothing/Coats/labcoat_cmo.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Coats/labcoat_cmo.rsi/meta.json @@ -21,6 +21,14 @@ "name": "open-equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, + { + "name": "open-equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Coats/labcoat_cmo.rsi/open-equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Coats/labcoat_cmo.rsi/open-equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..547839061b Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Coats/labcoat_cmo.rsi/open-equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Coats/labcoat_gene.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Coats/labcoat_gene.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..fa9a53d373 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Coats/labcoat_gene.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Coats/labcoat_gene.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Coats/labcoat_gene.rsi/meta.json index 442bfb2920..2311851853 100644 --- a/Resources/Textures/Clothing/OuterClothing/Coats/labcoat_gene.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Coats/labcoat_gene.rsi/meta.json @@ -21,6 +21,14 @@ "name": "open-equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, + { + "name": "open-equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Coats/labcoat_gene.rsi/open-equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Coats/labcoat_gene.rsi/open-equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..9cba3e8ef3 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Coats/labcoat_gene.rsi/open-equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Coats/labcoat_viro.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Coats/labcoat_viro.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..e432bb2d01 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Coats/labcoat_viro.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Coats/labcoat_viro.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Coats/labcoat_viro.rsi/meta.json index 6cb239d8c8..4d788f71ee 100644 --- a/Resources/Textures/Clothing/OuterClothing/Coats/labcoat_viro.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Coats/labcoat_viro.rsi/meta.json @@ -21,6 +21,14 @@ "name": "open-equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, + { + "name": "open-equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Coats/labcoat_viro.rsi/open-equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Coats/labcoat_viro.rsi/open-equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..2363b4118d Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Coats/labcoat_viro.rsi/open-equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Coats/pirate.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Coats/pirate.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..1679773c73 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Coats/pirate.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Coats/pirate.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Coats/pirate.rsi/meta.json index e482264df5..232bec9c0a 100644 --- a/Resources/Textures/Clothing/OuterClothing/Coats/pirate.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Coats/pirate.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. equipped-OUTERCLOTHING-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Coats/rndcoat.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Coats/rndcoat.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..89369f8794 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Coats/rndcoat.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Coats/rndcoat.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Coats/rndcoat.rsi/meta.json index 442bfb2920..2311851853 100644 --- a/Resources/Textures/Clothing/OuterClothing/Coats/rndcoat.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Coats/rndcoat.rsi/meta.json @@ -21,6 +21,14 @@ "name": "open-equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, + { + "name": "open-equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Coats/rndcoat.rsi/open-equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Coats/rndcoat.rsi/open-equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..973451e5f5 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Coats/rndcoat.rsi/open-equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Coats/warden.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Coats/warden.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..a56db4fd53 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Coats/warden.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Coats/warden.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Coats/warden.rsi/meta.json index e482264df5..232bec9c0a 100644 --- a/Resources/Textures/Clothing/OuterClothing/Coats/warden.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Coats/warden.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. equipped-OUTERCLOTHING-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/ERTSuits/ertengineer.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Hardsuits/ERTSuits/ertengineer.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..be85f19ea6 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Hardsuits/ERTSuits/ertengineer.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/ERTSuits/ertengineer.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Hardsuits/ERTSuits/ertengineer.rsi/meta.json index 4fbd43a08b..b7b0719efa 100644 --- a/Resources/Textures/Clothing/OuterClothing/Hardsuits/ERTSuits/ertengineer.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Hardsuits/ERTSuits/ertengineer.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from paradisestation at commit https://github.com/ParadiseSS13/Paradise/commit/12c21ced8432015485484b17e311dcceb7c458f6", + "copyright": "Taken from paradisestation at commit https://github.com/ParadiseSS13/Paradise/commit/12c21ced8432015485484b17e311dcceb7c458f6. Vox state by Flareguy for Space Station 14", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/ERTSuits/ertjanitor.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Hardsuits/ERTSuits/ertjanitor.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..e5f648a31f Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Hardsuits/ERTSuits/ertjanitor.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/ERTSuits/ertjanitor.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Hardsuits/ERTSuits/ertjanitor.rsi/meta.json index 4fbd43a08b..6de2d485b8 100644 --- a/Resources/Textures/Clothing/OuterClothing/Hardsuits/ERTSuits/ertjanitor.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Hardsuits/ERTSuits/ertjanitor.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from paradisestation at commit https://github.com/ParadiseSS13/Paradise/commit/12c21ced8432015485484b17e311dcceb7c458f6", + "copyright": "Taken from paradisestation at commit https://github.com/ParadiseSS13/Paradise/commit/12c21ced8432015485484b17e311dcceb7c458f6. Vox sprite by Flareguy for Space Station 14", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/ERTSuits/ertleader.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Hardsuits/ERTSuits/ertleader.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..cb2c630801 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Hardsuits/ERTSuits/ertleader.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/ERTSuits/ertleader.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Hardsuits/ERTSuits/ertleader.rsi/meta.json index 4fbd43a08b..6de2d485b8 100644 --- a/Resources/Textures/Clothing/OuterClothing/Hardsuits/ERTSuits/ertleader.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Hardsuits/ERTSuits/ertleader.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from paradisestation at commit https://github.com/ParadiseSS13/Paradise/commit/12c21ced8432015485484b17e311dcceb7c458f6", + "copyright": "Taken from paradisestation at commit https://github.com/ParadiseSS13/Paradise/commit/12c21ced8432015485484b17e311dcceb7c458f6. Vox sprite by Flareguy for Space Station 14", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/ERTSuits/ertmedical.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Hardsuits/ERTSuits/ertmedical.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..284954aabc Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Hardsuits/ERTSuits/ertmedical.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/ERTSuits/ertmedical.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Hardsuits/ERTSuits/ertmedical.rsi/meta.json index 4fbd43a08b..daa64b2f5c 100644 --- a/Resources/Textures/Clothing/OuterClothing/Hardsuits/ERTSuits/ertmedical.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Hardsuits/ERTSuits/ertmedical.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from paradisestation at commit https://github.com/ParadiseSS13/Paradise/commit/12c21ced8432015485484b17e311dcceb7c458f6", + "copyright": "Taken from paradisestation at commit https://github.com/ParadiseSS13/Paradise/commit/12c21ced8432015485484b17e311dcceb7c458f6. Vox sprite made by Flareguy", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/ERTSuits/ertsecurity.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Hardsuits/ERTSuits/ertsecurity.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..29130c57f0 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Hardsuits/ERTSuits/ertsecurity.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/ERTSuits/ertsecurity.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Hardsuits/ERTSuits/ertsecurity.rsi/meta.json index 4fbd43a08b..6de2d485b8 100644 --- a/Resources/Textures/Clothing/OuterClothing/Hardsuits/ERTSuits/ertsecurity.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Hardsuits/ERTSuits/ertsecurity.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from paradisestation at commit https://github.com/ParadiseSS13/Paradise/commit/12c21ced8432015485484b17e311dcceb7c458f6", + "copyright": "Taken from paradisestation at commit https://github.com/ParadiseSS13/Paradise/commit/12c21ced8432015485484b17e311dcceb7c458f6. Vox sprite by Flareguy for Space Station 14", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/atmospherics.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Hardsuits/atmospherics.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..b896fbef73 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Hardsuits/atmospherics.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/atmospherics.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Hardsuits/atmospherics.rsi/meta.json index ef5e62cca4..2da9e2ad1d 100644 --- a/Resources/Textures/Clothing/OuterClothing/Hardsuits/atmospherics.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Hardsuits/atmospherics.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Vox state made by Flareguy for SS14", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/capspace.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Hardsuits/capspace.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..644c67846b Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Hardsuits/capspace.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/capspace.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Hardsuits/capspace.rsi/meta.json index 7f50fdcf08..3271d26a2c 100644 --- a/Resources/Textures/Clothing/OuterClothing/Hardsuits/capspace.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Hardsuits/capspace.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Made by Emisse for SS14", + "copyright": "Made by Emisse for SS14. Vox state made by Flareguy for SS14", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/cburn.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Hardsuits/cburn.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..52c1cea7cf Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Hardsuits/cburn.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/cburn.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Hardsuits/cburn.rsi/meta.json index 2de954bbf9..2edd20e1dc 100644 --- a/Resources/Textures/Clothing/OuterClothing/Hardsuits/cburn.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Hardsuits/cburn.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Made by EmoGarbage404, harpy by VMSolidus", + "copyright": "Made by EmoGarbage404, harpy by VMSolidus. Vox state made by Flareguy for SS14", "size": { "x": 32, "y": 32 @@ -18,6 +18,10 @@ "name": "equipped-OUTERCLOTHING-harpy", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/clown.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Hardsuits/clown.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..bf6028dfdb Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Hardsuits/clown.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/clown.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Hardsuits/clown.rsi/meta.json index 090140fa8c..60c53e9cc2 100644 --- a/Resources/Textures/Clothing/OuterClothing/Hardsuits/clown.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Hardsuits/clown.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e, modified by brainfood1183 (github), harpy by VMSolidus", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e, modified by brainfood1183 (github), harpy by VMSolidus. Vox state made by Flareguy for SS14", "size": { "x": 32, "y": 32 @@ -18,6 +18,10 @@ "name": "equipped-OUTERCLOTHING-harpy", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/cybersun.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Hardsuits/cybersun.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..631815d06b Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Hardsuits/cybersun.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/cybersun.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Hardsuits/cybersun.rsi/meta.json index 1cfa2a9cfe..5a3fd99c11 100644 --- a/Resources/Textures/Clothing/OuterClothing/Hardsuits/cybersun.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Hardsuits/cybersun.rsi/meta.json @@ -1,30 +1,34 @@ -{ - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Sprite made by Gtheglorious based on the sprite made by emisse for ss14, harpy variant by VMSolidus", - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "icon" - }, - { - "name": "equipped-OUTERCLOTHING", - "directions": 4 - }, - { - "name": "inhand-left", - "directions": 4 - }, - { - "name": "inhand-right", - "directions": 4 - }, - { - "name": "equipped-OUTERCLOTHING-harpy", - "directions": 4 - } - ] -} +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Sprite made by Gtheglorious based on the sprite made by emisse for ss14, harpy variant by VMSolidus, vox state made by Flareguy for SS14.", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-OUTERCLOTHING", + "directions": 4 + }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "equipped-OUTERCLOTHING-harpy", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/deathsquad.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Hardsuits/deathsquad.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..cbf92dffde Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Hardsuits/deathsquad.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/deathsquad.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Hardsuits/deathsquad.rsi/meta.json index f6910b12aa..5054eea8f4 100644 --- a/Resources/Textures/Clothing/OuterClothing/Hardsuits/deathsquad.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Hardsuits/deathsquad.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e, edited by Emisse for SS14, harpy edit by VMSolidus", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e, edited by Emisse for SS14. Vox state made by Flareguy for SS14, harpy edit by VMSolidus", "size": { "x": 32, "y": 32 @@ -18,6 +18,10 @@ "name": "equipped-OUTERCLOTHING-harpy", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/engineering-white.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Hardsuits/engineering-white.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..a6066c595b Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Hardsuits/engineering-white.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/engineering-white.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Hardsuits/engineering-white.rsi/meta.json index ef5e62cca4..0a78966c1d 100644 --- a/Resources/Textures/Clothing/OuterClothing/Hardsuits/engineering-white.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Hardsuits/engineering-white.rsi/meta.json @@ -1,11 +1,11 @@ { - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", - "size": { - "x": 32, - "y": 32 - }, + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Vox state made by Flareguy for SS14", + "size": { + "x": 32, + "y": 32 + }, "states": [ { "name": "icon" @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 @@ -27,4 +31,4 @@ "directions": 4 } ] -} +} \ No newline at end of file diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/engineering.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Hardsuits/engineering.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..7e4e050f9d Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Hardsuits/engineering.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/engineering.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Hardsuits/engineering.rsi/meta.json index cbf3391af7..13f389a778 100644 --- a/Resources/Textures/Clothing/OuterClothing/Hardsuits/engineering.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Hardsuits/engineering.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e, harpy version by VMSolidus", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e, harpy version by VMSolidus. Vox state made by Flareguy for SS14", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/luxury.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Hardsuits/luxury.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..c082fd2340 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Hardsuits/luxury.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/luxury.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Hardsuits/luxury.rsi/meta.json index 0261a0564f..76e3264cbc 100644 --- a/Resources/Textures/Clothing/OuterClothing/Hardsuits/luxury.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Hardsuits/luxury.rsi/meta.json @@ -1,11 +1,11 @@ { - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Texture edit from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", - "size": { - "x": 32, - "y": 32 - }, + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Texture edit from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Vox state made by Flareguy for SS14", + "size": { + "x": 32, + "y": 32 + }, "states": [ { "name": "icon" @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 @@ -27,4 +31,4 @@ "directions": 4 } ] -} +} \ No newline at end of file diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/maxim.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Hardsuits/maxim.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..2085dc1c83 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Hardsuits/maxim.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/maxim.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Hardsuits/maxim.rsi/meta.json index 3641da6f50..9acde9a4de 100644 --- a/Resources/Textures/Clothing/OuterClothing/Hardsuits/maxim.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Hardsuits/maxim.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/6b3f58d7de4d4e374282819a7001eaa9bde1676d", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/6b3f58d7de4d4e374282819a7001eaa9bde1676d. Vox state made by Flareguy for SS14", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/medical.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Hardsuits/medical.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..6b6a36e0e7 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Hardsuits/medical.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/medical.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Hardsuits/medical.rsi/meta.json index ef5e62cca4..0a78966c1d 100644 --- a/Resources/Textures/Clothing/OuterClothing/Hardsuits/medical.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Hardsuits/medical.rsi/meta.json @@ -1,11 +1,11 @@ { - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", - "size": { - "x": 32, - "y": 32 - }, + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Vox state made by Flareguy for SS14", + "size": { + "x": 32, + "y": 32 + }, "states": [ { "name": "icon" @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 @@ -27,4 +31,4 @@ "directions": 4 } ] -} +} \ No newline at end of file diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/paramed.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Hardsuits/paramed.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..e8149c9cd9 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Hardsuits/paramed.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/paramed.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Hardsuits/paramed.rsi/meta.json index 64b98f1e79..403cfec384 100644 --- a/Resources/Textures/Clothing/OuterClothing/Hardsuits/paramed.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Hardsuits/paramed.rsi/meta.json @@ -1,30 +1,34 @@ -{ - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Taken from paradise station git at commit https://github.com/ParadiseSS13/Paradise/commit/e5e584804b4b0b373a6a69d23afb73fd3c094365, redrawn by Ubaser", - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "icon" - }, - { - "name": "equipped-OUTERCLOTHING", - "directions": 4 - }, - { - "name": "inhand-left", - "directions": 4 - }, - { - "name": "inhand-right", - "directions": 4 - }, - { - "name": "equipped-OUTERCLOTHING-harpy", - "directions": 4 - } - ] -} +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from paradise station git at commit https://github.com/ParadiseSS13/Paradise/commit/e5e584804b4b0b373a6a69d23afb73fd3c094365, redrawn by Ubaser. Vox state made by Flareguy for SS14", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-OUTERCLOTHING", + "directions": 4 + }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "equipped-OUTERCLOTHING-harpy", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/piratecaptain.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Hardsuits/piratecaptain.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..f0638fc80d Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Hardsuits/piratecaptain.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/piratecaptain.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Hardsuits/piratecaptain.rsi/meta.json index 8840689d9e..8663673f5c 100644 --- a/Resources/Textures/Clothing/OuterClothing/Hardsuits/piratecaptain.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Hardsuits/piratecaptain.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Made by brainfood1183 (Github) for ss14", + "copyright": "Made by brainfood1183 (Github) for ss14. Vox state made by Flareguy for SS14", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/pirateeva.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Hardsuits/pirateeva.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..ce3d20cc20 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Hardsuits/pirateeva.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/pirateeva.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Hardsuits/pirateeva.rsi/meta.json index 8840689d9e..8663673f5c 100644 --- a/Resources/Textures/Clothing/OuterClothing/Hardsuits/pirateeva.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Hardsuits/pirateeva.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Made by brainfood1183 (Github) for ss14", + "copyright": "Made by brainfood1183 (Github) for ss14. Vox state made by Flareguy for SS14", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/rd.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Hardsuits/rd.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..629584e995 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Hardsuits/rd.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/rd.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Hardsuits/rd.rsi/meta.json index def2d42704..c3caac8225 100644 --- a/Resources/Textures/Clothing/OuterClothing/Hardsuits/rd.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Hardsuits/rd.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Vox state made by Flareguy for SS14", "size": { "x": 32, "y": 32 @@ -18,6 +18,10 @@ "name": "equipped-OUTERCLOTHING-harpy", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/salvage.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Hardsuits/salvage.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..b8c26d6f41 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Hardsuits/salvage.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/salvage.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Hardsuits/salvage.rsi/meta.json index ef5e62cca4..0a78966c1d 100644 --- a/Resources/Textures/Clothing/OuterClothing/Hardsuits/salvage.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Hardsuits/salvage.rsi/meta.json @@ -1,11 +1,11 @@ { - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", - "size": { - "x": 32, - "y": 32 - }, + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Vox state made by Flareguy for SS14", + "size": { + "x": 32, + "y": 32 + }, "states": [ { "name": "icon" @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 @@ -27,4 +31,4 @@ "directions": 4 } ] -} +} \ No newline at end of file diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/santahardsuit.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Hardsuits/santahardsuit.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..0d8f373dc1 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Hardsuits/santahardsuit.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/santahardsuit.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Hardsuits/santahardsuit.rsi/meta.json index 13748622fe..eb28d4c8a1 100644 --- a/Resources/Textures/Clothing/OuterClothing/Hardsuits/santahardsuit.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Hardsuits/santahardsuit.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Edited by StanTheCarpenter. Originally taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Edited by StanTheCarpenter. Originally taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Vox state made by Flareguy for SS14", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/security-red.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Hardsuits/security-red.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..8c301cbe55 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Hardsuits/security-red.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/security-red.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Hardsuits/security-red.rsi/meta.json index e482264df5..15aef26a56 100644 --- a/Resources/Textures/Clothing/OuterClothing/Hardsuits/security-red.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Hardsuits/security-red.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Vox state made by Flareguy for SS14", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/security-warden.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Hardsuits/security-warden.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..30cf54325e Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Hardsuits/security-warden.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/security-warden.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Hardsuits/security-warden.rsi/meta.json index 54a0ed85fa..180439fbcc 100644 --- a/Resources/Textures/Clothing/OuterClothing/Hardsuits/security-warden.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Hardsuits/security-warden.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Sprite made by Gtheglorious based on the sprite made by Alekshhh for SS14", + "copyright": "Sprite made by Gtheglorious based on the sprite made by Alekshhh for SS14. Vox state made by Flareguy for SS14", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/security.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Hardsuits/security.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..c06d8fae90 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Hardsuits/security.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/security.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Hardsuits/security.rsi/meta.json index 32d6d8901a..8acf2ddc61 100644 --- a/Resources/Textures/Clothing/OuterClothing/Hardsuits/security.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Hardsuits/security.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Sprite made by Gtheglorious based on the sprite taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Sprite made by Gtheglorious based on the sprite taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Vox state made by Flareguy for SS14", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/spatio.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Hardsuits/spatio.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..999761e150 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Hardsuits/spatio.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/spatio.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Hardsuits/spatio.rsi/meta.json index f6a942a895..c04f7d758c 100644 --- a/Resources/Textures/Clothing/OuterClothing/Hardsuits/spatio.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Hardsuits/spatio.rsi/meta.json @@ -1,32 +1,34 @@ - -{ - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Original by Emisse, modified by EmoGarbage404", - - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "icon" - }, - { - "name": "equipped-OUTERCLOTHING", - "directions": 4 - }, - { - "name": "inhand-left", - "directions": 4 - }, - { - "name": "inhand-right", - "directions": 4 - }, - { - "name": "equipped-OUTERCLOTHING-harpy", - "directions": 4 - } - ] -} +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Original by Emisse, modified by EmoGarbage404. Vox state made by Flareguy for SS14", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-OUTERCLOTHING", + "directions": 4 + }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "equipped-OUTERCLOTHING-harpy", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/syndicate.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Hardsuits/syndicate.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..df2abbbf03 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Hardsuits/syndicate.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/syndicate.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Hardsuits/syndicate.rsi/meta.json index b9b739337b..3083012509 100644 --- a/Resources/Textures/Clothing/OuterClothing/Hardsuits/syndicate.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Hardsuits/syndicate.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/fb2d71495bfe81446159ef528534193d09dd8d34, equipped-OUTERCLOTHING-monkey made by Dutch-VanDerLinde", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/fb2d71495bfe81446159ef528534193d09dd8d34, equipped-OUTERCLOTHING-monkey made by Dutch-VanDerLinde, vox state made by Flareguy for SS14", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "equipped-OUTERCLOTHING-monkey", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/syndiecommander.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Hardsuits/syndiecommander.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..24c1aa2a87 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Hardsuits/syndiecommander.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/syndiecommander.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Hardsuits/syndiecommander.rsi/meta.json index 4aa4d60ccb..3744e040b6 100644 --- a/Resources/Textures/Clothing/OuterClothing/Hardsuits/syndiecommander.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Hardsuits/syndiecommander.rsi/meta.json @@ -1,30 +1,34 @@ -{ - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Taken from vg at commit https://github.com/vgstation-coders/vgstation13/commit/a16e41020a93479e9a7e2af343b1b74f7f2a61bd", - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "icon" - }, - { - "name": "equipped-OUTERCLOTHING", - "directions": 4 - }, - { - "name": "inhand-left", - "directions": 4 - }, - { - "name": "inhand-right", - "directions": 4 - }, - { - "name": "equipped-OUTERCLOTHING-harpy", - "directions": 4 - } - ] -} +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from vg at commit https://github.com/vgstation-coders/vgstation13/commit/a16e41020a93479e9a7e2af343b1b74f7f2a61bd. Vox state made by Flareguy for SS14", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-OUTERCLOTHING", + "directions": 4 + }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "equipped-OUTERCLOTHING-harpy", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/syndieelite.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Hardsuits/syndieelite.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..869cf41ce2 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Hardsuits/syndieelite.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/syndieelite.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Hardsuits/syndieelite.rsi/meta.json index b85c75656c..cde1c05977 100644 --- a/Resources/Textures/Clothing/OuterClothing/Hardsuits/syndieelite.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Hardsuits/syndieelite.rsi/meta.json @@ -1,30 +1,34 @@ -{ - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Taken from vg at commit https://github.com/vgstation-coders/vgstation13/commit/a16e41020a93479e9a7e2af343b1b74f7f2a61bd, harpy by VMSolidus", - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "icon" - }, - { - "name": "equipped-OUTERCLOTHING", - "directions": 4 - }, - { - "name": "equipped-OUTERCLOTHING-harpy", - "directions": 4 - }, - { - "name": "inhand-left", - "directions": 4 - }, - { - "name": "inhand-right", - "directions": 4 - } - ] -} +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from vg at commit https://github.com/vgstation-coders/vgstation13/commit/a16e41020a93479e9a7e2af343b1b74f7f2a61bd, harpy by VMSolidus. Vox state made by Flareguy for SS14", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-OUTERCLOTHING", + "directions": 4 + }, + { + "name": "equipped-OUTERCLOTHING-harpy", + "directions": 4 + }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/syndiemedic.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Hardsuits/syndiemedic.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..9417c5547b Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Hardsuits/syndiemedic.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/syndiemedic.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Hardsuits/syndiemedic.rsi/meta.json index 88d91565d7..2026bba893 100644 --- a/Resources/Textures/Clothing/OuterClothing/Hardsuits/syndiemedic.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Hardsuits/syndiemedic.rsi/meta.json @@ -1,11 +1,11 @@ { - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Based on tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e, modified by EmoGarbage404 (github), harpy by VMSolidus", - "size": { - "x": 32, - "y": 32 - }, + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Based on tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e, modified by EmoGarbage404 (github), harpy by VMSolidus. Vox state made by Flareguy for SS14", + "size": { + "x": 32, + "y": 32 + }, "states": [ { "name": "icon" @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 @@ -27,4 +31,4 @@ "directions": 4 } ] -} +} \ No newline at end of file diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/wizard.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Hardsuits/wizard.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..d4e4ab0f53 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Hardsuits/wizard.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/wizard.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Hardsuits/wizard.rsi/meta.json index e482264df5..15aef26a56 100644 --- a/Resources/Textures/Clothing/OuterClothing/Hardsuits/wizard.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Hardsuits/wizard.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Vox state made by Flareguy for SS14", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/apronbotanist.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Misc/apronbotanist.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..091fe54d8b Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Misc/apronbotanist.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/apronbotanist.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Misc/apronbotanist.rsi/meta.json index e482264df5..232bec9c0a 100644 --- a/Resources/Textures/Clothing/OuterClothing/Misc/apronbotanist.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Misc/apronbotanist.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. equipped-OUTERCLOTHING-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/apronchef.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Misc/apronchef.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..e0fd17d98a Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Misc/apronchef.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/apronchef.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Misc/apronchef.rsi/meta.json index e482264df5..232bec9c0a 100644 --- a/Resources/Textures/Clothing/OuterClothing/Misc/apronchef.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Misc/apronchef.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. equipped-OUTERCLOTHING-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/cardborg.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Misc/cardborg.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..e4ea5d5fa4 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Misc/cardborg.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/cardborg.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Misc/cardborg.rsi/meta.json index e482264df5..15aef26a56 100644 --- a/Resources/Textures/Clothing/OuterClothing/Misc/cardborg.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Misc/cardborg.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Vox state made by Flareguy for SS14", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/chef.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Misc/chef.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..a159e88777 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Misc/chef.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/chef.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Misc/chef.rsi/meta.json index e482264df5..232bec9c0a 100644 --- a/Resources/Textures/Clothing/OuterClothing/Misc/chef.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Misc/chef.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. equipped-OUTERCLOTHING-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/chef_idris.rsi/equipped-OUTERCLOTHING.png b/Resources/Textures/Clothing/OuterClothing/Misc/chef_idris.rsi/equipped-OUTERCLOTHING.png new file mode 100644 index 0000000000..53430fe1e9 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Misc/chef_idris.rsi/equipped-OUTERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/chef_idris.rsi/icon.png b/Resources/Textures/Clothing/OuterClothing/Misc/chef_idris.rsi/icon.png new file mode 100644 index 0000000000..d49f1321ea Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Misc/chef_idris.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/chef_idris.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Misc/chef_idris.rsi/meta.json new file mode 100644 index 0000000000..4820d6b667 --- /dev/null +++ b/Resources/Textures/Clothing/OuterClothing/Misc/chef_idris.rsi/meta.json @@ -0,0 +1,18 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from Aurorastation at commit https://github.com/Aurorastation/Aurora.3/commit/a8759f3c77b61315df50ee86969799f5a0afe894", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-OUTERCLOTHING", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/chef_nt.rsi/equipped-OUTERCLOTHING.png b/Resources/Textures/Clothing/OuterClothing/Misc/chef_nt.rsi/equipped-OUTERCLOTHING.png new file mode 100644 index 0000000000..9c90a41c05 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Misc/chef_nt.rsi/equipped-OUTERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/chef_nt.rsi/icon.png b/Resources/Textures/Clothing/OuterClothing/Misc/chef_nt.rsi/icon.png new file mode 100644 index 0000000000..1aeb2d82e4 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Misc/chef_nt.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/chef_nt.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Misc/chef_nt.rsi/meta.json new file mode 100644 index 0000000000..4820d6b667 --- /dev/null +++ b/Resources/Textures/Clothing/OuterClothing/Misc/chef_nt.rsi/meta.json @@ -0,0 +1,18 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from Aurorastation at commit https://github.com/Aurorastation/Aurora.3/commit/a8759f3c77b61315df50ee86969799f5a0afe894", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-OUTERCLOTHING", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/chef_orion.rsi/equipped-OUTERCLOTHING.png b/Resources/Textures/Clothing/OuterClothing/Misc/chef_orion.rsi/equipped-OUTERCLOTHING.png new file mode 100644 index 0000000000..ec931bebba Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Misc/chef_orion.rsi/equipped-OUTERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/chef_orion.rsi/icon.png b/Resources/Textures/Clothing/OuterClothing/Misc/chef_orion.rsi/icon.png new file mode 100644 index 0000000000..bd3c472750 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Misc/chef_orion.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/chef_orion.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Misc/chef_orion.rsi/meta.json new file mode 100644 index 0000000000..4820d6b667 --- /dev/null +++ b/Resources/Textures/Clothing/OuterClothing/Misc/chef_orion.rsi/meta.json @@ -0,0 +1,18 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from Aurorastation at commit https://github.com/Aurorastation/Aurora.3/commit/a8759f3c77b61315df50ee86969799f5a0afe894", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-OUTERCLOTHING", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/classicponcho.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Misc/classicponcho.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..94835833cc Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Misc/classicponcho.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/classicponcho.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Misc/classicponcho.rsi/meta.json index e482264df5..21cd9257fd 100644 --- a/Resources/Textures/Clothing/OuterClothing/Misc/classicponcho.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Misc/classicponcho.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. equipped-OUTERCLOTHING-vox state taken from Paradise at https://github.com/ParadiseSS13/Paradise/blob/fce54deb01d465957691ba2907218d4af4830db2/icons/mob/clothing/species/vox/suit.dmi", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/ghostsheet.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Misc/ghostsheet.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..df08ef1986 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Misc/ghostsheet.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/ghostsheet.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Misc/ghostsheet.rsi/meta.json index 5de1afef0f..ccfd0c0303 100644 --- a/Resources/Textures/Clothing/OuterClothing/Misc/ghostsheet.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Misc/ghostsheet.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/pull/38809", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/pull/38809. Vox state by Flareguy for SS1", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/grey_hoodie.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Misc/grey_hoodie.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..6423bd5c73 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Misc/grey_hoodie.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/grey_hoodie.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Misc/grey_hoodie.rsi/meta.json index 442bfb2920..b5f8619d65 100644 --- a/Resources/Textures/Clothing/OuterClothing/Misc/grey_hoodie.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Misc/grey_hoodie.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Vox state by Flareguy for SS14", "size": { "x": 32, "y": 32 @@ -17,6 +17,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "open-equipped-OUTERCLOTHING", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/nunrobe.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Misc/nunrobe.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..36e53e4396 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Misc/nunrobe.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/nunrobe.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Misc/nunrobe.rsi/meta.json index 217a3f9511..3fa2c7657c 100644 --- a/Resources/Textures/Clothing/OuterClothing/Misc/nunrobe.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Misc/nunrobe.rsi/meta.json @@ -1,26 +1,30 @@ -{ - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Taken from TGstation github https://github.com/tgstation/tgstation/commit/a20589c401fc9b0f23c1dea08c56d4a1697adcd6", - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "icon" - }, - { - "name": "equipped-OUTERCLOTHING", - "directions": 4 - }, - { - "name": "inhand-left", - "directions": 4 - }, - { - "name": "inhand-right", - "directions": 4 - } - ] -} \ No newline at end of file +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from TGstation github https://github.com/tgstation/tgstation/commit/a20589c401fc9b0f23c1dea08c56d4a1697adcd6. equipped-OUTERCLOTHING-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-OUTERCLOTHING", + "directions": 4 + }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/red_racoon.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Misc/red_racoon.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..ff43b16105 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Misc/red_racoon.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/red_racoon.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Misc/red_racoon.rsi/meta.json index b20ea0277f..cd0053daea 100644 --- a/Resources/Textures/Clothing/OuterClothing/Misc/red_racoon.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Misc/red_racoon.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Created by netwy(583844759429316618)", + "copyright": "Created by netwy(583844759429316618). Vox state made by Flareguy for SS14", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/redwizard.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Misc/redwizard.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..d5efb0d4b2 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Misc/redwizard.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/redwizard.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Misc/redwizard.rsi/meta.json index e482264df5..15aef26a56 100644 --- a/Resources/Textures/Clothing/OuterClothing/Misc/redwizard.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Misc/redwizard.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Vox state made by Flareguy for SS14", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/shadowkinrestraints.rsi/equipped-OUTERCLOTHING.png b/Resources/Textures/Clothing/OuterClothing/Misc/shadowkinrestraints.rsi/equipped-OUTERCLOTHING.png new file mode 100644 index 0000000000..e8618fc6b7 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Misc/shadowkinrestraints.rsi/equipped-OUTERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/shadowkinrestraints.rsi/icon.png b/Resources/Textures/Clothing/OuterClothing/Misc/shadowkinrestraints.rsi/icon.png new file mode 100644 index 0000000000..def3161bb0 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Misc/shadowkinrestraints.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/shadowkinrestraints.rsi/inhand-left.png b/Resources/Textures/Clothing/OuterClothing/Misc/shadowkinrestraints.rsi/inhand-left.png new file mode 100644 index 0000000000..5c15def766 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Misc/shadowkinrestraints.rsi/inhand-left.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/shadowkinrestraints.rsi/inhand-right.png b/Resources/Textures/Clothing/OuterClothing/Misc/shadowkinrestraints.rsi/inhand-right.png new file mode 100644 index 0000000000..99c96d368a Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Misc/shadowkinrestraints.rsi/inhand-right.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/shadowkinrestraints.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Misc/shadowkinrestraints.rsi/meta.json new file mode 100644 index 0000000000..57a0b994f9 --- /dev/null +++ b/Resources/Textures/Clothing/OuterClothing/Misc/shadowkinrestraints.rsi/meta.json @@ -0,0 +1,26 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "JustAnOrange", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "equipped-OUTERCLOTHING", + "directions": 4 + }, + { + "name": "icon" + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/skubbody.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Misc/skubbody.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..b50aa58c48 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Misc/skubbody.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/skubbody.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Misc/skubbody.rsi/meta.json index 9a00c5741a..ed75ee470b 100644 --- a/Resources/Textures/Clothing/OuterClothing/Misc/skubbody.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Misc/skubbody.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "EOBGames from tgstation", + "copyright": "EOBGames from tgstation. Vox state made by Flareguy for SS14", "size": { "x": 32, "y": 32 @@ -13,6 +13,10 @@ { "name": "equipped-OUTERCLOTHING", "directions": 4 + }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 } ] } diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/straight_jacket.rsi/body-overlay-2-vox.png b/Resources/Textures/Clothing/OuterClothing/Misc/straight_jacket.rsi/body-overlay-2-vox.png new file mode 100644 index 0000000000..0a23447f94 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Misc/straight_jacket.rsi/body-overlay-2-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/straight_jacket.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Misc/straight_jacket.rsi/meta.json index b41f0209d7..2eda10ef51 100644 --- a/Resources/Textures/Clothing/OuterClothing/Misc/straight_jacket.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Misc/straight_jacket.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Vox state made by Flareguy for SS14", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "body-overlay-2", "directions": 4 }, + { + "name": "body-overlay-2-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/violetwizard.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Misc/violetwizard.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..e39761b360 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Misc/violetwizard.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/violetwizard.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Misc/violetwizard.rsi/meta.json index e482264df5..15aef26a56 100644 --- a/Resources/Textures/Clothing/OuterClothing/Misc/violetwizard.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Misc/violetwizard.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Vox state made by Flareguy for SS14", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/wizard.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Misc/wizard.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..a586ec3fe3 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Misc/wizard.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Misc/wizard.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Misc/wizard.rsi/meta.json index e482264df5..15aef26a56 100644 --- a/Resources/Textures/Clothing/OuterClothing/Misc/wizard.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Misc/wizard.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Vox state made by Flareguy for SS14", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Suits/bombsuit.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Suits/bombsuit.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..36cea26549 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Suits/bombsuit.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Suits/bombsuit.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Suits/bombsuit.rsi/meta.json index e9a9a4453d..669e1d9857 100644 --- a/Resources/Textures/Clothing/OuterClothing/Suits/bombsuit.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Suits/bombsuit.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from cev-eris at commit https://github.com/discordia-space/CEV-Eris/commit/760f0be7af33a31f5a08a3291864e91539d0ebb7", + "copyright": "Taken from cev-eris at commit https://github.com/discordia-space/CEV-Eris/commit/760f0be7af33a31f5a08a3291864e91539d0ebb7. Vox state made by Flareguy for Space Station 14", "size": { "x": 32, "y": 32 @@ -18,6 +18,10 @@ "name": "equipped-OUTERCLOTHING-harpy", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Suits/chicken.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Suits/chicken.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..d9ccc1dd96 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Suits/chicken.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Suits/chicken.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Suits/chicken.rsi/meta.json index e482264df5..ca1146d007 100644 --- a/Resources/Textures/Clothing/OuterClothing/Suits/chicken.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Suits/chicken.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. equipped-OUTERCLOTHING-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79", "size": { "x": 32, "y": 32 @@ -21,6 +21,10 @@ { "name": "inhand-right", "directions": 4 + }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 } ] } diff --git a/Resources/Textures/Clothing/OuterClothing/Suits/eva.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Suits/eva.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..2fb7750843 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Suits/eva.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Suits/eva.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Suits/eva.rsi/meta.json index 4dcd7b13b5..136d76ea97 100644 --- a/Resources/Textures/Clothing/OuterClothing/Suits/eva.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Suits/eva.rsi/meta.json @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "equipped-OUTERCLOTHING-monkey", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Suits/eva_emergency.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Suits/eva_emergency.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..99b555f7ac Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Suits/eva_emergency.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Suits/eva_emergency.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Suits/eva_emergency.rsi/meta.json index 8d09b75006..87ae5d0fb0 100644 --- a/Resources/Textures/Clothing/OuterClothing/Suits/eva_emergency.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Suits/eva_emergency.rsi/meta.json @@ -18,6 +18,10 @@ "name": "equipped-OUTERCLOTHING-harpy", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Suits/eva_prisoner.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Suits/eva_prisoner.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..9601695dac Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Suits/eva_prisoner.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Suits/eva_prisoner.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Suits/eva_prisoner.rsi/meta.json index 0482d08642..510a3431af 100644 --- a/Resources/Textures/Clothing/OuterClothing/Suits/eva_prisoner.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Suits/eva_prisoner.rsi/meta.json @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "equipped-OUTERCLOTHING-monkey", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Suits/eva_syndicate.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Suits/eva_syndicate.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..7f71b04f1c Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Suits/eva_syndicate.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Suits/eva_syndicate.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Suits/eva_syndicate.rsi/meta.json index 0482d08642..510a3431af 100644 --- a/Resources/Textures/Clothing/OuterClothing/Suits/eva_syndicate.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Suits/eva_syndicate.rsi/meta.json @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "equipped-OUTERCLOTHING-monkey", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Suits/fire.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Suits/fire.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..920cc9e05f Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Suits/fire.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Suits/fire.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Suits/fire.rsi/meta.json index 0a8c670a89..5edb377409 100644 --- a/Resources/Textures/Clothing/OuterClothing/Suits/fire.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Suits/fire.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e, harpy edit by VMSolidus", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e, harpy edit by VMSolidus. equipped-OUTERCLOTHING-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79", "size": { "x": 32, "y": 32 @@ -18,6 +18,10 @@ "name": "equipped-OUTERCLOTHING-harpy", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Suits/monkey.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Suits/monkey.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..c585862bb1 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Suits/monkey.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Suits/monkey.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Suits/monkey.rsi/meta.json index e482264df5..920eab5520 100644 --- a/Resources/Textures/Clothing/OuterClothing/Suits/monkey.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Suits/monkey.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Vox state made by Flareguy for Space Station 14", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Suits/rad.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Suits/rad.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..13f9dacef3 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Suits/rad.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Suits/rad.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Suits/rad.rsi/meta.json index 0a8c670a89..e3387cff96 100644 --- a/Resources/Textures/Clothing/OuterClothing/Suits/rad.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Suits/rad.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e, harpy edit by VMSolidus", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e, harpy edit by VMSolidus. Vox state made by Flareguy for Space Station 14", "size": { "x": 32, "y": 32 @@ -18,6 +18,10 @@ "name": "equipped-OUTERCLOTHING-harpy", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Vests/detvest.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Vests/detvest.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..50e6d79c20 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Vests/detvest.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Vests/detvest.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Vests/detvest.rsi/meta.json index e482264df5..920eab5520 100644 --- a/Resources/Textures/Clothing/OuterClothing/Vests/detvest.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Vests/detvest.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Vox state made by Flareguy for Space Station 14", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Vests/hazard.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Vests/hazard.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..6d9f6dbead Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Vests/hazard.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Vests/hazard.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Vests/hazard.rsi/meta.json index 05ec90135e..4c56c355a4 100644 --- a/Resources/Textures/Clothing/OuterClothing/Vests/hazard.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Vests/hazard.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/c838ba21dae97db345e0113f99596decd1d66039", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/c838ba21dae97db345e0113f99596decd1d66039. Vox state made by Flareguy for Space Station 14", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Vests/vest.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Vests/vest.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..ee35c40fbd Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Vests/vest.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Vests/vest.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Vests/vest.rsi/meta.json index e482264df5..920eab5520 100644 --- a/Resources/Textures/Clothing/OuterClothing/Vests/vest.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Vests/vest.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Vox state made by Flareguy for Space Station 14", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/Vests/vest_idris.rsi/equipped-OUTERCLOTHING.png b/Resources/Textures/Clothing/OuterClothing/Vests/vest_idris.rsi/equipped-OUTERCLOTHING.png new file mode 100644 index 0000000000..a6d978ee53 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Vests/vest_idris.rsi/equipped-OUTERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Vests/vest_idris.rsi/icon.png b/Resources/Textures/Clothing/OuterClothing/Vests/vest_idris.rsi/icon.png new file mode 100644 index 0000000000..08f319524f Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Vests/vest_idris.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Vests/vest_idris.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Vests/vest_idris.rsi/meta.json new file mode 100644 index 0000000000..4820d6b667 --- /dev/null +++ b/Resources/Textures/Clothing/OuterClothing/Vests/vest_idris.rsi/meta.json @@ -0,0 +1,18 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from Aurorastation at commit https://github.com/Aurorastation/Aurora.3/commit/a8759f3c77b61315df50ee86969799f5a0afe894", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-OUTERCLOTHING", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/OuterClothing/Vests/vest_nt.rsi/equipped-OUTERCLOTHING.png b/Resources/Textures/Clothing/OuterClothing/Vests/vest_nt.rsi/equipped-OUTERCLOTHING.png new file mode 100644 index 0000000000..cdb943fac9 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Vests/vest_nt.rsi/equipped-OUTERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Vests/vest_nt.rsi/icon.png b/Resources/Textures/Clothing/OuterClothing/Vests/vest_nt.rsi/icon.png new file mode 100644 index 0000000000..8d2c25387e Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Vests/vest_nt.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Vests/vest_nt.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Vests/vest_nt.rsi/meta.json new file mode 100644 index 0000000000..4820d6b667 --- /dev/null +++ b/Resources/Textures/Clothing/OuterClothing/Vests/vest_nt.rsi/meta.json @@ -0,0 +1,18 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from Aurorastation at commit https://github.com/Aurorastation/Aurora.3/commit/a8759f3c77b61315df50ee86969799f5a0afe894", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-OUTERCLOTHING", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/OuterClothing/Vests/vest_orion.rsi/equipped-OUTERCLOTHING.png b/Resources/Textures/Clothing/OuterClothing/Vests/vest_orion.rsi/equipped-OUTERCLOTHING.png new file mode 100644 index 0000000000..70a30ad238 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Vests/vest_orion.rsi/equipped-OUTERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Vests/vest_orion.rsi/icon.png b/Resources/Textures/Clothing/OuterClothing/Vests/vest_orion.rsi/icon.png new file mode 100644 index 0000000000..cd4c9e6c05 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Vests/vest_orion.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Vests/vest_orion.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Vests/vest_orion.rsi/meta.json new file mode 100644 index 0000000000..4820d6b667 --- /dev/null +++ b/Resources/Textures/Clothing/OuterClothing/Vests/vest_orion.rsi/meta.json @@ -0,0 +1,18 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from Aurorastation at commit https://github.com/Aurorastation/Aurora.3/commit/a8759f3c77b61315df50ee86969799f5a0afe894", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-OUTERCLOTHING", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/OuterClothing/Vests/webvest.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Vests/webvest.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..17d75d29b3 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/Vests/webvest.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/Vests/webvest.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Vests/webvest.rsi/meta.json index e482264df5..0e30ab4d3f 100644 --- a/Resources/Textures/Clothing/OuterClothing/Vests/webvest.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/Vests/webvest.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e. Vox state by Flareguy for SS14", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-OUTERCLOTHING", "directions": 4 }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coat.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coat.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..440fc3375f Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coat.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coat.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coat.rsi/meta.json index 7752722477..19f610cdfe 100644 --- a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coat.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coat.rsi/meta.json @@ -1,26 +1,30 @@ -{ - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/77cff42b6c514e73881a885036be4b4dd2949f62", - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "icon" - }, - { - "name": "equipped-OUTERCLOTHING", - "directions": 4 - }, - { - "name": "inhand-left", - "directions": 4 - }, - { - "name": "inhand-right", - "directions": 4 - } - ] -} \ No newline at end of file +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/77cff42b6c514e73881a885036be4b4dd2949f62. equipped-OUTERCLOTHING-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-OUTERCLOTHING", + "directions": 4 + }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatatmos.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatatmos.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..1d75531317 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatatmos.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatatmos.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatatmos.rsi/meta.json index 7752722477..5d9e9636f7 100644 --- a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatatmos.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatatmos.rsi/meta.json @@ -1,26 +1,30 @@ -{ - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/77cff42b6c514e73881a885036be4b4dd2949f62", - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "icon" - }, - { - "name": "equipped-OUTERCLOTHING", - "directions": 4 - }, - { - "name": "inhand-left", - "directions": 4 - }, - { - "name": "inhand-right", - "directions": 4 - } - ] +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/77cff42b6c514e73881a885036be4b4dd2949f62. equipped-OUTERCLOTHING-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-OUTERCLOTHING", + "directions": 4 + }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] } \ No newline at end of file diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatbar.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatbar.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..092d66ea3e Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatbar.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatbar.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatbar.rsi/meta.json index 7863947d1c..19fcc6a935 100644 --- a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatbar.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatbar.rsi/meta.json @@ -1,26 +1,30 @@ -{ - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Taken from vg at commit https://github.com/vgstation-coders/vgstation13/commit/a16e41020a93479e9a7e2af343b1b74f7f2a61bd", - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "icon" - }, - { - "name": "equipped-OUTERCLOTHING", - "directions": 4 - }, - { - "name": "inhand-left", - "directions": 4 - }, - { - "name": "inhand-right", - "directions": 4 - } - ] +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from vg at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-OUTERCLOTHING", + "directions": 4 + }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] } \ No newline at end of file diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatcap.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatcap.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..0fca4396ea Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatcap.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatcap.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatcap.rsi/meta.json index 46b3e631a0..7ddd460a3b 100644 --- a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatcap.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatcap.rsi/meta.json @@ -1,26 +1,30 @@ -{ - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/77cff42b6c514e73881a885036be4b4dd2949f62 , edited by Skarletto (github)", - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "icon" - }, - { - "name": "equipped-OUTERCLOTHING", - "directions": 4 - }, - { - "name": "inhand-left", - "directions": 4 - }, - { - "name": "inhand-right", - "directions": 4 - } - ] +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/77cff42b6c514e73881a885036be4b4dd2949f62, edited by Skarletto (github). equipped-OUTERCLOTHING-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79 and repaletted to match with the default state", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-OUTERCLOTHING", + "directions": 4 + }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] } \ No newline at end of file diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatcargo.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatcargo.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..f4427249ec Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatcargo.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatcargo.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatcargo.rsi/meta.json index 7752722477..5d9e9636f7 100644 --- a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatcargo.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatcargo.rsi/meta.json @@ -1,26 +1,30 @@ -{ - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/77cff42b6c514e73881a885036be4b4dd2949f62", - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "icon" - }, - { - "name": "equipped-OUTERCLOTHING", - "directions": 4 - }, - { - "name": "inhand-left", - "directions": 4 - }, - { - "name": "inhand-right", - "directions": 4 - } - ] +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/77cff42b6c514e73881a885036be4b4dd2949f62. equipped-OUTERCLOTHING-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-OUTERCLOTHING", + "directions": 4 + }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] } \ No newline at end of file diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatce.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatce.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..29002eaf6b Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatce.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatce.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatce.rsi/meta.json index 7752722477..5d9e9636f7 100644 --- a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatce.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatce.rsi/meta.json @@ -1,26 +1,30 @@ -{ - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/77cff42b6c514e73881a885036be4b4dd2949f62", - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "icon" - }, - { - "name": "equipped-OUTERCLOTHING", - "directions": 4 - }, - { - "name": "inhand-left", - "directions": 4 - }, - { - "name": "inhand-right", - "directions": 4 - } - ] +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/77cff42b6c514e73881a885036be4b4dd2949f62. equipped-OUTERCLOTHING-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-OUTERCLOTHING", + "directions": 4 + }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] } \ No newline at end of file diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatclown.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatclown.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..781df9000b Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatclown.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatclown.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatclown.rsi/meta.json index 7863947d1c..19fcc6a935 100644 --- a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatclown.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatclown.rsi/meta.json @@ -1,26 +1,30 @@ -{ - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Taken from vg at commit https://github.com/vgstation-coders/vgstation13/commit/a16e41020a93479e9a7e2af343b1b74f7f2a61bd", - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "icon" - }, - { - "name": "equipped-OUTERCLOTHING", - "directions": 4 - }, - { - "name": "inhand-left", - "directions": 4 - }, - { - "name": "inhand-right", - "directions": 4 - } - ] +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from vg at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-OUTERCLOTHING", + "directions": 4 + }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] } \ No newline at end of file diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatengi.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatengi.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..015e325023 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatengi.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatengi.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatengi.rsi/meta.json index 7752722477..5d9e9636f7 100644 --- a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatengi.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatengi.rsi/meta.json @@ -1,26 +1,30 @@ -{ - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/77cff42b6c514e73881a885036be4b4dd2949f62", - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "icon" - }, - { - "name": "equipped-OUTERCLOTHING", - "directions": 4 - }, - { - "name": "inhand-left", - "directions": 4 - }, - { - "name": "inhand-right", - "directions": 4 - } - ] +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/77cff42b6c514e73881a885036be4b4dd2949f62. equipped-OUTERCLOTHING-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-OUTERCLOTHING", + "directions": 4 + }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] } \ No newline at end of file diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coathosarmored.rsi/equipped-OUTERCLOTHING.png b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coathosarmored.rsi/equipped-OUTERCLOTHING.png new file mode 100644 index 0000000000..d279e92da2 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coathosarmored.rsi/equipped-OUTERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coathosarmored.rsi/icon.png b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coathosarmored.rsi/icon.png new file mode 100644 index 0000000000..6f7573420d Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coathosarmored.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coathosarmored.rsi/inhand-left.png b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coathosarmored.rsi/inhand-left.png new file mode 100644 index 0000000000..85a0b83457 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coathosarmored.rsi/inhand-left.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coathosarmored.rsi/inhand-right.png b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coathosarmored.rsi/inhand-right.png new file mode 100644 index 0000000000..70ade34a8d Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coathosarmored.rsi/inhand-right.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coathosarmored.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coathosarmored.rsi/meta.json new file mode 100644 index 0000000000..4010520850 --- /dev/null +++ b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coathosarmored.rsi/meta.json @@ -0,0 +1,26 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from vg at commit https://github.com/vgstation-coders/vgstation13/commit/a16e41020a93479e9a7e2af343b1b74f7f2a61bd, recolored by Github user PursuitinAshes", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-OUTERCLOTHING", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coathydro.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coathydro.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..334e1e8fe8 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coathydro.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coathydro.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coathydro.rsi/meta.json index 7752722477..5d9e9636f7 100644 --- a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coathydro.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coathydro.rsi/meta.json @@ -1,26 +1,30 @@ -{ - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/77cff42b6c514e73881a885036be4b4dd2949f62", - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "icon" - }, - { - "name": "equipped-OUTERCLOTHING", - "directions": 4 - }, - { - "name": "inhand-left", - "directions": 4 - }, - { - "name": "inhand-right", - "directions": 4 - } - ] +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/77cff42b6c514e73881a885036be4b4dd2949f62. equipped-OUTERCLOTHING-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-OUTERCLOTHING", + "directions": 4 + }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] } \ No newline at end of file diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatmed.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatmed.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..7b1b9d883c Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatmed.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatmed.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatmed.rsi/meta.json index 7752722477..5d9e9636f7 100644 --- a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatmed.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatmed.rsi/meta.json @@ -1,26 +1,30 @@ -{ - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/77cff42b6c514e73881a885036be4b4dd2949f62", - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "icon" - }, - { - "name": "equipped-OUTERCLOTHING", - "directions": 4 - }, - { - "name": "inhand-left", - "directions": 4 - }, - { - "name": "inhand-right", - "directions": 4 - } - ] +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/77cff42b6c514e73881a885036be4b4dd2949f62. equipped-OUTERCLOTHING-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-OUTERCLOTHING", + "directions": 4 + }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] } \ No newline at end of file diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatmime.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatmime.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..f713d3c846 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatmime.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatmime.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatmime.rsi/meta.json index 7863947d1c..19fcc6a935 100644 --- a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatmime.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatmime.rsi/meta.json @@ -1,26 +1,30 @@ -{ - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Taken from vg at commit https://github.com/vgstation-coders/vgstation13/commit/a16e41020a93479e9a7e2af343b1b74f7f2a61bd", - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "icon" - }, - { - "name": "equipped-OUTERCLOTHING", - "directions": 4 - }, - { - "name": "inhand-left", - "directions": 4 - }, - { - "name": "inhand-right", - "directions": 4 - } - ] +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from vg at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-OUTERCLOTHING", + "directions": 4 + }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] } \ No newline at end of file diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatminer.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatminer.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..438eced1b5 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatminer.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatminer.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatminer.rsi/meta.json index 7752722477..5d9e9636f7 100644 --- a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatminer.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatminer.rsi/meta.json @@ -1,26 +1,30 @@ -{ - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/77cff42b6c514e73881a885036be4b4dd2949f62", - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "icon" - }, - { - "name": "equipped-OUTERCLOTHING", - "directions": 4 - }, - { - "name": "inhand-left", - "directions": 4 - }, - { - "name": "inhand-right", - "directions": 4 - } - ] +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/77cff42b6c514e73881a885036be4b4dd2949f62. equipped-OUTERCLOTHING-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-OUTERCLOTHING", + "directions": 4 + }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] } \ No newline at end of file diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatparamed.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatparamed.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..a7ff104066 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatparamed.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatparamed.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatparamed.rsi/meta.json index 7752722477..5d9e9636f7 100644 --- a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatparamed.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatparamed.rsi/meta.json @@ -1,26 +1,30 @@ -{ - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/77cff42b6c514e73881a885036be4b4dd2949f62", - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "icon" - }, - { - "name": "equipped-OUTERCLOTHING", - "directions": 4 - }, - { - "name": "inhand-left", - "directions": 4 - }, - { - "name": "inhand-right", - "directions": 4 - } - ] +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/77cff42b6c514e73881a885036be4b4dd2949f62. equipped-OUTERCLOTHING-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-OUTERCLOTHING", + "directions": 4 + }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] } \ No newline at end of file diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatsci.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatsci.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..1634304d13 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatsci.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatsci.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatsci.rsi/meta.json index 7752722477..5d9e9636f7 100644 --- a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatsci.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatsci.rsi/meta.json @@ -1,26 +1,30 @@ -{ - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/77cff42b6c514e73881a885036be4b4dd2949f62", - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "icon" - }, - { - "name": "equipped-OUTERCLOTHING", - "directions": 4 - }, - { - "name": "inhand-left", - "directions": 4 - }, - { - "name": "inhand-right", - "directions": 4 - } - ] +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/77cff42b6c514e73881a885036be4b4dd2949f62. equipped-OUTERCLOTHING-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-OUTERCLOTHING", + "directions": 4 + }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] } \ No newline at end of file diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatsec.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatsec.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..e67c55f114 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatsec.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatsec.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatsec.rsi/meta.json index 7752722477..5d9e9636f7 100644 --- a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatsec.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatsec.rsi/meta.json @@ -1,26 +1,30 @@ -{ - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/77cff42b6c514e73881a885036be4b4dd2949f62", - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "icon" - }, - { - "name": "equipped-OUTERCLOTHING", - "directions": 4 - }, - { - "name": "inhand-left", - "directions": 4 - }, - { - "name": "inhand-right", - "directions": 4 - } - ] +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/77cff42b6c514e73881a885036be4b4dd2949f62. equipped-OUTERCLOTHING-vox state taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-OUTERCLOTHING", + "directions": 4 + }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] } \ No newline at end of file diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatwarden.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatwarden.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000..b4f8cacea5 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatwarden.rsi/equipped-OUTERCLOTHING-vox.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatwarden.rsi/equipped-OUTERCLOTHING.png b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatwarden.rsi/equipped-OUTERCLOTHING.png index 28cf03f49c..baeeca8781 100644 Binary files a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatwarden.rsi/equipped-OUTERCLOTHING.png and b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatwarden.rsi/equipped-OUTERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatwarden.rsi/icon.png b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatwarden.rsi/icon.png index eaf984f437..854ea543e9 100644 Binary files a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatwarden.rsi/icon.png and b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatwarden.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatwarden.rsi/inhand-left.png b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatwarden.rsi/inhand-left.png index df033631e9..464c4bfc5e 100644 Binary files a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatwarden.rsi/inhand-left.png and b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatwarden.rsi/inhand-left.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatwarden.rsi/inhand-right.png b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatwarden.rsi/inhand-right.png index 77baaca885..d537128b82 100644 Binary files a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatwarden.rsi/inhand-right.png and b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatwarden.rsi/inhand-right.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatwarden.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatwarden.rsi/meta.json index 7863947d1c..ade0905fb8 100644 --- a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatwarden.rsi/meta.json +++ b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatwarden.rsi/meta.json @@ -1,26 +1,30 @@ -{ - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Taken from vg at commit https://github.com/vgstation-coders/vgstation13/commit/a16e41020a93479e9a7e2af343b1b74f7f2a61bd", - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "icon" - }, - { - "name": "equipped-OUTERCLOTHING", - "directions": 4 - }, - { - "name": "inhand-left", - "directions": 4 - }, - { - "name": "inhand-right", - "directions": 4 - } - ] -} \ No newline at end of file +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from vg at commit https://github.com/vgstation-coders/vgstation13/commit/31d6576ba8102135d058ef49c3cb6ecbe8db8a79 / https://github.com/tgstation/tgstation/commit/77cff42b6c514e73881a885036be4b4dd2949f62, recolored by Github user Dutch-VanDerLinde.", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-OUTERCLOTHING", + "directions": 4 + }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatwardenarmored.rsi/equipped-OUTERCLOTHING.png b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatwardenarmored.rsi/equipped-OUTERCLOTHING.png new file mode 100644 index 0000000000..28cf03f49c Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatwardenarmored.rsi/equipped-OUTERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatwardenarmored.rsi/icon.png b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatwardenarmored.rsi/icon.png new file mode 100644 index 0000000000..eaf984f437 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatwardenarmored.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatwardenarmored.rsi/inhand-left.png b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatwardenarmored.rsi/inhand-left.png new file mode 100644 index 0000000000..df033631e9 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatwardenarmored.rsi/inhand-left.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatwardenarmored.rsi/inhand-right.png b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatwardenarmored.rsi/inhand-right.png new file mode 100644 index 0000000000..77baaca885 Binary files /dev/null and b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatwardenarmored.rsi/inhand-right.png differ diff --git a/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatwardenarmored.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatwardenarmored.rsi/meta.json new file mode 100644 index 0000000000..c9f0d90ea1 --- /dev/null +++ b/Resources/Textures/Clothing/OuterClothing/WinterCoats/coatwardenarmored.rsi/meta.json @@ -0,0 +1,26 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from vg at commit https://github.com/vgstation-coders/vgstation13/commit/a16e41020a93479e9a7e2af343b1b74f7f2a61bd", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-OUTERCLOTHING", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Clothing/Shoes/Boots/jackboots.rsi/equipped-FEET-vox.png b/Resources/Textures/Clothing/Shoes/Boots/jackboots.rsi/equipped-FEET-vox.png new file mode 100644 index 0000000000..73ab1326b8 Binary files /dev/null and b/Resources/Textures/Clothing/Shoes/Boots/jackboots.rsi/equipped-FEET-vox.png differ diff --git a/Resources/Textures/Clothing/Shoes/Boots/jackboots.rsi/meta.json b/Resources/Textures/Clothing/Shoes/Boots/jackboots.rsi/meta.json index 54b1191d42..1abf6951f1 100644 --- a/Resources/Textures/Clothing/Shoes/Boots/jackboots.rsi/meta.json +++ b/Resources/Textures/Clothing/Shoes/Boots/jackboots.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/7e4e9d432d88981fb9bb463970c5b98ce85c0abe", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/7e4e9d432d88981fb9bb463970c5b98ce85c0abe. equipped-FEET-vox state taken from vgstation13 at https://github.com/vgstation-coders/vgstation13/blob/31d6576ba8102135d058ef49c3cb6ecbe8db8a79/icons/mob/species/vox/shoes.dmi", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-FEET", "directions": 4 }, + { + "name": "equipped-FEET-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/Shoes/Boots/magboots-advanced.rsi/equipped-FEET-vox.png b/Resources/Textures/Clothing/Shoes/Boots/magboots-advanced.rsi/equipped-FEET-vox.png new file mode 100644 index 0000000000..0d3d83cbad Binary files /dev/null and b/Resources/Textures/Clothing/Shoes/Boots/magboots-advanced.rsi/equipped-FEET-vox.png differ diff --git a/Resources/Textures/Clothing/Shoes/Boots/magboots-advanced.rsi/meta.json b/Resources/Textures/Clothing/Shoes/Boots/magboots-advanced.rsi/meta.json index 8d56bf0c2b..5b3afdcd74 100644 --- a/Resources/Textures/Clothing/Shoes/Boots/magboots-advanced.rsi/meta.json +++ b/Resources/Textures/Clothing/Shoes/Boots/magboots-advanced.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "https://github.com/tgstation/tgstation/commit/3d319f6157acc1c1cd9ebcb0f6f12641e051cf91", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/3d319f6157acc1c1cd9ebcb0f6f12641e051cf91. equipped-FEET-vox & on-equipped-FEET-vox state taken from vgstation13 at https://github.com/vgstation-coders/vgstation13/blob/31d6576ba8102135d058ef49c3cb6ecbe8db8a79/icons/mob/species/vox/shoes.dmi, and equipped-FEET-vox state modified to fix inconsistencies", "size": { "x": 32, "y": 32 @@ -15,6 +15,14 @@ "name": "on-equipped-FEET", "directions": 4 }, + { + "name": "equipped-FEET-vox", + "directions": 4 + }, + { + "name": "on-equipped-FEET-vox", + "directions": 4 + }, { "name": "icon" }, diff --git a/Resources/Textures/Clothing/Shoes/Boots/magboots-advanced.rsi/on-equipped-FEET-vox.png b/Resources/Textures/Clothing/Shoes/Boots/magboots-advanced.rsi/on-equipped-FEET-vox.png new file mode 100644 index 0000000000..9c1af7f061 Binary files /dev/null and b/Resources/Textures/Clothing/Shoes/Boots/magboots-advanced.rsi/on-equipped-FEET-vox.png differ diff --git a/Resources/Textures/Clothing/Shoes/Boots/magboots-syndicate.rsi/equipped-FEET-vox.png b/Resources/Textures/Clothing/Shoes/Boots/magboots-syndicate.rsi/equipped-FEET-vox.png new file mode 100644 index 0000000000..ff7074dbe3 Binary files /dev/null and b/Resources/Textures/Clothing/Shoes/Boots/magboots-syndicate.rsi/equipped-FEET-vox.png differ diff --git a/Resources/Textures/Clothing/Shoes/Boots/magboots-syndicate.rsi/meta.json b/Resources/Textures/Clothing/Shoes/Boots/magboots-syndicate.rsi/meta.json index ead74d8896..ae71606af3 100644 --- a/Resources/Textures/Clothing/Shoes/Boots/magboots-syndicate.rsi/meta.json +++ b/Resources/Textures/Clothing/Shoes/Boots/magboots-syndicate.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/debf90acfcafa4fb8d6723a37e0b8ac556c0702b", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/debf90acfcafa4fb8d6723a37e0b8ac556c0702b. equipped-FEET-vox & on-equipped-FEET-vox state taken from vgstation13 at https://github.com/vgstation-coders/vgstation13/blob/31d6576ba8102135d058ef49c3cb6ecbe8db8a79/icons/mob/species/vox/shoes.dmi, and equipped-FEET-vox state modified to fix inconsistencies", "size": { "x": 32, "y": 32 @@ -15,6 +15,14 @@ "name": "on-equipped-FEET", "directions": 4 }, + { + "name": "equipped-FEET-vox", + "directions": 4 + }, + { + "name": "on-equipped-FEET-vox", + "directions": 4 + }, { "name": "icon" }, diff --git a/Resources/Textures/Clothing/Shoes/Boots/magboots-syndicate.rsi/on-equipped-FEET-vox.png b/Resources/Textures/Clothing/Shoes/Boots/magboots-syndicate.rsi/on-equipped-FEET-vox.png new file mode 100644 index 0000000000..c14f707834 Binary files /dev/null and b/Resources/Textures/Clothing/Shoes/Boots/magboots-syndicate.rsi/on-equipped-FEET-vox.png differ diff --git a/Resources/Textures/Clothing/Shoes/Boots/magboots.rsi/equipped-FEET-vox.png b/Resources/Textures/Clothing/Shoes/Boots/magboots.rsi/equipped-FEET-vox.png new file mode 100644 index 0000000000..66083b253d Binary files /dev/null and b/Resources/Textures/Clothing/Shoes/Boots/magboots.rsi/equipped-FEET-vox.png differ diff --git a/Resources/Textures/Clothing/Shoes/Boots/magboots.rsi/meta.json b/Resources/Textures/Clothing/Shoes/Boots/magboots.rsi/meta.json index 96b3f4109e..f2aa6dbc5b 100644 --- a/Resources/Textures/Clothing/Shoes/Boots/magboots.rsi/meta.json +++ b/Resources/Textures/Clothing/Shoes/Boots/magboots.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/7e4e9d432d88981fb9bb463970c5b98ce85c0abe", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/7e4e9d432d88981fb9bb463970c5b98ce85c0abe. equipped-FEET-vox & on-equipped-FEET-vox state taken from vgstation13 at https://github.com/vgstation-coders/vgstation13/blob/31d6576ba8102135d058ef49c3cb6ecbe8db8a79/icons/mob/species/vox/shoes.dmi, and equipped-FEET-vox state modified to fix inconsistencies", "size": { "x": 32, "y": 32 @@ -15,6 +15,14 @@ "name": "on-equipped-FEET", "directions": 4 }, + { + "name": "equipped-FEET-vox", + "directions": 4 + }, + { + "name": "on-equipped-FEET-vox", + "directions": 4 + }, { "name": "icon" }, diff --git a/Resources/Textures/Clothing/Shoes/Boots/magboots.rsi/on-equipped-FEET-vox.png b/Resources/Textures/Clothing/Shoes/Boots/magboots.rsi/on-equipped-FEET-vox.png new file mode 100644 index 0000000000..e9d3dffc76 Binary files /dev/null and b/Resources/Textures/Clothing/Shoes/Boots/magboots.rsi/on-equipped-FEET-vox.png differ diff --git a/Resources/Textures/Clothing/Shoes/Misc/slippers.rsi/equipped-FEET-vox.png b/Resources/Textures/Clothing/Shoes/Misc/slippers.rsi/equipped-FEET-vox.png new file mode 100644 index 0000000000..02a752334b Binary files /dev/null and b/Resources/Textures/Clothing/Shoes/Misc/slippers.rsi/equipped-FEET-vox.png differ diff --git a/Resources/Textures/Clothing/Shoes/Misc/slippers.rsi/meta.json b/Resources/Textures/Clothing/Shoes/Misc/slippers.rsi/meta.json index 54b1191d42..1abf6951f1 100644 --- a/Resources/Textures/Clothing/Shoes/Misc/slippers.rsi/meta.json +++ b/Resources/Textures/Clothing/Shoes/Misc/slippers.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/7e4e9d432d88981fb9bb463970c5b98ce85c0abe", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/7e4e9d432d88981fb9bb463970c5b98ce85c0abe. equipped-FEET-vox state taken from vgstation13 at https://github.com/vgstation-coders/vgstation13/blob/31d6576ba8102135d058ef49c3cb6ecbe8db8a79/icons/mob/species/vox/shoes.dmi", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-FEET", "directions": 4 }, + { + "name": "equipped-FEET-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/Shoes/Misc/tourist.rsi/equipped-FEET-vox.png b/Resources/Textures/Clothing/Shoes/Misc/tourist.rsi/equipped-FEET-vox.png new file mode 100644 index 0000000000..a738307297 Binary files /dev/null and b/Resources/Textures/Clothing/Shoes/Misc/tourist.rsi/equipped-FEET-vox.png differ diff --git a/Resources/Textures/Clothing/Shoes/Misc/tourist.rsi/meta.json b/Resources/Textures/Clothing/Shoes/Misc/tourist.rsi/meta.json index 54b1191d42..1abf6951f1 100644 --- a/Resources/Textures/Clothing/Shoes/Misc/tourist.rsi/meta.json +++ b/Resources/Textures/Clothing/Shoes/Misc/tourist.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/7e4e9d432d88981fb9bb463970c5b98ce85c0abe", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/7e4e9d432d88981fb9bb463970c5b98ce85c0abe. equipped-FEET-vox state taken from vgstation13 at https://github.com/vgstation-coders/vgstation13/blob/31d6576ba8102135d058ef49c3cb6ecbe8db8a79/icons/mob/species/vox/shoes.dmi", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-FEET", "directions": 4 }, + { + "name": "equipped-FEET-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/Shoes/Specific/clown.rsi/equipped-FEET-vox.png b/Resources/Textures/Clothing/Shoes/Specific/clown.rsi/equipped-FEET-vox.png new file mode 100644 index 0000000000..2dc1885cd3 Binary files /dev/null and b/Resources/Textures/Clothing/Shoes/Specific/clown.rsi/equipped-FEET-vox.png differ diff --git a/Resources/Textures/Clothing/Shoes/Specific/clown.rsi/meta.json b/Resources/Textures/Clothing/Shoes/Specific/clown.rsi/meta.json index 54b1191d42..1abf6951f1 100644 --- a/Resources/Textures/Clothing/Shoes/Specific/clown.rsi/meta.json +++ b/Resources/Textures/Clothing/Shoes/Specific/clown.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/7e4e9d432d88981fb9bb463970c5b98ce85c0abe", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/7e4e9d432d88981fb9bb463970c5b98ce85c0abe. equipped-FEET-vox state taken from vgstation13 at https://github.com/vgstation-coders/vgstation13/blob/31d6576ba8102135d058ef49c3cb6ecbe8db8a79/icons/mob/species/vox/shoes.dmi", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-FEET", "directions": 4 }, + { + "name": "equipped-FEET-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/Shoes/Specific/swat.rsi/equipped-FEET-vox.png b/Resources/Textures/Clothing/Shoes/Specific/swat.rsi/equipped-FEET-vox.png new file mode 100644 index 0000000000..73ab1326b8 Binary files /dev/null and b/Resources/Textures/Clothing/Shoes/Specific/swat.rsi/equipped-FEET-vox.png differ diff --git a/Resources/Textures/Clothing/Shoes/Specific/swat.rsi/meta.json b/Resources/Textures/Clothing/Shoes/Specific/swat.rsi/meta.json index 54b1191d42..1abf6951f1 100644 --- a/Resources/Textures/Clothing/Shoes/Specific/swat.rsi/meta.json +++ b/Resources/Textures/Clothing/Shoes/Specific/swat.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/7e4e9d432d88981fb9bb463970c5b98ce85c0abe", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/7e4e9d432d88981fb9bb463970c5b98ce85c0abe. equipped-FEET-vox state taken from vgstation13 at https://github.com/vgstation-coders/vgstation13/blob/31d6576ba8102135d058ef49c3cb6ecbe8db8a79/icons/mob/species/vox/shoes.dmi", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-FEET", "directions": 4 }, + { + "name": "equipped-FEET-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/Shoes/Specific/wizard.rsi/equipped-FEET-vox.png b/Resources/Textures/Clothing/Shoes/Specific/wizard.rsi/equipped-FEET-vox.png new file mode 100644 index 0000000000..838d02bb45 Binary files /dev/null and b/Resources/Textures/Clothing/Shoes/Specific/wizard.rsi/equipped-FEET-vox.png differ diff --git a/Resources/Textures/Clothing/Shoes/Specific/wizard.rsi/meta.json b/Resources/Textures/Clothing/Shoes/Specific/wizard.rsi/meta.json index 54b1191d42..1abf6951f1 100644 --- a/Resources/Textures/Clothing/Shoes/Specific/wizard.rsi/meta.json +++ b/Resources/Textures/Clothing/Shoes/Specific/wizard.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/7e4e9d432d88981fb9bb463970c5b98ce85c0abe", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/7e4e9d432d88981fb9bb463970c5b98ce85c0abe. equipped-FEET-vox state taken from vgstation13 at https://github.com/vgstation-coders/vgstation13/blob/31d6576ba8102135d058ef49c3cb6ecbe8db8a79/icons/mob/species/vox/shoes.dmi", "size": { "x": 32, "y": 32 @@ -14,6 +14,10 @@ "name": "equipped-FEET", "directions": 4 }, + { + "name": "equipped-FEET-vox", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Clothing/Under/Socks/coder.rsi/equipped-FEET.png b/Resources/Textures/Clothing/Under/Socks/coder.rsi/equipped-FEET.png index 0fea622063..4bb67f166d 100644 Binary files a/Resources/Textures/Clothing/Under/Socks/coder.rsi/equipped-FEET.png and b/Resources/Textures/Clothing/Under/Socks/coder.rsi/equipped-FEET.png differ diff --git a/Resources/Textures/Clothing/Under/Socks/coder.rsi/meta.json b/Resources/Textures/Clothing/Under/Socks/coder.rsi/meta.json index 194b8c4f0f..1bb04ab6eb 100644 --- a/Resources/Textures/Clothing/Under/Socks/coder.rsi/meta.json +++ b/Resources/Textures/Clothing/Under/Socks/coder.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/c838ba21dae97db345e0113f99596decd1d66039", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/c838ba21dae97db345e0113f99596decd1d66039, equipped-FEET modified by Psychpsyo", "size": { "x": 32, "y": 32 diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_purple.rsi/equipped-INNERCLOTHING-monkey.png b/Resources/Textures/Clothing/Uniforms/Jumpskirt/bartender_purple.rsi/equipped-INNERCLOTHING-monkey.png similarity index 100% rename from Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_purple.rsi/equipped-INNERCLOTHING-monkey.png rename to Resources/Textures/Clothing/Uniforms/Jumpskirt/bartender_purple.rsi/equipped-INNERCLOTHING-monkey.png diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_purple.rsi/equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpskirt/bartender_purple.rsi/equipped-INNERCLOTHING.png similarity index 100% rename from Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_purple.rsi/equipped-INNERCLOTHING.png rename to Resources/Textures/Clothing/Uniforms/Jumpskirt/bartender_purple.rsi/equipped-INNERCLOTHING.png diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_purple.rsi/icon.png b/Resources/Textures/Clothing/Uniforms/Jumpskirt/bartender_purple.rsi/icon.png similarity index 100% rename from Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_purple.rsi/icon.png rename to Resources/Textures/Clothing/Uniforms/Jumpskirt/bartender_purple.rsi/icon.png diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_purple.rsi/inhand-left.png b/Resources/Textures/Clothing/Uniforms/Jumpskirt/bartender_purple.rsi/inhand-left.png similarity index 100% rename from Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_purple.rsi/inhand-left.png rename to Resources/Textures/Clothing/Uniforms/Jumpskirt/bartender_purple.rsi/inhand-left.png diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_purple.rsi/inhand-right.png b/Resources/Textures/Clothing/Uniforms/Jumpskirt/bartender_purple.rsi/inhand-right.png similarity index 100% rename from Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_purple.rsi/inhand-right.png rename to Resources/Textures/Clothing/Uniforms/Jumpskirt/bartender_purple.rsi/inhand-right.png diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_purple.rsi/meta.json b/Resources/Textures/Clothing/Uniforms/Jumpskirt/bartender_purple.rsi/meta.json similarity index 100% rename from Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_purple.rsi/meta.json rename to Resources/Textures/Clothing/Uniforms/Jumpskirt/bartender_purple.rsi/meta.json diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/gladiator.rsi/equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpskirt/gladiator.rsi/equipped-INNERCLOTHING.png similarity index 100% rename from Resources/Textures/Clothing/Uniforms/Jumpsuit/gladiator.rsi/equipped-INNERCLOTHING.png rename to Resources/Textures/Clothing/Uniforms/Jumpskirt/gladiator.rsi/equipped-INNERCLOTHING.png diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/gladiator.rsi/icon.png b/Resources/Textures/Clothing/Uniforms/Jumpskirt/gladiator.rsi/icon.png similarity index 100% rename from Resources/Textures/Clothing/Uniforms/Jumpsuit/gladiator.rsi/icon.png rename to Resources/Textures/Clothing/Uniforms/Jumpskirt/gladiator.rsi/icon.png diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/gladiator.rsi/meta.json b/Resources/Textures/Clothing/Uniforms/Jumpskirt/gladiator.rsi/meta.json similarity index 100% rename from Resources/Textures/Clothing/Uniforms/Jumpsuit/gladiator.rsi/meta.json rename to Resources/Textures/Clothing/Uniforms/Jumpskirt/gladiator.rsi/meta.json diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/kimono.rsi/equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpskirt/kimono.rsi/equipped-INNERCLOTHING.png similarity index 100% rename from Resources/Textures/Clothing/Uniforms/Jumpsuit/kimono.rsi/equipped-INNERCLOTHING.png rename to Resources/Textures/Clothing/Uniforms/Jumpskirt/kimono.rsi/equipped-INNERCLOTHING.png diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/kimono.rsi/icon.png b/Resources/Textures/Clothing/Uniforms/Jumpskirt/kimono.rsi/icon.png similarity index 100% rename from Resources/Textures/Clothing/Uniforms/Jumpsuit/kimono.rsi/icon.png rename to Resources/Textures/Clothing/Uniforms/Jumpskirt/kimono.rsi/icon.png diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/kimono.rsi/inhand-left.png b/Resources/Textures/Clothing/Uniforms/Jumpskirt/kimono.rsi/inhand-left.png similarity index 100% rename from Resources/Textures/Clothing/Uniforms/Jumpsuit/kimono.rsi/inhand-left.png rename to Resources/Textures/Clothing/Uniforms/Jumpskirt/kimono.rsi/inhand-left.png diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/kimono.rsi/inhand-right.png b/Resources/Textures/Clothing/Uniforms/Jumpskirt/kimono.rsi/inhand-right.png similarity index 100% rename from Resources/Textures/Clothing/Uniforms/Jumpsuit/kimono.rsi/inhand-right.png rename to Resources/Textures/Clothing/Uniforms/Jumpskirt/kimono.rsi/inhand-right.png diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/kimono.rsi/meta.json b/Resources/Textures/Clothing/Uniforms/Jumpskirt/kimono.rsi/meta.json similarity index 100% rename from Resources/Textures/Clothing/Uniforms/Jumpsuit/kimono.rsi/meta.json rename to Resources/Textures/Clothing/Uniforms/Jumpskirt/kimono.rsi/meta.json diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/monastic_robe_dark.rsi/equipped-INNERCLOTHING-monkey.png b/Resources/Textures/Clothing/Uniforms/Jumpskirt/monastic_robe_dark.rsi/equipped-INNERCLOTHING-monkey.png similarity index 100% rename from Resources/Textures/Clothing/Uniforms/Jumpsuit/monastic_robe_dark.rsi/equipped-INNERCLOTHING-monkey.png rename to Resources/Textures/Clothing/Uniforms/Jumpskirt/monastic_robe_dark.rsi/equipped-INNERCLOTHING-monkey.png diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/monastic_robe_dark.rsi/equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpskirt/monastic_robe_dark.rsi/equipped-INNERCLOTHING.png similarity index 100% rename from Resources/Textures/Clothing/Uniforms/Jumpsuit/monastic_robe_dark.rsi/equipped-INNERCLOTHING.png rename to Resources/Textures/Clothing/Uniforms/Jumpskirt/monastic_robe_dark.rsi/equipped-INNERCLOTHING.png diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/monastic_robe_dark.rsi/icon.png b/Resources/Textures/Clothing/Uniforms/Jumpskirt/monastic_robe_dark.rsi/icon.png similarity index 100% rename from Resources/Textures/Clothing/Uniforms/Jumpsuit/monastic_robe_dark.rsi/icon.png rename to Resources/Textures/Clothing/Uniforms/Jumpskirt/monastic_robe_dark.rsi/icon.png diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/monastic_robe_dark.rsi/inhand-left.png b/Resources/Textures/Clothing/Uniforms/Jumpskirt/monastic_robe_dark.rsi/inhand-left.png similarity index 100% rename from Resources/Textures/Clothing/Uniforms/Jumpsuit/monastic_robe_dark.rsi/inhand-left.png rename to Resources/Textures/Clothing/Uniforms/Jumpskirt/monastic_robe_dark.rsi/inhand-left.png diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/monastic_robe_dark.rsi/inhand-right.png b/Resources/Textures/Clothing/Uniforms/Jumpskirt/monastic_robe_dark.rsi/inhand-right.png similarity index 100% rename from Resources/Textures/Clothing/Uniforms/Jumpsuit/monastic_robe_dark.rsi/inhand-right.png rename to Resources/Textures/Clothing/Uniforms/Jumpskirt/monastic_robe_dark.rsi/inhand-right.png diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/monastic_robe_dark.rsi/meta.json b/Resources/Textures/Clothing/Uniforms/Jumpskirt/monastic_robe_dark.rsi/meta.json similarity index 100% rename from Resources/Textures/Clothing/Uniforms/Jumpsuit/monastic_robe_dark.rsi/meta.json rename to Resources/Textures/Clothing/Uniforms/Jumpskirt/monastic_robe_dark.rsi/meta.json diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/monastic_robe_light.rsi/equipped-INNERCLOTHING-monkey.png b/Resources/Textures/Clothing/Uniforms/Jumpskirt/monastic_robe_light.rsi/equipped-INNERCLOTHING-monkey.png similarity index 100% rename from Resources/Textures/Clothing/Uniforms/Jumpsuit/monastic_robe_light.rsi/equipped-INNERCLOTHING-monkey.png rename to Resources/Textures/Clothing/Uniforms/Jumpskirt/monastic_robe_light.rsi/equipped-INNERCLOTHING-monkey.png diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/monastic_robe_light.rsi/equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpskirt/monastic_robe_light.rsi/equipped-INNERCLOTHING.png similarity index 100% rename from Resources/Textures/Clothing/Uniforms/Jumpsuit/monastic_robe_light.rsi/equipped-INNERCLOTHING.png rename to Resources/Textures/Clothing/Uniforms/Jumpskirt/monastic_robe_light.rsi/equipped-INNERCLOTHING.png diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/monastic_robe_light.rsi/icon.png b/Resources/Textures/Clothing/Uniforms/Jumpskirt/monastic_robe_light.rsi/icon.png similarity index 100% rename from Resources/Textures/Clothing/Uniforms/Jumpsuit/monastic_robe_light.rsi/icon.png rename to Resources/Textures/Clothing/Uniforms/Jumpskirt/monastic_robe_light.rsi/icon.png diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/monastic_robe_light.rsi/inhand-left.png b/Resources/Textures/Clothing/Uniforms/Jumpskirt/monastic_robe_light.rsi/inhand-left.png similarity index 100% rename from Resources/Textures/Clothing/Uniforms/Jumpsuit/monastic_robe_light.rsi/inhand-left.png rename to Resources/Textures/Clothing/Uniforms/Jumpskirt/monastic_robe_light.rsi/inhand-left.png diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/monastic_robe_light.rsi/inhand-right.png b/Resources/Textures/Clothing/Uniforms/Jumpskirt/monastic_robe_light.rsi/inhand-right.png similarity index 100% rename from Resources/Textures/Clothing/Uniforms/Jumpsuit/monastic_robe_light.rsi/inhand-right.png rename to Resources/Textures/Clothing/Uniforms/Jumpskirt/monastic_robe_light.rsi/inhand-right.png diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/monastic_robe_light.rsi/meta.json b/Resources/Textures/Clothing/Uniforms/Jumpskirt/monastic_robe_light.rsi/meta.json similarity index 100% rename from Resources/Textures/Clothing/Uniforms/Jumpsuit/monastic_robe_light.rsi/meta.json rename to Resources/Textures/Clothing/Uniforms/Jumpskirt/monastic_robe_light.rsi/meta.json diff --git a/Resources/Textures/Clothing/Uniforms/Jumpskirt/olddress.rsi/equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpskirt/olddress.rsi/equipped-INNERCLOTHING.png new file mode 100644 index 0000000000..145cf97b2e Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpskirt/olddress.rsi/equipped-INNERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpskirt/olddress.rsi/icon.png b/Resources/Textures/Clothing/Uniforms/Jumpskirt/olddress.rsi/icon.png new file mode 100644 index 0000000000..28b2de1692 Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpskirt/olddress.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpskirt/olddress.rsi/inhand-left.png b/Resources/Textures/Clothing/Uniforms/Jumpskirt/olddress.rsi/inhand-left.png new file mode 100644 index 0000000000..a85d0a11e9 Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpskirt/olddress.rsi/inhand-left.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpskirt/olddress.rsi/inhand-right.png b/Resources/Textures/Clothing/Uniforms/Jumpskirt/olddress.rsi/inhand-right.png new file mode 100644 index 0000000000..4d6da567de Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpskirt/olddress.rsi/inhand-right.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpskirt/olddress.rsi/meta.json b/Resources/Textures/Clothing/Uniforms/Jumpskirt/olddress.rsi/meta.json new file mode 100644 index 0000000000..d4ea50d555 --- /dev/null +++ b/Resources/Textures/Clothing/Uniforms/Jumpskirt/olddress.rsi/meta.json @@ -0,0 +1,26 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "fujiwaranao", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-INNERCLOTHING", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/Uniforms/Jumpskirt/security.rsi/equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpskirt/security.rsi/equipped-INNERCLOTHING.png index 8ffa6e2e94..8781d46b5f 100644 Binary files a/Resources/Textures/Clothing/Uniforms/Jumpskirt/security.rsi/equipped-INNERCLOTHING.png and b/Resources/Textures/Clothing/Uniforms/Jumpskirt/security.rsi/equipped-INNERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpskirt/senior_officer.rsi/equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpskirt/senior_officer.rsi/equipped-INNERCLOTHING.png index b3ecbd9712..8d2ae43649 100644 Binary files a/Resources/Textures/Clothing/Uniforms/Jumpskirt/senior_officer.rsi/equipped-INNERCLOTHING.png and b/Resources/Textures/Clothing/Uniforms/Jumpskirt/senior_officer.rsi/equipped-INNERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpskirt/senior_officer.rsi/icon.png b/Resources/Textures/Clothing/Uniforms/Jumpskirt/senior_officer.rsi/icon.png index 18ab875179..b08cd213de 100644 Binary files a/Resources/Textures/Clothing/Uniforms/Jumpskirt/senior_officer.rsi/icon.png and b/Resources/Textures/Clothing/Uniforms/Jumpskirt/senior_officer.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpskirt/senior_physician.rsi/equipped-INNERCLOTHING-monkey.png b/Resources/Textures/Clothing/Uniforms/Jumpskirt/senior_physician.rsi/equipped-INNERCLOTHING-monkey.png index 4e98c89cc9..830f482ab8 100644 Binary files a/Resources/Textures/Clothing/Uniforms/Jumpskirt/senior_physician.rsi/equipped-INNERCLOTHING-monkey.png and b/Resources/Textures/Clothing/Uniforms/Jumpskirt/senior_physician.rsi/equipped-INNERCLOTHING-monkey.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpskirt/senior_physician.rsi/equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpskirt/senior_physician.rsi/equipped-INNERCLOTHING.png index 311e0aecb9..c92abfde99 100644 Binary files a/Resources/Textures/Clothing/Uniforms/Jumpskirt/senior_physician.rsi/equipped-INNERCLOTHING.png and b/Resources/Textures/Clothing/Uniforms/Jumpskirt/senior_physician.rsi/equipped-INNERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpskirt/senior_physician.rsi/icon.png b/Resources/Textures/Clothing/Uniforms/Jumpskirt/senior_physician.rsi/icon.png index aaee2537da..65ef81a252 100644 Binary files a/Resources/Textures/Clothing/Uniforms/Jumpskirt/senior_physician.rsi/icon.png and b/Resources/Textures/Clothing/Uniforms/Jumpskirt/senior_physician.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpskirt/senior_physician.rsi/inhand-left.png b/Resources/Textures/Clothing/Uniforms/Jumpskirt/senior_physician.rsi/inhand-left.png index e0b2d4759b..4381a0172b 100644 Binary files a/Resources/Textures/Clothing/Uniforms/Jumpskirt/senior_physician.rsi/inhand-left.png and b/Resources/Textures/Clothing/Uniforms/Jumpskirt/senior_physician.rsi/inhand-left.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpskirt/senior_physician.rsi/inhand-right.png b/Resources/Textures/Clothing/Uniforms/Jumpskirt/senior_physician.rsi/inhand-right.png index a2ecc2b551..3855dc44e1 100644 Binary files a/Resources/Textures/Clothing/Uniforms/Jumpskirt/senior_physician.rsi/inhand-right.png and b/Resources/Textures/Clothing/Uniforms/Jumpskirt/senior_physician.rsi/inhand-right.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_idris.rsi/equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_idris.rsi/equipped-INNERCLOTHING.png new file mode 100644 index 0000000000..0870a1469e Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_idris.rsi/equipped-INNERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_idris.rsi/flipped-equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_idris.rsi/flipped-equipped-INNERCLOTHING.png new file mode 100644 index 0000000000..b1542b0879 Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_idris.rsi/flipped-equipped-INNERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_idris.rsi/icon.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_idris.rsi/icon.png new file mode 100644 index 0000000000..e5618fd85b Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_idris.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_idris.rsi/icon_flipped.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_idris.rsi/icon_flipped.png new file mode 100644 index 0000000000..e5618fd85b Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_idris.rsi/icon_flipped.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_idris.rsi/meta.json b/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_idris.rsi/meta.json new file mode 100644 index 0000000000..5fb2373c7c --- /dev/null +++ b/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_idris.rsi/meta.json @@ -0,0 +1,25 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from Aurorastation at commit https://github.com/Aurorastation/Aurora.3/commit/a8759f3c77b61315df50ee86969799f5a0afe894", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "icon_flipped" + }, + { + "name": "equipped-INNERCLOTHING", + "directions": 4 + }, + { + "name": "flipped-equipped-INNERCLOTHING", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_nt.rsi/equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_nt.rsi/equipped-INNERCLOTHING.png new file mode 100644 index 0000000000..d96717ec85 Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_nt.rsi/equipped-INNERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_nt.rsi/flipped-equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_nt.rsi/flipped-equipped-INNERCLOTHING.png new file mode 100644 index 0000000000..b0938a8d9b Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_nt.rsi/flipped-equipped-INNERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_nt.rsi/icon.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_nt.rsi/icon.png new file mode 100644 index 0000000000..a2df7edad6 Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_nt.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_nt.rsi/icon_flipped.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_nt.rsi/icon_flipped.png new file mode 100644 index 0000000000..a2df7edad6 Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_nt.rsi/icon_flipped.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_nt.rsi/meta.json b/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_nt.rsi/meta.json new file mode 100644 index 0000000000..5fb2373c7c --- /dev/null +++ b/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_nt.rsi/meta.json @@ -0,0 +1,25 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from Aurorastation at commit https://github.com/Aurorastation/Aurora.3/commit/a8759f3c77b61315df50ee86969799f5a0afe894", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "icon_flipped" + }, + { + "name": "equipped-INNERCLOTHING", + "directions": 4 + }, + { + "name": "flipped-equipped-INNERCLOTHING", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_orion.rsi/equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_orion.rsi/equipped-INNERCLOTHING.png new file mode 100644 index 0000000000..582dfded6b Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_orion.rsi/equipped-INNERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_orion.rsi/flipped-equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_orion.rsi/flipped-equipped-INNERCLOTHING.png new file mode 100644 index 0000000000..0065f96ccf Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_orion.rsi/flipped-equipped-INNERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_orion.rsi/icon.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_orion.rsi/icon.png new file mode 100644 index 0000000000..023044764a Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_orion.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_orion.rsi/icon_flipped.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_orion.rsi/icon_flipped.png new file mode 100644 index 0000000000..023044764a Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_orion.rsi/icon_flipped.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_orion.rsi/meta.json b/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_orion.rsi/meta.json new file mode 100644 index 0000000000..5fb2373c7c --- /dev/null +++ b/Resources/Textures/Clothing/Uniforms/Jumpsuit/bartender_orion.rsi/meta.json @@ -0,0 +1,25 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from Aurorastation at commit https://github.com/Aurorastation/Aurora.3/commit/a8759f3c77b61315df50ee86969799f5a0afe894", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "icon_flipped" + }, + { + "name": "equipped-INNERCLOTHING", + "directions": 4 + }, + { + "name": "flipped-equipped-INNERCLOTHING", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/chef_idris.rsi/equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/chef_idris.rsi/equipped-INNERCLOTHING.png new file mode 100644 index 0000000000..0e198c8ca6 Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/chef_idris.rsi/equipped-INNERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/chef_idris.rsi/flipped-equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/chef_idris.rsi/flipped-equipped-INNERCLOTHING.png new file mode 100644 index 0000000000..ae0a368461 Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/chef_idris.rsi/flipped-equipped-INNERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/chef_idris.rsi/icon.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/chef_idris.rsi/icon.png new file mode 100644 index 0000000000..344384e22b Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/chef_idris.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/chef_idris.rsi/icon_flipped.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/chef_idris.rsi/icon_flipped.png new file mode 100644 index 0000000000..344384e22b Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/chef_idris.rsi/icon_flipped.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/chef_idris.rsi/meta.json b/Resources/Textures/Clothing/Uniforms/Jumpsuit/chef_idris.rsi/meta.json new file mode 100644 index 0000000000..5fb2373c7c --- /dev/null +++ b/Resources/Textures/Clothing/Uniforms/Jumpsuit/chef_idris.rsi/meta.json @@ -0,0 +1,25 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from Aurorastation at commit https://github.com/Aurorastation/Aurora.3/commit/a8759f3c77b61315df50ee86969799f5a0afe894", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "icon_flipped" + }, + { + "name": "equipped-INNERCLOTHING", + "directions": 4 + }, + { + "name": "flipped-equipped-INNERCLOTHING", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/chef_nt.rsi/equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/chef_nt.rsi/equipped-INNERCLOTHING.png new file mode 100644 index 0000000000..28f5d72a40 Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/chef_nt.rsi/equipped-INNERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/chef_nt.rsi/flipped-equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/chef_nt.rsi/flipped-equipped-INNERCLOTHING.png new file mode 100644 index 0000000000..83f9c5604c Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/chef_nt.rsi/flipped-equipped-INNERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/chef_nt.rsi/icon.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/chef_nt.rsi/icon.png new file mode 100644 index 0000000000..26e328aa54 Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/chef_nt.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/chef_nt.rsi/icon_flipped.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/chef_nt.rsi/icon_flipped.png new file mode 100644 index 0000000000..26e328aa54 Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/chef_nt.rsi/icon_flipped.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/chef_nt.rsi/meta.json b/Resources/Textures/Clothing/Uniforms/Jumpsuit/chef_nt.rsi/meta.json new file mode 100644 index 0000000000..5fb2373c7c --- /dev/null +++ b/Resources/Textures/Clothing/Uniforms/Jumpsuit/chef_nt.rsi/meta.json @@ -0,0 +1,25 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from Aurorastation at commit https://github.com/Aurorastation/Aurora.3/commit/a8759f3c77b61315df50ee86969799f5a0afe894", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "icon_flipped" + }, + { + "name": "equipped-INNERCLOTHING", + "directions": 4 + }, + { + "name": "flipped-equipped-INNERCLOTHING", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/chef_orion.rsi/equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/chef_orion.rsi/equipped-INNERCLOTHING.png new file mode 100644 index 0000000000..582dfded6b Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/chef_orion.rsi/equipped-INNERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/chef_orion.rsi/flipped-equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/chef_orion.rsi/flipped-equipped-INNERCLOTHING.png new file mode 100644 index 0000000000..0065f96ccf Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/chef_orion.rsi/flipped-equipped-INNERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/chef_orion.rsi/icon.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/chef_orion.rsi/icon.png new file mode 100644 index 0000000000..a8617562af Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/chef_orion.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/chef_orion.rsi/icon_flipped.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/chef_orion.rsi/icon_flipped.png new file mode 100644 index 0000000000..a8617562af Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/chef_orion.rsi/icon_flipped.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/chef_orion.rsi/meta.json b/Resources/Textures/Clothing/Uniforms/Jumpsuit/chef_orion.rsi/meta.json new file mode 100644 index 0000000000..5fb2373c7c --- /dev/null +++ b/Resources/Textures/Clothing/Uniforms/Jumpsuit/chef_orion.rsi/meta.json @@ -0,0 +1,25 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from Aurorastation at commit https://github.com/Aurorastation/Aurora.3/commit/a8759f3c77b61315df50ee86969799f5a0afe894", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "icon_flipped" + }, + { + "name": "equipped-INNERCLOTHING", + "directions": 4 + }, + { + "name": "flipped-equipped-INNERCLOTHING", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/hydro_idris.rsi/equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/hydro_idris.rsi/equipped-INNERCLOTHING.png new file mode 100644 index 0000000000..363f2d2a74 Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/hydro_idris.rsi/equipped-INNERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/hydro_idris.rsi/flipped-equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/hydro_idris.rsi/flipped-equipped-INNERCLOTHING.png new file mode 100644 index 0000000000..cac2097982 Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/hydro_idris.rsi/flipped-equipped-INNERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/hydro_idris.rsi/icon.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/hydro_idris.rsi/icon.png new file mode 100644 index 0000000000..c9c994b1ca Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/hydro_idris.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/hydro_idris.rsi/icon_flipped.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/hydro_idris.rsi/icon_flipped.png new file mode 100644 index 0000000000..c9c994b1ca Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/hydro_idris.rsi/icon_flipped.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/hydro_idris.rsi/meta.json b/Resources/Textures/Clothing/Uniforms/Jumpsuit/hydro_idris.rsi/meta.json new file mode 100644 index 0000000000..5fb2373c7c --- /dev/null +++ b/Resources/Textures/Clothing/Uniforms/Jumpsuit/hydro_idris.rsi/meta.json @@ -0,0 +1,25 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from Aurorastation at commit https://github.com/Aurorastation/Aurora.3/commit/a8759f3c77b61315df50ee86969799f5a0afe894", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "icon_flipped" + }, + { + "name": "equipped-INNERCLOTHING", + "directions": 4 + }, + { + "name": "flipped-equipped-INNERCLOTHING", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/hydro_nt.rsi/equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/hydro_nt.rsi/equipped-INNERCLOTHING.png new file mode 100644 index 0000000000..a7e697f4b3 Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/hydro_nt.rsi/equipped-INNERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/hydro_nt.rsi/flipped-equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/hydro_nt.rsi/flipped-equipped-INNERCLOTHING.png new file mode 100644 index 0000000000..1ce04abfd6 Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/hydro_nt.rsi/flipped-equipped-INNERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/hydro_nt.rsi/icon.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/hydro_nt.rsi/icon.png new file mode 100644 index 0000000000..7e69ab12b4 Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/hydro_nt.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/hydro_nt.rsi/icon_flipped.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/hydro_nt.rsi/icon_flipped.png new file mode 100644 index 0000000000..7e69ab12b4 Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/hydro_nt.rsi/icon_flipped.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/hydro_nt.rsi/meta.json b/Resources/Textures/Clothing/Uniforms/Jumpsuit/hydro_nt.rsi/meta.json new file mode 100644 index 0000000000..5fb2373c7c --- /dev/null +++ b/Resources/Textures/Clothing/Uniforms/Jumpsuit/hydro_nt.rsi/meta.json @@ -0,0 +1,25 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from Aurorastation at commit https://github.com/Aurorastation/Aurora.3/commit/a8759f3c77b61315df50ee86969799f5a0afe894", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "icon_flipped" + }, + { + "name": "equipped-INNERCLOTHING", + "directions": 4 + }, + { + "name": "flipped-equipped-INNERCLOTHING", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/hydro_orion.rsi/equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/hydro_orion.rsi/equipped-INNERCLOTHING.png new file mode 100644 index 0000000000..5be6ed06ff Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/hydro_orion.rsi/equipped-INNERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/hydro_orion.rsi/flipped-equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/hydro_orion.rsi/flipped-equipped-INNERCLOTHING.png new file mode 100644 index 0000000000..a30d64c9a3 Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/hydro_orion.rsi/flipped-equipped-INNERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/hydro_orion.rsi/icon.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/hydro_orion.rsi/icon.png new file mode 100644 index 0000000000..30e2a82547 Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/hydro_orion.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/hydro_orion.rsi/icon_flipped.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/hydro_orion.rsi/icon_flipped.png new file mode 100644 index 0000000000..30e2a82547 Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/hydro_orion.rsi/icon_flipped.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/hydro_orion.rsi/meta.json b/Resources/Textures/Clothing/Uniforms/Jumpsuit/hydro_orion.rsi/meta.json new file mode 100644 index 0000000000..5fb2373c7c --- /dev/null +++ b/Resources/Textures/Clothing/Uniforms/Jumpsuit/hydro_orion.rsi/meta.json @@ -0,0 +1,25 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from Aurorastation at commit https://github.com/Aurorastation/Aurora.3/commit/a8759f3c77b61315df50ee86969799f5a0afe894", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "icon_flipped" + }, + { + "name": "equipped-INNERCLOTHING", + "directions": 4 + }, + { + "name": "flipped-equipped-INNERCLOTHING", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/janitor_idris.rsi/equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/janitor_idris.rsi/equipped-INNERCLOTHING.png new file mode 100644 index 0000000000..7e7edddcda Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/janitor_idris.rsi/equipped-INNERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/janitor_idris.rsi/flipped-equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/janitor_idris.rsi/flipped-equipped-INNERCLOTHING.png new file mode 100644 index 0000000000..cb6862563b Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/janitor_idris.rsi/flipped-equipped-INNERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/janitor_idris.rsi/icon.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/janitor_idris.rsi/icon.png new file mode 100644 index 0000000000..8c4abbbf2b Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/janitor_idris.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/janitor_idris.rsi/icon_flipped.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/janitor_idris.rsi/icon_flipped.png new file mode 100644 index 0000000000..8c4abbbf2b Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/janitor_idris.rsi/icon_flipped.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/janitor_idris.rsi/meta.json b/Resources/Textures/Clothing/Uniforms/Jumpsuit/janitor_idris.rsi/meta.json new file mode 100644 index 0000000000..5fb2373c7c --- /dev/null +++ b/Resources/Textures/Clothing/Uniforms/Jumpsuit/janitor_idris.rsi/meta.json @@ -0,0 +1,25 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from Aurorastation at commit https://github.com/Aurorastation/Aurora.3/commit/a8759f3c77b61315df50ee86969799f5a0afe894", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "icon_flipped" + }, + { + "name": "equipped-INNERCLOTHING", + "directions": 4 + }, + { + "name": "flipped-equipped-INNERCLOTHING", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/janitor_nt.rsi/equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/janitor_nt.rsi/equipped-INNERCLOTHING.png new file mode 100644 index 0000000000..3a155aaf9e Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/janitor_nt.rsi/equipped-INNERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/janitor_nt.rsi/flipped-equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/janitor_nt.rsi/flipped-equipped-INNERCLOTHING.png new file mode 100644 index 0000000000..8a275a2d43 Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/janitor_nt.rsi/flipped-equipped-INNERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/janitor_nt.rsi/icon.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/janitor_nt.rsi/icon.png new file mode 100644 index 0000000000..6aaba29a2f Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/janitor_nt.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/janitor_nt.rsi/icon_flipped.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/janitor_nt.rsi/icon_flipped.png new file mode 100644 index 0000000000..6aaba29a2f Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/janitor_nt.rsi/icon_flipped.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/janitor_nt.rsi/meta.json b/Resources/Textures/Clothing/Uniforms/Jumpsuit/janitor_nt.rsi/meta.json new file mode 100644 index 0000000000..5fb2373c7c --- /dev/null +++ b/Resources/Textures/Clothing/Uniforms/Jumpsuit/janitor_nt.rsi/meta.json @@ -0,0 +1,25 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from Aurorastation at commit https://github.com/Aurorastation/Aurora.3/commit/a8759f3c77b61315df50ee86969799f5a0afe894", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "icon_flipped" + }, + { + "name": "equipped-INNERCLOTHING", + "directions": 4 + }, + { + "name": "flipped-equipped-INNERCLOTHING", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/janitor_orion.rsi/equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/janitor_orion.rsi/equipped-INNERCLOTHING.png new file mode 100644 index 0000000000..995b2caa14 Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/janitor_orion.rsi/equipped-INNERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/janitor_orion.rsi/flipped-equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/janitor_orion.rsi/flipped-equipped-INNERCLOTHING.png new file mode 100644 index 0000000000..7342f0ae8e Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/janitor_orion.rsi/flipped-equipped-INNERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/janitor_orion.rsi/icon.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/janitor_orion.rsi/icon.png new file mode 100644 index 0000000000..9fb0d1e0d4 Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/janitor_orion.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/janitor_orion.rsi/icon_flipped.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/janitor_orion.rsi/icon_flipped.png new file mode 100644 index 0000000000..9fb0d1e0d4 Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/janitor_orion.rsi/icon_flipped.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/janitor_orion.rsi/meta.json b/Resources/Textures/Clothing/Uniforms/Jumpsuit/janitor_orion.rsi/meta.json new file mode 100644 index 0000000000..5fb2373c7c --- /dev/null +++ b/Resources/Textures/Clothing/Uniforms/Jumpsuit/janitor_orion.rsi/meta.json @@ -0,0 +1,25 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from Aurorastation at commit https://github.com/Aurorastation/Aurora.3/commit/a8759f3c77b61315df50ee86969799f5a0afe894", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "icon_flipped" + }, + { + "name": "equipped-INNERCLOTHING", + "directions": 4 + }, + { + "name": "flipped-equipped-INNERCLOTHING", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_heph.rsi/equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_heph.rsi/equipped-INNERCLOTHING.png new file mode 100644 index 0000000000..e344719ee5 Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_heph.rsi/equipped-INNERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_heph.rsi/flipped-equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_heph.rsi/flipped-equipped-INNERCLOTHING.png new file mode 100644 index 0000000000..e7166beafc Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_heph.rsi/flipped-equipped-INNERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_heph.rsi/icon.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_heph.rsi/icon.png new file mode 100644 index 0000000000..f3ce2436e0 Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_heph.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_heph.rsi/icon_flipped.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_heph.rsi/icon_flipped.png new file mode 100644 index 0000000000..f3ce2436e0 Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_heph.rsi/icon_flipped.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_heph.rsi/meta.json b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_heph.rsi/meta.json new file mode 100644 index 0000000000..5fb2373c7c --- /dev/null +++ b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_heph.rsi/meta.json @@ -0,0 +1,25 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from Aurorastation at commit https://github.com/Aurorastation/Aurora.3/commit/a8759f3c77b61315df50ee86969799f5a0afe894", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "icon_flipped" + }, + { + "name": "equipped-INNERCLOTHING", + "directions": 4 + }, + { + "name": "flipped-equipped-INNERCLOTHING", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_idris.rsi/equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_idris.rsi/equipped-INNERCLOTHING.png new file mode 100644 index 0000000000..c7820e94ca Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_idris.rsi/equipped-INNERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_idris.rsi/flipped-equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_idris.rsi/flipped-equipped-INNERCLOTHING.png new file mode 100644 index 0000000000..cd9f3c5e60 Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_idris.rsi/flipped-equipped-INNERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_idris.rsi/icon.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_idris.rsi/icon.png new file mode 100644 index 0000000000..b732b87fff Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_idris.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_idris.rsi/icon_flipped.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_idris.rsi/icon_flipped.png new file mode 100644 index 0000000000..b732b87fff Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_idris.rsi/icon_flipped.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_idris.rsi/meta.json b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_idris.rsi/meta.json new file mode 100644 index 0000000000..5fb2373c7c --- /dev/null +++ b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_idris.rsi/meta.json @@ -0,0 +1,25 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from Aurorastation at commit https://github.com/Aurorastation/Aurora.3/commit/a8759f3c77b61315df50ee86969799f5a0afe894", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "icon_flipped" + }, + { + "name": "equipped-INNERCLOTHING", + "directions": 4 + }, + { + "name": "flipped-equipped-INNERCLOTHING", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_nt.rsi/equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_nt.rsi/equipped-INNERCLOTHING.png new file mode 100644 index 0000000000..88d44b1ae9 Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_nt.rsi/equipped-INNERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_nt.rsi/flipped-equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_nt.rsi/flipped-equipped-INNERCLOTHING.png new file mode 100644 index 0000000000..7376e70e15 Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_nt.rsi/flipped-equipped-INNERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_nt.rsi/icon.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_nt.rsi/icon.png new file mode 100644 index 0000000000..f1f520dcc6 Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_nt.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_nt.rsi/icon_flipped.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_nt.rsi/icon_flipped.png new file mode 100644 index 0000000000..f1f520dcc6 Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_nt.rsi/icon_flipped.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_nt.rsi/meta.json b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_nt.rsi/meta.json new file mode 100644 index 0000000000..5fb2373c7c --- /dev/null +++ b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_nt.rsi/meta.json @@ -0,0 +1,25 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from Aurorastation at commit https://github.com/Aurorastation/Aurora.3/commit/a8759f3c77b61315df50ee86969799f5a0afe894", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "icon_flipped" + }, + { + "name": "equipped-INNERCLOTHING", + "directions": 4 + }, + { + "name": "flipped-equipped-INNERCLOTHING", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_orion.rsi/equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_orion.rsi/equipped-INNERCLOTHING.png new file mode 100644 index 0000000000..b273c7e084 Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_orion.rsi/equipped-INNERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_orion.rsi/flipped-equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_orion.rsi/flipped-equipped-INNERCLOTHING.png new file mode 100644 index 0000000000..a2ce4da9bd Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_orion.rsi/flipped-equipped-INNERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_orion.rsi/icon.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_orion.rsi/icon.png new file mode 100644 index 0000000000..256f82647f Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_orion.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_orion.rsi/icon_flipped.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_orion.rsi/icon_flipped.png new file mode 100644 index 0000000000..256f82647f Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_orion.rsi/icon_flipped.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_orion.rsi/meta.json b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_orion.rsi/meta.json new file mode 100644 index 0000000000..5fb2373c7c --- /dev/null +++ b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_orion.rsi/meta.json @@ -0,0 +1,25 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from Aurorastation at commit https://github.com/Aurorastation/Aurora.3/commit/a8759f3c77b61315df50ee86969799f5a0afe894", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "icon_flipped" + }, + { + "name": "equipped-INNERCLOTHING", + "directions": 4 + }, + { + "name": "flipped-equipped-INNERCLOTHING", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_pmcg.rsi/equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_pmcg.rsi/equipped-INNERCLOTHING.png new file mode 100644 index 0000000000..08aa1dca97 Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_pmcg.rsi/equipped-INNERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_pmcg.rsi/flipped-equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_pmcg.rsi/flipped-equipped-INNERCLOTHING.png new file mode 100644 index 0000000000..92abe6d490 Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_pmcg.rsi/flipped-equipped-INNERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_pmcg.rsi/icon.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_pmcg.rsi/icon.png new file mode 100644 index 0000000000..a5b325ee0d Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_pmcg.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_pmcg.rsi/icon_flipped.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_pmcg.rsi/icon_flipped.png new file mode 100644 index 0000000000..a5b325ee0d Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_pmcg.rsi/icon_flipped.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_pmcg.rsi/meta.json b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_pmcg.rsi/meta.json new file mode 100644 index 0000000000..5fb2373c7c --- /dev/null +++ b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_pmcg.rsi/meta.json @@ -0,0 +1,25 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from Aurorastation at commit https://github.com/Aurorastation/Aurora.3/commit/a8759f3c77b61315df50ee86969799f5a0afe894", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "icon_flipped" + }, + { + "name": "equipped-INNERCLOTHING", + "directions": 4 + }, + { + "name": "flipped-equipped-INNERCLOTHING", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_zav.rsi/equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_zav.rsi/equipped-INNERCLOTHING.png new file mode 100644 index 0000000000..9fd318e746 Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_zav.rsi/equipped-INNERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_zav.rsi/flipped-equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_zav.rsi/flipped-equipped-INNERCLOTHING.png new file mode 100644 index 0000000000..423f85751a Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_zav.rsi/flipped-equipped-INNERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_zav.rsi/icon.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_zav.rsi/icon.png new file mode 100644 index 0000000000..02699cde78 Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_zav.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_zav.rsi/icon_flipped.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_zav.rsi/icon_flipped.png new file mode 100644 index 0000000000..02699cde78 Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_zav.rsi/icon_flipped.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_zav.rsi/meta.json b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_zav.rsi/meta.json new file mode 100644 index 0000000000..5fb2373c7c --- /dev/null +++ b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_zav.rsi/meta.json @@ -0,0 +1,25 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from Aurorastation at commit https://github.com/Aurorastation/Aurora.3/commit/a8759f3c77b61315df50ee86969799f5a0afe894", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "icon_flipped" + }, + { + "name": "equipped-INNERCLOTHING", + "directions": 4 + }, + { + "name": "flipped-equipped-INNERCLOTHING", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_zeng.rsi/equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_zeng.rsi/equipped-INNERCLOTHING.png new file mode 100644 index 0000000000..ec58f45df1 Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_zeng.rsi/equipped-INNERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_zeng.rsi/flipped-equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_zeng.rsi/flipped-equipped-INNERCLOTHING.png new file mode 100644 index 0000000000..33ab3d300d Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_zeng.rsi/flipped-equipped-INNERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_zeng.rsi/icon.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_zeng.rsi/icon.png new file mode 100644 index 0000000000..133890a5ae Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_zeng.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_zeng.rsi/icon_flipped.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_zeng.rsi/icon_flipped.png new file mode 100644 index 0000000000..133890a5ae Binary files /dev/null and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_zeng.rsi/icon_flipped.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_zeng.rsi/meta.json b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_zeng.rsi/meta.json new file mode 100644 index 0000000000..5fb2373c7c --- /dev/null +++ b/Resources/Textures/Clothing/Uniforms/Jumpsuit/librarian_zeng.rsi/meta.json @@ -0,0 +1,25 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from Aurorastation at commit https://github.com/Aurorastation/Aurora.3/commit/a8759f3c77b61315df50ee86969799f5a0afe894", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "icon_flipped" + }, + { + "name": "equipped-INNERCLOTHING", + "directions": 4 + }, + { + "name": "flipped-equipped-INNERCLOTHING", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/security.rsi/equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/security.rsi/equipped-INNERCLOTHING.png index b3977c9748..2fb771d84c 100644 Binary files a/Resources/Textures/Clothing/Uniforms/Jumpsuit/security.rsi/equipped-INNERCLOTHING.png and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/security.rsi/equipped-INNERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/security.rsi/meta.json b/Resources/Textures/Clothing/Uniforms/Jumpsuit/security.rsi/meta.json index 3f72fb4460..c11c48fe74 100644 --- a/Resources/Textures/Clothing/Uniforms/Jumpsuit/security.rsi/meta.json +++ b/Resources/Textures/Clothing/Uniforms/Jumpsuit/security.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/c838ba21dae97db345e0113f99596decd1d66039. In hand sprite scaled down by potato1234_x, monkey made by brainfood1183 (github) for ss14", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/c838ba21dae97db345e0113f99596decd1d66039. In hand sprite scaled down by potato1234_x, monkey made by brainfood1183 (github) for ss14. equipped-INNERCLOTHING edited by Dutch-VanDerLinde (github).", "size": { "x": 32, "y": 32 diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/senior_officer.rsi/meta.json b/Resources/Textures/Clothing/Uniforms/Jumpsuit/senior_officer.rsi/meta.json index 913d940881..bfad46e184 100644 --- a/Resources/Textures/Clothing/Uniforms/Jumpsuit/senior_officer.rsi/meta.json +++ b/Resources/Textures/Clothing/Uniforms/Jumpsuit/senior_officer.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Edit by Nairodian (github) of secred_s taken from tgstation at pull request https://github.com/tgstation/tgstation/pull/2984", + "copyright": "Edit by Nairodian (github) of secred_s taken from tgstation at pull request https://github.com/tgstation/tgstation/pull/2984, jumpskirt sprite made by Flareguy and edited by Dutch-VanDerLinde.", "size": { "x": 32, "y": 32 diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/senior_physician.rsi/equipped-INNERCLOTHING-monkey.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/senior_physician.rsi/equipped-INNERCLOTHING-monkey.png index 5248c1b09f..1c5fc823eb 100644 Binary files a/Resources/Textures/Clothing/Uniforms/Jumpsuit/senior_physician.rsi/equipped-INNERCLOTHING-monkey.png and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/senior_physician.rsi/equipped-INNERCLOTHING-monkey.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/senior_physician.rsi/equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/senior_physician.rsi/equipped-INNERCLOTHING.png index f67f3c67bf..3e68439c78 100644 Binary files a/Resources/Textures/Clothing/Uniforms/Jumpsuit/senior_physician.rsi/equipped-INNERCLOTHING.png and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/senior_physician.rsi/equipped-INNERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/senior_physician.rsi/icon.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/senior_physician.rsi/icon.png index d5d956c4e5..7a4e5b493f 100644 Binary files a/Resources/Textures/Clothing/Uniforms/Jumpsuit/senior_physician.rsi/icon.png and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/senior_physician.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/senior_physician.rsi/inhand-left.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/senior_physician.rsi/inhand-left.png index e0b2d4759b..4381a0172b 100644 Binary files a/Resources/Textures/Clothing/Uniforms/Jumpsuit/senior_physician.rsi/inhand-left.png and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/senior_physician.rsi/inhand-left.png differ diff --git a/Resources/Textures/Clothing/Uniforms/Jumpsuit/senior_physician.rsi/inhand-right.png b/Resources/Textures/Clothing/Uniforms/Jumpsuit/senior_physician.rsi/inhand-right.png index a2ecc2b551..3855dc44e1 100644 Binary files a/Resources/Textures/Clothing/Uniforms/Jumpsuit/senior_physician.rsi/inhand-right.png and b/Resources/Textures/Clothing/Uniforms/Jumpsuit/senior_physician.rsi/inhand-right.png differ diff --git a/Resources/Textures/DeltaV/Mobs/Animals/shrimp.rsi/dead.png b/Resources/Textures/DeltaV/Mobs/Animals/shrimp.rsi/dead.png new file mode 100644 index 0000000000..f6f1cff31f Binary files /dev/null and b/Resources/Textures/DeltaV/Mobs/Animals/shrimp.rsi/dead.png differ diff --git a/Resources/Textures/DeltaV/Mobs/Animals/shrimp.rsi/meta.json b/Resources/Textures/DeltaV/Mobs/Animals/shrimp.rsi/meta.json new file mode 100644 index 0000000000..0bf79dc66f --- /dev/null +++ b/Resources/Textures/DeltaV/Mobs/Animals/shrimp.rsi/meta.json @@ -0,0 +1,24 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Sprite by Leonardo-DaBepis", + "states": [ + { + "name": "shrimp", + "directions": 4, + "delays": [ + [ 0.3, 0.4, 0.5, 0.3 ], + [ 0.3, 0.4, 0.5, 0.3 ], + [ 0.3, 0.4, 0.5, 0.3 ], + [ 0.3, 0.4, 0.5, 0.3 ] + ] + }, + { + "name": "dead" + } + ] +} diff --git a/Resources/Textures/DeltaV/Mobs/Animals/shrimp.rsi/shrimp.png b/Resources/Textures/DeltaV/Mobs/Animals/shrimp.rsi/shrimp.png new file mode 100644 index 0000000000..c6a19ec13a Binary files /dev/null and b/Resources/Textures/DeltaV/Mobs/Animals/shrimp.rsi/shrimp.png differ diff --git a/Resources/Textures/DeltaV/Objects/Devices/cartridge.rsi/cart-mail.png b/Resources/Textures/DeltaV/Objects/Devices/cartridge.rsi/cart-mail.png new file mode 100644 index 0000000000..5734abb6fd Binary files /dev/null and b/Resources/Textures/DeltaV/Objects/Devices/cartridge.rsi/cart-mail.png differ diff --git a/Resources/Textures/DeltaV/Objects/Devices/cartridge.rsi/meta.json b/Resources/Textures/DeltaV/Objects/Devices/cartridge.rsi/meta.json index 293870d3a3..4a4ba3352f 100644 --- a/Resources/Textures/DeltaV/Objects/Devices/cartridge.rsi/meta.json +++ b/Resources/Textures/DeltaV/Objects/Devices/cartridge.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Timfa", + "copyright": "Timfa, plus edits by portfiend", "size": { "x": 32, "y": 32 @@ -9,6 +9,9 @@ "states": [ { "name": "cart-cri" + }, + { + "name": "cart-mail" } ] -} \ No newline at end of file +} diff --git a/Resources/Textures/Effects/creampie.rsi/creampie_vox.png b/Resources/Textures/Effects/creampie.rsi/creampie_vox.png new file mode 100644 index 0000000000..dfe199b0c8 Binary files /dev/null and b/Resources/Textures/Effects/creampie.rsi/creampie_vox.png differ diff --git a/Resources/Textures/Effects/creampie.rsi/meta.json b/Resources/Textures/Effects/creampie.rsi/meta.json index 54e0cc73c2..8db8a77945 100644 --- a/Resources/Textures/Effects/creampie.rsi/meta.json +++ b/Resources/Textures/Effects/creampie.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from https://github.com/tgstation/tgstation at 0d9c9a8233dfc3fc55edc538955a761a6328bee0. creampie_moth by MilenVolf, creampie_arachnid by PixelTheKermit (Github)", + "copyright": "Taken from https://github.com/tgstation/tgstation at 0d9c9a8233dfc3fc55edc538955a761a6328bee0. creampie_moth by MilenVolf, creampie_arachnid by PixelTheKermit (Github), creampie_vox by Errant", "size": { "x": 32, "y": 32 @@ -66,6 +66,10 @@ "name": "creampie_standborg", "directions": 4 }, + { + "name": "creampie_vox", + "directions": 4 + }, { "name": "creampie_xeno_crit" }, diff --git a/Resources/Textures/Effects/footprints.rsi/dragging-1.png b/Resources/Textures/Effects/footprints.rsi/dragging-1.png new file mode 100644 index 0000000000..74d2aeb074 Binary files /dev/null and b/Resources/Textures/Effects/footprints.rsi/dragging-1.png differ diff --git a/Resources/Textures/Effects/footprints.rsi/dragging-2.png b/Resources/Textures/Effects/footprints.rsi/dragging-2.png new file mode 100644 index 0000000000..cc45f4f8ae Binary files /dev/null and b/Resources/Textures/Effects/footprints.rsi/dragging-2.png differ diff --git a/Resources/Textures/Effects/footprints.rsi/dragging-3.png b/Resources/Textures/Effects/footprints.rsi/dragging-3.png new file mode 100644 index 0000000000..d0f7274dd3 Binary files /dev/null and b/Resources/Textures/Effects/footprints.rsi/dragging-3.png differ diff --git a/Resources/Textures/Effects/footprints.rsi/dragging-4.png b/Resources/Textures/Effects/footprints.rsi/dragging-4.png new file mode 100644 index 0000000000..5eb34b5d57 Binary files /dev/null and b/Resources/Textures/Effects/footprints.rsi/dragging-4.png differ diff --git a/Resources/Textures/Effects/footprints.rsi/dragging-5.png b/Resources/Textures/Effects/footprints.rsi/dragging-5.png new file mode 100644 index 0000000000..6b1b34d145 Binary files /dev/null and b/Resources/Textures/Effects/footprints.rsi/dragging-5.png differ diff --git a/Resources/Textures/Effects/footprints.rsi/dragging-test.png b/Resources/Textures/Effects/footprints.rsi/dragging-test.png new file mode 100644 index 0000000000..b8c9ba50a7 Binary files /dev/null and b/Resources/Textures/Effects/footprints.rsi/dragging-test.png differ diff --git a/Resources/Textures/Effects/footprints.rsi/footprint-left-bare-diona.png b/Resources/Textures/Effects/footprints.rsi/footprint-left-bare-diona.png new file mode 100644 index 0000000000..fa40e0f297 Binary files /dev/null and b/Resources/Textures/Effects/footprints.rsi/footprint-left-bare-diona.png differ diff --git a/Resources/Textures/Effects/footprints.rsi/footprint-left-bare-dwarf.png b/Resources/Textures/Effects/footprints.rsi/footprint-left-bare-dwarf.png new file mode 100644 index 0000000000..43b88aa164 Binary files /dev/null and b/Resources/Textures/Effects/footprints.rsi/footprint-left-bare-dwarf.png differ diff --git a/Resources/Textures/Effects/footprints.rsi/footprint-left-bare-human.png b/Resources/Textures/Effects/footprints.rsi/footprint-left-bare-human.png new file mode 100644 index 0000000000..f7ab3257c5 Binary files /dev/null and b/Resources/Textures/Effects/footprints.rsi/footprint-left-bare-human.png differ diff --git a/Resources/Textures/Effects/footprints.rsi/footprint-left-bare-lizard.png b/Resources/Textures/Effects/footprints.rsi/footprint-left-bare-lizard.png new file mode 100644 index 0000000000..e53ba99227 Binary files /dev/null and b/Resources/Textures/Effects/footprints.rsi/footprint-left-bare-lizard.png differ diff --git a/Resources/Textures/Effects/footprints.rsi/footprint-left-bare-slime.png b/Resources/Textures/Effects/footprints.rsi/footprint-left-bare-slime.png new file mode 100644 index 0000000000..87561cb161 Binary files /dev/null and b/Resources/Textures/Effects/footprints.rsi/footprint-left-bare-slime.png differ diff --git a/Resources/Textures/Effects/footprints.rsi/footprint-left-bare-spider.png b/Resources/Textures/Effects/footprints.rsi/footprint-left-bare-spider.png new file mode 100644 index 0000000000..4939e72c4b Binary files /dev/null and b/Resources/Textures/Effects/footprints.rsi/footprint-left-bare-spider.png differ diff --git a/Resources/Textures/Effects/footprints.rsi/footprint-right-bare-diona.png b/Resources/Textures/Effects/footprints.rsi/footprint-right-bare-diona.png new file mode 100644 index 0000000000..21f3a11774 Binary files /dev/null and b/Resources/Textures/Effects/footprints.rsi/footprint-right-bare-diona.png differ diff --git a/Resources/Textures/Effects/footprints.rsi/footprint-right-bare-dwarf.png b/Resources/Textures/Effects/footprints.rsi/footprint-right-bare-dwarf.png new file mode 100644 index 0000000000..e493ddbdf6 Binary files /dev/null and b/Resources/Textures/Effects/footprints.rsi/footprint-right-bare-dwarf.png differ diff --git a/Resources/Textures/Effects/footprints.rsi/footprint-right-bare-human.png b/Resources/Textures/Effects/footprints.rsi/footprint-right-bare-human.png new file mode 100644 index 0000000000..4de70b5c1e Binary files /dev/null and b/Resources/Textures/Effects/footprints.rsi/footprint-right-bare-human.png differ diff --git a/Resources/Textures/Effects/footprints.rsi/footprint-right-bare-lizard.png b/Resources/Textures/Effects/footprints.rsi/footprint-right-bare-lizard.png new file mode 100644 index 0000000000..e53ba99227 Binary files /dev/null and b/Resources/Textures/Effects/footprints.rsi/footprint-right-bare-lizard.png differ diff --git a/Resources/Textures/Effects/footprints.rsi/footprint-right-bare-slime.png b/Resources/Textures/Effects/footprints.rsi/footprint-right-bare-slime.png new file mode 100644 index 0000000000..c10fe24f0b Binary files /dev/null and b/Resources/Textures/Effects/footprints.rsi/footprint-right-bare-slime.png differ diff --git a/Resources/Textures/Effects/footprints.rsi/footprint-right-bare-spider.png b/Resources/Textures/Effects/footprints.rsi/footprint-right-bare-spider.png new file mode 100644 index 0000000000..e9f3a88776 Binary files /dev/null and b/Resources/Textures/Effects/footprints.rsi/footprint-right-bare-spider.png differ diff --git a/Resources/Textures/Effects/footprints.rsi/footprint-shoes.png b/Resources/Textures/Effects/footprints.rsi/footprint-shoes.png new file mode 100644 index 0000000000..6cf329a9b6 Binary files /dev/null and b/Resources/Textures/Effects/footprints.rsi/footprint-shoes.png differ diff --git a/Resources/Textures/Effects/footprints.rsi/footprint-suit.png b/Resources/Textures/Effects/footprints.rsi/footprint-suit.png new file mode 100644 index 0000000000..6bc32d343c Binary files /dev/null and b/Resources/Textures/Effects/footprints.rsi/footprint-suit.png differ diff --git a/Resources/Textures/Effects/footprints.rsi/meta.json b/Resources/Textures/Effects/footprints.rsi/meta.json new file mode 100644 index 0000000000..0ce2e096ac --- /dev/null +++ b/Resources/Textures/Effects/footprints.rsi/meta.json @@ -0,0 +1,71 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "IMPERIAL SPACE", + "states": [ + { + "name": "footprint-left-bare-diona" + }, + { + "name": "footprint-left-bare-dwarf" + }, + { + "name": "footprint-left-bare-human" + }, + { + "name": "footprint-left-bare-lizard" + }, + { + "name": "footprint-left-bare-slime" + }, + { + "name": "footprint-left-bare-spider" + }, + { + "name": "footprint-right-bare-diona" + }, + { + "name": "footprint-right-bare-dwarf" + }, + { + "name": "footprint-right-bare-human" + }, + { + "name": "footprint-right-bare-lizard" + }, + { + "name": "footprint-right-bare-slime" + }, + { + "name": "footprint-right-bare-spider" + }, + { + "name": "footprint-shoes" + }, + { + "name": "footprint-suit" + }, + { + "name": "dragging-1" + }, + { + "name": "dragging-2" + }, + { + "name": "dragging-3" + }, + { + "name": "dragging-4" + }, + { + "name": "dragging-5" + }, + { + "name": "dragging-test" + } + ] +} diff --git a/Resources/Textures/Interface/Actions/flight.rsi/flight_off.png b/Resources/Textures/Interface/Actions/flight.rsi/flight_off.png new file mode 100644 index 0000000000..852dd300e9 Binary files /dev/null and b/Resources/Textures/Interface/Actions/flight.rsi/flight_off.png differ diff --git a/Resources/Textures/Interface/Actions/flight.rsi/flight_on.png b/Resources/Textures/Interface/Actions/flight.rsi/flight_on.png new file mode 100644 index 0000000000..c47b923a68 Binary files /dev/null and b/Resources/Textures/Interface/Actions/flight.rsi/flight_on.png differ diff --git a/Resources/Textures/Interface/Actions/flight.rsi/meta.json b/Resources/Textures/Interface/Actions/flight.rsi/meta.json new file mode 100644 index 0000000000..b4a013190d --- /dev/null +++ b/Resources/Textures/Interface/Actions/flight.rsi/meta.json @@ -0,0 +1,17 @@ +{ + "copyright" : "Made by dootythefrooty (273243513800622090)", + "license" : "CC-BY-SA-3.0", + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "flight_off" + }, + { + "name": "flight_on" + } + ] + } \ No newline at end of file diff --git a/Resources/Textures/Interface/Actions/psionics.rsi/healing_word.png b/Resources/Textures/Interface/Actions/psionics.rsi/healing_word.png new file mode 100644 index 0000000000..800e2a46e6 Binary files /dev/null and b/Resources/Textures/Interface/Actions/psionics.rsi/healing_word.png differ diff --git a/Resources/Textures/Interface/Actions/psionics.rsi/meta.json b/Resources/Textures/Interface/Actions/psionics.rsi/meta.json new file mode 100644 index 0000000000..735bc293d1 --- /dev/null +++ b/Resources/Textures/Interface/Actions/psionics.rsi/meta.json @@ -0,0 +1,35 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "healing_word, revivify, shadeskip by leonardo_dabepis (discord), telekinetic_pulse by .mocho (discord), pyrokinetic_flare, summon_remilia, summon_bat and summon_imp by ghost581 (discord)", + "size": { + "x": 64, + "y": 64 + }, + "states": [ + { + "name": "healing_word" + }, + { + "name": "revivify" + }, + { + "name": "shadeskip" + }, + { + "name": "telekinetic_pulse" + }, + { + "name": "pyrokinetic_flare" + }, + { + "name": "summon_imp" + }, + { + "name": "summon_remilia" + }, + { + "name": "summon_bat" + } + ] +} diff --git a/Resources/Textures/Interface/Actions/psionics.rsi/pyrokinetic_flare.png b/Resources/Textures/Interface/Actions/psionics.rsi/pyrokinetic_flare.png new file mode 100644 index 0000000000..db728ec603 Binary files /dev/null and b/Resources/Textures/Interface/Actions/psionics.rsi/pyrokinetic_flare.png differ diff --git a/Resources/Textures/Interface/Actions/psionics.rsi/revivify.png b/Resources/Textures/Interface/Actions/psionics.rsi/revivify.png new file mode 100644 index 0000000000..087f40b4c9 Binary files /dev/null and b/Resources/Textures/Interface/Actions/psionics.rsi/revivify.png differ diff --git a/Resources/Textures/Interface/Actions/psionics.rsi/shadeskip.png b/Resources/Textures/Interface/Actions/psionics.rsi/shadeskip.png new file mode 100644 index 0000000000..8114477372 Binary files /dev/null and b/Resources/Textures/Interface/Actions/psionics.rsi/shadeskip.png differ diff --git a/Resources/Textures/Interface/Actions/psionics.rsi/summon_bat.png b/Resources/Textures/Interface/Actions/psionics.rsi/summon_bat.png new file mode 100644 index 0000000000..60f571278b Binary files /dev/null and b/Resources/Textures/Interface/Actions/psionics.rsi/summon_bat.png differ diff --git a/Resources/Textures/Interface/Actions/psionics.rsi/summon_imp.png b/Resources/Textures/Interface/Actions/psionics.rsi/summon_imp.png new file mode 100644 index 0000000000..5de7c274fc Binary files /dev/null and b/Resources/Textures/Interface/Actions/psionics.rsi/summon_imp.png differ diff --git a/Resources/Textures/Interface/Actions/psionics.rsi/summon_remilia.png b/Resources/Textures/Interface/Actions/psionics.rsi/summon_remilia.png new file mode 100644 index 0000000000..fcfe2a3726 Binary files /dev/null and b/Resources/Textures/Interface/Actions/psionics.rsi/summon_remilia.png differ diff --git a/Resources/Textures/Interface/Actions/psionics.rsi/telekinetic_pulse.png b/Resources/Textures/Interface/Actions/psionics.rsi/telekinetic_pulse.png new file mode 100644 index 0000000000..7c690a6adb Binary files /dev/null and b/Resources/Textures/Interface/Actions/psionics.rsi/telekinetic_pulse.png differ diff --git a/Resources/Textures/Interface/Actions/shadowkin_icons.rsi/darkswap.png b/Resources/Textures/Interface/Actions/shadowkin_icons.rsi/darkswap.png new file mode 100644 index 0000000000..3c2815e8c2 Binary files /dev/null and b/Resources/Textures/Interface/Actions/shadowkin_icons.rsi/darkswap.png differ diff --git a/Resources/Textures/Interface/Actions/shadowkin_icons.rsi/meta.json b/Resources/Textures/Interface/Actions/shadowkin_icons.rsi/meta.json new file mode 100644 index 0000000000..62b03fecaf --- /dev/null +++ b/Resources/Textures/Interface/Actions/shadowkin_icons.rsi/meta.json @@ -0,0 +1,23 @@ +{ + "version": 1, + "size": { + "x": 64, + "y": 64 + }, + "license": "CC-BY-SA-3.0", + "copyright": "DEATHB4DEFEAT#4404 (801294818839756811)", + "states": [ + { + "name": "darkswap", + "directions": 1 + }, + { + "name": "rest", + "directions": 1 + }, + { + "name": "shadeskip", + "directions": 1 + } + ] +} diff --git a/Resources/Textures/Interface/Actions/shadowkin_icons.rsi/rest.png b/Resources/Textures/Interface/Actions/shadowkin_icons.rsi/rest.png new file mode 100644 index 0000000000..188b4ec591 Binary files /dev/null and b/Resources/Textures/Interface/Actions/shadowkin_icons.rsi/rest.png differ diff --git a/Resources/Textures/Interface/Actions/shadowkin_icons.rsi/shadeskip.png b/Resources/Textures/Interface/Actions/shadowkin_icons.rsi/shadeskip.png new file mode 100644 index 0000000000..b0f1bd8dc8 Binary files /dev/null and b/Resources/Textures/Interface/Actions/shadowkin_icons.rsi/shadeskip.png differ diff --git a/Resources/Textures/Interface/Alerts/deflecting.rsi/deflecting0.png b/Resources/Textures/Interface/Alerts/deflecting.rsi/deflecting0.png new file mode 100644 index 0000000000..37404e77f7 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/deflecting.rsi/deflecting0.png differ diff --git a/Resources/Textures/Interface/Alerts/deflecting.rsi/meta.json b/Resources/Textures/Interface/Alerts/deflecting.rsi/meta.json new file mode 100644 index 0000000000..f5d94c891a --- /dev/null +++ b/Resources/Textures/Interface/Alerts/deflecting.rsi/meta.json @@ -0,0 +1,14 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Deflecting icon by Ubaser", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "deflecting0" + } + ] +} diff --git a/Resources/Textures/Interface/Alerts/mood.rsi/meta.json b/Resources/Textures/Interface/Alerts/mood.rsi/meta.json new file mode 100644 index 0000000000..0f6726a48d --- /dev/null +++ b/Resources/Textures/Interface/Alerts/mood.rsi/meta.json @@ -0,0 +1,60 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from NSV13 at b6b1e2bf2cc60455851317d8e82cca8716d9dac1", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "mood1" + }, + { + "name": "mood2" + }, + { + "name": "mood3" + }, + { + "name": "mood4" + }, + { + "name": "mood5" + }, + { + "name": "mood6" + }, + { + "name": "mood7" + }, + { + "name": "mood8" + }, + { + "name": "mood9" + }, + { + "name": "mood_happiness_bad" + }, + { + "name": "mood_happiness_good" + }, + { + "name": "mood_insane", + "delays": [ + [ + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07 + ] + ] + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Interface/Alerts/mood.rsi/mood1.png b/Resources/Textures/Interface/Alerts/mood.rsi/mood1.png new file mode 100644 index 0000000000..ae1e1386db Binary files /dev/null and b/Resources/Textures/Interface/Alerts/mood.rsi/mood1.png differ diff --git a/Resources/Textures/Interface/Alerts/mood.rsi/mood2.png b/Resources/Textures/Interface/Alerts/mood.rsi/mood2.png new file mode 100644 index 0000000000..41be928f02 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/mood.rsi/mood2.png differ diff --git a/Resources/Textures/Interface/Alerts/mood.rsi/mood3.png b/Resources/Textures/Interface/Alerts/mood.rsi/mood3.png new file mode 100644 index 0000000000..179991e198 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/mood.rsi/mood3.png differ diff --git a/Resources/Textures/Interface/Alerts/mood.rsi/mood4.png b/Resources/Textures/Interface/Alerts/mood.rsi/mood4.png new file mode 100644 index 0000000000..4ea7d70117 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/mood.rsi/mood4.png differ diff --git a/Resources/Textures/Interface/Alerts/mood.rsi/mood5.png b/Resources/Textures/Interface/Alerts/mood.rsi/mood5.png new file mode 100644 index 0000000000..c4c3370f62 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/mood.rsi/mood5.png differ diff --git a/Resources/Textures/Interface/Alerts/mood.rsi/mood6.png b/Resources/Textures/Interface/Alerts/mood.rsi/mood6.png new file mode 100644 index 0000000000..388483e143 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/mood.rsi/mood6.png differ diff --git a/Resources/Textures/Interface/Alerts/mood.rsi/mood7.png b/Resources/Textures/Interface/Alerts/mood.rsi/mood7.png new file mode 100644 index 0000000000..b4f944d045 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/mood.rsi/mood7.png differ diff --git a/Resources/Textures/Interface/Alerts/mood.rsi/mood8.png b/Resources/Textures/Interface/Alerts/mood.rsi/mood8.png new file mode 100644 index 0000000000..f12f71b7ff Binary files /dev/null and b/Resources/Textures/Interface/Alerts/mood.rsi/mood8.png differ diff --git a/Resources/Textures/Interface/Alerts/mood.rsi/mood9.png b/Resources/Textures/Interface/Alerts/mood.rsi/mood9.png new file mode 100644 index 0000000000..e65c650149 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/mood.rsi/mood9.png differ diff --git a/Resources/Textures/Interface/Alerts/mood.rsi/mood_happiness_bad.png b/Resources/Textures/Interface/Alerts/mood.rsi/mood_happiness_bad.png new file mode 100644 index 0000000000..4ed8f4d68f Binary files /dev/null and b/Resources/Textures/Interface/Alerts/mood.rsi/mood_happiness_bad.png differ diff --git a/Resources/Textures/Interface/Alerts/mood.rsi/mood_happiness_good.png b/Resources/Textures/Interface/Alerts/mood.rsi/mood_happiness_good.png new file mode 100644 index 0000000000..eb9943a301 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/mood.rsi/mood_happiness_good.png differ diff --git a/Resources/Textures/Interface/Alerts/mood.rsi/mood_insane.png b/Resources/Textures/Interface/Alerts/mood.rsi/mood_insane.png new file mode 100644 index 0000000000..b2407bbdad Binary files /dev/null and b/Resources/Textures/Interface/Alerts/mood.rsi/mood_insane.png differ diff --git a/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/meta.json b/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/meta.json new file mode 100644 index 0000000000..f642565f9a --- /dev/null +++ b/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/meta.json @@ -0,0 +1,43 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "DEATHB4DEFEAT#4404 (801294818839756811)", + "states": [ + { + "name": "power0", + "delays": [[0.8, 0.8, 0.8, 0.8, 0.8, 0.8]] + }, + { + "name": "power1", + "delays": [[0.7, 0.7, 0.7, 0.7, 0.7, 0.7]] + }, + { + "name": "power2", + "delays": [[0.6, 0.6, 0.6, 0.6, 0.6, 0.6]] + }, + { + "name": "power3", + "delays": [[0.5, 0.5, 0.5, 0.5, 0.5, 0.5]] + }, + { + "name": "power4", + "delays": [[0.4, 0.4, 0.4, 0.4, 0.4, 0.4]] + }, + { + "name": "power5", + "delays": [[0.3, 0.3, 0.3, 0.3, 0.3, 0.3]] + }, + { + "name": "power6", + "delays": [[0.2, 0.2, 0.2, 0.2, 0.2, 0.2]] + }, + { + "name": "power7", + "delays": [[0.1, 0.1, 0.1, 0.1, 0.1, 0.1]] + } + ] +} diff --git a/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/power0.png b/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/power0.png new file mode 100644 index 0000000000..ab370f753e Binary files /dev/null and b/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/power0.png differ diff --git a/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/power1.png b/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/power1.png new file mode 100644 index 0000000000..d72965eeec Binary files /dev/null and b/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/power1.png differ diff --git a/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/power2.png b/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/power2.png new file mode 100644 index 0000000000..1b2c51575c Binary files /dev/null and b/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/power2.png differ diff --git a/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/power3.png b/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/power3.png new file mode 100644 index 0000000000..0f93f925ac Binary files /dev/null and b/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/power3.png differ diff --git a/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/power4.png b/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/power4.png new file mode 100644 index 0000000000..3f3035da0d Binary files /dev/null and b/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/power4.png differ diff --git a/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/power5.png b/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/power5.png new file mode 100644 index 0000000000..af3f716861 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/power5.png differ diff --git a/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/power6.png b/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/power6.png new file mode 100644 index 0000000000..9f7957c44f Binary files /dev/null and b/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/power6.png differ diff --git a/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/power7.png b/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/power7.png new file mode 100644 index 0000000000..ac9f17f55b Binary files /dev/null and b/Resources/Textures/Interface/Alerts/shadowkin_power.rsi/power7.png differ diff --git a/Resources/Textures/Interface/Ashen/item_status_left.png b/Resources/Textures/Interface/Ashen/item_status_left.png new file mode 100644 index 0000000000..fb2bf2b9b4 Binary files /dev/null and b/Resources/Textures/Interface/Ashen/item_status_left.png differ diff --git a/Resources/Textures/Interface/Ashen/item_status_left_highlight.png b/Resources/Textures/Interface/Ashen/item_status_left_highlight.png new file mode 100644 index 0000000000..91cd15dd4c Binary files /dev/null and b/Resources/Textures/Interface/Ashen/item_status_left_highlight.png differ diff --git a/Resources/Textures/Interface/Ashen/item_status_right.png b/Resources/Textures/Interface/Ashen/item_status_right.png new file mode 100644 index 0000000000..53f4f362d0 Binary files /dev/null and b/Resources/Textures/Interface/Ashen/item_status_right.png differ diff --git a/Resources/Textures/Interface/Ashen/item_status_right_highlight.png b/Resources/Textures/Interface/Ashen/item_status_right_highlight.png new file mode 100644 index 0000000000..ad16bab6d1 Binary files /dev/null and b/Resources/Textures/Interface/Ashen/item_status_right_highlight.png differ diff --git a/Resources/Textures/Interface/Ashen/target_doll.png b/Resources/Textures/Interface/Ashen/target_doll.png new file mode 100644 index 0000000000..e8eb2f5271 Binary files /dev/null and b/Resources/Textures/Interface/Ashen/target_doll.png differ diff --git a/Resources/Textures/Interface/Ashen/template_small.png b/Resources/Textures/Interface/Ashen/template_small.png new file mode 100644 index 0000000000..f3a4379f76 Binary files /dev/null and b/Resources/Textures/Interface/Ashen/template_small.png differ diff --git a/Resources/Textures/Interface/AtmosMonitoring/status_bg.png b/Resources/Textures/Interface/AtmosMonitoring/status_bg.png new file mode 100644 index 0000000000..165a9b9d9f Binary files /dev/null and b/Resources/Textures/Interface/AtmosMonitoring/status_bg.png differ diff --git a/Resources/Textures/Interface/Clockwork/item_status_left.png b/Resources/Textures/Interface/Clockwork/item_status_left.png new file mode 100644 index 0000000000..1ce950362d Binary files /dev/null and b/Resources/Textures/Interface/Clockwork/item_status_left.png differ diff --git a/Resources/Textures/Interface/Clockwork/item_status_left_highlight.png b/Resources/Textures/Interface/Clockwork/item_status_left_highlight.png new file mode 100644 index 0000000000..f715e06276 Binary files /dev/null and b/Resources/Textures/Interface/Clockwork/item_status_left_highlight.png differ diff --git a/Resources/Textures/Interface/Clockwork/item_status_right.png b/Resources/Textures/Interface/Clockwork/item_status_right.png new file mode 100644 index 0000000000..5ea5ffcffa Binary files /dev/null and b/Resources/Textures/Interface/Clockwork/item_status_right.png differ diff --git a/Resources/Textures/Interface/Clockwork/item_status_right_highlight.png b/Resources/Textures/Interface/Clockwork/item_status_right_highlight.png new file mode 100644 index 0000000000..315d595c92 Binary files /dev/null and b/Resources/Textures/Interface/Clockwork/item_status_right_highlight.png differ diff --git a/Resources/Textures/Interface/Clockwork/target_doll.png b/Resources/Textures/Interface/Clockwork/target_doll.png new file mode 100644 index 0000000000..8426d42299 Binary files /dev/null and b/Resources/Textures/Interface/Clockwork/target_doll.png differ diff --git a/Resources/Textures/Interface/Default/item_status_left.png b/Resources/Textures/Interface/Default/item_status_left.png new file mode 100644 index 0000000000..6c980f226e Binary files /dev/null and b/Resources/Textures/Interface/Default/item_status_left.png differ diff --git a/Resources/Textures/Interface/Default/item_status_left_highlight.png b/Resources/Textures/Interface/Default/item_status_left_highlight.png new file mode 100644 index 0000000000..87dea5cf10 Binary files /dev/null and b/Resources/Textures/Interface/Default/item_status_left_highlight.png differ diff --git a/Resources/Textures/Interface/Default/item_status_right.png b/Resources/Textures/Interface/Default/item_status_right.png new file mode 100644 index 0000000000..82ad44b48c Binary files /dev/null and b/Resources/Textures/Interface/Default/item_status_right.png differ diff --git a/Resources/Textures/Interface/Default/item_status_right_highlight.png b/Resources/Textures/Interface/Default/item_status_right_highlight.png new file mode 100644 index 0000000000..0c1c344848 Binary files /dev/null and b/Resources/Textures/Interface/Default/item_status_right_highlight.png differ diff --git a/Resources/Textures/Interface/Default/target_doll.png b/Resources/Textures/Interface/Default/target_doll.png new file mode 100644 index 0000000000..e8eb2f5271 Binary files /dev/null and b/Resources/Textures/Interface/Default/target_doll.png differ diff --git a/Resources/Textures/Interface/Emotes/attributions.yml b/Resources/Textures/Interface/Emotes/attributions.yml new file mode 100644 index 0000000000..125651e419 --- /dev/null +++ b/Resources/Textures/Interface/Emotes/attributions.yml @@ -0,0 +1,117 @@ +# Attempted to keep the files in alphabetical order so its easier to audit. +# Finding individual authors is an unfeasible task. If you can reference the author please do so. + +- files: ["beep.png"] + license: "CC-BY-SA-3.0" + copyright: "Modified from borg_chest.png and borg_head.png by TyAshley (AllenTheGreat)" + source: "https://github.com/TyAshley" + +- files: ["buzz.png"] + license: "CC-BY-SA-3.0" + copyright: "Modified from existing bee texture (0.png) by TyAshley (AllenTheGreat)" + source: "https://github.com/TyAshley" + +- files: ["buzztwo.png"] + license: "CC-BY-SA-3.0" + copyright: "Modified from existing bee texture (0.png) by TyAshley (AllenTheGreat)" + source: "https://github.com/TyAshley" + +- files: ["chime.png"] + license: "CC-BY-SA-3.0" + copyright: "Modified from existing desk bell texture (normal.png) by TyAshley (AllenTheGreat)" + source: "https://github.com/TyAshley" + +- files: ["chirp.png"] + license: "CC-BY-SA-3.0" + copyright: "Modified from existing nymph texture (icon.png) by TyAshley (AllenTheGreat)" + source: "https://github.com/TyAshley" + +- files: ["chitter.png"] + license: "CC-BY-SA-3.0" + copyright: "Modified from mothroach.png by TyAshley (AllenTheGreat)" + source: "https://github.com/TyAshley" + +- files: ["clap.png"] + license: "CC-BY-SA-3.0" + copyright: "Created by TyAshley (AllenTheGreat) at commit 7f6c7cd82943dbc9a1fe8a79d6a924ac600b3fdb" + source: "https://github.com/TyAshley" + +- files: ["click.png"] + license: "CC-BY-SA-3.0" + copyright: "Modified from existing crab.png by TyAshley (AllenTheGreat)" + source: "https://github.com/TyAshley" + +- files: ["cough.png"] + license: "CC-BY-SA-3.0" + copyright: "Created by TyAshley (AllenTheGreat) at commit 7f6c7cd82943dbc9a1fe8a79d6a924ac600b3fdb" + source: "https://github.com/TyAshley" + +- files: ["cry.png"] + license: "CC-BY-SA-3.0" + copyright: "Modified from scream.png by TyAshley (AllenTheGreat)" + source: "https://github.com/TyAshley" + +- files: ["deathgasp.png"] + license: "CC-BY-SA-3.0" + copyright: "Modified from scream.png by TyAshley (AllenTheGreat)" + source: "https://github.com/TyAshley" + +- files: ["honk.png"] + license: "CC-BY-SA-3.0" + copyright: "Modified from existing bikehorn texture (icon.png) by TyAshley (AllenTheGreat)" + source: "https://github.com/TyAshley" + +- files: ["laugh.png"] + license: "CC-BY-SA-3.0" + copyright: "Modified from scream.png by TyAshley (AllenTheGreat)" + source: "https://github.com/TyAshley" + +- files: ["ping.png"] + license: "CC-BY-SA-3.0" + copyright: "Created by TyAshley (AllenTheGreat) at commit 7f6c7cd82943dbc9a1fe8a79d6a924ac600b3fdb" + source: "https://github.com/TyAshley" + +- files: ["salute.png"] + license: "CC-BY-SA-3.0" + copyright: "Modified from scream.png by TyAshley (AllenTheGreat)" + source: "https://github.com/TyAshley" + +- files: ["sigh.png"] + license: "CC-BY-SA-3.0" + copyright: "Created by TyAshley (AllenTheGreat) at commit 7f6c7cd82943dbc9a1fe8a79d6a924ac600b3fdb" + source: "https://github.com/TyAshley" + +- files: ["snap.png"] + license: "CC-BY-SA-3.0" + copyright: "Created by TyAshley (AllenTheGreat) at commit 7f6c7cd82943dbc9a1fe8a79d6a924ac600b3fdb" + source: "https://github.com/TyAshley" + +- files: ["squeak.png"] + license: "CC-BY-SA-3.0" + copyright: "Modified from mouse-0.png by TyAshley (AllenTheGreat)" + source: "https://github.com/TyAshley" + +- files: ["squish.png"] + license: "CC-BY-SA-3.0" + copyright: "Modified from blue_adult_slime.png by TyAshley (AllenTheGreat)" + source: "https://github.com/TyAshley" + +- files: ["vocal.png"] + license: "CC-BY-SA-3.0" + copyright: "Created by TyAshley (AllenTheGreat) at commit 7f6c7cd82943dbc9a1fe8a79d6a924ac600b3fdb" + source: "https://github.com/TyAshley" + +- files: ["weh.png"] + license: "CC-BY-SA-3.0" + copyright: "Modified from plushie_lizard.png by TyAshley (AllenTheGreat)" + source: "https://github.com/TyAshley" + +- files: ["whistle.png"] + license: "CC-BY-SA-3.0" + copyright: "Modified from scream.png by TyAshley (AllenTheGreat)" + source: "https://github.com/TyAshley" + +- files: ["yawn.png"] + license: "CC-BY-SA-3.0" + copyright: "Created by TyAshley (AllenTheGreat) at commit 7f6c7cd82943dbc9a1fe8a79d6a924ac600b3fdb" + source: "https://github.com/TyAshley" diff --git a/Resources/Textures/Interface/Emotes/beep.png b/Resources/Textures/Interface/Emotes/beep.png new file mode 100644 index 0000000000..f59b0925ab Binary files /dev/null and b/Resources/Textures/Interface/Emotes/beep.png differ diff --git a/Resources/Textures/Interface/Emotes/buzz.png b/Resources/Textures/Interface/Emotes/buzz.png new file mode 100644 index 0000000000..da7ac363a4 Binary files /dev/null and b/Resources/Textures/Interface/Emotes/buzz.png differ diff --git a/Resources/Textures/Interface/Emotes/buzztwo.png b/Resources/Textures/Interface/Emotes/buzztwo.png new file mode 100644 index 0000000000..460a8868a5 Binary files /dev/null and b/Resources/Textures/Interface/Emotes/buzztwo.png differ diff --git a/Resources/Textures/Interface/Emotes/chime.png b/Resources/Textures/Interface/Emotes/chime.png new file mode 100644 index 0000000000..ca595be5d3 Binary files /dev/null and b/Resources/Textures/Interface/Emotes/chime.png differ diff --git a/Resources/Textures/Interface/Emotes/chirp.png b/Resources/Textures/Interface/Emotes/chirp.png new file mode 100644 index 0000000000..57e8b67143 Binary files /dev/null and b/Resources/Textures/Interface/Emotes/chirp.png differ diff --git a/Resources/Textures/Interface/Emotes/chitter.png b/Resources/Textures/Interface/Emotes/chitter.png new file mode 100644 index 0000000000..f6e1101223 Binary files /dev/null and b/Resources/Textures/Interface/Emotes/chitter.png differ diff --git a/Resources/Textures/Interface/Emotes/clap.png b/Resources/Textures/Interface/Emotes/clap.png new file mode 100644 index 0000000000..a0ef9e1316 Binary files /dev/null and b/Resources/Textures/Interface/Emotes/clap.png differ diff --git a/Resources/Textures/Interface/Emotes/click.png b/Resources/Textures/Interface/Emotes/click.png new file mode 100644 index 0000000000..539aea7b92 Binary files /dev/null and b/Resources/Textures/Interface/Emotes/click.png differ diff --git a/Resources/Textures/Interface/Emotes/cough.png b/Resources/Textures/Interface/Emotes/cough.png new file mode 100644 index 0000000000..cb1c2832ac Binary files /dev/null and b/Resources/Textures/Interface/Emotes/cough.png differ diff --git a/Resources/Textures/Interface/Emotes/cry.png b/Resources/Textures/Interface/Emotes/cry.png new file mode 100644 index 0000000000..2793a11b9d Binary files /dev/null and b/Resources/Textures/Interface/Emotes/cry.png differ diff --git a/Resources/Textures/Interface/Emotes/deathgasp.png b/Resources/Textures/Interface/Emotes/deathgasp.png new file mode 100644 index 0000000000..e27d0bb573 Binary files /dev/null and b/Resources/Textures/Interface/Emotes/deathgasp.png differ diff --git a/Resources/Textures/Interface/Emotes/honk.png b/Resources/Textures/Interface/Emotes/honk.png new file mode 100644 index 0000000000..19441e4a0e Binary files /dev/null and b/Resources/Textures/Interface/Emotes/honk.png differ diff --git a/Resources/Textures/Interface/Emotes/laugh.png b/Resources/Textures/Interface/Emotes/laugh.png new file mode 100644 index 0000000000..a688fdb443 Binary files /dev/null and b/Resources/Textures/Interface/Emotes/laugh.png differ diff --git a/Resources/Textures/Interface/Emotes/ping.png b/Resources/Textures/Interface/Emotes/ping.png new file mode 100644 index 0000000000..7408eb1f28 Binary files /dev/null and b/Resources/Textures/Interface/Emotes/ping.png differ diff --git a/Resources/Textures/Interface/Emotes/salute.png b/Resources/Textures/Interface/Emotes/salute.png new file mode 100644 index 0000000000..5727d8fd85 Binary files /dev/null and b/Resources/Textures/Interface/Emotes/salute.png differ diff --git a/Resources/Textures/Interface/Emotes/sigh.png b/Resources/Textures/Interface/Emotes/sigh.png new file mode 100644 index 0000000000..ff49a56360 Binary files /dev/null and b/Resources/Textures/Interface/Emotes/sigh.png differ diff --git a/Resources/Textures/Interface/Emotes/snap.png b/Resources/Textures/Interface/Emotes/snap.png new file mode 100644 index 0000000000..ae6d81c19d Binary files /dev/null and b/Resources/Textures/Interface/Emotes/snap.png differ diff --git a/Resources/Textures/Interface/Emotes/squeak.png b/Resources/Textures/Interface/Emotes/squeak.png new file mode 100644 index 0000000000..e32a89f598 Binary files /dev/null and b/Resources/Textures/Interface/Emotes/squeak.png differ diff --git a/Resources/Textures/Interface/Emotes/squish.png b/Resources/Textures/Interface/Emotes/squish.png new file mode 100644 index 0000000000..efa1ce6c5e Binary files /dev/null and b/Resources/Textures/Interface/Emotes/squish.png differ diff --git a/Resources/Textures/Interface/Emotes/vocal.png b/Resources/Textures/Interface/Emotes/vocal.png new file mode 100644 index 0000000000..9b129ec466 Binary files /dev/null and b/Resources/Textures/Interface/Emotes/vocal.png differ diff --git a/Resources/Textures/Interface/Emotes/weh.png b/Resources/Textures/Interface/Emotes/weh.png new file mode 100644 index 0000000000..fea5ad3b73 Binary files /dev/null and b/Resources/Textures/Interface/Emotes/weh.png differ diff --git a/Resources/Textures/Interface/Emotes/whistle.png b/Resources/Textures/Interface/Emotes/whistle.png new file mode 100644 index 0000000000..029a52af23 Binary files /dev/null and b/Resources/Textures/Interface/Emotes/whistle.png differ diff --git a/Resources/Textures/Interface/Emotes/yawn.png b/Resources/Textures/Interface/Emotes/yawn.png new file mode 100644 index 0000000000..8130fc9ab4 Binary files /dev/null and b/Resources/Textures/Interface/Emotes/yawn.png differ diff --git a/Resources/Textures/Interface/Minimalist/SlotBackground.png b/Resources/Textures/Interface/Minimalist/SlotBackground.png index eb0ee037fd..85c944357e 100644 Binary files a/Resources/Textures/Interface/Minimalist/SlotBackground.png and b/Resources/Textures/Interface/Minimalist/SlotBackground.png differ diff --git a/Resources/Textures/Interface/Minimalist/item_status_left.png b/Resources/Textures/Interface/Minimalist/item_status_left.png new file mode 100644 index 0000000000..d70eca2fe9 Binary files /dev/null and b/Resources/Textures/Interface/Minimalist/item_status_left.png differ diff --git a/Resources/Textures/Interface/Minimalist/item_status_left_highlight.png b/Resources/Textures/Interface/Minimalist/item_status_left_highlight.png new file mode 100644 index 0000000000..b69872cd89 Binary files /dev/null and b/Resources/Textures/Interface/Minimalist/item_status_left_highlight.png differ diff --git a/Resources/Textures/Interface/Minimalist/item_status_right.png b/Resources/Textures/Interface/Minimalist/item_status_right.png new file mode 100644 index 0000000000..89171b9b47 Binary files /dev/null and b/Resources/Textures/Interface/Minimalist/item_status_right.png differ diff --git a/Resources/Textures/Interface/Minimalist/item_status_right_highlight.png b/Resources/Textures/Interface/Minimalist/item_status_right_highlight.png new file mode 100644 index 0000000000..d1474cee12 Binary files /dev/null and b/Resources/Textures/Interface/Minimalist/item_status_right_highlight.png differ diff --git a/Resources/Textures/Interface/Minimalist/target_doll.png b/Resources/Textures/Interface/Minimalist/target_doll.png new file mode 100644 index 0000000000..e8eb2f5271 Binary files /dev/null and b/Resources/Textures/Interface/Minimalist/target_doll.png differ diff --git a/Resources/Textures/Interface/Minimalist/template_small.png b/Resources/Textures/Interface/Minimalist/template_small.png new file mode 100644 index 0000000000..85c944357e Binary files /dev/null and b/Resources/Textures/Interface/Minimalist/template_small.png differ diff --git a/Resources/Textures/Interface/Misc/health_analyzer_out_of_range.png b/Resources/Textures/Interface/Misc/health_analyzer_out_of_range.png new file mode 100644 index 0000000000..8a76b0e83f Binary files /dev/null and b/Resources/Textures/Interface/Misc/health_analyzer_out_of_range.png differ diff --git a/Resources/Textures/Interface/Misc/job_icons.rsi/Librarian.png b/Resources/Textures/Interface/Misc/job_icons.rsi/Librarian.png index b10b1ed71e..114283c053 100644 Binary files a/Resources/Textures/Interface/Misc/job_icons.rsi/Librarian.png and b/Resources/Textures/Interface/Misc/job_icons.rsi/Librarian.png differ diff --git a/Resources/Textures/Interface/Nano/help.png b/Resources/Textures/Interface/Nano/help.png new file mode 100644 index 0000000000..17f5283334 Binary files /dev/null and b/Resources/Textures/Interface/Nano/help.png differ diff --git a/Resources/Textures/Interface/Nano/item_status_left.svg b/Resources/Textures/Interface/Nano/item_status_left.svg deleted file mode 100644 index c97f8de016..0000000000 --- a/Resources/Textures/Interface/Nano/item_status_left.svg +++ /dev/null @@ -1,100 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - - diff --git a/Resources/Textures/Interface/Nano/item_status_left.svg.96dpi.png b/Resources/Textures/Interface/Nano/item_status_left.svg.96dpi.png deleted file mode 100644 index e058fa7374..0000000000 Binary files a/Resources/Textures/Interface/Nano/item_status_left.svg.96dpi.png and /dev/null differ diff --git a/Resources/Textures/Interface/Nano/item_status_middle.svg b/Resources/Textures/Interface/Nano/item_status_middle.svg deleted file mode 100644 index a913981db1..0000000000 --- a/Resources/Textures/Interface/Nano/item_status_middle.svg +++ /dev/null @@ -1,100 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - - diff --git a/Resources/Textures/Interface/Nano/item_status_middle.svg.96dpi.png b/Resources/Textures/Interface/Nano/item_status_middle.svg.96dpi.png deleted file mode 100644 index ce41106b20..0000000000 Binary files a/Resources/Textures/Interface/Nano/item_status_middle.svg.96dpi.png and /dev/null differ diff --git a/Resources/Textures/Interface/Nano/item_status_right.svg b/Resources/Textures/Interface/Nano/item_status_right.svg deleted file mode 100644 index d898bb2ce0..0000000000 --- a/Resources/Textures/Interface/Nano/item_status_right.svg +++ /dev/null @@ -1,100 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - - diff --git a/Resources/Textures/Interface/Nano/item_status_right.svg.96dpi.png b/Resources/Textures/Interface/Nano/item_status_right.svg.96dpi.png deleted file mode 100644 index fb7c4a9e6e..0000000000 Binary files a/Resources/Textures/Interface/Nano/item_status_right.svg.96dpi.png and /dev/null differ diff --git a/Resources/Textures/Interface/Plasmafire/item_status_left.png b/Resources/Textures/Interface/Plasmafire/item_status_left.png new file mode 100644 index 0000000000..d5d25c9809 Binary files /dev/null and b/Resources/Textures/Interface/Plasmafire/item_status_left.png differ diff --git a/Resources/Textures/Interface/Plasmafire/item_status_left_highlight.png b/Resources/Textures/Interface/Plasmafire/item_status_left_highlight.png new file mode 100644 index 0000000000..afe37513fd Binary files /dev/null and b/Resources/Textures/Interface/Plasmafire/item_status_left_highlight.png differ diff --git a/Resources/Textures/Interface/Plasmafire/item_status_right.png b/Resources/Textures/Interface/Plasmafire/item_status_right.png new file mode 100644 index 0000000000..ca97f81c8f Binary files /dev/null and b/Resources/Textures/Interface/Plasmafire/item_status_right.png differ diff --git a/Resources/Textures/Interface/Plasmafire/item_status_right_highlight.png b/Resources/Textures/Interface/Plasmafire/item_status_right_highlight.png new file mode 100644 index 0000000000..b95822b737 Binary files /dev/null and b/Resources/Textures/Interface/Plasmafire/item_status_right_highlight.png differ diff --git a/Resources/Textures/Interface/Plasmafire/target_doll.png b/Resources/Textures/Interface/Plasmafire/target_doll.png new file mode 100644 index 0000000000..57262d6702 Binary files /dev/null and b/Resources/Textures/Interface/Plasmafire/target_doll.png differ diff --git a/Resources/Textures/Interface/Retro/item_status_left.png b/Resources/Textures/Interface/Retro/item_status_left.png new file mode 100644 index 0000000000..21b107b84d Binary files /dev/null and b/Resources/Textures/Interface/Retro/item_status_left.png differ diff --git a/Resources/Textures/Interface/Retro/item_status_left_highlight.png b/Resources/Textures/Interface/Retro/item_status_left_highlight.png new file mode 100644 index 0000000000..fdd5a4fe7d Binary files /dev/null and b/Resources/Textures/Interface/Retro/item_status_left_highlight.png differ diff --git a/Resources/Textures/Interface/Retro/item_status_right.png b/Resources/Textures/Interface/Retro/item_status_right.png new file mode 100644 index 0000000000..5e7d54618d Binary files /dev/null and b/Resources/Textures/Interface/Retro/item_status_right.png differ diff --git a/Resources/Textures/Interface/Retro/item_status_right_highlight.png b/Resources/Textures/Interface/Retro/item_status_right_highlight.png new file mode 100644 index 0000000000..c6e12c41e6 Binary files /dev/null and b/Resources/Textures/Interface/Retro/item_status_right_highlight.png differ diff --git a/Resources/Textures/Interface/Retro/target_doll.png b/Resources/Textures/Interface/Retro/target_doll.png new file mode 100644 index 0000000000..a147539c3a Binary files /dev/null and b/Resources/Textures/Interface/Retro/target_doll.png differ diff --git a/Resources/Textures/Interface/Retro/template_small.png b/Resources/Textures/Interface/Retro/template_small.png new file mode 100644 index 0000000000..7244b37f5c Binary files /dev/null and b/Resources/Textures/Interface/Retro/template_small.png differ diff --git a/Resources/Textures/Interface/Slimecore/item_status_left.png b/Resources/Textures/Interface/Slimecore/item_status_left.png new file mode 100644 index 0000000000..a7d940f401 Binary files /dev/null and b/Resources/Textures/Interface/Slimecore/item_status_left.png differ diff --git a/Resources/Textures/Interface/Slimecore/item_status_left_highlight.png b/Resources/Textures/Interface/Slimecore/item_status_left_highlight.png new file mode 100644 index 0000000000..322355b135 Binary files /dev/null and b/Resources/Textures/Interface/Slimecore/item_status_left_highlight.png differ diff --git a/Resources/Textures/Interface/Slimecore/item_status_right.png b/Resources/Textures/Interface/Slimecore/item_status_right.png new file mode 100644 index 0000000000..77b53340a6 Binary files /dev/null and b/Resources/Textures/Interface/Slimecore/item_status_right.png differ diff --git a/Resources/Textures/Interface/Slimecore/item_status_right_highlight.png b/Resources/Textures/Interface/Slimecore/item_status_right_highlight.png new file mode 100644 index 0000000000..1e1a631db4 Binary files /dev/null and b/Resources/Textures/Interface/Slimecore/item_status_right_highlight.png differ diff --git a/Resources/Textures/Interface/Slimecore/target_doll.png b/Resources/Textures/Interface/Slimecore/target_doll.png new file mode 100644 index 0000000000..0706eb661b Binary files /dev/null and b/Resources/Textures/Interface/Slimecore/target_doll.png differ diff --git a/Resources/Textures/Interface/Targeting/Doll/eyes.png b/Resources/Textures/Interface/Targeting/Doll/eyes.png new file mode 100644 index 0000000000..8cf9fa9e19 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Doll/eyes.png differ diff --git a/Resources/Textures/Interface/Targeting/Doll/eyes_hover.png b/Resources/Textures/Interface/Targeting/Doll/eyes_hover.png new file mode 100644 index 0000000000..72a30d73b8 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Doll/eyes_hover.png differ diff --git a/Resources/Textures/Interface/Targeting/Doll/groin.png b/Resources/Textures/Interface/Targeting/Doll/groin.png new file mode 100644 index 0000000000..2c1a3debda Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Doll/groin.png differ diff --git a/Resources/Textures/Interface/Targeting/Doll/groin_hover.png b/Resources/Textures/Interface/Targeting/Doll/groin_hover.png new file mode 100644 index 0000000000..313b6a1124 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Doll/groin_hover.png differ diff --git a/Resources/Textures/Interface/Targeting/Doll/head.png b/Resources/Textures/Interface/Targeting/Doll/head.png new file mode 100644 index 0000000000..9c645d2353 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Doll/head.png differ diff --git a/Resources/Textures/Interface/Targeting/Doll/head_hover.png b/Resources/Textures/Interface/Targeting/Doll/head_hover.png new file mode 100644 index 0000000000..3c03f2aebe Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Doll/head_hover.png differ diff --git a/Resources/Textures/Interface/Targeting/Doll/leftarm.png b/Resources/Textures/Interface/Targeting/Doll/leftarm.png new file mode 100644 index 0000000000..32661f5c6a Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Doll/leftarm.png differ diff --git a/Resources/Textures/Interface/Targeting/Doll/leftarm_hover.png b/Resources/Textures/Interface/Targeting/Doll/leftarm_hover.png new file mode 100644 index 0000000000..a9b2c960ce Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Doll/leftarm_hover.png differ diff --git a/Resources/Textures/Interface/Targeting/Doll/leftfoot.png b/Resources/Textures/Interface/Targeting/Doll/leftfoot.png new file mode 100644 index 0000000000..07da8e67b7 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Doll/leftfoot.png differ diff --git a/Resources/Textures/Interface/Targeting/Doll/leftfoot_hover.png b/Resources/Textures/Interface/Targeting/Doll/leftfoot_hover.png new file mode 100644 index 0000000000..2c9f9e38df Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Doll/leftfoot_hover.png differ diff --git a/Resources/Textures/Interface/Targeting/Doll/lefthand.png b/Resources/Textures/Interface/Targeting/Doll/lefthand.png new file mode 100644 index 0000000000..a0251efb2c Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Doll/lefthand.png differ diff --git a/Resources/Textures/Interface/Targeting/Doll/lefthand_hover.png b/Resources/Textures/Interface/Targeting/Doll/lefthand_hover.png new file mode 100644 index 0000000000..3ff52eae55 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Doll/lefthand_hover.png differ diff --git a/Resources/Textures/Interface/Targeting/Doll/leftleg.png b/Resources/Textures/Interface/Targeting/Doll/leftleg.png new file mode 100644 index 0000000000..99dc24ce62 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Doll/leftleg.png differ diff --git a/Resources/Textures/Interface/Targeting/Doll/leftleg_hover.png b/Resources/Textures/Interface/Targeting/Doll/leftleg_hover.png new file mode 100644 index 0000000000..f2c2979e41 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Doll/leftleg_hover.png differ diff --git a/Resources/Textures/Interface/Targeting/Doll/mouth.png b/Resources/Textures/Interface/Targeting/Doll/mouth.png new file mode 100644 index 0000000000..202f411874 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Doll/mouth.png differ diff --git a/Resources/Textures/Interface/Targeting/Doll/mouth_hover.png b/Resources/Textures/Interface/Targeting/Doll/mouth_hover.png new file mode 100644 index 0000000000..a5014f42ca Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Doll/mouth_hover.png differ diff --git a/Resources/Textures/Interface/Targeting/Doll/rightarm.png b/Resources/Textures/Interface/Targeting/Doll/rightarm.png new file mode 100644 index 0000000000..223afaacad Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Doll/rightarm.png differ diff --git a/Resources/Textures/Interface/Targeting/Doll/rightarm_hover.png b/Resources/Textures/Interface/Targeting/Doll/rightarm_hover.png new file mode 100644 index 0000000000..7121c80830 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Doll/rightarm_hover.png differ diff --git a/Resources/Textures/Interface/Targeting/Doll/rightfoot.png b/Resources/Textures/Interface/Targeting/Doll/rightfoot.png new file mode 100644 index 0000000000..d1c687c681 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Doll/rightfoot.png differ diff --git a/Resources/Textures/Interface/Targeting/Doll/rightfoot_hover.png b/Resources/Textures/Interface/Targeting/Doll/rightfoot_hover.png new file mode 100644 index 0000000000..60897a2880 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Doll/rightfoot_hover.png differ diff --git a/Resources/Textures/Interface/Targeting/Doll/righthand.png b/Resources/Textures/Interface/Targeting/Doll/righthand.png new file mode 100644 index 0000000000..e71f8de80e Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Doll/righthand.png differ diff --git a/Resources/Textures/Interface/Targeting/Doll/righthand_hover.png b/Resources/Textures/Interface/Targeting/Doll/righthand_hover.png new file mode 100644 index 0000000000..d51910da17 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Doll/righthand_hover.png differ diff --git a/Resources/Textures/Interface/Targeting/Doll/rightleg.png b/Resources/Textures/Interface/Targeting/Doll/rightleg.png new file mode 100644 index 0000000000..5f7b683610 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Doll/rightleg.png differ diff --git a/Resources/Textures/Interface/Targeting/Doll/rightleg_hover.png b/Resources/Textures/Interface/Targeting/Doll/rightleg_hover.png new file mode 100644 index 0000000000..b0936de0a0 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Doll/rightleg_hover.png differ diff --git a/Resources/Textures/Interface/Targeting/Doll/torso.png b/Resources/Textures/Interface/Targeting/Doll/torso.png new file mode 100644 index 0000000000..44297c96c3 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Doll/torso.png differ diff --git a/Resources/Textures/Interface/Targeting/Doll/torso_hover.png b/Resources/Textures/Interface/Targeting/Doll/torso_hover.png new file mode 100644 index 0000000000..70f5e5969c Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Doll/torso_hover.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/groin.rsi/groin_0.png b/Resources/Textures/Interface/Targeting/Status/groin.rsi/groin_0.png new file mode 100644 index 0000000000..2e46e3a8cf Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/groin.rsi/groin_0.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/groin.rsi/groin_1.png b/Resources/Textures/Interface/Targeting/Status/groin.rsi/groin_1.png new file mode 100644 index 0000000000..7df0059c12 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/groin.rsi/groin_1.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/groin.rsi/groin_2.png b/Resources/Textures/Interface/Targeting/Status/groin.rsi/groin_2.png new file mode 100644 index 0000000000..8a6e3add67 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/groin.rsi/groin_2.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/groin.rsi/groin_3.png b/Resources/Textures/Interface/Targeting/Status/groin.rsi/groin_3.png new file mode 100644 index 0000000000..6d7966fc14 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/groin.rsi/groin_3.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/groin.rsi/groin_4.png b/Resources/Textures/Interface/Targeting/Status/groin.rsi/groin_4.png new file mode 100644 index 0000000000..cc36cf23ca Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/groin.rsi/groin_4.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/groin.rsi/groin_5.png b/Resources/Textures/Interface/Targeting/Status/groin.rsi/groin_5.png new file mode 100644 index 0000000000..3f260e1225 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/groin.rsi/groin_5.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/groin.rsi/groin_6.png b/Resources/Textures/Interface/Targeting/Status/groin.rsi/groin_6.png new file mode 100644 index 0000000000..0bb632ba38 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/groin.rsi/groin_6.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/groin.rsi/groin_7.png b/Resources/Textures/Interface/Targeting/Status/groin.rsi/groin_7.png new file mode 100644 index 0000000000..0bb632ba38 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/groin.rsi/groin_7.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/groin.rsi/groin_8.png b/Resources/Textures/Interface/Targeting/Status/groin.rsi/groin_8.png new file mode 100644 index 0000000000..c23961f396 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/groin.rsi/groin_8.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/groin.rsi/meta.json b/Resources/Textures/Interface/Targeting/Status/groin.rsi/meta.json new file mode 100644 index 0000000000..6acd4a2c24 --- /dev/null +++ b/Resources/Textures/Interface/Targeting/Status/groin.rsi/meta.json @@ -0,0 +1,38 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Edited by Mocho from the original tgstation sprites taken at commit https://github.com/tgstation/tgstation/commit/c7e14784b35b1a136351c396d3a546f461ee2451", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "groin_0" + }, + { + "name": "groin_1" + }, + { + "name": "groin_2" + }, + { + "name": "groin_3" + }, + { + "name": "groin_4" + }, + { + "name": "groin_5" + }, + { + "name": "groin_6" + }, + { + "name": "groin_7" + }, + { + "name": "groin_8" + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Interface/Targeting/Status/head.rsi/head_0.png b/Resources/Textures/Interface/Targeting/Status/head.rsi/head_0.png new file mode 100644 index 0000000000..f547b0bc96 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/head.rsi/head_0.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/head.rsi/head_1.png b/Resources/Textures/Interface/Targeting/Status/head.rsi/head_1.png new file mode 100644 index 0000000000..0ba1a77dfd Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/head.rsi/head_1.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/head.rsi/head_2.png b/Resources/Textures/Interface/Targeting/Status/head.rsi/head_2.png new file mode 100644 index 0000000000..1cb55b4fe9 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/head.rsi/head_2.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/head.rsi/head_3.png b/Resources/Textures/Interface/Targeting/Status/head.rsi/head_3.png new file mode 100644 index 0000000000..d797b40a16 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/head.rsi/head_3.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/head.rsi/head_4.png b/Resources/Textures/Interface/Targeting/Status/head.rsi/head_4.png new file mode 100644 index 0000000000..fbc1678ce5 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/head.rsi/head_4.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/head.rsi/head_5.png b/Resources/Textures/Interface/Targeting/Status/head.rsi/head_5.png new file mode 100644 index 0000000000..f6caf62d40 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/head.rsi/head_5.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/head.rsi/head_6.png b/Resources/Textures/Interface/Targeting/Status/head.rsi/head_6.png new file mode 100644 index 0000000000..74b58c642b Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/head.rsi/head_6.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/head.rsi/head_7.png b/Resources/Textures/Interface/Targeting/Status/head.rsi/head_7.png new file mode 100644 index 0000000000..74b58c642b Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/head.rsi/head_7.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/head.rsi/head_8.png b/Resources/Textures/Interface/Targeting/Status/head.rsi/head_8.png new file mode 100644 index 0000000000..4d024c61f7 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/head.rsi/head_8.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/head.rsi/meta.json b/Resources/Textures/Interface/Targeting/Status/head.rsi/meta.json new file mode 100644 index 0000000000..2c34f86c28 --- /dev/null +++ b/Resources/Textures/Interface/Targeting/Status/head.rsi/meta.json @@ -0,0 +1,38 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/c7e14784b35b1a136351c396d3a546f461ee2451", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "head_0" + }, + { + "name": "head_1" + }, + { + "name": "head_2" + }, + { + "name": "head_3" + }, + { + "name": "head_4" + }, + { + "name": "head_5" + }, + { + "name": "head_6" + }, + { + "name": "head_7" + }, + { + "name": "head_8" + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Interface/Targeting/Status/leftarm.rsi/leftarm_0.png b/Resources/Textures/Interface/Targeting/Status/leftarm.rsi/leftarm_0.png new file mode 100644 index 0000000000..22c7982489 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/leftarm.rsi/leftarm_0.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/leftarm.rsi/leftarm_1.png b/Resources/Textures/Interface/Targeting/Status/leftarm.rsi/leftarm_1.png new file mode 100644 index 0000000000..cd3b5d4c1c Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/leftarm.rsi/leftarm_1.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/leftarm.rsi/leftarm_2.png b/Resources/Textures/Interface/Targeting/Status/leftarm.rsi/leftarm_2.png new file mode 100644 index 0000000000..e4db196167 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/leftarm.rsi/leftarm_2.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/leftarm.rsi/leftarm_3.png b/Resources/Textures/Interface/Targeting/Status/leftarm.rsi/leftarm_3.png new file mode 100644 index 0000000000..d6a204a18a Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/leftarm.rsi/leftarm_3.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/leftarm.rsi/leftarm_4.png b/Resources/Textures/Interface/Targeting/Status/leftarm.rsi/leftarm_4.png new file mode 100644 index 0000000000..2c9e81508b Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/leftarm.rsi/leftarm_4.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/leftarm.rsi/leftarm_5.png b/Resources/Textures/Interface/Targeting/Status/leftarm.rsi/leftarm_5.png new file mode 100644 index 0000000000..930a7b52ed Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/leftarm.rsi/leftarm_5.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/leftarm.rsi/leftarm_6.png b/Resources/Textures/Interface/Targeting/Status/leftarm.rsi/leftarm_6.png new file mode 100644 index 0000000000..efc7380c27 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/leftarm.rsi/leftarm_6.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/leftarm.rsi/leftarm_7.png b/Resources/Textures/Interface/Targeting/Status/leftarm.rsi/leftarm_7.png new file mode 100644 index 0000000000..efc7380c27 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/leftarm.rsi/leftarm_7.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/leftarm.rsi/leftarm_8.png b/Resources/Textures/Interface/Targeting/Status/leftarm.rsi/leftarm_8.png new file mode 100644 index 0000000000..a00a6b1822 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/leftarm.rsi/leftarm_8.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/leftarm.rsi/meta.json b/Resources/Textures/Interface/Targeting/Status/leftarm.rsi/meta.json new file mode 100644 index 0000000000..dab3ed611d --- /dev/null +++ b/Resources/Textures/Interface/Targeting/Status/leftarm.rsi/meta.json @@ -0,0 +1,38 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Edited by Mocho from the original tgstation sprites taken at commit https://github.com/tgstation/tgstation/commit/c7e14784b35b1a136351c396d3a546f461ee2451", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "leftarm_0" + }, + { + "name": "leftarm_1" + }, + { + "name": "leftarm_2" + }, + { + "name": "leftarm_3" + }, + { + "name": "leftarm_4" + }, + { + "name": "leftarm_5" + }, + { + "name": "leftarm_6" + }, + { + "name": "leftarm_7" + }, + { + "name": "leftarm_8" + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Interface/Targeting/Status/leftfoot.rsi/leftfoot_0.png b/Resources/Textures/Interface/Targeting/Status/leftfoot.rsi/leftfoot_0.png new file mode 100644 index 0000000000..af6e8eb45e Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/leftfoot.rsi/leftfoot_0.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/leftfoot.rsi/leftfoot_1.png b/Resources/Textures/Interface/Targeting/Status/leftfoot.rsi/leftfoot_1.png new file mode 100644 index 0000000000..b8e10c8c48 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/leftfoot.rsi/leftfoot_1.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/leftfoot.rsi/leftfoot_2.png b/Resources/Textures/Interface/Targeting/Status/leftfoot.rsi/leftfoot_2.png new file mode 100644 index 0000000000..31feb63b7b Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/leftfoot.rsi/leftfoot_2.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/leftfoot.rsi/leftfoot_3.png b/Resources/Textures/Interface/Targeting/Status/leftfoot.rsi/leftfoot_3.png new file mode 100644 index 0000000000..274855e419 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/leftfoot.rsi/leftfoot_3.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/leftfoot.rsi/leftfoot_4.png b/Resources/Textures/Interface/Targeting/Status/leftfoot.rsi/leftfoot_4.png new file mode 100644 index 0000000000..b3e5dfefa1 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/leftfoot.rsi/leftfoot_4.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/leftfoot.rsi/leftfoot_5.png b/Resources/Textures/Interface/Targeting/Status/leftfoot.rsi/leftfoot_5.png new file mode 100644 index 0000000000..b4af7c1c03 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/leftfoot.rsi/leftfoot_5.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/leftfoot.rsi/leftfoot_6.png b/Resources/Textures/Interface/Targeting/Status/leftfoot.rsi/leftfoot_6.png new file mode 100644 index 0000000000..9822dfba4c Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/leftfoot.rsi/leftfoot_6.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/leftfoot.rsi/leftfoot_7.png b/Resources/Textures/Interface/Targeting/Status/leftfoot.rsi/leftfoot_7.png new file mode 100644 index 0000000000..9822dfba4c Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/leftfoot.rsi/leftfoot_7.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/leftfoot.rsi/leftfoot_8.png b/Resources/Textures/Interface/Targeting/Status/leftfoot.rsi/leftfoot_8.png new file mode 100644 index 0000000000..bdaec2557c Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/leftfoot.rsi/leftfoot_8.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/leftfoot.rsi/meta.json b/Resources/Textures/Interface/Targeting/Status/leftfoot.rsi/meta.json new file mode 100644 index 0000000000..9396f537f8 --- /dev/null +++ b/Resources/Textures/Interface/Targeting/Status/leftfoot.rsi/meta.json @@ -0,0 +1,38 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Edited by Mocho from the original tgstation sprites taken at commit https://github.com/tgstation/tgstation/commit/c7e14784b35b1a136351c396d3a546f461ee2451", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "leftfoot_0" + }, + { + "name": "leftfoot_1" + }, + { + "name": "leftfoot_2" + }, + { + "name": "leftfoot_3" + }, + { + "name": "leftfoot_4" + }, + { + "name": "leftfoot_5" + }, + { + "name": "leftfoot_6" + }, + { + "name": "leftfoot_7" + }, + { + "name": "leftfoot_8" + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Interface/Targeting/Status/lefthand.rsi/lefthand_0.png b/Resources/Textures/Interface/Targeting/Status/lefthand.rsi/lefthand_0.png new file mode 100644 index 0000000000..f56984a54e Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/lefthand.rsi/lefthand_0.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/lefthand.rsi/lefthand_1.png b/Resources/Textures/Interface/Targeting/Status/lefthand.rsi/lefthand_1.png new file mode 100644 index 0000000000..e5a7aea1b4 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/lefthand.rsi/lefthand_1.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/lefthand.rsi/lefthand_2.png b/Resources/Textures/Interface/Targeting/Status/lefthand.rsi/lefthand_2.png new file mode 100644 index 0000000000..43c72f0b21 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/lefthand.rsi/lefthand_2.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/lefthand.rsi/lefthand_3.png b/Resources/Textures/Interface/Targeting/Status/lefthand.rsi/lefthand_3.png new file mode 100644 index 0000000000..3114a1995c Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/lefthand.rsi/lefthand_3.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/lefthand.rsi/lefthand_4.png b/Resources/Textures/Interface/Targeting/Status/lefthand.rsi/lefthand_4.png new file mode 100644 index 0000000000..83c4daefab Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/lefthand.rsi/lefthand_4.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/lefthand.rsi/lefthand_5.png b/Resources/Textures/Interface/Targeting/Status/lefthand.rsi/lefthand_5.png new file mode 100644 index 0000000000..06946c97c7 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/lefthand.rsi/lefthand_5.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/lefthand.rsi/lefthand_6.png b/Resources/Textures/Interface/Targeting/Status/lefthand.rsi/lefthand_6.png new file mode 100644 index 0000000000..7d2ed49661 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/lefthand.rsi/lefthand_6.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/lefthand.rsi/lefthand_7.png b/Resources/Textures/Interface/Targeting/Status/lefthand.rsi/lefthand_7.png new file mode 100644 index 0000000000..7d2ed49661 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/lefthand.rsi/lefthand_7.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/lefthand.rsi/lefthand_8.png b/Resources/Textures/Interface/Targeting/Status/lefthand.rsi/lefthand_8.png new file mode 100644 index 0000000000..2b33680d43 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/lefthand.rsi/lefthand_8.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/lefthand.rsi/meta.json b/Resources/Textures/Interface/Targeting/Status/lefthand.rsi/meta.json new file mode 100644 index 0000000000..f7d5a59525 --- /dev/null +++ b/Resources/Textures/Interface/Targeting/Status/lefthand.rsi/meta.json @@ -0,0 +1,38 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Edited by Mocho from the original tgstation sprites taken at commit https://github.com/tgstation/tgstation/commit/c7e14784b35b1a136351c396d3a546f461ee2451", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "lefthand_0" + }, + { + "name": "lefthand_1" + }, + { + "name": "lefthand_2" + }, + { + "name": "lefthand_3" + }, + { + "name": "lefthand_4" + }, + { + "name": "lefthand_5" + }, + { + "name": "lefthand_6" + }, + { + "name": "lefthand_7" + }, + { + "name": "lefthand_8" + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Interface/Targeting/Status/leftleg.rsi/leftleg_0.png b/Resources/Textures/Interface/Targeting/Status/leftleg.rsi/leftleg_0.png new file mode 100644 index 0000000000..9f8193be96 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/leftleg.rsi/leftleg_0.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/leftleg.rsi/leftleg_1.png b/Resources/Textures/Interface/Targeting/Status/leftleg.rsi/leftleg_1.png new file mode 100644 index 0000000000..ab0c3e410d Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/leftleg.rsi/leftleg_1.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/leftleg.rsi/leftleg_2.png b/Resources/Textures/Interface/Targeting/Status/leftleg.rsi/leftleg_2.png new file mode 100644 index 0000000000..6390bbf7ab Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/leftleg.rsi/leftleg_2.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/leftleg.rsi/leftleg_3.png b/Resources/Textures/Interface/Targeting/Status/leftleg.rsi/leftleg_3.png new file mode 100644 index 0000000000..9159aebd45 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/leftleg.rsi/leftleg_3.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/leftleg.rsi/leftleg_4.png b/Resources/Textures/Interface/Targeting/Status/leftleg.rsi/leftleg_4.png new file mode 100644 index 0000000000..9dcd84fffd Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/leftleg.rsi/leftleg_4.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/leftleg.rsi/leftleg_5.png b/Resources/Textures/Interface/Targeting/Status/leftleg.rsi/leftleg_5.png new file mode 100644 index 0000000000..4e302cda64 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/leftleg.rsi/leftleg_5.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/leftleg.rsi/leftleg_6.png b/Resources/Textures/Interface/Targeting/Status/leftleg.rsi/leftleg_6.png new file mode 100644 index 0000000000..d36a43c441 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/leftleg.rsi/leftleg_6.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/leftleg.rsi/leftleg_7.png b/Resources/Textures/Interface/Targeting/Status/leftleg.rsi/leftleg_7.png new file mode 100644 index 0000000000..d36a43c441 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/leftleg.rsi/leftleg_7.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/leftleg.rsi/leftleg_8.png b/Resources/Textures/Interface/Targeting/Status/leftleg.rsi/leftleg_8.png new file mode 100644 index 0000000000..75bd581d69 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/leftleg.rsi/leftleg_8.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/leftleg.rsi/meta.json b/Resources/Textures/Interface/Targeting/Status/leftleg.rsi/meta.json new file mode 100644 index 0000000000..887c60f37c --- /dev/null +++ b/Resources/Textures/Interface/Targeting/Status/leftleg.rsi/meta.json @@ -0,0 +1,38 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Edited by Mocho from the original tgstation sprites taken at commit https://github.com/tgstation/tgstation/commit/c7e14784b35b1a136351c396d3a546f461ee2451", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "leftleg_0" + }, + { + "name": "leftleg_1" + }, + { + "name": "leftleg_2" + }, + { + "name": "leftleg_3" + }, + { + "name": "leftleg_4" + }, + { + "name": "leftleg_5" + }, + { + "name": "leftleg_6" + }, + { + "name": "leftleg_7" + }, + { + "name": "leftleg_8" + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Interface/Targeting/Status/rightarm.rsi/meta.json b/Resources/Textures/Interface/Targeting/Status/rightarm.rsi/meta.json new file mode 100644 index 0000000000..eb19b5d4c4 --- /dev/null +++ b/Resources/Textures/Interface/Targeting/Status/rightarm.rsi/meta.json @@ -0,0 +1,38 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Edited by Mocho from the original tgstation sprites taken at commit https://github.com/tgstation/tgstation/commit/c7e14784b35b1a136351c396d3a546f461ee2451", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "rightarm_0" + }, + { + "name": "rightarm_1" + }, + { + "name": "rightarm_2" + }, + { + "name": "rightarm_3" + }, + { + "name": "rightarm_4" + }, + { + "name": "rightarm_5" + }, + { + "name": "rightarm_6" + }, + { + "name": "rightarm_7" + }, + { + "name": "rightarm_8" + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Interface/Targeting/Status/rightarm.rsi/rightarm_0.png b/Resources/Textures/Interface/Targeting/Status/rightarm.rsi/rightarm_0.png new file mode 100644 index 0000000000..44c8aca632 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/rightarm.rsi/rightarm_0.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/rightarm.rsi/rightarm_1.png b/Resources/Textures/Interface/Targeting/Status/rightarm.rsi/rightarm_1.png new file mode 100644 index 0000000000..035e0ae12e Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/rightarm.rsi/rightarm_1.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/rightarm.rsi/rightarm_2.png b/Resources/Textures/Interface/Targeting/Status/rightarm.rsi/rightarm_2.png new file mode 100644 index 0000000000..f6db2e12a9 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/rightarm.rsi/rightarm_2.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/rightarm.rsi/rightarm_3.png b/Resources/Textures/Interface/Targeting/Status/rightarm.rsi/rightarm_3.png new file mode 100644 index 0000000000..cd974fdeea Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/rightarm.rsi/rightarm_3.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/rightarm.rsi/rightarm_4.png b/Resources/Textures/Interface/Targeting/Status/rightarm.rsi/rightarm_4.png new file mode 100644 index 0000000000..0a5977cfaf Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/rightarm.rsi/rightarm_4.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/rightarm.rsi/rightarm_5.png b/Resources/Textures/Interface/Targeting/Status/rightarm.rsi/rightarm_5.png new file mode 100644 index 0000000000..1eb5f0eadd Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/rightarm.rsi/rightarm_5.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/rightarm.rsi/rightarm_6.png b/Resources/Textures/Interface/Targeting/Status/rightarm.rsi/rightarm_6.png new file mode 100644 index 0000000000..35eec2905a Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/rightarm.rsi/rightarm_6.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/rightarm.rsi/rightarm_7.png b/Resources/Textures/Interface/Targeting/Status/rightarm.rsi/rightarm_7.png new file mode 100644 index 0000000000..35eec2905a Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/rightarm.rsi/rightarm_7.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/rightarm.rsi/rightarm_8.png b/Resources/Textures/Interface/Targeting/Status/rightarm.rsi/rightarm_8.png new file mode 100644 index 0000000000..9f894b1bfb Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/rightarm.rsi/rightarm_8.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/rightfoot.rsi/meta.json b/Resources/Textures/Interface/Targeting/Status/rightfoot.rsi/meta.json new file mode 100644 index 0000000000..ce1b1f37e7 --- /dev/null +++ b/Resources/Textures/Interface/Targeting/Status/rightfoot.rsi/meta.json @@ -0,0 +1,38 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Edited by Mocho from the original tgstation sprites taken at commit https://github.com/tgstation/tgstation/commit/c7e14784b35b1a136351c396d3a546f461ee2451", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "rightfoot_0" + }, + { + "name": "rightfoot_1" + }, + { + "name": "rightfoot_2" + }, + { + "name": "rightfoot_3" + }, + { + "name": "rightfoot_4" + }, + { + "name": "rightfoot_5" + }, + { + "name": "rightfoot_6" + }, + { + "name": "rightfoot_7" + }, + { + "name": "rightfoot_8" + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Interface/Targeting/Status/rightfoot.rsi/rightfoot_0.png b/Resources/Textures/Interface/Targeting/Status/rightfoot.rsi/rightfoot_0.png new file mode 100644 index 0000000000..573e32ca10 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/rightfoot.rsi/rightfoot_0.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/rightfoot.rsi/rightfoot_1.png b/Resources/Textures/Interface/Targeting/Status/rightfoot.rsi/rightfoot_1.png new file mode 100644 index 0000000000..967639b3fd Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/rightfoot.rsi/rightfoot_1.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/rightfoot.rsi/rightfoot_2.png b/Resources/Textures/Interface/Targeting/Status/rightfoot.rsi/rightfoot_2.png new file mode 100644 index 0000000000..bff7d8016d Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/rightfoot.rsi/rightfoot_2.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/rightfoot.rsi/rightfoot_3.png b/Resources/Textures/Interface/Targeting/Status/rightfoot.rsi/rightfoot_3.png new file mode 100644 index 0000000000..d74824df98 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/rightfoot.rsi/rightfoot_3.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/rightfoot.rsi/rightfoot_4.png b/Resources/Textures/Interface/Targeting/Status/rightfoot.rsi/rightfoot_4.png new file mode 100644 index 0000000000..50f6abc2c3 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/rightfoot.rsi/rightfoot_4.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/rightfoot.rsi/rightfoot_5.png b/Resources/Textures/Interface/Targeting/Status/rightfoot.rsi/rightfoot_5.png new file mode 100644 index 0000000000..c824c98dfc Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/rightfoot.rsi/rightfoot_5.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/rightfoot.rsi/rightfoot_6.png b/Resources/Textures/Interface/Targeting/Status/rightfoot.rsi/rightfoot_6.png new file mode 100644 index 0000000000..ebc654d70e Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/rightfoot.rsi/rightfoot_6.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/rightfoot.rsi/rightfoot_7.png b/Resources/Textures/Interface/Targeting/Status/rightfoot.rsi/rightfoot_7.png new file mode 100644 index 0000000000..ebc654d70e Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/rightfoot.rsi/rightfoot_7.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/rightfoot.rsi/rightfoot_8.png b/Resources/Textures/Interface/Targeting/Status/rightfoot.rsi/rightfoot_8.png new file mode 100644 index 0000000000..06ab1d2519 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/rightfoot.rsi/rightfoot_8.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/righthand.rsi/meta.json b/Resources/Textures/Interface/Targeting/Status/righthand.rsi/meta.json new file mode 100644 index 0000000000..61c5b77862 --- /dev/null +++ b/Resources/Textures/Interface/Targeting/Status/righthand.rsi/meta.json @@ -0,0 +1,38 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Edited by Mocho from the original tgstation sprites taken at commit https://github.com/tgstation/tgstation/commit/c7e14784b35b1a136351c396d3a546f461ee2451", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "righthand_0" + }, + { + "name": "righthand_1" + }, + { + "name": "righthand_2" + }, + { + "name": "righthand_3" + }, + { + "name": "righthand_4" + }, + { + "name": "righthand_5" + }, + { + "name": "righthand_6" + }, + { + "name": "righthand_7" + }, + { + "name": "righthand_8" + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Interface/Targeting/Status/righthand.rsi/righthand_0.png b/Resources/Textures/Interface/Targeting/Status/righthand.rsi/righthand_0.png new file mode 100644 index 0000000000..a69056bb65 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/righthand.rsi/righthand_0.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/righthand.rsi/righthand_1.png b/Resources/Textures/Interface/Targeting/Status/righthand.rsi/righthand_1.png new file mode 100644 index 0000000000..e14168b2e7 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/righthand.rsi/righthand_1.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/righthand.rsi/righthand_2.png b/Resources/Textures/Interface/Targeting/Status/righthand.rsi/righthand_2.png new file mode 100644 index 0000000000..b12b15e883 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/righthand.rsi/righthand_2.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/righthand.rsi/righthand_3.png b/Resources/Textures/Interface/Targeting/Status/righthand.rsi/righthand_3.png new file mode 100644 index 0000000000..a030aa0980 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/righthand.rsi/righthand_3.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/righthand.rsi/righthand_4.png b/Resources/Textures/Interface/Targeting/Status/righthand.rsi/righthand_4.png new file mode 100644 index 0000000000..4afd3e7f53 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/righthand.rsi/righthand_4.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/righthand.rsi/righthand_5.png b/Resources/Textures/Interface/Targeting/Status/righthand.rsi/righthand_5.png new file mode 100644 index 0000000000..5062dafc20 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/righthand.rsi/righthand_5.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/righthand.rsi/righthand_6.png b/Resources/Textures/Interface/Targeting/Status/righthand.rsi/righthand_6.png new file mode 100644 index 0000000000..87e8f18ef8 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/righthand.rsi/righthand_6.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/righthand.rsi/righthand_7.png b/Resources/Textures/Interface/Targeting/Status/righthand.rsi/righthand_7.png new file mode 100644 index 0000000000..87e8f18ef8 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/righthand.rsi/righthand_7.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/righthand.rsi/righthand_8.png b/Resources/Textures/Interface/Targeting/Status/righthand.rsi/righthand_8.png new file mode 100644 index 0000000000..797e0683e8 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/righthand.rsi/righthand_8.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/rightleg.rsi/meta.json b/Resources/Textures/Interface/Targeting/Status/rightleg.rsi/meta.json new file mode 100644 index 0000000000..7dded85e45 --- /dev/null +++ b/Resources/Textures/Interface/Targeting/Status/rightleg.rsi/meta.json @@ -0,0 +1,38 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Edited by Mocho from the original tgstation sprites taken at commit https://github.com/tgstation/tgstation/commit/c7e14784b35b1a136351c396d3a546f461ee2451", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "rightleg_0" + }, + { + "name": "rightleg_1" + }, + { + "name": "rightleg_2" + }, + { + "name": "rightleg_3" + }, + { + "name": "rightleg_4" + }, + { + "name": "rightleg_5" + }, + { + "name": "rightleg_6" + }, + { + "name": "rightleg_7" + }, + { + "name": "rightleg_8" + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Interface/Targeting/Status/rightleg.rsi/rightleg_0.png b/Resources/Textures/Interface/Targeting/Status/rightleg.rsi/rightleg_0.png new file mode 100644 index 0000000000..f773330b02 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/rightleg.rsi/rightleg_0.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/rightleg.rsi/rightleg_1.png b/Resources/Textures/Interface/Targeting/Status/rightleg.rsi/rightleg_1.png new file mode 100644 index 0000000000..786b0777c3 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/rightleg.rsi/rightleg_1.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/rightleg.rsi/rightleg_2.png b/Resources/Textures/Interface/Targeting/Status/rightleg.rsi/rightleg_2.png new file mode 100644 index 0000000000..2ac7cbafa6 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/rightleg.rsi/rightleg_2.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/rightleg.rsi/rightleg_3.png b/Resources/Textures/Interface/Targeting/Status/rightleg.rsi/rightleg_3.png new file mode 100644 index 0000000000..2f9690690d Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/rightleg.rsi/rightleg_3.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/rightleg.rsi/rightleg_4.png b/Resources/Textures/Interface/Targeting/Status/rightleg.rsi/rightleg_4.png new file mode 100644 index 0000000000..ec62ccb062 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/rightleg.rsi/rightleg_4.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/rightleg.rsi/rightleg_5.png b/Resources/Textures/Interface/Targeting/Status/rightleg.rsi/rightleg_5.png new file mode 100644 index 0000000000..d30e124e64 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/rightleg.rsi/rightleg_5.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/rightleg.rsi/rightleg_6.png b/Resources/Textures/Interface/Targeting/Status/rightleg.rsi/rightleg_6.png new file mode 100644 index 0000000000..9c4ee1f903 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/rightleg.rsi/rightleg_6.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/rightleg.rsi/rightleg_7.png b/Resources/Textures/Interface/Targeting/Status/rightleg.rsi/rightleg_7.png new file mode 100644 index 0000000000..9c4ee1f903 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/rightleg.rsi/rightleg_7.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/rightleg.rsi/rightleg_8.png b/Resources/Textures/Interface/Targeting/Status/rightleg.rsi/rightleg_8.png new file mode 100644 index 0000000000..70ccb309ad Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/rightleg.rsi/rightleg_8.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/torso.rsi/meta.json b/Resources/Textures/Interface/Targeting/Status/torso.rsi/meta.json new file mode 100644 index 0000000000..7baeebd080 --- /dev/null +++ b/Resources/Textures/Interface/Targeting/Status/torso.rsi/meta.json @@ -0,0 +1,38 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Edited by Mocho from the original tgstation sprites taken at commit https://github.com/tgstation/tgstation/commit/c7e14784b35b1a136351c396d3a546f461ee2451", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "torso_0" + }, + { + "name": "torso_1" + }, + { + "name": "torso_2" + }, + { + "name": "torso_3" + }, + { + "name": "torso_4" + }, + { + "name": "torso_5" + }, + { + "name": "torso_6" + }, + { + "name": "torso_7" + }, + { + "name": "torso_8" + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Interface/Targeting/Status/torso.rsi/torso_0.png b/Resources/Textures/Interface/Targeting/Status/torso.rsi/torso_0.png new file mode 100644 index 0000000000..b20ce001ba Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/torso.rsi/torso_0.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/torso.rsi/torso_1.png b/Resources/Textures/Interface/Targeting/Status/torso.rsi/torso_1.png new file mode 100644 index 0000000000..591b6a5bab Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/torso.rsi/torso_1.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/torso.rsi/torso_2.png b/Resources/Textures/Interface/Targeting/Status/torso.rsi/torso_2.png new file mode 100644 index 0000000000..71f09fc7f6 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/torso.rsi/torso_2.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/torso.rsi/torso_3.png b/Resources/Textures/Interface/Targeting/Status/torso.rsi/torso_3.png new file mode 100644 index 0000000000..fa9f954321 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/torso.rsi/torso_3.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/torso.rsi/torso_4.png b/Resources/Textures/Interface/Targeting/Status/torso.rsi/torso_4.png new file mode 100644 index 0000000000..ada865a05f Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/torso.rsi/torso_4.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/torso.rsi/torso_5.png b/Resources/Textures/Interface/Targeting/Status/torso.rsi/torso_5.png new file mode 100644 index 0000000000..8f3a6ad7d0 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/torso.rsi/torso_5.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/torso.rsi/torso_6.png b/Resources/Textures/Interface/Targeting/Status/torso.rsi/torso_6.png new file mode 100644 index 0000000000..c289a454bd Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/torso.rsi/torso_6.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/torso.rsi/torso_7.png b/Resources/Textures/Interface/Targeting/Status/torso.rsi/torso_7.png new file mode 100644 index 0000000000..c289a454bd Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/torso.rsi/torso_7.png differ diff --git a/Resources/Textures/Interface/Targeting/Status/torso.rsi/torso_8.png b/Resources/Textures/Interface/Targeting/Status/torso.rsi/torso_8.png new file mode 100644 index 0000000000..eefec17cf6 Binary files /dev/null and b/Resources/Textures/Interface/Targeting/Status/torso.rsi/torso_8.png differ diff --git a/Resources/Textures/Interface/VerbIcons/ATTRIBUTION.txt b/Resources/Textures/Interface/VerbIcons/ATTRIBUTION.txt index 0b8ae856bd..69aa4650b3 100644 --- a/Resources/Textures/Interface/VerbIcons/ATTRIBUTION.txt +++ b/Resources/Textures/Interface/VerbIcons/ATTRIBUTION.txt @@ -7,3 +7,6 @@ https://game-icons.net/1x1/lorc/padlock.html unlock.svg by Delapouite under CC BY 3.0 https://game-icons.net/1x1/delapouite/padlock-open.html + +healing_word.png by LeonardoDaBepis under CC BY 3.0 +revivify.png by LeonardoDaBepis under CC BY 3.0 \ No newline at end of file diff --git a/Resources/Textures/Interface/VerbIcons/paint.svg b/Resources/Textures/Interface/VerbIcons/paint.svg new file mode 100644 index 0000000000..78a56e3570 --- /dev/null +++ b/Resources/Textures/Interface/VerbIcons/paint.svg @@ -0,0 +1,39 @@ + + + + + + diff --git a/Resources/Textures/Interface/VerbIcons/paint.svg.192dpi.png b/Resources/Textures/Interface/VerbIcons/paint.svg.192dpi.png new file mode 100644 index 0000000000..b7bd88245f Binary files /dev/null and b/Resources/Textures/Interface/VerbIcons/paint.svg.192dpi.png differ diff --git a/Resources/Textures/Interface/VerbIcons/paint.svg.192dpi.png.yml b/Resources/Textures/Interface/VerbIcons/paint.svg.192dpi.png.yml new file mode 100644 index 0000000000..5c43e23305 --- /dev/null +++ b/Resources/Textures/Interface/VerbIcons/paint.svg.192dpi.png.yml @@ -0,0 +1,2 @@ +sample: + filter: true diff --git a/Resources/Textures/Interface/emotes.svg b/Resources/Textures/Interface/emotes.svg new file mode 100644 index 0000000000..352f7ed294 --- /dev/null +++ b/Resources/Textures/Interface/emotes.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Resources/Textures/Interface/emotes.svg.192dpi.png b/Resources/Textures/Interface/emotes.svg.192dpi.png new file mode 100644 index 0000000000..4e1f3c4f48 Binary files /dev/null and b/Resources/Textures/Interface/emotes.svg.192dpi.png differ diff --git a/Resources/Textures/Interface/emotes.svg.192dpi.png.yml b/Resources/Textures/Interface/emotes.svg.192dpi.png.yml new file mode 100644 index 0000000000..dabd6601f7 --- /dev/null +++ b/Resources/Textures/Interface/emotes.svg.192dpi.png.yml @@ -0,0 +1,2 @@ +sample: + filter: true diff --git a/Resources/Textures/LobbyScreens/attributions.yml b/Resources/Textures/LobbyScreens/attributions.yml index afe4af7bf2..4dd3dc945b 100644 --- a/Resources/Textures/LobbyScreens/attributions.yml +++ b/Resources/Textures/LobbyScreens/attributions.yml @@ -37,3 +37,8 @@ license: "CC-BY-SA-3.0" copyright: "aserovich on discord" source: "https://github.com/space-wizards/space-station-14" + +- files: ["justaweekaway.webp"] + license: "CC-BY-SA-3.0" + copyright: "plantyfern on discord" + source: "https://github.com/space-wizards/space-station-14" \ No newline at end of file diff --git a/Resources/Textures/LobbyScreens/justaweekaway.webp b/Resources/Textures/LobbyScreens/justaweekaway.webp new file mode 100644 index 0000000000..6e8205043c Binary files /dev/null and b/Resources/Textures/LobbyScreens/justaweekaway.webp differ diff --git a/Resources/Textures/LobbyScreens/justaweekaway.yml b/Resources/Textures/LobbyScreens/justaweekaway.yml new file mode 100644 index 0000000000..5c43e23305 --- /dev/null +++ b/Resources/Textures/LobbyScreens/justaweekaway.yml @@ -0,0 +1,2 @@ +sample: + filter: true diff --git a/Resources/Textures/Markers/environment.rsi/base-blue.png b/Resources/Textures/Markers/environment.rsi/base-blue.png new file mode 100644 index 0000000000..ee77bb448a Binary files /dev/null and b/Resources/Textures/Markers/environment.rsi/base-blue.png differ diff --git a/Resources/Textures/Markers/environment.rsi/base-green.png b/Resources/Textures/Markers/environment.rsi/base-green.png new file mode 100644 index 0000000000..a39bca9cdc Binary files /dev/null and b/Resources/Textures/Markers/environment.rsi/base-green.png differ diff --git a/Resources/Textures/Markers/environment.rsi/base-red.png b/Resources/Textures/Markers/environment.rsi/base-red.png new file mode 100644 index 0000000000..e0d68f8b9e Binary files /dev/null and b/Resources/Textures/Markers/environment.rsi/base-red.png differ diff --git a/Resources/Textures/Markers/environment.rsi/fire.png b/Resources/Textures/Markers/environment.rsi/fire.png new file mode 100644 index 0000000000..71abc46e81 Binary files /dev/null and b/Resources/Textures/Markers/environment.rsi/fire.png differ diff --git a/Resources/Textures/Markers/environment.rsi/meta.json b/Resources/Textures/Markers/environment.rsi/meta.json new file mode 100644 index 0000000000..6fc7decc8b --- /dev/null +++ b/Resources/Textures/Markers/environment.rsi/meta.json @@ -0,0 +1,32 @@ +{ + "version": 1, + "license": "CC-BY-NC-SA-3.0", + "copyright": "Nuclear14", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "base-blue" + }, + { + "name": "base-red" + }, + { + "name": "base-green" + }, + { + "name": "fire" + }, + { + "name": "rad" + }, + { + "name": "wall" + }, + { + "name": "weather" + } + ] +} diff --git a/Resources/Textures/Markers/environment.rsi/rad.png b/Resources/Textures/Markers/environment.rsi/rad.png new file mode 100644 index 0000000000..ae01d87143 Binary files /dev/null and b/Resources/Textures/Markers/environment.rsi/rad.png differ diff --git a/Resources/Textures/Markers/environment.rsi/wall.png b/Resources/Textures/Markers/environment.rsi/wall.png new file mode 100644 index 0000000000..2aaeaaf014 Binary files /dev/null and b/Resources/Textures/Markers/environment.rsi/wall.png differ diff --git a/Resources/Textures/Markers/environment.rsi/weather.png b/Resources/Textures/Markers/environment.rsi/weather.png new file mode 100644 index 0000000000..adb9f53a47 Binary files /dev/null and b/Resources/Textures/Markers/environment.rsi/weather.png differ diff --git a/Resources/Textures/Mobs/Aliens/Xenos/rouny.rsi/crit.png b/Resources/Textures/Mobs/Aliens/Xenos/rouny.rsi/crit.png index 23a7ec0af6..72f56385f5 100644 Binary files a/Resources/Textures/Mobs/Aliens/Xenos/rouny.rsi/crit.png and b/Resources/Textures/Mobs/Aliens/Xenos/rouny.rsi/crit.png differ diff --git a/Resources/Textures/Mobs/Aliens/Xenos/rouny.rsi/dead.png b/Resources/Textures/Mobs/Aliens/Xenos/rouny.rsi/dead.png index cd355b6152..8e84643281 100644 Binary files a/Resources/Textures/Mobs/Aliens/Xenos/rouny.rsi/dead.png and b/Resources/Textures/Mobs/Aliens/Xenos/rouny.rsi/dead.png differ diff --git a/Resources/Textures/Mobs/Aliens/Xenos/rouny.rsi/meta.json b/Resources/Textures/Mobs/Aliens/Xenos/rouny.rsi/meta.json index cc020d01c4..f238edbb19 100644 --- a/Resources/Textures/Mobs/Aliens/Xenos/rouny.rsi/meta.json +++ b/Resources/Textures/Mobs/Aliens/Xenos/rouny.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "DakoDragon, discord ID: 56038550335922176", + "copyright": "Created by Fenndragon, discord ID: 190763499276861440", "size": { "x": 64, "y": 64 diff --git a/Resources/Textures/Mobs/Aliens/Xenos/rouny.rsi/running.png b/Resources/Textures/Mobs/Aliens/Xenos/rouny.rsi/running.png index 1438285113..556dc97d7e 100644 Binary files a/Resources/Textures/Mobs/Aliens/Xenos/rouny.rsi/running.png and b/Resources/Textures/Mobs/Aliens/Xenos/rouny.rsi/running.png differ diff --git a/Resources/Textures/Mobs/Aliens/Xenos/rouny.rsi/sleeping.png b/Resources/Textures/Mobs/Aliens/Xenos/rouny.rsi/sleeping.png index 9221665dd8..3bb814f467 100644 Binary files a/Resources/Textures/Mobs/Aliens/Xenos/rouny.rsi/sleeping.png and b/Resources/Textures/Mobs/Aliens/Xenos/rouny.rsi/sleeping.png differ diff --git a/Resources/Textures/Mobs/Animals/patriarch.rsi/meta.json b/Resources/Textures/Mobs/Animals/patriarch.rsi/meta.json new file mode 100644 index 0000000000..c8fa857fee --- /dev/null +++ b/Resources/Textures/Mobs/Animals/patriarch.rsi/meta.json @@ -0,0 +1,24 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "https://gitlab.com/cmdevs/colonial-warfare/-/blob/dev/icons/mob/xenonids/praetorian.dmi edited by Fenndragon", + "size": { + "x": 64, + "y": 64 + }, + "states": [ + { + "name": "patriarch", + "directions": 4 + }, + { + "name": "patriarch_sleeping" + }, + { + "name": "patriarch_crit" + }, + { + "name": "patriarch_dead" + } + ] +} diff --git a/Resources/Textures/Mobs/Animals/patriarch.rsi/patriarch.png b/Resources/Textures/Mobs/Animals/patriarch.rsi/patriarch.png new file mode 100644 index 0000000000..34cb16d555 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/patriarch.rsi/patriarch.png differ diff --git a/Resources/Textures/Mobs/Animals/patriarch.rsi/patriarch_crit.png b/Resources/Textures/Mobs/Animals/patriarch.rsi/patriarch_crit.png new file mode 100644 index 0000000000..add264b5b5 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/patriarch.rsi/patriarch_crit.png differ diff --git a/Resources/Textures/Mobs/Animals/patriarch.rsi/patriarch_dead.png b/Resources/Textures/Mobs/Animals/patriarch.rsi/patriarch_dead.png new file mode 100644 index 0000000000..f190202dcc Binary files /dev/null and b/Resources/Textures/Mobs/Animals/patriarch.rsi/patriarch_dead.png differ diff --git a/Resources/Textures/Mobs/Animals/patriarch.rsi/patriarch_sleeping.png b/Resources/Textures/Mobs/Animals/patriarch.rsi/patriarch_sleeping.png new file mode 100644 index 0000000000..2cf18ada64 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/patriarch.rsi/patriarch_sleeping.png differ diff --git a/Resources/Textures/Mobs/Animals/subject7355.rsi/glow.png b/Resources/Textures/Mobs/Animals/subject7355.rsi/glow.png new file mode 100644 index 0000000000..9fc359cf0c Binary files /dev/null and b/Resources/Textures/Mobs/Animals/subject7355.rsi/glow.png differ diff --git a/Resources/Textures/Mobs/Animals/subject7355.rsi/meta.json b/Resources/Textures/Mobs/Animals/subject7355.rsi/meta.json new file mode 100644 index 0000000000..c49d5687f1 --- /dev/null +++ b/Resources/Textures/Mobs/Animals/subject7355.rsi/meta.json @@ -0,0 +1,28 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "https://gitlab.com/cmdevs/colonial-warfare/-/tree/dev/icons/mob/xenonids", + "size": { + "x": 64, + "y": 64 + }, + "states": [ + { + "name": "subject7355", + "directions": 4 + }, + { + "name": "glow", + "directions": 4 + }, + { + "name": "subject7355_sleeping" + }, + { + "name": "subject7355_crit" + }, + { + "name": "subject7355_dead" + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Mobs/Animals/subject7355.rsi/subject7355.png b/Resources/Textures/Mobs/Animals/subject7355.rsi/subject7355.png new file mode 100644 index 0000000000..9b51c99325 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/subject7355.rsi/subject7355.png differ diff --git a/Resources/Textures/Mobs/Animals/subject7355.rsi/subject7355_crit.png b/Resources/Textures/Mobs/Animals/subject7355.rsi/subject7355_crit.png new file mode 100644 index 0000000000..d400295959 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/subject7355.rsi/subject7355_crit.png differ diff --git a/Resources/Textures/Mobs/Animals/subject7355.rsi/subject7355_dead.png b/Resources/Textures/Mobs/Animals/subject7355.rsi/subject7355_dead.png new file mode 100644 index 0000000000..6b28c7da7a Binary files /dev/null and b/Resources/Textures/Mobs/Animals/subject7355.rsi/subject7355_dead.png differ diff --git a/Resources/Textures/Mobs/Animals/subject7355.rsi/subject7355_sleeping.png b/Resources/Textures/Mobs/Animals/subject7355.rsi/subject7355_sleeping.png new file mode 100644 index 0000000000..1060058325 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/subject7355.rsi/subject7355_sleeping.png differ diff --git a/Resources/Textures/Mobs/Customization/Shadowkin/ears.rsi/meta.json b/Resources/Textures/Mobs/Customization/Shadowkin/ears.rsi/meta.json new file mode 100644 index 0000000000..b67f4f3489 --- /dev/null +++ b/Resources/Textures/Mobs/Customization/Shadowkin/ears.rsi/meta.json @@ -0,0 +1,19 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from S.P.L.U.R.T ears.dmi at commit https://github.com/SPLURT-Station/S.P.L.U.R.T-Station-13/commit/8b4abffbe538fbded19b44b989ebc6808748f31f", + "states": [ + { + "name": "shadowkin", + "directions": 4 + }, + { + "name": "shadowkin_stripes", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ears.rsi/shadowkin.png b/Resources/Textures/Mobs/Customization/Shadowkin/ears.rsi/shadowkin.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ears.rsi/shadowkin.png rename to Resources/Textures/Mobs/Customization/Shadowkin/ears.rsi/shadowkin.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ears.rsi/shadowkin_stripes.png b/Resources/Textures/Mobs/Customization/Shadowkin/ears.rsi/shadowkin_stripes.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ears.rsi/shadowkin_stripes.png rename to Resources/Textures/Mobs/Customization/Shadowkin/ears.rsi/shadowkin_stripes.png diff --git a/Resources/Textures/Mobs/Customization/Shadowkin/tails32x32.rsi/meta.json b/Resources/Textures/Mobs/Customization/Shadowkin/tails32x32.rsi/meta.json new file mode 100644 index 0000000000..4731b9de3d --- /dev/null +++ b/Resources/Textures/Mobs/Customization/Shadowkin/tails32x32.rsi/meta.json @@ -0,0 +1,19 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "https://github.com/SPLURT-Station/S.P.L.U.R.T-Station-13/commit/96703f76bccd8fe6a96b78524efb97a9af661767 Shadowkin big fluff made by DSC@Cabbage#9633 (561159087765848084)", + "states": [ + { + "name": "shadowkin_shorter", + "directions": 4 + }, + { + "name": "shadowkin_medium", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Parkstation/Mobs/Customization/tails32x32.rsi/shadowkin_medium.png b/Resources/Textures/Mobs/Customization/Shadowkin/tails32x32.rsi/shadowkin_medium.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/tails32x32.rsi/shadowkin_medium.png rename to Resources/Textures/Mobs/Customization/Shadowkin/tails32x32.rsi/shadowkin_medium.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/tails32x32.rsi/shadowkin_shorter.png b/Resources/Textures/Mobs/Customization/Shadowkin/tails32x32.rsi/shadowkin_shorter.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/tails32x32.rsi/shadowkin_shorter.png rename to Resources/Textures/Mobs/Customization/Shadowkin/tails32x32.rsi/shadowkin_shorter.png diff --git a/Resources/Textures/Mobs/Customization/Shadowkin/tails64x32.rsi/meta.json b/Resources/Textures/Mobs/Customization/Shadowkin/tails64x32.rsi/meta.json new file mode 100644 index 0000000000..535c01bbaf --- /dev/null +++ b/Resources/Textures/Mobs/Customization/Shadowkin/tails64x32.rsi/meta.json @@ -0,0 +1,23 @@ +{ + "version": 1, + "size": { + "x": 64, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "https://github.com/SPLURT-Station/S.P.L.U.R.T-Station-13/commit/96703f76bccd8fe6a96b78524efb97a9af661767 Shadowkin big fluff made by DSC@Cabbage#9633 (561159087765848084)", + "states": [ + { + "name": "shadowkin", + "directions": 4 + }, + { + "name": "shadowkin_big", + "directions": 4 + }, + { + "name": "shadowkin_big_fluff", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Parkstation/Mobs/Customization/tails64x32.rsi/shadowkin.png b/Resources/Textures/Mobs/Customization/Shadowkin/tails64x32.rsi/shadowkin.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/tails64x32.rsi/shadowkin.png rename to Resources/Textures/Mobs/Customization/Shadowkin/tails64x32.rsi/shadowkin.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/tails64x32.rsi/shadowkin_big.png b/Resources/Textures/Mobs/Customization/Shadowkin/tails64x32.rsi/shadowkin_big.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/tails64x32.rsi/shadowkin_big.png rename to Resources/Textures/Mobs/Customization/Shadowkin/tails64x32.rsi/shadowkin_big.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/tails64x32.rsi/shadowkin_big_fluff.png b/Resources/Textures/Mobs/Customization/Shadowkin/tails64x32.rsi/shadowkin_big_fluff.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/tails64x32.rsi/shadowkin_big_fluff.png rename to Resources/Textures/Mobs/Customization/Shadowkin/tails64x32.rsi/shadowkin_big_fluff.png diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/hesphiastos/hesphiastos_alt1.rsi/head-1.png b/Resources/Textures/Mobs/Customization/cyberlimbs/hesphiastos/hesphiastos_alt1.rsi/head-1.png new file mode 100644 index 0000000000..51a3c40058 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/hesphiastos/hesphiastos_alt1.rsi/head-1.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/hesphiastos/hesphiastos_alt1.rsi/head-2.png b/Resources/Textures/Mobs/Customization/cyberlimbs/hesphiastos/hesphiastos_alt1.rsi/head-2.png new file mode 100644 index 0000000000..17fbf43df0 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/hesphiastos/hesphiastos_alt1.rsi/head-2.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/hesphiastos/hesphiastos_alt1.rsi/head-3.png b/Resources/Textures/Mobs/Customization/cyberlimbs/hesphiastos/hesphiastos_alt1.rsi/head-3.png new file mode 100644 index 0000000000..e59e1c1d38 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/hesphiastos/hesphiastos_alt1.rsi/head-3.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/hesphiastos/hesphiastos_alt1.rsi/head.png b/Resources/Textures/Mobs/Customization/cyberlimbs/hesphiastos/hesphiastos_alt1.rsi/head.png deleted file mode 100644 index b6446e6a6d..0000000000 Binary files a/Resources/Textures/Mobs/Customization/cyberlimbs/hesphiastos/hesphiastos_alt1.rsi/head.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/hesphiastos/hesphiastos_alt1.rsi/meta.json b/Resources/Textures/Mobs/Customization/cyberlimbs/hesphiastos/hesphiastos_alt1.rsi/meta.json index 9f87381cd8..5e35e7226f 100644 --- a/Resources/Textures/Mobs/Customization/cyberlimbs/hesphiastos/hesphiastos_alt1.rsi/meta.json +++ b/Resources/Textures/Mobs/Customization/cyberlimbs/hesphiastos/hesphiastos_alt1.rsi/meta.json @@ -1,6 +1,6 @@ { "version": 1, - "copyright": "Sprites from Paradise Station (https://github.com/ParadiseSS13/Paradise)", + "copyright": "Sprites from Paradise Station (https://github.com/ParadiseSS13/Paradise). Edited by Timemaster99", "license": "CC-BY-SA-3.0", "size": { "x": 32, @@ -8,7 +8,15 @@ }, "states": [ { - "name": "head", + "name": "head-1", + "directions": 4 + }, + { + "name": "head-2", + "directions": 4 + }, + { + "name": "head-3", "directions": 4 } ] diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/morpheus/morpheus_alt1.rsi/head.png b/Resources/Textures/Mobs/Customization/cyberlimbs/morpheus/morpheus_alt1.rsi/head.png new file mode 100644 index 0000000000..bed52ce671 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/morpheus/morpheus_alt1.rsi/head.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/morpheus/morpheus_alt1.rsi/meta.json b/Resources/Textures/Mobs/Customization/cyberlimbs/morpheus/morpheus_alt1.rsi/meta.json new file mode 100644 index 0000000000..9f87381cd8 --- /dev/null +++ b/Resources/Textures/Mobs/Customization/cyberlimbs/morpheus/morpheus_alt1.rsi/meta.json @@ -0,0 +1,15 @@ +{ + "version": 1, + "copyright": "Sprites from Paradise Station (https://github.com/ParadiseSS13/Paradise)", + "license": "CC-BY-SA-3.0", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "head", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/morpheus/morpheus_main.rsi/head.png b/Resources/Textures/Mobs/Customization/cyberlimbs/morpheus/morpheus_main.rsi/head.png new file mode 100644 index 0000000000..63a66c5f86 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/morpheus/morpheus_main.rsi/head.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/morpheus/morpheus_main.rsi/l_arm.png b/Resources/Textures/Mobs/Customization/cyberlimbs/morpheus/morpheus_main.rsi/l_arm.png new file mode 100644 index 0000000000..9813e583fe Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/morpheus/morpheus_main.rsi/l_arm.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/morpheus/morpheus_main.rsi/l_foot.png b/Resources/Textures/Mobs/Customization/cyberlimbs/morpheus/morpheus_main.rsi/l_foot.png new file mode 100644 index 0000000000..468d4f3898 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/morpheus/morpheus_main.rsi/l_foot.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/morpheus/morpheus_main.rsi/l_hand.png b/Resources/Textures/Mobs/Customization/cyberlimbs/morpheus/morpheus_main.rsi/l_hand.png new file mode 100644 index 0000000000..05df57f392 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/morpheus/morpheus_main.rsi/l_hand.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/morpheus/morpheus_main.rsi/l_leg.png b/Resources/Textures/Mobs/Customization/cyberlimbs/morpheus/morpheus_main.rsi/l_leg.png new file mode 100644 index 0000000000..236edc982e Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/morpheus/morpheus_main.rsi/l_leg.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/morpheus/morpheus_main.rsi/meta.json b/Resources/Textures/Mobs/Customization/cyberlimbs/morpheus/morpheus_main.rsi/meta.json new file mode 100644 index 0000000000..118c3f613c --- /dev/null +++ b/Resources/Textures/Mobs/Customization/cyberlimbs/morpheus/morpheus_main.rsi/meta.json @@ -0,0 +1,51 @@ +{ + "version": 1, + "copyright": "Sprites from Paradise Station (https://github.com/ParadiseSS13/Paradise)", + "license": "CC-BY-SA-3.0", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "l_foot", + "directions": 4 + }, + { + "name": "r_foot", + "directions": 4 + }, + { + "name": "l_leg", + "directions": 4 + }, + { + "name": "r_leg", + "directions": 4 + }, + { + "name": "torso", + "directions": 4 + }, + { + "name": "l_arm", + "directions": 4 + }, + { + "name": "r_arm", + "directions": 4 + }, + { + "name": "l_hand", + "directions": 4 + }, + { + "name": "r_hand", + "directions": 4 + }, + { + "name": "head", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/morpheus/morpheus_main.rsi/r_arm.png b/Resources/Textures/Mobs/Customization/cyberlimbs/morpheus/morpheus_main.rsi/r_arm.png new file mode 100644 index 0000000000..27ba8e0d26 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/morpheus/morpheus_main.rsi/r_arm.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/morpheus/morpheus_main.rsi/r_foot.png b/Resources/Textures/Mobs/Customization/cyberlimbs/morpheus/morpheus_main.rsi/r_foot.png new file mode 100644 index 0000000000..e0b3dd55b5 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/morpheus/morpheus_main.rsi/r_foot.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/morpheus/morpheus_main.rsi/r_hand.png b/Resources/Textures/Mobs/Customization/cyberlimbs/morpheus/morpheus_main.rsi/r_hand.png new file mode 100644 index 0000000000..716f604b4f Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/morpheus/morpheus_main.rsi/r_hand.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/morpheus/morpheus_main.rsi/r_leg.png b/Resources/Textures/Mobs/Customization/cyberlimbs/morpheus/morpheus_main.rsi/r_leg.png new file mode 100644 index 0000000000..92a0aa7b47 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/morpheus/morpheus_main.rsi/r_leg.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/morpheus/morpheus_main.rsi/torso.png b/Resources/Textures/Mobs/Customization/cyberlimbs/morpheus/morpheus_main.rsi/torso.png new file mode 100644 index 0000000000..49afe13d65 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/morpheus/morpheus_main.rsi/torso.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_alt1.rsi/head-1.png b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_alt1.rsi/head-1.png new file mode 100644 index 0000000000..d89df4071b Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_alt1.rsi/head-1.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_alt1.rsi/head-2.png b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_alt1.rsi/head-2.png new file mode 100644 index 0000000000..1540a6cac4 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_alt1.rsi/head-2.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_alt1.rsi/meta.json b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_alt1.rsi/meta.json new file mode 100644 index 0000000000..576edfc9c5 --- /dev/null +++ b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_alt1.rsi/meta.json @@ -0,0 +1,19 @@ +{ + "version": 1, + "copyright": "Sprites from Paradise Station (https://github.com/ParadiseSS13/Paradise). Edited by Timemaster99.", + "license": "CC-BY-SA-3.0", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "head-1", + "directions": 4 + }, + { + "name": "head-2", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/l_arm-1.png b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/l_arm-1.png new file mode 100644 index 0000000000..f5694178c5 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/l_arm-1.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/l_arm-2.png b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/l_arm-2.png new file mode 100644 index 0000000000..f87f611652 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/l_arm-2.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/l_foot-1.png b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/l_foot-1.png new file mode 100644 index 0000000000..4ee57febec Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/l_foot-1.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/l_foot-2.png b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/l_foot-2.png new file mode 100644 index 0000000000..fcdf5bf650 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/l_foot-2.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/l_hand-1.png b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/l_hand-1.png new file mode 100644 index 0000000000..381573480f Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/l_hand-1.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/l_hand-2.png b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/l_hand-2.png new file mode 100644 index 0000000000..c502e77a18 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/l_hand-2.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/l_leg-1.png b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/l_leg-1.png new file mode 100644 index 0000000000..a4bee8acb4 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/l_leg-1.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/l_leg-2.png b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/l_leg-2.png new file mode 100644 index 0000000000..0be292d26c Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/l_leg-2.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/meta.json b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/meta.json new file mode 100644 index 0000000000..9f4ca1ee51 --- /dev/null +++ b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/meta.json @@ -0,0 +1,83 @@ +{ + "version": 1, + "copyright": "Sprites originally from Paradise Station (https://github.com/ParadiseSS13/Paradise). Monochromatic version made by: DayOS (https://github.com/Day-OS)", + "license": "CC-BY-SA-3.0", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "l_foot-1", + "directions": 4 + }, + { + "name": "l_foot-2", + "directions": 4 + }, + { + "name": "r_foot-1", + "directions": 4 + }, + { + "name": "r_foot-2", + "directions": 4 + }, + { + "name": "l_leg-1", + "directions": 4 + }, + { + "name": "l_leg-2", + "directions": 4 + }, + { + "name": "r_leg-1", + "directions": 4 + }, + { + "name": "r_leg-2", + "directions": 4 + }, + { + "name": "torso-1", + "directions": 4 + }, + { + "name": "torso-2", + "directions": 4 + }, + { + "name": "l_arm-1", + "directions": 4 + }, + { + "name": "l_arm-2", + "directions": 4 + }, + { + "name": "r_arm-1", + "directions": 4 + }, + { + "name": "r_arm-2", + "directions": 4 + }, + { + "name": "l_hand-1", + "directions": 4 + }, + { + "name": "l_hand-2", + "directions": 4 + }, + { + "name": "r_hand-1", + "directions": 4 + }, + { + "name": "r_hand-2", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/r_arm-1.png b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/r_arm-1.png new file mode 100644 index 0000000000..b2a1996652 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/r_arm-1.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/r_arm-2.png b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/r_arm-2.png new file mode 100644 index 0000000000..a79a0eb648 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/r_arm-2.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/r_foot-1.png b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/r_foot-1.png new file mode 100644 index 0000000000..da79cffbc9 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/r_foot-1.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/r_foot-2.png b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/r_foot-2.png new file mode 100644 index 0000000000..90b5deb87c Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/r_foot-2.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/r_hand-1.png b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/r_hand-1.png new file mode 100644 index 0000000000..9d4084b0ca Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/r_hand-1.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/r_hand-2.png b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/r_hand-2.png new file mode 100644 index 0000000000..a2cc6b71a6 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/r_hand-2.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/r_leg-1.png b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/r_leg-1.png new file mode 100644 index 0000000000..5d1bc5a60d Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/r_leg-1.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/r_leg-2.png b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/r_leg-2.png new file mode 100644 index 0000000000..b2b85c3213 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/r_leg-2.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/torso-1.png b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/torso-1.png new file mode 100644 index 0000000000..c757f06420 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/torso-1.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/torso-2.png b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/torso-2.png new file mode 100644 index 0000000000..cd822361bf Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_main.rsi/torso-2.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_monitor.rsi/head-1.png b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_monitor.rsi/head-1.png new file mode 100644 index 0000000000..99add5cc50 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_monitor.rsi/head-1.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_monitor.rsi/head-2.png b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_monitor.rsi/head-2.png new file mode 100644 index 0000000000..0d5ffd6c10 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_monitor.rsi/head-2.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_monitor.rsi/meta.json b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_monitor.rsi/meta.json new file mode 100644 index 0000000000..9d2654d15c --- /dev/null +++ b/Resources/Textures/Mobs/Customization/cyberlimbs/shellguard/shellguard_monitor.rsi/meta.json @@ -0,0 +1,19 @@ +{ + "version": 1, + "copyright": "Sprites originally from Paradise Station (https://github.com/ParadiseSS13/Paradise). Monochromatic version made by: DayOS (https://github.com/Day-OS)", + "license": "CC-BY-SA-3.0", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "head-1", + "directions": 4 + }, + { + "name": "head-2", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_alt1.rsi/head.png b/Resources/Textures/Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_alt1.rsi/head.png new file mode 100644 index 0000000000..9fbd2083d1 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_alt1.rsi/head.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_alt1.rsi/meta.json b/Resources/Textures/Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_alt1.rsi/meta.json new file mode 100644 index 0000000000..ef992f309c --- /dev/null +++ b/Resources/Textures/Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_alt1.rsi/meta.json @@ -0,0 +1,15 @@ +{ + "version": 1, + "copyright": "Sprites from Paradise Station (https://github.com/ParadiseSS13/Paradise). Edited by Timemaster99", + "license": "CC-BY-SA-3.0", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "head", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_main.rsi/head.png b/Resources/Textures/Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_main.rsi/head.png new file mode 100644 index 0000000000..c50698e68c Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_main.rsi/head.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_main.rsi/l_arm.png b/Resources/Textures/Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_main.rsi/l_arm.png new file mode 100644 index 0000000000..1c5f646e36 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_main.rsi/l_arm.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_main.rsi/l_foot.png b/Resources/Textures/Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_main.rsi/l_foot.png new file mode 100644 index 0000000000..45d9e8e246 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_main.rsi/l_foot.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_main.rsi/l_hand.png b/Resources/Textures/Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_main.rsi/l_hand.png new file mode 100644 index 0000000000..062d14745c Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_main.rsi/l_hand.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_main.rsi/l_leg.png b/Resources/Textures/Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_main.rsi/l_leg.png new file mode 100644 index 0000000000..f120754830 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_main.rsi/l_leg.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_main.rsi/meta.json b/Resources/Textures/Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_main.rsi/meta.json new file mode 100644 index 0000000000..f30906990a --- /dev/null +++ b/Resources/Textures/Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_main.rsi/meta.json @@ -0,0 +1,51 @@ +{ + "version": 1, + "copyright": "Sprites from Paradise Station (https://github.com/ParadiseSS13/Paradise). Edited by Timemaster99", + "license": "CC-BY-SA-3.0", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "l_foot", + "directions": 4 + }, + { + "name": "r_foot", + "directions": 4 + }, + { + "name": "l_leg", + "directions": 4 + }, + { + "name": "r_leg", + "directions": 4 + }, + { + "name": "torso", + "directions": 4 + }, + { + "name": "l_arm", + "directions": 4 + }, + { + "name": "r_arm", + "directions": 4 + }, + { + "name": "l_hand", + "directions": 4 + }, + { + "name": "r_hand", + "directions": 4 + }, + { + "name": "head", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_main.rsi/r_arm.png b/Resources/Textures/Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_main.rsi/r_arm.png new file mode 100644 index 0000000000..24e15d419a Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_main.rsi/r_arm.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_main.rsi/r_foot.png b/Resources/Textures/Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_main.rsi/r_foot.png new file mode 100644 index 0000000000..f04c2fd7a6 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_main.rsi/r_foot.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_main.rsi/r_hand.png b/Resources/Textures/Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_main.rsi/r_hand.png new file mode 100644 index 0000000000..287556a83a Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_main.rsi/r_hand.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_main.rsi/r_leg.png b/Resources/Textures/Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_main.rsi/r_leg.png new file mode 100644 index 0000000000..653101e63a Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_main.rsi/r_leg.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_main.rsi/torso.png b/Resources/Textures/Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_main.rsi/torso.png new file mode 100644 index 0000000000..ad1bd42ca6 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_main.rsi/torso.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_monitor.rsi/head.png b/Resources/Textures/Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_monitor.rsi/head.png new file mode 100644 index 0000000000..31ef25deb5 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_monitor.rsi/head.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_monitor.rsi/meta.json b/Resources/Textures/Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_monitor.rsi/meta.json new file mode 100644 index 0000000000..9f87381cd8 --- /dev/null +++ b/Resources/Textures/Mobs/Customization/cyberlimbs/wardtakahashi/wardtakahashi_monitor.rsi/meta.json @@ -0,0 +1,15 @@ +{ + "version": 1, + "copyright": "Sprites from Paradise Station (https://github.com/ParadiseSS13/Paradise)", + "license": "CC-BY-SA-3.0", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "head", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_alt1.rsi/head-1.png b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_alt1.rsi/head-1.png new file mode 100644 index 0000000000..fe3943fac0 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_alt1.rsi/head-1.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_alt1.rsi/head-2.png b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_alt1.rsi/head-2.png new file mode 100644 index 0000000000..baf6e438f8 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_alt1.rsi/head-2.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_alt1.rsi/meta.json b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_alt1.rsi/meta.json new file mode 100644 index 0000000000..576edfc9c5 --- /dev/null +++ b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_alt1.rsi/meta.json @@ -0,0 +1,19 @@ +{ + "version": 1, + "copyright": "Sprites from Paradise Station (https://github.com/ParadiseSS13/Paradise). Edited by Timemaster99.", + "license": "CC-BY-SA-3.0", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "head-1", + "directions": 4 + }, + { + "name": "head-2", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/l_arm-1.png b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/l_arm-1.png new file mode 100644 index 0000000000..48cf08df07 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/l_arm-1.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/l_arm-2.png b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/l_arm-2.png new file mode 100644 index 0000000000..1ef9cc0473 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/l_arm-2.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/l_foot-1.png b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/l_foot-1.png new file mode 100644 index 0000000000..38b28406ac Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/l_foot-1.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/l_foot-2.png b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/l_foot-2.png new file mode 100644 index 0000000000..0d745f2d56 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/l_foot-2.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/l_hand-1.png b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/l_hand-1.png new file mode 100644 index 0000000000..72a1d92d83 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/l_hand-1.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/l_hand-2.png b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/l_hand-2.png new file mode 100644 index 0000000000..d61d58b478 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/l_hand-2.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/l_leg-1.png b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/l_leg-1.png new file mode 100644 index 0000000000..6e813b8b63 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/l_leg-1.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/l_leg-2.png b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/l_leg-2.png new file mode 100644 index 0000000000..89106d6420 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/l_leg-2.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/meta.json b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/meta.json new file mode 100644 index 0000000000..35c1cad74b --- /dev/null +++ b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/meta.json @@ -0,0 +1,82 @@ +{ + "version": 1, + "copyright": "Sprites originally from Paradise Station (https://github.com/ParadiseSS13/Paradise). Monochromatic version made by: DayOS (https://github.com/Day-OS)", + "license": "CC-BY-SA-3.0", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "l_foot-1", + "directions": 4 + },{ + "name": "l_foot-2", + "directions": 4 + }, + { + "name": "r_foot-1", + "directions": 4 + }, + { + "name": "r_foot-2", + "directions": 4 + }, + { + "name": "l_leg-1", + "directions": 4 + }, + { + "name": "l_leg-2", + "directions": 4 + }, + { + "name": "r_leg-1", + "directions": 4 + }, + { + "name": "r_leg-2", + "directions": 4 + }, + { + "name": "torso-1", + "directions": 4 + }, + { + "name": "torso-2", + "directions": 4 + }, + { + "name": "l_arm-1", + "directions": 4 + }, + { + "name": "l_arm-2", + "directions": 4 + }, + { + "name": "r_arm-1", + "directions": 4 + }, + { + "name": "r_arm-2", + "directions": 4 + }, + { + "name": "l_hand-1", + "directions": 4 + }, + { + "name": "l_hand-2", + "directions": 4 + }, + { + "name": "r_hand-1", + "directions": 4 + }, + { + "name": "r_hand-2", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/r_arm-1.png b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/r_arm-1.png new file mode 100644 index 0000000000..0409bb02fe Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/r_arm-1.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/r_arm-2.png b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/r_arm-2.png new file mode 100644 index 0000000000..061988dbcd Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/r_arm-2.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/r_foot-1.png b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/r_foot-1.png new file mode 100644 index 0000000000..ef8b2b141a Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/r_foot-1.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/r_foot-2.png b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/r_foot-2.png new file mode 100644 index 0000000000..74d17c3c73 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/r_foot-2.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/r_hand-1.png b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/r_hand-1.png new file mode 100644 index 0000000000..9d9dfb2848 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/r_hand-1.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/r_hand-2.png b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/r_hand-2.png new file mode 100644 index 0000000000..9d4e6c3e16 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/r_hand-2.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/r_leg-1.png b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/r_leg-1.png new file mode 100644 index 0000000000..7ef9033cc0 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/r_leg-1.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/r_leg-2.png b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/r_leg-2.png new file mode 100644 index 0000000000..cb88587d55 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/r_leg-2.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/torso-1.png b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/torso-1.png new file mode 100644 index 0000000000..db42c9a82b Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/torso-1.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/torso-2.png b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/torso-2.png new file mode 100644 index 0000000000..70271b0c3a Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_main.rsi/torso-2.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_monitor.rsi/head-1.png b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_monitor.rsi/head-1.png new file mode 100644 index 0000000000..e48ad79298 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_monitor.rsi/head-1.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_monitor.rsi/head-2.png b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_monitor.rsi/head-2.png new file mode 100644 index 0000000000..bf1903f4bc Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_monitor.rsi/head-2.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_monitor.rsi/meta.json b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_monitor.rsi/meta.json new file mode 100644 index 0000000000..9d2654d15c --- /dev/null +++ b/Resources/Textures/Mobs/Customization/cyberlimbs/xion/xion_monitor.rsi/meta.json @@ -0,0 +1,19 @@ +{ + "version": 1, + "copyright": "Sprites originally from Paradise Station (https://github.com/ParadiseSS13/Paradise). Monochromatic version made by: DayOS (https://github.com/Day-OS)", + "license": "CC-BY-SA-3.0", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "head-1", + "directions": 4 + }, + { + "name": "head-2", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/groin.png b/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/groin.png new file mode 100644 index 0000000000..29ae064b0d Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/groin.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/head-1.png b/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/head-1.png new file mode 100644 index 0000000000..792a57e81d Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/head-1.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/head-2.png b/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/head-2.png new file mode 100644 index 0000000000..14e7e83c2f Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/head-2.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/l_arm-1.png b/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/l_arm-1.png new file mode 100644 index 0000000000..93064d5605 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/l_arm-1.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/l_arm-2.png b/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/l_arm-2.png new file mode 100644 index 0000000000..7df87a8915 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/l_arm-2.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/l_foot-1.png b/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/l_foot-1.png new file mode 100644 index 0000000000..8fb9ec75e2 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/l_foot-1.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/l_foot-2.png b/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/l_foot-2.png new file mode 100644 index 0000000000..767bd0d4d6 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/l_foot-2.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/l_hand-1.png b/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/l_hand-1.png new file mode 100644 index 0000000000..d0660f1bc1 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/l_hand-1.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/l_hand-2.png b/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/l_hand-2.png new file mode 100644 index 0000000000..5bb2251d14 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/l_hand-2.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/l_leg-1.png b/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/l_leg-1.png new file mode 100644 index 0000000000..4ce036704f Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/l_leg-1.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/l_leg-2.png b/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/l_leg-2.png new file mode 100644 index 0000000000..237eb32d6e Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/l_leg-2.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/meta.json b/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/meta.json new file mode 100644 index 0000000000..bf863d580f --- /dev/null +++ b/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/meta.json @@ -0,0 +1,95 @@ +{ + "version": 1, + "copyright": "Sprites from Paradise Station (https://github.com/ParadiseSS13/Paradise). Monochromatic version by Timemaster99", + "license": "CC-BY-SA-3.0", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "l_foot-1", + "directions": 4 + }, + { + "name": "l_foot-2", + "directions": 4 + }, + { + "name": "r_foot-1", + "directions": 4 + }, + { + "name": "r_foot-2", + "directions": 4 + }, + { + "name": "l_leg-1", + "directions": 4 + }, + { + "name": "l_leg-2", + "directions": 4 + }, + { + "name": "r_leg-1", + "directions": 4 + }, + { + "name": "r_leg-2", + "directions": 4 + }, + { + "name": "groin", + "directions": 4 + }, + { + "name": "torso-1", + "directions": 4 + }, + { + "name": "torso-2", + "directions": 4 + }, + { + "name": "l_arm-1", + "directions": 4 + }, + { + "name": "l_arm-2", + "directions": 4 + }, + { + "name": "r_arm-1", + "directions": 4 + }, + { + "name": "r_arm-2", + "directions": 4 + }, + { + "name": "l_hand-1", + "directions": 4 + }, + { + "name": "l_hand-2", + "directions": 4 + }, + { + "name": "r_hand-1", + "directions": 4 + }, + { + "name": "r_hand-2", + "directions": 4 + }, + { + "name": "head-1", + "directions": 4 + }, + { + "name": "head-2", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/r_arm-1.png b/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/r_arm-1.png new file mode 100644 index 0000000000..6bd612a5d3 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/r_arm-1.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/r_arm-2.png b/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/r_arm-2.png new file mode 100644 index 0000000000..69be000d90 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/r_arm-2.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/r_foot-1.png b/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/r_foot-1.png new file mode 100644 index 0000000000..f3f1b5121d Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/r_foot-1.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/r_foot-2.png b/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/r_foot-2.png new file mode 100644 index 0000000000..d5a6fb2a71 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/r_foot-2.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/r_hand-1.png b/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/r_hand-1.png new file mode 100644 index 0000000000..a77c7f00e6 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/r_hand-1.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/r_hand-2.png b/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/r_hand-2.png new file mode 100644 index 0000000000..e8d557eae8 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/r_hand-2.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/r_leg-1.png b/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/r_leg-1.png new file mode 100644 index 0000000000..effbab2037 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/r_leg-1.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/r_leg-2.png b/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/r_leg-2.png new file mode 100644 index 0000000000..5e22421eb8 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/r_leg-2.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/torso-1.png b/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/torso-1.png new file mode 100644 index 0000000000..0e1480d698 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/torso-1.png differ diff --git a/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/torso-2.png b/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/torso-2.png new file mode 100644 index 0000000000..d6bffee42c Binary files /dev/null and b/Resources/Textures/Mobs/Customization/cyberlimbs/zenghu/zenghu_main.rsi/torso-2.png differ diff --git a/Resources/Textures/Mobs/Customization/eyes.rsi/meta.json b/Resources/Textures/Mobs/Customization/eyes.rsi/meta.json index cb94dfab3e..78339d7b76 100644 --- a/Resources/Textures/Mobs/Customization/eyes.rsi/meta.json +++ b/Resources/Textures/Mobs/Customization/eyes.rsi/meta.json @@ -1 +1 @@ -{"version": 1, "license": "CC-BY-SA-3.0","copyright": "Vox_eyes Taken from https://github.com/vgstation-coders/vgstation13 at 02ff588d59b3c560c685d9ca75e882d32a72d8cb and human_eyes taken from https://github.com/tgstation/tgstation/blob/8024397cc81c5f47f74cf4279e35728487d0a1a7/icons/mob/human_parts_greyscale.dmi and modified by DrSmugleaf", "size": {"x": 32, "y": 32}, "states": [{"name": "diona", "directions": 4}, {"name": "eyes", "directions": 4}, {"name":"no_eyes"},{"name": "vox_eyes_s", "directions": 4}]} +{"version": 1, "license": "CC-BY-SA-3.0","copyright": "human_eyes taken from https://github.com/tgstation/tgstation/blob/8024397cc81c5f47f74cf4279e35728487d0a1a7/icons/mob/human_parts_greyscale.dmi and modified by DrSmugleaf", "size": {"x": 32, "y": 32}, "states": [{"name": "diona", "directions": 4}, {"name": "eyes", "directions": 4}, {"name":"no_eyes"}]} diff --git a/Resources/Textures/Mobs/Customization/eyes.rsi/vox_eyes_s.png b/Resources/Textures/Mobs/Customization/eyes.rsi/vox_eyes_s.png deleted file mode 100644 index 807e9374c4..0000000000 Binary files a/Resources/Textures/Mobs/Customization/eyes.rsi/vox_eyes_s.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Customization/human_hair.rsi/meta.json b/Resources/Textures/Mobs/Customization/human_hair.rsi/meta.json index 5cb41c258b..8fd4f5e1b9 100644 --- a/Resources/Textures/Mobs/Customization/human_hair.rsi/meta.json +++ b/Resources/Textures/Mobs/Customization/human_hair.rsi/meta.json @@ -667,6 +667,10 @@ "name": "spikyponytail", "directions": 4 }, + { + "name": "spookylong", + "directions": 4 + }, { "name": "stail", "directions": 4 diff --git a/Resources/Textures/Mobs/Customization/human_hair.rsi/spookylong.png b/Resources/Textures/Mobs/Customization/human_hair.rsi/spookylong.png new file mode 100644 index 0000000000..4e79da275b Binary files /dev/null and b/Resources/Textures/Mobs/Customization/human_hair.rsi/spookylong.png differ diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_antenna.rsi/ipc_antenna_antlers.png b/Resources/Textures/Mobs/Customization/ipc_antenna.rsi/ipc_antenna_antlers.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_antenna.rsi/ipc_antenna_antlers.png rename to Resources/Textures/Mobs/Customization/ipc_antenna.rsi/ipc_antenna_antlers.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_antenna.rsi/ipc_antenna_crowned.png b/Resources/Textures/Mobs/Customization/ipc_antenna.rsi/ipc_antenna_crowned.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_antenna.rsi/ipc_antenna_crowned.png rename to Resources/Textures/Mobs/Customization/ipc_antenna.rsi/ipc_antenna_crowned.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_antenna.rsi/ipc_antenna_cyberhead.png b/Resources/Textures/Mobs/Customization/ipc_antenna.rsi/ipc_antenna_cyberhead.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_antenna.rsi/ipc_antenna_cyberhead.png rename to Resources/Textures/Mobs/Customization/ipc_antenna.rsi/ipc_antenna_cyberhead.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_antenna.rsi/ipc_antenna_droneeyes.png b/Resources/Textures/Mobs/Customization/ipc_antenna.rsi/ipc_antenna_droneeyes.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_antenna.rsi/ipc_antenna_droneeyes.png rename to Resources/Textures/Mobs/Customization/ipc_antenna.rsi/ipc_antenna_droneeyes.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_antenna.rsi/ipc_antenna_light.png b/Resources/Textures/Mobs/Customization/ipc_antenna.rsi/ipc_antenna_light.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_antenna.rsi/ipc_antenna_light.png rename to Resources/Textures/Mobs/Customization/ipc_antenna.rsi/ipc_antenna_light.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_antenna.rsi/ipc_antenna_lightb.png b/Resources/Textures/Mobs/Customization/ipc_antenna.rsi/ipc_antenna_lightb.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_antenna.rsi/ipc_antenna_lightb.png rename to Resources/Textures/Mobs/Customization/ipc_antenna.rsi/ipc_antenna_lightb.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_antenna.rsi/ipc_antenna_sidelights.png b/Resources/Textures/Mobs/Customization/ipc_antenna.rsi/ipc_antenna_sidelights.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_antenna.rsi/ipc_antenna_sidelights.png rename to Resources/Textures/Mobs/Customization/ipc_antenna.rsi/ipc_antenna_sidelights.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_antenna.rsi/ipc_antenna_tesla.png b/Resources/Textures/Mobs/Customization/ipc_antenna.rsi/ipc_antenna_tesla.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_antenna.rsi/ipc_antenna_tesla.png rename to Resources/Textures/Mobs/Customization/ipc_antenna.rsi/ipc_antenna_tesla.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_antenna.rsi/ipc_antenna_towers.png b/Resources/Textures/Mobs/Customization/ipc_antenna.rsi/ipc_antenna_towers.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_antenna.rsi/ipc_antenna_towers.png rename to Resources/Textures/Mobs/Customization/ipc_antenna.rsi/ipc_antenna_towers.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_antenna.rsi/ipc_antenna_tv.png b/Resources/Textures/Mobs/Customization/ipc_antenna.rsi/ipc_antenna_tv.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_antenna.rsi/ipc_antenna_tv.png rename to Resources/Textures/Mobs/Customization/ipc_antenna.rsi/ipc_antenna_tv.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_antenna.rsi/meta.json b/Resources/Textures/Mobs/Customization/ipc_antenna.rsi/meta.json similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_antenna.rsi/meta.json rename to Resources/Textures/Mobs/Customization/ipc_antenna.rsi/meta.json diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_blank.png b/Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_blank.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_blank.png rename to Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_blank.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_blue.png b/Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_blue.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_blue.png rename to Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_blue.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_breakout.png b/Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_breakout.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_breakout.png rename to Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_breakout.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_bsod.png b/Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_bsod.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_bsod.png rename to Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_bsod.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_console.png b/Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_console.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_console.png rename to Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_console.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_ecgwave.png b/Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_ecgwave.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_ecgwave.png rename to Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_ecgwave.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_eight.png b/Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_eight.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_eight.png rename to Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_eight.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_exclaim.png b/Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_exclaim.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_exclaim.png rename to Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_exclaim.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_eyes.png b/Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_eyes.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_eyes.png rename to Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_eyes.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_eyesangry.png b/Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_eyesangry.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_eyesangry.png rename to Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_eyesangry.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_eyestall.png b/Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_eyestall.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_eyestall.png rename to Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_eyestall.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_frown.png b/Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_frown.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_frown.png rename to Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_frown.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_glider.png b/Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_glider.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_glider.png rename to Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_glider.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_goggles.png b/Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_goggles.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_goggles.png rename to Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_goggles.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_heart.png b/Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_heart.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_heart.png rename to Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_heart.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_l.png b/Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_l.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_l.png rename to Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_l.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_loading.png b/Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_loading.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_loading.png rename to Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_loading.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_monoeye.png b/Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_monoeye.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_monoeye.png rename to Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_monoeye.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_nature.png b/Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_nature.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_nature.png rename to Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_nature.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_orange.png b/Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_orange.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_orange.png rename to Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_orange.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_pink.png b/Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_pink.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_pink.png rename to Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_pink.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_question.png b/Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_question.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_question.png rename to Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_question.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_rainbowdiag.png b/Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_rainbowdiag.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_rainbowdiag.png rename to Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_rainbowdiag.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_rainbowhoriz.png b/Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_rainbowhoriz.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_rainbowhoriz.png rename to Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_rainbowhoriz.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_redtext.png b/Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_redtext.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_redtext.png rename to Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_redtext.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_rgb.png b/Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_rgb.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_rgb.png rename to Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_rgb.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_ring.png b/Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_ring.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_ring.png rename to Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_ring.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_scroll.png b/Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_scroll.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_scroll.png rename to Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_scroll.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_shower.png b/Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_shower.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_shower.png rename to Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_shower.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_sinewave.png b/Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_sinewave.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_sinewave.png rename to Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_sinewave.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_smile.png b/Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_smile.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_smile.png rename to Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_smile.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_squarewave.png b/Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_squarewave.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_squarewave.png rename to Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_squarewave.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_stars.png b/Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_stars.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_stars.png rename to Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_stars.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_static.png b/Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_static.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_static.png rename to Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_static.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_tetris.png b/Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_tetris.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_tetris.png rename to Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_tetris.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_textdrop.png b/Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_textdrop.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_textdrop.png rename to Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_textdrop.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_tv.png b/Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_tv.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_tv.png rename to Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_tv.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_windowsxp.png b/Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_windowsxp.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_windowsxp.png rename to Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_windowsxp.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_yellow.png b/Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_yellow.png similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/ipc_screen_yellow.png rename to Resources/Textures/Mobs/Customization/ipc_screens.rsi/ipc_screen_yellow.png diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/meta.json b/Resources/Textures/Mobs/Customization/ipc_screens.rsi/meta.json similarity index 100% rename from Resources/Textures/Parkstation/Mobs/Customization/ipc_screens.rsi/meta.json rename to Resources/Textures/Mobs/Customization/ipc_screens.rsi/meta.json diff --git a/Resources/Textures/Mobs/Customization/kemonomimi-ears.rsi/bullishhorns.png b/Resources/Textures/Mobs/Customization/kemonomimi-ears.rsi/bullishhorns.png new file mode 100644 index 0000000000..cb80732656 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/kemonomimi-ears.rsi/bullishhorns.png differ diff --git a/Resources/Textures/Mobs/Customization/kemonomimi-ears.rsi/bunnyearstone1.png b/Resources/Textures/Mobs/Customization/kemonomimi-ears.rsi/bunnyearstone1.png new file mode 100644 index 0000000000..6a2f37f003 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/kemonomimi-ears.rsi/bunnyearstone1.png differ diff --git a/Resources/Textures/Mobs/Customization/kemonomimi-ears.rsi/bunnyearstone2.png b/Resources/Textures/Mobs/Customization/kemonomimi-ears.rsi/bunnyearstone2.png new file mode 100644 index 0000000000..2da5da89a0 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/kemonomimi-ears.rsi/bunnyearstone2.png differ diff --git a/Resources/Textures/Mobs/Customization/kemonomimi-ears.rsi/foxears.png b/Resources/Textures/Mobs/Customization/kemonomimi-ears.rsi/foxears.png new file mode 100644 index 0000000000..c37755fc3c Binary files /dev/null and b/Resources/Textures/Mobs/Customization/kemonomimi-ears.rsi/foxears.png differ diff --git a/Resources/Textures/Mobs/Customization/kemonomimi-ears.rsi/goathornstone1.png b/Resources/Textures/Mobs/Customization/kemonomimi-ears.rsi/goathornstone1.png new file mode 100644 index 0000000000..74a1bf1c7a Binary files /dev/null and b/Resources/Textures/Mobs/Customization/kemonomimi-ears.rsi/goathornstone1.png differ diff --git a/Resources/Textures/Mobs/Customization/kemonomimi-ears.rsi/goathornstone2.png b/Resources/Textures/Mobs/Customization/kemonomimi-ears.rsi/goathornstone2.png new file mode 100644 index 0000000000..c260f4e98d Binary files /dev/null and b/Resources/Textures/Mobs/Customization/kemonomimi-ears.rsi/goathornstone2.png differ diff --git a/Resources/Textures/Mobs/Customization/kemonomimi-ears.rsi/meta.json b/Resources/Textures/Mobs/Customization/kemonomimi-ears.rsi/meta.json new file mode 100644 index 0000000000..fcee2ae217 --- /dev/null +++ b/Resources/Textures/Mobs/Customization/kemonomimi-ears.rsi/meta.json @@ -0,0 +1,35 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Body parts by @Kilath", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "bullishhorns", + "directions": 4 + }, + { + "name": "bunnyearstone1", + "directions": 4 + }, + { + "name": "bunnyearstone2", + "directions": 4 + }, + { + "name": "foxears", + "directions": 4 + }, + { + "name": "goathornstone1", + "directions": 4 + }, + { + "name": "goathornstone2", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Mobs/Customization/kemonomimi-tails.rsi/bunnytail.png b/Resources/Textures/Mobs/Customization/kemonomimi-tails.rsi/bunnytail.png new file mode 100644 index 0000000000..220acb476f Binary files /dev/null and b/Resources/Textures/Mobs/Customization/kemonomimi-tails.rsi/bunnytail.png differ diff --git a/Resources/Textures/Mobs/Customization/kemonomimi-tails.rsi/doblefurtailtone1.png b/Resources/Textures/Mobs/Customization/kemonomimi-tails.rsi/doblefurtailtone1.png new file mode 100644 index 0000000000..88852ce600 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/kemonomimi-tails.rsi/doblefurtailtone1.png differ diff --git a/Resources/Textures/Mobs/Customization/kemonomimi-tails.rsi/doblefurtailtone2.png b/Resources/Textures/Mobs/Customization/kemonomimi-tails.rsi/doblefurtailtone2.png new file mode 100644 index 0000000000..138e64bcf8 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/kemonomimi-tails.rsi/doblefurtailtone2.png differ diff --git a/Resources/Textures/Mobs/Customization/kemonomimi-tails.rsi/fluffytail.png b/Resources/Textures/Mobs/Customization/kemonomimi-tails.rsi/fluffytail.png new file mode 100644 index 0000000000..227add91f3 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/kemonomimi-tails.rsi/fluffytail.png differ diff --git a/Resources/Textures/Mobs/Customization/kemonomimi-tails.rsi/foxninetailstone1.png b/Resources/Textures/Mobs/Customization/kemonomimi-tails.rsi/foxninetailstone1.png new file mode 100644 index 0000000000..4faf8ce5b5 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/kemonomimi-tails.rsi/foxninetailstone1.png differ diff --git a/Resources/Textures/Mobs/Customization/kemonomimi-tails.rsi/foxninetailstone2.png b/Resources/Textures/Mobs/Customization/kemonomimi-tails.rsi/foxninetailstone2.png new file mode 100644 index 0000000000..d391c48368 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/kemonomimi-tails.rsi/foxninetailstone2.png differ diff --git a/Resources/Textures/Mobs/Customization/kemonomimi-tails.rsi/foxthreetailstone1.png b/Resources/Textures/Mobs/Customization/kemonomimi-tails.rsi/foxthreetailstone1.png new file mode 100644 index 0000000000..68e03d1d9e Binary files /dev/null and b/Resources/Textures/Mobs/Customization/kemonomimi-tails.rsi/foxthreetailstone1.png differ diff --git a/Resources/Textures/Mobs/Customization/kemonomimi-tails.rsi/foxthreetailstone2.png b/Resources/Textures/Mobs/Customization/kemonomimi-tails.rsi/foxthreetailstone2.png new file mode 100644 index 0000000000..b309c9d628 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/kemonomimi-tails.rsi/foxthreetailstone2.png differ diff --git a/Resources/Textures/Mobs/Customization/kemonomimi-tails.rsi/foxtwotailstone1.png b/Resources/Textures/Mobs/Customization/kemonomimi-tails.rsi/foxtwotailstone1.png new file mode 100644 index 0000000000..a87f5316ca Binary files /dev/null and b/Resources/Textures/Mobs/Customization/kemonomimi-tails.rsi/foxtwotailstone1.png differ diff --git a/Resources/Textures/Mobs/Customization/kemonomimi-tails.rsi/foxtwotailstone2.png b/Resources/Textures/Mobs/Customization/kemonomimi-tails.rsi/foxtwotailstone2.png new file mode 100644 index 0000000000..07fc4877c8 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/kemonomimi-tails.rsi/foxtwotailstone2.png differ diff --git a/Resources/Textures/Mobs/Customization/kemonomimi-tails.rsi/horsetailculty.png b/Resources/Textures/Mobs/Customization/kemonomimi-tails.rsi/horsetailculty.png new file mode 100644 index 0000000000..b9e788df70 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/kemonomimi-tails.rsi/horsetailculty.png differ diff --git a/Resources/Textures/Mobs/Customization/kemonomimi-tails.rsi/horsetaillong.png b/Resources/Textures/Mobs/Customization/kemonomimi-tails.rsi/horsetaillong.png new file mode 100644 index 0000000000..4f67cbc7cd Binary files /dev/null and b/Resources/Textures/Mobs/Customization/kemonomimi-tails.rsi/horsetaillong.png differ diff --git a/Resources/Textures/Mobs/Customization/kemonomimi-tails.rsi/meta.json b/Resources/Textures/Mobs/Customization/kemonomimi-tails.rsi/meta.json new file mode 100644 index 0000000000..29005fb211 --- /dev/null +++ b/Resources/Textures/Mobs/Customization/kemonomimi-tails.rsi/meta.json @@ -0,0 +1,71 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Body parts by @Kilath", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "bunnytail", + "directions": 4 + }, + { + "name": "doblefurtailtone1", + "directions": 4 + }, + { + "name": "doblefurtailtone2", + "directions": 4 + }, + { + "name": "fluffytail", + "directions": 4 + }, + { + "name": "foxninetailstone1", + "directions": 4 + }, + { + "name": "foxninetailstone2", + "directions": 4 + }, + { + "name": "foxthreetailstone1", + "directions": 4 + }, + { + "name": "foxthreetailstone2", + "directions": 4 + }, + { + "name": "foxtwotailstone1", + "directions": 4 + }, + { + "name": "foxtwotailstone2", + "directions": 4 + }, + { + "name": "horsetailculty", + "directions": 4 + }, + { + "name": "horsetaillong", + "directions": 4 + }, + { + "name": "sharktail", + "directions": 4 + }, + { + "name": "tasseltailtone1", + "directions": 4 + }, + { + "name": "tasseltailtone2", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Mobs/Customization/kemonomimi-tails.rsi/sharktail.png b/Resources/Textures/Mobs/Customization/kemonomimi-tails.rsi/sharktail.png new file mode 100644 index 0000000000..9ebdb7bf79 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/kemonomimi-tails.rsi/sharktail.png differ diff --git a/Resources/Textures/Mobs/Customization/kemonomimi-tails.rsi/tasseltailtone1.png b/Resources/Textures/Mobs/Customization/kemonomimi-tails.rsi/tasseltailtone1.png new file mode 100644 index 0000000000..d45b14f99b Binary files /dev/null and b/Resources/Textures/Mobs/Customization/kemonomimi-tails.rsi/tasseltailtone1.png differ diff --git a/Resources/Textures/Mobs/Customization/kemonomimi-tails.rsi/tasseltailtone2.png b/Resources/Textures/Mobs/Customization/kemonomimi-tails.rsi/tasseltailtone2.png new file mode 100644 index 0000000000..edb0e539bd Binary files /dev/null and b/Resources/Textures/Mobs/Customization/kemonomimi-tails.rsi/tasseltailtone2.png differ diff --git a/Resources/Textures/Mobs/Customization/reptilian_parts.rsi/frills_axolotl.png b/Resources/Textures/Mobs/Customization/reptilian_parts.rsi/frills_axolotl.png index f7f54acdae..d61b60824c 100644 Binary files a/Resources/Textures/Mobs/Customization/reptilian_parts.rsi/frills_axolotl.png and b/Resources/Textures/Mobs/Customization/reptilian_parts.rsi/frills_axolotl.png differ diff --git a/Resources/Textures/Mobs/Customization/reptilian_parts.rsi/frills_neckfull.png b/Resources/Textures/Mobs/Customization/reptilian_parts.rsi/frills_neckfull.png new file mode 100644 index 0000000000..dfbe573096 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/reptilian_parts.rsi/frills_neckfull.png differ diff --git a/Resources/Textures/Mobs/Customization/reptilian_parts.rsi/meta.json b/Resources/Textures/Mobs/Customization/reptilian_parts.rsi/meta.json index 89a44c1cfd..1aaff3850e 100644 --- a/Resources/Textures/Mobs/Customization/reptilian_parts.rsi/meta.json +++ b/Resources/Textures/Mobs/Customization/reptilian_parts.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "https://github.com/Skyrat-SS13/Skyrat-tg/tree/40e3cdbb15b8bc0d5ef2fb46133adf805bda5297, while Argali, Ayrshire, Myrsore and Bighorn are drawn by Ubaser, and Kobold Ears are drawn by Pigeonpeas. Body_underbelly made by Nairod(github) for SS14. Large drawn by Ubaser.", + "copyright": "https://github.com/Skyrat-SS13/Skyrat-tg/tree/40e3cdbb15b8bc0d5ef2fb46133adf805bda5297, while Argali, Ayrshire, Myrsore and Bighorn are drawn by Ubaser, and Kobold Ears are drawn by Pigeonpeas. Body_underbelly made by Nairod(github) for SS14. Large drawn by Ubaser. Splotch modified from Sharp by KittenColony(github). Frills neckfull come from: https://github.com/Bubberstation/Bubberstation/commit/8bc6b83404803466a560b694bf22ef3c0ac266a2", "size": { "x": 32, "y": 32 @@ -185,19 +185,19 @@ "name": "frills_divinity", "directions": 4 }, - { + { "name": "horns_double", "directions": 4 }, - { + { "name": "frills_axolotl", "directions": 4 }, - { + { "name": "frills_hood_primary", "directions": 4 }, - { + { "name": "frills_hood_secondary", "directions": 4 }, @@ -208,58 +208,70 @@ { "name": "body_tiger", "directions": 4 - }, - { - "name": "head_tiger", - "directions": 4 - }, - { - "name": "l_arm_tiger", - "directions": 4 - }, - { - "name": "l_leg_tiger", - "directions": 4 - }, - { - "name": "r_arm_tiger", - "directions": 4 - }, - { - "name": "horns_argali", - "directions": 4 - }, - { - "name": "horns_ayrshire", - "directions": 4 - }, - { - "name": "horns_myrsore", - "directions": 4 - }, - { - "name": "horns_bighorn", - "directions": 4 - }, - { - "name": "horns_kobold_ears", - "directions": 4 - }, - { - "name": "r_leg_tiger", - "directions": 4 - }, - { - "name": "horns_floppy_kobold_ears", - "directions": 4 - }, + }, + { + "name": "head_tiger", + "directions": 4 + }, + { + "name": "l_arm_tiger", + "directions": 4 + }, + { + "name": "l_leg_tiger", + "directions": 4 + }, + { + "name": "r_arm_tiger", + "directions": 4 + }, + { + "name": "horns_argali", + "directions": 4 + }, + { + "name": "horns_ayrshire", + "directions": 4 + }, + { + "name": "horns_myrsore", + "directions": 4 + }, + { + "name": "horns_bighorn", + "directions": 4 + }, + { + "name": "horns_kobold_ears", + "directions": 4 + }, + { + "name": "r_leg_tiger", + "directions": 4 + }, + { + "name": "horns_floppy_kobold_ears", + "directions": 4 + }, { "name": "body_underbelly", "directions": 4 - }, - { - "name": "body_backspikes", - "directions": 4 - } + }, + { + "name": "body_backspikes", + "directions": 4 + }, + { + "name": "snout_splotch_primary", + "directions": 4 + }, + { + "name": "snout_splotch_secondary", + "directions": 4 + }, + { + "name": "frills_neckfull", + "directions": 4 + } ] } diff --git a/Resources/Textures/Mobs/Customization/reptilian_parts.rsi/snout_splotch_primary.png b/Resources/Textures/Mobs/Customization/reptilian_parts.rsi/snout_splotch_primary.png new file mode 100644 index 0000000000..850b985e5d Binary files /dev/null and b/Resources/Textures/Mobs/Customization/reptilian_parts.rsi/snout_splotch_primary.png differ diff --git a/Resources/Textures/Mobs/Customization/reptilian_parts.rsi/snout_splotch_secondary.png b/Resources/Textures/Mobs/Customization/reptilian_parts.rsi/snout_splotch_secondary.png new file mode 100644 index 0000000000..5a982ed354 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/reptilian_parts.rsi/snout_splotch_secondary.png differ diff --git a/Resources/Textures/Mobs/Customization/vox_facial_hair.rsi/beard_s.png b/Resources/Textures/Mobs/Customization/vox_facial_hair.rsi/beard_s.png new file mode 100644 index 0000000000..92aeea8f36 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/vox_facial_hair.rsi/beard_s.png differ diff --git a/Resources/Textures/Mobs/Customization/vox_facial_hair.rsi/colonel_s.png b/Resources/Textures/Mobs/Customization/vox_facial_hair.rsi/colonel_s.png new file mode 100644 index 0000000000..2e657d675a Binary files /dev/null and b/Resources/Textures/Mobs/Customization/vox_facial_hair.rsi/colonel_s.png differ diff --git a/Resources/Textures/Mobs/Customization/vox_facial_hair.rsi/fu_s.png b/Resources/Textures/Mobs/Customization/vox_facial_hair.rsi/fu_s.png new file mode 100644 index 0000000000..87f97eeaee Binary files /dev/null and b/Resources/Textures/Mobs/Customization/vox_facial_hair.rsi/fu_s.png differ diff --git a/Resources/Textures/Mobs/Customization/vox_facial_hair.rsi/mane_s.png b/Resources/Textures/Mobs/Customization/vox_facial_hair.rsi/mane_s.png new file mode 100644 index 0000000000..08b120f500 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/vox_facial_hair.rsi/mane_s.png differ diff --git a/Resources/Textures/Mobs/Customization/vox_facial_hair.rsi/meta.json b/Resources/Textures/Mobs/Customization/vox_facial_hair.rsi/meta.json index c52ad8a2a2..64eebb8883 100644 --- a/Resources/Textures/Mobs/Customization/vox_facial_hair.rsi/meta.json +++ b/Resources/Textures/Mobs/Customization/vox_facial_hair.rsi/meta.json @@ -1 +1,31 @@ -{"version": 1, "size": {"x": 32, "y": 32}, "license": "CC-BY-SA-3.0", "copyright": "https://github.com/vgstation-coders/vgstation13/blob/02ff588d59b3c560c685d9ca75e882d32a72d8cb/icons/mob/human_face.dmi", "states": [{"name": "vox_beard_s", "directions": 4}, {"name": "vox_colonel_s", "directions": 4}, {"name": "vox_fu_s", "directions": 4}, {"name": "vox_neck_s", "directions": 4}, {"name": "vox_ruff_beard_s", "directions": 4}, {"name": "vox_ruff_beard_s2", "directions": 4}]} +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from Paradise at https://github.com/ParadiseSS13/Paradise/blob/0f9ef5962f4422836c0a42f289fb24d018918cbc/icons/mob/sprite_accessories/vox/vox_facial_hair.dmi and greyscaled", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "colonel_s", + "directions": 4 + }, + { + "name": "fu_s", + "directions": 4 + }, + { + "name": "neck_s", + "directions": 4 + }, + { + "name": "beard_s", + "directions": 4 + }, + { + "name": "mane_s", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Mobs/Customization/vox_facial_hair.rsi/neck_s.png b/Resources/Textures/Mobs/Customization/vox_facial_hair.rsi/neck_s.png new file mode 100644 index 0000000000..f630e8ce8b Binary files /dev/null and b/Resources/Textures/Mobs/Customization/vox_facial_hair.rsi/neck_s.png differ diff --git a/Resources/Textures/Mobs/Customization/vox_facial_hair.rsi/vox_beard_s.png b/Resources/Textures/Mobs/Customization/vox_facial_hair.rsi/vox_beard_s.png deleted file mode 100644 index 8e922e58de..0000000000 Binary files a/Resources/Textures/Mobs/Customization/vox_facial_hair.rsi/vox_beard_s.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Customization/vox_facial_hair.rsi/vox_colonel_s.png b/Resources/Textures/Mobs/Customization/vox_facial_hair.rsi/vox_colonel_s.png deleted file mode 100644 index 8de4dd6803..0000000000 Binary files a/Resources/Textures/Mobs/Customization/vox_facial_hair.rsi/vox_colonel_s.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Customization/vox_facial_hair.rsi/vox_fu_s.png b/Resources/Textures/Mobs/Customization/vox_facial_hair.rsi/vox_fu_s.png deleted file mode 100644 index e49e84baf0..0000000000 Binary files a/Resources/Textures/Mobs/Customization/vox_facial_hair.rsi/vox_fu_s.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Customization/vox_facial_hair.rsi/vox_neck_s.png b/Resources/Textures/Mobs/Customization/vox_facial_hair.rsi/vox_neck_s.png deleted file mode 100644 index 9009717cee..0000000000 Binary files a/Resources/Textures/Mobs/Customization/vox_facial_hair.rsi/vox_neck_s.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Customization/vox_facial_hair.rsi/vox_ruff_beard_s.png b/Resources/Textures/Mobs/Customization/vox_facial_hair.rsi/vox_ruff_beard_s.png deleted file mode 100644 index 365f134c44..0000000000 Binary files a/Resources/Textures/Mobs/Customization/vox_facial_hair.rsi/vox_ruff_beard_s.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Customization/vox_facial_hair.rsi/vox_ruff_beard_s2.png b/Resources/Textures/Mobs/Customization/vox_facial_hair.rsi/vox_ruff_beard_s2.png deleted file mode 100644 index 0858c19f05..0000000000 Binary files a/Resources/Textures/Mobs/Customization/vox_facial_hair.rsi/vox_ruff_beard_s2.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/afro_s.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/afro_s.png new file mode 100644 index 0000000000..74b09a0635 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/vox_hair.rsi/afro_s.png differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/braid_s.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/braid_s.png new file mode 100644 index 0000000000..ff2aa4acbc Binary files /dev/null and b/Resources/Textures/Mobs/Customization/vox_hair.rsi/braid_s.png differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/crestedquills_s.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/crestedquills_s.png new file mode 100644 index 0000000000..f089905438 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/vox_hair.rsi/crestedquills_s.png differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/emperorquills_s.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/emperorquills_s.png new file mode 100644 index 0000000000..899918e694 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/vox_hair.rsi/emperorquills_s.png differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/flowing_s.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/flowing_s.png new file mode 100644 index 0000000000..98139350d4 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/vox_hair.rsi/flowing_s.png differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/hawk_s.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/hawk_s.png new file mode 100644 index 0000000000..c262fc1d91 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/vox_hair.rsi/hawk_s.png differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/horns_s.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/horns_s.png new file mode 100644 index 0000000000..8891151c9f Binary files /dev/null and b/Resources/Textures/Mobs/Customization/vox_hair.rsi/horns_s.png differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/keelquills_s.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/keelquills_s.png new file mode 100644 index 0000000000..c3c947bcce Binary files /dev/null and b/Resources/Textures/Mobs/Customization/vox_hair.rsi/keelquills_s.png differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/keetquills_s.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/keetquills_s.png new file mode 100644 index 0000000000..910542dde1 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/vox_hair.rsi/keetquills_s.png differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/kingly_s.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/kingly_s.png new file mode 100644 index 0000000000..1d0a1d8d89 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/vox_hair.rsi/kingly_s.png differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/long_braid_s.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/long_braid_s.png new file mode 100644 index 0000000000..3df31052db Binary files /dev/null and b/Resources/Textures/Mobs/Customization/vox_hair.rsi/long_braid_s.png differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/mange_s.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/mange_s.png new file mode 100644 index 0000000000..afa934e880 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/vox_hair.rsi/mange_s.png differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/meta.json b/Resources/Textures/Mobs/Customization/vox_hair.rsi/meta.json index 2f14178424..9b08260bd6 100644 --- a/Resources/Textures/Mobs/Customization/vox_hair.rsi/meta.json +++ b/Resources/Textures/Mobs/Customization/vox_hair.rsi/meta.json @@ -1 +1,99 @@ -{"version": 1, "size": {"x": 32, "y": 32}, "license": "CC-BY-SA-3.0", "copyright": "https://github.com/vgstation-coders/vgstation13/blob/02ff588d59b3c560c685d9ca75e882d32a72d8cb/icons/mob/human_face.dmi", "states": [{"name": "vox_afro_s", "directions": 4}, {"name": "vox_afro_s2", "directions": 4}, {"name": "vox_bald_s", "directions": 4}, {"name": "vox_cropped_s", "directions": 4}, {"name": "vox_cropped_s2", "directions": 4}, {"name": "vox_horns_s", "directions": 4}, {"name": "vox_horns_s2", "directions": 4}, {"name": "vox_kingly_s", "directions": 4}, {"name": "vox_kingly_s2", "directions": 4}, {"name": "vox_mange_s", "directions": 4}, {"name": "vox_mange_s2", "directions": 4}, {"name": "vox_mohawk_s", "directions": 4}, {"name": "vox_mohawk_s2", "directions": 4}, {"name": "vox_nights_s", "directions": 4}, {"name": "vox_nights_s2", "directions": 4}, {"name": "vox_pony_s", "directions": 4}, {"name": "vox_pony_s2", "directions": 4}, {"name": "vox_rows_s", "directions": 4}, {"name": "vox_rows_s2", "directions": 4}, {"name": "vox_ruff_hawk_s", "directions": 4}, {"name": "vox_ruff_hawk_s2", "directions": 4}, {"name": "vox_shortquills_s", "directions": 4}, {"name": "vox_shortquills_s2", "directions": 4}, {"name": "vox_surf_s", "directions": 4}, {"name": "vox_surf_s2", "directions": 4}, {"name": "vox_yasu_s", "directions": 4}, {"name": "vox_yasu_s2", "directions": 4}]} +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from Paradise at https://github.com/ParadiseSS13/Paradise/blob/dcd1f5d88a8c5ba9634fc3fce67a76ada45f71dc/icons/mob/sprite_accessories/vox/vox_hair.dmi and greyscaled", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "crestedquills_s", + "directions": 4 + }, + { + "name": "emperorquills_s", + "directions": 4 + }, + { + "name": "keelquills_s", + "directions": 4 + }, + { + "name": "keetquills_s", + "directions": 4 + }, + { + "name": "shortquills_s", + "directions": 4 + }, + { + "name": "tielquills_s", + "directions": 4 + }, + { + "name": "kingly_s", + "directions": 4 + }, + { + "name": "afro_s", + "directions": 4 + }, + { + "name": "yasu_s", + "directions": 4 + }, + { + "name": "razor_s", + "directions": 4 + }, + { + "name": "razor_clipped_s", + "directions": 4 + }, + { + "name": "mohawk_s", + "directions": 4 + }, + { + "name": "horns_s", + "directions": 4 + }, + { + "name": "nights_s", + "directions": 4 + }, + { + "name": "hawk_s", + "directions": 4 + }, + { + "name": "long_braid_s", + "directions": 4 + }, + { + "name": "short_braid_s", + "directions": 4 + }, + { + "name": "mange_s", + "directions": 4 + }, + { + "name": "ponytail_s", + "directions": 4 + }, + { + "name": "braid_s", + "directions": 4 + }, + { + "name": "surf_s", + "directions": 4 + }, + { + "name": "flowing_s", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/mohawk_s.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/mohawk_s.png new file mode 100644 index 0000000000..b8466620b8 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/vox_hair.rsi/mohawk_s.png differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/nights_s.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/nights_s.png new file mode 100644 index 0000000000..68d2654dcd Binary files /dev/null and b/Resources/Textures/Mobs/Customization/vox_hair.rsi/nights_s.png differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/ponytail_s.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/ponytail_s.png new file mode 100644 index 0000000000..316f08fe9a Binary files /dev/null and b/Resources/Textures/Mobs/Customization/vox_hair.rsi/ponytail_s.png differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/razor_clipped_s.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/razor_clipped_s.png new file mode 100644 index 0000000000..e5ffe57daa Binary files /dev/null and b/Resources/Textures/Mobs/Customization/vox_hair.rsi/razor_clipped_s.png differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/razor_s.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/razor_s.png new file mode 100644 index 0000000000..0b57cec2c1 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/vox_hair.rsi/razor_s.png differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/short_braid_s.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/short_braid_s.png new file mode 100644 index 0000000000..6f7a1e86ca Binary files /dev/null and b/Resources/Textures/Mobs/Customization/vox_hair.rsi/short_braid_s.png differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/shortquills_s.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/shortquills_s.png new file mode 100644 index 0000000000..a0d496aeed Binary files /dev/null and b/Resources/Textures/Mobs/Customization/vox_hair.rsi/shortquills_s.png differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/surf_s.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/surf_s.png new file mode 100644 index 0000000000..712ccb542f Binary files /dev/null and b/Resources/Textures/Mobs/Customization/vox_hair.rsi/surf_s.png differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/tielquills_s.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/tielquills_s.png new file mode 100644 index 0000000000..aa121bd331 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/vox_hair.rsi/tielquills_s.png differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_afro_s.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_afro_s.png deleted file mode 100644 index 60b9b18c21..0000000000 Binary files a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_afro_s.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_afro_s2.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_afro_s2.png deleted file mode 100644 index 0858c19f05..0000000000 Binary files a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_afro_s2.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_bald_s.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_bald_s.png deleted file mode 100644 index 0858c19f05..0000000000 Binary files a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_bald_s.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_cropped_s.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_cropped_s.png deleted file mode 100644 index 67574355ee..0000000000 Binary files a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_cropped_s.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_cropped_s2.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_cropped_s2.png deleted file mode 100644 index 0858c19f05..0000000000 Binary files a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_cropped_s2.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_horns_s.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_horns_s.png deleted file mode 100644 index dbb4ae8f28..0000000000 Binary files a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_horns_s.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_horns_s2.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_horns_s2.png deleted file mode 100644 index 0858c19f05..0000000000 Binary files a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_horns_s2.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_kingly_s.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_kingly_s.png deleted file mode 100644 index 3f35429e14..0000000000 Binary files a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_kingly_s.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_kingly_s2.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_kingly_s2.png deleted file mode 100644 index 0858c19f05..0000000000 Binary files a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_kingly_s2.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_mange_s.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_mange_s.png deleted file mode 100644 index 138c58e690..0000000000 Binary files a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_mange_s.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_mange_s2.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_mange_s2.png deleted file mode 100644 index 0858c19f05..0000000000 Binary files a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_mange_s2.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_mohawk_s.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_mohawk_s.png deleted file mode 100644 index 0a5589b0ba..0000000000 Binary files a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_mohawk_s.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_mohawk_s2.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_mohawk_s2.png deleted file mode 100644 index 0858c19f05..0000000000 Binary files a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_mohawk_s2.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_nights_s.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_nights_s.png deleted file mode 100644 index 0a587eafce..0000000000 Binary files a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_nights_s.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_nights_s2.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_nights_s2.png deleted file mode 100644 index 0858c19f05..0000000000 Binary files a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_nights_s2.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_pony_s.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_pony_s.png deleted file mode 100644 index a9ff211389..0000000000 Binary files a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_pony_s.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_pony_s2.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_pony_s2.png deleted file mode 100644 index 0858c19f05..0000000000 Binary files a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_pony_s2.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_rows_s.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_rows_s.png deleted file mode 100644 index cee8b9a103..0000000000 Binary files a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_rows_s.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_rows_s2.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_rows_s2.png deleted file mode 100644 index 0858c19f05..0000000000 Binary files a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_rows_s2.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_ruff_hawk_s.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_ruff_hawk_s.png deleted file mode 100644 index 41c3cb6dec..0000000000 Binary files a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_ruff_hawk_s.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_ruff_hawk_s2.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_ruff_hawk_s2.png deleted file mode 100644 index 0858c19f05..0000000000 Binary files a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_ruff_hawk_s2.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_shortquills_s.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_shortquills_s.png deleted file mode 100644 index c83ef673a5..0000000000 Binary files a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_shortquills_s.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_shortquills_s2.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_shortquills_s2.png deleted file mode 100644 index 0858c19f05..0000000000 Binary files a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_shortquills_s2.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_surf_s.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_surf_s.png deleted file mode 100644 index df2191cacd..0000000000 Binary files a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_surf_s.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_surf_s2.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_surf_s2.png deleted file mode 100644 index 0858c19f05..0000000000 Binary files a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_surf_s2.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_yasu_s.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_yasu_s.png deleted file mode 100644 index 6461305e85..0000000000 Binary files a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_yasu_s.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_yasu_s2.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_yasu_s2.png deleted file mode 100644 index 0858c19f05..0000000000 Binary files a/Resources/Textures/Mobs/Customization/vox_hair.rsi/vox_yasu_s2.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Customization/vox_hair.rsi/yasu_s.png b/Resources/Textures/Mobs/Customization/vox_hair.rsi/yasu_s.png new file mode 100644 index 0000000000..6ee3de1dad Binary files /dev/null and b/Resources/Textures/Mobs/Customization/vox_hair.rsi/yasu_s.png differ diff --git a/Resources/Textures/Mobs/Customization/vox_parts.rsi/beak.png b/Resources/Textures/Mobs/Customization/vox_parts.rsi/beak.png new file mode 100644 index 0000000000..23744679b6 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/vox_parts.rsi/beak.png differ diff --git a/Resources/Textures/Mobs/Customization/vox_parts.rsi/l_arm.png b/Resources/Textures/Mobs/Customization/vox_parts.rsi/l_arm.png new file mode 100644 index 0000000000..e35fb3c6bf Binary files /dev/null and b/Resources/Textures/Mobs/Customization/vox_parts.rsi/l_arm.png differ diff --git a/Resources/Textures/Mobs/Customization/vox_parts.rsi/l_foot.png b/Resources/Textures/Mobs/Customization/vox_parts.rsi/l_foot.png new file mode 100644 index 0000000000..5ab81e3616 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/vox_parts.rsi/l_foot.png differ diff --git a/Resources/Textures/Mobs/Customization/vox_parts.rsi/l_hand.png b/Resources/Textures/Mobs/Customization/vox_parts.rsi/l_hand.png new file mode 100644 index 0000000000..660da8a5d1 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/vox_parts.rsi/l_hand.png differ diff --git a/Resources/Textures/Mobs/Customization/vox_parts.rsi/l_leg.png b/Resources/Textures/Mobs/Customization/vox_parts.rsi/l_leg.png new file mode 100644 index 0000000000..1a81369838 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/vox_parts.rsi/l_leg.png differ diff --git a/Resources/Textures/Mobs/Customization/vox_parts.rsi/meta.json b/Resources/Textures/Mobs/Customization/vox_parts.rsi/meta.json new file mode 100644 index 0000000000..fd5c14b6a3 --- /dev/null +++ b/Resources/Textures/Mobs/Customization/vox_parts.rsi/meta.json @@ -0,0 +1,55 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from https://github.com/vgstation-coders/vgstation13 at 02ff588d59b3c560c685d9ca75e882d32a72d8cb, modified by Bhijn, Errant and Flareguy", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "beak", + "directions": 4 + }, + { + "name": "l_arm", + "directions": 4 + }, + { + "name": "l_foot", + "directions": 4 + }, + { + "name": "l_hand", + "directions": 4 + }, + { + "name": "l_leg", + "directions": 4 + }, + { + "name": "r_arm", + "directions": 4 + }, + { + "name": "r_foot", + "directions": 4 + }, + { + "name": "r_hand", + "directions": 4 + }, + { + "name": "r_leg", + "directions": 4 + }, + { + "name": "tail", + "directions": 4 + }, + { + "name": "tail_stenciled", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Mobs/Customization/vox_parts.rsi/r_arm.png b/Resources/Textures/Mobs/Customization/vox_parts.rsi/r_arm.png new file mode 100644 index 0000000000..c8c70752f4 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/vox_parts.rsi/r_arm.png differ diff --git a/Resources/Textures/Mobs/Customization/vox_parts.rsi/r_foot.png b/Resources/Textures/Mobs/Customization/vox_parts.rsi/r_foot.png new file mode 100644 index 0000000000..58dbe90b09 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/vox_parts.rsi/r_foot.png differ diff --git a/Resources/Textures/Mobs/Customization/vox_parts.rsi/r_hand.png b/Resources/Textures/Mobs/Customization/vox_parts.rsi/r_hand.png new file mode 100644 index 0000000000..e433456bf2 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/vox_parts.rsi/r_hand.png differ diff --git a/Resources/Textures/Mobs/Customization/vox_parts.rsi/r_leg.png b/Resources/Textures/Mobs/Customization/vox_parts.rsi/r_leg.png new file mode 100644 index 0000000000..d616753129 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/vox_parts.rsi/r_leg.png differ diff --git a/Resources/Textures/Mobs/Customization/vox_parts.rsi/tail.png b/Resources/Textures/Mobs/Customization/vox_parts.rsi/tail.png new file mode 100644 index 0000000000..0e63d3327b Binary files /dev/null and b/Resources/Textures/Mobs/Customization/vox_parts.rsi/tail.png differ diff --git a/Resources/Textures/Mobs/Customization/vox_parts.rsi/tail_stenciled.png b/Resources/Textures/Mobs/Customization/vox_parts.rsi/tail_stenciled.png new file mode 100644 index 0000000000..50627ac522 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/vox_parts.rsi/tail_stenciled.png differ diff --git a/Resources/Textures/Mobs/Customization/vox_tattoos.rsi/heart_l_arm.png b/Resources/Textures/Mobs/Customization/vox_tattoos.rsi/heart_l_arm.png new file mode 100644 index 0000000000..0ff82bbaf2 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/vox_tattoos.rsi/heart_l_arm.png differ diff --git a/Resources/Textures/Mobs/Customization/vox_tattoos.rsi/heart_r_arm.png b/Resources/Textures/Mobs/Customization/vox_tattoos.rsi/heart_r_arm.png new file mode 100644 index 0000000000..774c96692c Binary files /dev/null and b/Resources/Textures/Mobs/Customization/vox_tattoos.rsi/heart_r_arm.png differ diff --git a/Resources/Textures/Mobs/Customization/vox_tattoos.rsi/hive_s.png b/Resources/Textures/Mobs/Customization/vox_tattoos.rsi/hive_s.png new file mode 100644 index 0000000000..8361f55864 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/vox_tattoos.rsi/hive_s.png differ diff --git a/Resources/Textures/Mobs/Customization/vox_tattoos.rsi/meta.json b/Resources/Textures/Mobs/Customization/vox_tattoos.rsi/meta.json new file mode 100644 index 0000000000..725fbb6a0d --- /dev/null +++ b/Resources/Textures/Mobs/Customization/vox_tattoos.rsi/meta.json @@ -0,0 +1,27 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from Paradise at https://github.com/ParadiseSS13/Paradise/blob/ef7a4d962915cb36b138eeb59663f0053d4906fe/icons/mob/sprite_accessories/vox/vox_body_markings.dmi and modified by Flareguy", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "heart_l_arm", + "directions": 4 + }, + { + "name": "heart_r_arm", + "directions": 4 + }, + { + "name": "hive_s", + "directions": 4 + }, + { + "name": "nightling_s", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Mobs/Customization/vox_tattoos.rsi/nightling_s.png b/Resources/Textures/Mobs/Customization/vox_tattoos.rsi/nightling_s.png new file mode 100644 index 0000000000..72b0b30fd5 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/vox_tattoos.rsi/nightling_s.png differ diff --git a/Resources/Textures/Mobs/Demons/glimmer_wisp.rsi/meta.json b/Resources/Textures/Mobs/Demons/glimmer_wisp.rsi/meta.json new file mode 100644 index 0000000000..4f2cce97e1 --- /dev/null +++ b/Resources/Textures/Mobs/Demons/glimmer_wisp.rsi/meta.json @@ -0,0 +1,14 @@ +{ + "version": 1, + "license": "CC-BY-SA-4.0", + "copyright": "Created by @Vordenburg (github) for Nyanotrasen", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "willowisp" + } + ] +} diff --git a/Resources/Textures/Mobs/Demons/glimmer_wisp.rsi/willowisp.png b/Resources/Textures/Mobs/Demons/glimmer_wisp.rsi/willowisp.png new file mode 100644 index 0000000000..6ee16245ed Binary files /dev/null and b/Resources/Textures/Mobs/Demons/glimmer_wisp.rsi/willowisp.png differ diff --git a/Resources/Textures/Mobs/Demons/imp.rsi/imp.png b/Resources/Textures/Mobs/Demons/imp.rsi/imp.png new file mode 100644 index 0000000000..575c223ee8 Binary files /dev/null and b/Resources/Textures/Mobs/Demons/imp.rsi/imp.png differ diff --git a/Resources/Textures/Mobs/Demons/imp.rsi/meta.json b/Resources/Textures/Mobs/Demons/imp.rsi/meta.json new file mode 100644 index 0000000000..98f33679c7 --- /dev/null +++ b/Resources/Textures/Mobs/Demons/imp.rsi/meta.json @@ -0,0 +1,15 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Created by ghost581(Discord)", + "states": [ + { + "name": "imp", + "delays": [[0.3, 0.3, 0.3, 0.3]] + } + ] +} diff --git a/Resources/Textures/Mobs/Pets/pitbull.rsi/meta.json b/Resources/Textures/Mobs/Pets/pitbull.rsi/meta.json new file mode 100644 index 0000000000..86ff0bf07d --- /dev/null +++ b/Resources/Textures/Mobs/Pets/pitbull.rsi/meta.json @@ -0,0 +1,22 @@ +{ + "version": 1, + "size": + { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Hyenh#6078 (313846233099927552)", + "states": + [ + { + "name": "pibble", + "directions": 4 + }, + + { + "name": "pibble_dead", + "directions": 1 + } + ] +} diff --git a/Resources/Textures/Mobs/Pets/pitbull.rsi/pibble.png b/Resources/Textures/Mobs/Pets/pitbull.rsi/pibble.png new file mode 100644 index 0000000000..8644989d93 Binary files /dev/null and b/Resources/Textures/Mobs/Pets/pitbull.rsi/pibble.png differ diff --git a/Resources/Textures/Mobs/Pets/pitbull.rsi/pibble_dead.png b/Resources/Textures/Mobs/Pets/pitbull.rsi/pibble_dead.png new file mode 100644 index 0000000000..2054aea3e5 Binary files /dev/null and b/Resources/Textures/Mobs/Pets/pitbull.rsi/pibble_dead.png differ diff --git a/Resources/Textures/Mobs/Pets/ventbull.rsi/meta.json b/Resources/Textures/Mobs/Pets/ventbull.rsi/meta.json new file mode 100644 index 0000000000..86ff0bf07d --- /dev/null +++ b/Resources/Textures/Mobs/Pets/ventbull.rsi/meta.json @@ -0,0 +1,22 @@ +{ + "version": 1, + "size": + { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Hyenh#6078 (313846233099927552)", + "states": + [ + { + "name": "pibble", + "directions": 4 + }, + + { + "name": "pibble_dead", + "directions": 1 + } + ] +} diff --git a/Resources/Textures/Mobs/Pets/ventbull.rsi/pibble.png b/Resources/Textures/Mobs/Pets/ventbull.rsi/pibble.png new file mode 100644 index 0000000000..57b5a3d2b5 Binary files /dev/null and b/Resources/Textures/Mobs/Pets/ventbull.rsi/pibble.png differ diff --git a/Resources/Textures/Mobs/Pets/ventbull.rsi/pibble_dead.png b/Resources/Textures/Mobs/Pets/ventbull.rsi/pibble_dead.png new file mode 100644 index 0000000000..5482582a18 Binary files /dev/null and b/Resources/Textures/Mobs/Pets/ventbull.rsi/pibble_dead.png differ diff --git a/Resources/Textures/Mobs/Species/IPC/organs.rsi/ears.png b/Resources/Textures/Mobs/Species/IPC/organs.rsi/ears.png new file mode 100644 index 0000000000..9966cc2ac2 Binary files /dev/null and b/Resources/Textures/Mobs/Species/IPC/organs.rsi/ears.png differ diff --git a/Resources/Textures/Mobs/Species/IPC/organs.rsi/eyeball-l.png b/Resources/Textures/Mobs/Species/IPC/organs.rsi/eyeball-l.png new file mode 100644 index 0000000000..09b98e316f Binary files /dev/null and b/Resources/Textures/Mobs/Species/IPC/organs.rsi/eyeball-l.png differ diff --git a/Resources/Textures/Mobs/Species/IPC/organs.rsi/eyeball-r.png b/Resources/Textures/Mobs/Species/IPC/organs.rsi/eyeball-r.png new file mode 100644 index 0000000000..f1ff37a002 Binary files /dev/null and b/Resources/Textures/Mobs/Species/IPC/organs.rsi/eyeball-r.png differ diff --git a/Resources/Textures/Mobs/Species/IPC/organs.rsi/heart-off.png b/Resources/Textures/Mobs/Species/IPC/organs.rsi/heart-off.png new file mode 100644 index 0000000000..7dda0d3a8e Binary files /dev/null and b/Resources/Textures/Mobs/Species/IPC/organs.rsi/heart-off.png differ diff --git a/Resources/Textures/Mobs/Species/IPC/organs.rsi/heart-on.png b/Resources/Textures/Mobs/Species/IPC/organs.rsi/heart-on.png new file mode 100644 index 0000000000..676a641989 Binary files /dev/null and b/Resources/Textures/Mobs/Species/IPC/organs.rsi/heart-on.png differ diff --git a/Resources/Textures/Mobs/Species/IPC/organs.rsi/meta.json b/Resources/Textures/Mobs/Species/IPC/organs.rsi/meta.json new file mode 100644 index 0000000000..d6b1b51038 --- /dev/null +++ b/Resources/Textures/Mobs/Species/IPC/organs.rsi/meta.json @@ -0,0 +1,47 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from Yogstation", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "heart-off" + }, + { + "name": "heart-on", + "delays": [ + [ + 0.6, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + }, + { + "name": "eyeball-r" + }, + { + "name": "tongue" + }, + { + "name": "eyeball-l" + }, + { + "name": "microcell", + "delays": [ + [ + 0.5, + 0.5 + ] + ] + }, + { + "name": "ears" + } + ] +} diff --git a/Resources/Textures/Mobs/Species/IPC/organs.rsi/microcell.png b/Resources/Textures/Mobs/Species/IPC/organs.rsi/microcell.png new file mode 100644 index 0000000000..18b692a5a9 Binary files /dev/null and b/Resources/Textures/Mobs/Species/IPC/organs.rsi/microcell.png differ diff --git a/Resources/Textures/Mobs/Species/IPC/organs.rsi/tongue.png b/Resources/Textures/Mobs/Species/IPC/organs.rsi/tongue.png new file mode 100644 index 0000000000..dee2ed3b99 Binary files /dev/null and b/Resources/Textures/Mobs/Species/IPC/organs.rsi/tongue.png differ diff --git a/Resources/Textures/Mobs/Species/IPC/parts.rsi/full.png b/Resources/Textures/Mobs/Species/IPC/parts.rsi/full.png new file mode 100644 index 0000000000..7faae4a077 Binary files /dev/null and b/Resources/Textures/Mobs/Species/IPC/parts.rsi/full.png differ diff --git a/Resources/Textures/Mobs/Species/IPC/parts.rsi/head_f.png b/Resources/Textures/Mobs/Species/IPC/parts.rsi/head_f.png new file mode 100644 index 0000000000..31d77176c9 Binary files /dev/null and b/Resources/Textures/Mobs/Species/IPC/parts.rsi/head_f.png differ diff --git a/Resources/Textures/Mobs/Species/IPC/parts.rsi/head_m.png b/Resources/Textures/Mobs/Species/IPC/parts.rsi/head_m.png new file mode 100644 index 0000000000..53d6069a28 Binary files /dev/null and b/Resources/Textures/Mobs/Species/IPC/parts.rsi/head_m.png differ diff --git a/Resources/Textures/Mobs/Species/IPC/parts.rsi/l_arm.png b/Resources/Textures/Mobs/Species/IPC/parts.rsi/l_arm.png new file mode 100644 index 0000000000..4f042bf40e Binary files /dev/null and b/Resources/Textures/Mobs/Species/IPC/parts.rsi/l_arm.png differ diff --git a/Resources/Textures/Mobs/Species/IPC/parts.rsi/l_foot.png b/Resources/Textures/Mobs/Species/IPC/parts.rsi/l_foot.png new file mode 100644 index 0000000000..bb9bede218 Binary files /dev/null and b/Resources/Textures/Mobs/Species/IPC/parts.rsi/l_foot.png differ diff --git a/Resources/Textures/Mobs/Species/IPC/parts.rsi/l_hand.png b/Resources/Textures/Mobs/Species/IPC/parts.rsi/l_hand.png new file mode 100644 index 0000000000..5b6c2df090 Binary files /dev/null and b/Resources/Textures/Mobs/Species/IPC/parts.rsi/l_hand.png differ diff --git a/Resources/Textures/Mobs/Species/IPC/parts.rsi/l_leg.png b/Resources/Textures/Mobs/Species/IPC/parts.rsi/l_leg.png new file mode 100644 index 0000000000..788f2769d4 Binary files /dev/null and b/Resources/Textures/Mobs/Species/IPC/parts.rsi/l_leg.png differ diff --git a/Resources/Textures/Mobs/Species/IPC/parts.rsi/meta.json b/Resources/Textures/Mobs/Species/IPC/parts.rsi/meta.json new file mode 100644 index 0000000000..1463c57a06 --- /dev/null +++ b/Resources/Textures/Mobs/Species/IPC/parts.rsi/meta.json @@ -0,0 +1,62 @@ +{ + "version": 2, + "license": "CC-BY-SA-3.0", + "copyright": "Original drawn by @robustyanka on Discord, modified by @pspritechologist", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "full" + }, + { + "name": "head_m", + "directions": 4 + }, + { + "name": "head_f", + "directions": 4 + }, + { + "name": "torso_m", + "directions": 4 + }, + { + "name": "torso_f", + "directions": 4 + }, + { + "name": "r_arm", + "directions": 4 + }, + { + "name": "l_arm", + "directions": 4 + }, + { + "name": "r_hand", + "directions": 4 + }, + { + "name": "l_hand", + "directions": 4 + }, + { + "name": "r_leg", + "directions": 4 + }, + { + "name": "l_leg", + "directions": 4 + }, + { + "name": "r_foot", + "directions": 4 + }, + { + "name": "l_foot", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Mobs/Species/IPC/parts.rsi/r_arm.png b/Resources/Textures/Mobs/Species/IPC/parts.rsi/r_arm.png new file mode 100644 index 0000000000..6c1ff1ec9c Binary files /dev/null and b/Resources/Textures/Mobs/Species/IPC/parts.rsi/r_arm.png differ diff --git a/Resources/Textures/Mobs/Species/IPC/parts.rsi/r_foot.png b/Resources/Textures/Mobs/Species/IPC/parts.rsi/r_foot.png new file mode 100644 index 0000000000..2389c30aea Binary files /dev/null and b/Resources/Textures/Mobs/Species/IPC/parts.rsi/r_foot.png differ diff --git a/Resources/Textures/Mobs/Species/IPC/parts.rsi/r_hand.png b/Resources/Textures/Mobs/Species/IPC/parts.rsi/r_hand.png new file mode 100644 index 0000000000..3ec4fab037 Binary files /dev/null and b/Resources/Textures/Mobs/Species/IPC/parts.rsi/r_hand.png differ diff --git a/Resources/Textures/Mobs/Species/IPC/parts.rsi/r_leg.png b/Resources/Textures/Mobs/Species/IPC/parts.rsi/r_leg.png new file mode 100644 index 0000000000..f424b2efbc Binary files /dev/null and b/Resources/Textures/Mobs/Species/IPC/parts.rsi/r_leg.png differ diff --git a/Resources/Textures/Mobs/Species/IPC/parts.rsi/torso_f.png b/Resources/Textures/Mobs/Species/IPC/parts.rsi/torso_f.png new file mode 100644 index 0000000000..b36a2eed8c Binary files /dev/null and b/Resources/Textures/Mobs/Species/IPC/parts.rsi/torso_f.png differ diff --git a/Resources/Textures/Mobs/Species/IPC/parts.rsi/torso_m.png b/Resources/Textures/Mobs/Species/IPC/parts.rsi/torso_m.png new file mode 100644 index 0000000000..df2588b562 Binary files /dev/null and b/Resources/Textures/Mobs/Species/IPC/parts.rsi/torso_m.png differ diff --git a/Resources/Textures/Mobs/Species/Misc/Pizza/parts.rsi/l_arm.png b/Resources/Textures/Mobs/Species/Misc/Pizza/parts.rsi/l_arm.png new file mode 100644 index 0000000000..d2838e8b7d Binary files /dev/null and b/Resources/Textures/Mobs/Species/Misc/Pizza/parts.rsi/l_arm.png differ diff --git a/Resources/Textures/Mobs/Species/Misc/Pizza/parts.rsi/meta.json b/Resources/Textures/Mobs/Species/Misc/Pizza/parts.rsi/meta.json new file mode 100644 index 0000000000..27215dfbca --- /dev/null +++ b/Resources/Textures/Mobs/Species/Misc/Pizza/parts.rsi/meta.json @@ -0,0 +1,19 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Made by Agoichi (823598558690934824)", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "l_arm", + "directions": 4 + }, + { + "name": "r_arm", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Mobs/Species/Misc/Pizza/parts.rsi/r_arm.png b/Resources/Textures/Mobs/Species/Misc/Pizza/parts.rsi/r_arm.png new file mode 100644 index 0000000000..a85b505f90 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Misc/Pizza/parts.rsi/r_arm.png differ diff --git a/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/appendix.png b/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/appendix.png new file mode 100644 index 0000000000..0d2ad309c7 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/appendix.png differ diff --git a/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/brain.png b/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/brain.png new file mode 100644 index 0000000000..ac2806b79c Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/brain.png differ diff --git a/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/core.png b/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/core.png new file mode 100644 index 0000000000..ac2d7893fd Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/core.png differ diff --git a/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/ears.png b/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/ears.png new file mode 100644 index 0000000000..6ff3ac86b7 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/ears.png differ diff --git a/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/eyes.png b/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/eyes.png new file mode 100644 index 0000000000..f7c0a306aa Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/eyes.png differ diff --git a/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/heart.png b/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/heart.png new file mode 100644 index 0000000000..1b79b529ae Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/heart.png differ diff --git a/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/kidneys.png b/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/kidneys.png new file mode 100644 index 0000000000..482bb24102 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/kidneys.png differ diff --git a/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/liver.png b/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/liver.png new file mode 100644 index 0000000000..0a2e6ab25a Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/liver.png differ diff --git a/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/lungs.png b/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/lungs.png new file mode 100644 index 0000000000..a76c9fc1eb Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/lungs.png differ diff --git a/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/meta.json b/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/meta.json new file mode 100644 index 0000000000..1c9aebfb6d --- /dev/null +++ b/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/meta.json @@ -0,0 +1,44 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "https://github.com/tgstation/tgstation/commit/f309886bf3e29808206693e9142304260df134e9", + "states": [ + { + "name": "appendix" + }, + { + "name": "brain" + }, + { + "name": "core" + }, + { + "name": "ears" + }, + { + "name": "eyes" + }, + { + "name": "heart" + }, + { + "name": "kidneys" + }, + { + "name": "liver" + }, + { + "name": "lungs" + }, + { + "name": "stomach" + }, + { + "name": "tongue" + } + ] +} diff --git a/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/stomach.png b/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/stomach.png new file mode 100644 index 0000000000..a0341750d3 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/stomach.png differ diff --git a/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/tongue.png b/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/tongue.png new file mode 100644 index 0000000000..64306900f5 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/organs.rsi/tongue.png differ diff --git a/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/eyes.png b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/eyes.png new file mode 100644 index 0000000000..20fd326f17 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/eyes.png differ diff --git a/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/full-nomarkings.png b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/full-nomarkings.png new file mode 100644 index 0000000000..b62d81fec7 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/full-nomarkings.png differ diff --git a/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/full.png b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/full.png new file mode 100644 index 0000000000..253eb0c3c9 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/full.png differ diff --git a/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/head_f.png b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/head_f.png new file mode 100644 index 0000000000..9d99bd1890 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/head_f.png differ diff --git a/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/head_m.png b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/head_m.png new file mode 100644 index 0000000000..9d99bd1890 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/head_m.png differ diff --git a/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/l_arm.png b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/l_arm.png new file mode 100644 index 0000000000..078ca333f6 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/l_arm.png differ diff --git a/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/l_foot.png b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/l_foot.png new file mode 100644 index 0000000000..30ad69dc3f Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/l_foot.png differ diff --git a/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/l_hand.png b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/l_hand.png new file mode 100644 index 0000000000..0cbc7371d1 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/l_hand.png differ diff --git a/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/l_leg.png b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/l_leg.png new file mode 100644 index 0000000000..b961dfc842 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/l_leg.png differ diff --git a/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/meta.json b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/meta.json new file mode 100644 index 0000000000..a259ab696b --- /dev/null +++ b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/meta.json @@ -0,0 +1,71 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "https://github.com/SPLURT-Station/S.P.L.U.R.T-Station-13/commit/5bc3ea02ce03a551c85017f1ddd411315a19a5ca#diff-519788fa2ca74299d1686a44d3ab2098b49ed5ab65d293ec742bead7d49f0b8d", + "states": [ + { + "name": "full", + "directions": 4 + }, + { + "name": "full-nomarkings", + "directions": 4 + }, + { + "name": "head_m", + "directions": 4 + }, + { + "name": "head_f", + "directions": 4 + }, + { + "name": "torso_m", + "directions": 4 + }, + { + "name": "torso_f", + "directions": 4 + }, + { + "name": "r_arm", + "directions": 4 + }, + { + "name": "l_arm", + "directions": 4 + }, + { + "name": "r_hand", + "directions": 4 + }, + { + "name": "l_hand", + "directions": 4 + }, + { + "name": "r_leg", + "directions": 4 + }, + { + "name": "r_foot", + "directions": 4 + }, + { + "name": "l_leg", + "directions": 4 + }, + { + "name": "l_foot", + "directions": 4 + }, + { + "name": "eyes", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/r_arm.png b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/r_arm.png new file mode 100644 index 0000000000..c294120942 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/r_arm.png differ diff --git a/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/r_foot.png b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/r_foot.png new file mode 100644 index 0000000000..390e0a27ee Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/r_foot.png differ diff --git a/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/r_hand.png b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/r_hand.png new file mode 100644 index 0000000000..331e33a587 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/r_hand.png differ diff --git a/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/r_leg.png b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/r_leg.png new file mode 100644 index 0000000000..6b0270f634 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/r_leg.png differ diff --git a/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/torso_f.png b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/torso_f.png new file mode 100644 index 0000000000..83cc63cdd2 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/torso_f.png differ diff --git a/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/torso_m.png b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/torso_m.png new file mode 100644 index 0000000000..dafc83b65e Binary files /dev/null and b/Resources/Textures/Mobs/Species/Shadowkin/parts.rsi/torso_m.png differ diff --git a/Resources/Textures/Mobs/Species/Vox/displacement.rsi/jumpsuit.png b/Resources/Textures/Mobs/Species/Vox/displacement.rsi/jumpsuit.png new file mode 100644 index 0000000000..2c938634eb Binary files /dev/null and b/Resources/Textures/Mobs/Species/Vox/displacement.rsi/jumpsuit.png differ diff --git a/Resources/Textures/Mobs/Species/Vox/displacement.rsi/meta.json b/Resources/Textures/Mobs/Species/Vox/displacement.rsi/meta.json new file mode 100644 index 0000000000..6ea6c552b9 --- /dev/null +++ b/Resources/Textures/Mobs/Species/Vox/displacement.rsi/meta.json @@ -0,0 +1,18 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Made by PJB3005", + "size": { + "x": 32, + "y": 32 + }, + "load": { + "srgb": false + }, + "states": [ + { + "name": "jumpsuit", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Mobs/Species/Vox/parts.rsi/eyes.png b/Resources/Textures/Mobs/Species/Vox/parts.rsi/eyes.png new file mode 100644 index 0000000000..5069e90b53 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Vox/parts.rsi/eyes.png differ diff --git a/Resources/Textures/Mobs/Species/Vox/parts.rsi/full.png b/Resources/Textures/Mobs/Species/Vox/parts.rsi/full.png new file mode 100644 index 0000000000..6338e4d112 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Vox/parts.rsi/full.png differ diff --git a/Resources/Textures/Mobs/Species/Vox/parts.rsi/groin.png b/Resources/Textures/Mobs/Species/Vox/parts.rsi/groin.png new file mode 100644 index 0000000000..ec0dd8402e Binary files /dev/null and b/Resources/Textures/Mobs/Species/Vox/parts.rsi/groin.png differ diff --git a/Resources/Textures/Mobs/Species/Vox/parts.rsi/groin_f.png b/Resources/Textures/Mobs/Species/Vox/parts.rsi/groin_f.png deleted file mode 100644 index 15c0ed8d66..0000000000 Binary files a/Resources/Textures/Mobs/Species/Vox/parts.rsi/groin_f.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Species/Vox/parts.rsi/groin_m.png b/Resources/Textures/Mobs/Species/Vox/parts.rsi/groin_m.png deleted file mode 100644 index 15c0ed8d66..0000000000 Binary files a/Resources/Textures/Mobs/Species/Vox/parts.rsi/groin_m.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Species/Vox/parts.rsi/head.png b/Resources/Textures/Mobs/Species/Vox/parts.rsi/head.png new file mode 100644 index 0000000000..955e6c7b2a Binary files /dev/null and b/Resources/Textures/Mobs/Species/Vox/parts.rsi/head.png differ diff --git a/Resources/Textures/Mobs/Species/Vox/parts.rsi/head_f.png b/Resources/Textures/Mobs/Species/Vox/parts.rsi/head_f.png deleted file mode 100644 index 6d92de1b90..0000000000 Binary files a/Resources/Textures/Mobs/Species/Vox/parts.rsi/head_f.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Species/Vox/parts.rsi/head_m.png b/Resources/Textures/Mobs/Species/Vox/parts.rsi/head_m.png deleted file mode 100644 index 6d92de1b90..0000000000 Binary files a/Resources/Textures/Mobs/Species/Vox/parts.rsi/head_m.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Species/Vox/parts.rsi/l_arm.png b/Resources/Textures/Mobs/Species/Vox/parts.rsi/l_arm.png index bdd61871c5..258127dbae 100644 Binary files a/Resources/Textures/Mobs/Species/Vox/parts.rsi/l_arm.png and b/Resources/Textures/Mobs/Species/Vox/parts.rsi/l_arm.png differ diff --git a/Resources/Textures/Mobs/Species/Vox/parts.rsi/l_foot.png b/Resources/Textures/Mobs/Species/Vox/parts.rsi/l_foot.png index d12c19cf0c..3b81ae7059 100644 Binary files a/Resources/Textures/Mobs/Species/Vox/parts.rsi/l_foot.png and b/Resources/Textures/Mobs/Species/Vox/parts.rsi/l_foot.png differ diff --git a/Resources/Textures/Mobs/Species/Vox/parts.rsi/l_hand.png b/Resources/Textures/Mobs/Species/Vox/parts.rsi/l_hand.png index 0d1048e090..d321880c7b 100644 Binary files a/Resources/Textures/Mobs/Species/Vox/parts.rsi/l_hand.png and b/Resources/Textures/Mobs/Species/Vox/parts.rsi/l_hand.png differ diff --git a/Resources/Textures/Mobs/Species/Vox/parts.rsi/l_leg.png b/Resources/Textures/Mobs/Species/Vox/parts.rsi/l_leg.png index 20eebad860..918b343f98 100644 Binary files a/Resources/Textures/Mobs/Species/Vox/parts.rsi/l_leg.png and b/Resources/Textures/Mobs/Species/Vox/parts.rsi/l_leg.png differ diff --git a/Resources/Textures/Mobs/Species/Vox/parts.rsi/meta.json b/Resources/Textures/Mobs/Species/Vox/parts.rsi/meta.json index 1070da1203..4704e093b4 100644 --- a/Resources/Textures/Mobs/Species/Vox/parts.rsi/meta.json +++ b/Resources/Textures/Mobs/Species/Vox/parts.rsi/meta.json @@ -1,26 +1,25 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from https://github.com/vgstation-coders/vgstation13 at 02ff588d59b3c560c685d9ca75e882d32a72d8cb", + "copyright": "Taken from https://github.com/vgstation-coders/vgstation13 at 02ff588d59b3c560c685d9ca75e882d32a72d8cb, and modified by Bhijn, Errant and Flareguy", "size": { "x": 32, "y": 32 }, "states": [ { - "name": "groin_f", + "name": "eyes", "directions": 4 }, { - "name": "groin_m", - "directions": 4 + "name": "full" }, { - "name": "head_f", + "name": "groin", "directions": 4 }, { - "name": "head_m", + "name": "head", "directions": 4 }, { @@ -60,11 +59,7 @@ "directions": 4 }, { - "name": "torso_f", - "directions": 4 - }, - { - "name": "torso_m", + "name": "torso", "directions": 4 }, { diff --git a/Resources/Textures/Mobs/Species/Vox/parts.rsi/r_arm.png b/Resources/Textures/Mobs/Species/Vox/parts.rsi/r_arm.png index 0c1f703efd..766cd378ea 100644 Binary files a/Resources/Textures/Mobs/Species/Vox/parts.rsi/r_arm.png and b/Resources/Textures/Mobs/Species/Vox/parts.rsi/r_arm.png differ diff --git a/Resources/Textures/Mobs/Species/Vox/parts.rsi/r_foot.png b/Resources/Textures/Mobs/Species/Vox/parts.rsi/r_foot.png index 80d3a78759..2511bc5252 100644 Binary files a/Resources/Textures/Mobs/Species/Vox/parts.rsi/r_foot.png and b/Resources/Textures/Mobs/Species/Vox/parts.rsi/r_foot.png differ diff --git a/Resources/Textures/Mobs/Species/Vox/parts.rsi/r_hand.png b/Resources/Textures/Mobs/Species/Vox/parts.rsi/r_hand.png index d794c608bd..98f8b376a8 100644 Binary files a/Resources/Textures/Mobs/Species/Vox/parts.rsi/r_hand.png and b/Resources/Textures/Mobs/Species/Vox/parts.rsi/r_hand.png differ diff --git a/Resources/Textures/Mobs/Species/Vox/parts.rsi/r_leg.png b/Resources/Textures/Mobs/Species/Vox/parts.rsi/r_leg.png index 37417e2815..45b1ae82e7 100644 Binary files a/Resources/Textures/Mobs/Species/Vox/parts.rsi/r_leg.png and b/Resources/Textures/Mobs/Species/Vox/parts.rsi/r_leg.png differ diff --git a/Resources/Textures/Mobs/Species/Vox/parts.rsi/torso.png b/Resources/Textures/Mobs/Species/Vox/parts.rsi/torso.png new file mode 100644 index 0000000000..841d409735 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Vox/parts.rsi/torso.png differ diff --git a/Resources/Textures/Mobs/Species/Vox/parts.rsi/torso_f.png b/Resources/Textures/Mobs/Species/Vox/parts.rsi/torso_f.png deleted file mode 100644 index 75bb51ed37..0000000000 Binary files a/Resources/Textures/Mobs/Species/Vox/parts.rsi/torso_f.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Species/Vox/parts.rsi/torso_m.png b/Resources/Textures/Mobs/Species/Vox/parts.rsi/torso_m.png deleted file mode 100644 index 75bb51ed37..0000000000 Binary files a/Resources/Textures/Mobs/Species/Vox/parts.rsi/torso_m.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Species/Vox/parts.rsi/vox_m.png b/Resources/Textures/Mobs/Species/Vox/parts.rsi/vox_m.png index 31f75a7996..8eead3c97b 100644 Binary files a/Resources/Textures/Mobs/Species/Vox/parts.rsi/vox_m.png and b/Resources/Textures/Mobs/Species/Vox/parts.rsi/vox_m.png differ diff --git a/Resources/Textures/Nyanotrasen/Objects/Materials/materials.rsi/meta.json b/Resources/Textures/Nyanotrasen/Objects/Materials/materials.rsi/meta.json deleted file mode 100644 index 87a4078ff7..0000000000 --- a/Resources/Textures/Nyanotrasen/Objects/Materials/materials.rsi/meta.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/commit/f8f4aeda930fcd0805ca4cc76d9bc9412a5b3428", - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "bluespace" - }, - { - "name": "bluespace_2" - }, - { - "name": "bluespace_3" - } - ] -} diff --git a/Resources/Textures/Nyanotrasen/Structures/Machines/metempsychotic.rsi/meta.json b/Resources/Textures/Nyanotrasen/Structures/Machines/metempsychotic.rsi/meta.json deleted file mode 100644 index 7276fde67e..0000000000 --- a/Resources/Textures/Nyanotrasen/Structures/Machines/metempsychotic.rsi/meta.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "version": 1, - "license": "CC-BY-4.0", - "copyright": "Created by discord user Four Hydra Heads#2075 (971500282364178512)", - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "pod_0" - }, - { - "name": "pod_1", - "delays": [ [ 0.1, 0.1, 0.1, 0.1, 0.1, 0.1 ] ] - } - ] -} diff --git a/Resources/Textures/Objects/Consumable/Drinks/budgetinsulsdrink.rsi/fill-1.png b/Resources/Textures/Objects/Consumable/Drinks/budgetinsulsdrink.rsi/fill-1.png new file mode 100644 index 0000000000..aca8f4931d Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/budgetinsulsdrink.rsi/fill-1.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/budgetinsulsdrink.rsi/fill-2.png b/Resources/Textures/Objects/Consumable/Drinks/budgetinsulsdrink.rsi/fill-2.png new file mode 100644 index 0000000000..4715162ebe Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/budgetinsulsdrink.rsi/fill-2.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/budgetinsulsdrink.rsi/fill-3.png b/Resources/Textures/Objects/Consumable/Drinks/budgetinsulsdrink.rsi/fill-3.png new file mode 100644 index 0000000000..04bfd58d03 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/budgetinsulsdrink.rsi/fill-3.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/budgetinsulsdrink.rsi/icon.png b/Resources/Textures/Objects/Consumable/Drinks/budgetinsulsdrink.rsi/icon.png new file mode 100644 index 0000000000..04bfd58d03 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/budgetinsulsdrink.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/budgetinsulsdrink.rsi/icon_empty.png b/Resources/Textures/Objects/Consumable/Drinks/budgetinsulsdrink.rsi/icon_empty.png new file mode 100644 index 0000000000..4d446abc54 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/budgetinsulsdrink.rsi/icon_empty.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/budgetinsulsdrink.rsi/meta.json b/Resources/Textures/Objects/Consumable/Drinks/budgetinsulsdrink.rsi/meta.json new file mode 100644 index 0000000000..14c43f186f --- /dev/null +++ b/Resources/Textures/Objects/Consumable/Drinks/budgetinsulsdrink.rsi/meta.json @@ -0,0 +1,28 @@ +{ + "version": 1, + "size": + { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Glass and fillstates by Hanzdegloker on Github.", + "states": + [ + { + "name": "icon" + }, + { + "name": "icon_empty" + }, + { + "name": "fill-1" + }, + { + "name": "fill-2" + }, + { + "name": "fill-3" + } + ] +} diff --git a/Resources/Textures/Objects/Consumable/Drinks/coffeeglass.rsi/fill-1.png b/Resources/Textures/Objects/Consumable/Drinks/coffeeglass.rsi/fill-1.png new file mode 100644 index 0000000000..e4e832ce8d Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/coffeeglass.rsi/fill-1.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/coffeeglass.rsi/fill-2.png b/Resources/Textures/Objects/Consumable/Drinks/coffeeglass.rsi/fill-2.png new file mode 100644 index 0000000000..c778f3c428 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/coffeeglass.rsi/fill-2.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/coffeeglass.rsi/fill-3.png b/Resources/Textures/Objects/Consumable/Drinks/coffeeglass.rsi/fill-3.png new file mode 100644 index 0000000000..480528bc96 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/coffeeglass.rsi/fill-3.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/coffeeglass.rsi/fill-4.png b/Resources/Textures/Objects/Consumable/Drinks/coffeeglass.rsi/fill-4.png new file mode 100644 index 0000000000..da13e68139 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/coffeeglass.rsi/fill-4.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/coffeeglass.rsi/icon.png b/Resources/Textures/Objects/Consumable/Drinks/coffeeglass.rsi/icon.png new file mode 100644 index 0000000000..08de309039 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/coffeeglass.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/coffeeglass.rsi/icon_empty.png b/Resources/Textures/Objects/Consumable/Drinks/coffeeglass.rsi/icon_empty.png new file mode 100644 index 0000000000..d46a00edd0 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/coffeeglass.rsi/icon_empty.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/coffeeglass.rsi/meta.json b/Resources/Textures/Objects/Consumable/Drinks/coffeeglass.rsi/meta.json new file mode 100644 index 0000000000..32c5e0342d --- /dev/null +++ b/Resources/Textures/Objects/Consumable/Drinks/coffeeglass.rsi/meta.json @@ -0,0 +1,29 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Made by borkroman. Fill levels by RumiTiger", + "states": [ + { + "name": "icon" + }, + { + "name": "icon_empty" + }, + { + "name": "fill-1" + }, + { + "name": "fill-2" + }, + { + "name": "fill-3" + }, + { + "name": "fill-4" + } + ] +} diff --git a/Resources/Textures/Objects/Consumable/Drinks/colaglass.rsi/fill-1.png b/Resources/Textures/Objects/Consumable/Drinks/colaglass.rsi/fill-1.png new file mode 100644 index 0000000000..a36d304a4d Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/colaglass.rsi/fill-1.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/colaglass.rsi/fill-2.png b/Resources/Textures/Objects/Consumable/Drinks/colaglass.rsi/fill-2.png new file mode 100644 index 0000000000..6351e9f3dd Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/colaglass.rsi/fill-2.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/colaglass.rsi/fill-3.png b/Resources/Textures/Objects/Consumable/Drinks/colaglass.rsi/fill-3.png new file mode 100644 index 0000000000..bb7bee49d0 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/colaglass.rsi/fill-3.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/colaglass.rsi/fill-4.png b/Resources/Textures/Objects/Consumable/Drinks/colaglass.rsi/fill-4.png new file mode 100644 index 0000000000..1808bdbd04 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/colaglass.rsi/fill-4.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/colaglass.rsi/fill-5.png b/Resources/Textures/Objects/Consumable/Drinks/colaglass.rsi/fill-5.png new file mode 100644 index 0000000000..734126d866 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/colaglass.rsi/fill-5.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/colaglass.rsi/icon.png b/Resources/Textures/Objects/Consumable/Drinks/colaglass.rsi/icon.png new file mode 100644 index 0000000000..2894625a2e Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/colaglass.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/colaglass.rsi/icon_empty.png b/Resources/Textures/Objects/Consumable/Drinks/colaglass.rsi/icon_empty.png new file mode 100644 index 0000000000..b20279f8f1 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/colaglass.rsi/icon_empty.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/colaglass.rsi/meta.json b/Resources/Textures/Objects/Consumable/Drinks/colaglass.rsi/meta.json new file mode 100644 index 0000000000..ec55ebfa1f --- /dev/null +++ b/Resources/Textures/Objects/Consumable/Drinks/colaglass.rsi/meta.json @@ -0,0 +1,32 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Made by RumiTiger", + "states": [ + { + "name": "icon" + }, + { + "name": "icon_empty" + }, + { + "name": "fill-1" + }, + { + "name": "fill-2" + }, + { + "name": "fill-3" + }, + { + "name": "fill-4" + }, + { + "name": "fill-5" + } + ] +} diff --git a/Resources/Textures/Objects/Consumable/Drinks/dr_gibb_glass.rsi/fill-1.png b/Resources/Textures/Objects/Consumable/Drinks/dr_gibb_glass.rsi/fill-1.png new file mode 100644 index 0000000000..68731e8fcb Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/dr_gibb_glass.rsi/fill-1.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/dr_gibb_glass.rsi/fill-2.png b/Resources/Textures/Objects/Consumable/Drinks/dr_gibb_glass.rsi/fill-2.png new file mode 100644 index 0000000000..716aebbb83 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/dr_gibb_glass.rsi/fill-2.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/dr_gibb_glass.rsi/fill-3.png b/Resources/Textures/Objects/Consumable/Drinks/dr_gibb_glass.rsi/fill-3.png new file mode 100644 index 0000000000..74f54a4c83 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/dr_gibb_glass.rsi/fill-3.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/dr_gibb_glass.rsi/fill-4.png b/Resources/Textures/Objects/Consumable/Drinks/dr_gibb_glass.rsi/fill-4.png new file mode 100644 index 0000000000..da44083964 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/dr_gibb_glass.rsi/fill-4.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/dr_gibb_glass.rsi/fill-5.png b/Resources/Textures/Objects/Consumable/Drinks/dr_gibb_glass.rsi/fill-5.png new file mode 100644 index 0000000000..db2b9315bf Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/dr_gibb_glass.rsi/fill-5.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/dr_gibb_glass.rsi/icon.png b/Resources/Textures/Objects/Consumable/Drinks/dr_gibb_glass.rsi/icon.png index 620cf5ea01..6a68486662 100644 Binary files a/Resources/Textures/Objects/Consumable/Drinks/dr_gibb_glass.rsi/icon.png and b/Resources/Textures/Objects/Consumable/Drinks/dr_gibb_glass.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/dr_gibb_glass.rsi/icon_empty.png b/Resources/Textures/Objects/Consumable/Drinks/dr_gibb_glass.rsi/icon_empty.png new file mode 100644 index 0000000000..af2dcb404c Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/dr_gibb_glass.rsi/icon_empty.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/dr_gibb_glass.rsi/meta.json b/Resources/Textures/Objects/Consumable/Drinks/dr_gibb_glass.rsi/meta.json index db0ac608ed..ec55ebfa1f 100644 --- a/Resources/Textures/Objects/Consumable/Drinks/dr_gibb_glass.rsi/meta.json +++ b/Resources/Textures/Objects/Consumable/Drinks/dr_gibb_glass.rsi/meta.json @@ -1 +1,32 @@ -{"version": 1, "size": {"x": 32, "y": 32}, "license": "CC-BY-SA-3.0", "copyright": "https://github.com/discordia-space/CEV-Eris/raw/f7aa28fd4b4d0386c3393d829681ebca526f1d2d/icons/obj/drinks.dmi", "states": [{"name": "icon"}]} \ No newline at end of file +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Made by RumiTiger", + "states": [ + { + "name": "icon" + }, + { + "name": "icon_empty" + }, + { + "name": "fill-1" + }, + { + "name": "fill-2" + }, + { + "name": "fill-3" + }, + { + "name": "fill-4" + }, + { + "name": "fill-5" + } + ] +} diff --git a/Resources/Textures/Objects/Consumable/Drinks/energy_drink.rsi/icon.png b/Resources/Textures/Objects/Consumable/Drinks/energy_drink.rsi/icon.png index 7a577d477c..e9e2aea716 100644 Binary files a/Resources/Textures/Objects/Consumable/Drinks/energy_drink.rsi/icon.png and b/Resources/Textures/Objects/Consumable/Drinks/energy_drink.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/energy_drink.rsi/icon_open.png b/Resources/Textures/Objects/Consumable/Drinks/energy_drink.rsi/icon_open.png index b26d37e377..44688ef3e9 100644 Binary files a/Resources/Textures/Objects/Consumable/Drinks/energy_drink.rsi/icon_open.png and b/Resources/Textures/Objects/Consumable/Drinks/energy_drink.rsi/icon_open.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/greenteaglass.rsi/fill-1.png b/Resources/Textures/Objects/Consumable/Drinks/greenteaglass.rsi/fill-1.png new file mode 100644 index 0000000000..613efd1171 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/greenteaglass.rsi/fill-1.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/greenteaglass.rsi/fill-2.png b/Resources/Textures/Objects/Consumable/Drinks/greenteaglass.rsi/fill-2.png new file mode 100644 index 0000000000..785703f4df Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/greenteaglass.rsi/fill-2.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/greenteaglass.rsi/fill-3.png b/Resources/Textures/Objects/Consumable/Drinks/greenteaglass.rsi/fill-3.png new file mode 100644 index 0000000000..1f482c8df9 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/greenteaglass.rsi/fill-3.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/greenteaglass.rsi/fill-4.png b/Resources/Textures/Objects/Consumable/Drinks/greenteaglass.rsi/fill-4.png new file mode 100644 index 0000000000..99c7dffa36 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/greenteaglass.rsi/fill-4.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/greenteaglass.rsi/icon.png b/Resources/Textures/Objects/Consumable/Drinks/greenteaglass.rsi/icon.png new file mode 100644 index 0000000000..9c77c673ff Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/greenteaglass.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/greenteaglass.rsi/icon_empty.png b/Resources/Textures/Objects/Consumable/Drinks/greenteaglass.rsi/icon_empty.png new file mode 100644 index 0000000000..71eb435183 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/greenteaglass.rsi/icon_empty.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/greenteaglass.rsi/meta.json b/Resources/Textures/Objects/Consumable/Drinks/greenteaglass.rsi/meta.json new file mode 100644 index 0000000000..32c5e0342d --- /dev/null +++ b/Resources/Textures/Objects/Consumable/Drinks/greenteaglass.rsi/meta.json @@ -0,0 +1,29 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Made by borkroman. Fill levels by RumiTiger", + "states": [ + { + "name": "icon" + }, + { + "name": "icon_empty" + }, + { + "name": "fill-1" + }, + { + "name": "fill-2" + }, + { + "name": "fill-3" + }, + { + "name": "fill-4" + } + ] +} diff --git a/Resources/Textures/Objects/Consumable/Drinks/icebucket.rsi/icon.png b/Resources/Textures/Objects/Consumable/Drinks/icebucket.rsi/icon.png new file mode 100644 index 0000000000..228d098060 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/icebucket.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/icebucket.rsi/meta.json b/Resources/Textures/Objects/Consumable/Drinks/icebucket.rsi/meta.json new file mode 100644 index 0000000000..f55a85dc26 --- /dev/null +++ b/Resources/Textures/Objects/Consumable/Drinks/icebucket.rsi/meta.json @@ -0,0 +1,14 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Made by Dezzzix; Discord: dezzzix", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + } + ] + } diff --git a/Resources/Textures/Objects/Consumable/Drinks/icedgreenteaglass.rsi/fill-1.png b/Resources/Textures/Objects/Consumable/Drinks/icedgreenteaglass.rsi/fill-1.png new file mode 100644 index 0000000000..2d217ef147 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/icedgreenteaglass.rsi/fill-1.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/icedgreenteaglass.rsi/fill-2.png b/Resources/Textures/Objects/Consumable/Drinks/icedgreenteaglass.rsi/fill-2.png new file mode 100644 index 0000000000..d8ca5b63af Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/icedgreenteaglass.rsi/fill-2.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/icedgreenteaglass.rsi/fill-3.png b/Resources/Textures/Objects/Consumable/Drinks/icedgreenteaglass.rsi/fill-3.png new file mode 100644 index 0000000000..a65df2aaed Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/icedgreenteaglass.rsi/fill-3.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/icedgreenteaglass.rsi/fill-4.png b/Resources/Textures/Objects/Consumable/Drinks/icedgreenteaglass.rsi/fill-4.png new file mode 100644 index 0000000000..3ae28ff68f Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/icedgreenteaglass.rsi/fill-4.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/icedgreenteaglass.rsi/fill-5.png b/Resources/Textures/Objects/Consumable/Drinks/icedgreenteaglass.rsi/fill-5.png new file mode 100644 index 0000000000..72c2f4cecf Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/icedgreenteaglass.rsi/fill-5.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/icedgreenteaglass.rsi/icon.png b/Resources/Textures/Objects/Consumable/Drinks/icedgreenteaglass.rsi/icon.png new file mode 100644 index 0000000000..999834b142 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/icedgreenteaglass.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/icedgreenteaglass.rsi/icon_empty.png b/Resources/Textures/Objects/Consumable/Drinks/icedgreenteaglass.rsi/icon_empty.png new file mode 100644 index 0000000000..e10df8d172 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/icedgreenteaglass.rsi/icon_empty.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/icedgreenteaglass.rsi/meta.json b/Resources/Textures/Objects/Consumable/Drinks/icedgreenteaglass.rsi/meta.json new file mode 100644 index 0000000000..ec55ebfa1f --- /dev/null +++ b/Resources/Textures/Objects/Consumable/Drinks/icedgreenteaglass.rsi/meta.json @@ -0,0 +1,32 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Made by RumiTiger", + "states": [ + { + "name": "icon" + }, + { + "name": "icon_empty" + }, + { + "name": "fill-1" + }, + { + "name": "fill-2" + }, + { + "name": "fill-3" + }, + { + "name": "fill-4" + }, + { + "name": "fill-5" + } + ] +} diff --git a/Resources/Textures/Objects/Consumable/Drinks/iceglass.rsi/fill-1.png b/Resources/Textures/Objects/Consumable/Drinks/iceglass.rsi/fill-1.png new file mode 100644 index 0000000000..04c8198fc9 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/iceglass.rsi/fill-1.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/iceglass.rsi/fill-2.png b/Resources/Textures/Objects/Consumable/Drinks/iceglass.rsi/fill-2.png new file mode 100644 index 0000000000..367a9e178b Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/iceglass.rsi/fill-2.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/iceglass.rsi/fill-3.png b/Resources/Textures/Objects/Consumable/Drinks/iceglass.rsi/fill-3.png new file mode 100644 index 0000000000..02022cd03b Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/iceglass.rsi/fill-3.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/iceglass.rsi/icon.png b/Resources/Textures/Objects/Consumable/Drinks/iceglass.rsi/icon.png index 61f63fd057..96807c9a5d 100644 Binary files a/Resources/Textures/Objects/Consumable/Drinks/iceglass.rsi/icon.png and b/Resources/Textures/Objects/Consumable/Drinks/iceglass.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/iceglass.rsi/icon_empty.png b/Resources/Textures/Objects/Consumable/Drinks/iceglass.rsi/icon_empty.png new file mode 100644 index 0000000000..5d1f677b89 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/iceglass.rsi/icon_empty.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/iceglass.rsi/meta.json b/Resources/Textures/Objects/Consumable/Drinks/iceglass.rsi/meta.json index db0ac608ed..bdfdb18a0d 100644 --- a/Resources/Textures/Objects/Consumable/Drinks/iceglass.rsi/meta.json +++ b/Resources/Textures/Objects/Consumable/Drinks/iceglass.rsi/meta.json @@ -1 +1,26 @@ -{"version": 1, "size": {"x": 32, "y": 32}, "license": "CC-BY-SA-3.0", "copyright": "https://github.com/discordia-space/CEV-Eris/raw/f7aa28fd4b4d0386c3393d829681ebca526f1d2d/icons/obj/drinks.dmi", "states": [{"name": "icon"}]} \ No newline at end of file +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Made by borkroman. Fill levels by RumiTiger", + "states": [ + { + "name": "icon" + }, + { + "name": "icon_empty" + }, + { + "name": "fill-1" + }, + { + "name": "fill-2" + }, + { + "name": "fill-3" + } + ] +} diff --git a/Resources/Textures/Objects/Consumable/Drinks/jigger.rsi/icon.png b/Resources/Textures/Objects/Consumable/Drinks/jigger.rsi/icon.png new file mode 100644 index 0000000000..6c65ca0202 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/jigger.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/jigger.rsi/meta.json b/Resources/Textures/Objects/Consumable/Drinks/jigger.rsi/meta.json new file mode 100644 index 0000000000..f55a85dc26 --- /dev/null +++ b/Resources/Textures/Objects/Consumable/Drinks/jigger.rsi/meta.json @@ -0,0 +1,14 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Made by Dezzzix; Discord: dezzzix", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + } + ] + } diff --git a/Resources/Textures/Objects/Consumable/Drinks/lemonjuiceglass.rsi/fill-1.png b/Resources/Textures/Objects/Consumable/Drinks/lemonjuiceglass.rsi/fill-1.png new file mode 100644 index 0000000000..4888127187 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/lemonjuiceglass.rsi/fill-1.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/lemonjuiceglass.rsi/fill-2.png b/Resources/Textures/Objects/Consumable/Drinks/lemonjuiceglass.rsi/fill-2.png new file mode 100644 index 0000000000..081ad92014 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/lemonjuiceglass.rsi/fill-2.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/lemonjuiceglass.rsi/fill-3.png b/Resources/Textures/Objects/Consumable/Drinks/lemonjuiceglass.rsi/fill-3.png new file mode 100644 index 0000000000..97a8f2c29b Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/lemonjuiceglass.rsi/fill-3.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/lemonjuiceglass.rsi/fill-4.png b/Resources/Textures/Objects/Consumable/Drinks/lemonjuiceglass.rsi/fill-4.png new file mode 100644 index 0000000000..5b8af69ec8 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/lemonjuiceglass.rsi/fill-4.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/lemonjuiceglass.rsi/fill-5.png b/Resources/Textures/Objects/Consumable/Drinks/lemonjuiceglass.rsi/fill-5.png new file mode 100644 index 0000000000..9b2bc6a3f7 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/lemonjuiceglass.rsi/fill-5.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/lemonjuiceglass.rsi/icon.png b/Resources/Textures/Objects/Consumable/Drinks/lemonjuiceglass.rsi/icon.png new file mode 100644 index 0000000000..9ee1bdd517 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/lemonjuiceglass.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/lemonjuiceglass.rsi/icon_empty.png b/Resources/Textures/Objects/Consumable/Drinks/lemonjuiceglass.rsi/icon_empty.png new file mode 100644 index 0000000000..abbdd77b27 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/lemonjuiceglass.rsi/icon_empty.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/lemonjuiceglass.rsi/meta.json b/Resources/Textures/Objects/Consumable/Drinks/lemonjuiceglass.rsi/meta.json new file mode 100644 index 0000000000..ec55ebfa1f --- /dev/null +++ b/Resources/Textures/Objects/Consumable/Drinks/lemonjuiceglass.rsi/meta.json @@ -0,0 +1,32 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Made by RumiTiger", + "states": [ + { + "name": "icon" + }, + { + "name": "icon_empty" + }, + { + "name": "fill-1" + }, + { + "name": "fill-2" + }, + { + "name": "fill-3" + }, + { + "name": "fill-4" + }, + { + "name": "fill-5" + } + ] +} diff --git a/Resources/Textures/Objects/Consumable/Drinks/moonshineglass.rsi/fill-1.png b/Resources/Textures/Objects/Consumable/Drinks/moonshineglass.rsi/fill-1.png new file mode 100644 index 0000000000..824ffcf3d6 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/moonshineglass.rsi/fill-1.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/moonshineglass.rsi/fill-2.png b/Resources/Textures/Objects/Consumable/Drinks/moonshineglass.rsi/fill-2.png new file mode 100644 index 0000000000..5159ff3671 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/moonshineglass.rsi/fill-2.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/moonshineglass.rsi/fill-3.png b/Resources/Textures/Objects/Consumable/Drinks/moonshineglass.rsi/fill-3.png new file mode 100644 index 0000000000..6d2115a95a Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/moonshineglass.rsi/fill-3.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/moonshineglass.rsi/fill-4.png b/Resources/Textures/Objects/Consumable/Drinks/moonshineglass.rsi/fill-4.png new file mode 100644 index 0000000000..90ca019493 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/moonshineglass.rsi/fill-4.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/moonshineglass.rsi/fill-5.png b/Resources/Textures/Objects/Consumable/Drinks/moonshineglass.rsi/fill-5.png new file mode 100644 index 0000000000..c50860e4d0 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/moonshineglass.rsi/fill-5.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/moonshineglass.rsi/fill-6.png b/Resources/Textures/Objects/Consumable/Drinks/moonshineglass.rsi/fill-6.png new file mode 100644 index 0000000000..1488e2b629 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/moonshineglass.rsi/fill-6.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/moonshineglass.rsi/icon.png b/Resources/Textures/Objects/Consumable/Drinks/moonshineglass.rsi/icon.png new file mode 100644 index 0000000000..5fa26544fd Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/moonshineglass.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/moonshineglass.rsi/icon_empty.png b/Resources/Textures/Objects/Consumable/Drinks/moonshineglass.rsi/icon_empty.png new file mode 100644 index 0000000000..673679cb4a Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/moonshineglass.rsi/icon_empty.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/moonshineglass.rsi/meta.json b/Resources/Textures/Objects/Consumable/Drinks/moonshineglass.rsi/meta.json new file mode 100644 index 0000000000..5fe156daaa --- /dev/null +++ b/Resources/Textures/Objects/Consumable/Drinks/moonshineglass.rsi/meta.json @@ -0,0 +1,35 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Made by RumiTiger", + "states": [ + { + "name": "icon" + }, + { + "name": "icon_empty" + }, + { + "name": "fill-1" + }, + { + "name": "fill-2" + }, + { + "name": "fill-3" + }, + { + "name": "fill-4" + }, + { + "name": "fill-5" + }, + { + "name": "fill-6" + } + ] +} diff --git a/Resources/Textures/Objects/Consumable/Drinks/orangejuiceglass.rsi/fill-1.png b/Resources/Textures/Objects/Consumable/Drinks/orangejuiceglass.rsi/fill-1.png new file mode 100644 index 0000000000..6a56b31eee Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/orangejuiceglass.rsi/fill-1.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/orangejuiceglass.rsi/fill-2.png b/Resources/Textures/Objects/Consumable/Drinks/orangejuiceglass.rsi/fill-2.png new file mode 100644 index 0000000000..0a853551c5 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/orangejuiceglass.rsi/fill-2.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/orangejuiceglass.rsi/fill-3.png b/Resources/Textures/Objects/Consumable/Drinks/orangejuiceglass.rsi/fill-3.png new file mode 100644 index 0000000000..92293d8cf2 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/orangejuiceglass.rsi/fill-3.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/orangejuiceglass.rsi/fill-4.png b/Resources/Textures/Objects/Consumable/Drinks/orangejuiceglass.rsi/fill-4.png new file mode 100644 index 0000000000..85f6dea9b6 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/orangejuiceglass.rsi/fill-4.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/orangejuiceglass.rsi/fill-5.png b/Resources/Textures/Objects/Consumable/Drinks/orangejuiceglass.rsi/fill-5.png new file mode 100644 index 0000000000..598d0be5b6 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/orangejuiceglass.rsi/fill-5.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/orangejuiceglass.rsi/icon.png b/Resources/Textures/Objects/Consumable/Drinks/orangejuiceglass.rsi/icon.png new file mode 100644 index 0000000000..64e185065a Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/orangejuiceglass.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/orangejuiceglass.rsi/icon_empty.png b/Resources/Textures/Objects/Consumable/Drinks/orangejuiceglass.rsi/icon_empty.png new file mode 100644 index 0000000000..fc940737b2 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/orangejuiceglass.rsi/icon_empty.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/orangejuiceglass.rsi/meta.json b/Resources/Textures/Objects/Consumable/Drinks/orangejuiceglass.rsi/meta.json new file mode 100644 index 0000000000..ec55ebfa1f --- /dev/null +++ b/Resources/Textures/Objects/Consumable/Drinks/orangejuiceglass.rsi/meta.json @@ -0,0 +1,32 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Made by RumiTiger", + "states": [ + { + "name": "icon" + }, + { + "name": "icon_empty" + }, + { + "name": "fill-1" + }, + { + "name": "fill-2" + }, + { + "name": "fill-3" + }, + { + "name": "fill-4" + }, + { + "name": "fill-5" + } + ] +} diff --git a/Resources/Textures/Objects/Consumable/Drinks/rubberneck.rsi/fill-1.png b/Resources/Textures/Objects/Consumable/Drinks/rubberneck.rsi/fill-1.png new file mode 100644 index 0000000000..f5afa0bed8 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/rubberneck.rsi/fill-1.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/rubberneck.rsi/fill-2.png b/Resources/Textures/Objects/Consumable/Drinks/rubberneck.rsi/fill-2.png new file mode 100644 index 0000000000..79bcdcfb8c Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/rubberneck.rsi/fill-2.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/rubberneck.rsi/fill-3.png b/Resources/Textures/Objects/Consumable/Drinks/rubberneck.rsi/fill-3.png new file mode 100644 index 0000000000..772aa80d6a Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/rubberneck.rsi/fill-3.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/rubberneck.rsi/icon.png b/Resources/Textures/Objects/Consumable/Drinks/rubberneck.rsi/icon.png new file mode 100644 index 0000000000..772aa80d6a Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/rubberneck.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/rubberneck.rsi/icon_empty.png b/Resources/Textures/Objects/Consumable/Drinks/rubberneck.rsi/icon_empty.png new file mode 100644 index 0000000000..416d1f7717 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/rubberneck.rsi/icon_empty.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/rubberneck.rsi/meta.json b/Resources/Textures/Objects/Consumable/Drinks/rubberneck.rsi/meta.json new file mode 100644 index 0000000000..a26c7355ae --- /dev/null +++ b/Resources/Textures/Objects/Consumable/Drinks/rubberneck.rsi/meta.json @@ -0,0 +1,28 @@ +{ + "version": 1, + "size": + { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "https://tgstation13.org/wiki/images/archive/4/4e/20220514022531%21Rubberneck.png. Fill levels by Hanzdegloker on GitHub.", + "states": + [ + { + "name": "icon" + }, + { + "name": "icon_empty" + }, + { + "name": "fill-1" + }, + { + "name": "fill-2" + }, + { + "name": "fill-3" + } + ] +} diff --git a/Resources/Textures/Objects/Consumable/Drinks/space-up_glass.rsi/fill-1.png b/Resources/Textures/Objects/Consumable/Drinks/space-up_glass.rsi/fill-1.png new file mode 100644 index 0000000000..077d84804e Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/space-up_glass.rsi/fill-1.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/space-up_glass.rsi/fill-2.png b/Resources/Textures/Objects/Consumable/Drinks/space-up_glass.rsi/fill-2.png new file mode 100644 index 0000000000..763d02e7b8 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/space-up_glass.rsi/fill-2.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/space-up_glass.rsi/fill-3.png b/Resources/Textures/Objects/Consumable/Drinks/space-up_glass.rsi/fill-3.png new file mode 100644 index 0000000000..ba76b0c22b Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/space-up_glass.rsi/fill-3.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/space-up_glass.rsi/fill-4.png b/Resources/Textures/Objects/Consumable/Drinks/space-up_glass.rsi/fill-4.png new file mode 100644 index 0000000000..ce9f636dbc Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/space-up_glass.rsi/fill-4.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/space-up_glass.rsi/fill-5.png b/Resources/Textures/Objects/Consumable/Drinks/space-up_glass.rsi/fill-5.png new file mode 100644 index 0000000000..b79be48363 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/space-up_glass.rsi/fill-5.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/space-up_glass.rsi/fill-6.png b/Resources/Textures/Objects/Consumable/Drinks/space-up_glass.rsi/fill-6.png new file mode 100644 index 0000000000..9abb7c672b Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/space-up_glass.rsi/fill-6.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/space-up_glass.rsi/icon.png b/Resources/Textures/Objects/Consumable/Drinks/space-up_glass.rsi/icon.png index 5930cd7536..e5eeaba264 100644 Binary files a/Resources/Textures/Objects/Consumable/Drinks/space-up_glass.rsi/icon.png and b/Resources/Textures/Objects/Consumable/Drinks/space-up_glass.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/space-up_glass.rsi/icon_empty.png b/Resources/Textures/Objects/Consumable/Drinks/space-up_glass.rsi/icon_empty.png new file mode 100644 index 0000000000..9e12bbbe4d Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/space-up_glass.rsi/icon_empty.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/space-up_glass.rsi/meta.json b/Resources/Textures/Objects/Consumable/Drinks/space-up_glass.rsi/meta.json index db0ac608ed..5fe156daaa 100644 --- a/Resources/Textures/Objects/Consumable/Drinks/space-up_glass.rsi/meta.json +++ b/Resources/Textures/Objects/Consumable/Drinks/space-up_glass.rsi/meta.json @@ -1 +1,35 @@ -{"version": 1, "size": {"x": 32, "y": 32}, "license": "CC-BY-SA-3.0", "copyright": "https://github.com/discordia-space/CEV-Eris/raw/f7aa28fd4b4d0386c3393d829681ebca526f1d2d/icons/obj/drinks.dmi", "states": [{"name": "icon"}]} \ No newline at end of file +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Made by RumiTiger", + "states": [ + { + "name": "icon" + }, + { + "name": "icon_empty" + }, + { + "name": "fill-1" + }, + { + "name": "fill-2" + }, + { + "name": "fill-3" + }, + { + "name": "fill-4" + }, + { + "name": "fill-5" + }, + { + "name": "fill-6" + } + ] +} diff --git a/Resources/Textures/Objects/Consumable/Drinks/space_mountain_wind_glass.rsi/fill-1.png b/Resources/Textures/Objects/Consumable/Drinks/space_mountain_wind_glass.rsi/fill-1.png new file mode 100644 index 0000000000..53db03fd96 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/space_mountain_wind_glass.rsi/fill-1.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/space_mountain_wind_glass.rsi/fill-2.png b/Resources/Textures/Objects/Consumable/Drinks/space_mountain_wind_glass.rsi/fill-2.png new file mode 100644 index 0000000000..601efddc0d Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/space_mountain_wind_glass.rsi/fill-2.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/space_mountain_wind_glass.rsi/fill-3.png b/Resources/Textures/Objects/Consumable/Drinks/space_mountain_wind_glass.rsi/fill-3.png new file mode 100644 index 0000000000..4c50ee9fd7 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/space_mountain_wind_glass.rsi/fill-3.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/space_mountain_wind_glass.rsi/fill-4.png b/Resources/Textures/Objects/Consumable/Drinks/space_mountain_wind_glass.rsi/fill-4.png new file mode 100644 index 0000000000..38cf3fdbfc Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/space_mountain_wind_glass.rsi/fill-4.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/space_mountain_wind_glass.rsi/fill-5.png b/Resources/Textures/Objects/Consumable/Drinks/space_mountain_wind_glass.rsi/fill-5.png new file mode 100644 index 0000000000..06be04566c Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/space_mountain_wind_glass.rsi/fill-5.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/space_mountain_wind_glass.rsi/icon.png b/Resources/Textures/Objects/Consumable/Drinks/space_mountain_wind_glass.rsi/icon.png index 812d7fd488..6303745c50 100644 Binary files a/Resources/Textures/Objects/Consumable/Drinks/space_mountain_wind_glass.rsi/icon.png and b/Resources/Textures/Objects/Consumable/Drinks/space_mountain_wind_glass.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/space_mountain_wind_glass.rsi/icon_empty.png b/Resources/Textures/Objects/Consumable/Drinks/space_mountain_wind_glass.rsi/icon_empty.png new file mode 100644 index 0000000000..d576502499 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/space_mountain_wind_glass.rsi/icon_empty.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/space_mountain_wind_glass.rsi/meta.json b/Resources/Textures/Objects/Consumable/Drinks/space_mountain_wind_glass.rsi/meta.json index db0ac608ed..ec55ebfa1f 100644 --- a/Resources/Textures/Objects/Consumable/Drinks/space_mountain_wind_glass.rsi/meta.json +++ b/Resources/Textures/Objects/Consumable/Drinks/space_mountain_wind_glass.rsi/meta.json @@ -1 +1,32 @@ -{"version": 1, "size": {"x": 32, "y": 32}, "license": "CC-BY-SA-3.0", "copyright": "https://github.com/discordia-space/CEV-Eris/raw/f7aa28fd4b4d0386c3393d829681ebca526f1d2d/icons/obj/drinks.dmi", "states": [{"name": "icon"}]} \ No newline at end of file +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Made by RumiTiger", + "states": [ + { + "name": "icon" + }, + { + "name": "icon_empty" + }, + { + "name": "fill-1" + }, + { + "name": "fill-2" + }, + { + "name": "fill-3" + }, + { + "name": "fill-4" + }, + { + "name": "fill-5" + } + ] +} diff --git a/Resources/Textures/Objects/Consumable/Drinks/sugarglass.rsi/fill-1.png b/Resources/Textures/Objects/Consumable/Drinks/sugarglass.rsi/fill-1.png new file mode 100644 index 0000000000..6dce3834ee Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/sugarglass.rsi/fill-1.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/sugarglass.rsi/fill-2.png b/Resources/Textures/Objects/Consumable/Drinks/sugarglass.rsi/fill-2.png new file mode 100644 index 0000000000..64dfddb473 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/sugarglass.rsi/fill-2.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/sugarglass.rsi/fill-3.png b/Resources/Textures/Objects/Consumable/Drinks/sugarglass.rsi/fill-3.png new file mode 100644 index 0000000000..2d281d5189 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/sugarglass.rsi/fill-3.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/sugarglass.rsi/fill-4.png b/Resources/Textures/Objects/Consumable/Drinks/sugarglass.rsi/fill-4.png new file mode 100644 index 0000000000..18ee0e4280 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/sugarglass.rsi/fill-4.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/sugarglass.rsi/fill-5.png b/Resources/Textures/Objects/Consumable/Drinks/sugarglass.rsi/fill-5.png new file mode 100644 index 0000000000..fe4fdead76 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/sugarglass.rsi/fill-5.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/sugarglass.rsi/fill-6.png b/Resources/Textures/Objects/Consumable/Drinks/sugarglass.rsi/fill-6.png new file mode 100644 index 0000000000..9a023cdb01 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/sugarglass.rsi/fill-6.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/sugarglass.rsi/fill-7.png b/Resources/Textures/Objects/Consumable/Drinks/sugarglass.rsi/fill-7.png new file mode 100644 index 0000000000..d7cd6ca7be Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/sugarglass.rsi/fill-7.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/sugarglass.rsi/icon.png b/Resources/Textures/Objects/Consumable/Drinks/sugarglass.rsi/icon.png new file mode 100644 index 0000000000..cf2066eb37 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/sugarglass.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/sugarglass.rsi/icon_empty.png b/Resources/Textures/Objects/Consumable/Drinks/sugarglass.rsi/icon_empty.png new file mode 100644 index 0000000000..5f3bc4f4c7 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/sugarglass.rsi/icon_empty.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/sugarglass.rsi/meta.json b/Resources/Textures/Objects/Consumable/Drinks/sugarglass.rsi/meta.json new file mode 100644 index 0000000000..55fdf44163 --- /dev/null +++ b/Resources/Textures/Objects/Consumable/Drinks/sugarglass.rsi/meta.json @@ -0,0 +1,38 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Made by RumiTiger", + "states": [ + { + "name": "icon" + }, + { + "name": "icon_empty" + }, + { + "name": "fill-1" + }, + { + "name": "fill-2" + }, + { + "name": "fill-3" + }, + { + "name": "fill-4" + }, + { + "name": "fill-5" + }, + { + "name": "fill-6" + }, + { + "name": "fill-7" + } + ] +} diff --git a/Resources/Textures/Objects/Consumable/Drinks/teaglass.rsi/fill-1.png b/Resources/Textures/Objects/Consumable/Drinks/teaglass.rsi/fill-1.png new file mode 100644 index 0000000000..05e6af71a7 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/teaglass.rsi/fill-1.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/teaglass.rsi/fill-2.png b/Resources/Textures/Objects/Consumable/Drinks/teaglass.rsi/fill-2.png new file mode 100644 index 0000000000..cdf98da4a6 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/teaglass.rsi/fill-2.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/teaglass.rsi/fill-3.png b/Resources/Textures/Objects/Consumable/Drinks/teaglass.rsi/fill-3.png new file mode 100644 index 0000000000..1f514088a2 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/teaglass.rsi/fill-3.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/teaglass.rsi/fill-4.png b/Resources/Textures/Objects/Consumable/Drinks/teaglass.rsi/fill-4.png new file mode 100644 index 0000000000..1a8ed41fde Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/teaglass.rsi/fill-4.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/teaglass.rsi/icon.png b/Resources/Textures/Objects/Consumable/Drinks/teaglass.rsi/icon.png index 727bc3f2fb..b31ed22db5 100644 Binary files a/Resources/Textures/Objects/Consumable/Drinks/teaglass.rsi/icon.png and b/Resources/Textures/Objects/Consumable/Drinks/teaglass.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/teaglass.rsi/icon_empty.png b/Resources/Textures/Objects/Consumable/Drinks/teaglass.rsi/icon_empty.png new file mode 100644 index 0000000000..abad93afb3 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/teaglass.rsi/icon_empty.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/teaglass.rsi/meta.json b/Resources/Textures/Objects/Consumable/Drinks/teaglass.rsi/meta.json index db0ac608ed..019c918be9 100644 --- a/Resources/Textures/Objects/Consumable/Drinks/teaglass.rsi/meta.json +++ b/Resources/Textures/Objects/Consumable/Drinks/teaglass.rsi/meta.json @@ -1 +1,29 @@ -{"version": 1, "size": {"x": 32, "y": 32}, "license": "CC-BY-SA-3.0", "copyright": "https://github.com/discordia-space/CEV-Eris/raw/f7aa28fd4b4d0386c3393d829681ebca526f1d2d/icons/obj/drinks.dmi", "states": [{"name": "icon"}]} \ No newline at end of file +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Made by RumiTiger", + "states": [ + { + "name": "icon" + }, + { + "name": "icon_empty" + }, + { + "name": "fill-1" + }, + { + "name": "fill-2" + }, + { + "name": "fill-3" + }, + { + "name": "fill-4" + } + ] +} diff --git a/Resources/Textures/Objects/Consumable/Drinks/tequillaglass.rsi/fill-1.png b/Resources/Textures/Objects/Consumable/Drinks/tequillaglass.rsi/fill-1.png index 202cfe304c..30399627af 100644 Binary files a/Resources/Textures/Objects/Consumable/Drinks/tequillaglass.rsi/fill-1.png and b/Resources/Textures/Objects/Consumable/Drinks/tequillaglass.rsi/fill-1.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/tequillaglass.rsi/fill-2.png b/Resources/Textures/Objects/Consumable/Drinks/tequillaglass.rsi/fill-2.png index 8cb7a51424..ed17e56693 100644 Binary files a/Resources/Textures/Objects/Consumable/Drinks/tequillaglass.rsi/fill-2.png and b/Resources/Textures/Objects/Consumable/Drinks/tequillaglass.rsi/fill-2.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/tequillaglass.rsi/fill-3.png b/Resources/Textures/Objects/Consumable/Drinks/tequillaglass.rsi/fill-3.png index b17d5ec6b6..a240f84f26 100644 Binary files a/Resources/Textures/Objects/Consumable/Drinks/tequillaglass.rsi/fill-3.png and b/Resources/Textures/Objects/Consumable/Drinks/tequillaglass.rsi/fill-3.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/tequillaglass.rsi/fill-4.png b/Resources/Textures/Objects/Consumable/Drinks/tequillaglass.rsi/fill-4.png deleted file mode 100644 index 642c08ff68..0000000000 Binary files a/Resources/Textures/Objects/Consumable/Drinks/tequillaglass.rsi/fill-4.png and /dev/null differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/tequillaglass.rsi/icon.png b/Resources/Textures/Objects/Consumable/Drinks/tequillaglass.rsi/icon.png index 4cac2a3aa4..b1510211cf 100644 Binary files a/Resources/Textures/Objects/Consumable/Drinks/tequillaglass.rsi/icon.png and b/Resources/Textures/Objects/Consumable/Drinks/tequillaglass.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/tequillaglass.rsi/icon_empty.png b/Resources/Textures/Objects/Consumable/Drinks/tequillaglass.rsi/icon_empty.png index 72476b77c0..dd4fd594d5 100644 Binary files a/Resources/Textures/Objects/Consumable/Drinks/tequillaglass.rsi/icon_empty.png and b/Resources/Textures/Objects/Consumable/Drinks/tequillaglass.rsi/icon_empty.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/tequillaglass.rsi/meta.json b/Resources/Textures/Objects/Consumable/Drinks/tequillaglass.rsi/meta.json index 20e933bb57..a2e79dfea1 100644 --- a/Resources/Textures/Objects/Consumable/Drinks/tequillaglass.rsi/meta.json +++ b/Resources/Textures/Objects/Consumable/Drinks/tequillaglass.rsi/meta.json @@ -1,31 +1,26 @@ { - "version": 1, - "size": + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Made by RumiTiger", + "states": [ { - "x": 32, - "y": 32 + "name": "icon" }, - "license": "CC-BY-SA-3.0", - "copyright": "https://github.com/discordia-space/CEV-Eris/raw/f7aa28fd4b4d0386c3393d829681ebca526f1d2d/icons/obj/drinks.dmi. Fill levels by Tayrtahn on GitHub.", - "states": - [ - { - "name": "icon" - }, - { - "name": "icon_empty" - }, - { - "name": "fill-1" - }, - { - "name": "fill-2" - }, - { - "name": "fill-3" - }, - { - "name": "fill-4" - } - ] + { + "name": "icon_empty" + }, + { + "name": "fill-1" + }, + { + "name": "fill-2" + }, + { + "name": "fill-3" + } + ] } diff --git a/Resources/Textures/Objects/Consumable/Drinks/tonicglass.rsi/fill-1.png b/Resources/Textures/Objects/Consumable/Drinks/tonicglass.rsi/fill-1.png new file mode 100644 index 0000000000..0082f2b814 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/tonicglass.rsi/fill-1.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/tonicglass.rsi/fill-2.png b/Resources/Textures/Objects/Consumable/Drinks/tonicglass.rsi/fill-2.png new file mode 100644 index 0000000000..cd667bfb33 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/tonicglass.rsi/fill-2.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/tonicglass.rsi/fill-3.png b/Resources/Textures/Objects/Consumable/Drinks/tonicglass.rsi/fill-3.png new file mode 100644 index 0000000000..0edd69b4db Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/tonicglass.rsi/fill-3.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/tonicglass.rsi/fill-4.png b/Resources/Textures/Objects/Consumable/Drinks/tonicglass.rsi/fill-4.png new file mode 100644 index 0000000000..f3cf6be999 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/tonicglass.rsi/fill-4.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/tonicglass.rsi/fill-5.png b/Resources/Textures/Objects/Consumable/Drinks/tonicglass.rsi/fill-5.png new file mode 100644 index 0000000000..3e1c0d68c1 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/tonicglass.rsi/fill-5.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/tonicglass.rsi/icon.png b/Resources/Textures/Objects/Consumable/Drinks/tonicglass.rsi/icon.png new file mode 100644 index 0000000000..438ed21e51 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/tonicglass.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/tonicglass.rsi/icon_empty.png b/Resources/Textures/Objects/Consumable/Drinks/tonicglass.rsi/icon_empty.png new file mode 100644 index 0000000000..1a306745d9 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/tonicglass.rsi/icon_empty.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/tonicglass.rsi/meta.json b/Resources/Textures/Objects/Consumable/Drinks/tonicglass.rsi/meta.json new file mode 100644 index 0000000000..ec55ebfa1f --- /dev/null +++ b/Resources/Textures/Objects/Consumable/Drinks/tonicglass.rsi/meta.json @@ -0,0 +1,32 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Made by RumiTiger", + "states": [ + { + "name": "icon" + }, + { + "name": "icon_empty" + }, + { + "name": "fill-1" + }, + { + "name": "fill-2" + }, + { + "name": "fill-3" + }, + { + "name": "fill-4" + }, + { + "name": "fill-5" + } + ] +} diff --git a/Resources/Textures/Objects/Consumable/Drinks/watermelonglass.rsi/fill-1.png b/Resources/Textures/Objects/Consumable/Drinks/watermelonglass.rsi/fill-1.png new file mode 100644 index 0000000000..533f3b8177 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/watermelonglass.rsi/fill-1.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/watermelonglass.rsi/fill-2.png b/Resources/Textures/Objects/Consumable/Drinks/watermelonglass.rsi/fill-2.png new file mode 100644 index 0000000000..7563af563a Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/watermelonglass.rsi/fill-2.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/watermelonglass.rsi/fill-3.png b/Resources/Textures/Objects/Consumable/Drinks/watermelonglass.rsi/fill-3.png new file mode 100644 index 0000000000..38c729ef38 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/watermelonglass.rsi/fill-3.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/watermelonglass.rsi/fill-4.png b/Resources/Textures/Objects/Consumable/Drinks/watermelonglass.rsi/fill-4.png new file mode 100644 index 0000000000..010224c48e Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/watermelonglass.rsi/fill-4.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/watermelonglass.rsi/icon.png b/Resources/Textures/Objects/Consumable/Drinks/watermelonglass.rsi/icon.png new file mode 100644 index 0000000000..e551fdb07b Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/watermelonglass.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/watermelonglass.rsi/icon_empty.png b/Resources/Textures/Objects/Consumable/Drinks/watermelonglass.rsi/icon_empty.png new file mode 100644 index 0000000000..ab9ba53863 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/watermelonglass.rsi/icon_empty.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/watermelonglass.rsi/meta.json b/Resources/Textures/Objects/Consumable/Drinks/watermelonglass.rsi/meta.json new file mode 100644 index 0000000000..019c918be9 --- /dev/null +++ b/Resources/Textures/Objects/Consumable/Drinks/watermelonglass.rsi/meta.json @@ -0,0 +1,29 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Made by RumiTiger", + "states": [ + { + "name": "icon" + }, + { + "name": "icon_empty" + }, + { + "name": "fill-1" + }, + { + "name": "fill-2" + }, + { + "name": "fill-3" + }, + { + "name": "fill-4" + } + ] +} diff --git a/Resources/Textures/Objects/Consumable/Drinks/xenobasher.rsi/fill-1.png b/Resources/Textures/Objects/Consumable/Drinks/xenobasher.rsi/fill-1.png new file mode 100644 index 0000000000..3aef3bd8ab Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/xenobasher.rsi/fill-1.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/xenobasher.rsi/fill-2.png b/Resources/Textures/Objects/Consumable/Drinks/xenobasher.rsi/fill-2.png new file mode 100644 index 0000000000..ce4411ee1e Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/xenobasher.rsi/fill-2.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/xenobasher.rsi/icon.png b/Resources/Textures/Objects/Consumable/Drinks/xenobasher.rsi/icon.png new file mode 100644 index 0000000000..ce4411ee1e Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/xenobasher.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/xenobasher.rsi/icon_empty.png b/Resources/Textures/Objects/Consumable/Drinks/xenobasher.rsi/icon_empty.png new file mode 100644 index 0000000000..85097b80cc Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/xenobasher.rsi/icon_empty.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/xenobasher.rsi/meta.json b/Resources/Textures/Objects/Consumable/Drinks/xenobasher.rsi/meta.json new file mode 100644 index 0000000000..9743bd909e --- /dev/null +++ b/Resources/Textures/Objects/Consumable/Drinks/xenobasher.rsi/meta.json @@ -0,0 +1,23 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Created by Hanzdegloker on github", + "states": [ + { + "name": "icon" + }, + { + "name": "icon_empty" + }, + { + "name": "fill-1" + }, + { + "name": "fill-2" + } + ] +} diff --git a/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar-gold.rsi/lit-equipped-MASK-vox.png b/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar-gold.rsi/lit-equipped-MASK-vox.png new file mode 100644 index 0000000000..c0723dcde6 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar-gold.rsi/lit-equipped-MASK-vox.png differ diff --git a/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar-gold.rsi/meta.json b/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar-gold.rsi/meta.json index 6b6687d88c..17b46355b6 100644 --- a/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar-gold.rsi/meta.json +++ b/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar-gold.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/bfc9c6ba8126ee8c41564d68c4bfb9ce37faa8f8 | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/bfc9c6ba8126ee8c41564d68c4bfb9ce37faa8f8 | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d | lit-equipped-MASK-vox & unlit-equipped-MASK-vox states taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/4638130fab5ff0e9faa220688811349d3297a33e", "size": { "x": 32, "y": 32 @@ -57,6 +57,56 @@ ] ] }, + { + "name": "unlit-equipped-MASK-vox", + "directions": 4 + }, + { + "name": "lit-equipped-MASK-vox", + "directions": 4, + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + }, { "name": "burnt-icon" }, diff --git a/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar-gold.rsi/unlit-equipped-MASK-vox.png b/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar-gold.rsi/unlit-equipped-MASK-vox.png new file mode 100644 index 0000000000..3cbf873967 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar-gold.rsi/unlit-equipped-MASK-vox.png differ diff --git a/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar.rsi/lit-equipped-MASK-vox.png b/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar.rsi/lit-equipped-MASK-vox.png new file mode 100644 index 0000000000..c0723dcde6 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar.rsi/lit-equipped-MASK-vox.png differ diff --git a/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar.rsi/meta.json b/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar.rsi/meta.json index 6b6687d88c..17b46355b6 100644 --- a/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar.rsi/meta.json +++ b/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/bfc9c6ba8126ee8c41564d68c4bfb9ce37faa8f8 | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/bfc9c6ba8126ee8c41564d68c4bfb9ce37faa8f8 | vulpkanin version taken from Paradise station at https://github.com/ParadiseSS13/Paradise/commit/f0fa4e1fd809482fbc104a310aa34cebf7df157d | lit-equipped-MASK-vox & unlit-equipped-MASK-vox states taken from /vg/station at commit https://github.com/vgstation-coders/vgstation13/commit/4638130fab5ff0e9faa220688811349d3297a33e", "size": { "x": 32, "y": 32 @@ -57,6 +57,56 @@ ] ] }, + { + "name": "unlit-equipped-MASK-vox", + "directions": 4 + }, + { + "name": "lit-equipped-MASK-vox", + "directions": 4, + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + }, { "name": "burnt-icon" }, diff --git a/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar.rsi/unlit-equipped-MASK-vox.png b/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar.rsi/unlit-equipped-MASK-vox.png new file mode 100644 index 0000000000..3cbf873967 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Smokeables/Cigars/cigar.rsi/unlit-equipped-MASK-vox.png differ diff --git a/Resources/Textures/Objects/Consumable/TrashDrinks/goldschlagerbottle_empty.rsi/icon.png b/Resources/Textures/Objects/Consumable/TrashDrinks/gildlagerbottle_empty.rsi/icon.png similarity index 100% rename from Resources/Textures/Objects/Consumable/TrashDrinks/goldschlagerbottle_empty.rsi/icon.png rename to Resources/Textures/Objects/Consumable/TrashDrinks/gildlagerbottle_empty.rsi/icon.png diff --git a/Resources/Textures/Objects/Consumable/TrashDrinks/goldschlagerbottle_empty.rsi/meta.json b/Resources/Textures/Objects/Consumable/TrashDrinks/gildlagerbottle_empty.rsi/meta.json similarity index 100% rename from Resources/Textures/Objects/Consumable/TrashDrinks/goldschlagerbottle_empty.rsi/meta.json rename to Resources/Textures/Objects/Consumable/TrashDrinks/gildlagerbottle_empty.rsi/meta.json diff --git a/Resources/Textures/Objects/Devices/chameleon_projector.rsi/icon.png b/Resources/Textures/Objects/Devices/chameleon_projector.rsi/icon.png new file mode 100644 index 0000000000..ce20b5eeee Binary files /dev/null and b/Resources/Textures/Objects/Devices/chameleon_projector.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Devices/chameleon_projector.rsi/inhand-left.png b/Resources/Textures/Objects/Devices/chameleon_projector.rsi/inhand-left.png new file mode 100644 index 0000000000..2d3863145b Binary files /dev/null and b/Resources/Textures/Objects/Devices/chameleon_projector.rsi/inhand-left.png differ diff --git a/Resources/Textures/Objects/Devices/chameleon_projector.rsi/inhand-right.png b/Resources/Textures/Objects/Devices/chameleon_projector.rsi/inhand-right.png new file mode 100644 index 0000000000..1704b9c3c1 Binary files /dev/null and b/Resources/Textures/Objects/Devices/chameleon_projector.rsi/inhand-right.png differ diff --git a/Resources/Textures/Objects/Devices/chameleon_projector.rsi/meta.json b/Resources/Textures/Objects/Devices/chameleon_projector.rsi/meta.json new file mode 100644 index 0000000000..3eb42e9e6f --- /dev/null +++ b/Resources/Textures/Objects/Devices/chameleon_projector.rsi/meta.json @@ -0,0 +1,32 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation at ", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon", + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Objects/Devices/flatpack.rsi/emitter.png b/Resources/Textures/Objects/Devices/flatpack.rsi/emitter.png new file mode 100644 index 0000000000..c663886f9c Binary files /dev/null and b/Resources/Textures/Objects/Devices/flatpack.rsi/emitter.png differ diff --git a/Resources/Textures/Objects/Devices/flatpack.rsi/meta.json b/Resources/Textures/Objects/Devices/flatpack.rsi/meta.json index 2d1ca37141..11f5d24e11 100644 --- a/Resources/Textures/Objects/Devices/flatpack.rsi/meta.json +++ b/Resources/Textures/Objects/Devices/flatpack.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC0-1.0", - "copyright": "Created by EmoGarbage404 (github) for SS14, solar-assembly-part taken from tgstation and modified at https://tgstation13.org/wiki/Guide_to_construction#Solar_Panels_and_Trackers, ame-part taken from vgstation at https://github.com/vgstation-coders/vgstation13/commit/1b7952787c06c21ef1623e494dcfe7cb1f46e041; singularity-generator, tesla-generator, radiation-collector, containment-field-generator, tesla-coil, grounding-rod inner icons made by lzk228", + "copyright": "service_music by erhardsteinhauer, Created by EmoGarbage404 (github) for SS14, solar-assembly-part taken from tgstation and modified at https://tgstation13.org/wiki/Guide_to_construction#Solar_Panels_and_Trackers, ame-part taken from vgstation at https://github.com/vgstation-coders/vgstation13/commit/1b7952787c06c21ef1623e494dcfe7cb1f46e041; singularity-generator, tesla-generator, radiation-collector, containment-field-generator, tesla-coil, grounding-rod inner icons made by lzk228; emitter made by pigeonpeas", "size": { "x": 32, "y": 32 @@ -39,6 +39,12 @@ }, { "name": "containment-field-generator" + }, + { + "name": "emitter" + }, + { + "name": "service_music" } ] } diff --git a/Resources/Textures/Objects/Devices/flatpack.rsi/service_music.png b/Resources/Textures/Objects/Devices/flatpack.rsi/service_music.png new file mode 100644 index 0000000000..db1a9a8aec Binary files /dev/null and b/Resources/Textures/Objects/Devices/flatpack.rsi/service_music.png differ diff --git a/Resources/Textures/Objects/Devices/jammer.rsi/jammer-on.png b/Resources/Textures/Objects/Devices/jammer.rsi/jammer-on.png deleted file mode 100644 index 987e571b26..0000000000 Binary files a/Resources/Textures/Objects/Devices/jammer.rsi/jammer-on.png and /dev/null differ diff --git a/Resources/Textures/Objects/Devices/jammer.rsi/jammer_high_charge.png b/Resources/Textures/Objects/Devices/jammer.rsi/jammer_high_charge.png new file mode 100644 index 0000000000..e288427e71 Binary files /dev/null and b/Resources/Textures/Objects/Devices/jammer.rsi/jammer_high_charge.png differ diff --git a/Resources/Textures/Objects/Devices/jammer.rsi/jammer_low_charge.png b/Resources/Textures/Objects/Devices/jammer.rsi/jammer_low_charge.png new file mode 100644 index 0000000000..0950a95df7 Binary files /dev/null and b/Resources/Textures/Objects/Devices/jammer.rsi/jammer_low_charge.png differ diff --git a/Resources/Textures/Objects/Devices/jammer.rsi/jammer_medium_charge.png b/Resources/Textures/Objects/Devices/jammer.rsi/jammer_medium_charge.png new file mode 100644 index 0000000000..7c12da8606 Binary files /dev/null and b/Resources/Textures/Objects/Devices/jammer.rsi/jammer_medium_charge.png differ diff --git a/Resources/Textures/Objects/Devices/jammer.rsi/meta.json b/Resources/Textures/Objects/Devices/jammer.rsi/meta.json index c5cc9f56d2..d837374a87 100644 --- a/Resources/Textures/Objects/Devices/jammer.rsi/meta.json +++ b/Resources/Textures/Objects/Devices/jammer.rsi/meta.json @@ -1,23 +1,33 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from Paradise at https://github.com/ParadiseSS13/Paradise/blob/93d9c70530c7299ef0af96fe2178096b2a62e036/icons/obj/device.dmi", + "copyright": "Taken from https://github.com/tgstation/tgstation/commit/c65da5a49477413310c81c460ea4b243a9f864dd with minor edits.", "size": { "x": 32, "y": 32 }, "states": [ { - "name": "jammer" + "name": "jammer", + "directions": 1 }, { - "name": "jammer-on", - "delays": [ - [ - 0.8, - 0.2 - ] + "name": "jammer_high_charge", + "directions": 1 + }, + { + "name": "jammer_medium_charge", + "directions": 1 + }, + { + "name": "jammer_low_charge", + "directions": 1, + "delays": [ + [ + 0.3, + 0.3 ] + ] } ] } diff --git a/Resources/Textures/Objects/Devices/pda.rsi/meta.json b/Resources/Textures/Objects/Devices/pda.rsi/meta.json index f77f8c0e71..1debaf447f 100644 --- a/Resources/Textures/Objects/Devices/pda.rsi/meta.json +++ b/Resources/Textures/Objects/Devices/pda.rsi/meta.json @@ -34,6 +34,27 @@ { "name": "pda-atmos" }, + { + "name": "pda-cmonano" + }, + { + "name": "pda-hopnano" + }, + { + "name": "pda-hosnano" + }, + { + "name": "pda-rdnano" + }, + { + "name": "pda-cenano" + }, + { + "name": "pda-qmnano" + }, + { + "name": "pda-captainnano" + }, { "name": "pda-bartender" }, @@ -142,7 +163,7 @@ "name": "pda-rd" }, { - "name": "pda-roboticist" + "name": "pda-robotics" }, { "name": "pda-science" diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-captainnano.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-captainnano.png new file mode 100644 index 0000000000..010eef5ffc Binary files /dev/null and b/Resources/Textures/Objects/Devices/pda.rsi/pda-captainnano.png differ diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-cenano.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-cenano.png new file mode 100644 index 0000000000..d01b5b830b Binary files /dev/null and b/Resources/Textures/Objects/Devices/pda.rsi/pda-cenano.png differ diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-cmonano.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-cmonano.png new file mode 100644 index 0000000000..0fab5ebc0f Binary files /dev/null and b/Resources/Textures/Objects/Devices/pda.rsi/pda-cmonano.png differ diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-hopnano.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-hopnano.png new file mode 100644 index 0000000000..41e6dd8bc9 Binary files /dev/null and b/Resources/Textures/Objects/Devices/pda.rsi/pda-hopnano.png differ diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-hosnano.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-hosnano.png new file mode 100644 index 0000000000..e03293f380 Binary files /dev/null and b/Resources/Textures/Objects/Devices/pda.rsi/pda-hosnano.png differ diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-qmnano.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-qmnano.png new file mode 100644 index 0000000000..e1479a7bcd Binary files /dev/null and b/Resources/Textures/Objects/Devices/pda.rsi/pda-qmnano.png differ diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-rdnano.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-rdnano.png new file mode 100644 index 0000000000..47c5a55e6c Binary files /dev/null and b/Resources/Textures/Objects/Devices/pda.rsi/pda-rdnano.png differ diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-roboticist.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-robotics.png similarity index 100% rename from Resources/Textures/Objects/Devices/pda.rsi/pda-roboticist.png rename to Resources/Textures/Objects/Devices/pda.rsi/pda-robotics.png diff --git a/Resources/Textures/Objects/Devices/securityhandy.rsi/meta.json b/Resources/Textures/Objects/Devices/securityhandy.rsi/meta.json new file mode 100644 index 0000000000..18a2d93272 --- /dev/null +++ b/Resources/Textures/Objects/Devices/securityhandy.rsi/meta.json @@ -0,0 +1,28 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from cev-eris and modified by Swept at https://github.com/discordia-space/CEV-Eris/commit/efce5b6c3be75458ce238dcc01510e8f8a653ca6", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "walkietalkie" + }, + { + "name": "walkietalkie-inhand-left", + "directions": 4 + }, + { + "name": "walkietalkie-inhand-right", + "directions": 4 + }, + { + "name": "walkietalkie-off" + }, + { + "name": "walkietalkie-on" + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Objects/Devices/securityhandy.rsi/walkietalkie-inhand-left.png b/Resources/Textures/Objects/Devices/securityhandy.rsi/walkietalkie-inhand-left.png new file mode 100644 index 0000000000..5815169abf Binary files /dev/null and b/Resources/Textures/Objects/Devices/securityhandy.rsi/walkietalkie-inhand-left.png differ diff --git a/Resources/Textures/Objects/Devices/securityhandy.rsi/walkietalkie-inhand-right.png b/Resources/Textures/Objects/Devices/securityhandy.rsi/walkietalkie-inhand-right.png new file mode 100644 index 0000000000..ed1e606cfb Binary files /dev/null and b/Resources/Textures/Objects/Devices/securityhandy.rsi/walkietalkie-inhand-right.png differ diff --git a/Resources/Textures/Objects/Devices/securityhandy.rsi/walkietalkie-off.png b/Resources/Textures/Objects/Devices/securityhandy.rsi/walkietalkie-off.png new file mode 100644 index 0000000000..2afdf8d962 Binary files /dev/null and b/Resources/Textures/Objects/Devices/securityhandy.rsi/walkietalkie-off.png differ diff --git a/Resources/Textures/Objects/Devices/securityhandy.rsi/walkietalkie-on.png b/Resources/Textures/Objects/Devices/securityhandy.rsi/walkietalkie-on.png new file mode 100644 index 0000000000..dfd79a6441 Binary files /dev/null and b/Resources/Textures/Objects/Devices/securityhandy.rsi/walkietalkie-on.png differ diff --git a/Resources/Textures/Objects/Devices/securityhandy.rsi/walkietalkie.png b/Resources/Textures/Objects/Devices/securityhandy.rsi/walkietalkie.png new file mode 100644 index 0000000000..12aaa3f2f0 Binary files /dev/null and b/Resources/Textures/Objects/Devices/securityhandy.rsi/walkietalkie.png differ diff --git a/Resources/Textures/Objects/Fun/bwoink_hammer.rsi/icon.png b/Resources/Textures/Objects/Fun/bwoink_hammer.rsi/icon.png new file mode 100644 index 0000000000..74702e12ef Binary files /dev/null and b/Resources/Textures/Objects/Fun/bwoink_hammer.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Fun/bwoink_hammer.rsi/inhand-left.png b/Resources/Textures/Objects/Fun/bwoink_hammer.rsi/inhand-left.png new file mode 100644 index 0000000000..2fdf4e67a3 Binary files /dev/null and b/Resources/Textures/Objects/Fun/bwoink_hammer.rsi/inhand-left.png differ diff --git a/Resources/Textures/Objects/Fun/bwoink_hammer.rsi/inhand-right.png b/Resources/Textures/Objects/Fun/bwoink_hammer.rsi/inhand-right.png new file mode 100644 index 0000000000..48d2596718 Binary files /dev/null and b/Resources/Textures/Objects/Fun/bwoink_hammer.rsi/inhand-right.png differ diff --git a/Resources/Textures/Objects/Fun/bwoink_hammer.rsi/meta.json b/Resources/Textures/Objects/Fun/bwoink_hammer.rsi/meta.json new file mode 100644 index 0000000000..a022711cc3 --- /dev/null +++ b/Resources/Textures/Objects/Fun/bwoink_hammer.rsi/meta.json @@ -0,0 +1,22 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "inhands made by Blu , Icon taken from beestation and belonged to original ss13 https://github.com/BeeStation/BeeStation-Hornet", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Objects/Fun/spraycans.rsi/clown-inhand-left.png b/Resources/Textures/Objects/Fun/spraycans.rsi/clown-inhand-left.png new file mode 100644 index 0000000000..d90d5b21b7 Binary files /dev/null and b/Resources/Textures/Objects/Fun/spraycans.rsi/clown-inhand-left.png differ diff --git a/Resources/Textures/Objects/Fun/spraycans.rsi/clown-inhand-right.png b/Resources/Textures/Objects/Fun/spraycans.rsi/clown-inhand-right.png new file mode 100644 index 0000000000..27b68e2cfe Binary files /dev/null and b/Resources/Textures/Objects/Fun/spraycans.rsi/clown-inhand-right.png differ diff --git a/Resources/Textures/Objects/Fun/spraycans.rsi/meta.json b/Resources/Textures/Objects/Fun/spraycans.rsi/meta.json index 0f883ee280..f34820cec4 100644 --- a/Resources/Textures/Objects/Fun/spraycans.rsi/meta.json +++ b/Resources/Textures/Objects/Fun/spraycans.rsi/meta.json @@ -58,9 +58,6 @@ { "name": "spray" }, - { - "name": "spray_cap" - }, { "name": "spray_cap_colors" }, @@ -70,6 +67,22 @@ { "name": "equipped-BELT", "directions": 4 + }, + { + "name": "clown-inhand-right", + "directions": 4 + }, + { + "name": "clown-inhand-left", + "directions": 4 + }, + { + "name": "spray-inhand-right", + "directions": 4 + }, + { + "name": "spray-inhand-left", + "directions": 4 } ] } diff --git a/Resources/Textures/Objects/Fun/spraycans.rsi/spray-inhand-left.png b/Resources/Textures/Objects/Fun/spraycans.rsi/spray-inhand-left.png new file mode 100644 index 0000000000..ad3ad959de Binary files /dev/null and b/Resources/Textures/Objects/Fun/spraycans.rsi/spray-inhand-left.png differ diff --git a/Resources/Textures/Objects/Fun/spraycans.rsi/spray-inhand-right.png b/Resources/Textures/Objects/Fun/spraycans.rsi/spray-inhand-right.png new file mode 100644 index 0000000000..353e47c56f Binary files /dev/null and b/Resources/Textures/Objects/Fun/spraycans.rsi/spray-inhand-right.png differ diff --git a/Resources/Textures/Objects/Fun/spraycans.rsi/spray_cap.png b/Resources/Textures/Objects/Fun/spraycans.rsi/spray_cap.png deleted file mode 100644 index 01d2d49bc0..0000000000 Binary files a/Resources/Textures/Objects/Fun/spraycans.rsi/spray_cap.png and /dev/null differ diff --git a/Resources/Textures/Objects/Fun/toys.rsi/meta.json b/Resources/Textures/Objects/Fun/toys.rsi/meta.json index 24345aadf3..cc03557e0b 100644 --- a/Resources/Textures/Objects/Fun/toys.rsi/meta.json +++ b/Resources/Textures/Objects/Fun/toys.rsi/meta.json @@ -386,6 +386,9 @@ { "name": "beachb-inhand-right", "directions": 4 + }, + { + "name": "shadowkin" } ] } \ No newline at end of file diff --git a/Resources/Textures/Objects/Fun/toys.rsi/shadowkin.png b/Resources/Textures/Objects/Fun/toys.rsi/shadowkin.png new file mode 100644 index 0000000000..df4118ecd4 Binary files /dev/null and b/Resources/Textures/Objects/Fun/toys.rsi/shadowkin.png differ diff --git a/Resources/Textures/Objects/Fun/whistle.rsi/meta.json b/Resources/Textures/Objects/Fun/whistle.rsi/meta.json deleted file mode 100644 index 59159ff617..0000000000 --- a/Resources/Textures/Objects/Fun/whistle.rsi/meta.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Made by Foleps (discord)", - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "securityWhistle" - } - ] -} diff --git a/Resources/Textures/Objects/Fun/whistles.rsi/equipped-NECK.png b/Resources/Textures/Objects/Fun/whistles.rsi/equipped-NECK.png new file mode 100644 index 0000000000..89fdd599b2 Binary files /dev/null and b/Resources/Textures/Objects/Fun/whistles.rsi/equipped-NECK.png differ diff --git a/Resources/Textures/Objects/Fun/whistles.rsi/meta.json b/Resources/Textures/Objects/Fun/whistles.rsi/meta.json new file mode 100644 index 0000000000..4f59ad60e0 --- /dev/null +++ b/Resources/Textures/Objects/Fun/whistles.rsi/meta.json @@ -0,0 +1,33 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "sec and whistle made by Foleps (discord), trench by Firewatchin (discord)", + "size": { + "x": 32, + "y": 32 + }, + + "states": [ + { + "name": "equipped-NECK", + "directions": 4 + }, + { + "name": "whistle" + }, + { + "name": "sec-equipped-NECK", + "directions": 4 + }, + { + "name": "sec" + }, + { + "name": "trench-equipped-NECK", + "directions": 4 + }, + { + "name": "trench" + } + ] +} diff --git a/Resources/Textures/Objects/Fun/whistles.rsi/sec-equipped-NECK.png b/Resources/Textures/Objects/Fun/whistles.rsi/sec-equipped-NECK.png new file mode 100644 index 0000000000..053c457f03 Binary files /dev/null and b/Resources/Textures/Objects/Fun/whistles.rsi/sec-equipped-NECK.png differ diff --git a/Resources/Textures/Objects/Fun/whistle.rsi/securityWhistle.png b/Resources/Textures/Objects/Fun/whistles.rsi/sec.png similarity index 100% rename from Resources/Textures/Objects/Fun/whistle.rsi/securityWhistle.png rename to Resources/Textures/Objects/Fun/whistles.rsi/sec.png diff --git a/Resources/Textures/Clothing/Neck/Misc/whistles.rsi/equipped-NECK.png b/Resources/Textures/Objects/Fun/whistles.rsi/trench-equipped-NECK.png similarity index 100% rename from Resources/Textures/Clothing/Neck/Misc/whistles.rsi/equipped-NECK.png rename to Resources/Textures/Objects/Fun/whistles.rsi/trench-equipped-NECK.png diff --git a/Resources/Textures/Clothing/Neck/Misc/whistles.rsi/icon.png b/Resources/Textures/Objects/Fun/whistles.rsi/trench.png similarity index 100% rename from Resources/Textures/Clothing/Neck/Misc/whistles.rsi/icon.png rename to Resources/Textures/Objects/Fun/whistles.rsi/trench.png diff --git a/Resources/Textures/Objects/Fun/whistles.rsi/whistle.png b/Resources/Textures/Objects/Fun/whistles.rsi/whistle.png new file mode 100644 index 0000000000..35db0ffca4 Binary files /dev/null and b/Resources/Textures/Objects/Fun/whistles.rsi/whistle.png differ diff --git a/Resources/Textures/Nyanotrasen/Objects/Materials/materials.rsi/bluespace.png b/Resources/Textures/Objects/Materials/materials.rsi/bluespace.png similarity index 100% rename from Resources/Textures/Nyanotrasen/Objects/Materials/materials.rsi/bluespace.png rename to Resources/Textures/Objects/Materials/materials.rsi/bluespace.png diff --git a/Resources/Textures/Nyanotrasen/Objects/Materials/materials.rsi/bluespace_2.png b/Resources/Textures/Objects/Materials/materials.rsi/bluespace_2.png similarity index 100% rename from Resources/Textures/Nyanotrasen/Objects/Materials/materials.rsi/bluespace_2.png rename to Resources/Textures/Objects/Materials/materials.rsi/bluespace_2.png diff --git a/Resources/Textures/Nyanotrasen/Objects/Materials/materials.rsi/bluespace_3.png b/Resources/Textures/Objects/Materials/materials.rsi/bluespace_3.png similarity index 100% rename from Resources/Textures/Nyanotrasen/Objects/Materials/materials.rsi/bluespace_3.png rename to Resources/Textures/Objects/Materials/materials.rsi/bluespace_3.png diff --git a/Resources/Textures/Objects/Materials/materials.rsi/meta.json b/Resources/Textures/Objects/Materials/materials.rsi/meta.json index f0307208e9..016ccddc29 100644 --- a/Resources/Textures/Objects/Materials/materials.rsi/meta.json +++ b/Resources/Textures/Objects/Materials/materials.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/c6e3401f2e7e1e55c57060cdf956a98ef1fefc24 , bear pelt remade by Alekshhh, wood sprite modified by MisterMecky, wood_2 and wood_3 made by MisterMecky based on wood sprite, cardboard sprites made by MisterMecky, bananium, bananium_1 and peel made by brainfood1183 (github) for ss14", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/c6e3401f2e7e1e55c57060cdf956a98ef1fefc24 , bear pelt remade by Alekshhh, wood sprite modified by MisterMecky, wood_2 and wood_3 made by MisterMecky based on wood sprite, cardboard sprites made by MisterMecky, bananium, bananium_1 and peel made by brainfood1183 (github) for ss14. Pyrotton sprites are drawn by Ubaser, using the cotton material sprites as a base. Bluespace and Normality taken from tgstation at https://github.com/tgstation/tgstation/commit/f8f4aeda930fcd0805ca4cc76d9bc9412a5b3428, reshaded by Aidenkrz", "size": { "x": 32, "y": 32 @@ -66,6 +66,15 @@ { "name": "cotton_3" }, + { + "name": "pyrotton" + }, + { + "name": "pyrotton_2" + }, + { + "name": "pyrotton_3" + }, { "name": "diamond" }, @@ -150,6 +159,24 @@ }, { "name": "bones_3" + }, + { + "name": "bluespace" + }, + { + "name": "bluespace_2" + }, + { + "name": "bluespace_3" + }, + { + "name": "normality" + }, + { + "name": "normality_2" + }, + { + "name": "normality_3" } ] } diff --git a/Resources/Textures/Objects/Materials/materials.rsi/normality.png b/Resources/Textures/Objects/Materials/materials.rsi/normality.png new file mode 100644 index 0000000000..3a91811667 Binary files /dev/null and b/Resources/Textures/Objects/Materials/materials.rsi/normality.png differ diff --git a/Resources/Textures/Objects/Materials/materials.rsi/normality_2.png b/Resources/Textures/Objects/Materials/materials.rsi/normality_2.png new file mode 100644 index 0000000000..fd672a9b09 Binary files /dev/null and b/Resources/Textures/Objects/Materials/materials.rsi/normality_2.png differ diff --git a/Resources/Textures/Objects/Materials/materials.rsi/normality_3.png b/Resources/Textures/Objects/Materials/materials.rsi/normality_3.png new file mode 100644 index 0000000000..8895885954 Binary files /dev/null and b/Resources/Textures/Objects/Materials/materials.rsi/normality_3.png differ diff --git a/Resources/Textures/Objects/Materials/materials.rsi/pyrotton.png b/Resources/Textures/Objects/Materials/materials.rsi/pyrotton.png new file mode 100644 index 0000000000..daa6701c39 Binary files /dev/null and b/Resources/Textures/Objects/Materials/materials.rsi/pyrotton.png differ diff --git a/Resources/Textures/Objects/Materials/materials.rsi/pyrotton_2.png b/Resources/Textures/Objects/Materials/materials.rsi/pyrotton_2.png new file mode 100644 index 0000000000..fcd2689557 Binary files /dev/null and b/Resources/Textures/Objects/Materials/materials.rsi/pyrotton_2.png differ diff --git a/Resources/Textures/Objects/Materials/materials.rsi/pyrotton_3.png b/Resources/Textures/Objects/Materials/materials.rsi/pyrotton_3.png new file mode 100644 index 0000000000..072ba2c6d1 Binary files /dev/null and b/Resources/Textures/Objects/Materials/materials.rsi/pyrotton_3.png differ diff --git a/Resources/Textures/Objects/Materials/ore.rsi/bluespace.png b/Resources/Textures/Objects/Materials/ore.rsi/bluespace.png new file mode 100644 index 0000000000..8aa4a33552 Binary files /dev/null and b/Resources/Textures/Objects/Materials/ore.rsi/bluespace.png differ diff --git a/Resources/Textures/Objects/Materials/ore.rsi/meta.json b/Resources/Textures/Objects/Materials/ore.rsi/meta.json index 98a10750bf..bc5dde9012 100644 --- a/Resources/Textures/Objects/Materials/ore.rsi/meta.json +++ b/Resources/Textures/Objects/Materials/ore.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-NC-SA-3.0", - "copyright": "silver, plasma taken from https://github.com/vgstation-coders/vgstation13 at commit f2ef221849675915a78fd92fe622c32ab740e085, spacequartz taken from https://github.com/goonstation/goonstation at commit b51daf824df46a3a1426475f982c09479818e522 and reshaded by Alekshhh, bananium; uranium; iron; gold; coal by Alekshhh", + "copyright": "silver, plasma taken from https://github.com/vgstation-coders/vgstation13 at commit f2ef221849675915a78fd92fe622c32ab740e085, spacequartz taken from https://github.com/goonstation/goonstation at commit b51daf824df46a3a1426475f982c09479818e522 and reshaded by Alekshhh, bananium; uranium; iron; gold; coal by Alekshhh. Bluespace and Normality taken from tgstation at https://github.com/tgstation/tgstation/commit/f8f4aeda930fcd0805ca4cc76d9bc9412a5b3428, reshaded by Aidenkrz", "size": { "x": 32, "y": 32 @@ -33,6 +33,12 @@ }, { "name": "salt" + }, + { + "name": "bluespace" + }, + { + "name": "normality" } ] } diff --git a/Resources/Textures/Objects/Materials/ore.rsi/normality.png b/Resources/Textures/Objects/Materials/ore.rsi/normality.png new file mode 100644 index 0000000000..e0bb3341b6 Binary files /dev/null and b/Resources/Textures/Objects/Materials/ore.rsi/normality.png differ diff --git a/Resources/Textures/Objects/Misc/chopstick.rsi/icon.png b/Resources/Textures/Objects/Misc/chopstick.rsi/icon.png new file mode 100644 index 0000000000..95acfb53cd Binary files /dev/null and b/Resources/Textures/Objects/Misc/chopstick.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Misc/chopstick.rsi/inhand-left.png b/Resources/Textures/Objects/Misc/chopstick.rsi/inhand-left.png new file mode 100644 index 0000000000..48fa05ce34 Binary files /dev/null and b/Resources/Textures/Objects/Misc/chopstick.rsi/inhand-left.png differ diff --git a/Resources/Textures/Objects/Misc/chopstick.rsi/inhand-right.png b/Resources/Textures/Objects/Misc/chopstick.rsi/inhand-right.png new file mode 100644 index 0000000000..51dc3961d5 Binary files /dev/null and b/Resources/Textures/Objects/Misc/chopstick.rsi/inhand-right.png differ diff --git a/Resources/Textures/Objects/Misc/chopstick.rsi/meta.json b/Resources/Textures/Objects/Misc/chopstick.rsi/meta.json new file mode 100644 index 0000000000..78e9149225 --- /dev/null +++ b/Resources/Textures/Objects/Misc/chopstick.rsi/meta.json @@ -0,0 +1,25 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from goonstation at https://github.com/goonstation/goonstation/pull/1179", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "paired" + } + ] + } \ No newline at end of file diff --git a/Resources/Textures/Objects/Misc/chopstick.rsi/paired.png b/Resources/Textures/Objects/Misc/chopstick.rsi/paired.png new file mode 100644 index 0000000000..220dc31ecb Binary files /dev/null and b/Resources/Textures/Objects/Misc/chopstick.rsi/paired.png differ diff --git a/Resources/Textures/Objects/Misc/mail_capsule.rsi/icon-cash.png b/Resources/Textures/Objects/Misc/mail_capsule.rsi/icon-cash.png new file mode 100644 index 0000000000..8ba33c95b5 Binary files /dev/null and b/Resources/Textures/Objects/Misc/mail_capsule.rsi/icon-cash.png differ diff --git a/Resources/Textures/Objects/Misc/mail_capsule.rsi/icon-empty.png b/Resources/Textures/Objects/Misc/mail_capsule.rsi/icon-empty.png new file mode 100644 index 0000000000..085b787410 Binary files /dev/null and b/Resources/Textures/Objects/Misc/mail_capsule.rsi/icon-empty.png differ diff --git a/Resources/Textures/Objects/Misc/mail_capsule.rsi/icon-food.png b/Resources/Textures/Objects/Misc/mail_capsule.rsi/icon-food.png new file mode 100644 index 0000000000..d08489cec8 Binary files /dev/null and b/Resources/Textures/Objects/Misc/mail_capsule.rsi/icon-food.png differ diff --git a/Resources/Textures/Objects/Misc/mail_capsule.rsi/icon-mail.png b/Resources/Textures/Objects/Misc/mail_capsule.rsi/icon-mail.png new file mode 100644 index 0000000000..b35a2acc0f Binary files /dev/null and b/Resources/Textures/Objects/Misc/mail_capsule.rsi/icon-mail.png differ diff --git a/Resources/Textures/Objects/Misc/mail_capsule.rsi/meta.json b/Resources/Textures/Objects/Misc/mail_capsule.rsi/meta.json new file mode 100644 index 0000000000..2e9bfc3617 --- /dev/null +++ b/Resources/Textures/Objects/Misc/mail_capsule.rsi/meta.json @@ -0,0 +1,26 @@ +{ + "version": 1, + "license": "CC-BY-SA-4.0", + "copyright": "Made for Frontier by erhardsteinhauer (discord)", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon-empty" + }, + { + "name": "icon-mail" + }, + { + "name": "icon-food" + }, + { + "name": "icon-cash" + }, + { + "name": "spent" + } + ] +} diff --git a/Resources/Textures/Objects/Misc/mail_capsule.rsi/spent.png b/Resources/Textures/Objects/Misc/mail_capsule.rsi/spent.png new file mode 100644 index 0000000000..acd0d0577f Binary files /dev/null and b/Resources/Textures/Objects/Misc/mail_capsule.rsi/spent.png differ diff --git a/Resources/Textures/Objects/Misc/utensils.rsi/bar_spoon-inhand-left.png b/Resources/Textures/Objects/Misc/utensils.rsi/bar_spoon-inhand-left.png new file mode 100644 index 0000000000..fba15fa255 Binary files /dev/null and b/Resources/Textures/Objects/Misc/utensils.rsi/bar_spoon-inhand-left.png differ diff --git a/Resources/Textures/Objects/Misc/utensils.rsi/bar_spoon-inhand-right.png b/Resources/Textures/Objects/Misc/utensils.rsi/bar_spoon-inhand-right.png new file mode 100644 index 0000000000..d2b32fd8b8 Binary files /dev/null and b/Resources/Textures/Objects/Misc/utensils.rsi/bar_spoon-inhand-right.png differ diff --git a/Resources/Textures/Objects/Misc/utensils.rsi/bar_spoon.png b/Resources/Textures/Objects/Misc/utensils.rsi/bar_spoon.png new file mode 100644 index 0000000000..f24ded7e6b Binary files /dev/null and b/Resources/Textures/Objects/Misc/utensils.rsi/bar_spoon.png differ diff --git a/Resources/Textures/Objects/Misc/utensils.rsi/meta.json b/Resources/Textures/Objects/Misc/utensils.rsi/meta.json index 30dd4e8564..77aeb5e3c1 100644 --- a/Resources/Textures/Objects/Misc/utensils.rsi/meta.json +++ b/Resources/Textures/Objects/Misc/utensils.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/commit/e1142f20f5e4661cb6845cfcf2dd69f864d67432 and modified by Swept", + "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/commit/e1142f20f5e4661cb6845cfcf2dd69f864d67432 and modified by Swept; Bar spoon by Dezzzix", "size": { "x": 32, "y": 32 @@ -40,6 +40,17 @@ }, { "name": "plastic_knife" + }, + { + "name": "bar_spoon" + }, + { + "name": "bar_spoon-inhand-left", + "directions": 4 + }, + { + "name": "bar_spoon-inhand-right", + "directions": 4 } ] } diff --git a/Resources/Textures/Objects/Power/portable_recharger.rsi/charging-equipped-BACKPACK.png b/Resources/Textures/Objects/Power/portable_recharger.rsi/charging-equipped-BACKPACK.png new file mode 100644 index 0000000000..500ad05704 Binary files /dev/null and b/Resources/Textures/Objects/Power/portable_recharger.rsi/charging-equipped-BACKPACK.png differ diff --git a/Resources/Textures/Objects/Power/portable_recharger.rsi/charging-unlit.png b/Resources/Textures/Objects/Power/portable_recharger.rsi/charging-unlit.png new file mode 100644 index 0000000000..6c902b7d7e Binary files /dev/null and b/Resources/Textures/Objects/Power/portable_recharger.rsi/charging-unlit.png differ diff --git a/Resources/Textures/Objects/Power/portable_recharger.rsi/charging.png b/Resources/Textures/Objects/Power/portable_recharger.rsi/charging.png new file mode 100644 index 0000000000..6105bb4ad3 Binary files /dev/null and b/Resources/Textures/Objects/Power/portable_recharger.rsi/charging.png differ diff --git a/Resources/Textures/Objects/Power/portable_recharger.rsi/inhand-left.png b/Resources/Textures/Objects/Power/portable_recharger.rsi/inhand-left.png new file mode 100644 index 0000000000..e3bc6d8299 Binary files /dev/null and b/Resources/Textures/Objects/Power/portable_recharger.rsi/inhand-left.png differ diff --git a/Resources/Textures/Objects/Power/portable_recharger.rsi/inhand-right.png b/Resources/Textures/Objects/Power/portable_recharger.rsi/inhand-right.png new file mode 100644 index 0000000000..8c65192231 Binary files /dev/null and b/Resources/Textures/Objects/Power/portable_recharger.rsi/inhand-right.png differ diff --git a/Resources/Textures/Objects/Power/portable_recharger.rsi/meta.json b/Resources/Textures/Objects/Power/portable_recharger.rsi/meta.json new file mode 100644 index 0000000000..794f491bdb --- /dev/null +++ b/Resources/Textures/Objects/Power/portable_recharger.rsi/meta.json @@ -0,0 +1,92 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Sprited by Lomovar", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "charging", + "delays": [ + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ] + ] + }, + { + "name": "charging-equipped-BACKPACK", + "directions": 4, + "delays": [ + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ], + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ] + ] + }, + { + "name": "charging-unlit", + "delays": [ + [ + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3 + ] + ] + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} + diff --git a/Resources/Textures/Objects/Specific/Chapel/quran.rsi/icon.png b/Resources/Textures/Objects/Specific/Chapel/quran.rsi/icon.png new file mode 100644 index 0000000000..0c710e8850 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Chapel/quran.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Specific/Chapel/quran.rsi/inhand-left.png b/Resources/Textures/Objects/Specific/Chapel/quran.rsi/inhand-left.png new file mode 100644 index 0000000000..f5c0eca315 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Chapel/quran.rsi/inhand-left.png differ diff --git a/Resources/Textures/Objects/Specific/Chapel/quran.rsi/inhand-right.png b/Resources/Textures/Objects/Specific/Chapel/quran.rsi/inhand-right.png new file mode 100644 index 0000000000..bc8b034185 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Chapel/quran.rsi/inhand-right.png differ diff --git a/Resources/Textures/Objects/Specific/Chapel/quran.rsi/meta.json b/Resources/Textures/Objects/Specific/Chapel/quran.rsi/meta.json new file mode 100644 index 0000000000..64d37670f1 --- /dev/null +++ b/Resources/Textures/Objects/Specific/Chapel/quran.rsi/meta.json @@ -0,0 +1,22 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/commit/40d89d11ea4a5cb81d61dc1018b46f4e7d32c62a, modified by Terraspark4941", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Objects/Specific/Hydroponics/chili.rsi/produce.png b/Resources/Textures/Objects/Specific/Hydroponics/chili.rsi/produce.png index 5033cdecc7..873f89e181 100644 Binary files a/Resources/Textures/Objects/Specific/Hydroponics/chili.rsi/produce.png and b/Resources/Textures/Objects/Specific/Hydroponics/chili.rsi/produce.png differ diff --git a/Resources/Textures/Objects/Specific/Hydroponics/chilly.rsi/produce.png b/Resources/Textures/Objects/Specific/Hydroponics/chilly.rsi/produce.png index 45ea8a32f5..b2c59dc31a 100644 Binary files a/Resources/Textures/Objects/Specific/Hydroponics/chilly.rsi/produce.png and b/Resources/Textures/Objects/Specific/Hydroponics/chilly.rsi/produce.png differ diff --git a/Resources/Textures/Objects/Specific/Hydroponics/pyrotton.rsi/dead.png b/Resources/Textures/Objects/Specific/Hydroponics/pyrotton.rsi/dead.png new file mode 100644 index 0000000000..39d4b40f4c Binary files /dev/null and b/Resources/Textures/Objects/Specific/Hydroponics/pyrotton.rsi/dead.png differ diff --git a/Resources/Textures/Objects/Specific/Hydroponics/pyrotton.rsi/harvest.png b/Resources/Textures/Objects/Specific/Hydroponics/pyrotton.rsi/harvest.png new file mode 100644 index 0000000000..1012ccfe03 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Hydroponics/pyrotton.rsi/harvest.png differ diff --git a/Resources/Textures/Objects/Specific/Hydroponics/pyrotton.rsi/meta.json b/Resources/Textures/Objects/Specific/Hydroponics/pyrotton.rsi/meta.json new file mode 100644 index 0000000000..4a6e3c94fc --- /dev/null +++ b/Resources/Textures/Objects/Specific/Hydroponics/pyrotton.rsi/meta.json @@ -0,0 +1,32 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Drawn by Ubaser, using the cotton sprites as a base.", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "dead" + }, + { + "name": "harvest" + }, + { + "name": "produce" + }, + { + "name": "seed" + }, + { + "name": "stage-1" + }, + { + "name": "stage-2" + }, + { + "name": "stage-3" + } + ] +} diff --git a/Resources/Textures/Objects/Specific/Hydroponics/pyrotton.rsi/produce.png b/Resources/Textures/Objects/Specific/Hydroponics/pyrotton.rsi/produce.png new file mode 100644 index 0000000000..72a98653c8 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Hydroponics/pyrotton.rsi/produce.png differ diff --git a/Resources/Textures/Objects/Specific/Hydroponics/pyrotton.rsi/seed.png b/Resources/Textures/Objects/Specific/Hydroponics/pyrotton.rsi/seed.png new file mode 100644 index 0000000000..6528f4f326 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Hydroponics/pyrotton.rsi/seed.png differ diff --git a/Resources/Textures/Objects/Specific/Hydroponics/pyrotton.rsi/stage-1.png b/Resources/Textures/Objects/Specific/Hydroponics/pyrotton.rsi/stage-1.png new file mode 100644 index 0000000000..c86f85f0d2 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Hydroponics/pyrotton.rsi/stage-1.png differ diff --git a/Resources/Textures/Objects/Specific/Hydroponics/pyrotton.rsi/stage-2.png b/Resources/Textures/Objects/Specific/Hydroponics/pyrotton.rsi/stage-2.png new file mode 100644 index 0000000000..7bc634ce83 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Hydroponics/pyrotton.rsi/stage-2.png differ diff --git a/Resources/Textures/Objects/Specific/Hydroponics/pyrotton.rsi/stage-3.png b/Resources/Textures/Objects/Specific/Hydroponics/pyrotton.rsi/stage-3.png new file mode 100644 index 0000000000..31aa7399f5 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Hydroponics/pyrotton.rsi/stage-3.png differ diff --git a/Resources/Textures/Objects/Specific/Hydroponics/rainbow_cannabis.rsi/dead.png b/Resources/Textures/Objects/Specific/Hydroponics/rainbow_cannabis.rsi/dead.png new file mode 100644 index 0000000000..2eb3c64ca9 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Hydroponics/rainbow_cannabis.rsi/dead.png differ diff --git a/Resources/Textures/Objects/Specific/Hydroponics/rainbow_cannabis.rsi/dried.png b/Resources/Textures/Objects/Specific/Hydroponics/rainbow_cannabis.rsi/dried.png new file mode 100644 index 0000000000..77f23e938c Binary files /dev/null and b/Resources/Textures/Objects/Specific/Hydroponics/rainbow_cannabis.rsi/dried.png differ diff --git a/Resources/Textures/Objects/Specific/Hydroponics/rainbow_cannabis.rsi/harvest.png b/Resources/Textures/Objects/Specific/Hydroponics/rainbow_cannabis.rsi/harvest.png new file mode 100644 index 0000000000..9702e5b488 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Hydroponics/rainbow_cannabis.rsi/harvest.png differ diff --git a/Resources/Textures/Objects/Specific/Hydroponics/rainbow_cannabis.rsi/meta.json b/Resources/Textures/Objects/Specific/Hydroponics/rainbow_cannabis.rsi/meta.json new file mode 100644 index 0000000000..e5bfeb99ef --- /dev/null +++ b/Resources/Textures/Objects/Specific/Hydroponics/rainbow_cannabis.rsi/meta.json @@ -0,0 +1,173 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from https://github.com/vgstation-coders/vgstation13 at 1dbcf389b0ec6b2c51b002df5fef8dd1519f8068 and modified by potato1234_x", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "dead" + }, + { + "name": "harvest", + "delays": + [ + [ + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08 + ] + ] + }, + { + "name": "powderpile_rainbow" + }, + { + "name": "produce", + "delays": + [ + [ + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08 + ] + ] + }, + { + "name": "dried" + }, + { + "name": "seed" + }, + { + "name": "stage-1", + "delays": + [ + [ + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08 + ] + ] + }, + { + "name": "stage-2", + "delays": + [ + [ + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08 + ] + ] + }, + { + "name": "stage-3", + "delays": + [ + [ + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08 + ] + ] + } + ] +} diff --git a/Resources/Textures/Objects/Specific/Hydroponics/rainbow_cannabis.rsi/powderpile_rainbow.png b/Resources/Textures/Objects/Specific/Hydroponics/rainbow_cannabis.rsi/powderpile_rainbow.png new file mode 100644 index 0000000000..15057f8785 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Hydroponics/rainbow_cannabis.rsi/powderpile_rainbow.png differ diff --git a/Resources/Textures/Objects/Specific/Hydroponics/rainbow_cannabis.rsi/produce.png b/Resources/Textures/Objects/Specific/Hydroponics/rainbow_cannabis.rsi/produce.png new file mode 100644 index 0000000000..400fed1b21 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Hydroponics/rainbow_cannabis.rsi/produce.png differ diff --git a/Resources/Textures/Objects/Specific/Hydroponics/rainbow_cannabis.rsi/seed.png b/Resources/Textures/Objects/Specific/Hydroponics/rainbow_cannabis.rsi/seed.png new file mode 100644 index 0000000000..fa194148f3 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Hydroponics/rainbow_cannabis.rsi/seed.png differ diff --git a/Resources/Textures/Objects/Specific/Hydroponics/rainbow_cannabis.rsi/stage-1.png b/Resources/Textures/Objects/Specific/Hydroponics/rainbow_cannabis.rsi/stage-1.png new file mode 100644 index 0000000000..8a3e17b098 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Hydroponics/rainbow_cannabis.rsi/stage-1.png differ diff --git a/Resources/Textures/Objects/Specific/Hydroponics/rainbow_cannabis.rsi/stage-2.png b/Resources/Textures/Objects/Specific/Hydroponics/rainbow_cannabis.rsi/stage-2.png new file mode 100644 index 0000000000..d30ed1ed26 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Hydroponics/rainbow_cannabis.rsi/stage-2.png differ diff --git a/Resources/Textures/Objects/Specific/Hydroponics/rainbow_cannabis.rsi/stage-3.png b/Resources/Textures/Objects/Specific/Hydroponics/rainbow_cannabis.rsi/stage-3.png new file mode 100644 index 0000000000..f4880b69e0 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Hydroponics/rainbow_cannabis.rsi/stage-3.png differ diff --git a/Resources/Textures/Nyanotrasen/Objects/Specific/Mail/mail.rsi/broken.png b/Resources/Textures/Objects/Specific/Mail/mail.rsi/broken.png similarity index 100% rename from Resources/Textures/Nyanotrasen/Objects/Specific/Mail/mail.rsi/broken.png rename to Resources/Textures/Objects/Specific/Mail/mail.rsi/broken.png diff --git a/Resources/Textures/Nyanotrasen/Objects/Specific/Mail/mail.rsi/fragile.png b/Resources/Textures/Objects/Specific/Mail/mail.rsi/fragile.png similarity index 100% rename from Resources/Textures/Nyanotrasen/Objects/Specific/Mail/mail.rsi/fragile.png rename to Resources/Textures/Objects/Specific/Mail/mail.rsi/fragile.png diff --git a/Resources/Textures/Nyanotrasen/Objects/Specific/Mail/mail.rsi/icon.png b/Resources/Textures/Objects/Specific/Mail/mail.rsi/icon.png similarity index 100% rename from Resources/Textures/Nyanotrasen/Objects/Specific/Mail/mail.rsi/icon.png rename to Resources/Textures/Objects/Specific/Mail/mail.rsi/icon.png diff --git a/Resources/Textures/Nyanotrasen/Objects/Specific/Mail/mail.rsi/locked.png b/Resources/Textures/Objects/Specific/Mail/mail.rsi/locked.png similarity index 100% rename from Resources/Textures/Nyanotrasen/Objects/Specific/Mail/mail.rsi/locked.png rename to Resources/Textures/Objects/Specific/Mail/mail.rsi/locked.png diff --git a/Resources/Textures/Nyanotrasen/Objects/Specific/Mail/mail.rsi/meta.json b/Resources/Textures/Objects/Specific/Mail/mail.rsi/meta.json similarity index 100% rename from Resources/Textures/Nyanotrasen/Objects/Specific/Mail/mail.rsi/meta.json rename to Resources/Textures/Objects/Specific/Mail/mail.rsi/meta.json diff --git a/Resources/Textures/Nyanotrasen/Objects/Specific/Mail/mail.rsi/postmark.png b/Resources/Textures/Objects/Specific/Mail/mail.rsi/postmark.png similarity index 100% rename from Resources/Textures/Nyanotrasen/Objects/Specific/Mail/mail.rsi/postmark.png rename to Resources/Textures/Objects/Specific/Mail/mail.rsi/postmark.png diff --git a/Resources/Textures/Nyanotrasen/Objects/Specific/Mail/mail.rsi/priority.png b/Resources/Textures/Objects/Specific/Mail/mail.rsi/priority.png similarity index 100% rename from Resources/Textures/Nyanotrasen/Objects/Specific/Mail/mail.rsi/priority.png rename to Resources/Textures/Objects/Specific/Mail/mail.rsi/priority.png diff --git a/Resources/Textures/Nyanotrasen/Objects/Specific/Mail/mail.rsi/priority_inactive.png b/Resources/Textures/Objects/Specific/Mail/mail.rsi/priority_inactive.png similarity index 100% rename from Resources/Textures/Nyanotrasen/Objects/Specific/Mail/mail.rsi/priority_inactive.png rename to Resources/Textures/Objects/Specific/Mail/mail.rsi/priority_inactive.png diff --git a/Resources/Textures/Nyanotrasen/Objects/Specific/Mail/mail.rsi/trash.png b/Resources/Textures/Objects/Specific/Mail/mail.rsi/trash.png similarity index 100% rename from Resources/Textures/Nyanotrasen/Objects/Specific/Mail/mail.rsi/trash.png rename to Resources/Textures/Objects/Specific/Mail/mail.rsi/trash.png diff --git a/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/broken.png b/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/broken.png new file mode 100644 index 0000000000..1c798c4075 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/broken.png differ diff --git a/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/fragile.png b/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/fragile.png new file mode 100644 index 0000000000..0917000cbe Binary files /dev/null and b/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/fragile.png differ diff --git a/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/icon.png b/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/icon.png new file mode 100644 index 0000000000..f3974ab116 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/inhand-left.png b/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/inhand-left.png new file mode 100644 index 0000000000..ccbc87cf3b Binary files /dev/null and b/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/inhand-left.png differ diff --git a/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/inhand-right.png b/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/inhand-right.png new file mode 100644 index 0000000000..ccbc87cf3b Binary files /dev/null and b/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/inhand-right.png differ diff --git a/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/locked.png b/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/locked.png new file mode 100644 index 0000000000..1cacaf1921 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/locked.png differ diff --git a/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/meta.json b/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/meta.json new file mode 100644 index 0000000000..ac5345ba1a --- /dev/null +++ b/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/meta.json @@ -0,0 +1,40 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation (obj/storage/closet.dmi, obj/service/bureaucracy.dmi), modified by Whatstone (Discord). broken, inhand-left, inhand-right by Whatstone.", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "broken" + }, + { + "name": "fragile" + }, + { + "name": "icon" + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "locked" + }, + { + "name": "priority" + }, + { + "name": "priority_inactive" + }, + { + "name": "trash" + } + ] +} diff --git a/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/priority.png b/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/priority.png new file mode 100644 index 0000000000..9c5a74ad10 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/priority.png differ diff --git a/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/priority_inactive.png b/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/priority_inactive.png new file mode 100644 index 0000000000..fc03165b57 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/priority_inactive.png differ diff --git a/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/trash.png b/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/trash.png new file mode 100644 index 0000000000..2ef4ee7233 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Mail/mail_large.rsi/trash.png differ diff --git a/Resources/Textures/Objects/Specific/Medical/Surgery/bone_gel.rsi/bone-gel.png b/Resources/Textures/Objects/Specific/Medical/Surgery/bone_gel.rsi/bone-gel.png new file mode 100644 index 0000000000..f66bf6cdf9 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Medical/Surgery/bone_gel.rsi/bone-gel.png differ diff --git a/Resources/Textures/Objects/Specific/Medical/Surgery/bone_gel.rsi/bone-gel_0.png b/Resources/Textures/Objects/Specific/Medical/Surgery/bone_gel.rsi/bone-gel_0.png new file mode 100644 index 0000000000..4399c73304 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Medical/Surgery/bone_gel.rsi/bone-gel_0.png differ diff --git a/Resources/Textures/Objects/Specific/Medical/Surgery/bone_gel.rsi/bone-gel_25.png b/Resources/Textures/Objects/Specific/Medical/Surgery/bone_gel.rsi/bone-gel_25.png new file mode 100644 index 0000000000..3d47302afd Binary files /dev/null and b/Resources/Textures/Objects/Specific/Medical/Surgery/bone_gel.rsi/bone-gel_25.png differ diff --git a/Resources/Textures/Objects/Specific/Medical/Surgery/bone_gel.rsi/bone-gel_50.png b/Resources/Textures/Objects/Specific/Medical/Surgery/bone_gel.rsi/bone-gel_50.png new file mode 100644 index 0000000000..744b46f1ef Binary files /dev/null and b/Resources/Textures/Objects/Specific/Medical/Surgery/bone_gel.rsi/bone-gel_50.png differ diff --git a/Resources/Textures/Objects/Specific/Medical/Surgery/bone_gel.rsi/bone-gel_75.png b/Resources/Textures/Objects/Specific/Medical/Surgery/bone_gel.rsi/bone-gel_75.png new file mode 100644 index 0000000000..4fbd689bb4 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Medical/Surgery/bone_gel.rsi/bone-gel_75.png differ diff --git a/Resources/Textures/Objects/Specific/Medical/Surgery/bone_gel.rsi/meta.json b/Resources/Textures/Objects/Specific/Medical/Surgery/bone_gel.rsi/meta.json new file mode 100644 index 0000000000..61ab52970b --- /dev/null +++ b/Resources/Textures/Objects/Specific/Medical/Surgery/bone_gel.rsi/meta.json @@ -0,0 +1,29 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from cmss13 at https://github.com/cmss13-devs/cmss13/blob/7917d83bac9cddd14c0ca0b457256b2683cc047f/icons/obj/items/surgery_tools.dmi", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "bone-gel" + }, + { + "name": "predator_bone-gel" + }, + { + "name": "bone-gel_75" + }, + { + "name": "bone-gel_50" + }, + { + "name": "bone-gel_25" + }, + { + "name": "bone-gel_0" + } + ] +} diff --git a/Resources/Textures/Objects/Specific/Medical/Surgery/bone_gel.rsi/predator_bone-gel.png b/Resources/Textures/Objects/Specific/Medical/Surgery/bone_gel.rsi/predator_bone-gel.png new file mode 100644 index 0000000000..4eb66f5843 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Medical/Surgery/bone_gel.rsi/predator_bone-gel.png differ diff --git a/Resources/Textures/Objects/Specific/Medical/Surgery/bonesetter.rsi/bonesetter.png b/Resources/Textures/Objects/Specific/Medical/Surgery/bonesetter.rsi/bonesetter.png new file mode 100644 index 0000000000..b876de0d28 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Medical/Surgery/bonesetter.rsi/bonesetter.png differ diff --git a/Resources/Textures/Objects/Specific/Medical/Surgery/bonesetter.rsi/meta.json b/Resources/Textures/Objects/Specific/Medical/Surgery/bonesetter.rsi/meta.json new file mode 100644 index 0000000000..227b276eda --- /dev/null +++ b/Resources/Textures/Objects/Specific/Medical/Surgery/bonesetter.rsi/meta.json @@ -0,0 +1,17 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from cmss13 at https://github.com/cmss13-devs/cmss13/blob/7917d83bac9cddd14c0ca0b457256b2683cc047f/icons/obj/items/surgery_tools.dmi", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "bonesetter" + }, + { + "name": "predator_bonesetter" + } + ] +} diff --git a/Resources/Textures/Objects/Specific/Medical/Surgery/bonesetter.rsi/predator_bonesetter.png b/Resources/Textures/Objects/Specific/Medical/Surgery/bonesetter.rsi/predator_bonesetter.png new file mode 100644 index 0000000000..c77903ff77 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Medical/Surgery/bonesetter.rsi/predator_bonesetter.png differ diff --git a/Resources/Textures/Objects/Specific/Medical/Surgery/manipulation.rsi/insertion.png b/Resources/Textures/Objects/Specific/Medical/Surgery/manipulation.rsi/insertion.png new file mode 100644 index 0000000000..961c3c641a Binary files /dev/null and b/Resources/Textures/Objects/Specific/Medical/Surgery/manipulation.rsi/insertion.png differ diff --git a/Resources/Textures/Objects/Specific/Medical/Surgery/manipulation.rsi/meta.json b/Resources/Textures/Objects/Specific/Medical/Surgery/manipulation.rsi/meta.json new file mode 100644 index 0000000000..af2b78c1ea --- /dev/null +++ b/Resources/Textures/Objects/Specific/Medical/Surgery/manipulation.rsi/meta.json @@ -0,0 +1,14 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from Aurorastation at https://github.com/Aurorastation/Aurora.3/commit/ac435b98e2d0a455ad319dca3bb9bfdc0cd8b051", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "insertion" + } + ] +} diff --git a/Resources/Textures/Objects/Specific/Robotics/borgmodule.rsi/icon-bomb.png b/Resources/Textures/Objects/Specific/Robotics/borgmodule.rsi/icon-bomb.png new file mode 100644 index 0000000000..5f876573f3 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Robotics/borgmodule.rsi/icon-bomb.png differ diff --git a/Resources/Textures/Objects/Specific/Robotics/borgmodule.rsi/meta.json b/Resources/Textures/Objects/Specific/Robotics/borgmodule.rsi/meta.json index ce8f8187b7..1b3eba668d 100644 --- a/Resources/Textures/Objects/Specific/Robotics/borgmodule.rsi/meta.json +++ b/Resources/Textures/Objects/Specific/Robotics/borgmodule.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC0-1.0", - "copyright": "Created by EmoGarbage404 (github) for Space Station 14. icon-construction.png created by deltanedas (github).", + "copyright": "Created by EmoGarbage404 (github) for Space Station 14. icon-construction.png created by deltanedas (github). syndicateborgbomb.png created by Mangohydra (github).", "size": { "x": 32, "y": 32 @@ -112,6 +112,9 @@ { "name": "janitor" }, + { + "name": "icon-bomb" + }, { "name": "medical" }, @@ -124,6 +127,9 @@ { "name": "service" }, + { + "name": "syndicateborgbomb" + }, { "name": "syndicate" } diff --git a/Resources/Textures/Objects/Specific/Robotics/borgmodule.rsi/syndicateborgbomb.png b/Resources/Textures/Objects/Specific/Robotics/borgmodule.rsi/syndicateborgbomb.png new file mode 100644 index 0000000000..c5238b96fc Binary files /dev/null and b/Resources/Textures/Objects/Specific/Robotics/borgmodule.rsi/syndicateborgbomb.png differ diff --git a/Resources/Textures/Objects/Storage/boxes.rsi/meta.json b/Resources/Textures/Objects/Storage/boxes.rsi/meta.json index b30927da33..0cba2f59d9 100644 --- a/Resources/Textures/Objects/Storage/boxes.rsi/meta.json +++ b/Resources/Textures/Objects/Storage/boxes.rsi/meta.json @@ -197,6 +197,9 @@ { "name": "shellslug" }, + { + "name": "shelluranium" + }, { "name": "shelltoy" }, diff --git a/Resources/Textures/Objects/Storage/boxes.rsi/shelluranium.png b/Resources/Textures/Objects/Storage/boxes.rsi/shelluranium.png new file mode 100644 index 0000000000..2a4e1dee15 Binary files /dev/null and b/Resources/Textures/Objects/Storage/boxes.rsi/shelluranium.png differ diff --git a/Resources/Textures/Objects/Tanks/emergency.rsi/equipped-SUITSTORAGE.png b/Resources/Textures/Objects/Tanks/emergency.rsi/equipped-SUITSTORAGE.png new file mode 100644 index 0000000000..a37261e3f7 Binary files /dev/null and b/Resources/Textures/Objects/Tanks/emergency.rsi/equipped-SUITSTORAGE.png differ diff --git a/Resources/Textures/Objects/Tanks/emergency.rsi/meta.json b/Resources/Textures/Objects/Tanks/emergency.rsi/meta.json index b4d4730957..c71ae8955e 100644 --- a/Resources/Textures/Objects/Tanks/emergency.rsi/meta.json +++ b/Resources/Textures/Objects/Tanks/emergency.rsi/meta.json @@ -71,6 +71,10 @@ { "name": "inhand-right", "directions": 4 + }, + { + "name": "equipped-SUITSTORAGE", + "directions": 4 } ] } diff --git a/Resources/Textures/Objects/Tanks/emergency_clown.rsi/equipped-SUITSTORAGE.png b/Resources/Textures/Objects/Tanks/emergency_clown.rsi/equipped-SUITSTORAGE.png new file mode 100644 index 0000000000..dd19aa5680 Binary files /dev/null and b/Resources/Textures/Objects/Tanks/emergency_clown.rsi/equipped-SUITSTORAGE.png differ diff --git a/Resources/Textures/Objects/Tanks/emergency_clown.rsi/meta.json b/Resources/Textures/Objects/Tanks/emergency_clown.rsi/meta.json index 489cf3072a..21d23712e9 100644 --- a/Resources/Textures/Objects/Tanks/emergency_clown.rsi/meta.json +++ b/Resources/Textures/Objects/Tanks/emergency_clown.rsi/meta.json @@ -66,6 +66,10 @@ { "name": "inhand-right", "directions": 4 + }, + { + "name": "equipped-SUITSTORAGE", + "directions": 4 } ] } diff --git a/Resources/Textures/Objects/Tanks/emergency_double.rsi/equipped-SUITSTORAGE.png b/Resources/Textures/Objects/Tanks/emergency_double.rsi/equipped-SUITSTORAGE.png new file mode 100644 index 0000000000..f139bc6db2 Binary files /dev/null and b/Resources/Textures/Objects/Tanks/emergency_double.rsi/equipped-SUITSTORAGE.png differ diff --git a/Resources/Textures/Objects/Tanks/emergency_double.rsi/meta.json b/Resources/Textures/Objects/Tanks/emergency_double.rsi/meta.json index 6b3765d794..b3ab67fad6 100644 --- a/Resources/Textures/Objects/Tanks/emergency_double.rsi/meta.json +++ b/Resources/Textures/Objects/Tanks/emergency_double.rsi/meta.json @@ -71,6 +71,10 @@ { "name": "inhand-right", "directions": 4 + }, + { + "name": "equipped-SUITSTORAGE", + "directions": 4 } ] } diff --git a/Resources/Textures/Objects/Tanks/emergency_double_red.rsi/equipped-SUITSTORAGE.png b/Resources/Textures/Objects/Tanks/emergency_double_red.rsi/equipped-SUITSTORAGE.png new file mode 100644 index 0000000000..7e2f947d34 Binary files /dev/null and b/Resources/Textures/Objects/Tanks/emergency_double_red.rsi/equipped-SUITSTORAGE.png differ diff --git a/Resources/Textures/Objects/Tanks/emergency_double_red.rsi/meta.json b/Resources/Textures/Objects/Tanks/emergency_double_red.rsi/meta.json index ce287bf277..74fc108c09 100644 --- a/Resources/Textures/Objects/Tanks/emergency_double_red.rsi/meta.json +++ b/Resources/Textures/Objects/Tanks/emergency_double_red.rsi/meta.json @@ -66,6 +66,10 @@ { "name": "inhand-right", "directions": 4 + }, + { + "name": "equipped-SUITSTORAGE", + "directions": 4 } ] } diff --git a/Resources/Textures/Objects/Tanks/emergency_extended.rsi/equipped-SUITSTORAGE.png b/Resources/Textures/Objects/Tanks/emergency_extended.rsi/equipped-SUITSTORAGE.png new file mode 100644 index 0000000000..0470c6895f Binary files /dev/null and b/Resources/Textures/Objects/Tanks/emergency_extended.rsi/equipped-SUITSTORAGE.png differ diff --git a/Resources/Textures/Objects/Tanks/emergency_extended.rsi/meta.json b/Resources/Textures/Objects/Tanks/emergency_extended.rsi/meta.json index a1661aee78..fe19dcec1c 100644 --- a/Resources/Textures/Objects/Tanks/emergency_extended.rsi/meta.json +++ b/Resources/Textures/Objects/Tanks/emergency_extended.rsi/meta.json @@ -66,6 +66,10 @@ { "name": "inhand-right", "directions": 4 + }, + { + "name": "equipped-SUITSTORAGE", + "directions": 4 } ] } diff --git a/Resources/Textures/Objects/Tanks/emergency_extended_red.rsi/equipped-SUITSTORAGE.png b/Resources/Textures/Objects/Tanks/emergency_extended_red.rsi/equipped-SUITSTORAGE.png new file mode 100644 index 0000000000..db800b2a3b Binary files /dev/null and b/Resources/Textures/Objects/Tanks/emergency_extended_red.rsi/equipped-SUITSTORAGE.png differ diff --git a/Resources/Textures/Objects/Tanks/emergency_extended_red.rsi/meta.json b/Resources/Textures/Objects/Tanks/emergency_extended_red.rsi/meta.json index ce287bf277..74fc108c09 100644 --- a/Resources/Textures/Objects/Tanks/emergency_extended_red.rsi/meta.json +++ b/Resources/Textures/Objects/Tanks/emergency_extended_red.rsi/meta.json @@ -66,6 +66,10 @@ { "name": "inhand-right", "directions": 4 + }, + { + "name": "equipped-SUITSTORAGE", + "directions": 4 } ] } diff --git a/Resources/Textures/Objects/Tanks/emergency_red.rsi/equipped-SUITSTORAGE.png b/Resources/Textures/Objects/Tanks/emergency_red.rsi/equipped-SUITSTORAGE.png new file mode 100644 index 0000000000..db800b2a3b Binary files /dev/null and b/Resources/Textures/Objects/Tanks/emergency_red.rsi/equipped-SUITSTORAGE.png differ diff --git a/Resources/Textures/Objects/Tanks/emergency_red.rsi/meta.json b/Resources/Textures/Objects/Tanks/emergency_red.rsi/meta.json index ce3d401ca2..d71cdae541 100644 --- a/Resources/Textures/Objects/Tanks/emergency_red.rsi/meta.json +++ b/Resources/Textures/Objects/Tanks/emergency_red.rsi/meta.json @@ -66,6 +66,10 @@ { "name": "inhand-right", "directions": 4 + }, + { + "name": "equipped-SUITSTORAGE", + "directions": 4 } ] } diff --git a/Resources/Textures/Objects/Tanks/emergency_yellow.rsi/equipped-SUITSTORAGE.png b/Resources/Textures/Objects/Tanks/emergency_yellow.rsi/equipped-SUITSTORAGE.png new file mode 100644 index 0000000000..0470c6895f Binary files /dev/null and b/Resources/Textures/Objects/Tanks/emergency_yellow.rsi/equipped-SUITSTORAGE.png differ diff --git a/Resources/Textures/Objects/Tanks/emergency_yellow.rsi/meta.json b/Resources/Textures/Objects/Tanks/emergency_yellow.rsi/meta.json index 6b3765d794..b3ab67fad6 100644 --- a/Resources/Textures/Objects/Tanks/emergency_yellow.rsi/meta.json +++ b/Resources/Textures/Objects/Tanks/emergency_yellow.rsi/meta.json @@ -71,6 +71,10 @@ { "name": "inhand-right", "directions": 4 + }, + { + "name": "equipped-SUITSTORAGE", + "directions": 4 } ] } diff --git a/Resources/Textures/Objects/Tanks/plasma.rsi/equipped-SUITSTORAGE.png b/Resources/Textures/Objects/Tanks/plasma.rsi/equipped-SUITSTORAGE.png new file mode 100644 index 0000000000..e282da2e6d Binary files /dev/null and b/Resources/Textures/Objects/Tanks/plasma.rsi/equipped-SUITSTORAGE.png differ diff --git a/Resources/Textures/Objects/Tanks/plasma.rsi/meta.json b/Resources/Textures/Objects/Tanks/plasma.rsi/meta.json index a88e367a85..2d3199f75c 100644 --- a/Resources/Textures/Objects/Tanks/plasma.rsi/meta.json +++ b/Resources/Textures/Objects/Tanks/plasma.rsi/meta.json @@ -21,6 +21,10 @@ { "name": "inhand-right", "directions": 4 + }, + { + "name": "equipped-SUITSTORAGE", + "directions": 4 } ] } diff --git a/Resources/Textures/Objects/Weapons/Guns/Battery/antiquelasergun.rsi/base.png b/Resources/Textures/Objects/Weapons/Guns/Battery/antiquelasergun.rsi/base.png index d6da03ed28..003858cf9d 100644 Binary files a/Resources/Textures/Objects/Weapons/Guns/Battery/antiquelasergun.rsi/base.png and b/Resources/Textures/Objects/Weapons/Guns/Battery/antiquelasergun.rsi/base.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Battery/antiquelasergun.rsi/icon.png b/Resources/Textures/Objects/Weapons/Guns/Battery/antiquelasergun.rsi/icon.png index d2e461be28..88be89b378 100644 Binary files a/Resources/Textures/Objects/Weapons/Guns/Battery/antiquelasergun.rsi/icon.png and b/Resources/Textures/Objects/Weapons/Guns/Battery/antiquelasergun.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Battery/antiquelasergun.rsi/mag-unshaded-0.png b/Resources/Textures/Objects/Weapons/Guns/Battery/antiquelasergun.rsi/mag-unshaded-0.png index 32fba189df..042d52f5d7 100644 Binary files a/Resources/Textures/Objects/Weapons/Guns/Battery/antiquelasergun.rsi/mag-unshaded-0.png and b/Resources/Textures/Objects/Weapons/Guns/Battery/antiquelasergun.rsi/mag-unshaded-0.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Battery/antiquelasergun.rsi/mag-unshaded-1.png b/Resources/Textures/Objects/Weapons/Guns/Battery/antiquelasergun.rsi/mag-unshaded-1.png index b0fa62d39f..5e64562bdc 100644 Binary files a/Resources/Textures/Objects/Weapons/Guns/Battery/antiquelasergun.rsi/mag-unshaded-1.png and b/Resources/Textures/Objects/Weapons/Guns/Battery/antiquelasergun.rsi/mag-unshaded-1.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Battery/antiquelasergun.rsi/mag-unshaded-2.png b/Resources/Textures/Objects/Weapons/Guns/Battery/antiquelasergun.rsi/mag-unshaded-2.png index 4ad441aa0b..df4767bff9 100644 Binary files a/Resources/Textures/Objects/Weapons/Guns/Battery/antiquelasergun.rsi/mag-unshaded-2.png and b/Resources/Textures/Objects/Weapons/Guns/Battery/antiquelasergun.rsi/mag-unshaded-2.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Battery/antiquelasergun.rsi/mag-unshaded-3.png b/Resources/Textures/Objects/Weapons/Guns/Battery/antiquelasergun.rsi/mag-unshaded-3.png index 1678a31534..fcbbc48af1 100644 Binary files a/Resources/Textures/Objects/Weapons/Guns/Battery/antiquelasergun.rsi/mag-unshaded-3.png and b/Resources/Textures/Objects/Weapons/Guns/Battery/antiquelasergun.rsi/mag-unshaded-3.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Battery/antiquelasergun.rsi/mag-unshaded-4.png b/Resources/Textures/Objects/Weapons/Guns/Battery/antiquelasergun.rsi/mag-unshaded-4.png index 5300c65d31..40e557d736 100644 Binary files a/Resources/Textures/Objects/Weapons/Guns/Battery/antiquelasergun.rsi/mag-unshaded-4.png and b/Resources/Textures/Objects/Weapons/Guns/Battery/antiquelasergun.rsi/mag-unshaded-4.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Battery/antiquelasergun.rsi/meta.json b/Resources/Textures/Objects/Weapons/Guns/Battery/antiquelasergun.rsi/meta.json index 04e2ac9309..86ecf2bff1 100644 --- a/Resources/Textures/Objects/Weapons/Guns/Battery/antiquelasergun.rsi/meta.json +++ b/Resources/Textures/Objects/Weapons/Guns/Battery/antiquelasergun.rsi/meta.json @@ -1,80 +1,80 @@ -{ - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "taken from tg station at commit https://github.com/tgstation/tgstation/commit/8b7f8ba6a3327c7381967c550f185dffafd11a57", - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "icon" - }, - { - "name": "base" - }, - { - "name": "mag-unshaded-0" - }, - { - "name": "mag-unshaded-1" - }, - { - "name": "mag-unshaded-2" - }, - { - "name": "mag-unshaded-3" - }, - { - "name": "mag-unshaded-4" - }, - { - "name": "inhand-left-0", - "directions": 4 - }, - { - "name": "inhand-right-0", - "directions": 4 - }, - { - "name": "inhand-left-1", - "directions": 4 - }, - { - "name": "inhand-right-1", - "directions": 4 - }, - { - "name": "inhand-left-2", - "directions": 4 - }, - { - "name": "inhand-right-2", - "directions": 4 - }, - { - "name": "inhand-left-3", - "directions": 4 - }, - { - "name": "inhand-right-3", - "directions": 4 - }, - { - "name": "inhand-left", - "directions": 4 - }, - { - "name": "inhand-right", - "directions": 4 - }, - { - "name": "equipped-BELT", - "directions": 4 - }, - { - "name": "equipped-SUITSTORAGE", - "directions": 4 - } - ] -} +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "icon by RiceMar1244 based on icon taken from tg station at commit https://github.com/tgstation/tgstation/commit/8b7f8ba6a3327c7381967c550f185dffafd11a57", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "base" + }, + { + "name": "mag-unshaded-0" + }, + { + "name": "mag-unshaded-1" + }, + { + "name": "mag-unshaded-2" + }, + { + "name": "mag-unshaded-3" + }, + { + "name": "mag-unshaded-4" + }, + { + "name": "inhand-left-0", + "directions": 4 + }, + { + "name": "inhand-right-0", + "directions": 4 + }, + { + "name": "inhand-left-1", + "directions": 4 + }, + { + "name": "inhand-right-1", + "directions": 4 + }, + { + "name": "inhand-left-2", + "directions": 4 + }, + { + "name": "inhand-right-2", + "directions": 4 + }, + { + "name": "inhand-left-3", + "directions": 4 + }, + { + "name": "inhand-right-3", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "equipped-BELT", + "directions": 4 + }, + { + "name": "equipped-SUITSTORAGE", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Objects/Weapons/Guns/Battery/laser_cannon.rsi/meta.json b/Resources/Textures/Objects/Weapons/Guns/Battery/laser_cannon.rsi/meta.json index 6657216b76..acbd06c270 100644 --- a/Resources/Textures/Objects/Weapons/Guns/Battery/laser_cannon.rsi/meta.json +++ b/Resources/Textures/Objects/Weapons/Guns/Battery/laser_cannon.rsi/meta.json @@ -1,77 +1,85 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from vgstation at https://github.com/vgstation-coders/vgstation13/commit/125c975f1b3bf9826b37029e9ab5a5f89e975a7e, backpack sprite by Peptide, backpack sling sprite edited by Boaz1111", + "copyright": "Taken from vgstation at https://github.com/vgstation-coders/vgstation13/commit/125c975f1b3bf9826b37029e9ab5a5f89e975a7e, backpack sprite by Peptide, backpack sling sprite edited by Boaz1111, wield sprites by RiceMar1244", "size": { "x": 32, "y": 32 }, - "states": [ - { - "name": "icon" - }, - { - "name": "base" - }, - { - "name": "mag-unshaded-1" - }, - { - "name": "mag-unshaded-2" - }, - { - "name": "mag-unshaded-3" - }, - { - "name": "mag-unshaded-4" - }, - { - "name": "inhand-left", - "directions": 4 - }, - { - "name": "inhand-right", - "directions": 4 - }, - { - "name": "0-inhand-left", - "directions": 4 - }, - { - "name": "0-inhand-right", - "directions": 4 - }, - { - "name": "25-inhand-left", - "directions": 4 - }, - { - "name": "25-inhand-right", - "directions": 4 - }, - { - "name": "50-inhand-left", - "directions": 4 - }, - { - "name": "50-inhand-right", - "directions": 4 - }, - { - "name": "75-inhand-left", - "directions": 4 - }, - { - "name": "75-inhand-right", - "directions": 4 - }, - { - "name": "equipped-BACKPACK", - "directions": 4 - }, - { - "name": "equipped-SUITSTORAGE", - "directions": 4 - } - ] + "states": [ + { + "name": "icon" + }, + { + "name": "base" + }, + { + "name": "mag-unshaded-1" + }, + { + "name": "mag-unshaded-2" + }, + { + "name": "mag-unshaded-3" + }, + { + "name": "mag-unshaded-4" + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "wielded-inhand-left", + "directions": 4 + }, + { + "name": "wielded-inhand-right", + "directions": 4 + }, + { + "name": "0-inhand-left", + "directions": 4 + }, + { + "name": "0-inhand-right", + "directions": 4 + }, + { + "name": "25-inhand-left", + "directions": 4 + }, + { + "name": "25-inhand-right", + "directions": 4 + }, + { + "name": "50-inhand-left", + "directions": 4 + }, + { + "name": "50-inhand-right", + "directions": 4 + }, + { + "name": "75-inhand-left", + "directions": 4 + }, + { + "name": "75-inhand-right", + "directions": 4 + }, + { + "name": "equipped-BACKPACK", + "directions": 4 + }, + { + "name": "equipped-SUITSTORAGE", + "directions": 4 + } + ] } diff --git a/Resources/Textures/Objects/Weapons/Guns/Battery/laser_cannon.rsi/wielded-inhand-left.png b/Resources/Textures/Objects/Weapons/Guns/Battery/laser_cannon.rsi/wielded-inhand-left.png new file mode 100644 index 0000000000..38327d9708 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Battery/laser_cannon.rsi/wielded-inhand-left.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Battery/laser_cannon.rsi/wielded-inhand-right.png b/Resources/Textures/Objects/Weapons/Guns/Battery/laser_cannon.rsi/wielded-inhand-right.png new file mode 100644 index 0000000000..4e0a357b84 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Battery/laser_cannon.rsi/wielded-inhand-right.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Battery/laser_gun.rsi/meta.json b/Resources/Textures/Objects/Weapons/Guns/Battery/laser_gun.rsi/meta.json index 7ec48da4fe..2b374ec4dd 100644 --- a/Resources/Textures/Objects/Weapons/Guns/Battery/laser_gun.rsi/meta.json +++ b/Resources/Textures/Objects/Weapons/Guns/Battery/laser_gun.rsi/meta.json @@ -1,45 +1,53 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from Polaris at https://github.com/PolarisSS13/Polaris/commit/9ded73fb85b9106d6bbf1c9a34d1d2fa27ee0e2e, backpack sprite by Peptide, backpack sling sprite edited by Boaz1111", + "copyright": "Taken from Polaris at https://github.com/PolarisSS13/Polaris/commit/9ded73fb85b9106d6bbf1c9a34d1d2fa27ee0e2e, backpack sprite by Peptide, backpack sling sprite edited by Boaz1111, wield sprites by RiceMar1244", "size": { "x": 32, "y": 32 }, "states": [ - { - "name": "icon" - }, - { - "name": "base" - }, - { - "name": "mag-unshaded-1" - }, - { - "name": "mag-unshaded-2" - }, - { - "name": "mag-unshaded-3" - }, - { - "name": "mag-unshaded-4" - }, - { - "name": "inhand-left", - "directions": 4 - }, - { - "name": "inhand-right", - "directions": 4 - }, - { - "name": "equipped-BACKPACK", - "directions": 4 - }, - { - "name": "equipped-SUITSTORAGE", - "directions": 4 - } + { + "name": "icon" + }, + { + "name": "base" + }, + { + "name": "mag-unshaded-1" + }, + { + "name": "mag-unshaded-2" + }, + { + "name": "mag-unshaded-3" + }, + { + "name": "mag-unshaded-4" + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "wielded-inhand-left", + "directions": 4 + }, + { + "name": "wielded-inhand-right", + "directions": 4 + }, + { + "name": "equipped-BACKPACK", + "directions": 4 + }, + { + "name": "equipped-SUITSTORAGE", + "directions": 4 + } ] } diff --git a/Resources/Textures/Objects/Weapons/Guns/Battery/laser_gun.rsi/wielded-inhand-left.png b/Resources/Textures/Objects/Weapons/Guns/Battery/laser_gun.rsi/wielded-inhand-left.png new file mode 100644 index 0000000000..126915fd27 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Battery/laser_gun.rsi/wielded-inhand-left.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Battery/laser_gun.rsi/wielded-inhand-right.png b/Resources/Textures/Objects/Weapons/Guns/Battery/laser_gun.rsi/wielded-inhand-right.png new file mode 100644 index 0000000000..93a9303c5e Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Battery/laser_gun.rsi/wielded-inhand-right.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Battery/pulse_carbine.rsi/meta.json b/Resources/Textures/Objects/Weapons/Guns/Battery/pulse_carbine.rsi/meta.json index 3b48bfc87c..434d66fcfd 100644 --- a/Resources/Textures/Objects/Weapons/Guns/Battery/pulse_carbine.rsi/meta.json +++ b/Resources/Textures/Objects/Weapons/Guns/Battery/pulse_carbine.rsi/meta.json @@ -1,45 +1,53 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/pull/27386/commits/4814da0f8e0d88f430c8b335e541e0a7734755a2 backpack sprite by Peptide (copy of pulse rifle), backpack sling sprite edited by Boaz1111", + "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/pull/27386/commits/4814da0f8e0d88f430c8b335e541e0a7734755a2 backpack sprite by Peptide (copy of pulse rifle), backpack sling sprite edited by Boaz1111, wield sprites by RiceMar1244", "size": { "x": 32, "y": 32 }, "states": [ - { - "name": "icon" - }, - { - "name": "base" - }, - { - "name": "mag-unshaded-1" - }, - { - "name": "mag-unshaded-2" - }, - { - "name": "mag-unshaded-3" - }, - { - "name": "mag-unshaded-4" - }, - { - "name": "inhand-left", - "directions": 4 - }, - { - "name": "inhand-right", - "directions": 4 - }, - { - "name": "equipped-BACKPACK", - "directions": 4 - }, - { - "name": "equipped-SUITSTORAGE", - "directions": 4 - } + { + "name": "icon" + }, + { + "name": "base" + }, + { + "name": "mag-unshaded-1" + }, + { + "name": "mag-unshaded-2" + }, + { + "name": "mag-unshaded-3" + }, + { + "name": "mag-unshaded-4" + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "wielded-inhand-left", + "directions": 4 + }, + { + "name": "wielded-inhand-right", + "directions": 4 + }, + { + "name": "equipped-BACKPACK", + "directions": 4 + }, + { + "name": "equipped-SUITSTORAGE", + "directions": 4 + } ] } diff --git a/Resources/Textures/Objects/Weapons/Guns/Battery/pulse_carbine.rsi/wielded-inhand-left.png b/Resources/Textures/Objects/Weapons/Guns/Battery/pulse_carbine.rsi/wielded-inhand-left.png new file mode 100644 index 0000000000..b9df7a2cb2 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Battery/pulse_carbine.rsi/wielded-inhand-left.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Battery/pulse_carbine.rsi/wielded-inhand-right.png b/Resources/Textures/Objects/Weapons/Guns/Battery/pulse_carbine.rsi/wielded-inhand-right.png new file mode 100644 index 0000000000..644db232d2 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Battery/pulse_carbine.rsi/wielded-inhand-right.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Battery/pulse_rifle.rsi/meta.json b/Resources/Textures/Objects/Weapons/Guns/Battery/pulse_rifle.rsi/meta.json index 1a906ef965..023ad90cfa 100644 --- a/Resources/Textures/Objects/Weapons/Guns/Battery/pulse_rifle.rsi/meta.json +++ b/Resources/Textures/Objects/Weapons/Guns/Battery/pulse_rifle.rsi/meta.json @@ -1,45 +1,53 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/pull/27386/commits/4814da0f8e0d88f430c8b335e541e0a7734755a2, backpack sprite by Peptide, backpack sling sprite edited by Boaz1111", + "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/pull/27386/commits/4814da0f8e0d88f430c8b335e541e0a7734755a2, backpack sprite by Peptide, backpack sling sprite edited by Boaz1111, wield sprites by RiceMar1244", "size": { "x": 32, "y": 32 }, "states": [ - { - "name": "icon" - }, - { - "name": "base" - }, - { - "name": "mag-unshaded-1" - }, - { - "name": "mag-unshaded-2" - }, - { - "name": "mag-unshaded-3" - }, - { - "name": "mag-unshaded-4" - }, - { - "name": "inhand-left", - "directions": 4 - }, - { - "name": "inhand-right", - "directions": 4 - }, - { - "name": "equipped-BACKPACK", - "directions": 4 - }, - { - "name": "equipped-SUITSTORAGE", - "directions": 4 - } + { + "name": "icon" + }, + { + "name": "base" + }, + { + "name": "mag-unshaded-1" + }, + { + "name": "mag-unshaded-2" + }, + { + "name": "mag-unshaded-3" + }, + { + "name": "mag-unshaded-4" + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "wielded-inhand-left", + "directions": 4 + }, + { + "name": "wielded-inhand-right", + "directions": 4 + }, + { + "name": "equipped-BACKPACK", + "directions": 4 + }, + { + "name": "equipped-SUITSTORAGE", + "directions": 4 + } ] } diff --git a/Resources/Textures/Objects/Weapons/Guns/Battery/pulse_rifle.rsi/wielded-inhand-left.png b/Resources/Textures/Objects/Weapons/Guns/Battery/pulse_rifle.rsi/wielded-inhand-left.png new file mode 100644 index 0000000000..b9df7a2cb2 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Battery/pulse_rifle.rsi/wielded-inhand-left.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Battery/pulse_rifle.rsi/wielded-inhand-right.png b/Resources/Textures/Objects/Weapons/Guns/Battery/pulse_rifle.rsi/wielded-inhand-right.png new file mode 100644 index 0000000000..644db232d2 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Battery/pulse_rifle.rsi/wielded-inhand-right.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Battery/xray.rsi/meta.json b/Resources/Textures/Objects/Weapons/Guns/Battery/xray.rsi/meta.json index 990ba51185..8766242121 100644 --- a/Resources/Textures/Objects/Weapons/Guns/Battery/xray.rsi/meta.json +++ b/Resources/Textures/Objects/Weapons/Guns/Battery/xray.rsi/meta.json @@ -1,54 +1,62 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from cev-eris at https://github.com/discordia-space/CEV-Eris/raw/167a810bc8534a56c74ffa8f1373acd3b1ac70ee/icons/obj/guns/energy/xray.dmi, backpack sprite by peptide, backpack sling sprite edited by Boaz1111", + "copyright": "Taken from cev-eris at https://github.com/discordia-space/CEV-Eris/raw/167a810bc8534a56c74ffa8f1373acd3b1ac70ee/icons/obj/guns/energy/xray.dmi, backpack sprite by peptide, backpack sling sprite edited by Boaz1111, wield sprites by RiceMar1244", "size": { "x": 32, "y": 32 }, - "states": [ - { - "name": "icon" - }, - { - "name": "base" - }, - { - "name": "mag-unshaded-0", - "delays": [ - [ - 0.3, - 0.3 - ] - ] - }, - { - "name": "mag-unshaded-1" - }, - { - "name": "mag-unshaded-2" - }, - { - "name": "mag-unshaded-3" - }, - { - "name": "mag-unshaded-4" - }, - { - "name": "inhand-left", - "directions": 4 - }, - { - "name": "inhand-right", - "directions": 4 - }, - { - "name": "equipped-BACKPACK", - "directions": 4 - }, - { - "name": "equipped-SUITSTORAGE", - "directions": 4 - } - ] + "states": [ + { + "name": "icon" + }, + { + "name": "base" + }, + { + "name": "mag-unshaded-0", + "delays": [ + [ + 0.3, + 0.3 + ] + ] + }, + { + "name": "mag-unshaded-1" + }, + { + "name": "mag-unshaded-2" + }, + { + "name": "mag-unshaded-3" + }, + { + "name": "mag-unshaded-4" + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "wielded-inhand-left", + "directions": 4 + }, + { + "name": "wielded-inhand-right", + "directions": 4 + }, + { + "name": "equipped-BACKPACK", + "directions": 4 + }, + { + "name": "equipped-SUITSTORAGE", + "directions": 4 + } + ] } diff --git a/Resources/Textures/Objects/Weapons/Guns/Battery/xray.rsi/wielded-inhand-left.png b/Resources/Textures/Objects/Weapons/Guns/Battery/xray.rsi/wielded-inhand-left.png new file mode 100644 index 0000000000..9fa2fafdff Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Battery/xray.rsi/wielded-inhand-left.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Battery/xray.rsi/wielded-inhand-right.png b/Resources/Textures/Objects/Weapons/Guns/Battery/xray.rsi/wielded-inhand-right.png new file mode 100644 index 0000000000..63615e2df1 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Battery/xray.rsi/wielded-inhand-right.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Launchers/china_lake.rsi/meta.json b/Resources/Textures/Objects/Weapons/Guns/Launchers/china_lake.rsi/meta.json index a5e32a3b39..d3b991783f 100644 --- a/Resources/Textures/Objects/Weapons/Guns/Launchers/china_lake.rsi/meta.json +++ b/Resources/Textures/Objects/Weapons/Guns/Launchers/china_lake.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-4.0", - "copyright": "Taken/modified from cev-eris at https://github.com/discordia-space/CEV-Eris/pull/6042/commits/64916c98f4847acc4adf3a2416bf78c005fd7dd7, https://github.com/discordia-space/CEV-Eris/blob/master/icons/obj/guns/launcher/grenadelauncher.dmi, backpack sprite by Peptide, backpack sling sprite edited by Boaz1111", + "copyright": "Taken/modified from cev-eris at https://github.com/discordia-space/CEV-Eris/pull/6042/commits/64916c98f4847acc4adf3a2416bf78c005fd7dd7, https://github.com/discordia-space/CEV-Eris/blob/master/icons/obj/guns/launcher/grenadelauncher.dmi, backpack sprite by Peptide, backpack sling sprite edited by Boaz1111, wield sprites by RiceMar1244", "size": { "x": 32, "y": 32 @@ -21,11 +21,19 @@ "name": "inhand-right", "directions": 4 }, + { + "name": "wielded-inhand-left", + "directions": 4 + }, + { + "name": "wielded-inhand-right", + "directions": 4 + }, { "name": "equipped-BACKPACK", "directions": 4 }, - { + { "name": "equipped-SUITSTORAGE", "directions": 4 } diff --git a/Resources/Textures/Objects/Weapons/Guns/Launchers/china_lake.rsi/wielded-inhand-left.png b/Resources/Textures/Objects/Weapons/Guns/Launchers/china_lake.rsi/wielded-inhand-left.png new file mode 100644 index 0000000000..a1d40617d2 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Launchers/china_lake.rsi/wielded-inhand-left.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Launchers/china_lake.rsi/wielded-inhand-right.png b/Resources/Textures/Objects/Weapons/Guns/Launchers/china_lake.rsi/wielded-inhand-right.png new file mode 100644 index 0000000000..146fe9b1c2 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Launchers/china_lake.rsi/wielded-inhand-right.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Launchers/mail.rsi/bolt-open.png b/Resources/Textures/Objects/Weapons/Guns/Launchers/mail.rsi/bolt-open.png new file mode 100644 index 0000000000..87c6d812ec Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Launchers/mail.rsi/bolt-open.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Launchers/mail.rsi/equipped-BACKPACK.png b/Resources/Textures/Objects/Weapons/Guns/Launchers/mail.rsi/equipped-BACKPACK.png new file mode 100644 index 0000000000..c17a68eb35 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Launchers/mail.rsi/equipped-BACKPACK.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Launchers/mail.rsi/equipped-BELT.png b/Resources/Textures/Objects/Weapons/Guns/Launchers/mail.rsi/equipped-BELT.png new file mode 100644 index 0000000000..59dc5f13ed Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Launchers/mail.rsi/equipped-BELT.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Launchers/mail.rsi/icon.png b/Resources/Textures/Objects/Weapons/Guns/Launchers/mail.rsi/icon.png new file mode 100644 index 0000000000..dd7b21e167 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Launchers/mail.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Launchers/mail.rsi/inhand-left.png b/Resources/Textures/Objects/Weapons/Guns/Launchers/mail.rsi/inhand-left.png new file mode 100644 index 0000000000..427389f40d Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Launchers/mail.rsi/inhand-left.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Launchers/mail.rsi/inhand-right.png b/Resources/Textures/Objects/Weapons/Guns/Launchers/mail.rsi/inhand-right.png new file mode 100644 index 0000000000..8e50ef04f9 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Launchers/mail.rsi/inhand-right.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Launchers/mail.rsi/meta.json b/Resources/Textures/Objects/Weapons/Guns/Launchers/mail.rsi/meta.json new file mode 100644 index 0000000000..464c22d1a7 --- /dev/null +++ b/Resources/Textures/Objects/Weapons/Guns/Launchers/mail.rsi/meta.json @@ -0,0 +1,33 @@ +{ + "version": 1, + "license": "CC-BY-SA-4.0", + "copyright": "Taken/modified from cev-eris at https://github.com/discordia-space/CEV-Eris/pull/6042/commits/64916c98f4847acc4adf3a2416bf78c005fd7dd7, https://github.com/discordia-space/CEV-Eris/blob/master/icons/obj/guns/launcher/grenadelauncher.dmi, backpack sprite by Peptide, resprited for mail gun by erhardsteinhauer (discord)", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "bolt-open" + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "equipped-BELT", + "directions": 4 + }, + { + "name": "equipped-BACKPACK", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Objects/Weapons/Guns/Pistols/N1984.rsi/base.png b/Resources/Textures/Objects/Weapons/Guns/Pistols/N1984.rsi/base.png index 984060bd91..8453ff926f 100644 Binary files a/Resources/Textures/Objects/Weapons/Guns/Pistols/N1984.rsi/base.png and b/Resources/Textures/Objects/Weapons/Guns/Pistols/N1984.rsi/base.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Pistols/N1984.rsi/bolt-open.png b/Resources/Textures/Objects/Weapons/Guns/Pistols/N1984.rsi/bolt-open.png index 92f2c6e391..9b260ad638 100644 Binary files a/Resources/Textures/Objects/Weapons/Guns/Pistols/N1984.rsi/bolt-open.png and b/Resources/Textures/Objects/Weapons/Guns/Pistols/N1984.rsi/bolt-open.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Pistols/N1984.rsi/icon.png b/Resources/Textures/Objects/Weapons/Guns/Pistols/N1984.rsi/icon.png index bbe6128065..4a114bc5bf 100644 Binary files a/Resources/Textures/Objects/Weapons/Guns/Pistols/N1984.rsi/icon.png and b/Resources/Textures/Objects/Weapons/Guns/Pistols/N1984.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Pistols/N1984.rsi/inhand-left.png b/Resources/Textures/Objects/Weapons/Guns/Pistols/N1984.rsi/inhand-left.png index 73e6d63b9c..ba1c8b9316 100644 Binary files a/Resources/Textures/Objects/Weapons/Guns/Pistols/N1984.rsi/inhand-left.png and b/Resources/Textures/Objects/Weapons/Guns/Pistols/N1984.rsi/inhand-left.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Pistols/N1984.rsi/inhand-right.png b/Resources/Textures/Objects/Weapons/Guns/Pistols/N1984.rsi/inhand-right.png index 443e6d2587..bb63cdda35 100644 Binary files a/Resources/Textures/Objects/Weapons/Guns/Pistols/N1984.rsi/inhand-right.png and b/Resources/Textures/Objects/Weapons/Guns/Pistols/N1984.rsi/inhand-right.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Pistols/N1984nl.rsi/base.png b/Resources/Textures/Objects/Weapons/Guns/Pistols/N1984nl.rsi/base.png new file mode 100644 index 0000000000..96926b74b3 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Pistols/N1984nl.rsi/base.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Pistols/N1984nl.rsi/bolt-open.png b/Resources/Textures/Objects/Weapons/Guns/Pistols/N1984nl.rsi/bolt-open.png new file mode 100644 index 0000000000..198796654d Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Pistols/N1984nl.rsi/bolt-open.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Pistols/N1984nl.rsi/equipped-BELT.png b/Resources/Textures/Objects/Weapons/Guns/Pistols/N1984nl.rsi/equipped-BELT.png new file mode 100644 index 0000000000..ab0d0a822f Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Pistols/N1984nl.rsi/equipped-BELT.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Pistols/N1984nl.rsi/equipped-SUITSTORAGE.png b/Resources/Textures/Objects/Weapons/Guns/Pistols/N1984nl.rsi/equipped-SUITSTORAGE.png new file mode 100644 index 0000000000..ab0d0a822f Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Pistols/N1984nl.rsi/equipped-SUITSTORAGE.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Pistols/N1984nl.rsi/icon.png b/Resources/Textures/Objects/Weapons/Guns/Pistols/N1984nl.rsi/icon.png new file mode 100644 index 0000000000..43cd3b8a6a Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Pistols/N1984nl.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Pistols/N1984nl.rsi/inhand-left.png b/Resources/Textures/Objects/Weapons/Guns/Pistols/N1984nl.rsi/inhand-left.png new file mode 100644 index 0000000000..6edffcf2ca Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Pistols/N1984nl.rsi/inhand-left.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Pistols/N1984nl.rsi/inhand-right.png b/Resources/Textures/Objects/Weapons/Guns/Pistols/N1984nl.rsi/inhand-right.png new file mode 100644 index 0000000000..bfaeff21d7 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Pistols/N1984nl.rsi/inhand-right.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Pistols/N1984nl.rsi/mag-0.png b/Resources/Textures/Objects/Weapons/Guns/Pistols/N1984nl.rsi/mag-0.png new file mode 100644 index 0000000000..c661c6dfdc Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Pistols/N1984nl.rsi/mag-0.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Pistols/N1984nl.rsi/meta.json b/Resources/Textures/Objects/Weapons/Guns/Pistols/N1984nl.rsi/meta.json new file mode 100644 index 0000000000..86cb488aec --- /dev/null +++ b/Resources/Textures/Objects/Weapons/Guns/Pistols/N1984nl.rsi/meta.json @@ -0,0 +1,39 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Created by IProduceWidgets (github) to make the style of desertrose at https://github.com/DesertRose2/desertrose/commit/120961e254d7f83a6e00a02c76e734f9e5019345, https://github.com/DesertRose2/desertrose/blob/master/icons/obj/guns/projectile.dmi, modified by VMSolidus(Github)", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "base" + }, + { + "name": "bolt-open" + }, + { + "name": "mag-0" + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "equipped-BELT", + "directions": 4 + }, + { + "name": "equipped-SUITSTORAGE", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Objects/Weapons/Guns/Pistols/mk58nl.rsi/base.png b/Resources/Textures/Objects/Weapons/Guns/Pistols/mk58nl.rsi/base.png new file mode 100644 index 0000000000..35fabdc39e Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Pistols/mk58nl.rsi/base.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Pistols/mk58nl.rsi/bolt-open.png b/Resources/Textures/Objects/Weapons/Guns/Pistols/mk58nl.rsi/bolt-open.png new file mode 100644 index 0000000000..fbaec30e80 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Pistols/mk58nl.rsi/bolt-open.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Pistols/mk58nl.rsi/equipped-BELT.png b/Resources/Textures/Objects/Weapons/Guns/Pistols/mk58nl.rsi/equipped-BELT.png new file mode 100644 index 0000000000..785f4037d7 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Pistols/mk58nl.rsi/equipped-BELT.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Pistols/mk58nl.rsi/equipped-SUITSTORAGE.png b/Resources/Textures/Objects/Weapons/Guns/Pistols/mk58nl.rsi/equipped-SUITSTORAGE.png new file mode 100644 index 0000000000..785f4037d7 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Pistols/mk58nl.rsi/equipped-SUITSTORAGE.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Pistols/mk58nl.rsi/icon.png b/Resources/Textures/Objects/Weapons/Guns/Pistols/mk58nl.rsi/icon.png new file mode 100644 index 0000000000..6374765cdc Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Pistols/mk58nl.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Pistols/mk58nl.rsi/inhand-left.png b/Resources/Textures/Objects/Weapons/Guns/Pistols/mk58nl.rsi/inhand-left.png new file mode 100644 index 0000000000..6edffcf2ca Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Pistols/mk58nl.rsi/inhand-left.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Pistols/mk58nl.rsi/inhand-right.png b/Resources/Textures/Objects/Weapons/Guns/Pistols/mk58nl.rsi/inhand-right.png new file mode 100644 index 0000000000..bfaeff21d7 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Pistols/mk58nl.rsi/inhand-right.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Pistols/mk58nl.rsi/mag-0.png b/Resources/Textures/Objects/Weapons/Guns/Pistols/mk58nl.rsi/mag-0.png new file mode 100644 index 0000000000..c38916a8a1 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Pistols/mk58nl.rsi/mag-0.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Pistols/mk58nl.rsi/meta.json b/Resources/Textures/Objects/Weapons/Guns/Pistols/mk58nl.rsi/meta.json new file mode 100644 index 0000000000..4a0d66bec3 --- /dev/null +++ b/Resources/Textures/Objects/Weapons/Guns/Pistols/mk58nl.rsi/meta.json @@ -0,0 +1,39 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from desertrose at https://github.com/DesertRose2/desertrose/commit/120961e254d7f83a6e00a02c76e734f9e5019345, https://github.com/DesertRose2/desertrose/blob/master/icons/obj/guns/projectile.dmi, modified by VMSolidus(Github)", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "base" + }, + { + "name": "bolt-open" + }, + { + "name": "mag-0" + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "equipped-BELT", + "directions": 4 + }, + { + "name": "equipped-SUITSTORAGE", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Objects/Weapons/Guns/Pistols/viper.rsi/meta.json b/Resources/Textures/Objects/Weapons/Guns/Pistols/viper.rsi/meta.json index 6ac14bfc8f..6e8276cc98 100644 --- a/Resources/Textures/Objects/Weapons/Guns/Pistols/viper.rsi/meta.json +++ b/Resources/Textures/Objects/Weapons/Guns/Pistols/viper.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken/modified from tgstation at https://github.com/tgstation/tgstation/pull/41804/commits/1baf679a544505960cebd071425f1df60669cdf3", + "copyright": "Taken/modified from tgstation at https://github.com/tgstation/tgstation/pull/41804/commits/1baf679a544505960cebd071425f1df60669cdf3, modified by VMSolidus", "size": { "x": 32, "y": 32 diff --git a/Resources/Textures/Objects/Weapons/Guns/Pistols/vipernl.rsi/base.png b/Resources/Textures/Objects/Weapons/Guns/Pistols/vipernl.rsi/base.png new file mode 100644 index 0000000000..8f71309a4f Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Pistols/vipernl.rsi/base.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Pistols/vipernl.rsi/bolt-open.png b/Resources/Textures/Objects/Weapons/Guns/Pistols/vipernl.rsi/bolt-open.png new file mode 100644 index 0000000000..f4cd1110b5 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Pistols/vipernl.rsi/bolt-open.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Pistols/vipernl.rsi/equipped-BELT.png b/Resources/Textures/Objects/Weapons/Guns/Pistols/vipernl.rsi/equipped-BELT.png new file mode 100644 index 0000000000..07f1f9fa26 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Pistols/vipernl.rsi/equipped-BELT.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Pistols/vipernl.rsi/equipped-SUITSTORAGE.png b/Resources/Textures/Objects/Weapons/Guns/Pistols/vipernl.rsi/equipped-SUITSTORAGE.png new file mode 100644 index 0000000000..07f1f9fa26 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Pistols/vipernl.rsi/equipped-SUITSTORAGE.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Pistols/vipernl.rsi/icon.png b/Resources/Textures/Objects/Weapons/Guns/Pistols/vipernl.rsi/icon.png new file mode 100644 index 0000000000..396600d94f Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Pistols/vipernl.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Pistols/vipernl.rsi/inhand-left.png b/Resources/Textures/Objects/Weapons/Guns/Pistols/vipernl.rsi/inhand-left.png new file mode 100644 index 0000000000..631d2edd37 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Pistols/vipernl.rsi/inhand-left.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Pistols/vipernl.rsi/inhand-right.png b/Resources/Textures/Objects/Weapons/Guns/Pistols/vipernl.rsi/inhand-right.png new file mode 100644 index 0000000000..9eb5acea48 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Pistols/vipernl.rsi/inhand-right.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Pistols/vipernl.rsi/mag-0.png b/Resources/Textures/Objects/Weapons/Guns/Pistols/vipernl.rsi/mag-0.png new file mode 100644 index 0000000000..ae6e64af1f Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Pistols/vipernl.rsi/mag-0.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Pistols/vipernl.rsi/meta.json b/Resources/Textures/Objects/Weapons/Guns/Pistols/vipernl.rsi/meta.json new file mode 100644 index 0000000000..6ac14bfc8f --- /dev/null +++ b/Resources/Textures/Objects/Weapons/Guns/Pistols/vipernl.rsi/meta.json @@ -0,0 +1,42 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken/modified from tgstation at https://github.com/tgstation/tgstation/pull/41804/commits/1baf679a544505960cebd071425f1df60669cdf3", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "base" + }, + { + "name": "bolt-open" + }, + { + "name": "mag-0" + }, + { + "name": "suppressor" + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "equipped-BELT", + "directions": 4 + }, + { + "name": "equipped-SUITSTORAGE", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Objects/Weapons/Guns/Pistols/vipernl.rsi/suppressor.png b/Resources/Textures/Objects/Weapons/Guns/Pistols/vipernl.rsi/suppressor.png new file mode 100644 index 0000000000..401301c43e Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Pistols/vipernl.rsi/suppressor.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Rifles/ak.rsi/meta.json b/Resources/Textures/Objects/Weapons/Guns/Rifles/ak.rsi/meta.json index 919d0f87ac..0257e70364 100644 --- a/Resources/Textures/Objects/Weapons/Guns/Rifles/ak.rsi/meta.json +++ b/Resources/Textures/Objects/Weapons/Guns/Rifles/ak.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken/modified from cev-eris at https://github.com/discordia-space/CEV-Eris/pull/6042/commits/64916c98f4847acc4adf3a2416bf78c005fd7dd7, https://github.com/discordia-space/CEV-Eris/raw/e1a3cbe9ba2e6e29b7f1cad1bb456b390aac936d/icons/obj/guns/projectile.dmi, backpack sprite by Peptide, backpack sling sprite edited by Boaz1111", + "copyright": "Taken/modified from cev-eris at https://github.com/discordia-space/CEV-Eris/pull/6042/commits/64916c98f4847acc4adf3a2416bf78c005fd7dd7, https://github.com/discordia-space/CEV-Eris/raw/e1a3cbe9ba2e6e29b7f1cad1bb456b390aac936d/icons/obj/guns/projectile.dmi, backpack sprite by Peptide, backpack sling sprite edited by Boaz1111, wield sprites by RiceMar1244", "size": { "x": 32, "y": 32 @@ -27,13 +27,21 @@ "name": "inhand-right", "directions": 4 }, + { + "name": "wielded-inhand-left", + "directions": 4 + }, + { + "name": "wielded-inhand-right", + "directions": 4 + }, { "name": "equipped-BACKPACK", "directions": 4 - }, - { - "name": "equipped-SUITSTORAGE", - "directions": 4 + }, + { + "name": "equipped-SUITSTORAGE", + "directions": 4 } ] } diff --git a/Resources/Textures/Objects/Weapons/Guns/Rifles/ak.rsi/wielded-inhand-left.png b/Resources/Textures/Objects/Weapons/Guns/Rifles/ak.rsi/wielded-inhand-left.png new file mode 100644 index 0000000000..2fa1f7811d Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Rifles/ak.rsi/wielded-inhand-left.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Rifles/ak.rsi/wielded-inhand-right.png b/Resources/Textures/Objects/Weapons/Guns/Rifles/ak.rsi/wielded-inhand-right.png new file mode 100644 index 0000000000..c358af57c3 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Rifles/ak.rsi/wielded-inhand-right.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Rifles/lecter.rsi/meta.json b/Resources/Textures/Objects/Weapons/Guns/Rifles/lecter.rsi/meta.json index 2f30ef18a8..c45db54aff 100644 --- a/Resources/Textures/Objects/Weapons/Guns/Rifles/lecter.rsi/meta.json +++ b/Resources/Textures/Objects/Weapons/Guns/Rifles/lecter.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken/modified from tgstation at https://github.com/tgstation/tgstation/pull/41393/commits/1e56473177d0994d163c9edca3d13d6e5b640cc4, https://github.com/tgstation/tgstation/tree/master/icons/obj/weapons/guns backpack sprite by Peptide (copy of carbine), backpack sling sprite edited by Boaz1111", + "copyright": "Taken/modified from tgstation at https://github.com/tgstation/tgstation/pull/41393/commits/1e56473177d0994d163c9edca3d13d6e5b640cc4, https://github.com/tgstation/tgstation/tree/master/icons/obj/weapons/guns backpack sprite by Peptide (copy of carbine), backpack sling sprite edited by Boaz1111, wield sprites by RiceMar1244", "size": { "x": 32, "y": 32 @@ -27,11 +27,19 @@ "name": "inhand-right", "directions": 4 }, + { + "name": "wielded-inhand-left", + "directions": 4 + }, + { + "name": "wielded-inhand-right", + "directions": 4 + }, { "name": "equipped-BACKPACK", "directions": 4 }, - { + { "name": "equipped-SUITSTORAGE", "directions": 4 } diff --git a/Resources/Textures/Objects/Weapons/Guns/Rifles/lecter.rsi/wielded-inhand-left.png b/Resources/Textures/Objects/Weapons/Guns/Rifles/lecter.rsi/wielded-inhand-left.png new file mode 100644 index 0000000000..42926b00f4 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Rifles/lecter.rsi/wielded-inhand-left.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Rifles/lecter.rsi/wielded-inhand-right.png b/Resources/Textures/Objects/Weapons/Guns/Rifles/lecter.rsi/wielded-inhand-right.png new file mode 100644 index 0000000000..fd58fca837 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Rifles/lecter.rsi/wielded-inhand-right.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/SMGs/c20r.rsi/meta.json b/Resources/Textures/Objects/Weapons/Guns/SMGs/c20r.rsi/meta.json index 8b1af3c504..2ff87fa291 100644 --- a/Resources/Textures/Objects/Weapons/Guns/SMGs/c20r.rsi/meta.json +++ b/Resources/Textures/Objects/Weapons/Guns/SMGs/c20r.rsi/meta.json @@ -45,11 +45,19 @@ "name": "inhand-right", "directions": 4 }, + { + "name": "wielded-inhand-left", + "directions": 4 + }, + { + "name": "wielded-inhand-right", + "directions": 4 + }, { "name": "equipped-BACKPACK", "directions": 4 }, - { + { "name": "equipped-SUITSTORAGE", "directions": 4 } diff --git a/Resources/Textures/Objects/Weapons/Guns/SMGs/c20r.rsi/wielded-inhand-left.png b/Resources/Textures/Objects/Weapons/Guns/SMGs/c20r.rsi/wielded-inhand-left.png new file mode 100644 index 0000000000..50b0e2a1d4 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/SMGs/c20r.rsi/wielded-inhand-left.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/SMGs/c20r.rsi/wielded-inhand-right.png b/Resources/Textures/Objects/Weapons/Guns/SMGs/c20r.rsi/wielded-inhand-right.png new file mode 100644 index 0000000000..eaf5e92574 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/SMGs/c20r.rsi/wielded-inhand-right.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/SMGs/drozd.rsi/meta.json b/Resources/Textures/Objects/Weapons/Guns/SMGs/drozd.rsi/meta.json index 25feebd92c..bd66b2c968 100644 --- a/Resources/Textures/Objects/Weapons/Guns/SMGs/drozd.rsi/meta.json +++ b/Resources/Textures/Objects/Weapons/Guns/SMGs/drozd.rsi/meta.json @@ -1,42 +1,50 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken/modified from cev-eris at https://github.com/discordia-space/CEV-Eris/raw/56cbafd6ad8c013ccd5472d6c4a0db790f7f872a/icons/obj/guns/projectile/drozd.dmi, sprite modification by Jaсkal 298/TaralGit, backpack sling sprite edited by Boaz1111", + "copyright": "Taken/modified from cev-eris at https://github.com/discordia-space/CEV-Eris/raw/56cbafd6ad8c013ccd5472d6c4a0db790f7f872a/icons/obj/guns/projectile/drozd.dmi, sprite modification by Jaсkal 298/TaralGit, backpack sling sprite edited by Boaz1111, wield sprites by RiceMar1244", "size": { "x": 32, "y": 32 }, - "states": [ - { - "name": "icon" - }, - { - "name": "base" - }, - { - "name": "bolt-open" - }, - { - "name": "mag-0" - }, - { - "name": "suppressor" - }, - { - "name": "inhand-left", - "directions": 4 - }, - { - "name": "inhand-right", - "directions": 4 - }, - { - "name": "equipped-BACKPACK", - "directions": 4 - }, - { - "name": "equipped-SUITSTORAGE", - "directions": 4 - } + "states": [ + { + "name": "icon" + }, + { + "name": "base" + }, + { + "name": "bolt-open" + }, + { + "name": "mag-0" + }, + { + "name": "suppressor" + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "wielded-inhand-left", + "directions": 4 + }, + { + "name": "wielded-inhand-right", + "directions": 4 + }, + { + "name": "equipped-BACKPACK", + "directions": 4 + }, + { + "name": "equipped-SUITSTORAGE", + "directions": 4 + } ] } diff --git a/Resources/Textures/Objects/Weapons/Guns/SMGs/drozd.rsi/wielded-inhand-left.png b/Resources/Textures/Objects/Weapons/Guns/SMGs/drozd.rsi/wielded-inhand-left.png new file mode 100644 index 0000000000..ee66cf3df9 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/SMGs/drozd.rsi/wielded-inhand-left.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/SMGs/drozd.rsi/wielded-inhand-right.png b/Resources/Textures/Objects/Weapons/Guns/SMGs/drozd.rsi/wielded-inhand-right.png new file mode 100644 index 0000000000..c5d6695605 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/SMGs/drozd.rsi/wielded-inhand-right.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Shotguns/blunderbuss.rsi/meta.json b/Resources/Textures/Objects/Weapons/Guns/Shotguns/blunderbuss.rsi/meta.json index f55fd2db20..b3ab88b772 100644 --- a/Resources/Textures/Objects/Weapons/Guns/Shotguns/blunderbuss.rsi/meta.json +++ b/Resources/Textures/Objects/Weapons/Guns/Shotguns/blunderbuss.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Made by brainfood1183(Github) inspired by an image created by rezierré#5003 (Discord)", + "copyright": "Made by brainfood1183(Github) inspired by an image created by rezierré#5003 (Discord), wield sprites by RiceMar1244", "size": { "x": 32, "y": 32 @@ -20,6 +20,14 @@ { "name": "inhand-left", "directions": 4 + }, + { + "name": "wielded-inhand-left", + "directions": 4 + }, + { + "name": "wielded-inhand-right", + "directions": 4 } ] } diff --git a/Resources/Textures/Objects/Weapons/Guns/Shotguns/blunderbuss.rsi/wielded-inhand-left.png b/Resources/Textures/Objects/Weapons/Guns/Shotguns/blunderbuss.rsi/wielded-inhand-left.png new file mode 100644 index 0000000000..da4a7f4f92 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Shotguns/blunderbuss.rsi/wielded-inhand-left.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Shotguns/blunderbuss.rsi/wielded-inhand-right.png b/Resources/Textures/Objects/Weapons/Guns/Shotguns/blunderbuss.rsi/wielded-inhand-right.png new file mode 100644 index 0000000000..ac02c47a84 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Shotguns/blunderbuss.rsi/wielded-inhand-right.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Shotguns/bulldog.rsi/meta.json b/Resources/Textures/Objects/Weapons/Guns/Shotguns/bulldog.rsi/meta.json index 5ef981dec9..8699d00b7d 100644 --- a/Resources/Textures/Objects/Weapons/Guns/Shotguns/bulldog.rsi/meta.json +++ b/Resources/Textures/Objects/Weapons/Guns/Shotguns/bulldog.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/commit/1b6831dab1e1a74c0d91f2229adb87abbb089d31, backpack sprite by Peptide, backpack sling sprite edited by Boaz1111", + "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/commit/1b6831dab1e1a74c0d91f2229adb87abbb089d31, backpack sprite by Peptide, backpack sling sprite edited by Boaz1111, wield sprites by RiceMar1244", "size": { "x": 32, "y": 32 @@ -27,11 +27,19 @@ "name": "inhand-right", "directions": 4 }, + { + "name": "wielded-inhand-left", + "directions": 4 + }, + { + "name": "wielded-inhand-right", + "directions": 4 + }, { "name": "equipped-BACKPACK", "directions": 4 }, - { + { "name": "equipped-SUITSTORAGE", "directions": 4 } diff --git a/Resources/Textures/Objects/Weapons/Guns/Shotguns/bulldog.rsi/wielded-inhand-left.png b/Resources/Textures/Objects/Weapons/Guns/Shotguns/bulldog.rsi/wielded-inhand-left.png new file mode 100644 index 0000000000..9a45848ec5 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Shotguns/bulldog.rsi/wielded-inhand-left.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Shotguns/bulldog.rsi/wielded-inhand-right.png b/Resources/Textures/Objects/Weapons/Guns/Shotguns/bulldog.rsi/wielded-inhand-right.png new file mode 100644 index 0000000000..7252681aba Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Shotguns/bulldog.rsi/wielded-inhand-right.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Shotguns/db_shotgun.rsi/meta.json b/Resources/Textures/Objects/Weapons/Guns/Shotguns/db_shotgun.rsi/meta.json index 27d7ebd665..3add3a471b 100644 --- a/Resources/Textures/Objects/Weapons/Guns/Shotguns/db_shotgun.rsi/meta.json +++ b/Resources/Textures/Objects/Weapons/Guns/Shotguns/db_shotgun.rsi/meta.json @@ -17,7 +17,7 @@ "name": "equipped-BACKPACK", "directions": 4 }, - { + { "name": "equipped-SUITSTORAGE", "directions": 4 } diff --git a/Resources/Textures/Objects/Weapons/Guns/Shotguns/inhands_64x.rsi/db-inhand-left.png b/Resources/Textures/Objects/Weapons/Guns/Shotguns/db_shotgun_inhands_64x.rsi/inhand-left.png similarity index 100% rename from Resources/Textures/Objects/Weapons/Guns/Shotguns/inhands_64x.rsi/db-inhand-left.png rename to Resources/Textures/Objects/Weapons/Guns/Shotguns/db_shotgun_inhands_64x.rsi/inhand-left.png diff --git a/Resources/Textures/Objects/Weapons/Guns/Shotguns/inhands_64x.rsi/db-inhand-right.png b/Resources/Textures/Objects/Weapons/Guns/Shotguns/db_shotgun_inhands_64x.rsi/inhand-right.png similarity index 100% rename from Resources/Textures/Objects/Weapons/Guns/Shotguns/inhands_64x.rsi/db-inhand-right.png rename to Resources/Textures/Objects/Weapons/Guns/Shotguns/db_shotgun_inhands_64x.rsi/inhand-right.png diff --git a/Resources/Textures/Objects/Weapons/Guns/Shotguns/db_shotgun_inhands_64x.rsi/meta.json b/Resources/Textures/Objects/Weapons/Guns/Shotguns/db_shotgun_inhands_64x.rsi/meta.json new file mode 100644 index 0000000000..6c1d1581e8 --- /dev/null +++ b/Resources/Textures/Objects/Weapons/Guns/Shotguns/db_shotgun_inhands_64x.rsi/meta.json @@ -0,0 +1,27 @@ +{ + "version": 1, + "license": "CC-BY-NC-4.0", + "copyright": "Taken from https://github.com/tgstation/tgstation/ at commit fb2d71495bfe81446159ef528534193d09dd8d34, wield sprites by RiceMar1244", + "size": { + "x": 64, + "y": 64 + }, + "states": [ + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "wielded-inhand-left", + "directions": 4 + }, + { + "name": "wielded-inhand-right", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Objects/Weapons/Guns/Shotguns/db_shotgun_inhands_64x.rsi/wielded-inhand-left.png b/Resources/Textures/Objects/Weapons/Guns/Shotguns/db_shotgun_inhands_64x.rsi/wielded-inhand-left.png new file mode 100644 index 0000000000..27c2e92359 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Shotguns/db_shotgun_inhands_64x.rsi/wielded-inhand-left.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Shotguns/db_shotgun_inhands_64x.rsi/wielded-inhand-right.png b/Resources/Textures/Objects/Weapons/Guns/Shotguns/db_shotgun_inhands_64x.rsi/wielded-inhand-right.png new file mode 100644 index 0000000000..78165792c7 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Shotguns/db_shotgun_inhands_64x.rsi/wielded-inhand-right.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Shotguns/enforcer.rsi/meta.json b/Resources/Textures/Objects/Weapons/Guns/Shotguns/enforcer.rsi/meta.json index f3a7f48582..42b8d94d6d 100644 --- a/Resources/Textures/Objects/Weapons/Guns/Shotguns/enforcer.rsi/meta.json +++ b/Resources/Textures/Objects/Weapons/Guns/Shotguns/enforcer.rsi/meta.json @@ -17,7 +17,7 @@ "name": "equipped-BACKPACK", "directions": 4 }, - { + { "name": "equipped-SUITSTORAGE", "directions": 4 } diff --git a/Resources/Textures/Objects/Weapons/Guns/Shotguns/inhands_64x.rsi/enforcer-inhand-left.png b/Resources/Textures/Objects/Weapons/Guns/Shotguns/enforcer_inhands_64x.rsi/inhand-left.png similarity index 100% rename from Resources/Textures/Objects/Weapons/Guns/Shotguns/inhands_64x.rsi/enforcer-inhand-left.png rename to Resources/Textures/Objects/Weapons/Guns/Shotguns/enforcer_inhands_64x.rsi/inhand-left.png diff --git a/Resources/Textures/Objects/Weapons/Guns/Shotguns/inhands_64x.rsi/enforcer-inhand-right.png b/Resources/Textures/Objects/Weapons/Guns/Shotguns/enforcer_inhands_64x.rsi/inhand-right.png similarity index 100% rename from Resources/Textures/Objects/Weapons/Guns/Shotguns/inhands_64x.rsi/enforcer-inhand-right.png rename to Resources/Textures/Objects/Weapons/Guns/Shotguns/enforcer_inhands_64x.rsi/inhand-right.png diff --git a/Resources/Textures/Objects/Weapons/Guns/Shotguns/enforcer_inhands_64x.rsi/meta.json b/Resources/Textures/Objects/Weapons/Guns/Shotguns/enforcer_inhands_64x.rsi/meta.json new file mode 100644 index 0000000000..6c1d1581e8 --- /dev/null +++ b/Resources/Textures/Objects/Weapons/Guns/Shotguns/enforcer_inhands_64x.rsi/meta.json @@ -0,0 +1,27 @@ +{ + "version": 1, + "license": "CC-BY-NC-4.0", + "copyright": "Taken from https://github.com/tgstation/tgstation/ at commit fb2d71495bfe81446159ef528534193d09dd8d34, wield sprites by RiceMar1244", + "size": { + "x": 64, + "y": 64 + }, + "states": [ + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "wielded-inhand-left", + "directions": 4 + }, + { + "name": "wielded-inhand-right", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Objects/Weapons/Guns/Shotguns/enforcer_inhands_64x.rsi/wielded-inhand-left.png b/Resources/Textures/Objects/Weapons/Guns/Shotguns/enforcer_inhands_64x.rsi/wielded-inhand-left.png new file mode 100644 index 0000000000..5f5b50d715 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Shotguns/enforcer_inhands_64x.rsi/wielded-inhand-left.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Shotguns/enforcer_inhands_64x.rsi/wielded-inhand-right.png b/Resources/Textures/Objects/Weapons/Guns/Shotguns/enforcer_inhands_64x.rsi/wielded-inhand-right.png new file mode 100644 index 0000000000..4810c9c375 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Shotguns/enforcer_inhands_64x.rsi/wielded-inhand-right.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Shotguns/inhands_64x.rsi/improvised-inhand-left.png b/Resources/Textures/Objects/Weapons/Guns/Shotguns/improvised_shotgun_inhands_64x.rsi/inhand-left.png similarity index 100% rename from Resources/Textures/Objects/Weapons/Guns/Shotguns/inhands_64x.rsi/improvised-inhand-left.png rename to Resources/Textures/Objects/Weapons/Guns/Shotguns/improvised_shotgun_inhands_64x.rsi/inhand-left.png diff --git a/Resources/Textures/Objects/Weapons/Guns/Shotguns/inhands_64x.rsi/improvised-inhand-right.png b/Resources/Textures/Objects/Weapons/Guns/Shotguns/improvised_shotgun_inhands_64x.rsi/inhand-right.png similarity index 100% rename from Resources/Textures/Objects/Weapons/Guns/Shotguns/inhands_64x.rsi/improvised-inhand-right.png rename to Resources/Textures/Objects/Weapons/Guns/Shotguns/improvised_shotgun_inhands_64x.rsi/inhand-right.png diff --git a/Resources/Textures/Objects/Weapons/Guns/Shotguns/improvised_shotgun_inhands_64x.rsi/meta.json b/Resources/Textures/Objects/Weapons/Guns/Shotguns/improvised_shotgun_inhands_64x.rsi/meta.json new file mode 100644 index 0000000000..6c1d1581e8 --- /dev/null +++ b/Resources/Textures/Objects/Weapons/Guns/Shotguns/improvised_shotgun_inhands_64x.rsi/meta.json @@ -0,0 +1,27 @@ +{ + "version": 1, + "license": "CC-BY-NC-4.0", + "copyright": "Taken from https://github.com/tgstation/tgstation/ at commit fb2d71495bfe81446159ef528534193d09dd8d34, wield sprites by RiceMar1244", + "size": { + "x": 64, + "y": 64 + }, + "states": [ + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "wielded-inhand-left", + "directions": 4 + }, + { + "name": "wielded-inhand-right", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Objects/Weapons/Guns/Shotguns/improvised_shotgun_inhands_64x.rsi/wielded-inhand-left.png b/Resources/Textures/Objects/Weapons/Guns/Shotguns/improvised_shotgun_inhands_64x.rsi/wielded-inhand-left.png new file mode 100644 index 0000000000..26a569bfc2 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Shotguns/improvised_shotgun_inhands_64x.rsi/wielded-inhand-left.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Shotguns/improvised_shotgun_inhands_64x.rsi/wielded-inhand-right.png b/Resources/Textures/Objects/Weapons/Guns/Shotguns/improvised_shotgun_inhands_64x.rsi/wielded-inhand-right.png new file mode 100644 index 0000000000..e2380308b4 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Shotguns/improvised_shotgun_inhands_64x.rsi/wielded-inhand-right.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Shotguns/inhands_64x.rsi/meta.json b/Resources/Textures/Objects/Weapons/Guns/Shotguns/inhands_64x.rsi/meta.json deleted file mode 100644 index fefe1f6eb7..0000000000 --- a/Resources/Textures/Objects/Weapons/Guns/Shotguns/inhands_64x.rsi/meta.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "version": 1, - "license": "CC-BY-NC-4.0", - "copyright": "Taken from https://github.com/tgstation/tgstation/ at commit fb2d71495bfe81446159ef528534193d09dd8d34, sawn-inhand states modified from db-inhand by Flareguy", - "size": { - "x": 64, - "y": 64 - }, - "states": [ - { - "name": "pump-inhand-left", - "directions": 4 - }, - { - "name": "pump-inhand-right", - "directions": 4 - }, - { - "name": "enforcer-inhand-left", - "directions": 4 - }, - { - "name": "enforcer-inhand-right", - "directions": 4 - }, - { - "name": "db-inhand-left", - "directions": 4 - }, - { - "name": "db-inhand-right", - "directions": 4 - }, - { - "name": "sawn-inhand-left", - "directions": 4 - }, - { - "name": "sawn-inhand-right", - "directions": 4 - }, - { - "name": "improvised-inhand-left", - "directions": 4 - }, - { - "name": "improvised-inhand-right", - "directions": 4 - } - ] -} diff --git a/Resources/Textures/Objects/Weapons/Guns/Shotguns/pump.rsi/meta.json b/Resources/Textures/Objects/Weapons/Guns/Shotguns/pump.rsi/meta.json index 27d7ebd665..3add3a471b 100644 --- a/Resources/Textures/Objects/Weapons/Guns/Shotguns/pump.rsi/meta.json +++ b/Resources/Textures/Objects/Weapons/Guns/Shotguns/pump.rsi/meta.json @@ -17,7 +17,7 @@ "name": "equipped-BACKPACK", "directions": 4 }, - { + { "name": "equipped-SUITSTORAGE", "directions": 4 } diff --git a/Resources/Textures/Objects/Weapons/Guns/Shotguns/inhands_64x.rsi/pump-inhand-left.png b/Resources/Textures/Objects/Weapons/Guns/Shotguns/pump_inhands_64x.rsi/inhand-left.png similarity index 100% rename from Resources/Textures/Objects/Weapons/Guns/Shotguns/inhands_64x.rsi/pump-inhand-left.png rename to Resources/Textures/Objects/Weapons/Guns/Shotguns/pump_inhands_64x.rsi/inhand-left.png diff --git a/Resources/Textures/Objects/Weapons/Guns/Shotguns/inhands_64x.rsi/pump-inhand-right.png b/Resources/Textures/Objects/Weapons/Guns/Shotguns/pump_inhands_64x.rsi/inhand-right.png similarity index 100% rename from Resources/Textures/Objects/Weapons/Guns/Shotguns/inhands_64x.rsi/pump-inhand-right.png rename to Resources/Textures/Objects/Weapons/Guns/Shotguns/pump_inhands_64x.rsi/inhand-right.png diff --git a/Resources/Textures/Objects/Weapons/Guns/Shotguns/pump_inhands_64x.rsi/meta.json b/Resources/Textures/Objects/Weapons/Guns/Shotguns/pump_inhands_64x.rsi/meta.json new file mode 100644 index 0000000000..6c1d1581e8 --- /dev/null +++ b/Resources/Textures/Objects/Weapons/Guns/Shotguns/pump_inhands_64x.rsi/meta.json @@ -0,0 +1,27 @@ +{ + "version": 1, + "license": "CC-BY-NC-4.0", + "copyright": "Taken from https://github.com/tgstation/tgstation/ at commit fb2d71495bfe81446159ef528534193d09dd8d34, wield sprites by RiceMar1244", + "size": { + "x": 64, + "y": 64 + }, + "states": [ + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "wielded-inhand-left", + "directions": 4 + }, + { + "name": "wielded-inhand-right", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Objects/Weapons/Guns/Shotguns/pump_inhands_64x.rsi/wielded-inhand-left.png b/Resources/Textures/Objects/Weapons/Guns/Shotguns/pump_inhands_64x.rsi/wielded-inhand-left.png new file mode 100644 index 0000000000..09fbaae551 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Shotguns/pump_inhands_64x.rsi/wielded-inhand-left.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Shotguns/pump_inhands_64x.rsi/wielded-inhand-right.png b/Resources/Textures/Objects/Weapons/Guns/Shotguns/pump_inhands_64x.rsi/wielded-inhand-right.png new file mode 100644 index 0000000000..00b475f6b4 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Shotguns/pump_inhands_64x.rsi/wielded-inhand-right.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Shotguns/inhands_64x.rsi/sawn-inhand-left.png b/Resources/Textures/Objects/Weapons/Guns/Shotguns/sawn_inhands_64x.rsi/inhand-left.png similarity index 100% rename from Resources/Textures/Objects/Weapons/Guns/Shotguns/inhands_64x.rsi/sawn-inhand-left.png rename to Resources/Textures/Objects/Weapons/Guns/Shotguns/sawn_inhands_64x.rsi/inhand-left.png diff --git a/Resources/Textures/Objects/Weapons/Guns/Shotguns/inhands_64x.rsi/sawn-inhand-right.png b/Resources/Textures/Objects/Weapons/Guns/Shotguns/sawn_inhands_64x.rsi/inhand-right.png similarity index 100% rename from Resources/Textures/Objects/Weapons/Guns/Shotguns/inhands_64x.rsi/sawn-inhand-right.png rename to Resources/Textures/Objects/Weapons/Guns/Shotguns/sawn_inhands_64x.rsi/inhand-right.png diff --git a/Resources/Textures/Objects/Weapons/Guns/Shotguns/sawn_inhands_64x.rsi/meta.json b/Resources/Textures/Objects/Weapons/Guns/Shotguns/sawn_inhands_64x.rsi/meta.json new file mode 100644 index 0000000000..852268d187 --- /dev/null +++ b/Resources/Textures/Objects/Weapons/Guns/Shotguns/sawn_inhands_64x.rsi/meta.json @@ -0,0 +1,19 @@ +{ + "version": 1, + "license": "CC-BY-NC-4.0", + "copyright": "Taken from https://github.com/tgstation/tgstation/ at commit fb2d71495bfe81446159ef528534193d09dd8d34, sawn-inhand states modified from db-inhand by Flareguy", + "size": { + "x": 64, + "y": 64 + }, + "states": [ + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Objects/Weapons/Guns/Snipers/bolt_gun_wood.rsi/meta.json b/Resources/Textures/Objects/Weapons/Guns/Snipers/bolt_gun_wood.rsi/meta.json index 4f7fc5d6f5..e4f1ef8dd8 100644 --- a/Resources/Textures/Objects/Weapons/Guns/Snipers/bolt_gun_wood.rsi/meta.json +++ b/Resources/Textures/Objects/Weapons/Guns/Snipers/bolt_gun_wood.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from InterBay13 at https://github.com/AndySok/InterBay13/commit/84824582fe1381d9ea6282b9da407994ab8ab509, backpack sling sprite edited by Boaz1111", + "copyright": "Taken from InterBay13 at https://github.com/AndySok/InterBay13/commit/84824582fe1381d9ea6282b9da407994ab8ab509, backpack sling sprite edited by Boaz1111, wield sprites by RiceMar1244", "size": { "x": 32, "y": 32 @@ -21,11 +21,19 @@ "name": "inhand-right", "directions": 4 }, + { + "name": "wielded-inhand-left", + "directions": 4 + }, + { + "name": "wielded-inhand-right", + "directions": 4 + }, { "name": "equipped-BACKPACK", "directions": 4 }, - { + { "name": "equipped-SUITSTORAGE", "directions": 4 } diff --git a/Resources/Textures/Objects/Weapons/Guns/Snipers/bolt_gun_wood.rsi/wielded-inhand-left.png b/Resources/Textures/Objects/Weapons/Guns/Snipers/bolt_gun_wood.rsi/wielded-inhand-left.png new file mode 100644 index 0000000000..dc8f572764 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Snipers/bolt_gun_wood.rsi/wielded-inhand-left.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Snipers/bolt_gun_wood.rsi/wielded-inhand-right.png b/Resources/Textures/Objects/Weapons/Guns/Snipers/bolt_gun_wood.rsi/wielded-inhand-right.png new file mode 100644 index 0000000000..7a478a3976 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Snipers/bolt_gun_wood.rsi/wielded-inhand-right.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Snipers/heavy_sniper.rsi/meta.json b/Resources/Textures/Objects/Weapons/Guns/Snipers/heavy_sniper.rsi/meta.json index da8881c7c5..818c56417d 100644 --- a/Resources/Textures/Objects/Weapons/Guns/Snipers/heavy_sniper.rsi/meta.json +++ b/Resources/Textures/Objects/Weapons/Guns/Snipers/heavy_sniper.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/pull/13612/commits/c1cf3c42b0cd00023937e46845a7c32d6beefa0e, backpack sling sprite edited by Boaz1111", + "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/pull/13612/commits/c1cf3c42b0cd00023937e46845a7c32d6beefa0e, backpack sling sprite edited by Boaz1111, wield sprites by RiceMar1244", "size": { "x": 32, "y": 32 @@ -21,11 +21,19 @@ "name": "inhand-right", "directions": 4 }, + { + "name": "wielded-inhand-left", + "directions": 4 + }, + { + "name": "wielded-inhand-right", + "directions": 4 + }, { "name": "equipped-BACKPACK", "directions": 4 }, - { + { "name": "equipped-SUITSTORAGE", "directions": 4 } diff --git a/Resources/Textures/Objects/Weapons/Guns/Snipers/heavy_sniper.rsi/wielded-inhand-left.png b/Resources/Textures/Objects/Weapons/Guns/Snipers/heavy_sniper.rsi/wielded-inhand-left.png new file mode 100644 index 0000000000..d373352b9f Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Snipers/heavy_sniper.rsi/wielded-inhand-left.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Snipers/heavy_sniper.rsi/wielded-inhand-right.png b/Resources/Textures/Objects/Weapons/Guns/Snipers/heavy_sniper.rsi/wielded-inhand-right.png new file mode 100644 index 0000000000..03625930c8 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Snipers/heavy_sniper.rsi/wielded-inhand-right.png differ diff --git a/Resources/Textures/Objects/Weapons/Melee/cane.rsi/cane-empty.png b/Resources/Textures/Objects/Weapons/Melee/cane.rsi/cane-empty.png new file mode 100644 index 0000000000..64cb9ef3aa Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Melee/cane.rsi/cane-empty.png differ diff --git a/Resources/Textures/Objects/Weapons/Melee/cane.rsi/cane.png b/Resources/Textures/Objects/Weapons/Melee/cane.rsi/cane.png new file mode 100644 index 0000000000..721847ff28 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Melee/cane.rsi/cane.png differ diff --git a/Resources/Textures/Objects/Weapons/Melee/cane.rsi/inhand-left.png b/Resources/Textures/Objects/Weapons/Melee/cane.rsi/inhand-left.png new file mode 100644 index 0000000000..caa2f23270 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Melee/cane.rsi/inhand-left.png differ diff --git a/Resources/Textures/Objects/Weapons/Melee/cane.rsi/inhand-right.png b/Resources/Textures/Objects/Weapons/Melee/cane.rsi/inhand-right.png new file mode 100644 index 0000000000..8f12a08afd Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Melee/cane.rsi/inhand-right.png differ diff --git a/Resources/Textures/Objects/Weapons/Melee/cane.rsi/meta.json b/Resources/Textures/Objects/Weapons/Melee/cane.rsi/meta.json new file mode 100644 index 0000000000..913fcb524b --- /dev/null +++ b/Resources/Textures/Objects/Weapons/Melee/cane.rsi/meta.json @@ -0,0 +1,33 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Sprited by ps3moira#9488 on discord", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "cane-empty" + }, + { + "name": "cane" + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "wielded-inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "wielded-inhand-right", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Objects/Weapons/Melee/cane.rsi/wielded-inhand-left.png b/Resources/Textures/Objects/Weapons/Melee/cane.rsi/wielded-inhand-left.png new file mode 100644 index 0000000000..f6f87a4a90 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Melee/cane.rsi/wielded-inhand-left.png differ diff --git a/Resources/Textures/Objects/Weapons/Melee/cane.rsi/wielded-inhand-right.png b/Resources/Textures/Objects/Weapons/Melee/cane.rsi/wielded-inhand-right.png new file mode 100644 index 0000000000..e1f0449b4c Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Melee/cane.rsi/wielded-inhand-right.png differ diff --git a/Resources/Textures/Objects/Weapons/Melee/cane_blade.rsi/icon.png b/Resources/Textures/Objects/Weapons/Melee/cane_blade.rsi/icon.png new file mode 100644 index 0000000000..6581dc96e8 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Melee/cane_blade.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Weapons/Melee/cane_blade.rsi/inhand-left.png b/Resources/Textures/Objects/Weapons/Melee/cane_blade.rsi/inhand-left.png new file mode 100644 index 0000000000..f8e57880cb Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Melee/cane_blade.rsi/inhand-left.png differ diff --git a/Resources/Textures/Objects/Weapons/Melee/cane_blade.rsi/inhand-right.png b/Resources/Textures/Objects/Weapons/Melee/cane_blade.rsi/inhand-right.png new file mode 100644 index 0000000000..5fa04f7f87 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Melee/cane_blade.rsi/inhand-right.png differ diff --git a/Resources/Textures/Objects/Weapons/Melee/cane_blade.rsi/meta.json b/Resources/Textures/Objects/Weapons/Melee/cane_blade.rsi/meta.json new file mode 100644 index 0000000000..a48335cc0d --- /dev/null +++ b/Resources/Textures/Objects/Weapons/Melee/cane_blade.rsi/meta.json @@ -0,0 +1,22 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Sprited by ps3moira#9488 on discord", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "icon" + } + ] +} diff --git a/Resources/Textures/Objects/Weapons/Melee/telebaton.rsi/icon-off.png b/Resources/Textures/Objects/Weapons/Melee/telebaton.rsi/icon-off.png new file mode 100644 index 0000000000..77efc57356 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Melee/telebaton.rsi/icon-off.png differ diff --git a/Resources/Textures/Objects/Weapons/Melee/telebaton.rsi/icon.png b/Resources/Textures/Objects/Weapons/Melee/telebaton.rsi/icon.png new file mode 100644 index 0000000000..fb6adc7b3d Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Melee/telebaton.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Weapons/Melee/telebaton.rsi/inhand-left.png b/Resources/Textures/Objects/Weapons/Melee/telebaton.rsi/inhand-left.png new file mode 100644 index 0000000000..6f31eafd0f Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Melee/telebaton.rsi/inhand-left.png differ diff --git a/Resources/Textures/Objects/Weapons/Melee/telebaton.rsi/inhand-right.png b/Resources/Textures/Objects/Weapons/Melee/telebaton.rsi/inhand-right.png new file mode 100644 index 0000000000..3ff76ef4da Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Melee/telebaton.rsi/inhand-right.png differ diff --git a/Resources/Textures/Objects/Weapons/Melee/telebaton.rsi/meta.json b/Resources/Textures/Objects/Weapons/Melee/telebaton.rsi/meta.json new file mode 100644 index 0000000000..1abc5be52f --- /dev/null +++ b/Resources/Textures/Objects/Weapons/Melee/telebaton.rsi/meta.json @@ -0,0 +1,25 @@ +{ + "version": 1, + "license": "CC-BY-NC-SA-3.0", + "copyright": "Taken from Bee Station 13 at https://github.com/BeeStation/BeeStation-Hornet/commit/59b6fbe2e6f6f4194d650e2c51c6ae70b8a14aca", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "icon-off" + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Objects/Weapons/Throwable/energy_bola.rsi/icon.png b/Resources/Textures/Objects/Weapons/Throwable/energy_bola.rsi/icon.png new file mode 100644 index 0000000000..a7192bb8e9 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Throwable/energy_bola.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Weapons/Throwable/energy_bola.rsi/meta.json b/Resources/Textures/Objects/Weapons/Throwable/energy_bola.rsi/meta.json new file mode 100644 index 0000000000..dbbae21857 --- /dev/null +++ b/Resources/Textures/Objects/Weapons/Throwable/energy_bola.rsi/meta.json @@ -0,0 +1,18 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/b3e22d34474afbee7b84ba282ce0a9b8f332d377.", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon", + "delays": + [ + [0.1, 0.1] + ] + } + ] +} diff --git a/Resources/Textures/Parkstation/Mobs/Customization/ears.rsi/meta.json b/Resources/Textures/Parkstation/Mobs/Customization/ears.rsi/meta.json index f86e0e8585..c2945681be 100644 --- a/Resources/Textures/Parkstation/Mobs/Customization/ears.rsi/meta.json +++ b/Resources/Textures/Parkstation/Mobs/Customization/ears.rsi/meta.json @@ -63,14 +63,6 @@ "name": "mouse", "directions": 4 }, - { - "name": "shadowkin", - "directions": 4 - }, - { - "name": "shadowkin_stripes", - "directions": 4 - }, { "name": "sylveon_primary", "directions": 4 diff --git a/Resources/Textures/Parkstation/Mobs/Customization/tails32x32.rsi/meta.json b/Resources/Textures/Parkstation/Mobs/Customization/tails32x32.rsi/meta.json index aac828f74b..ab06f3fbeb 100644 --- a/Resources/Textures/Parkstation/Mobs/Customization/tails32x32.rsi/meta.json +++ b/Resources/Textures/Parkstation/Mobs/Customization/tails32x32.rsi/meta.json @@ -87,14 +87,6 @@ "name": "maw", "directions": 4 }, - { - "name": "shadowkin_shorter", - "directions": 4 - }, - { - "name": "shadowkin_medium", - "directions": 4 - }, { "name": "shark_fin", "directions": 4 diff --git a/Resources/Textures/Parkstation/Mobs/Customization/tails64x32.rsi/meta.json b/Resources/Textures/Parkstation/Mobs/Customization/tails64x32.rsi/meta.json deleted file mode 100644 index 2aea3b4201..0000000000 --- a/Resources/Textures/Parkstation/Mobs/Customization/tails64x32.rsi/meta.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "version": 1, - "size": { - "x": 64, - "y": 32 - }, - "license": "CC-BY-SA-3.0", - "copyright": "https://github.com/SPLURT-Station/S.P.L.U.R.T-Station-13/commit/96703f76bccd8fe6a96b78524efb97a9af661767 Shadowkin big fluff made by DSC@Cabbage#9633 (561159087765848084)", - "states": [ - { - "name": "shadowkin", - "directions": 4 - }, - { - "name": "shadowkin_big", - "directions": 4 - }, - { - "name": "shadowkin_big_fluff", - "directions": 4 - } - ] -} diff --git a/Resources/Textures/Parkstation/Structures/Furniture/Benches/pews.rsi/left.png b/Resources/Textures/Parkstation/Structures/Furniture/Benches/pews.rsi/left.png index 9631b4b171..beb8cd4a0b 100644 Binary files a/Resources/Textures/Parkstation/Structures/Furniture/Benches/pews.rsi/left.png and b/Resources/Textures/Parkstation/Structures/Furniture/Benches/pews.rsi/left.png differ diff --git a/Resources/Textures/Parkstation/Structures/Furniture/Benches/pews.rsi/right.png b/Resources/Textures/Parkstation/Structures/Furniture/Benches/pews.rsi/right.png index ea0922930f..c1f2949a71 100644 Binary files a/Resources/Textures/Parkstation/Structures/Furniture/Benches/pews.rsi/right.png and b/Resources/Textures/Parkstation/Structures/Furniture/Benches/pews.rsi/right.png differ diff --git a/Resources/Textures/Parkstation/Structures/Furniture/Benches/steel_bench.rsi/left.png b/Resources/Textures/Parkstation/Structures/Furniture/Benches/steel_bench.rsi/left.png index 260c7298f7..b6c1ef00fc 100644 Binary files a/Resources/Textures/Parkstation/Structures/Furniture/Benches/steel_bench.rsi/left.png and b/Resources/Textures/Parkstation/Structures/Furniture/Benches/steel_bench.rsi/left.png differ diff --git a/Resources/Textures/Parkstation/Structures/Furniture/Benches/steel_bench.rsi/right.png b/Resources/Textures/Parkstation/Structures/Furniture/Benches/steel_bench.rsi/right.png index b6c1ef00fc..260c7298f7 100644 Binary files a/Resources/Textures/Parkstation/Structures/Furniture/Benches/steel_bench.rsi/right.png and b/Resources/Textures/Parkstation/Structures/Furniture/Benches/steel_bench.rsi/right.png differ diff --git a/Resources/Textures/Parkstation/Structures/Furniture/Benches/steel_bench_white.rsi/left.png b/Resources/Textures/Parkstation/Structures/Furniture/Benches/steel_bench_white.rsi/left.png index d456300e10..a412a51871 100644 Binary files a/Resources/Textures/Parkstation/Structures/Furniture/Benches/steel_bench_white.rsi/left.png and b/Resources/Textures/Parkstation/Structures/Furniture/Benches/steel_bench_white.rsi/left.png differ diff --git a/Resources/Textures/Parkstation/Structures/Furniture/Benches/steel_bench_white.rsi/right.png b/Resources/Textures/Parkstation/Structures/Furniture/Benches/steel_bench_white.rsi/right.png index a412a51871..d456300e10 100644 Binary files a/Resources/Textures/Parkstation/Structures/Furniture/Benches/steel_bench_white.rsi/right.png and b/Resources/Textures/Parkstation/Structures/Furniture/Benches/steel_bench_white.rsi/right.png differ diff --git a/Resources/Textures/Shaders/color_tint.swsl b/Resources/Textures/Shaders/color_tint.swsl new file mode 100644 index 0000000000..a5449b2d4d --- /dev/null +++ b/Resources/Textures/Shaders/color_tint.swsl @@ -0,0 +1,56 @@ +light_mode unshaded; + +uniform sampler2D SCREEN_TEXTURE; +uniform lowp vec3 tint_color; // RGB color between 0 and 1 +uniform lowp float tint_amount; // Number between 0 and 1 + +// Function to convert RGB to HSV. +highp vec3 rgb2hsv(highp vec3 c) +{ + highp vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + highp vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); + highp vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); + + highp float d = q.x - min(q.w, q.y); + /* float e = 1.0e-10; */ + highp float e = 0.0000000001; + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); +} + +// Function to convert HSV to RGB. +highp vec3 hsv2rgb(highp vec3 c) +{ + highp vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + highp vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + +void fragment() { + highp vec4 color = zTextureSpec(SCREEN_TEXTURE, FRAGCOORD.xy * SCREEN_PIXEL_SIZE); + + // Convert color to HSV. + highp vec3 hsvTint = rgb2hsv(tint_color); + highp vec3 hsvColor = rgb2hsv(color.rgb); + + // Set the original hue to the tint hue as long as it's not greyscale. + if (hsvTint.y > 0.05 && hsvTint.z != 0.0) + { + hsvColor.x = hsvTint.x; + } + // Modify saturation based on tint color saturation, + // Halving it if it's higher and capping it at the original. + hsvColor.y = (hsvColor.y < hsvTint.y) ? + mix(hsvColor.y, hsvTint.y, 0.75) : mix(hsvColor.y, hsvTint.y, 0.35); + + // Modify value based on tint color value, but only if it's darker. + hsvColor.z = (mix(hsvColor.z, hsvTint.z, 0.85) <= hsvColor.z) ? + mix(hsvColor.z, hsvTint.z, 0.85) : hsvColor.z; + + // Convert back to RGB. + highp vec3 rgbColorMod = hsv2rgb(hsvColor); + + // Mix the final RGB product with the original color to the intensity of the tint. + color.rgb = mix(color.rgb, rgbColorMod, tint_amount); + + COLOR = color; +} \ No newline at end of file diff --git a/Resources/Textures/Shaders/displacement.swsl b/Resources/Textures/Shaders/displacement.swsl new file mode 100644 index 0000000000..ba5ca57852 --- /dev/null +++ b/Resources/Textures/Shaders/displacement.swsl @@ -0,0 +1,18 @@ +uniform sampler2D displacementMap; +uniform highp float displacementSize; +uniform highp vec4 displacementUV; + +varying highp vec2 displacementUVOut; + +void vertex() { + displacementUVOut = mix(displacementUV.xy, displacementUV.zw, tCoord2); +} + +void fragment() { + highp vec4 displacementSample = texture2D(displacementMap, displacementUVOut); + highp vec2 displacementValue = (displacementSample.xy - vec2(128.0 / 255.0)) / (1.0 - 128.0 / 255.0); + COLOR = zTexture(UV + displacementValue * TEXTURE_PIXEL_SIZE * displacementSize * vec2(1.0, -1.0)); + COLOR.a *= displacementSample.a; +} + + diff --git a/Resources/Textures/Shaders/ethereal.swsl b/Resources/Textures/Shaders/ethereal.swsl new file mode 100644 index 0000000000..dc9d971e1c --- /dev/null +++ b/Resources/Textures/Shaders/ethereal.swsl @@ -0,0 +1,75 @@ +light_mode unshaded; + +uniform sampler2D SCREEN_TEXTURE; + +// Function to convert RGB to HSV. +highp vec3 rgb2hsv(highp vec3 c) +{ + highp vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + highp vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); + highp vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); + + highp float d = q.x - min(q.w, q.y); + /* float e = 1.0e-10; */ + highp float e = 0.0000000001; + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); +} + +// Function to convert HSV to RGB. +highp vec3 hsv2rgb(highp vec3 c) +{ + highp vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + highp vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + +// Random number function with potential negative values. +highp float rand(highp vec2 n) { + highp float r = 2.0 * (0.5 + 0.5 * fract (sin (dot (n.xy, vec2(12.9898, 78.233)))* 43758.5453)) - 1.0; + return r * (r < 0.0 ? 0.8 : 1.3); +} + +void fragment() { + highp vec4 color = zTextureSpec(SCREEN_TEXTURE, FRAGCOORD.xy * SCREEN_PIXEL_SIZE); + + // Increase the contrast of the image if the luminance is low enough. + highp float luminance = dot(color.rgb, vec3(0.2126, 0.7152, 0.0722)); + if (luminance < 0.06) { + color.rgb *= 0.5; + } + + // Convert to HSV. + highp vec3 hsvColor = rgb2hsv(color.rgb); + + // Apply a breathing effect to the value of the image. + hsvColor.z *= mix(0.35, 0.7, (sin(TIME) * 0.65)); + + // Increase the saturation of the color, incorperating a random value, as long as the value is above 0.1. + if (hsvColor.z > 0.065) { + hsvColor.y *= (rand(FRAGCOORD.xy * (TIME * 0.15)) * 1.5) + 1.0; + } + + // Convert back to RGB. + color.rgb = hsv2rgb(hsvColor); + + + + // get distortion magnitude. hand crafted from a random jumble of trig functions + highp float w = sin(TIME + (FRAGCOORD.x + FRAGCOORD.y + 2.0*sin(TIME*0.3) * sin(TIME*0.3 + FRAGCOORD.x - FRAGCOORD.y)) ); + + // visualize distortion via: + // COLOR = vec4(w,w,w,1.0); + + w *= 5.0; + + highp vec4 background = zTextureSpec(SCREEN_TEXTURE, ( FRAGCOORD.xy + vec2(w) ) * SCREEN_PIXEL_SIZE ); + highp vec3 hsvBg = rgb2hsv(background.rgb); + hsvBg.x *= -1.0; + background.rgb = hsv2rgb(hsvBg); + + color.xyz = mix(background.xyz, color.xyz, 0.75); + + + + COLOR = color; +} diff --git a/Resources/Textures/Shaders/flap.swsl b/Resources/Textures/Shaders/flap.swsl new file mode 100644 index 0000000000..3082e19b49 --- /dev/null +++ b/Resources/Textures/Shaders/flap.swsl @@ -0,0 +1,35 @@ +preset raw; + +varying highp vec4 VtxModulate; +varying highp vec2 Pos; + +uniform highp float Speed; +uniform highp float Multiplier; +uniform highp float Offset; + +void fragment() { + highp vec4 texColor = zTexture(UV); + lowp vec3 lightSample = texture2D(lightMap, Pos).rgb; + COLOR = texColor * VtxModulate * vec4(lightSample, 1.0); +} + +void vertex() { + vec2 pos = aPos; + + // Apply MVP transformation first + vec2 transformedPos = apply_mvp(pos); + + // Calculate vertical movement in screen space + float verticalOffset = (sin(TIME * Speed) + Offset) * Multiplier; + + // Apply vertical movement after MVP transformation + transformedPos.y += verticalOffset; + + // Assign the final position + VERTEX = transformedPos; + + // Keep the original UV coordinates + UV = mix(modifyUV.xy, modifyUV.zw, tCoord); + Pos = (VERTEX + 1.0) / 2.0; + VtxModulate = zFromSrgb(modulate); +} \ No newline at end of file diff --git a/Resources/Textures/Shaders/saturationscale.swsl b/Resources/Textures/Shaders/saturationscale.swsl new file mode 100644 index 0000000000..9829e20762 --- /dev/null +++ b/Resources/Textures/Shaders/saturationscale.swsl @@ -0,0 +1,12 @@ +uniform highp float saturation; // Between 0 and 2; +uniform sampler2D SCREEN_TEXTURE; + +void fragment() { + highp vec4 color = texture(SCREEN_TEXTURE, UV); + + highp float brightness = (color.r + color.g + color.b) / 3.0; + + color.rgb = mix(vec3(brightness), color.rgb, saturation); + + COLOR = color; +} diff --git a/Resources/Textures/Structures/Doors/Windoors/windoor.rsi/assembly.png b/Resources/Textures/Structures/Doors/Windoors/windoor.rsi/assembly.png index 668997d837..1c397d15c7 100644 Binary files a/Resources/Textures/Structures/Doors/Windoors/windoor.rsi/assembly.png and b/Resources/Textures/Structures/Doors/Windoors/windoor.rsi/assembly.png differ diff --git a/Resources/Textures/Structures/Doors/Windoors/windoor.rsi/closed.png b/Resources/Textures/Structures/Doors/Windoors/windoor.rsi/closed.png index fe1f75ba13..528b2cada2 100644 Binary files a/Resources/Textures/Structures/Doors/Windoors/windoor.rsi/closed.png and b/Resources/Textures/Structures/Doors/Windoors/windoor.rsi/closed.png differ diff --git a/Resources/Textures/Structures/Doors/Windoors/windoor.rsi/closing.png b/Resources/Textures/Structures/Doors/Windoors/windoor.rsi/closing.png index e7085665d7..44c87b9aca 100644 Binary files a/Resources/Textures/Structures/Doors/Windoors/windoor.rsi/closing.png and b/Resources/Textures/Structures/Doors/Windoors/windoor.rsi/closing.png differ diff --git a/Resources/Textures/Structures/Doors/Windoors/windoor.rsi/meta.json b/Resources/Textures/Structures/Doors/Windoors/windoor.rsi/meta.json index 66954b6662..ecc17f8aff 100644 --- a/Resources/Textures/Structures/Doors/Windoors/windoor.rsi/meta.json +++ b/Resources/Textures/Structures/Doors/Windoors/windoor.rsi/meta.json @@ -1,7 +1,7 @@ { "version":1, "license":"CC-BY-SA-3.0", - "copyright":"Taken from tgstation at https://github.com/tgstation/tgstation/blob/3681006d7102045e334e8eddb23a8685fcdb258a/icons/obj/doors/windoor.dmi", + "copyright":"Taken from Paradise at https://github.com/ParadiseSS13/Paradise/blob/bd91a1b962fe9bd38e346e9fafd4ebb10784fcb3/icons/obj/doors/windoor.dmi", "size": { "x":32, "y":32 diff --git a/Resources/Textures/Structures/Doors/Windoors/windoor.rsi/opening.png b/Resources/Textures/Structures/Doors/Windoors/windoor.rsi/opening.png index 5b68943ff6..08b296af38 100644 Binary files a/Resources/Textures/Structures/Doors/Windoors/windoor.rsi/opening.png and b/Resources/Textures/Structures/Doors/Windoors/windoor.rsi/opening.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/carpet.rsi/full.png b/Resources/Textures/Structures/Furniture/Tables/carpet.rsi/full.png index 1b774e0575..c5587a9fa1 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/carpet.rsi/full.png and b/Resources/Textures/Structures/Furniture/Tables/carpet.rsi/full.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/carpet.rsi/meta.json b/Resources/Textures/Structures/Furniture/Tables/carpet.rsi/meta.json index 0ebc40b814..1ad8b4f358 100644 --- a/Resources/Textures/Structures/Furniture/Tables/carpet.rsi/meta.json +++ b/Resources/Textures/Structures/Furniture/Tables/carpet.rsi/meta.json @@ -5,159 +5,42 @@ "y": 32 }, "license": "CC-BY-SA-3.0", - "copyright": "Taken from https://github.com/discordia-space/CEV-Eris/blob/0b3ab17dbad632ddf738b63900ef8df1926bba47/icons/obj/tables.dmi", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/47f6d91b37609b940152de62befa931dba35adf0", "states": [ { - "name": "full", - "delays": [ - [ - 1 - ] - ] + "name": "full" }, { "name": "state_0", - "directions": 4, - "delays": [ - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ] - ] + "directions": 4 }, { "name": "state_1", - "directions": 4, - "delays": [ - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ] - ] + "directions": 4 }, { "name": "state_2", - "directions": 4, - "delays": [ - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ] - ] + "directions": 4 }, { "name": "state_3", - "directions": 4, - "delays": [ - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ] - ] + "directions": 4 }, { "name": "state_4", - "directions": 4, - "delays": [ - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ] - ] + "directions": 4 }, { "name": "state_5", - "directions": 4, - "delays": [ - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ] - ] + "directions": 4 }, { "name": "state_6", - "directions": 4, - "delays": [ - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ] - ] + "directions": 4 }, { "name": "state_7", - "directions": 4, - "delays": [ - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ] - ] + "directions": 4 } ] } diff --git a/Resources/Textures/Structures/Furniture/Tables/carpet.rsi/state_0.png b/Resources/Textures/Structures/Furniture/Tables/carpet.rsi/state_0.png index f57395c659..197d42df82 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/carpet.rsi/state_0.png and b/Resources/Textures/Structures/Furniture/Tables/carpet.rsi/state_0.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/carpet.rsi/state_1.png b/Resources/Textures/Structures/Furniture/Tables/carpet.rsi/state_1.png index f3631cfc0e..43aca220cf 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/carpet.rsi/state_1.png and b/Resources/Textures/Structures/Furniture/Tables/carpet.rsi/state_1.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/carpet.rsi/state_2.png b/Resources/Textures/Structures/Furniture/Tables/carpet.rsi/state_2.png index f57395c659..197d42df82 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/carpet.rsi/state_2.png and b/Resources/Textures/Structures/Furniture/Tables/carpet.rsi/state_2.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/carpet.rsi/state_3.png b/Resources/Textures/Structures/Furniture/Tables/carpet.rsi/state_3.png index f3631cfc0e..43aca220cf 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/carpet.rsi/state_3.png and b/Resources/Textures/Structures/Furniture/Tables/carpet.rsi/state_3.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/carpet.rsi/state_4.png b/Resources/Textures/Structures/Furniture/Tables/carpet.rsi/state_4.png index a8d469a32f..5ab0bb93df 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/carpet.rsi/state_4.png and b/Resources/Textures/Structures/Furniture/Tables/carpet.rsi/state_4.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/carpet.rsi/state_5.png b/Resources/Textures/Structures/Furniture/Tables/carpet.rsi/state_5.png index 6cbcfa1bef..f429ddeb5a 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/carpet.rsi/state_5.png and b/Resources/Textures/Structures/Furniture/Tables/carpet.rsi/state_5.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/carpet.rsi/state_6.png b/Resources/Textures/Structures/Furniture/Tables/carpet.rsi/state_6.png index a8d469a32f..5ab0bb93df 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/carpet.rsi/state_6.png and b/Resources/Textures/Structures/Furniture/Tables/carpet.rsi/state_6.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/carpet.rsi/state_7.png b/Resources/Textures/Structures/Furniture/Tables/carpet.rsi/state_7.png index 29bd4db74b..0209287820 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/carpet.rsi/state_7.png and b/Resources/Textures/Structures/Furniture/Tables/carpet.rsi/state_7.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/frame.rsi/full.png b/Resources/Textures/Structures/Furniture/Tables/frame.rsi/full.png index 2af9b7ff1b..4eed0b0b08 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/frame.rsi/full.png and b/Resources/Textures/Structures/Furniture/Tables/frame.rsi/full.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/frame.rsi/state_0.png b/Resources/Textures/Structures/Furniture/Tables/frame.rsi/state_0.png index 2ba1f779a2..b0eb81d2c9 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/frame.rsi/state_0.png and b/Resources/Textures/Structures/Furniture/Tables/frame.rsi/state_0.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/frame.rsi/state_1.png b/Resources/Textures/Structures/Furniture/Tables/frame.rsi/state_1.png index 27684dc258..2a982fa394 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/frame.rsi/state_1.png and b/Resources/Textures/Structures/Furniture/Tables/frame.rsi/state_1.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/frame.rsi/state_2.png b/Resources/Textures/Structures/Furniture/Tables/frame.rsi/state_2.png index 2ba1f779a2..b0eb81d2c9 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/frame.rsi/state_2.png and b/Resources/Textures/Structures/Furniture/Tables/frame.rsi/state_2.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/frame.rsi/state_3.png b/Resources/Textures/Structures/Furniture/Tables/frame.rsi/state_3.png index 27684dc258..2a982fa394 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/frame.rsi/state_3.png and b/Resources/Textures/Structures/Furniture/Tables/frame.rsi/state_3.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/frame.rsi/state_4.png b/Resources/Textures/Structures/Furniture/Tables/frame.rsi/state_4.png index f2df00c177..cc0ad2cf1c 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/frame.rsi/state_4.png and b/Resources/Textures/Structures/Furniture/Tables/frame.rsi/state_4.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/frame.rsi/state_5.png b/Resources/Textures/Structures/Furniture/Tables/frame.rsi/state_5.png index 682b8d26e1..47e94567e7 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/frame.rsi/state_5.png and b/Resources/Textures/Structures/Furniture/Tables/frame.rsi/state_5.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/frame.rsi/state_6.png b/Resources/Textures/Structures/Furniture/Tables/frame.rsi/state_6.png index f2df00c177..cc0ad2cf1c 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/frame.rsi/state_6.png and b/Resources/Textures/Structures/Furniture/Tables/frame.rsi/state_6.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/generic.rsi/full.png b/Resources/Textures/Structures/Furniture/Tables/generic.rsi/full.png index 41b2c199d1..4f41d6f7de 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/generic.rsi/full.png and b/Resources/Textures/Structures/Furniture/Tables/generic.rsi/full.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/generic.rsi/meta.json b/Resources/Textures/Structures/Furniture/Tables/generic.rsi/meta.json index 0ebc40b814..3d992bc28d 100644 --- a/Resources/Textures/Structures/Furniture/Tables/generic.rsi/meta.json +++ b/Resources/Textures/Structures/Furniture/Tables/generic.rsi/meta.json @@ -1,163 +1,46 @@ { - "version": 1, - "size": { - "x": 32, - "y": 32 - }, - "license": "CC-BY-SA-3.0", - "copyright": "Taken from https://github.com/discordia-space/CEV-Eris/blob/0b3ab17dbad632ddf738b63900ef8df1926bba47/icons/obj/tables.dmi", - "states": [ - { - "name": "full", - "delays": [ - [ - 1 - ] - ] - }, - { - "name": "state_0", - "directions": 4, - "delays": [ - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ] - ] - }, - { - "name": "state_1", - "directions": 4, - "delays": [ - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ] - ] - }, - { - "name": "state_2", - "directions": 4, - "delays": [ - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ] - ] - }, - { - "name": "state_3", - "directions": 4, - "delays": [ - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ] - ] - }, - { - "name": "state_4", - "directions": 4, - "delays": [ - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ] - ] - }, - { - "name": "state_5", - "directions": 4, - "delays": [ - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ] - ] - }, - { - "name": "state_6", - "directions": 4, - "delays": [ - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ] - ] - }, - { - "name": "state_7", - "directions": 4, - "delays": [ - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ] - ] - } - ] -} + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from Paradise at https://github.com/ParadiseSS13/Paradise/blob/bd91a1b962fe9bd38e346e9fafd4ebb10784fcb3/icons/obj/smooth_structures/tables/table.dmi", + "states": [ + { + "name": "full" + }, + { + "name": "state_0", + "directions": 4 + }, + { + "name": "state_1", + "directions": 4 + }, + { + "name": "state_2", + "directions": 4 + }, + { + "name": "state_3", + "directions": 4 + }, + { + "name": "state_4", + "directions": 4 + }, + { + "name": "state_5", + "directions": 4 + }, + { + "name": "state_6", + "directions": 4 + }, + { + "name": "state_7", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Structures/Furniture/Tables/generic.rsi/state_0.png b/Resources/Textures/Structures/Furniture/Tables/generic.rsi/state_0.png index 47ab5fc187..7f0bb2b5b2 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/generic.rsi/state_0.png and b/Resources/Textures/Structures/Furniture/Tables/generic.rsi/state_0.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/generic.rsi/state_1.png b/Resources/Textures/Structures/Furniture/Tables/generic.rsi/state_1.png index 3e78a2531b..fa15c2495e 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/generic.rsi/state_1.png and b/Resources/Textures/Structures/Furniture/Tables/generic.rsi/state_1.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/generic.rsi/state_2.png b/Resources/Textures/Structures/Furniture/Tables/generic.rsi/state_2.png index 47ab5fc187..7f0bb2b5b2 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/generic.rsi/state_2.png and b/Resources/Textures/Structures/Furniture/Tables/generic.rsi/state_2.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/generic.rsi/state_3.png b/Resources/Textures/Structures/Furniture/Tables/generic.rsi/state_3.png index 3e78a2531b..fa15c2495e 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/generic.rsi/state_3.png and b/Resources/Textures/Structures/Furniture/Tables/generic.rsi/state_3.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/generic.rsi/state_4.png b/Resources/Textures/Structures/Furniture/Tables/generic.rsi/state_4.png index f4f9168b07..e5d4b519fe 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/generic.rsi/state_4.png and b/Resources/Textures/Structures/Furniture/Tables/generic.rsi/state_4.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/generic.rsi/state_5.png b/Resources/Textures/Structures/Furniture/Tables/generic.rsi/state_5.png index 0046d10029..eaec7e603a 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/generic.rsi/state_5.png and b/Resources/Textures/Structures/Furniture/Tables/generic.rsi/state_5.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/generic.rsi/state_6.png b/Resources/Textures/Structures/Furniture/Tables/generic.rsi/state_6.png index f4f9168b07..e5d4b519fe 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/generic.rsi/state_6.png and b/Resources/Textures/Structures/Furniture/Tables/generic.rsi/state_6.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/generic.rsi/state_7.png b/Resources/Textures/Structures/Furniture/Tables/generic.rsi/state_7.png index 165f578dc0..8cac72fc6f 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/generic.rsi/state_7.png and b/Resources/Textures/Structures/Furniture/Tables/generic.rsi/state_7.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/glass.rsi/full.png b/Resources/Textures/Structures/Furniture/Tables/glass.rsi/full.png index 4c394c4650..cbcbd3a3b1 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/glass.rsi/full.png and b/Resources/Textures/Structures/Furniture/Tables/glass.rsi/full.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/glass.rsi/meta.json b/Resources/Textures/Structures/Furniture/Tables/glass.rsi/meta.json index 0ebc40b814..1ad8b4f358 100644 --- a/Resources/Textures/Structures/Furniture/Tables/glass.rsi/meta.json +++ b/Resources/Textures/Structures/Furniture/Tables/glass.rsi/meta.json @@ -5,159 +5,42 @@ "y": 32 }, "license": "CC-BY-SA-3.0", - "copyright": "Taken from https://github.com/discordia-space/CEV-Eris/blob/0b3ab17dbad632ddf738b63900ef8df1926bba47/icons/obj/tables.dmi", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/47f6d91b37609b940152de62befa931dba35adf0", "states": [ { - "name": "full", - "delays": [ - [ - 1 - ] - ] + "name": "full" }, { "name": "state_0", - "directions": 4, - "delays": [ - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ] - ] + "directions": 4 }, { "name": "state_1", - "directions": 4, - "delays": [ - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ] - ] + "directions": 4 }, { "name": "state_2", - "directions": 4, - "delays": [ - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ] - ] + "directions": 4 }, { "name": "state_3", - "directions": 4, - "delays": [ - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ] - ] + "directions": 4 }, { "name": "state_4", - "directions": 4, - "delays": [ - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ] - ] + "directions": 4 }, { "name": "state_5", - "directions": 4, - "delays": [ - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ] - ] + "directions": 4 }, { "name": "state_6", - "directions": 4, - "delays": [ - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ] - ] + "directions": 4 }, { "name": "state_7", - "directions": 4, - "delays": [ - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ] - ] + "directions": 4 } ] } diff --git a/Resources/Textures/Structures/Furniture/Tables/glass.rsi/state_0.png b/Resources/Textures/Structures/Furniture/Tables/glass.rsi/state_0.png index 2ed3dbb4c0..d8d134f0cf 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/glass.rsi/state_0.png and b/Resources/Textures/Structures/Furniture/Tables/glass.rsi/state_0.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/glass.rsi/state_1.png b/Resources/Textures/Structures/Furniture/Tables/glass.rsi/state_1.png index f63429f102..4d979a5381 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/glass.rsi/state_1.png and b/Resources/Textures/Structures/Furniture/Tables/glass.rsi/state_1.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/glass.rsi/state_2.png b/Resources/Textures/Structures/Furniture/Tables/glass.rsi/state_2.png index 2ed3dbb4c0..d8d134f0cf 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/glass.rsi/state_2.png and b/Resources/Textures/Structures/Furniture/Tables/glass.rsi/state_2.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/glass.rsi/state_3.png b/Resources/Textures/Structures/Furniture/Tables/glass.rsi/state_3.png index f63429f102..4d979a5381 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/glass.rsi/state_3.png and b/Resources/Textures/Structures/Furniture/Tables/glass.rsi/state_3.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/glass.rsi/state_4.png b/Resources/Textures/Structures/Furniture/Tables/glass.rsi/state_4.png index 71d87be2d1..7ba52d1dd5 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/glass.rsi/state_4.png and b/Resources/Textures/Structures/Furniture/Tables/glass.rsi/state_4.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/glass.rsi/state_5.png b/Resources/Textures/Structures/Furniture/Tables/glass.rsi/state_5.png index 74794bbf34..16c34ffa59 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/glass.rsi/state_5.png and b/Resources/Textures/Structures/Furniture/Tables/glass.rsi/state_5.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/glass.rsi/state_6.png b/Resources/Textures/Structures/Furniture/Tables/glass.rsi/state_6.png index 71d87be2d1..7ba52d1dd5 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/glass.rsi/state_6.png and b/Resources/Textures/Structures/Furniture/Tables/glass.rsi/state_6.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/glass.rsi/state_7.png b/Resources/Textures/Structures/Furniture/Tables/glass.rsi/state_7.png index ecdf387b8f..c2e924aaaf 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/glass.rsi/state_7.png and b/Resources/Textures/Structures/Furniture/Tables/glass.rsi/state_7.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/plasma.rsi/full.png b/Resources/Textures/Structures/Furniture/Tables/plasma.rsi/full.png index 7345eb6591..7f999c5233 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/plasma.rsi/full.png and b/Resources/Textures/Structures/Furniture/Tables/plasma.rsi/full.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/plasma.rsi/meta.json b/Resources/Textures/Structures/Furniture/Tables/plasma.rsi/meta.json index 0ebc40b814..1ad8b4f358 100644 --- a/Resources/Textures/Structures/Furniture/Tables/plasma.rsi/meta.json +++ b/Resources/Textures/Structures/Furniture/Tables/plasma.rsi/meta.json @@ -5,159 +5,42 @@ "y": 32 }, "license": "CC-BY-SA-3.0", - "copyright": "Taken from https://github.com/discordia-space/CEV-Eris/blob/0b3ab17dbad632ddf738b63900ef8df1926bba47/icons/obj/tables.dmi", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/47f6d91b37609b940152de62befa931dba35adf0", "states": [ { - "name": "full", - "delays": [ - [ - 1 - ] - ] + "name": "full" }, { "name": "state_0", - "directions": 4, - "delays": [ - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ] - ] + "directions": 4 }, { "name": "state_1", - "directions": 4, - "delays": [ - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ] - ] + "directions": 4 }, { "name": "state_2", - "directions": 4, - "delays": [ - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ] - ] + "directions": 4 }, { "name": "state_3", - "directions": 4, - "delays": [ - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ] - ] + "directions": 4 }, { "name": "state_4", - "directions": 4, - "delays": [ - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ] - ] + "directions": 4 }, { "name": "state_5", - "directions": 4, - "delays": [ - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ] - ] + "directions": 4 }, { "name": "state_6", - "directions": 4, - "delays": [ - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ] - ] + "directions": 4 }, { "name": "state_7", - "directions": 4, - "delays": [ - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ] - ] + "directions": 4 } ] } diff --git a/Resources/Textures/Structures/Furniture/Tables/plasma.rsi/state_0.png b/Resources/Textures/Structures/Furniture/Tables/plasma.rsi/state_0.png index a8791e01c1..5ff4a7ceac 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/plasma.rsi/state_0.png and b/Resources/Textures/Structures/Furniture/Tables/plasma.rsi/state_0.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/plasma.rsi/state_1.png b/Resources/Textures/Structures/Furniture/Tables/plasma.rsi/state_1.png index 4e04182a05..dab25bc1cd 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/plasma.rsi/state_1.png and b/Resources/Textures/Structures/Furniture/Tables/plasma.rsi/state_1.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/plasma.rsi/state_2.png b/Resources/Textures/Structures/Furniture/Tables/plasma.rsi/state_2.png index a8791e01c1..5ff4a7ceac 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/plasma.rsi/state_2.png and b/Resources/Textures/Structures/Furniture/Tables/plasma.rsi/state_2.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/plasma.rsi/state_3.png b/Resources/Textures/Structures/Furniture/Tables/plasma.rsi/state_3.png index 4e04182a05..dab25bc1cd 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/plasma.rsi/state_3.png and b/Resources/Textures/Structures/Furniture/Tables/plasma.rsi/state_3.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/plasma.rsi/state_4.png b/Resources/Textures/Structures/Furniture/Tables/plasma.rsi/state_4.png index 230838bfc6..34d673b2ea 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/plasma.rsi/state_4.png and b/Resources/Textures/Structures/Furniture/Tables/plasma.rsi/state_4.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/plasma.rsi/state_5.png b/Resources/Textures/Structures/Furniture/Tables/plasma.rsi/state_5.png index 2da0cebb25..428050b93c 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/plasma.rsi/state_5.png and b/Resources/Textures/Structures/Furniture/Tables/plasma.rsi/state_5.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/plasma.rsi/state_6.png b/Resources/Textures/Structures/Furniture/Tables/plasma.rsi/state_6.png index 230838bfc6..34d673b2ea 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/plasma.rsi/state_6.png and b/Resources/Textures/Structures/Furniture/Tables/plasma.rsi/state_6.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/plasma.rsi/state_7.png b/Resources/Textures/Structures/Furniture/Tables/plasma.rsi/state_7.png index 82880b87ad..aab0092a56 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/plasma.rsi/state_7.png and b/Resources/Textures/Structures/Furniture/Tables/plasma.rsi/state_7.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/r_glass.rsi/full.png b/Resources/Textures/Structures/Furniture/Tables/r_glass.rsi/full.png index be30ee84c5..0fae301078 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/r_glass.rsi/full.png and b/Resources/Textures/Structures/Furniture/Tables/r_glass.rsi/full.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/r_glass.rsi/meta.json b/Resources/Textures/Structures/Furniture/Tables/r_glass.rsi/meta.json index 0ebc40b814..966ed1ee95 100644 --- a/Resources/Textures/Structures/Furniture/Tables/r_glass.rsi/meta.json +++ b/Resources/Textures/Structures/Furniture/Tables/r_glass.rsi/meta.json @@ -1,163 +1,46 @@ { - "version": 1, - "size": { - "x": 32, - "y": 32 - }, - "license": "CC-BY-SA-3.0", - "copyright": "Taken from https://github.com/discordia-space/CEV-Eris/blob/0b3ab17dbad632ddf738b63900ef8df1926bba47/icons/obj/tables.dmi", - "states": [ - { - "name": "full", - "delays": [ - [ - 1 - ] - ] - }, - { - "name": "state_0", - "directions": 4, - "delays": [ - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ] - ] - }, - { - "name": "state_1", - "directions": 4, - "delays": [ - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ] - ] - }, - { - "name": "state_2", - "directions": 4, - "delays": [ - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ] - ] - }, - { - "name": "state_3", - "directions": 4, - "delays": [ - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ] - ] - }, - { - "name": "state_4", - "directions": 4, - "delays": [ - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ] - ] - }, - { - "name": "state_5", - "directions": 4, - "delays": [ - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ] - ] - }, - { - "name": "state_6", - "directions": 4, - "delays": [ - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ] - ] - }, - { - "name": "state_7", - "directions": 4, - "delays": [ - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ] - ] - } - ] -} + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from Paradise at https://github.com/ParadiseSS13/Paradise/blob/bd91a1b962fe9bd38e346e9fafd4ebb10784fcb3/icons/obj/smooth_structures/tables/rglass_table.dmi", + "states": [ + { + "name": "full" + }, + { + "name": "state_0", + "directions": 4 + }, + { + "name": "state_1", + "directions": 4 + }, + { + "name": "state_2", + "directions": 4 + }, + { + "name": "state_3", + "directions": 4 + }, + { + "name": "state_4", + "directions": 4 + }, + { + "name": "state_5", + "directions": 4 + }, + { + "name": "state_6", + "directions": 4 + }, + { + "name": "state_7", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Structures/Furniture/Tables/r_glass.rsi/state_0.png b/Resources/Textures/Structures/Furniture/Tables/r_glass.rsi/state_0.png index 862aff9338..bc2443592a 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/r_glass.rsi/state_0.png and b/Resources/Textures/Structures/Furniture/Tables/r_glass.rsi/state_0.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/r_glass.rsi/state_1.png b/Resources/Textures/Structures/Furniture/Tables/r_glass.rsi/state_1.png index 8b97929d64..46a287ee48 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/r_glass.rsi/state_1.png and b/Resources/Textures/Structures/Furniture/Tables/r_glass.rsi/state_1.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/r_glass.rsi/state_2.png b/Resources/Textures/Structures/Furniture/Tables/r_glass.rsi/state_2.png index 862aff9338..bc2443592a 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/r_glass.rsi/state_2.png and b/Resources/Textures/Structures/Furniture/Tables/r_glass.rsi/state_2.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/r_glass.rsi/state_3.png b/Resources/Textures/Structures/Furniture/Tables/r_glass.rsi/state_3.png index 8b97929d64..46a287ee48 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/r_glass.rsi/state_3.png and b/Resources/Textures/Structures/Furniture/Tables/r_glass.rsi/state_3.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/r_glass.rsi/state_4.png b/Resources/Textures/Structures/Furniture/Tables/r_glass.rsi/state_4.png index 57fdbb30d7..e4fd4d16dd 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/r_glass.rsi/state_4.png and b/Resources/Textures/Structures/Furniture/Tables/r_glass.rsi/state_4.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/r_glass.rsi/state_5.png b/Resources/Textures/Structures/Furniture/Tables/r_glass.rsi/state_5.png index b3231dc972..d04eac89c9 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/r_glass.rsi/state_5.png and b/Resources/Textures/Structures/Furniture/Tables/r_glass.rsi/state_5.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/r_glass.rsi/state_6.png b/Resources/Textures/Structures/Furniture/Tables/r_glass.rsi/state_6.png index 57fdbb30d7..e4fd4d16dd 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/r_glass.rsi/state_6.png and b/Resources/Textures/Structures/Furniture/Tables/r_glass.rsi/state_6.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/r_glass.rsi/state_7.png b/Resources/Textures/Structures/Furniture/Tables/r_glass.rsi/state_7.png index 495a1103fb..8e15bd7f11 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/r_glass.rsi/state_7.png and b/Resources/Textures/Structures/Furniture/Tables/r_glass.rsi/state_7.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/reinforced.rsi/full.png b/Resources/Textures/Structures/Furniture/Tables/reinforced.rsi/full.png index 19dec0a984..478eca14b0 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/reinforced.rsi/full.png and b/Resources/Textures/Structures/Furniture/Tables/reinforced.rsi/full.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/reinforced.rsi/meta.json b/Resources/Textures/Structures/Furniture/Tables/reinforced.rsi/meta.json index 0ebc40b814..3f2fb05658 100644 --- a/Resources/Textures/Structures/Furniture/Tables/reinforced.rsi/meta.json +++ b/Resources/Textures/Structures/Furniture/Tables/reinforced.rsi/meta.json @@ -1,163 +1,46 @@ { - "version": 1, - "size": { - "x": 32, - "y": 32 - }, - "license": "CC-BY-SA-3.0", - "copyright": "Taken from https://github.com/discordia-space/CEV-Eris/blob/0b3ab17dbad632ddf738b63900ef8df1926bba47/icons/obj/tables.dmi", - "states": [ - { - "name": "full", - "delays": [ - [ - 1 - ] - ] - }, - { - "name": "state_0", - "directions": 4, - "delays": [ - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ] - ] - }, - { - "name": "state_1", - "directions": 4, - "delays": [ - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ] - ] - }, - { - "name": "state_2", - "directions": 4, - "delays": [ - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ] - ] - }, - { - "name": "state_3", - "directions": 4, - "delays": [ - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ] - ] - }, - { - "name": "state_4", - "directions": 4, - "delays": [ - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ] - ] - }, - { - "name": "state_5", - "directions": 4, - "delays": [ - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ] - ] - }, - { - "name": "state_6", - "directions": 4, - "delays": [ - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ] - ] - }, - { - "name": "state_7", - "directions": 4, - "delays": [ - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ], - [ - 1.0 - ] - ] - } - ] -} + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from Taken from Paradise at https://github.com/ParadiseSS13/Paradise/blob/bd91a1b962fe9bd38e346e9fafd4ebb10784fcb3/icons/obj/smooth_structures/tables/reinforced_table.dmi", + "states": [ + { + "name": "full" + }, + { + "name": "state_0", + "directions": 4 + }, + { + "name": "state_1", + "directions": 4 + }, + { + "name": "state_2", + "directions": 4 + }, + { + "name": "state_3", + "directions": 4 + }, + { + "name": "state_4", + "directions": 4 + }, + { + "name": "state_5", + "directions": 4 + }, + { + "name": "state_6", + "directions": 4 + }, + { + "name": "state_7", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Structures/Furniture/Tables/reinforced.rsi/state_0.png b/Resources/Textures/Structures/Furniture/Tables/reinforced.rsi/state_0.png index 1e061724e1..b42b1c05bb 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/reinforced.rsi/state_0.png and b/Resources/Textures/Structures/Furniture/Tables/reinforced.rsi/state_0.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/reinforced.rsi/state_1.png b/Resources/Textures/Structures/Furniture/Tables/reinforced.rsi/state_1.png index 428ae7c3e1..6d74f31f6e 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/reinforced.rsi/state_1.png and b/Resources/Textures/Structures/Furniture/Tables/reinforced.rsi/state_1.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/reinforced.rsi/state_2.png b/Resources/Textures/Structures/Furniture/Tables/reinforced.rsi/state_2.png index 1e061724e1..b42b1c05bb 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/reinforced.rsi/state_2.png and b/Resources/Textures/Structures/Furniture/Tables/reinforced.rsi/state_2.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/reinforced.rsi/state_3.png b/Resources/Textures/Structures/Furniture/Tables/reinforced.rsi/state_3.png index 428ae7c3e1..6d74f31f6e 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/reinforced.rsi/state_3.png and b/Resources/Textures/Structures/Furniture/Tables/reinforced.rsi/state_3.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/reinforced.rsi/state_4.png b/Resources/Textures/Structures/Furniture/Tables/reinforced.rsi/state_4.png index a822918179..32186acdaa 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/reinforced.rsi/state_4.png and b/Resources/Textures/Structures/Furniture/Tables/reinforced.rsi/state_4.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/reinforced.rsi/state_5.png b/Resources/Textures/Structures/Furniture/Tables/reinforced.rsi/state_5.png index f57b63ecac..09654a2cc3 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/reinforced.rsi/state_5.png and b/Resources/Textures/Structures/Furniture/Tables/reinforced.rsi/state_5.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/reinforced.rsi/state_6.png b/Resources/Textures/Structures/Furniture/Tables/reinforced.rsi/state_6.png index a822918179..32186acdaa 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/reinforced.rsi/state_6.png and b/Resources/Textures/Structures/Furniture/Tables/reinforced.rsi/state_6.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/reinforced.rsi/state_7.png b/Resources/Textures/Structures/Furniture/Tables/reinforced.rsi/state_7.png index 52bb74a0da..8cac72fc6f 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/reinforced.rsi/state_7.png and b/Resources/Textures/Structures/Furniture/Tables/reinforced.rsi/state_7.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/wood.rsi/full.png b/Resources/Textures/Structures/Furniture/Tables/wood.rsi/full.png index e4c06d9d8f..3c24c896e6 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/wood.rsi/full.png and b/Resources/Textures/Structures/Furniture/Tables/wood.rsi/full.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/wood.rsi/meta.json b/Resources/Textures/Structures/Furniture/Tables/wood.rsi/meta.json index 667cf5efc1..dc98ecc234 100644 --- a/Resources/Textures/Structures/Furniture/Tables/wood.rsi/meta.json +++ b/Resources/Textures/Structures/Furniture/Tables/wood.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from vgstation at commit https://github.com/vgstation-coders/vgstation13/commit/a138859aec54b20dac65eb76e94e9db1b2202f55 and modified by Swept", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/47f6d91b37609b940152de62befa931dba35adf0", "size": { "x": 32, "y": 32 diff --git a/Resources/Textures/Structures/Furniture/Tables/wood.rsi/state_0.png b/Resources/Textures/Structures/Furniture/Tables/wood.rsi/state_0.png index 1e93bd9a76..458b725532 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/wood.rsi/state_0.png and b/Resources/Textures/Structures/Furniture/Tables/wood.rsi/state_0.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/wood.rsi/state_1.png b/Resources/Textures/Structures/Furniture/Tables/wood.rsi/state_1.png index 7e03504066..e522858f14 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/wood.rsi/state_1.png and b/Resources/Textures/Structures/Furniture/Tables/wood.rsi/state_1.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/wood.rsi/state_2.png b/Resources/Textures/Structures/Furniture/Tables/wood.rsi/state_2.png index 1e93bd9a76..458b725532 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/wood.rsi/state_2.png and b/Resources/Textures/Structures/Furniture/Tables/wood.rsi/state_2.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/wood.rsi/state_3.png b/Resources/Textures/Structures/Furniture/Tables/wood.rsi/state_3.png index 7e03504066..e522858f14 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/wood.rsi/state_3.png and b/Resources/Textures/Structures/Furniture/Tables/wood.rsi/state_3.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/wood.rsi/state_4.png b/Resources/Textures/Structures/Furniture/Tables/wood.rsi/state_4.png index 9421e66126..a57e1cf0d9 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/wood.rsi/state_4.png and b/Resources/Textures/Structures/Furniture/Tables/wood.rsi/state_4.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/wood.rsi/state_5.png b/Resources/Textures/Structures/Furniture/Tables/wood.rsi/state_5.png index 679bcc34ae..c1d14dfb97 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/wood.rsi/state_5.png and b/Resources/Textures/Structures/Furniture/Tables/wood.rsi/state_5.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/wood.rsi/state_6.png b/Resources/Textures/Structures/Furniture/Tables/wood.rsi/state_6.png index 9421e66126..a57e1cf0d9 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/wood.rsi/state_6.png and b/Resources/Textures/Structures/Furniture/Tables/wood.rsi/state_6.png differ diff --git a/Resources/Textures/Structures/Furniture/Tables/wood.rsi/state_7.png b/Resources/Textures/Structures/Furniture/Tables/wood.rsi/state_7.png index 87f2e5da7c..6b15d22996 100644 Binary files a/Resources/Textures/Structures/Furniture/Tables/wood.rsi/state_7.png and b/Resources/Textures/Structures/Furniture/Tables/wood.rsi/state_7.png differ diff --git a/Resources/Textures/Structures/Machines/artifact_crusher.rsi/icon.png b/Resources/Textures/Structures/Machines/artifact_crusher.rsi/icon.png new file mode 100644 index 0000000000..af5f78e368 Binary files /dev/null and b/Resources/Textures/Structures/Machines/artifact_crusher.rsi/icon.png differ diff --git a/Resources/Textures/Structures/Machines/artifact_crusher.rsi/meta.json b/Resources/Textures/Structures/Machines/artifact_crusher.rsi/meta.json index dc0d23c539..279bc73ec7 100644 --- a/Resources/Textures/Structures/Machines/artifact_crusher.rsi/meta.json +++ b/Resources/Textures/Structures/Machines/artifact_crusher.rsi/meta.json @@ -7,6 +7,9 @@ "y": 64 }, "states": [ + { + "name": "icon" + }, { "name": "glass" }, diff --git a/Resources/Textures/Structures/Machines/circuit_imprinter_hypercon.rsi/building.png b/Resources/Textures/Structures/Machines/circuit_imprinter_hypercon.rsi/building.png new file mode 100644 index 0000000000..7987532f39 Binary files /dev/null and b/Resources/Textures/Structures/Machines/circuit_imprinter_hypercon.rsi/building.png differ diff --git a/Resources/Textures/Structures/Machines/circuit_imprinter_hypercon.rsi/icon.png b/Resources/Textures/Structures/Machines/circuit_imprinter_hypercon.rsi/icon.png new file mode 100644 index 0000000000..e56878a7ad Binary files /dev/null and b/Resources/Textures/Structures/Machines/circuit_imprinter_hypercon.rsi/icon.png differ diff --git a/Resources/Textures/Structures/Machines/circuit_imprinter_hypercon.rsi/meta.json b/Resources/Textures/Structures/Machines/circuit_imprinter_hypercon.rsi/meta.json new file mode 100644 index 0000000000..faa9a362b4 --- /dev/null +++ b/Resources/Textures/Structures/Machines/circuit_imprinter_hypercon.rsi/meta.json @@ -0,0 +1,52 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Made by Piksqu for ss14, based on the circuit imprinter sprite taken from tgstation at https://github.com/tgstation/tgstation/commit/40d89d11ea4a5cb81d61dc1018b46f4e7d32c62a", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "panel" + }, + { + "name": "unlit" + }, + { + "name": "building", + "delays": [ + [ + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.16, + 0.18 + ] + ] + } + ] +} diff --git a/Resources/Textures/Structures/Machines/circuit_imprinter_hypercon.rsi/panel.png b/Resources/Textures/Structures/Machines/circuit_imprinter_hypercon.rsi/panel.png new file mode 100644 index 0000000000..e9c369c734 Binary files /dev/null and b/Resources/Textures/Structures/Machines/circuit_imprinter_hypercon.rsi/panel.png differ diff --git a/Resources/Textures/Structures/Machines/circuit_imprinter_hypercon.rsi/unlit.png b/Resources/Textures/Structures/Machines/circuit_imprinter_hypercon.rsi/unlit.png new file mode 100644 index 0000000000..9a9e240fbc Binary files /dev/null and b/Resources/Textures/Structures/Machines/circuit_imprinter_hypercon.rsi/unlit.png differ diff --git a/Resources/Textures/Structures/Machines/fax_machine.rsi/inserting_hamster.png b/Resources/Textures/Structures/Machines/fax_machine.rsi/inserting_hamster.png new file mode 100644 index 0000000000..5f14e3013f Binary files /dev/null and b/Resources/Textures/Structures/Machines/fax_machine.rsi/inserting_hamster.png differ diff --git a/Resources/Textures/Structures/Machines/fax_machine.rsi/inserting_mothroach.png b/Resources/Textures/Structures/Machines/fax_machine.rsi/inserting_mothroach.png new file mode 100644 index 0000000000..d034322697 Binary files /dev/null and b/Resources/Textures/Structures/Machines/fax_machine.rsi/inserting_mothroach.png differ diff --git a/Resources/Textures/Structures/Machines/fax_machine.rsi/inserting_mouse.png b/Resources/Textures/Structures/Machines/fax_machine.rsi/inserting_mouse.png new file mode 100644 index 0000000000..7fb87053f3 Binary files /dev/null and b/Resources/Textures/Structures/Machines/fax_machine.rsi/inserting_mouse.png differ diff --git a/Resources/Textures/Structures/Machines/fax_machine.rsi/meta.json b/Resources/Textures/Structures/Machines/fax_machine.rsi/meta.json index 1a8856301d..00681ca6da 100644 --- a/Resources/Textures/Structures/Machines/fax_machine.rsi/meta.json +++ b/Resources/Textures/Structures/Machines/fax_machine.rsi/meta.json @@ -40,6 +40,63 @@ ] ] }, + { + "name": "inserting_hamster", + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "inserting_mothroach", + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "inserting_mouse", + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + }, { "name": "printing", "delays": [ diff --git a/Resources/Textures/Structures/Machines/jukebox.rsi/meta.json b/Resources/Textures/Structures/Machines/jukebox.rsi/meta.json new file mode 100644 index 0000000000..f447b26ddf --- /dev/null +++ b/Resources/Textures/Structures/Machines/jukebox.rsi/meta.json @@ -0,0 +1,31 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from https://github.com/tgstation/tgstation at f349b842c84f500399bd5673e5e34a6bc45b001a, direct dmi link https://github.com/tgstation/tgstation/blob/f349b842c84f500399bd5673e5e34a6bc45b001a/icons/obj/stationobjs.dmi", + "states": [ + { + "name": "on" + }, + { + "name": "off" + }, + { + "name": "select", + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Structures/Machines/jukebox.rsi/off.png b/Resources/Textures/Structures/Machines/jukebox.rsi/off.png new file mode 100644 index 0000000000..f3c24b1c56 Binary files /dev/null and b/Resources/Textures/Structures/Machines/jukebox.rsi/off.png differ diff --git a/Resources/Textures/Structures/Machines/jukebox.rsi/on.png b/Resources/Textures/Structures/Machines/jukebox.rsi/on.png new file mode 100644 index 0000000000..b397adc16a Binary files /dev/null and b/Resources/Textures/Structures/Machines/jukebox.rsi/on.png differ diff --git a/Resources/Textures/Structures/Machines/jukebox.rsi/select.png b/Resources/Textures/Structures/Machines/jukebox.rsi/select.png new file mode 100644 index 0000000000..0dd6d81373 Binary files /dev/null and b/Resources/Textures/Structures/Machines/jukebox.rsi/select.png differ diff --git a/Resources/Textures/Structures/Machines/limbgrower.rsi/limbgrower_fill.png b/Resources/Textures/Structures/Machines/limbgrower.rsi/limbgrower_fill.png new file mode 100644 index 0000000000..1e45b36622 Binary files /dev/null and b/Resources/Textures/Structures/Machines/limbgrower.rsi/limbgrower_fill.png differ diff --git a/Resources/Textures/Structures/Machines/limbgrower.rsi/limbgrower_idleoff.png b/Resources/Textures/Structures/Machines/limbgrower.rsi/limbgrower_idleoff.png new file mode 100644 index 0000000000..73bab51916 Binary files /dev/null and b/Resources/Textures/Structures/Machines/limbgrower.rsi/limbgrower_idleoff.png differ diff --git a/Resources/Textures/Structures/Machines/limbgrower.rsi/limbgrower_idleon.png b/Resources/Textures/Structures/Machines/limbgrower.rsi/limbgrower_idleon.png new file mode 100644 index 0000000000..f47f93eb86 Binary files /dev/null and b/Resources/Textures/Structures/Machines/limbgrower.rsi/limbgrower_idleon.png differ diff --git a/Resources/Textures/Structures/Machines/limbgrower.rsi/limbgrower_openpanel.png b/Resources/Textures/Structures/Machines/limbgrower.rsi/limbgrower_openpanel.png new file mode 100644 index 0000000000..cc174c7d8b Binary files /dev/null and b/Resources/Textures/Structures/Machines/limbgrower.rsi/limbgrower_openpanel.png differ diff --git a/Resources/Textures/Structures/Machines/limbgrower.rsi/limbgrower_panelopen.png b/Resources/Textures/Structures/Machines/limbgrower.rsi/limbgrower_panelopen.png new file mode 100644 index 0000000000..d4c4de1f8c Binary files /dev/null and b/Resources/Textures/Structures/Machines/limbgrower.rsi/limbgrower_panelopen.png differ diff --git a/Resources/Textures/Structures/Machines/limbgrower.rsi/limbgrower_unfill.png b/Resources/Textures/Structures/Machines/limbgrower.rsi/limbgrower_unfill.png new file mode 100644 index 0000000000..6aa57f247e Binary files /dev/null and b/Resources/Textures/Structures/Machines/limbgrower.rsi/limbgrower_unfill.png differ diff --git a/Resources/Textures/Structures/Machines/limbgrower.rsi/meta.json b/Resources/Textures/Structures/Machines/limbgrower.rsi/meta.json new file mode 100644 index 0000000000..1b5f86463f --- /dev/null +++ b/Resources/Textures/Structures/Machines/limbgrower.rsi/meta.json @@ -0,0 +1,85 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from /tg/station at commit 85c26c1", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "limbgrower_fill", + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "limbgrower_unfill", + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "limbgrower_openpanel", + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "limbgrower_idleoff", + "delays": [ + [ + 0.3, + 0.3, + 0.3, + 0.3 + ] + ] + }, + { + "name": "limbgrower_idleon", + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "limbgrower_panelopen" + } + ] +} diff --git a/Resources/Textures/Nyanotrasen/Structures/Machines/metempsychotic.rsi/pod_1.png b/Resources/Textures/Structures/Machines/metempsychotic.rsi/cloning_active.png similarity index 100% rename from Resources/Textures/Nyanotrasen/Structures/Machines/metempsychotic.rsi/pod_1.png rename to Resources/Textures/Structures/Machines/metempsychotic.rsi/cloning_active.png diff --git a/Resources/Textures/Structures/Machines/metempsychotic.rsi/cloning_failed.png b/Resources/Textures/Structures/Machines/metempsychotic.rsi/cloning_failed.png new file mode 100644 index 0000000000..e85f4b2cba Binary files /dev/null and b/Resources/Textures/Structures/Machines/metempsychotic.rsi/cloning_failed.png differ diff --git a/Resources/Textures/Nyanotrasen/Structures/Machines/metempsychotic.rsi/pod_0.png b/Resources/Textures/Structures/Machines/metempsychotic.rsi/cloning_idle.png similarity index 100% rename from Resources/Textures/Nyanotrasen/Structures/Machines/metempsychotic.rsi/pod_0.png rename to Resources/Textures/Structures/Machines/metempsychotic.rsi/cloning_idle.png diff --git a/Resources/Textures/Structures/Machines/metempsychotic.rsi/meta.json b/Resources/Textures/Structures/Machines/metempsychotic.rsi/meta.json new file mode 100644 index 0000000000..da5034f713 --- /dev/null +++ b/Resources/Textures/Structures/Machines/metempsychotic.rsi/meta.json @@ -0,0 +1,22 @@ +{ + "version": 1, + "license": "CC-BY-4.0", + "copyright": "Created by discord user Four Hydra Heads#2075 (971500282364178512), failed state edited by VMSolidus", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "cloning_idle" + }, + { + "name": "cloning_active", + "delays": [ [ 0.1, 0.1, 0.1, 0.1, 0.1, 0.1 ] ] + }, + { + "name": "cloning_failed", + "delays": [ [ 0.1, 0.1, 0.1, 0.1 ] ] + } + ] +} diff --git a/Resources/Textures/Structures/Piping/Atmospherics/pipe.rsi/Bend-inhand-left.png b/Resources/Textures/Structures/Piping/Atmospherics/pipe.rsi/Bend-inhand-left.png new file mode 100644 index 0000000000..f4f60cdd17 Binary files /dev/null and b/Resources/Textures/Structures/Piping/Atmospherics/pipe.rsi/Bend-inhand-left.png differ diff --git a/Resources/Textures/Structures/Piping/Atmospherics/pipe.rsi/Bend-inhand-right.png b/Resources/Textures/Structures/Piping/Atmospherics/pipe.rsi/Bend-inhand-right.png new file mode 100644 index 0000000000..dfd245221f Binary files /dev/null and b/Resources/Textures/Structures/Piping/Atmospherics/pipe.rsi/Bend-inhand-right.png differ diff --git a/Resources/Textures/Structures/Piping/Atmospherics/pipe.rsi/Fourway-inhand-left.png b/Resources/Textures/Structures/Piping/Atmospherics/pipe.rsi/Fourway-inhand-left.png new file mode 100644 index 0000000000..4da2f458e8 Binary files /dev/null and b/Resources/Textures/Structures/Piping/Atmospherics/pipe.rsi/Fourway-inhand-left.png differ diff --git a/Resources/Textures/Structures/Piping/Atmospherics/pipe.rsi/Fourway-inhand-right.png b/Resources/Textures/Structures/Piping/Atmospherics/pipe.rsi/Fourway-inhand-right.png new file mode 100644 index 0000000000..a59a797165 Binary files /dev/null and b/Resources/Textures/Structures/Piping/Atmospherics/pipe.rsi/Fourway-inhand-right.png differ diff --git a/Resources/Textures/Structures/Piping/Atmospherics/pipe.rsi/TJunction-inhand-left.png b/Resources/Textures/Structures/Piping/Atmospherics/pipe.rsi/TJunction-inhand-left.png new file mode 100644 index 0000000000..8b14c9117a Binary files /dev/null and b/Resources/Textures/Structures/Piping/Atmospherics/pipe.rsi/TJunction-inhand-left.png differ diff --git a/Resources/Textures/Structures/Piping/Atmospherics/pipe.rsi/TJunction-inhand-right.png b/Resources/Textures/Structures/Piping/Atmospherics/pipe.rsi/TJunction-inhand-right.png new file mode 100644 index 0000000000..828920b400 Binary files /dev/null and b/Resources/Textures/Structures/Piping/Atmospherics/pipe.rsi/TJunction-inhand-right.png differ diff --git a/Resources/Textures/Structures/Piping/Atmospherics/pipe.rsi/inhand-left.png b/Resources/Textures/Structures/Piping/Atmospherics/pipe.rsi/inhand-left.png new file mode 100644 index 0000000000..7ca777a2e9 Binary files /dev/null and b/Resources/Textures/Structures/Piping/Atmospherics/pipe.rsi/inhand-left.png differ diff --git a/Resources/Textures/Structures/Piping/Atmospherics/pipe.rsi/inhand-right.png b/Resources/Textures/Structures/Piping/Atmospherics/pipe.rsi/inhand-right.png new file mode 100644 index 0000000000..6c8c55a515 Binary files /dev/null and b/Resources/Textures/Structures/Piping/Atmospherics/pipe.rsi/inhand-right.png differ diff --git a/Resources/Textures/Structures/Piping/Atmospherics/pipe.rsi/meta.json b/Resources/Textures/Structures/Piping/Atmospherics/pipe.rsi/meta.json index ae4ff9b12d..aecb62aee5 100644 --- a/Resources/Textures/Structures/Piping/Atmospherics/pipe.rsi/meta.json +++ b/Resources/Textures/Structures/Piping/Atmospherics/pipe.rsi/meta.json @@ -5,8 +5,40 @@ "y":32 }, "license":"CC-BY-SA-3.0", - "copyright":"pipeTrinaryConnectors made by Menshin for SS14 based on pipeTJunction, the rest is taken from https://github.com/tgstation/tgstation at commit 57cd1d59ca019dd0e7811ac451f295f818e573da", + "copyright":"Inhand sprites by alzore_(discord) for SS14. pipeTrinaryConnectors made by Menshin for SS14 based on pipeTJunction, the rest is taken from https://github.com/tgstation/tgstation at commit 57cd1d59ca019dd0e7811ac451f295f818e573da.", "states":[ + { + "name": "inhand-left", + "directions":4 + }, + { + "name":"inhand-right", + "directions":4 + }, + { + "name": "Bend-inhand-left", + "directions":4 + }, + { + "name":"Bend-inhand-right", + "directions":4 + }, + { + "name": "TJunction-inhand-left", + "directions":4 + }, + { + "name":"TJunction-inhand-right", + "directions":4 + }, + { + "name": "Fourway-inhand-left", + "directions":4 + }, + { + "name":"Fourway-inhand-right", + "directions":4 + }, { "name":"pipeBroken", "directions":1 @@ -38,6 +70,18 @@ { "name":"pipeTrinaryConnectors", "directions":4 + }, + { + "name":"storageStraight", + "directions":4 + }, + { + "name":"storageBend", + "directions":4 + }, + { + "name":"storageTJunction", + "directions":4 } ] } diff --git a/Resources/Textures/Structures/Piping/Atmospherics/pipe.rsi/pipeBend.png b/Resources/Textures/Structures/Piping/Atmospherics/pipe.rsi/pipeBend.png index b6408718eb..cda379a65b 100644 Binary files a/Resources/Textures/Structures/Piping/Atmospherics/pipe.rsi/pipeBend.png and b/Resources/Textures/Structures/Piping/Atmospherics/pipe.rsi/pipeBend.png differ diff --git a/Resources/Textures/Structures/Piping/Atmospherics/pipe.rsi/storageBend.png b/Resources/Textures/Structures/Piping/Atmospherics/pipe.rsi/storageBend.png new file mode 100644 index 0000000000..39ffe213ef Binary files /dev/null and b/Resources/Textures/Structures/Piping/Atmospherics/pipe.rsi/storageBend.png differ diff --git a/Resources/Textures/Structures/Piping/Atmospherics/pipe.rsi/storageStraight.png b/Resources/Textures/Structures/Piping/Atmospherics/pipe.rsi/storageStraight.png new file mode 100644 index 0000000000..715aeb5824 Binary files /dev/null and b/Resources/Textures/Structures/Piping/Atmospherics/pipe.rsi/storageStraight.png differ diff --git a/Resources/Textures/Structures/Piping/Atmospherics/pipe.rsi/storageTJunction.png b/Resources/Textures/Structures/Piping/Atmospherics/pipe.rsi/storageTJunction.png new file mode 100644 index 0000000000..af8376a152 Binary files /dev/null and b/Resources/Textures/Structures/Piping/Atmospherics/pipe.rsi/storageTJunction.png differ diff --git a/Resources/Textures/Structures/Specific/barberchair.rsi/barberchair.png b/Resources/Textures/Structures/Specific/barberchair.rsi/barberchair.png new file mode 100644 index 0000000000..244a875dca Binary files /dev/null and b/Resources/Textures/Structures/Specific/barberchair.rsi/barberchair.png differ diff --git a/Resources/Textures/Structures/Specific/barberchair.rsi/meta.json b/Resources/Textures/Structures/Specific/barberchair.rsi/meta.json new file mode 100644 index 0000000000..2e6b70f2fc --- /dev/null +++ b/Resources/Textures/Structures/Specific/barberchair.rsi/meta.json @@ -0,0 +1,15 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from BlueMoon at https://github.com/BlueMoon-Labs/MOLOT-BlueMoon-Station/blob/master/modular_bluemoon/krashly/icons/obj/chairs.dmi", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "barberchair", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Structures/Specific/barbershop.rsi/barberchair.png b/Resources/Textures/Structures/Specific/barbershop.rsi/barberchair.png deleted file mode 100644 index 7e8fd78ab7..0000000000 Binary files a/Resources/Textures/Structures/Specific/barbershop.rsi/barberchair.png and /dev/null differ diff --git a/Resources/Textures/Structures/Specific/barbershop.rsi/meta.json b/Resources/Textures/Structures/Specific/barbershop.rsi/meta.json index 1226428bcf..6d57cc380d 100644 --- a/Resources/Textures/Structures/Specific/barbershop.rsi/meta.json +++ b/Resources/Textures/Structures/Specific/barbershop.rsi/meta.json @@ -18,9 +18,6 @@ ] ] }, - { - "name": "barberchair" - }, { "name": "dyedispenser" }, diff --git a/Resources/Textures/Structures/Storage/Crates/uranium.rsi/base.png b/Resources/Textures/Structures/Storage/Crates/uranium.rsi/base.png new file mode 100644 index 0000000000..b0b2f0aef5 Binary files /dev/null and b/Resources/Textures/Structures/Storage/Crates/uranium.rsi/base.png differ diff --git a/Resources/Textures/Structures/Storage/Crates/uranium.rsi/closed.png b/Resources/Textures/Structures/Storage/Crates/uranium.rsi/closed.png new file mode 100644 index 0000000000..7170698294 Binary files /dev/null and b/Resources/Textures/Structures/Storage/Crates/uranium.rsi/closed.png differ diff --git a/Resources/Textures/Structures/Storage/Crates/uranium.rsi/icon.png b/Resources/Textures/Structures/Storage/Crates/uranium.rsi/icon.png new file mode 100644 index 0000000000..f56c7ef4e1 Binary files /dev/null and b/Resources/Textures/Structures/Storage/Crates/uranium.rsi/icon.png differ diff --git a/Resources/Textures/Structures/Storage/Crates/uranium.rsi/locked.png b/Resources/Textures/Structures/Storage/Crates/uranium.rsi/locked.png new file mode 100644 index 0000000000..aceacfce59 Binary files /dev/null and b/Resources/Textures/Structures/Storage/Crates/uranium.rsi/locked.png differ diff --git a/Resources/Textures/Structures/Storage/Crates/uranium.rsi/meta.json b/Resources/Textures/Structures/Storage/Crates/uranium.rsi/meta.json new file mode 100644 index 0000000000..6a4a45c012 --- /dev/null +++ b/Resources/Textures/Structures/Storage/Crates/uranium.rsi/meta.json @@ -0,0 +1,45 @@ +{ + "version": 1, + "license": "CC0-1.0", + "copyright": "Created by EmoGarbage404 (github) for Space Station 14.", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "base" + }, + { + "name": "closed" + }, + { + "name": "open" + }, + { + "name": "welded" + }, + { + "name": "sparking", + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + }, + { + "name": "locked" + }, + { + "name": "unlocked" + } + ] +} diff --git a/Resources/Textures/Structures/Storage/Crates/uranium.rsi/open.png b/Resources/Textures/Structures/Storage/Crates/uranium.rsi/open.png new file mode 100644 index 0000000000..de95795487 Binary files /dev/null and b/Resources/Textures/Structures/Storage/Crates/uranium.rsi/open.png differ diff --git a/Resources/Textures/Structures/Storage/Crates/uranium.rsi/sparking.png b/Resources/Textures/Structures/Storage/Crates/uranium.rsi/sparking.png new file mode 100644 index 0000000000..87b78b9b46 Binary files /dev/null and b/Resources/Textures/Structures/Storage/Crates/uranium.rsi/sparking.png differ diff --git a/Resources/Textures/Structures/Storage/Crates/uranium.rsi/unlocked.png b/Resources/Textures/Structures/Storage/Crates/uranium.rsi/unlocked.png new file mode 100644 index 0000000000..94b89fa655 Binary files /dev/null and b/Resources/Textures/Structures/Storage/Crates/uranium.rsi/unlocked.png differ diff --git a/Resources/Textures/Structures/Storage/Crates/uranium.rsi/welded.png b/Resources/Textures/Structures/Storage/Crates/uranium.rsi/welded.png new file mode 100644 index 0000000000..311739a270 Binary files /dev/null and b/Resources/Textures/Structures/Storage/Crates/uranium.rsi/welded.png differ diff --git a/Resources/Textures/Structures/Storage/closet.rsi/bartender.png b/Resources/Textures/Structures/Storage/closet.rsi/bartender.png new file mode 100644 index 0000000000..5846a93d96 Binary files /dev/null and b/Resources/Textures/Structures/Storage/closet.rsi/bartender.png differ diff --git a/Resources/Textures/Structures/Storage/closet.rsi/bartender_door.png b/Resources/Textures/Structures/Storage/closet.rsi/bartender_door.png new file mode 100644 index 0000000000..a8f3f1fd1f Binary files /dev/null and b/Resources/Textures/Structures/Storage/closet.rsi/bartender_door.png differ diff --git a/Resources/Textures/Structures/Storage/closet.rsi/bartender_open.png b/Resources/Textures/Structures/Storage/closet.rsi/bartender_open.png new file mode 100644 index 0000000000..947b376353 Binary files /dev/null and b/Resources/Textures/Structures/Storage/closet.rsi/bartender_open.png differ diff --git a/Resources/Textures/Structures/Storage/closet.rsi/jan.png b/Resources/Textures/Structures/Storage/closet.rsi/jan.png new file mode 100644 index 0000000000..dd15c4a3a6 Binary files /dev/null and b/Resources/Textures/Structures/Storage/closet.rsi/jan.png differ diff --git a/Resources/Textures/Structures/Storage/closet.rsi/jan_door.png b/Resources/Textures/Structures/Storage/closet.rsi/jan_door.png new file mode 100644 index 0000000000..055374e977 Binary files /dev/null and b/Resources/Textures/Structures/Storage/closet.rsi/jan_door.png differ diff --git a/Resources/Textures/Structures/Storage/closet.rsi/jan_open.png b/Resources/Textures/Structures/Storage/closet.rsi/jan_open.png new file mode 100644 index 0000000000..d2da542a63 Binary files /dev/null and b/Resources/Textures/Structures/Storage/closet.rsi/jan_open.png differ diff --git a/Resources/Textures/Structures/Storage/closet.rsi/meta.json b/Resources/Textures/Structures/Storage/closet.rsi/meta.json index d3802637c0..23e024d043 100644 --- a/Resources/Textures/Structures/Storage/closet.rsi/meta.json +++ b/Resources/Textures/Structures/Storage/closet.rsi/meta.json @@ -511,6 +511,33 @@ ] ] }, + { + "name": "bartender" + }, + { + "name": "bartender_door" + }, + { + "name": "bartender_open" + }, + { + "name": "jan" + }, + { + "name": "jan_door" + }, + { + "name": "jan_open" + }, + { + "name": "syndicate1" + }, + { + "name": "syndicate1_door" + }, + { + "name": "syndicate1_open" + }, { "name": "syndicate" }, diff --git a/Resources/Textures/Structures/Storage/closet.rsi/syndicate1.png b/Resources/Textures/Structures/Storage/closet.rsi/syndicate1.png new file mode 100644 index 0000000000..879f85b639 Binary files /dev/null and b/Resources/Textures/Structures/Storage/closet.rsi/syndicate1.png differ diff --git a/Resources/Textures/Structures/Storage/closet.rsi/syndicate1_door.png b/Resources/Textures/Structures/Storage/closet.rsi/syndicate1_door.png new file mode 100644 index 0000000000..e654ce7f12 Binary files /dev/null and b/Resources/Textures/Structures/Storage/closet.rsi/syndicate1_door.png differ diff --git a/Resources/Textures/Structures/Storage/closet.rsi/syndicate1_open.png b/Resources/Textures/Structures/Storage/closet.rsi/syndicate1_open.png new file mode 100644 index 0000000000..7f5d1dcaf5 Binary files /dev/null and b/Resources/Textures/Structures/Storage/closet.rsi/syndicate1_open.png differ diff --git a/Resources/Textures/Structures/Storage/glassbox.rsi/DamageOverlay_12.png b/Resources/Textures/Structures/Storage/glassbox.rsi/DamageOverlay_12.png index cce6fe0ba3..a82bb8b233 100644 Binary files a/Resources/Textures/Structures/Storage/glassbox.rsi/DamageOverlay_12.png and b/Resources/Textures/Structures/Storage/glassbox.rsi/DamageOverlay_12.png differ diff --git a/Resources/Textures/Structures/Storage/glassbox.rsi/DamageOverlay_4.png b/Resources/Textures/Structures/Storage/glassbox.rsi/DamageOverlay_4.png index 08b6f66449..054f1f520c 100644 Binary files a/Resources/Textures/Structures/Storage/glassbox.rsi/DamageOverlay_4.png and b/Resources/Textures/Structures/Storage/glassbox.rsi/DamageOverlay_4.png differ diff --git a/Resources/Textures/Structures/Storage/glassbox.rsi/DamageOverlay_8.png b/Resources/Textures/Structures/Storage/glassbox.rsi/DamageOverlay_8.png index 6d980d1c67..e1c2297468 100644 Binary files a/Resources/Textures/Structures/Storage/glassbox.rsi/DamageOverlay_8.png and b/Resources/Textures/Structures/Storage/glassbox.rsi/DamageOverlay_8.png differ diff --git a/Resources/Textures/Structures/Storage/glassbox.rsi/glassbox-empty-open.png b/Resources/Textures/Structures/Storage/glassbox.rsi/base.png similarity index 100% rename from Resources/Textures/Structures/Storage/glassbox.rsi/glassbox-empty-open.png rename to Resources/Textures/Structures/Storage/glassbox.rsi/base.png diff --git a/Resources/Textures/Structures/Storage/glassbox.rsi/glass-4.png b/Resources/Textures/Structures/Storage/glassbox.rsi/glass-broken.png similarity index 100% rename from Resources/Textures/Structures/Storage/glassbox.rsi/glass-4.png rename to Resources/Textures/Structures/Storage/glassbox.rsi/glass-broken.png diff --git a/Resources/Textures/Structures/Storage/glassbox.rsi/glassbox-filled-closed.png b/Resources/Textures/Structures/Storage/glassbox.rsi/glassbox-filled-closed.png deleted file mode 100644 index b558cf5212..0000000000 Binary files a/Resources/Textures/Structures/Storage/glassbox.rsi/glassbox-filled-closed.png and /dev/null differ diff --git a/Resources/Textures/Structures/Storage/glassbox.rsi/glassbox-filled-open.png b/Resources/Textures/Structures/Storage/glassbox.rsi/glassbox-filled-open.png deleted file mode 100644 index 48db8e88e5..0000000000 Binary files a/Resources/Textures/Structures/Storage/glassbox.rsi/glassbox-filled-open.png and /dev/null differ diff --git a/Resources/Textures/Structures/Storage/glassbox.rsi/glassbox.png b/Resources/Textures/Structures/Storage/glassbox.rsi/glassbox.png deleted file mode 100644 index 3a3bf591ca..0000000000 Binary files a/Resources/Textures/Structures/Storage/glassbox.rsi/glassbox.png and /dev/null differ diff --git a/Resources/Textures/Structures/Storage/glassbox.rsi/icon.png b/Resources/Textures/Structures/Storage/glassbox.rsi/icon.png new file mode 100644 index 0000000000..9d1c8c8685 Binary files /dev/null and b/Resources/Textures/Structures/Storage/glassbox.rsi/icon.png differ diff --git a/Resources/Textures/Structures/Storage/glassbox.rsi/meta.json b/Resources/Textures/Structures/Storage/glassbox.rsi/meta.json index 5ce653f37b..33decc4009 100644 --- a/Resources/Textures/Structures/Storage/glassbox.rsi/meta.json +++ b/Resources/Textures/Structures/Storage/glassbox.rsi/meta.json @@ -1,50 +1,44 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation https://github.com/tgstation/tgstation/commit/0129a094635aac51e00fdc7aa3b4248affc1f49d Sprite modified and updated by Nimfar11 (Github), Shatter resprite by KREKS", + "copyright": "Taken from tgstation https://github.com/tgstation/tgstation/commit/0129a094635aac51e00fdc7aa3b4248affc1f49d Sprite modified and updated by Nimfar11 (Github), Shatter resprite by KREKS and modified by MilenVolf (GitHub)", "size": { "x": 32, "y": 32 }, "states": [ { - "name": "glass" - }, - { - "name": "DamageOverlay_4" + "name": "base" }, { - "name": "DamageOverlay_8" + "name": "glass" }, { - "name": "DamageOverlay_12" + "name": "glass-up" }, { - "name": "glass-4" + "name": "glass-broken" }, { - "name": "glass-up" + "name": "caplaser" }, { "name": "locked" }, - { - "name": "caplaser" - }, { "name": "unlocked" }, { - "name": "glassbox" + "name": "icon" }, { - "name": "glassbox-empty-open" + "name": "DamageOverlay_4" }, { - "name": "glassbox-filled-closed" + "name": "DamageOverlay_8" }, { - "name": "glassbox-filled-open" + "name": "DamageOverlay_12" } ] } \ No newline at end of file diff --git a/Resources/Textures/Structures/Storage/wall_locker.rsi/meta.json b/Resources/Textures/Structures/Storage/wall_locker.rsi/meta.json index b6ed63c7a3..a954c838e3 100644 --- a/Resources/Textures/Structures/Storage/wall_locker.rsi/meta.json +++ b/Resources/Textures/Structures/Storage/wall_locker.rsi/meta.json @@ -33,6 +33,9 @@ { "name": "unlocked" }, { "name": "welded" }, { "name": "white_door" }, - { "name": "yellow_door" } + { "name": "yellow_door" }, + { "name": "rad" }, + { "name": "rad_door" }, + { "name": "rad_open" } ] } diff --git a/Resources/Textures/Structures/Storage/wall_locker.rsi/rad.png b/Resources/Textures/Structures/Storage/wall_locker.rsi/rad.png new file mode 100644 index 0000000000..5c7a95a8f8 Binary files /dev/null and b/Resources/Textures/Structures/Storage/wall_locker.rsi/rad.png differ diff --git a/Resources/Textures/Structures/Storage/wall_locker.rsi/rad_door.png b/Resources/Textures/Structures/Storage/wall_locker.rsi/rad_door.png new file mode 100644 index 0000000000..7f0f248953 Binary files /dev/null and b/Resources/Textures/Structures/Storage/wall_locker.rsi/rad_door.png differ diff --git a/Resources/Textures/Structures/Storage/wall_locker.rsi/rad_open.png b/Resources/Textures/Structures/Storage/wall_locker.rsi/rad_open.png new file mode 100644 index 0000000000..2beb8fd4d6 Binary files /dev/null and b/Resources/Textures/Structures/Storage/wall_locker.rsi/rad_open.png differ diff --git a/Resources/Textures/Structures/Wallmounts/air_monitors.rsi/alarm1.png b/Resources/Textures/Structures/Wallmounts/air_monitors.rsi/alarm1.png index d371695e12..9d8640f85b 100644 Binary files a/Resources/Textures/Structures/Wallmounts/air_monitors.rsi/alarm1.png and b/Resources/Textures/Structures/Wallmounts/air_monitors.rsi/alarm1.png differ diff --git a/Resources/Textures/Structures/Wallmounts/air_monitors.rsi/alarm2.png b/Resources/Textures/Structures/Wallmounts/air_monitors.rsi/alarm2.png index 9d8640f85b..d371695e12 100644 Binary files a/Resources/Textures/Structures/Wallmounts/air_monitors.rsi/alarm2.png and b/Resources/Textures/Structures/Wallmounts/air_monitors.rsi/alarm2.png differ diff --git a/Resources/Textures/Structures/Wallmounts/air_monitors.rsi/meta.json b/Resources/Textures/Structures/Wallmounts/air_monitors.rsi/meta.json index 7484be2ffe..72f6d194f8 100644 --- a/Resources/Textures/Structures/Wallmounts/air_monitors.rsi/meta.json +++ b/Resources/Textures/Structures/Wallmounts/air_monitors.rsi/meta.json @@ -10,56 +10,17 @@ { "name": "alarm0", "directions": 4, - "delays": [ - [2.8, 2.8], - [2.8, 2.8], - [2.8, 2.8], - [2.8, 2.8] - ] + "delays": [ [ 2.8, 2.8 ], [ 2.8, 2.8 ], [ 2.8, 2.8 ], [ 2.8, 2.8 ] ] }, { "name": "alarm1", "directions": 4, - "delays": [ - [ - 0.6, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, - 0.2, 0.2, 0.2, 0.2, 0.2, 0.6, 0.6, 0.6 - ], - [ - 0.6, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, - 0.2, 0.2, 0.2, 0.2, 0.2, 0.6, 0.6, 0.6 - ], - [ - 0.6, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, - 0.2, 0.2, 0.2, 0.2, 0.2, 0.6, 0.6, 0.6 - ], - [ - 0.6, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, - 0.2, 0.2, 0.2, 0.2, 0.2, 0.6, 0.6, 0.6 - ] - ] + "delays": [ [ 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4 ], [ 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4 ], [ 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4 ], [ 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4 ] ] }, { "name": "alarm2", "directions": 4, - "delays": [ - [ - 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.4, 0.4, 0.4, 0.4, - 0.4, 0.4 - ], - [ - 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.4, 0.4, 0.4, 0.4, - 0.4, 0.4 - ], - [ - 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.4, 0.4, 0.4, 0.4, - 0.4, 0.4 - ], - [ - 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.4, 0.4, 0.4, 0.4, - 0.4, 0.4 - ] - ] + "delays": [ [ 0.6, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.6, 0.6, 0.6 ], [ 0.6, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.6, 0.6, 0.6 ], [ 0.6, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.6, 0.6, 0.6 ], [ 0.6, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.6, 0.6, 0.6 ] ] }, { "name": "alarm_b1", @@ -84,12 +45,12 @@ { "name": "auth_off", "directions": 1, - "delays": [[1]] + "delays": [ [ 1 ] ] }, { "name": "auth_on", "directions": 1, - "delays": [[0.1, 0.1]] + "delays": [ [ 0.1, 0.1 ] ] }, { "name": "fire0", @@ -106,22 +67,12 @@ { "name": "fire_2", "directions": 4, - "delays": [ - [0.5, 0.5], - [0.5, 0.5], - [0.5, 0.5], - [0.5, 0.5] - ] + "delays": [ [ 0.5, 0.5 ], [ 0.5, 0.5 ], [ 0.5, 0.5 ], [ 0.5, 0.5 ] ] }, { "name": "fire_3", "directions": 4, - "delays": [ - [0.3, 0.3], - [0.3, 0.3], - [0.3, 0.3], - [0.3, 0.3] - ] + "delays": [ [ 0.3, 0.3 ], [ 0.3, 0.3 ], [ 0.3, 0.3 ], [ 0.3, 0.3 ] ] }, { "name": "fire_b0", @@ -142,12 +93,7 @@ { "name": "fire_emagged", "directions": 4, - "delays": [ - [0.3, 0.3], - [0.3, 0.3], - [0.3, 0.3], - [0.3, 0.3] - ] + "delays": [ [ 0.3, 0.3 ], [ 0.3, 0.3 ], [ 0.3, 0.3 ], [ 0.3, 0.3 ] ] }, { "name": "fire_detected" @@ -159,12 +105,7 @@ { "name": "fire_on", "directions": 4, - "delays": [ - [0.2, 0.2, 0.2, 0.2, 0.2, 0.2], - [0.2, 0.2, 0.2, 0.2, 0.2, 0.2], - [0.2, 0.2, 0.2, 0.2, 0.2, 0.2], - [0.2, 0.2, 0.2, 0.2, 0.2, 0.2] - ] + "delays": [ [ 0.2, 0.2, 0.2, 0.2, 0.2, 0.2 ], [ 0.2, 0.2, 0.2, 0.2, 0.2, 0.2 ], [ 0.2, 0.2, 0.2, 0.2, 0.2, 0.2 ], [ 0.2, 0.2, 0.2, 0.2, 0.2, 0.2 ] ] }, { "name": "fire_overlay" @@ -174,4 +115,4 @@ "directions": 4 } ] -} +} \ No newline at end of file diff --git a/Resources/Textures/Structures/Walls/rock.rsi/meta.json b/Resources/Textures/Structures/Walls/rock.rsi/meta.json index aa46326a4b..27935ec089 100644 --- a/Resources/Textures/Structures/Walls/rock.rsi/meta.json +++ b/Resources/Textures/Structures/Walls/rock.rsi/meta.json @@ -198,6 +198,9 @@ }, { "name": "rock_andesite_west" + }, + { + "name": "rock_bluespace" } ] } diff --git a/Resources/Textures/Structures/Walls/rock.rsi/rock_bluespace.png b/Resources/Textures/Structures/Walls/rock.rsi/rock_bluespace.png new file mode 100644 index 0000000000..e3368c34d7 Binary files /dev/null and b/Resources/Textures/Structures/Walls/rock.rsi/rock_bluespace.png differ diff --git a/Resources/Textures/Structures/Windows/directional.rsi/frosted_window.png b/Resources/Textures/Structures/Windows/directional.rsi/frosted_window.png index 4f9bf4c955..c9f0b82f5d 100644 Binary files a/Resources/Textures/Structures/Windows/directional.rsi/frosted_window.png and b/Resources/Textures/Structures/Windows/directional.rsi/frosted_window.png differ diff --git a/Resources/Textures/Structures/Windows/directional.rsi/meta.json b/Resources/Textures/Structures/Windows/directional.rsi/meta.json index 5a01452c00..f93cacfa69 100644 --- a/Resources/Textures/Structures/Windows/directional.rsi/meta.json +++ b/Resources/Textures/Structures/Windows/directional.rsi/meta.json @@ -1,47 +1,47 @@ { - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "tgstation at ff1c30ac123dd28c6b5fee11e7f03654f5daa731, plasma windows from paradise at 44c12c6d9d0e42a9d3a582dff7e9a8d72b6ea68a, uranium windows made via edit by SphiraI(github), clockwork windows from https://github.com/tgstation/tgstation/blob/21b42d49ecf2b87f665b5f122368f6a247676721/icons/obj/smooth_structures/structure_variations.dmi", - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "window", - "directions": 4 + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from Paradise at https://github.com/ParadiseSS13/Paradise/blob/bd91a1b962fe9bd38e346e9fafd4ebb10784fcb3/icons/obj/structures.dmi, uranium windows made via edit by SphiraI(github), clockwork windows from https://github.com/tgstation/tgstation/blob/21b42d49ecf2b87f665b5f122368f6a247676721/icons/obj/smooth_structures/structure_variations.dmi", + "size": { + "x": 32, + "y": 32 }, - { - "name": "reinforced_window", - "directions": 4 - }, - { - "name": "tinted_window", - "directions": 4 - }, - { - "name": "frosted_window", - "directions": 4 - }, - { - "name": "clock_window", - "directions": 4 - }, - { - "name": "plasma_window", - "directions": 4 - }, - { - "name": "plasma_reinforced_window", - "directions": 4 - }, - { - "name": "uranium_window", - "directions": 4 - }, - { - "name": "uranium_reinforced_window", - "directions": 4 - } - ] -} + "states": [ + { + "name": "window", + "directions": 4 + }, + { + "name": "reinforced_window", + "directions": 4 + }, + { + "name": "tinted_window", + "directions": 4 + }, + { + "name": "frosted_window", + "directions": 4 + }, + { + "name": "clock_window", + "directions": 4 + }, + { + "name": "plasma_window", + "directions": 4 + }, + { + "name": "plasma_reinforced_window", + "directions": 4 + }, + { + "name": "uranium_window", + "directions": 4 + }, + { + "name": "uranium_reinforced_window", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Structures/Windows/directional.rsi/plasma_reinforced_window.png b/Resources/Textures/Structures/Windows/directional.rsi/plasma_reinforced_window.png index c1269b424e..2c18ad87f1 100644 Binary files a/Resources/Textures/Structures/Windows/directional.rsi/plasma_reinforced_window.png and b/Resources/Textures/Structures/Windows/directional.rsi/plasma_reinforced_window.png differ diff --git a/Resources/Textures/Structures/Windows/directional.rsi/plasma_window.png b/Resources/Textures/Structures/Windows/directional.rsi/plasma_window.png index b1d1f4fef5..92f31ef80d 100644 Binary files a/Resources/Textures/Structures/Windows/directional.rsi/plasma_window.png and b/Resources/Textures/Structures/Windows/directional.rsi/plasma_window.png differ diff --git a/Resources/Textures/Structures/Windows/directional.rsi/reinforced_window.png b/Resources/Textures/Structures/Windows/directional.rsi/reinforced_window.png index 865f079317..1be51d9932 100644 Binary files a/Resources/Textures/Structures/Windows/directional.rsi/reinforced_window.png and b/Resources/Textures/Structures/Windows/directional.rsi/reinforced_window.png differ diff --git a/Resources/Textures/Structures/Windows/directional.rsi/tinted_window.png b/Resources/Textures/Structures/Windows/directional.rsi/tinted_window.png index 4f9bf4c955..c9f0b82f5d 100644 Binary files a/Resources/Textures/Structures/Windows/directional.rsi/tinted_window.png and b/Resources/Textures/Structures/Windows/directional.rsi/tinted_window.png differ diff --git a/Resources/Textures/Structures/Windows/directional.rsi/window.png b/Resources/Textures/Structures/Windows/directional.rsi/window.png index a510742c94..b902ba8e9f 100644 Binary files a/Resources/Textures/Structures/Windows/directional.rsi/window.png and b/Resources/Textures/Structures/Windows/directional.rsi/window.png differ diff --git a/Resources/Textures/Structures/cargo_pallets.rsi/cargo_pallet_buy_0.png b/Resources/Textures/Structures/cargo_pallets.rsi/cargo_pallet_buy_0.png new file mode 100644 index 0000000000..1505c892b8 Binary files /dev/null and b/Resources/Textures/Structures/cargo_pallets.rsi/cargo_pallet_buy_0.png differ diff --git a/Resources/Textures/Structures/cargo_pallets.rsi/cargo_pallet_buy_1.png b/Resources/Textures/Structures/cargo_pallets.rsi/cargo_pallet_buy_1.png new file mode 100644 index 0000000000..e0d5a4d39e Binary files /dev/null and b/Resources/Textures/Structures/cargo_pallets.rsi/cargo_pallet_buy_1.png differ diff --git a/Resources/Textures/Structures/cargo_pallets.rsi/cargo_pallet_buy_2.png b/Resources/Textures/Structures/cargo_pallets.rsi/cargo_pallet_buy_2.png new file mode 100644 index 0000000000..1505c892b8 Binary files /dev/null and b/Resources/Textures/Structures/cargo_pallets.rsi/cargo_pallet_buy_2.png differ diff --git a/Resources/Textures/Structures/cargo_pallets.rsi/cargo_pallet_buy_3.png b/Resources/Textures/Structures/cargo_pallets.rsi/cargo_pallet_buy_3.png new file mode 100644 index 0000000000..e0d5a4d39e Binary files /dev/null and b/Resources/Textures/Structures/cargo_pallets.rsi/cargo_pallet_buy_3.png differ diff --git a/Resources/Textures/Structures/cargo_pallets.rsi/cargo_pallet_buy_4.png b/Resources/Textures/Structures/cargo_pallets.rsi/cargo_pallet_buy_4.png new file mode 100644 index 0000000000..38cb411508 Binary files /dev/null and b/Resources/Textures/Structures/cargo_pallets.rsi/cargo_pallet_buy_4.png differ diff --git a/Resources/Textures/Structures/cargo_pallets.rsi/cargo_pallet_buy_5.png b/Resources/Textures/Structures/cargo_pallets.rsi/cargo_pallet_buy_5.png new file mode 100644 index 0000000000..b863e36b8c Binary files /dev/null and b/Resources/Textures/Structures/cargo_pallets.rsi/cargo_pallet_buy_5.png differ diff --git a/Resources/Textures/Structures/cargo_pallets.rsi/cargo_pallet_buy_6.png b/Resources/Textures/Structures/cargo_pallets.rsi/cargo_pallet_buy_6.png new file mode 100644 index 0000000000..38cb411508 Binary files /dev/null and b/Resources/Textures/Structures/cargo_pallets.rsi/cargo_pallet_buy_6.png differ diff --git a/Resources/Textures/Structures/cargo_pallets.rsi/cargo_pallet_buy_7.png b/Resources/Textures/Structures/cargo_pallets.rsi/cargo_pallet_buy_7.png new file mode 100644 index 0000000000..88ffa378d2 Binary files /dev/null and b/Resources/Textures/Structures/cargo_pallets.rsi/cargo_pallet_buy_7.png differ diff --git a/Resources/Textures/Structures/cargo_pallets.rsi/cargo_pallet_sell_0.png b/Resources/Textures/Structures/cargo_pallets.rsi/cargo_pallet_sell_0.png new file mode 100644 index 0000000000..136a48087b Binary files /dev/null and b/Resources/Textures/Structures/cargo_pallets.rsi/cargo_pallet_sell_0.png differ diff --git a/Resources/Textures/Structures/cargo_pallets.rsi/cargo_pallet_sell_1.png b/Resources/Textures/Structures/cargo_pallets.rsi/cargo_pallet_sell_1.png new file mode 100644 index 0000000000..06fb688c5b Binary files /dev/null and b/Resources/Textures/Structures/cargo_pallets.rsi/cargo_pallet_sell_1.png differ diff --git a/Resources/Textures/Structures/cargo_pallets.rsi/cargo_pallet_sell_2.png b/Resources/Textures/Structures/cargo_pallets.rsi/cargo_pallet_sell_2.png new file mode 100644 index 0000000000..136a48087b Binary files /dev/null and b/Resources/Textures/Structures/cargo_pallets.rsi/cargo_pallet_sell_2.png differ diff --git a/Resources/Textures/Structures/cargo_pallets.rsi/cargo_pallet_sell_3.png b/Resources/Textures/Structures/cargo_pallets.rsi/cargo_pallet_sell_3.png new file mode 100644 index 0000000000..06fb688c5b Binary files /dev/null and b/Resources/Textures/Structures/cargo_pallets.rsi/cargo_pallet_sell_3.png differ diff --git a/Resources/Textures/Structures/cargo_pallets.rsi/cargo_pallet_sell_4.png b/Resources/Textures/Structures/cargo_pallets.rsi/cargo_pallet_sell_4.png new file mode 100644 index 0000000000..80a85ef238 Binary files /dev/null and b/Resources/Textures/Structures/cargo_pallets.rsi/cargo_pallet_sell_4.png differ diff --git a/Resources/Textures/Structures/cargo_pallets.rsi/cargo_pallet_sell_5.png b/Resources/Textures/Structures/cargo_pallets.rsi/cargo_pallet_sell_5.png new file mode 100644 index 0000000000..4a194d7fb1 Binary files /dev/null and b/Resources/Textures/Structures/cargo_pallets.rsi/cargo_pallet_sell_5.png differ diff --git a/Resources/Textures/Structures/cargo_pallets.rsi/cargo_pallet_sell_6.png b/Resources/Textures/Structures/cargo_pallets.rsi/cargo_pallet_sell_6.png new file mode 100644 index 0000000000..80a85ef238 Binary files /dev/null and b/Resources/Textures/Structures/cargo_pallets.rsi/cargo_pallet_sell_6.png differ diff --git a/Resources/Textures/Structures/cargo_pallets.rsi/cargo_pallet_sell_7.png b/Resources/Textures/Structures/cargo_pallets.rsi/cargo_pallet_sell_7.png new file mode 100644 index 0000000000..88ffa378d2 Binary files /dev/null and b/Resources/Textures/Structures/cargo_pallets.rsi/cargo_pallet_sell_7.png differ diff --git a/Resources/Textures/Structures/cargo_pallets.rsi/meta.json b/Resources/Textures/Structures/cargo_pallets.rsi/meta.json index 4751d95b9b..c32fcfefd0 100644 --- a/Resources/Textures/Structures/cargo_pallets.rsi/meta.json +++ b/Resources/Textures/Structures/cargo_pallets.rsi/meta.json @@ -10,8 +10,72 @@ { "name": "cargo_pallet_buy" }, + { + "name": "cargo_pallet_buy_0", + "directions": 4 + }, + { + "name": "cargo_pallet_buy_1", + "directions": 4 + }, + { + "name": "cargo_pallet_buy_2", + "directions": 4 + }, + { + "name": "cargo_pallet_buy_3", + "directions": 4 + }, + { + "name": "cargo_pallet_buy_4", + "directions": 4 + }, + { + "name": "cargo_pallet_buy_5", + "directions": 4 + }, + { + "name": "cargo_pallet_buy_6", + "directions": 4 + }, + { + "name": "cargo_pallet_buy_7", + "directions": 4 + }, { "name": "cargo_pallet_sell" + }, + { + "name": "cargo_pallet_sell_0", + "directions": 4 + }, + { + "name": "cargo_pallet_sell_1", + "directions": 4 + }, + { + "name": "cargo_pallet_sell_2", + "directions": 4 + }, + { + "name": "cargo_pallet_sell_3", + "directions": 4 + }, + { + "name": "cargo_pallet_sell_4", + "directions": 4 + }, + { + "name": "cargo_pallet_sell_5", + "directions": 4 + }, + { + "name": "cargo_pallet_sell_6", + "directions": 4 + }, + { + "name": "cargo_pallet_sell_7", + "directions": 4 } ] } diff --git a/Resources/Textures/Tiles/Misc/floortrap.rsi/floortrap.png b/Resources/Textures/Tiles/Misc/floortrap.rsi/floortrap.png new file mode 100644 index 0000000000..391437064e Binary files /dev/null and b/Resources/Textures/Tiles/Misc/floortrap.rsi/floortrap.png differ diff --git a/Resources/Textures/Tiles/Misc/floortrap.rsi/floortrapspawn.png b/Resources/Textures/Tiles/Misc/floortrap.rsi/floortrapspawn.png new file mode 100644 index 0000000000..764a0fed15 Binary files /dev/null and b/Resources/Textures/Tiles/Misc/floortrap.rsi/floortrapspawn.png differ diff --git a/Resources/Textures/Tiles/Misc/floortrap.rsi/meta.json b/Resources/Textures/Tiles/Misc/floortrap.rsi/meta.json new file mode 100644 index 0000000000..586fad6d23 --- /dev/null +++ b/Resources/Textures/Tiles/Misc/floortrap.rsi/meta.json @@ -0,0 +1,17 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Made by Nimfar11 (github) for ss14", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "floortrap" + }, + { + "name": "floortrapspawn" + } + ] +} diff --git a/Resources/Textures/Tips/tippy.rsi/down.png b/Resources/Textures/Tips/tippy.rsi/down.png new file mode 100644 index 0000000000..bdfcf315b6 Binary files /dev/null and b/Resources/Textures/Tips/tippy.rsi/down.png differ diff --git a/Resources/Textures/Tips/tippy.rsi/left.png b/Resources/Textures/Tips/tippy.rsi/left.png new file mode 100644 index 0000000000..f2293c6111 Binary files /dev/null and b/Resources/Textures/Tips/tippy.rsi/left.png differ diff --git a/Resources/Textures/Tips/tippy.rsi/meta.json b/Resources/Textures/Tips/tippy.rsi/meta.json new file mode 100644 index 0000000000..68942d731c --- /dev/null +++ b/Resources/Textures/Tips/tippy.rsi/meta.json @@ -0,0 +1,20 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "down" + }, + { + "name": "left" + }, + { + "name": "right" + } + ] +} diff --git a/Resources/Textures/Tips/tippy.rsi/right.png b/Resources/Textures/Tips/tippy.rsi/right.png new file mode 100644 index 0000000000..900262932d Binary files /dev/null and b/Resources/Textures/Tips/tippy.rsi/right.png differ diff --git a/Resources/engineCommandPerms.yml b/Resources/engineCommandPerms.yml index 51743c6e82..42cc4668a9 100644 --- a/Resources/engineCommandPerms.yml +++ b/Resources/engineCommandPerms.yml @@ -96,6 +96,8 @@ - tp - tpto - respawn + - tippy + - tip - Flags: SERVER Commands: diff --git a/Resources/keybinds.yml b/Resources/keybinds.yml index 2cca749317..904190fcf5 100644 --- a/Resources/keybinds.yml +++ b/Resources/keybinds.yml @@ -70,21 +70,27 @@ binds: - function: CameraRotateLeft type: State key: NumpadNum7 + mod1: Control - function: CameraRotateRight type: State key: NumpadNum9 + mod1: Control - function: CameraReset type: State key: NumpadNum8 + mod1: Control - function: ZoomOut type: State key: NumpadNum4 + mod1: Control - function: ZoomIn type: State key: NumpadNum6 + mod1: Control - function: ResetZoom type: State key: NumpadNum5 + mod1: Control # Misc - function: ShowEscapeMenu type: State @@ -187,6 +193,9 @@ binds: - function: OpenCharacterMenu type: State key: C +- function: OpenEmotesMenu + type: State + key: Y - function: OpenLanguageMenu type: State key: L @@ -264,6 +273,10 @@ binds: - function: ToggleStanding type: State key: R +- function: ToggleCrawlingUnder + type: State + mod1: Shift + key: R - function: ShowDebugConsole type: State key: Tilde @@ -461,6 +474,9 @@ binds: - function: OpenDecalSpawnWindow type: State key: F8 +- function: OpenScoreboardWindow + type: State + key: F9 - function: OpenSandboxWindow type: State key: B @@ -543,3 +559,25 @@ binds: - function: Hotbar9 type: State key: Num9 +- function: LookUp + type: State + key: Space +# Targeting +- function: TargetHead + type: State + key: NumpadNum8 +- function: TargetTorso + type: State + key: NumpadNum5 +- function: TargetLeftArm + type: State + key: NumpadNum6 +- function: TargetRightArm + type: State + key: NumpadNum4 +- function: TargetLeftLeg + type: State + key: NumpadNum3 +- function: TargetRightLeg + type: State + key: NumpadNum1 diff --git a/RobustToolbox b/RobustToolbox index eb63809999..a9aea7027f 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit eb638099999dce3a43d90772ca976ae010d649c0 +Subproject commit a9aea7027f1840c83bcaf1c973caf099745f9eed diff --git a/SECURITY.md b/SECURITY.md index 38197078a3..22bdbd9fbf 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,9 +1,8 @@ # Reporting a security vulnerability + You can report a security vulnerability through Discord or through email. -If you want to send an email, you can contact us at . -If you want to contact us through Discord, you can join [our server](https://discord.gg/MwDDf6t) -and then **privately** message anyone with the `@Wizard` or `@SS14 Maintainer` role. +If you want to send an email, you can contact us at . +If you want to contact us through Discord, you can join [our server](https://discord.gg/X4QEXxUrsJ) and then **privately** message anyone with the `@Maintainer` role. -In either case, **do not publicly disclose the vulnerability until we explicitly give -you permission to do so**. +In either case, **do not publicly disclose the vulnerability until we explicitly give you permission to do so**. diff --git a/SpaceStation14.sln.DotSettings b/SpaceStation14.sln.DotSettings index c12acc6e93..bb13808664 100644 --- a/SpaceStation14.sln.DotSettings +++ b/SpaceStation14.sln.DotSettings @@ -1,6 +1,14 @@  False False + HINT + SUGGESTION + SUGGESTION + SUGGESTION + SUGGESTION + SUGGESTION + WARNING + WARNING WARNING WARNING WARNING @@ -34,27 +42,63 @@ WARNING WARNING WARNING - Required - Required - Required - Required - RequiredForMultiline - Required - Required - Required + WARNING + NotRequired + NotRequired + NotRequired + NotRequired + NotRequiredForBoth + NotRequired + NotRequired + NotRequired + ExpressionBody + Join + ExpressionBody + TargetTyped + True + False NEXT_LINE NEXT_LINE + False + False NEXT_LINE + 2 + 2 + 2 + 1 NEXT_LINE + TOGETHER_SAME_LINE + True + True + True + True + True + True + USUAL_INDENT + USUAL_INDENT INDENT NEXT_LINE NEXT_LINE + False + False + True + False + True NEXT_LINE + IF_OWNER_IS_SINGLE_LINE + NEVER + False NEXT_LINE + True + True + True + True + False AABB AL BB CC + FTL GC GD GL @@ -654,6 +698,7 @@ public sealed partial class $CLASS$ : Shared$CLASS$ { True True True + True True True True diff --git a/Tools/SS14 Aseprite Plugins/Displacement Map Flip.lua b/Tools/SS14 Aseprite Plugins/Displacement Map Flip.lua new file mode 100644 index 0000000000..3291685071 --- /dev/null +++ b/Tools/SS14 Aseprite Plugins/Displacement Map Flip.lua @@ -0,0 +1,78 @@ +local sprite = app.editor.sprite +local cel = app.cel + +if sprite.selection.isEmpty then + print("You need to select something sorry") + return +end + +local diag = Dialog{ + title = "Flip Displacement Map" +} + +diag:check{ + id = "horizontal", + label = "flip horizontal?" +} + +diag:check{ + id = "vertical", + label = "flip vertical?" +} + +diag:button{ + text = "ok", + focus = true, + onclick = function(ev) + local horizontal = diag.data["horizontal"] + local vertical = diag.data["vertical"] + + local selection = sprite.selection + local image = cel.image:clone() + + for x = 0, selection.bounds.width do + for y = 0, selection.bounds.height do + local xSel = x + selection.origin.x + local ySel = y + selection.origin.y + + local xImg = xSel - cel.position.x + local yImg = ySel - cel.position.y + + if xImg < 0 or xImg >= image.width or yImg < 0 or yImg >= image.height then + goto continue + end + + local imgValue = image:getPixel(xImg, yImg) + local color = Color(imgValue) + + if horizontal then + color.red = 128 + -(color.red - 128) + end + + if vertical then + color.green = 128 + -(color.green - 128) + end + + image:drawPixel( + xImg, + yImg, + app.pixelColor.rgba(color.red, color.green, color.blue, color.alpha)) + + ::continue:: + end + end + + cel.image = image + + diag:close() + end +} + +diag:button{ + text = "cancel", + onclick = function(ev) + diag:close() + end +} + +diag:show() diff --git a/Tools/SS14 Aseprite Plugins/Displacement Map Visualizer.lua b/Tools/SS14 Aseprite Plugins/Displacement Map Visualizer.lua new file mode 100644 index 0000000000..468636c07d --- /dev/null +++ b/Tools/SS14 Aseprite Plugins/Displacement Map Visualizer.lua @@ -0,0 +1,171 @@ +-- Displacement Map Visualizer +-- +-- This script will create a little preview window that will test a displacement map. +-- +-- TODO: Handling of sizes != 127 doesn't work properly and rounds differently from the real shader. Ah well. + +local scale = 4 + +-- This script requires UI +if not app.isUIAvailable then + return +end + +local getOffsetPixel = function(x, y, image, rect) + local posX = x - rect.x + local posY = y - rect.y + + if posX < 0 or posX >= image.width or posY < 0 or posY >= image.height then + return image.spec.transparentColor + end + + return image:getPixel(posX, posY) +end + +local pixelValueToColor = function(sprite, value) + return Color(value) +end + +local applyDisplacementMap = function(width, height, size, displacement, displacementRect, target, targetRect) + -- print(Color(displacement:getPixel(17, 15)).red) + local image = target:clone() + image:resize(width, height) + image:clear() + + for x = 0, width - 1 do + for y = 0, height - 1 do + local value = getOffsetPixel(x, y, displacement, displacementRect) + local color = pixelValueToColor(sprite, value) + + if color.alpha ~= 0 then + local offset_x = (color.red - 128) / 127 * size + local offset_y = (color.green - 128) / 127 * size + + local colorValue = getOffsetPixel(x + offset_x, y + offset_y, target, targetRect) + image:drawPixel(x, y, colorValue) + end + end + end + + return image +end + +local dialog = nil + +local sprite = app.editor.sprite +local spriteChanged = sprite.events:on("change", + function(ev) + dialog:repaint() + end) + +local layers = {} +for i,layer in ipairs(sprite.layers) do + table.insert(layers, 1, layer.name) +end + +local findLayer = function(sprite, name) + for i, layer in ipairs(sprite.layers) do + if layer.name == name then + return layer + end + end + + return nil +end + +dialog = Dialog{ + title = "Displacement map preview", + onclose = function(ev) + sprite.events:off(spriteChanged) + end} + +dialog:canvas{ + id = "canvas", + width = sprite.width * scale, + height = sprite.height * scale, + onpaint = function(ev) + local context = ev.context + + local layerDisplacement = findLayer(sprite, dialog.data["displacement-select"]) + local layerTarget = findLayer(sprite, dialog.data["reference-select"]) + local layerBackground = findLayer(sprite, dialog.data["background-select"]) + -- print(layerDisplacement.name) + -- print(layerTarget.name) + + local celDisplacement = layerDisplacement:cel(1) + local celTarget = layerTarget:cel(1) + local celBackground = layerBackground:cel(1) + + -- Draw background + context:drawImage( + -- srcImage + celBackground.image, + -- srcPos + 0, 0, + -- srcSize + celBackground.image.width, celBackground.image.height, + -- dstPos + celBackground.position.x * scale, celBackground.position.y * scale, + -- dstSize + celBackground.image.width * scale, celBackground.image.height * scale) + + -- Apply displacement map and draw + local image = applyDisplacementMap( + sprite.width, sprite.height, + dialog.data["size"], + celDisplacement.image, celDisplacement.bounds, + celTarget.image, celTarget.bounds) + + context:drawImage( + -- srcImage + image, + -- srcPos + 0, 0, + -- srcSize + image.width, image.height, + -- dstPos + 0, 0, + -- dstSize + image.width * scale, image.height * scale) + end +} + +dialog:combobox{ + id = "displacement-select", + label = "displacement layer", + options = layers, + onchange = function(ev) + dialog:repaint() + end +} + +dialog:combobox{ + id = "reference-select", + label = "reference layer", + options = layers, + onchange = function(ev) + dialog:repaint() + end +} + +dialog:combobox{ + id = "background-select", + label = "background layer", + options = layers, + onchange = function(ev) + dialog:repaint() + end +} + +dialog:slider{ + id = "size", + label = "displacement size", + min = 1, + max = 127, + value = 127, + onchange = function(ev) + dialog:repaint() + end +} + +dialog:show{wait = false} diff --git a/Tools/SS14 Aseprite Plugins/Displacement Map.png b/Tools/SS14 Aseprite Plugins/Displacement Map.png new file mode 100644 index 0000000000..50744cef60 Binary files /dev/null and b/Tools/SS14 Aseprite Plugins/Displacement Map.png differ diff --git a/Tools/actions_changelogs_since_last_run.py b/Tools/actions_changelogs_since_last_run.py index e24e74c51f..35020b44aa 100755 --- a/Tools/actions_changelogs_since_last_run.py +++ b/Tools/actions_changelogs_since_last_run.py @@ -16,16 +16,15 @@ GITHUB_REPOSITORY = os.environ["GITHUB_REPOSITORY"] GITHUB_RUN = os.environ["GITHUB_RUN_ID"] GITHUB_TOKEN = os.environ["GITHUB_TOKEN"] +CHANGELOG_DIR = os.environ["CHANGELOG_DIR"] +CHANGELOG_WEBHOOK = os.environ["CHANGELOG_WEBHOOK"] # https://discord.com/developers/docs/resources/webhook DISCORD_SPLIT_LIMIT = 2000 -DISCORD_WEBHOOK_URL = os.environ.get("DISCORD_WEBHOOK_URL") - -CHANGELOG_FILE = "Resources/Changelog/DeltaVChangelog.yml" TYPES_TO_EMOJI = { "Fix": "🐛", - "Add": "🆕", + "Add": "✨", "Remove": "❌", "Tweak": "⚒️" } @@ -33,7 +32,7 @@ ChangelogEntry = dict[str, Any] def main(): - if not DISCORD_WEBHOOK_URL: + if not CHANGELOG_WEBHOOK: return session = requests.Session() @@ -45,7 +44,7 @@ def main(): last_sha = most_recent['head_commit']['id'] print(f"Last successful publish job was {most_recent['id']}: {last_sha}") last_changelog = yaml.safe_load(get_last_changelog(session, last_sha)) - with open(CHANGELOG_FILE, "r") as f: + with open(CHANGELOG_DIR, "r") as f: cur_changelog = yaml.safe_load(f) diff = diff_changelog(last_changelog, cur_changelog) @@ -93,7 +92,7 @@ def get_last_changelog(sess: requests.Session, sha: str) -> str: "Accept": "application/vnd.github.raw" } - resp = sess.get(f"{GITHUB_API_URL}/repos/{GITHUB_REPOSITORY}/contents/{CHANGELOG_FILE}", headers=headers, params=params) + resp = sess.get(f"{GITHUB_API_URL}/repos/{GITHUB_REPOSITORY}/contents/{CHANGELOG_DIR}", headers=headers, params=params) resp.raise_for_status() return resp.text @@ -108,25 +107,25 @@ def diff_changelog(old: dict[str, Any], cur: dict[str, Any]) -> Iterable[Changel def get_discord_body(content: str): return { - "content": content, - # Do not allow any mentions. - "allowed_mentions": { - "parse": [] - }, - # SUPPRESS_EMBEDS - "flags": 1 << 2 - } + "content": content, + # Do not allow any mentions. + "allowed_mentions": { + "parse": [] + }, + # SUPPRESS_EMBEDS + "flags": 1 << 2 + } def send_discord(content: str): body = get_discord_body(content) - response = requests.post(DISCORD_WEBHOOK_URL, json=body) + response = requests.post(CHANGELOG_WEBHOOK, json=body) response.raise_for_status() def send_to_discord(entries: Iterable[ChangelogEntry]) -> None: - if not DISCORD_WEBHOOK_URL: + if not CHANGELOG_WEBHOOK: print(f"No discord webhook URL found, skipping discord send") return @@ -138,7 +137,7 @@ def send_to_discord(entries: Iterable[ChangelogEntry]) -> None: for name, group in itertools.groupby(entries, lambda x: x["author"]): # Need to split text to avoid discord character limit group_content = io.StringIO() - group_content.write(f"**{name}** updated:\n") + group_content.write(f"## {name}:\n") for entry in group: for change in entry["changes"]: @@ -146,7 +145,7 @@ def send_to_discord(entries: Iterable[ChangelogEntry]) -> None: message = change['message'] url = entry.get("url") if url and url.strip(): - group_content.write(f"{emoji} [-]({url}) {message}\n") + group_content.write(f"{emoji} - [{message}]({url})\n") else: group_content.write(f"{emoji} - {message}\n") @@ -157,7 +156,7 @@ def send_to_discord(entries: Iterable[ChangelogEntry]) -> None: # If adding the text would bring it over the group limit then send the message and start a new one if message_length + group_length >= DISCORD_SPLIT_LIMIT: - print("Split changelog and sending to discord") + print("Split changelog and sending to discord") send_discord(message_text) # Reset the message @@ -165,7 +164,7 @@ def send_to_discord(entries: Iterable[ChangelogEntry]) -> None: # Flush the group to the message message_content.write(group_text) - + # Clean up anything remaining message_text = message_content.getvalue() if len(message_text) > 0: diff --git a/Tools/changelogs/changelog.js b/Tools/changelogs/changelog.js index 07a70f30fb..26e2014c9e 100644 --- a/Tools/changelogs/changelog.js +++ b/Tools/changelogs/changelog.js @@ -57,6 +57,7 @@ async function main() { changes: entries, id: getHighestCLNumber() + 1, time: time, + url: `https://github.com/${process.env.GITHUB_REPOSITORY}/pull/${process.env.PR_NUMBER}` }; // Write changelogs diff --git a/Tools/contribs_shared.ps1 b/Tools/contribs_shared.ps1 index ba97c50a9a..12340cda70 100644 --- a/Tools/contribs_shared.ps1 +++ b/Tools/contribs_shared.ps1 @@ -9,6 +9,10 @@ $ignore = @{ "PJBot" = $true + "github-actions[bot]" = $true "ZDDM" = $true "TYoung86" = $true + "paul" = $true # erroneously included -- presumably from PaulRitter, somehow, who is already credited + "08a" = $true # erroneously included -- valid github account, but not an actual contributor, probably an alias of a contributor who does not own this github account and is already credited somewhere. + "UristMcContributor" = $true # this was an account used to demonstrate how to create a valid PR, and is in actuality Willhelm53, who is already credited. } diff --git a/Tools/dump_github_contributors.ps1 b/Tools/dump_github_contributors.ps1 index 193eec4692..d88b9db612 100755 --- a/Tools/dump_github_contributors.ps1 +++ b/Tools/dump_github_contributors.ps1 @@ -3,10 +3,22 @@ $scriptDir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent . $(join-path $scriptDir contribs_shared.ps1) +if ($null -eq $env:GITHUB_TOKEN) +{ + throw "A GitHub API token is required to run this script properly without being rate limited. If you're a user, generate a personal access token and use that. If you're running this in a GitHub action, make sure you expose the GITHUB_TOKEN secret as an environment variable." +} + function load_contribs([string] $repo) { + # https://developer.github.com/enterprise/2.8/v3/repos/#list-contributors + # We use the ?anon=1 query param for reasons explained later. $qParams = @{ "per_page" = 100 + "anon" = 1 + } + + $headers = @{ + Authorization="Bearer $env:GITHUB_TOKEN" } $url = "https://api.github.com/repos/{0}/contributors" -f $repo @@ -15,7 +27,7 @@ function load_contribs([string] $repo) while ($null -ne $url) { - $resp = Invoke-WebRequest $url -Body $qParams + $resp = Invoke-WebRequest $url -Body $qParams -Headers $headers $url = $resp.RelationLink.next @@ -23,6 +35,80 @@ function load_contribs([string] $repo) $r += $j } + # After collecting all the paginated data, we still aren't done. + # GitHub's API, for some reason, has a hard cap on 500 email addresses per repo which it will collate + # SS14 has gone past this limit for quite some time, so GitHub will stop including accounts, starting + # with those that have lower contributions, as valid distinct users with a `login` field. + # + # This is obviously a problem. + # To remedy, we first use the ?anon=1 parameter to force GitHub to include all committers emails, even + # those that it has, in its great and infinite wisdom, chosen to not properly attach to a GitHub account. + # + # Of course, this is normally an issue -- we use this API specifically because we want to only get + # committers with valid GitHub accounts, otherwise we pollute the contributor log with random aliases + # and names that people don't use, things like that. + # + # So, okay, solution: + # 1) Go over our list, and check for ones which only have a `name` and `email` field ('anonymous' contributors) + # and which dont already appear. + # 2) Check to see if the email ends with `@users.noreply.github.com`. + # - To my knowledge, GitHub includes an email in the form of `(numbers)+(username)@users.noreply.github.com` + # - when commits are made using someones GitHub account, and they aren't attaching another email to their account + # 3) If an email of this form was found, we can assume this is one of the 'missing' contribs and extract their GitHub username. + # 4) If an email of this form -wasn't- found, but they're still anonymous, we -unfortunately- still have to check if they're a valid GitHub user + # because GitHub might have just force-anonymized them anyway! + # + # It's possible their `name` is a valid GitHub user, but that this is a coincidence and they aren't actually a contributor. + # There is kind of not really jack shit we can do about that! It's not that common though and it's probably more likely to attribute + # correctly than not. + # 5) Then, we just add a `login` field to our object with their true username and let the rest of the code do its job. + + foreach ($contributor in $r) + { + if ($null -ne $contributor.name ` + -And $null -ne $contributor.email ` + -And $contributor.email -match '\d+\+(.*)@users\.noreply\.github\.com$') + { + $username = $Matches.1 + # Use their `name` if its equivalent to the extracted username, + # since that one will have proper casing. Otherwise just let them be a lowercasecel + if ($contributor.name.ToLower() -eq $username) + { + $username = $contributor.name + } + + if (($r).login -contains $username) + { + continue + } + + $contributor | Add-Member -MemberType NoteProperty -Name "login" -Value $username + } + elseif ($null -eq $contributor.login ` + -And $null -ne $contributor.name ` + -And !$contributor.name.Contains(" ")) + { + $username = $contributor.name + # They're an anonymous user, without a GH email, and their name doesn't contain a space + # (since a valid GH username can't have a space) + # Might still be a valid contrib??? + if (($r).login -contains $username) + { + continue + } + + $userUrl = "https://api.github.com/users/{0}" -f $username + + try + { + $userResp = Invoke-WebRequest $userUrl -Headers $headers + $userJ = ConvertFrom-Json $userResp.Content + $contributor | Add-Member -MemberType NoteProperty -Name "login" -Value $userJ.login + } + catch {} # if it 404s do nothing. powershell doesn't seem to really have a simpler way to do this. + } + } + return $r } @@ -34,4 +120,4 @@ $contentJson = load_contribs("Simple-Station/Einstein-Engines") | Where-Object { -not $ignore[$_] }` | ForEach-Object { if($replacements[$_] -eq $null){ $_ } else { $replacements[$_] }} ` | Sort-object ` - | Join-String -Separator ", " + | Join-String -Separator ", " \ No newline at end of file diff --git a/Tools/publish_multi_request.py b/Tools/publish_multi_request.py new file mode 100755 index 0000000000..131d1f7f76 --- /dev/null +++ b/Tools/publish_multi_request.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 + +import requests +import os +import subprocess +from typing import Iterable + +PUBLISH_TOKEN = os.environ["PUBLISH_TOKEN"] +VERSION = os.environ["GITHUB_SHA"] + +RELEASE_DIR = "release" + +# +# CONFIGURATION PARAMETERS +# Forks should change these to publish to their own infrastructure. +# +ROBUST_CDN_URL = "https://cdn.simplestation.org/" +FORK_ID = "einstein-engines" + +def main(): + session = requests.Session() + session.headers = { + "Authorization": f"Bearer {PUBLISH_TOKEN}", + } + + print(f"Starting publish on Robust.Cdn for version {VERSION}") + + data = { + "version": VERSION, + "engineVersion": get_engine_version(), + } + headers = { + "Content-Type": "application/json" + } + resp = session.post(f"{ROBUST_CDN_URL}fork/{FORK_ID}/publish/start", json=data, headers=headers) + resp.raise_for_status() + print("Publish successfully started, adding files...") + + for file in get_files_to_publish(): + print(f"Publishing {file}") + with open(file, "rb") as f: + headers = { + "Content-Type": "application/octet-stream", + "Robust-Cdn-Publish-File": os.path.basename(file), + "Robust-Cdn-Publish-Version": VERSION + } + resp = session.post(f"{ROBUST_CDN_URL}fork/{FORK_ID}/publish/file", data=f, headers=headers) + + resp.raise_for_status() + + print("Successfully pushed files, finishing publish...") + + data = { + "version": VERSION + } + headers = { + "Content-Type": "application/json" + } + resp = session.post(f"{ROBUST_CDN_URL}fork/{FORK_ID}/publish/finish", json=data, headers=headers) + resp.raise_for_status() + + print("SUCCESS!") + + +def get_files_to_publish() -> Iterable[str]: + for file in os.listdir(RELEASE_DIR): + yield os.path.join(RELEASE_DIR, file) + + +def get_engine_version() -> str: + proc = subprocess.run(["git", "describe","--tags", "--abbrev=0"], stdout=subprocess.PIPE, cwd="RobustToolbox", check=True, encoding="UTF-8") + tag = proc.stdout.strip() + assert tag.startswith("v") + return tag[1:] # Cut off v prefix. + + +if __name__ == '__main__': + main() diff --git a/shell.nix b/shell.nix index 57d64e0071..ce17c6acea 100644 --- a/shell.nix +++ b/shell.nix @@ -51,5 +51,7 @@ in pkgs.mkShell { export ROBUST_SOUNDFONT_OVERRIDE=${pkgs.soundfont-fluid}/share/soundfonts/FluidR3_GM2-2.sf2 export XDG_DATA_DIRS=$GSETTINGS_SCHEMAS_PATH export LD_LIBRARY_PATH=${pkgs.lib.makeLibraryPath dependencies} + export DOTNET_ROOT=${pkgs.dotnetCorePackages.sdk_8_0_1xx} + export PATH="$PATH:/home/$(whoami)/.dotnet/tools" ''; }